17#include "llvm/ADT/APSInt.h"
18#include "llvm/Support/Debug.h"
19#include "llvm/Support/DebugLog.h"
21#define DEBUG_TYPE "value-bounds-op-interface"
28#include "mlir/Interfaces/ValueBoundsOpInterface.cpp.inc"
32 if (
auto bbArg = dyn_cast<BlockArgument>(value))
40 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
41 assert(offsets.size() == sizes.size() &&
42 "expected same number of offsets, sizes, strides");
43 assert(offsets.size() == strides.size() &&
44 "expected same number of offsets, sizes, strides");
49 : mixedOffsets(offsets), mixedSizes(sizes) {
50 assert(offsets.size() == sizes.size() &&
51 "expected same number of offsets and sizes");
56 mixedStrides.append(offsets.size(),
Builder(ctx).getIndexAttr(1));
66 if (
auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
69 return intVal.getSExtValue();
73 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
74 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
75 return intAttr.getValue().getSExtValue();
89 std::optional<int64_t> dim) {
92 assert(!dim &&
"expected no dim for index-typed values");
94 b.getAffineConstantExpr(*constInt));
97 Value value = cast<Value>(ofr);
100 assert(isa<ShapedType>(value.
getType()) &&
"expected shaped type");
106 b.getAffineSymbolExpr(0));
107 mapOperands.emplace_back(value, dim);
112 assert(map.getNumResults() == 1 &&
"expected single result");
117 for (
int64_t i = 0, e = map.getNumDims(); i < e; ++i)
118 dimReplacements.push_back(
b.getAffineSymbolExpr(i));
119 for (
int64_t i = 0, e = map.getNumSymbols(); i < e; ++i)
120 symReplacements.push_back(
b.getAffineSymbolExpr(i + map.getNumDims()));
122 dimReplacements, symReplacements, 0,
123 map.getNumSymbols() + map.getNumDims());
127 for (
auto [
index, var] : llvm::enumerate(mapOperands)) {
128 assert(var.map.getNumResults() == 1 &&
"expected single result");
129 assert(var.map.getNumDims() == 0 &&
"expected only symbols");
131 for (
auto valueDim : var.mapOperands) {
132 auto *it = llvm::find(this->mapOperands, valueDim);
133 if (it != this->mapOperands.end()) {
135 symReplacements.push_back(
b.getAffineSymbolExpr(
136 std::distance(this->mapOperands.begin(), it)));
139 symReplacements.push_back(
140 b.getAffineSymbolExpr(this->mapOperands.size()));
141 this->mapOperands.push_back(valueDim);
144 replacements[
b.getAffineSymbolExpr(
index)] =
145 var.map.getResult(0).replaceSymbols(symReplacements);
147 this->map = tmpMap.
replace(replacements, 0,
148 this->mapOperands.size());
169 assert(!dim.has_value() &&
"invalid dim value");
170 }
else if (
auto shapedType = dyn_cast<ShapedType>(value.
getType())) {
171 assert(*dim >= 0 &&
"invalid dim value");
172 if (shapedType.hasRank())
173 assert(*dim < shapedType.getRank() &&
"invalid dim value");
175 llvm_unreachable(
"unsupported type");
187 LogicalResult status =
cstr.addBound(
193 if (failed(status)) {
199 LDBG() <<
"Failed to add bound: " << expr <<
"\n";
204 std::optional<int64_t> dim) {
213 std::optional<int64_t> constSize = std::nullopt;
214 auto shapedType = dyn_cast<ShapedType>(value.
getType());
216 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
217 constSize = shapedType.getDimSize(*dim);
219 constSize = *constInt;
230 return builder.getAffineConstantExpr(*constSize);
240 bound(value)[*dim] == *constSize;
242 bound(value) == *constSize;
243 return builder.getAffineConstantExpr(*constSize);
253 if (
Value value = llvm::dyn_cast_if_present<Value>(ofr))
254 return getExpr(value, std::nullopt);
256 assert(constInt.has_value() &&
"expected Integer constant");
257 return builder.getAffineConstantExpr(*constInt);
261 return builder.getAffineConstantExpr(constant);
265 std::optional<int64_t> dim,
266 bool isSymbol,
bool addToWorklist) {
273 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
274 :
cstr.appendVar(VarKind::SetDim);
275 LDBG() <<
"Inserting constraint set column " << pos <<
" for: " << value
285 LDBG() <<
"Push to worklist: " << value
294 int64_t pos = isSymbol ?
cstr.appendVar(VarKind::Symbol)
295 :
cstr.appendVar(VarKind::SetDim);
296 LDBG() <<
"Inserting anonymous constraint set column " << pos;
308 assert(map.
getNumResults() == 1 &&
"expected affine map with one result");
314 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
315 return getExpr(v.first, v.second);
329 return insert(var.map, var.mapOperands, isSymbol);
333 std::optional<int64_t> dim)
const {
336 assert((isa<OpResult>(value) ||
337 cast<BlockArgument>(value).getOwner()->isEntryBlock()) &&
338 "unstructured control flow is not supported");
340 LDBG() <<
"Getting pos for: " << value
350 assert(pos >= 0 && pos <
cstr.getNumDimAndSymbolVars() &&
"invalid position");
351 return pos <
cstr.getNumDimVars()
352 ?
builder.getAffineDimExpr(pos)
353 :
builder.getAffineSymbolExpr(pos -
cstr.getNumDimVars());
357 std::optional<int64_t> dim)
const {
364 LDBG() <<
"Processing value bounds worklist...";
369 "did not expect std::nullopt on worklist");
371 Value value = valueDim.first;
376 auto shapedType = cast<ShapedType>(value.
getType());
377 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
378 bound(value)[dim] ==
getExpr(shapedType.getDimSize(dim));
384 auto maybeDim = dim ==
kIndexValue ? std::nullopt : std::make_optional(dim);
386 LDBG() <<
"Stop condition met for: " << value <<
" (dim: " << maybeDim
395 LDBG() <<
"Query value bounds for: " << value
399 valueBoundsOp.populateBoundsForIndexValue(value, *
this);
401 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *
this);
405 LDBG() <<
"--> ValueBoundsOpInterface not implemented";
410 auto dstOp = value.
getDefiningOp<DestinationStyleOpInterface>();
413 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->get();
421 cstr.projectOut(pos);
425 assert(erased &&
"inconsistent reverse mapping");
450 std::optional<int64_t> except) {
467 int64_t ubAdjustment = closedUB ? 0 : 1;
475 assert(pos == 0 &&
"expected first column");
476 cstr.processWorklist();
482 p.second ==
kIndexValue ? std::nullopt : std::make_optional(p.second);
485 cstr.projectOutAnonymous(pos);
489 cstr.cstr.getSliceBounds(pos, 1, ctx, &lb, &
ub,
495 if ((type != BoundType::LB) &&
496 (
ub.empty() || !
ub[0] ||
ub[0].getNumResults() == 0))
499 if ((type != BoundType::UB) &&
500 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
504 if (type != BoundType::LB)
505 assert(
ub.size() == 1 &&
ub[0].getNumResults() == 1 &&
506 "multiple bounds not supported");
507 if (type != BoundType::UB)
508 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
509 "multiple bounds not supported");
512 if (type == BoundType::EQ &&
ub[0] != lb[0])
516 if (type == BoundType::EQ || type == BoundType::LB) {
521 ub[0].getResult(0) + ubAdjustment);
525 assert(
cstr.cstr.getNumDimAndSymbolVars() ==
cstr.positionToValueDim.size() &&
526 "inconsistent mapping state");
528 int64_t numDims = 0, numSymbols = 0;
529 for (
int64_t i = 0; i <
cstr.cstr.getNumDimAndSymbolVars(); ++i) {
536 bool isDim = i <
cstr.cstr.getNumDimVars();
538 if (
bound.isFunctionOfDim(i))
541 if (
bound.isFunctionOfSymbol(i -
cstr.cstr.getNumDimVars()))
548 replacementDims.push_back(
b.getAffineConstantExpr(0));
550 replacementSymbols.push_back(
b.getAffineConstantExpr(0));
556 replacementDims.push_back(
b.getAffineDimExpr(numDims++));
558 replacementSymbols.push_back(
b.getAffineSymbolExpr(numSymbols++));
561 assert(
cstr.positionToValueDim[i].has_value() &&
562 "cannot build affine map in terms of anonymous column");
564 Value value = valueDim.first;
570 mapOperands.push_back(std::make_pair(value, std::nullopt));
574 assert(cast<ShapedType>(value.
getType()).isDynamicDim(dim) &&
575 "expected dynamic dim");
576 mapOperands.push_back(std::make_pair(value, dim));
579 resultMap =
bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
580 numDims, numSymbols);
588 resultMap, mapOperands, type, var,
590 return llvm::is_contained(dependencies, std::make_pair(v, d));
601 auto isIndependent = [&](
Value v) {
607 if (!visited.insert(next).second)
609 if (llvm::is_contained(independencies, next))
622 resultMap, mapOperands, type, var,
624 return isIndependent(v);
635 auto defaultStopCondition = [&](
Value v, std::optional<int64_t> dim,
637 return cstr.cstr.getConstantBound64(type, pos).has_value();
642 pos =
cstr.populateConstraints(var.map, var.mapOperands);
643 assert(pos == 0 &&
"expected `map` is the first column");
646 int64_t ubAdjustment = closedUB ? 0 : 1;
647 if (
auto bound =
cstr.cstr.getConstantBound64(type, pos))
648 return type == BoundType::UB ? *
bound + ubAdjustment : *
bound;
653 std::optional<int64_t> dim) {
677 std::optional<int64_t> dim1,
678 std::optional<int64_t> dim2) {
686 b.getAffineDimExpr(0) -
b.getAffineDimExpr(1));
688 Variable(map, {{value1, dim1}, {value2, dim2}}));
703 if (
cstr.isEmpty()) {
704 LDBG() <<
"cannot compare value/dims: constraint system is already empty";
715 if (cmp ==
LT || cmp ==
LE) {
718 }
else if (cmp ==
GT || cmp ==
GE) {
722 llvm_unreachable(
"unsupported comparison operator");
724 if (cmp ==
LE || cmp ==
GE)
725 eq[
cstr.getNumCols() - 1] -= 1;
730 cstr.addInequality(eq);
731 bool isEmpty =
cstr.isEmpty();
732 cstr.removeInequality(ineqPos);
756 std::optional<bool> le =
762 std::optional<bool> ge =
771 llvm_unreachable(
"invalid comparison operator");
785 int64_t lhsPos = -1, rhsPos = -1;
789 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
790 size_t(rhsPos) >=
cstr.positionToValueDim.size())
793 return cstr.comparePos(lhsPos, cmp, rhsPos);
796 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
797 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
798 return cstr.comparePos(lhsPos, cmp, rhsPos);
804 int64_t lhsPos = -1, rhsPos = -1;
808 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
809 size_t(rhsPos) >=
cstr.positionToValueDim.size())
812 FailureOr<bool> ordered =
cstr.strongComparePos(lhsPos, cmp, rhsPos);
813 return failed(ordered);
816 lhsPos =
cstr.populateConstraints(
lhs.map,
lhs.mapOperands);
817 rhsPos =
cstr.populateConstraints(
rhs.map,
rhs.mapOperands);
818 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
830 "expected slices of same rank");
832 "expected slices of same rank");
834 "expected slices of same rank");
837 bool foundUnknownBound =
false;
841 b.getAffineSymbolExpr(0) +
842 b.getAffineSymbolExpr(1) *
b.getAffineSymbolExpr(2) -
843 b.getAffineSymbolExpr(3));
857 foundUnknownBound |= failed(constBound);
858 if (succeeded(constBound) && *constBound <= 0)
874 foundUnknownBound |= failed(constBound);
875 if (succeeded(constBound) && *constBound <= 0)
882 if (foundUnknownBound)
894 "expected slices of same rank");
896 "expected slices of same rank");
898 "expected slices of same rank");
904 for (
auto [offset1, offset2] :
906 FailureOr<bool> equal =
areEqual(offset1, offset2);
912 for (
auto [size1, size2] :
914 FailureOr<bool> equal =
areEqual(size1, size2);
920 for (
auto [stride1, stride2] :
922 FailureOr<bool> equal =
areEqual(stride1, stride2);
932 llvm::errs() <<
"==========\nColumns:\n";
933 llvm::errs() <<
"(column\tdim\tvalue)\n";
935 llvm::errs() <<
" " <<
index <<
"\t";
938 llvm::errs() <<
"n/a\t";
940 llvm::errs() << valueDim->second <<
"\t";
944 llvm::errs() <<
"(result " <<
result.getResultNumber() <<
")";
946 llvm::errs() <<
"(bbarg "
947 << cast<BlockArgument>(valueDim->first).getArgNumber()
950 llvm::errs() <<
"\n";
952 llvm::errs() <<
"n/a\tn/a\n";
955 llvm::errs() <<
"\nConstraint set:\n";
957 llvm::errs() <<
"==========\n";
962 assert(!this->dim.has_value() &&
"dim was already set");
974 cstr.addBound(BoundType::UB, cstr.getPos(value, this->dim), expr);
989 cstr.addBound(BoundType::LB, cstr.getPos(value, this->dim), expr);
996 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