MLIR 23.0.0git
IntegerRangeAnalysis.cpp
Go to the documentation of this file.
1//===- IntegerRangeAnalysis.cpp - Integer range analysis --------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the dataflow analysis class for integer range inference
10// which is used in transformations over the `arith` dialect such as
11// branch elimination or signed->unsigned rewriting
12//
13//===----------------------------------------------------------------------===//
14
19#include "mlir/IR/Dialect.h"
21#include "mlir/IR/Operation.h"
24#include "mlir/IR/Value.h"
29#include "mlir/Support/LLVM.h"
30#include "llvm/ADT/STLExtras.h"
31#include "llvm/Support/Casting.h"
32#include "llvm/Support/Debug.h"
33#include "llvm/Support/DebugLog.h"
34#include <cassert>
35#include <optional>
36#include <utility>
37
38#define DEBUG_TYPE "int-range-analysis"
39
40using namespace mlir;
41using namespace mlir::dataflow;
42
43namespace mlir::dataflow {
44LogicalResult staticallyNonNegative(DataFlowSolver &solver, Value v) {
46 if (!result || result->getValue().isUninitialized())
47 return failure();
48 const ConstantIntRanges &range = result->getValue().getValue();
49 return success(range.smin().isNonNegative());
50}
51
52LogicalResult staticallyNonNegative(DataFlowSolver &solver, Operation *op) {
53 auto nonNegativePred = [&solver](Value v) -> bool {
54 return succeeded(staticallyNonNegative(solver, v));
55 };
56 return success(llvm::all_of(op->getOperands(), nonNegativePred) &&
57 llvm::all_of(op->getResults(), nonNegativePred));
58}
59} // namespace mlir::dataflow
60
61/// Number of merge-site joins a single integer-range lattice element is
62/// allowed to absorb before `IntegerValueRangeLattice::join` forces it to
63/// its max as a sound over-approximation.
64///
65/// Trade-off: high enough that realistic loops with dynamic bounds (which
66/// typically converge to a tight range in a small number of merge
67/// iterations) are not widened prematurely; low enough that the +1
68/// ratchet pathology this widening exists to cut off (loop-carried ranges
69/// growing by one per worklist visit) terminates after at most this many
70/// extra solver iterations rather than ~2^31.
71static constexpr unsigned kIntegerRangeWideningBudget = 128;
72
75 if (mergeChangeCount >= kIntegerRangeWideningBudget) {
77 cast<Value>(getAnchor())));
78 }
79 if (changed == ChangeResult::Change)
80 ++mergeChangeCount;
81 return changed;
82}
83
87 auto inferrable = dyn_cast<InferIntRangeInterface>(op);
88 if (!inferrable) {
89 setAllToEntryStates(results);
90 return success();
91 }
92
93 LDBG() << "Inferring ranges for "
94 << OpWithFlags(op, OpPrintingFlags().skipRegions());
95 auto argRanges = llvm::map_to_vector(
96 operands, [](const IntegerValueRangeLattice *lattice) {
97 return lattice->getValue();
98 });
99
100 auto joinCallback = [&](Value v, const IntegerValueRange &attrs) {
101 auto result = dyn_cast<OpResult>(v);
102 if (!result)
103 return;
104 assert(llvm::is_contained(op->getResults(), result));
105
106 LDBG() << "Inferred range " << attrs;
107 IntegerValueRangeLattice *lattice = results[result.getResultNumber()];
108 propagateIfChanged(lattice, lattice->join(attrs));
109 };
110
111 inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
112 return success();
113}
114
116 Operation *op, const RegionSuccessor &successor,
117 ValueRange nonSuccessorInputs,
118 ArrayRef<IntegerValueRangeLattice *> nonSuccessorInputLattices) {
119 assert(nonSuccessorInputs.size() == nonSuccessorInputLattices.size() &&
120 "size mismatch");
121 if (auto inferrable = dyn_cast<InferIntRangeInterface>(op)) {
122 LDBG() << "Inferring ranges for "
123 << OpWithFlags(op, OpPrintingFlags().skipRegions());
124
125 auto argRanges = llvm::map_to_vector(op->getOperands(), [&](Value value) {
126 return getLatticeElementFor(getProgramPointAfter(op), value)->getValue();
127 });
128
129 auto joinCallback = [&](Value v, const IntegerValueRange &attrs) {
130 auto arg = dyn_cast<BlockArgument>(v);
131 if (!arg)
132 return;
133 if (!llvm::is_contained(successor.getSuccessor()->getArguments(), arg))
134 return;
135
136 LDBG() << "Inferred range " << attrs;
137 auto it = llvm::find(successor.getSuccessor()->getArguments(), arg);
138 unsigned nonSuccessorInputIdx =
139 std::distance(successor.getSuccessor()->getArguments().begin(), it);
140 IntegerValueRangeLattice *lattice =
141 nonSuccessorInputLattices[nonSuccessorInputIdx];
142 propagateIfChanged(lattice, lattice->join(attrs));
143 };
144
145 inferrable.inferResultRangesFromOptional(argRanges, joinCallback);
146 return;
147 }
148
149 /// Given a lower bound, upper bound, or step from a LoopLikeInterface return
150 /// the lower/upper bound for that result if possible.
151 auto getLoopBoundFromFold = [&](OpFoldResult loopBound, Type boundType,
152 Block *block, bool getUpper) {
153 unsigned int width = ConstantIntRanges::getStorageBitwidth(boundType);
154 if (auto attr = dyn_cast<Attribute>(loopBound)) {
155 if (auto bound = dyn_cast<IntegerAttr>(attr))
156 return bound.getValue();
157 } else if (auto value = llvm::dyn_cast<Value>(loopBound)) {
158 const IntegerValueRangeLattice *lattice =
160 if (lattice != nullptr && !lattice->getValue().isUninitialized())
161 return getUpper ? lattice->getValue().getValue().smax()
162 : lattice->getValue().getValue().smin();
163 }
164 // Given the results of getConstant{Lower,Upper}Bound()
165 // or getConstantStep() on a LoopLikeInterface return the lower/upper
166 // bound
167 return getUpper ? APInt::getSignedMaxValue(width)
168 : APInt::getSignedMinValue(width);
169 };
170
171 // Infer bounds for loop arguments that have static bounds
172 if (auto loop = dyn_cast<LoopLikeOpInterface>(op)) {
173 std::optional<llvm::SmallVector<Value>> maybeIvs =
174 loop.getLoopInductionVars();
175 if (!maybeIvs) {
176 return SparseForwardDataFlowAnalysis ::visitNonControlFlowArguments(
177 op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
178 }
179 // Some loop implementations may return nullopt for non-constant bounds
180 // (e.g. affine.for with a dynamic upper bound), even when induction
181 // variables exist. Fall back to the generic analysis in that case.
182 std::optional<SmallVector<OpFoldResult>> maybeLowerBounds =
183 loop.getLoopLowerBounds();
184 std::optional<SmallVector<OpFoldResult>> maybeUpperBounds =
185 loop.getLoopUpperBounds();
186 std::optional<SmallVector<OpFoldResult>> maybeSteps = loop.getLoopSteps();
187 if (!maybeLowerBounds || !maybeUpperBounds || !maybeSteps) {
189 op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
190 }
191 SmallVector<OpFoldResult> lowerBounds = *maybeLowerBounds;
192 SmallVector<OpFoldResult> upperBounds = *maybeUpperBounds;
193 SmallVector<OpFoldResult> steps = *maybeSteps;
194 for (auto [iv, lowerBound, upperBound, step] :
195 llvm::zip_equal(*maybeIvs, lowerBounds, upperBounds, steps)) {
196 Block *block = iv.getParentBlock();
197 APInt min = getLoopBoundFromFold(lowerBound, iv.getType(), block,
198 /*getUpper=*/false);
199 APInt max = getLoopBoundFromFold(upperBound, iv.getType(), block,
200 /*getUpper=*/true);
201 // Assume positivity for uniscoverable steps by way of getUpper = true.
202 APInt stepVal =
203 getLoopBoundFromFold(step, iv.getType(), block, /*getUpper=*/true);
204
205 if (stepVal.isNegative()) {
206 std::swap(min, max);
207 } else {
208 // Correct the upper bound by subtracting 1 so that it becomes a <=
209 // bound, because loops do not generally include their upper bound.
210 max -= 1;
211 }
212
213 // If we infer the lower bound to be larger than the upper bound, the
214 // resulting range is meaningless and should not be used in further
215 // inferences.
216 if (max.sge(min)) {
218 auto ivRange = ConstantIntRanges::fromSigned(min, max);
219 propagateIfChanged(ivEntry, ivEntry->join(IntegerValueRange{ivRange}));
220 }
221 }
222 return;
223 }
224
226 op, successor, nonSuccessorInputs, nonSuccessorInputLattices);
227}
return success()
static constexpr unsigned kIntegerRangeWideningBudget
Number of merge-site joins a single integer-range lattice element is allowed to absorb before Integer...
static Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static Value min(ImplicitLocOpBuilder &builder, Value value, Value bound)
Block represents an ordered list of Operations.
Definition Block.h:33
A set of arbitrary-precision integers representing bounds on a given integer value.
const APInt & smax() const
The maximum value of an integer when it is interpreted as signed.
const APInt & smin() const
The minimum value of an integer when it is interpreted as signed.
static ConstantIntRanges fromSigned(const APInt &smin, const APInt &smax)
Create an ConstantIntRanges with the signed minimum and maximum equal to smin and smax,...
static unsigned getStorageBitwidth(Type type)
Return the bitwidth that should be used for integer ranges describing type.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
The general data-flow analysis solver.
const StateT * lookupState(AnchorT anchor) const
Lookup an analysis state for the given lattice anchor.
This lattice value represents the integer range of an SSA value.
const ConstantIntRanges & getValue() const
Get the known integer value range.
bool isUninitialized() const
Whether the range is uninitialized.
static IntegerValueRange getMaxRange(Value value)
Create a maximal range ([0, uint_max(t)] / [int_min(t), int_max(t)]) range that is used to mark the v...
This class represents a single result from folding an operation.
Set of flags used to control the behavior of the various IR print methods (e.g.
A wrapper class that allows for printing an operation with a set of flags, useful to act as a "stream...
Definition Operation.h:1143
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:404
result_range getResults()
Definition Operation.h:441
This class represents a successor of a region.
Region * getSuccessor() const
Return the given region successor.
BlockArgListType getArguments()
Definition Region.h:81
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
void visitNonControlFlowArguments(Operation *op, const RegionSuccessor &successor, ValueRange nonSuccessorInputs, ArrayRef< IntegerValueRangeLattice * > nonSuccessorInputLattices) override
Visit block arguments or operation results of an operation with region control-flow for which values ...
LogicalResult visitOperation(Operation *op, ArrayRef< const IntegerValueRangeLattice * > operands, ArrayRef< IntegerValueRangeLattice * > results) override
Visit an operation.
This lattice element represents the integer value range of an SSA value.
ChangeResult join(const AbstractSparseLattice &rhs) override
Join the information contained in 'rhs' into this lattice.
ValueT & getValue()
Return the value held by this lattice.
ChangeResult join(const AbstractSparseLattice &rhs) override
Join the information contained in the 'rhs' lattice into this lattice.
IntegerValueRangeLattice * getLatticeElement(Value value) override
void setAllToEntryStates(ArrayRef< IntegerValueRangeLattice * > lattices)
virtual void visitNonControlFlowArguments(Operation *op, const RegionSuccessor &successor, ValueRange nonSuccessorInputs, ArrayRef< StateT * > nonSuccessorInputLattices)
Given an operation with possible region control-flow, the lattices of the operands,...
const IntegerValueRangeLattice * getLatticeElementFor(ProgramPoint *point, Value value)
LogicalResult staticallyNonNegative(DataFlowSolver &solver, Operation *op)
Succeeds if an op can be converted to its unsigned equivalent without changing its semantics.
Include the generated interface declarations.
ChangeResult
A result type used to indicate if a change happened.