15#include "llvm/ADT/APSInt.h"
16#include "llvm/ADT/SmallVectorExtras.h"
17#include "llvm/Support/Debug.h"
18#include "llvm/Support/DebugLog.h"
22#define DEBUG_TYPE "value-bounds-op-interface"
29#include "mlir/Interfaces/ValueBoundsOpInterface.cpp.inc"
34 return cstr.populateAndCompare(
41 return cstr.populateAndCompare(
48 return cstr.populateAndCompare(
55 return cstr.populateAndCompare(
61 if (
auto bbArg = dyn_cast<BlockArgument>(value))
69 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
70 assert(offsets.size() == sizes.size() &&
71 "expected same number of offsets, sizes, strides");
72 assert(offsets.size() == strides.size() &&
73 "expected same number of offsets, sizes, strides");
78 : mixedOffsets(offsets), mixedSizes(sizes) {
79 assert(offsets.size() == sizes.size() &&
80 "expected same number of offsets and sizes");
85 mixedStrides.append(offsets.size(),
Builder(ctx).getIndexAttr(1));
95 if (
auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
98 return intVal.getSExtValue();
102 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
103 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
104 return intAttr.getValue().getSExtValue();
127 std::optional<int64_t> dim) {
130 assert(!dim &&
"expected no dim for index/integer-typed values");
132 b.getAffineConstantExpr(*constInt));
135 Value value = cast<Value>(ofr);
138 assert(isa<ShapedType>(value.
getType()) &&
"expected shaped type");
141 "expected index or integer type");
145 b.getAffineSymbolExpr(0));
146 mapOperands.emplace_back(value, dim);
151 assert(map.getNumResults() == 1 &&
"expected single result");
158 for (
int64_t i = 0, e = map.getNumDims(); i < e; ++i)
159 dimReplacements.push_back(
b.getAffineSymbolExpr(i));
160 for (
int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
161 symReplacements.push_back(
b.getAffineSymbolExpr(i + map.getNumDims()));
163 dimReplacements, symReplacements, 0,
164 map.getNumSymbols() + map.getNumDims());
168 for (
auto [
index, var] : llvm::enumerate(mapOperands)) {
169 assert(var.map.getNumResults() == 1 &&
"expected single result");
170 assert(var.map.getNumDims() == 0 &&
"expected only symbols");
172 for (
auto valueDim : var.mapOperands) {
173 auto *it = llvm::find(this->mapOperands, valueDim);
174 if (it != this->mapOperands.end()) {
176 symReplacements.push_back(
b.getAffineSymbolExpr(
177 std::distance(this->mapOperands.begin(), it)));
180 symReplacements.push_back(
181 b.getAffineSymbolExpr(this->mapOperands.size()));
182 this->mapOperands.push_back(valueDim);
185 replacements[
b.getAffineSymbolExpr(
index)] =
186 var.map.getResult(0).replaceSymbols(symReplacements);
188 this->map = tmpMap.
replace(replacements, 0,
189 this->mapOperands.size());
211 assert(!dim.has_value() &&
"invalid dim value");
212 }
else if (
auto shapedType = dyn_cast<ShapedType>(value.
getType())) {
213 assert(*dim >= 0 &&
"invalid dim value");
214 if (shapedType.hasRank())
215 assert(*dim < shapedType.getRank() &&
"invalid dim value");
217 llvm_unreachable(
"unsupported type");
229 LogicalResult status =
cstr.addBound(
235 if (failed(status)) {
241 LDBG() <<
"Failed to add bound: " << expr <<
"\n";
246 std::optional<int64_t> dim) {
255 std::optional<int64_t> constSize = std::nullopt;
256 auto shapedType = dyn_cast<ShapedType>(value.
getType());
258 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
259 constSize = shapedType.getDimSize(*dim);
261 constSize = *constInt;
272 return builder.getAffineConstantExpr(*constSize);
282 bound(value)[*dim] == *constSize;
284 bound(value) == *constSize;
285 return builder.getAffineConstantExpr(*constSize);
295 if (
Value value = llvm::dyn_cast_if_present<Value>(ofr))
296 return getExpr(value, std::nullopt);
298 assert(constInt.has_value() &&
"expected Integer constant");
299 return builder.getAffineConstantExpr(*constInt);
303 return builder.getAffineConstantExpr(constant);
307 std::optional<int64_t> dim,
308 bool isSymbol,
bool addToWorklist) {
315 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
316 :
cstr.appendVar(VarKind::SetDim);
317 LDBG() <<
"Inserting constraint set column " << pos <<
" for: " << value
334 (!isa<BlockArgument>(value) ||
335 cast<BlockArgument>(value).getOwner()->isEntryBlock())) {
336 LDBG() <<
"Push to worklist: " << value
345 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
346 :
cstr.appendVar(VarKind::SetDim);
347 LDBG() <<
"Inserting anonymous constraint set column " << pos;
359 assert(map.
getNumResults() == 1 &&
"expected affine map with one result");
365 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
380 return insert(var.map, var.mapOperands, isSymbol);
384 std::optional<int64_t> dim)
const {
388 LDBG() <<
"Getting pos for: " << value
398 assert(pos >= 0 && pos <
cstr.getNumDimAndSymbolVars() &&
"invalid position");
399 return pos <
cstr.getNumDimVars()
400 ?
builder.getAffineDimExpr(pos)
401 :
builder.getAffineSymbolExpr(pos -
cstr.getNumDimVars());
405 std::optional<int64_t> dim)
const {
412 LDBG() <<
"Processing value bounds worklist...";
417 "did not expect std::nullopt on worklist");
419 Value value = valueDim.first;
424 auto shapedType = cast<ShapedType>(value.
getType());
425 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
426 bound(value)[dim] ==
getExpr(shapedType.getDimSize(dim));
432 auto maybeDim = dim ==
kIndexValue ? std::nullopt : std::make_optional(dim);
434 LDBG() <<
"Stop condition met for: " << value <<
" (dim: " << maybeDim
443 LDBG() <<
"Query value bounds for: " << value
447 valueBoundsOp.populateBoundsForIndexValue(value, *
this);
449 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *
this);
453 LDBG() <<
"--> ValueBoundsOpInterface not implemented";
458 auto dstOp = value.
getDefiningOp<DestinationStyleOpInterface>();
461 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
469 cstr.projectOut(pos);
473 assert(erased &&
"inconsistent reverse mapping");
498 std::optional<int64_t> except) {
524 assert(pos == 0 &&
"expected first column");
525 cstr.processWorklist();
531 p.second ==
kIndexValue ? std::nullopt : std::make_optional(p.second);
534 cstr.projectOutAnonymous(pos);
538 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &
ub,
544 if ((type != BoundType::LB) &&
545 (
ub.empty() || !
ub[0] ||
ub[0].getNumResults() == 0))
548 if ((type != BoundType::UB) &&
549 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
553 if (type != BoundType::LB)
554 assert(
ub.size() == 1 &&
ub[0].getNumResults() == 1 &&
555 "multiple bounds not supported");
556 if (type != BoundType::UB)
557 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
558 "multiple bounds not supported");
561 if (type == BoundType::EQ &&
ub[0] != lb[0])
565 if (type == BoundType::EQ || type == BoundType::LB) {
570 ub[0].getResult(0) + ubAdjustment);
574 assert(
cstr.cstr.getNumDimAndSymbolVars() ==
cstr.positionToValueDim.size() &&
575 "inconsistent mapping state");
577 int64_t numDims = 0, numSymbols = 0;
578 for (
int64_t i = 0; i <
cstr.cstr.getNumDimAndSymbolVars(); ++i) {
585 bool isDim = i <
cstr.cstr.getNumDimVars();
587 if (
bound.isFunctionOfDim(i))
590 if (
bound.isFunctionOfSymbol(i -
cstr.cstr.getNumDimVars()))
597 replacementDims.push_back(
b.getAffineConstantExpr(0));
599 replacementSymbols.push_back(
b.getAffineConstantExpr(0));
605 replacementDims.push_back(
b.getAffineDimExpr(numDims++));
607 replacementSymbols.push_back(
b.getAffineSymbolExpr(numSymbols++));
610 assert(
cstr.positionToValueDim[i].has_value() &&
611 "cannot build affine map in terms of anonymous column");
613 Value value = valueDim.first;
619 "expected index or integer type");
620 mapOperands.push_back(std::make_pair(value, std::nullopt));
624 assert(cast<ShapedType>(value.
getType()).isDynamicDim(dim) &&
625 "expected dynamic dim");
626 mapOperands.push_back(std::make_pair(value, dim));
629 resultMap =
bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
630 numDims, numSymbols);
639 resultMap, mapOperands, type, var,
641 return llvm::is_contained(dependencies, std::make_pair(v, d));
653 auto isIndependent = [&](
Value v) {
659 if (!visited.insert(next).second)
661 if (llvm::is_contained(independencies, next))
674 resultMap, mapOperands, type, var,
676 return isIndependent(v);
687 auto defaultStopCondition = [&](
Value v, std::optional<int64_t> dim,
689 return cstr.cstr.getConstantBound64(type, pos).has_value();
695 pos =
cstr.populateConstraints(var.map, var.mapOperands);
696 assert(pos == 0 &&
"expected `map` is the first column");
700 if (
auto bound =
cstr.cstr.getConstantBound64(type, pos))
701 return type == BoundType::UB ? *
bound + ubAdjustment : *
bound;
706 std::optional<int64_t> dim) {
730 std::optional<int64_t> dim1,
731 std::optional<int64_t> dim2) {
739 b.getAffineDimExpr(0) -
b.getAffineDimExpr(1));
741 Variable(map, {{value1, dim1}, {value2, dim2}}));
756 if (
cstr.isEmpty()) {
757 LDBG() <<
"cannot compare value/dims: constraint system is already empty";
770 if (cmp ==
LT || cmp ==
LE) {
773 }
else if (cmp ==
GT || cmp ==
GE) {
777 llvm_unreachable(
"unsupported comparison operator");
779 if (cmp ==
LE || cmp ==
GE)
780 eq[
cstr.getNumCols() - 1] -= 1;
785 cstr.addInequality(eq);
786 bool isEmpty =
cstr.isEmpty();
787 cstr.removeInequality(ineqPos);
811 std::optional<bool> le =
817 std::optional<bool> ge =
826 llvm_unreachable(
"invalid comparison operator");
840 int64_t lhsPos = -1, rhsPos = -1;
844 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
845 size_t(rhsPos) >=
cstr.positionToValueDim.size())
848 return cstr.comparePos(lhsPos, cmp, rhsPos);
851 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
852 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
853 return cstr.comparePos(lhsPos, cmp, rhsPos);
859 int64_t lhsPos = -1, rhsPos = -1;
863 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
864 size_t(rhsPos) >=
cstr.positionToValueDim.size())
867 FailureOr<bool> ordered =
cstr.strongComparePos(lhsPos, cmp, rhsPos);
868 return failed(ordered);
871 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
872 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
873 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
885 "expected slices of same rank");
887 "expected slices of same rank");
889 "expected slices of same rank");
892 bool foundUnknownBound =
false;
896 b.getAffineSymbolExpr(0) +
897 b.getAffineSymbolExpr(1) *
b.getAffineSymbolExpr(2) -
898 b.getAffineSymbolExpr(3));
912 foundUnknownBound |= failed(constBound);
913 if (succeeded(constBound) && *constBound <= 0)
929 foundUnknownBound |= failed(constBound);
930 if (succeeded(constBound) && *constBound <= 0)
937 if (foundUnknownBound)
949 "expected slices of same rank");
951 "expected slices of same rank");
953 "expected slices of same rank");
959 for (
auto [offset1, offset2] :
961 FailureOr<bool> equal =
areEqual(offset1, offset2);
967 for (
auto [size1, size2] :
969 FailureOr<bool> equal =
areEqual(size1, size2);
975 for (
auto [stride1, stride2] :
977 FailureOr<bool> equal =
areEqual(stride1, stride2);
987 llvm::errs() <<
"==========\nColumns:\n";
988 llvm::errs() <<
"(column\tdim\tvalue)\n";
990 llvm::errs() <<
" " <<
index <<
"\t";
993 llvm::errs() <<
"n/a\t";
995 llvm::errs() << valueDim->second <<
"\t";
999 llvm::errs() <<
"(result " <<
result.getResultNumber() <<
")";
1001 llvm::errs() <<
"(bbarg "
1002 << cast<BlockArgument>(valueDim->first).getArgNumber()
1005 llvm::errs() <<
"\n";
1007 llvm::errs() <<
"n/a\tn/a\n";
1010 llvm::errs() <<
"\nConstraint set:\n";
1012 llvm::errs() <<
"==========\n";
1017 assert(!this->dim.has_value() &&
"dim was already set");
1029 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
1044 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
1051 cstr.addBound(BoundType::EQ, cstr.getPos(value, this->dim), expr);
static llvm::ManagedStatic< PassManagerOptions > options
static bool isIndexOrIntegerType(Type type)
static Operation * getOwnerOfValue(Value value)
static void assertValidValueDim(Value value, std::optional< int64_t > dim, ValueBoundsOptions options)
Base type for affine expression.
AffineExpr replaceDimsAndSymbols(ArrayRef< AffineExpr > dimReplacements, ArrayRef< AffineExpr > symReplacements) const
This method substitutes any uses of dimensions and symbols (e.g.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
unsigned getNumResults() const
AffineMap replaceDimsAndSymbols(ArrayRef< AffineExpr > dimReplacements, ArrayRef< AffineExpr > symReplacements, unsigned numResultDims, unsigned numResultSyms) const
This method substitutes any uses of dimensions and symbols (e.g.
AffineExpr getResult(unsigned idx) const
AffineMap replace(AffineExpr expr, AffineExpr replacement, unsigned numResultDims, unsigned numResultSyms) const
Sparse replace method.
Attributes are known-constant values of operations.
This class is a general helper class for creating context-global objects like types,...
IntegerAttr getIndexAttr(int64_t value)
A hyperrectangular slice, represented as a list of offsets, sizes and strides.
HyperrectangularSlice(ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ArrayRef< OpFoldResult > strides)
ArrayRef< OpFoldResult > getMixedStrides() const
ArrayRef< OpFoldResult > getMixedSizes() const
ArrayRef< OpFoldResult > getMixedOffsets() const
MLIRContext is the top-level object for a collection of MLIR operations.
This class represents a single result from folding an operation.
MLIRContext * getContext() const
This is a value defined by a result of an operation.
Operation is the basic unit of execution within MLIR.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
OperationName getName()
The name of an operation is the key identifier for it.
operand_range getOperands()
Returns an iterator on the underlying Value's.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
bool isInteger() const
Return true if this is an integer type (with the specified width).
Helper class that builds a bound for a shaped value dimension or index-typed value.
void operator>(AffineExpr expr)
BoundBuilder & operator[](int64_t dim)
Specify a dimension, assuming that the underlying value is a shaped value.
void operator<=(AffineExpr expr)
void operator==(AffineExpr expr)
void operator<(AffineExpr expr)
void operator>=(AffineExpr expr)
A variable that can be added to the constraint set as a "column".
Variable(OpFoldResult ofr)
Construct a variable for an index-typed attribute or SSA value.
MLIRContext * getContext() const
static bool compare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Return "true" if "lhs cmp rhs" was proven to hold.
static FailureOr< bool > areEqual(const Variable &var1, const Variable &var2)
Compute whether the given variables are equal.
static LogicalResult computeBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, StopConditionFn stopCondition, ValueBoundsOptions options={})
Compute a bound for the given variable.
DenseMap< ValueDim, int64_t > valueDimToPosition
Reverse mapping of values/shape dimensions to columns.
void processWorklist()
Iteratively process all elements on the worklist until an index-typed value or shaped value meets sto...
bool addConservativeSemiAffineBounds
Should conservative bounds be added for semi-affine expressions.
static bool isProvablyNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably negative.
static bool isProvablyPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably positive.
AffineExpr getExpr(Value value, std::optional< int64_t > dim=std::nullopt)
Return an expression that represents the given index-typed value or shaped value dimension.
static FailureOr< bool > areEquivalentSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1, const HyperrectangularSlice &slice2)
Return "true" if the given slices are guaranteed to be equivalent.
ValueBoundsConstraintSet(MLIRContext *ctx, const StopConditionFn &stopCondition, ValueBoundsOptions options={}, bool addConservativeSemiAffineBounds=false)
void projectOut(int64_t pos)
Project out the given column in the constraint set.
std::function< bool( Value, std::optional< int64_t >, ValueBoundsConstraintSet &cstr)> StopConditionFn
The stop condition when traversing the backward slice of a shaped value/ index-type value.
static FailureOr< int64_t > computeConstantDelta(Value value1, Value value2, std::optional< int64_t > dim1=std::nullopt, std::optional< int64_t > dim2=std::nullopt)
Compute a constant delta between the given two values.
static llvm::FailureOr< bool > strongCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
This function is similar to ValueBoundsConstraintSet::compare, except that it returns false if !...
static FailureOr< int64_t > computeConstantBound(presburger::BoundType type, const Variable &var, const StopConditionFn &stopCondition=nullptr, ValueBoundsOptions options={})
Compute a constant bound for the given variable.
void addBound(presburger::BoundType type, int64_t pos, AffineExpr expr)
Bound the given column in the underlying constraint set by the given expression.
StopConditionFn stopCondition
The current stop condition function.
ComparisonOperator
Comparison operator for ValueBoundsConstraintSet::compare.
BoundBuilder bound(Value value)
Add a bound for the given index-typed value or shaped value.
static bool isProvablyNonNegative(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-negative.
int64_t getPos(Value value, std::optional< int64_t > dim=std::nullopt) const
Return the column position of the given value/dimension.
int64_t insert(Value value, std::optional< int64_t > dim, bool isSymbol=true, bool addToWorklist=true)
Insert a value/dimension into the constraint set.
bool comparePos(int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos)
Return "true" if, based on the current state of the constraint system, "lhs cmp rhs" was proven to ho...
ValueBoundsOptions options
Options that control value bound computation.
SmallVector< std::optional< ValueDim >, 4 > positionToValueDim
Mapping of columns to values/shape dimensions.
static bool isProvablyNonPositive(Value value, ValueBoundsConstraintSet &cstr)
Return "true" if the given value is provably non-positive.
void dump() const
Debugging only: Dump the constraint set and the column-to-value/dim mapping to llvm::errs.
std::queue< int64_t > worklist
Worklist of values/shape dimensions that have not been processed yet.
FlatLinearConstraints cstr
Constraint system of equalities and inequalities.
static LogicalResult computeIndependentBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, ValueRange independencies, ValueBoundsOptions options={})
Compute a bound in that is independent of all values in independencies.
bool isMapped(Value value, std::optional< int64_t > dim=std::nullopt) const
Return "true" if the given value/dim is mapped (i.e., has a corresponding column in the constraint sy...
llvm::FailureOr< bool > strongComparePos(int64_t lhsPos, ComparisonOperator cmp, int64_t rhsPos)
Return "true" if, based on the current state of the constraint system, "lhs cmp rhs" was proven to ho...
AffineExpr getPosExpr(int64_t pos)
Return an affine expression that represents column pos in the constraint set.
void projectOutAnonymous(std::optional< int64_t > except=std::nullopt)
static FailureOr< bool > areOverlappingSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1, const HyperrectangularSlice &slice2)
Return "true" if the given slices are guaranteed to be overlapping.
std::pair< Value, int64_t > ValueDim
An index-typed value or the dimension of a shaped-type value.
void populateConstraints(Value value, std::optional< int64_t > dim)
Traverse the IR starting from the given value/dim and populate constraints as long as the stop condit...
Builder builder
Builder for constructing affine expressions.
bool populateAndCompare(const Variable &lhs, ComparisonOperator cmp, const Variable &rhs)
Populate constraints for lhs/rhs (until the stop condition is met).
static constexpr int64_t kIndexValue
Dimension identifier to indicate a value is index-typed.
static LogicalResult computeDependentBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, ValueDimList dependencies, ValueBoundsOptions options={})
Compute a bound in terms of the values/dimensions in dependencies.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
MLIRContext * getContext() const
Utility to get the associated MLIRContext that this value is defined in.
Type getType() const
Return the type of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
The OpAsmOpInterface, see OpAsmInterface.td for more details.
BoundType
The type of bound: equal, lower bound or upper bound.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
bool operator==(StringAttr lhs, std::nullptr_t)
Define comparisons for StringAttr against nullptr and itself to avoid the StringRef overloads from be...
SmallVector< std::pair< Value, std::optional< int64_t > >, 2 > ValueDimList
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
AffineMap foldAttributesIntoMap(Builder &b, AffineMap map, ArrayRef< OpFoldResult > operands, SmallVector< Value > &remainingValues)
Fold all attributes among the given operands into the affine map.
llvm::function_ref< Fn > function_ref
Options that control value bound computation.