1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
|
//===- IntegerRangeAnalysis.cpp - Integer range analysis --------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the dataflow analysis class for integer range inference
// which is used in transformations over the `arith` dialect such as
// branch elimination or signed->unsigned rewriting
//
//===----------------------------------------------------------------------===//
#include "mlir/Analysis/DataFlow/IntegerRangeAnalysis.h"
#include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h"
#include "mlir/Interfaces/InferIntRangeInterface.h"
#include "mlir/Interfaces/LoopLikeInterface.h"
#include "llvm/Support/Debug.h"
#include <optional>
#define DEBUG_TYPE "int-range-analysis"
using namespace mlir;
using namespace mlir::dataflow;
IntegerValueRange IntegerValueRange::getMaxRange(Value value) {
unsigned width = ConstantIntRanges::getStorageBitwidth(value.getType());
if (width == 0)
return {};
APInt umin = APInt::getMinValue(width);
APInt umax = APInt::getMaxValue(width);
APInt smin = width != 0 ? APInt::getSignedMinValue(width) : umin;
APInt smax = width != 0 ? APInt::getSignedMaxValue(width) : umax;
return IntegerValueRange{ConstantIntRanges{umin, umax, smin, smax}};
}
void IntegerValueRangeLattice::onUpdate(DataFlowSolver *solver) const {
Lattice::onUpdate(solver);
// If the integer range can be narrowed to a constant, update the constant
// value of the SSA value.
std::optional<APInt> constant = getValue().getValue().getConstantValue();
auto value = point.get<Value>();
auto *cv = solver->getOrCreateState<Lattice<ConstantValue>>(value);
if (!constant)
return solver->propagateIfChanged(
cv, cv->join(ConstantValue::getUnknownConstant()));
Dialect *dialect;
if (auto *parent = value.getDefiningOp())
dialect = parent->getDialect();
else
dialect = value.getParentBlock()->getParentOp()->getDialect();
solver->propagateIfChanged(
cv, cv->join(ConstantValue(IntegerAttr::get(value.getType(), *constant),
dialect)));
}
void IntegerRangeAnalysis::visitOperation(
Operation *op, ArrayRef<const IntegerValueRangeLattice *> operands,
ArrayRef<IntegerValueRangeLattice *> results) {
// If the lattice on any operand is unitialized, bail out.
if (llvm::any_of(operands, [](const IntegerValueRangeLattice *lattice) {
return lattice->getValue().isUninitialized();
})) {
return;
}
// Ignore non-integer outputs - return early if the op has no scalar
// integer results
bool hasIntegerResult = false;
for (auto it : llvm::zip(results, op->getResults())) {
Value value = std::get<1>(it);
if (value.getType().isIntOrIndex()) {
hasIntegerResult = true;
} else {
IntegerValueRangeLattice *lattice = std::get<0>(it);
propagateIfChanged(lattice,
lattice->join(IntegerValueRange::getMaxRange(value)));
}
}
if (!hasIntegerResult)
return;
auto inferrable = dyn_cast<InferIntRangeInterface>(op);
if (!inferrable)
return setAllToEntryStates(results);
LLVM_DEBUG(llvm::dbgs() << "Inferring ranges for " << *op << "\n");
SmallVector<ConstantIntRanges> argRanges(
llvm::map_range(operands, [](const IntegerValueRangeLattice *val) {
return val->getValue().getValue();
}));
auto joinCallback = [&](Value v, const ConstantIntRanges &attrs) {
auto result = dyn_cast<OpResult>(v);
if (!result)
return;
assert(llvm::is_contained(op->getResults(), result));
LLVM_DEBUG(llvm::dbgs() << "Inferred range " << attrs << "\n");
IntegerValueRangeLattice *lattice = results[result.getResultNumber()];
IntegerValueRange oldRange = lattice->getValue();
ChangeResult changed = lattice->join(IntegerValueRange{attrs});
// Catch loop results with loop variant bounds and conservatively make
// them [-inf, inf] so we don't circle around infinitely often (because
// the dataflow analysis in MLIR doesn't attempt to work out trip counts
// and often can't).
bool isYieldedResult = llvm::any_of(v.getUsers(), [](Operation *op) {
return op->hasTrait<OpTrait::IsTerminator>();
});
if (isYieldedResult && !oldRange.isUninitialized() &&
!(lattice->getValue() == oldRange)) {
LLVM_DEBUG(llvm::dbgs() << "Loop variant loop result detected\n");
changed |= lattice->join(IntegerValueRange::getMaxRange(v));
}
propagateIfChanged(lattice, changed);
};
inferrable.inferResultRanges(argRanges, joinCallback);
}
void IntegerRangeAnalysis::visitNonControlFlowArguments(
Operation *op, const RegionSuccessor &successor,
ArrayRef<IntegerValueRangeLattice *> argLattices, unsigned firstIndex) {
if (auto inferrable = dyn_cast<InferIntRangeInterface>(op)) {
LLVM_DEBUG(llvm::dbgs() << "Inferring ranges for " << *op << "\n");
// If the lattice on any operand is unitialized, bail out.
if (llvm::any_of(op->getOperands(), [&](Value value) {
return getLatticeElementFor(op, value)->getValue().isUninitialized();
}))
return;
SmallVector<ConstantIntRanges> argRanges(
llvm::map_range(op->getOperands(), [&](Value value) {
return getLatticeElementFor(op, value)->getValue().getValue();
}));
auto joinCallback = [&](Value v, const ConstantIntRanges &attrs) {
auto arg = dyn_cast<BlockArgument>(v);
if (!arg)
return;
if (!llvm::is_contained(successor.getSuccessor()->getArguments(), arg))
return;
LLVM_DEBUG(llvm::dbgs() << "Inferred range " << attrs << "\n");
IntegerValueRangeLattice *lattice = argLattices[arg.getArgNumber()];
IntegerValueRange oldRange = lattice->getValue();
ChangeResult changed = lattice->join(IntegerValueRange{attrs});
// Catch loop results with loop variant bounds and conservatively make
// them [-inf, inf] so we don't circle around infinitely often (because
// the dataflow analysis in MLIR doesn't attempt to work out trip counts
// and often can't).
bool isYieldedValue = llvm::any_of(v.getUsers(), [](Operation *op) {
return op->hasTrait<OpTrait::IsTerminator>();
});
if (isYieldedValue && !oldRange.isUninitialized() &&
!(lattice->getValue() == oldRange)) {
LLVM_DEBUG(llvm::dbgs() << "Loop variant loop result detected\n");
changed |= lattice->join(IntegerValueRange::getMaxRange(v));
}
propagateIfChanged(lattice, changed);
};
inferrable.inferResultRanges(argRanges, joinCallback);
return;
}
/// Given the results of getConstant{Lower,Upper}Bound() or getConstantStep()
/// on a LoopLikeInterface return the lower/upper bound for that result if
/// possible.
auto getLoopBoundFromFold = [&](std::optional<OpFoldResult> loopBound,
Type boundType, bool getUpper) {
unsigned int width = ConstantIntRanges::getStorageBitwidth(boundType);
if (loopBound.has_value()) {
if (loopBound->is<Attribute>()) {
if (auto bound =
dyn_cast_or_null<IntegerAttr>(loopBound->get<Attribute>()))
return bound.getValue();
} else if (auto value = llvm::dyn_cast_if_present<Value>(*loopBound)) {
const IntegerValueRangeLattice *lattice =
getLatticeElementFor(op, value);
if (lattice != nullptr)
return getUpper ? lattice->getValue().getValue().smax()
: lattice->getValue().getValue().smin();
}
}
// Given the results of getConstant{Lower,Upper}Bound()
// or getConstantStep() on a LoopLikeInterface return the lower/upper
// bound
return getUpper ? APInt::getSignedMaxValue(width)
: APInt::getSignedMinValue(width);
};
// Infer bounds for loop arguments that have static bounds
if (auto loop = dyn_cast<LoopLikeOpInterface>(op)) {
std::optional<Value> iv = loop.getSingleInductionVar();
if (!iv) {
return SparseDataFlowAnalysis ::visitNonControlFlowArguments(
op, successor, argLattices, firstIndex);
}
std::optional<OpFoldResult> lowerBound = loop.getSingleLowerBound();
std::optional<OpFoldResult> upperBound = loop.getSingleUpperBound();
std::optional<OpFoldResult> step = loop.getSingleStep();
APInt min = getLoopBoundFromFold(lowerBound, iv->getType(),
/*getUpper=*/false);
APInt max = getLoopBoundFromFold(upperBound, iv->getType(),
/*getUpper=*/true);
// Assume positivity for uniscoverable steps by way of getUpper = true.
APInt stepVal =
getLoopBoundFromFold(step, iv->getType(), /*getUpper=*/true);
if (stepVal.isNegative()) {
std::swap(min, max);
} else {
// Correct the upper bound by subtracting 1 so that it becomes a <=
// bound, because loops do not generally include their upper bound.
max -= 1;
}
IntegerValueRangeLattice *ivEntry = getLatticeElement(*iv);
auto ivRange = ConstantIntRanges::fromSigned(min, max);
propagateIfChanged(ivEntry, ivEntry->join(IntegerValueRange{ivRange}));
return;
}
return SparseDataFlowAnalysis::visitNonControlFlowArguments(
op, successor, argLattices, firstIndex);
}
|