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"
33 if (
auto bbArg = dyn_cast<BlockArgument>(value))
41 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
42 assert(offsets.size() == sizes.size() &&
43 "expected same number of offsets, sizes, strides");
44 assert(offsets.size() == strides.size() &&
45 "expected same number of offsets, sizes, strides");
50 : mixedOffsets(offsets), mixedSizes(sizes) {
51 assert(offsets.size() == sizes.size() &&
52 "expected same number of offsets and sizes");
57 mixedStrides.append(offsets.size(),
Builder(ctx).getIndexAttr(1));
67 if (
auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
70 return intVal.getSExtValue();
74 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
75 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
76 return intAttr.getValue().getSExtValue();
99 std::optional<int64_t> dim) {
102 assert(!dim &&
"expected no dim for index/integer-typed values");
104 b.getAffineConstantExpr(*constInt));
107 Value value = cast<Value>(ofr);
110 assert(isa<ShapedType>(value.
getType()) &&
"expected shaped type");
113 "expected index or integer type");
117 b.getAffineSymbolExpr(0));
118 mapOperands.emplace_back(value, dim);
123 assert(map.getNumResults() == 1 &&
"expected single result");
130 for (
int64_t i = 0, e = map.getNumDims(); i < e; ++i)
131 dimReplacements.push_back(
b.getAffineSymbolExpr(i));
132 for (
int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
133 symReplacements.push_back(
b.getAffineSymbolExpr(i + map.getNumDims()));
135 dimReplacements, symReplacements, 0,
136 map.getNumSymbols() + map.getNumDims());
140 for (
auto [
index, var] : llvm::enumerate(mapOperands)) {
141 assert(var.map.getNumResults() == 1 &&
"expected single result");
142 assert(var.map.getNumDims() == 0 &&
"expected only symbols");
144 for (
auto valueDim : var.mapOperands) {
145 auto *it = llvm::find(this->mapOperands, valueDim);
146 if (it != this->mapOperands.end()) {
148 symReplacements.push_back(
b.getAffineSymbolExpr(
149 std::distance(this->mapOperands.begin(), it)));
152 symReplacements.push_back(
153 b.getAffineSymbolExpr(this->mapOperands.size()));
154 this->mapOperands.push_back(valueDim);
157 replacements[
b.getAffineSymbolExpr(
index)] =
158 var.map.getResult(0).replaceSymbols(symReplacements);
160 this->map = tmpMap.
replace(replacements, 0,
161 this->mapOperands.size());
183 assert(!dim.has_value() &&
"invalid dim value");
184 }
else if (
auto shapedType = dyn_cast<ShapedType>(value.
getType())) {
185 assert(*dim >= 0 &&
"invalid dim value");
186 if (shapedType.hasRank())
187 assert(*dim < shapedType.getRank() &&
"invalid dim value");
189 llvm_unreachable(
"unsupported type");
201 LogicalResult status =
cstr.addBound(
207 if (failed(status)) {
213 LDBG() <<
"Failed to add bound: " << expr <<
"\n";
218 std::optional<int64_t> dim) {
227 std::optional<int64_t> constSize = std::nullopt;
228 auto shapedType = dyn_cast<ShapedType>(value.
getType());
230 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
231 constSize = shapedType.getDimSize(*dim);
233 constSize = *constInt;
244 return builder.getAffineConstantExpr(*constSize);
254 bound(value)[*dim] == *constSize;
256 bound(value) == *constSize;
257 return builder.getAffineConstantExpr(*constSize);
267 if (
Value value = llvm::dyn_cast_if_present<Value>(ofr))
268 return getExpr(value, std::nullopt);
270 assert(constInt.has_value() &&
"expected Integer constant");
271 return builder.getAffineConstantExpr(*constInt);
275 return builder.getAffineConstantExpr(constant);
279 std::optional<int64_t> dim,
280 bool isSymbol,
bool addToWorklist) {
287 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
288 :
cstr.appendVar(VarKind::SetDim);
289 LDBG() <<
"Inserting constraint set column " << pos <<
" for: " << value
306 (!isa<BlockArgument>(value) ||
307 cast<BlockArgument>(value).getOwner()->isEntryBlock())) {
308 LDBG() <<
"Push to worklist: " << value
317 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
318 :
cstr.appendVar(VarKind::SetDim);
319 LDBG() <<
"Inserting anonymous constraint set column " << pos;
331 assert(map.
getNumResults() == 1 &&
"expected affine map with one result");
337 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
338 return getExpr(v.first, v.second);
352 return insert(var.map, var.mapOperands, isSymbol);
356 std::optional<int64_t> dim)
const {
360 LDBG() <<
"Getting pos for: " << value
370 assert(pos >= 0 && pos <
cstr.getNumDimAndSymbolVars() &&
"invalid position");
371 return pos <
cstr.getNumDimVars()
372 ?
builder.getAffineDimExpr(pos)
373 :
builder.getAffineSymbolExpr(pos -
cstr.getNumDimVars());
377 std::optional<int64_t> dim)
const {
384 LDBG() <<
"Processing value bounds worklist...";
389 "did not expect std::nullopt on worklist");
391 Value value = valueDim.first;
396 auto shapedType = cast<ShapedType>(value.
getType());
397 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
398 bound(value)[dim] ==
getExpr(shapedType.getDimSize(dim));
404 auto maybeDim = dim ==
kIndexValue ? std::nullopt : std::make_optional(dim);
406 LDBG() <<
"Stop condition met for: " << value <<
" (dim: " << maybeDim
415 LDBG() <<
"Query value bounds for: " << value
419 valueBoundsOp.populateBoundsForIndexValue(value, *
this);
421 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *
this);
425 LDBG() <<
"--> ValueBoundsOpInterface not implemented";
430 auto dstOp = value.
getDefiningOp<DestinationStyleOpInterface>();
433 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
441 cstr.projectOut(pos);
445 assert(erased &&
"inconsistent reverse mapping");
470 std::optional<int64_t> except) {
496 assert(pos == 0 &&
"expected first column");
497 cstr.processWorklist();
503 p.second ==
kIndexValue ? std::nullopt : std::make_optional(p.second);
506 cstr.projectOutAnonymous(pos);
510 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &
ub,
516 if ((type != BoundType::LB) &&
517 (
ub.empty() || !
ub[0] ||
ub[0].getNumResults() == 0))
520 if ((type != BoundType::UB) &&
521 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
525 if (type != BoundType::LB)
526 assert(
ub.size() == 1 &&
ub[0].getNumResults() == 1 &&
527 "multiple bounds not supported");
528 if (type != BoundType::UB)
529 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
530 "multiple bounds not supported");
533 if (type == BoundType::EQ &&
ub[0] != lb[0])
537 if (type == BoundType::EQ || type == BoundType::LB) {
542 ub[0].getResult(0) + ubAdjustment);
546 assert(
cstr.cstr.getNumDimAndSymbolVars() ==
cstr.positionToValueDim.size() &&
547 "inconsistent mapping state");
549 int64_t numDims = 0, numSymbols = 0;
550 for (
int64_t i = 0; i <
cstr.cstr.getNumDimAndSymbolVars(); ++i) {
557 bool isDim = i <
cstr.cstr.getNumDimVars();
559 if (
bound.isFunctionOfDim(i))
562 if (
bound.isFunctionOfSymbol(i -
cstr.cstr.getNumDimVars()))
569 replacementDims.push_back(
b.getAffineConstantExpr(0));
571 replacementSymbols.push_back(
b.getAffineConstantExpr(0));
577 replacementDims.push_back(
b.getAffineDimExpr(numDims++));
579 replacementSymbols.push_back(
b.getAffineSymbolExpr(numSymbols++));
582 assert(
cstr.positionToValueDim[i].has_value() &&
583 "cannot build affine map in terms of anonymous column");
585 Value value = valueDim.first;
591 "expected index or integer type");
592 mapOperands.push_back(std::make_pair(value, std::nullopt));
596 assert(cast<ShapedType>(value.
getType()).isDynamicDim(dim) &&
597 "expected dynamic dim");
598 mapOperands.push_back(std::make_pair(value, dim));
601 resultMap =
bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
602 numDims, numSymbols);
611 resultMap, mapOperands, type, var,
613 return llvm::is_contained(dependencies, std::make_pair(v, d));
625 auto isIndependent = [&](
Value v) {
631 if (!visited.insert(next).second)
633 if (llvm::is_contained(independencies, next))
646 resultMap, mapOperands, type, var,
648 return isIndependent(v);
659 auto defaultStopCondition = [&](
Value v, std::optional<int64_t> dim,
661 return cstr.cstr.getConstantBound64(type, pos).has_value();
667 pos =
cstr.populateConstraints(var.map, var.mapOperands);
668 assert(pos == 0 &&
"expected `map` is the first column");
672 if (
auto bound =
cstr.cstr.getConstantBound64(type, pos))
673 return type == BoundType::UB ? *
bound + ubAdjustment : *
bound;
678 std::optional<int64_t> dim) {
702 std::optional<int64_t> dim1,
703 std::optional<int64_t> dim2) {
711 b.getAffineDimExpr(0) -
b.getAffineDimExpr(1));
713 Variable(map, {{value1, dim1}, {value2, dim2}}));
728 if (
cstr.isEmpty()) {
729 LDBG() <<
"cannot compare value/dims: constraint system is already empty";
742 if (cmp ==
LT || cmp ==
LE) {
745 }
else if (cmp ==
GT || cmp ==
GE) {
749 llvm_unreachable(
"unsupported comparison operator");
751 if (cmp ==
LE || cmp ==
GE)
752 eq[
cstr.getNumCols() - 1] -= 1;
757 cstr.addInequality(eq);
758 bool isEmpty =
cstr.isEmpty();
759 cstr.removeInequality(ineqPos);
783 std::optional<bool> le =
789 std::optional<bool> ge =
798 llvm_unreachable(
"invalid comparison operator");
812 int64_t lhsPos = -1, rhsPos = -1;
816 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
817 size_t(rhsPos) >=
cstr.positionToValueDim.size())
820 return cstr.comparePos(lhsPos, cmp, rhsPos);
823 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
824 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
825 return cstr.comparePos(lhsPos, cmp, rhsPos);
831 int64_t lhsPos = -1, rhsPos = -1;
835 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
836 size_t(rhsPos) >=
cstr.positionToValueDim.size())
839 FailureOr<bool> ordered =
cstr.strongComparePos(lhsPos, cmp, rhsPos);
840 return failed(ordered);
843 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
844 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
845 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
857 "expected slices of same rank");
859 "expected slices of same rank");
861 "expected slices of same rank");
864 bool foundUnknownBound =
false;
868 b.getAffineSymbolExpr(0) +
869 b.getAffineSymbolExpr(1) *
b.getAffineSymbolExpr(2) -
870 b.getAffineSymbolExpr(3));
884 foundUnknownBound |= failed(constBound);
885 if (succeeded(constBound) && *constBound <= 0)
901 foundUnknownBound |= failed(constBound);
902 if (succeeded(constBound) && *constBound <= 0)
909 if (foundUnknownBound)
921 "expected slices of same rank");
923 "expected slices of same rank");
925 "expected slices of same rank");
931 for (
auto [offset1, offset2] :
933 FailureOr<bool> equal =
areEqual(offset1, offset2);
939 for (
auto [size1, size2] :
941 FailureOr<bool> equal =
areEqual(size1, size2);
947 for (
auto [stride1, stride2] :
949 FailureOr<bool> equal =
areEqual(stride1, stride2);
959 llvm::errs() <<
"==========\nColumns:\n";
960 llvm::errs() <<
"(column\tdim\tvalue)\n";
962 llvm::errs() <<
" " <<
index <<
"\t";
965 llvm::errs() <<
"n/a\t";
967 llvm::errs() << valueDim->second <<
"\t";
971 llvm::errs() <<
"(result " <<
result.getResultNumber() <<
")";
973 llvm::errs() <<
"(bbarg "
974 << cast<BlockArgument>(valueDim->first).getArgNumber()
977 llvm::errs() <<
"\n";
979 llvm::errs() <<
"n/a\tn/a\n";
982 llvm::errs() <<
"\nConstraint set:\n";
984 llvm::errs() <<
"==========\n";
989 assert(!this->dim.has_value() &&
"dim was already set");
1001 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
1016 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
1023 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,...
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.
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.
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.
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.