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();
90 std::optional<int64_t> dim) {
93 assert(!dim &&
"expected no dim for index-typed values");
95 b.getAffineConstantExpr(*constInt));
98 Value value = cast<Value>(ofr);
101 assert(isa<ShapedType>(value.
getType()) &&
"expected shaped type");
107 b.getAffineSymbolExpr(0));
108 mapOperands.emplace_back(value, dim);
113 assert(map.getNumResults() == 1 &&
"expected single result");
118 for (
int64_t i = 0, e = map.getNumDims(); i < e; ++i)
119 dimReplacements.push_back(
b.getAffineSymbolExpr(i));
120 for (
int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
121 symReplacements.push_back(
b.getAffineSymbolExpr(i + map.getNumDims()));
123 dimReplacements, symReplacements, 0,
124 map.getNumSymbols() + map.getNumDims());
128 for (
auto [
index, var] : llvm::enumerate(mapOperands)) {
129 assert(var.map.getNumResults() == 1 &&
"expected single result");
130 assert(var.map.getNumDims() == 0 &&
"expected only symbols");
132 for (
auto valueDim : var.mapOperands) {
133 auto *it = llvm::find(this->mapOperands, valueDim);
134 if (it != this->mapOperands.end()) {
136 symReplacements.push_back(
b.getAffineSymbolExpr(
137 std::distance(this->mapOperands.begin(), it)));
140 symReplacements.push_back(
141 b.getAffineSymbolExpr(this->mapOperands.size()));
142 this->mapOperands.push_back(valueDim);
145 replacements[
b.getAffineSymbolExpr(
index)] =
146 var.map.getResult(0).replaceSymbols(symReplacements);
148 this->map = tmpMap.
replace(replacements, 0,
149 this->mapOperands.size());
170 assert(!dim.has_value() &&
"invalid dim value");
171 }
else if (
auto shapedType = dyn_cast<ShapedType>(value.
getType())) {
172 assert(*dim >= 0 &&
"invalid dim value");
173 if (shapedType.hasRank())
174 assert(*dim < shapedType.getRank() &&
"invalid dim value");
176 llvm_unreachable(
"unsupported type");
188 LogicalResult status =
cstr.addBound(
194 if (failed(status)) {
200 LDBG() <<
"Failed to add bound: " << expr <<
"\n";
205 std::optional<int64_t> dim) {
214 std::optional<int64_t> constSize = std::nullopt;
215 auto shapedType = dyn_cast<ShapedType>(value.
getType());
217 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
218 constSize = shapedType.getDimSize(*dim);
220 constSize = *constInt;
231 return builder.getAffineConstantExpr(*constSize);
241 bound(value)[*dim] == *constSize;
243 bound(value) == *constSize;
244 return builder.getAffineConstantExpr(*constSize);
254 if (
Value value = llvm::dyn_cast_if_present<Value>(ofr))
255 return getExpr(value, std::nullopt);
257 assert(constInt.has_value() &&
"expected Integer constant");
258 return builder.getAffineConstantExpr(*constInt);
262 return builder.getAffineConstantExpr(constant);
266 std::optional<int64_t> dim,
267 bool isSymbol,
bool addToWorklist) {
274 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
275 :
cstr.appendVar(VarKind::SetDim);
276 LDBG() <<
"Inserting constraint set column " << pos <<
" for: " << value
286 LDBG() <<
"Push to worklist: " << value
295 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
296 :
cstr.appendVar(VarKind::SetDim);
297 LDBG() <<
"Inserting anonymous constraint set column " << pos;
309 assert(map.
getNumResults() == 1 &&
"expected affine map with one result");
315 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
316 return getExpr(v.first, v.second);
330 return insert(var.map, var.mapOperands, isSymbol);
334 std::optional<int64_t> dim)
const {
337 assert((isa<OpResult>(value) ||
338 cast<BlockArgument>(value).getOwner()->isEntryBlock()) &&
339 "unstructured control flow is not supported");
341 LDBG() <<
"Getting pos for: " << value
351 assert(pos >= 0 && pos <
cstr.getNumDimAndSymbolVars() &&
"invalid position");
352 return pos <
cstr.getNumDimVars()
353 ?
builder.getAffineDimExpr(pos)
354 :
builder.getAffineSymbolExpr(pos -
cstr.getNumDimVars());
358 std::optional<int64_t> dim)
const {
365 LDBG() <<
"Processing value bounds worklist...";
370 "did not expect std::nullopt on worklist");
372 Value value = valueDim.first;
377 auto shapedType = cast<ShapedType>(value.
getType());
378 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
379 bound(value)[dim] ==
getExpr(shapedType.getDimSize(dim));
385 auto maybeDim = dim ==
kIndexValue ? std::nullopt : std::make_optional(dim);
387 LDBG() <<
"Stop condition met for: " << value <<
" (dim: " << maybeDim
396 LDBG() <<
"Query value bounds for: " << value
400 valueBoundsOp.populateBoundsForIndexValue(value, *
this);
402 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *
this);
406 LDBG() <<
"--> ValueBoundsOpInterface not implemented";
411 auto dstOp = value.
getDefiningOp<DestinationStyleOpInterface>();
414 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
422 cstr.projectOut(pos);
426 assert(erased &&
"inconsistent reverse mapping");
451 std::optional<int64_t> except) {
468 int64_t ubAdjustment = closedUB ? 0 : 1;
476 assert(pos == 0 &&
"expected first column");
477 cstr.processWorklist();
483 p.second ==
kIndexValue ? std::nullopt : std::make_optional(p.second);
486 cstr.projectOutAnonymous(pos);
490 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &
ub,
496 if ((type != BoundType::LB) &&
497 (
ub.empty() || !
ub[0] ||
ub[0].getNumResults() == 0))
500 if ((type != BoundType::UB) &&
501 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
505 if (type != BoundType::LB)
506 assert(
ub.size() == 1 &&
ub[0].getNumResults() == 1 &&
507 "multiple bounds not supported");
508 if (type != BoundType::UB)
509 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
510 "multiple bounds not supported");
513 if (type == BoundType::EQ &&
ub[0] != lb[0])
517 if (type == BoundType::EQ || type == BoundType::LB) {
522 ub[0].getResult(0) + ubAdjustment);
526 assert(
cstr.cstr.getNumDimAndSymbolVars() ==
cstr.positionToValueDim.size() &&
527 "inconsistent mapping state");
529 int64_t numDims = 0, numSymbols = 0;
530 for (
int64_t i = 0; i <
cstr.cstr.getNumDimAndSymbolVars(); ++i) {
537 bool isDim = i <
cstr.cstr.getNumDimVars();
539 if (
bound.isFunctionOfDim(i))
542 if (
bound.isFunctionOfSymbol(i -
cstr.cstr.getNumDimVars()))
549 replacementDims.push_back(
b.getAffineConstantExpr(0));
551 replacementSymbols.push_back(
b.getAffineConstantExpr(0));
557 replacementDims.push_back(
b.getAffineDimExpr(numDims++));
559 replacementSymbols.push_back(
b.getAffineSymbolExpr(numSymbols++));
562 assert(
cstr.positionToValueDim[i].has_value() &&
563 "cannot build affine map in terms of anonymous column");
565 Value value = valueDim.first;
571 mapOperands.push_back(std::make_pair(value, std::nullopt));
575 assert(cast<ShapedType>(value.
getType()).isDynamicDim(dim) &&
576 "expected dynamic dim");
577 mapOperands.push_back(std::make_pair(value, dim));
580 resultMap =
bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
581 numDims, numSymbols);
589 resultMap, mapOperands, type, var,
591 return llvm::is_contained(dependencies, std::make_pair(v, d));
602 auto isIndependent = [&](
Value v) {
608 if (!visited.insert(next).second)
610 if (llvm::is_contained(independencies, next))
623 resultMap, mapOperands, type, var,
625 return isIndependent(v);
636 auto defaultStopCondition = [&](
Value v, std::optional<int64_t> dim,
638 return cstr.cstr.getConstantBound64(type, pos).has_value();
643 pos =
cstr.populateConstraints(var.map, var.mapOperands);
644 assert(pos == 0 &&
"expected `map` is the first column");
647 int64_t ubAdjustment = closedUB ? 0 : 1;
648 if (
auto bound =
cstr.cstr.getConstantBound64(type, pos))
649 return type == BoundType::UB ? *
bound + ubAdjustment : *
bound;
654 std::optional<int64_t> dim) {
678 std::optional<int64_t> dim1,
679 std::optional<int64_t> dim2) {
687 b.getAffineDimExpr(0) -
b.getAffineDimExpr(1));
689 Variable(map, {{value1, dim1}, {value2, dim2}}));
704 if (
cstr.isEmpty()) {
705 LDBG() <<
"cannot compare value/dims: constraint system is already empty";
716 if (cmp ==
LT || cmp ==
LE) {
719 }
else if (cmp ==
GT || cmp ==
GE) {
723 llvm_unreachable(
"unsupported comparison operator");
725 if (cmp ==
LE || cmp ==
GE)
726 eq[
cstr.getNumCols() - 1] -= 1;
731 cstr.addInequality(eq);
732 bool isEmpty =
cstr.isEmpty();
733 cstr.removeInequality(ineqPos);
757 std::optional<bool> le =
763 std::optional<bool> ge =
772 llvm_unreachable(
"invalid comparison operator");
786 int64_t lhsPos = -1, rhsPos = -1;
790 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
791 size_t(rhsPos) >=
cstr.positionToValueDim.size())
794 return cstr.comparePos(lhsPos, cmp, rhsPos);
797 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
798 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
799 return cstr.comparePos(lhsPos, cmp, rhsPos);
805 int64_t lhsPos = -1, rhsPos = -1;
809 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
810 size_t(rhsPos) >=
cstr.positionToValueDim.size())
813 FailureOr<bool> ordered =
cstr.strongComparePos(lhsPos, cmp, rhsPos);
814 return failed(ordered);
817 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
818 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
819 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
831 "expected slices of same rank");
833 "expected slices of same rank");
835 "expected slices of same rank");
838 bool foundUnknownBound =
false;
842 b.getAffineSymbolExpr(0) +
843 b.getAffineSymbolExpr(1) *
b.getAffineSymbolExpr(2) -
844 b.getAffineSymbolExpr(3));
858 foundUnknownBound |= failed(constBound);
859 if (succeeded(constBound) && *constBound <= 0)
875 foundUnknownBound |= failed(constBound);
876 if (succeeded(constBound) && *constBound <= 0)
883 if (foundUnknownBound)
895 "expected slices of same rank");
897 "expected slices of same rank");
899 "expected slices of same rank");
905 for (
auto [offset1, offset2] :
907 FailureOr<bool> equal =
areEqual(offset1, offset2);
913 for (
auto [size1, size2] :
915 FailureOr<bool> equal =
areEqual(size1, size2);
921 for (
auto [stride1, stride2] :
923 FailureOr<bool> equal =
areEqual(stride1, stride2);
933 llvm::errs() <<
"==========\nColumns:\n";
934 llvm::errs() <<
"(column\tdim\tvalue)\n";
936 llvm::errs() <<
" " <<
index <<
"\t";
939 llvm::errs() <<
"n/a\t";
941 llvm::errs() << valueDim->second <<
"\t";
945 llvm::errs() <<
"(result " <<
result.getResultNumber() <<
")";
947 llvm::errs() <<
"(bbarg "
948 << cast<BlockArgument>(valueDim->first).getArgNumber()
951 llvm::errs() <<
"\n";
953 llvm::errs() <<
"n/a\tn/a\n";
956 llvm::errs() <<
"\nConstraint set:\n";
958 llvm::errs() <<
"==========\n";
963 assert(!this->dim.has_value() &&
"dim was already set");
975 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
990 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
997 cstr.addBound(BoundType::EQ, cstr.getPos(value, this->dim), expr);
static Operation * getOwnerOfValue(Value value)
static void assertValidValueDim(Value value, std::optional< int64_t > dim)
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.
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.
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.
SmallVector< std::optional< ValueDim > > positionToValueDim
Mapping of columns to values/shape dimensions.
static LogicalResult computeIndependentBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, ValueRange independencies, bool closedUB=false)
Compute a bound in that is independent of all values in independencies.
static FailureOr< bool > areEquivalentSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1, const HyperrectangularSlice &slice2)
Return "true" if the given slices are guaranteed to be equivalent.
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.
ValueBoundsConstraintSet(MLIRContext *ctx, const StopConditionFn &stopCondition, bool addConservativeSemiAffineBounds=false)
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 !...
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 LogicalResult computeBound(AffineMap &resultMap, ValueDimList &mapOperands, presburger::BoundType type, const Variable &var, StopConditionFn stopCondition, bool closedUB=false)
Compute a bound for the given variable.
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...
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.
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 FailureOr< int64_t > computeConstantBound(presburger::BoundType type, const Variable &var, const StopConditionFn &stopCondition=nullptr, bool closedUB=false)
Compute a constant bound for the given variable.
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, bool closedUB=false)
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...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
SmallVector< std::pair< Value, std::optional< int64_t > > > ValueDimList
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