17 #include "llvm/ADT/APSInt.h"
18 #include "llvm/Support/Debug.h"
20 #define DEBUG_TYPE "value-bounds-op-interface"
27 #include "mlir/Interfaces/ValueBoundsOpInterface.cpp.inc"
31 if (
auto bbArg = dyn_cast<BlockArgument>(value))
39 : mixedOffsets(offsets), mixedSizes(sizes), mixedStrides(strides) {
40 assert(offsets.size() == sizes.size() &&
41 "expected same number of offsets, sizes, strides");
42 assert(offsets.size() == strides.size() &&
43 "expected same number of offsets, sizes, strides");
48 : mixedOffsets(offsets), mixedSizes(sizes) {
49 assert(offsets.size() == sizes.size() &&
50 "expected same number of offsets and sizes");
55 mixedStrides.append(offsets.size(),
Builder(ctx).getIndexAttr(1));
60 op.getMixedStrides()) {}
65 if (
auto val = llvm::dyn_cast_if_present<Value>(ofr)) {
68 return intVal.getSExtValue();
72 Attribute attr = llvm::dyn_cast_if_present<Attribute>(ofr);
73 if (
auto intAttr = dyn_cast_or_null<IntegerAttr>(attr))
74 return intAttr.getValue().getSExtValue();
88 std::optional<int64_t> dim) {
91 assert(!dim &&
"expected no dim for index-typed values");
96 Value value = cast<Value>(ofr);
99 assert(isa<ShapedType>(value.
getType()) &&
"expected shaped type");
106 mapOperands.emplace_back(value, dim);
111 assert(map.
getNumResults() == 1 &&
"expected single result");
116 for (int64_t i = 0, e = map.
getNumDims(); i < e; ++i)
121 dimReplacements, symReplacements, 0,
127 assert(var.map.getNumResults() == 1 &&
"expected single result");
128 assert(var.map.getNumDims() == 0 &&
"expected only symbols");
130 for (
auto valueDim : var.mapOperands) {
131 auto it = llvm::find(this->mapOperands, valueDim);
132 if (it != this->mapOperands.end()) {
135 std::distance(this->mapOperands.begin(), it)));
138 symReplacements.push_back(
140 this->mapOperands.push_back(valueDim);
144 var.map.getResult(0).replaceSymbols(symReplacements);
146 this->map = tmpMap.
replace(replacements, 0,
147 this->mapOperands.size());
168 assert(!dim.has_value() &&
"invalid dim value");
169 }
else if (
auto shapedType = dyn_cast<ShapedType>(value.
getType())) {
170 assert(*dim >= 0 &&
"invalid dim value");
171 if (shapedType.hasRank())
172 assert(*dim < shapedType.getRank() &&
"invalid dim value");
174 llvm_unreachable(
"unsupported type");
198 LLVM_DEBUG(llvm::dbgs() <<
"Failed to add bound: " << expr <<
"\n");
203 std::optional<int64_t> dim) {
212 std::optional<int64_t> constSize = std::nullopt;
213 auto shapedType = dyn_cast<ShapedType>(value.
getType());
215 if (shapedType.hasRank() && !shapedType.isDynamicDim(*dim))
216 constSize = shapedType.getDimSize(*dim);
218 constSize = *constInt;
237 (void)
insert(value, dim,
true,
false);
239 bound(value)[*dim] == *constSize;
241 bound(value) == *constSize;
252 if (
Value value = llvm::dyn_cast_if_present<Value>(ofr))
253 return getExpr(value, std::nullopt);
255 assert(constInt.has_value() &&
"expected Integer constant");
264 std::optional<int64_t> dim,
265 bool isSymbol,
bool addToWorklist) {
274 LLVM_DEBUG(llvm::dbgs() <<
"Inserting constraint set column " << pos
286 LLVM_DEBUG(llvm::dbgs() <<
"Push to worklist: " << value
287 <<
" (dim: " << dim.value_or(
kIndexValue) <<
")\n");
297 LLVM_DEBUG(llvm::dbgs() <<
"Inserting anonymous constraint set column " << pos
310 assert(map.
getNumResults() == 1 &&
"expected affine map with one result");
311 int64_t pos =
insert(isSymbol);
316 auto mapper = [&](std::pair<Value, std::optional<int64_t>> v) {
317 return getExpr(v.first, v.second);
331 return insert(var.map, var.mapOperands, isSymbol);
335 std::optional<int64_t> dim)
const {
338 assert((isa<OpResult>(value) ||
339 cast<BlockArgument>(value).getOwner()->isEntryBlock()) &&
340 "unstructured control flow is not supported");
342 LLVM_DEBUG(llvm::dbgs() <<
"Getting pos for: " << value
360 std::optional<int64_t> dim)
const {
367 LLVM_DEBUG(llvm::dbgs() <<
"Processing value bounds worklist...\n");
372 "did not expect std::nullopt on worklist");
374 Value value = valueDim.first;
375 int64_t dim = valueDim.second;
379 auto shapedType = cast<ShapedType>(value.
getType());
380 if (shapedType.hasRank() && !shapedType.isDynamicDim(dim)) {
381 bound(value)[dim] ==
getExpr(shapedType.getDimSize(dim));
387 auto maybeDim = dim ==
kIndexValue ? std::nullopt : std::make_optional(dim);
389 LLVM_DEBUG(llvm::dbgs() <<
"Stop condition met for: " << value
390 <<
" (dim: " << maybeDim <<
")\n");
398 LLVM_DEBUG(llvm::dbgs()
399 <<
"Query value bounds for: " << value
403 valueBoundsOp.populateBoundsForIndexValue(value, *
this);
405 valueBoundsOp.populateBoundsForShapedValueDim(value, dim, *
this);
409 LLVM_DEBUG(llvm::dbgs() <<
"--> ValueBoundsOpInterface not implemented\n");
414 auto dstOp = value.
getDefiningOp<DestinationStyleOpInterface>();
417 Value tiedOperand = dstOp.getTiedOpOperand(cast<OpResult>(value))->
get();
429 assert(erased &&
"inconsistent reverse mapping");
454 std::optional<int64_t> except) {
471 int64_t ubAdjustment = closedUB ? 0 : 1;
478 int64_t pos =
cstr.insert(var,
false);
479 assert(pos == 0 &&
"expected first column");
480 cstr.processWorklist();
486 p.second ==
kIndexValue ? std::nullopt : std::make_optional(p.second);
489 cstr.projectOutAnonymous(pos);
499 if ((type != BoundType::LB) &&
500 (ub.empty() || !ub[0] || ub[0].getNumResults() == 0))
503 if ((type != BoundType::UB) &&
504 (lb.empty() || !lb[0] || lb[0].getNumResults() == 0))
508 if (type != BoundType::LB)
509 assert(ub.size() == 1 && ub[0].getNumResults() == 1 &&
510 "multiple bounds not supported");
511 if (type != BoundType::UB)
512 assert(lb.size() == 1 && lb[0].getNumResults() == 1 &&
513 "multiple bounds not supported");
516 if (type == BoundType::EQ && ub[0] != lb[0])
520 if (type == BoundType::EQ || type == BoundType::LB) {
525 ub[0].getResult(0) + ubAdjustment);
530 "inconsistent mapping state");
532 int64_t numDims = 0, numSymbols = 0;
542 if (
bound.isFunctionOfDim(i))
565 assert(
cstr.positionToValueDim[i].has_value() &&
566 "cannot build affine map in terms of anonymous column");
568 Value value = valueDim.first;
569 int64_t dim = valueDim.second;
574 mapOperands.push_back(std::make_pair(value, std::nullopt));
578 assert(cast<ShapedType>(value.
getType()).isDynamicDim(dim) &&
579 "expected dynamic dim");
580 mapOperands.push_back(std::make_pair(value, dim));
583 resultMap =
bound.replaceDimsAndSymbols(replacementDims, replacementSymbols,
584 numDims, numSymbols);
592 resultMap, mapOperands, type, var,
594 return llvm::is_contained(dependencies, std::make_pair(v, d));
605 auto isIndependent = [&](
Value v) {
611 if (!visited.insert(next).second)
613 if (llvm::is_contained(independencies, next))
626 resultMap, mapOperands, type, var,
628 return isIndependent(v);
639 auto defaultStopCondition = [&](
Value v, std::optional<int64_t> dim,
646 pos =
cstr.populateConstraints(var.map, var.mapOperands);
647 assert(pos == 0 &&
"expected `map` is the first column");
650 int64_t ubAdjustment = closedUB ? 0 : 1;
652 return type == BoundType::UB ? *
bound + ubAdjustment : *
bound;
657 std::optional<int64_t> dim) {
672 int64_t pos =
insert(map, std::move(operands),
false);
681 std::optional<int64_t> dim1,
682 std::optional<int64_t> dim2) {
692 Variable(map, {{value1, dim1}, {value2, dim2}}));
710 <<
"cannot compare value/dims: constraint system is already empty");
716 return comparePos(lhsPos, ComparisonOperator::LE, rhsPos) &&
717 comparePos(lhsPos, ComparisonOperator::GE, rhsPos);
721 if (cmp ==
LT || cmp ==
LE) {
724 }
else if (cmp ==
GT || cmp ==
GE) {
728 llvm_unreachable(
"unsupported comparison operator");
730 if (cmp ==
LE || cmp ==
GE)
753 case ComparisonOperator::LT:
754 return strongCmp(ComparisonOperator::LT, ComparisonOperator::GE);
755 case ComparisonOperator::LE:
756 return strongCmp(ComparisonOperator::LE, ComparisonOperator::GT);
757 case ComparisonOperator::GT:
758 return strongCmp(ComparisonOperator::GT, ComparisonOperator::LE);
759 case ComparisonOperator::GE:
760 return strongCmp(ComparisonOperator::GE, ComparisonOperator::LT);
761 case ComparisonOperator::EQ: {
762 std::optional<bool> le =
768 std::optional<bool> ge =
777 llvm_unreachable(
"invalid comparison operator");
791 int64_t lhsPos = -1, rhsPos = -1;
795 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
796 size_t(rhsPos) >=
cstr.positionToValueDim.size())
799 return cstr.comparePos(lhsPos, cmp, rhsPos);
802 lhsPos =
cstr.populateConstraints(lhs.map, lhs.mapOperands);
803 rhsPos =
cstr.populateConstraints(rhs.map, rhs.mapOperands);
804 return cstr.comparePos(lhsPos, cmp, rhsPos);
810 int64_t lhsPos = -1, rhsPos = -1;
814 if (
size_t(lhsPos) >=
cstr.positionToValueDim.size() ||
815 size_t(rhsPos) >=
cstr.positionToValueDim.size())
818 FailureOr<bool> ordered =
cstr.strongComparePos(lhsPos, cmp, rhsPos);
822 lhsPos =
cstr.populateConstraints(lhs.map, lhs.mapOperands);
823 rhsPos =
cstr.populateConstraints(rhs.map, rhs.mapOperands);
824 return cstr.strongComparePos(lhsPos, cmp, rhsPos);
836 "expected slices of same rank");
838 "expected slices of same rank");
840 "expected slices of same rank");
843 bool foundUnknownBound =
false;
863 foundUnknownBound |=
failed(constBound);
864 if (succeeded(constBound) && *constBound <= 0)
880 foundUnknownBound |=
failed(constBound);
881 if (succeeded(constBound) && *constBound <= 0)
888 if (foundUnknownBound)
900 "expected slices of same rank");
902 "expected slices of same rank");
904 "expected slices of same rank");
910 for (
auto [offset1, offset2] :
912 FailureOr<bool> equal =
areEqual(offset1, offset2);
918 for (
auto [size1, size2] :
920 FailureOr<bool> equal =
areEqual(size1, size2);
926 for (
auto [stride1, stride2] :
928 FailureOr<bool> equal =
areEqual(stride1, stride2);
938 llvm::errs() <<
"==========\nColumns:\n";
939 llvm::errs() <<
"(column\tdim\tvalue)\n";
941 llvm::errs() <<
" " << index <<
"\t";
944 llvm::errs() <<
"n/a\t";
946 llvm::errs() << valueDim->second <<
"\t";
949 if (
OpResult result = dyn_cast<OpResult>(valueDim->first)) {
950 llvm::errs() <<
"(result " << result.getResultNumber() <<
")";
952 llvm::errs() <<
"(bbarg "
953 << cast<BlockArgument>(valueDim->first).getArgNumber()
956 llvm::errs() <<
"\n";
958 llvm::errs() <<
"n/a\tn/a\n";
961 llvm::errs() <<
"\nConstraint set:\n";
963 llvm::errs() <<
"==========\n";
968 assert(!this->dim.has_value() &&
"dim was already set");
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.
MLIRContext * getContext() const
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumSymbols() const
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,...
AffineExpr getAffineSymbolExpr(unsigned position)
AffineExpr getAffineConstantExpr(int64_t constant)
AffineExpr getAffineDimExpr(unsigned position)
void getSliceBounds(unsigned offset, unsigned num, MLIRContext *context, SmallVectorImpl< AffineMap > *lbMaps, SmallVectorImpl< AffineMap > *ubMaps, bool closedUB=false)
Computes the lower and upper bounds of the first num dimensional variables (starting at offset) as an...
LogicalResult addBound(presburger::BoundType type, unsigned pos, AffineMap boundMap, bool isClosedBound, AddConservativeSemiAffineBounds=AddConservativeSemiAffineBounds::No)
Adds a bound for the variable at the specified position with constraints being drawn from the specifi...
A hyperrectangular slice, represented as a list of offsets, sizes and strides.
HyperrectangularSlice(ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ArrayRef< OpFoldResult > strides)
ArrayRef< OpFoldResult > getMixedOffsets() const
ArrayRef< OpFoldResult > getMixedSizes() const
ArrayRef< OpFoldResult > getMixedStrides() 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".
MLIRContext * getContext() const
Variable(OpFoldResult ofr)
Construct a variable for an index-typed attribute or SSA value.
A helper class to be used with ValueBoundsOpInterface.
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.
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...
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.
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.
unsigned getNumSymbolVars() const
std::optional< int64_t > getConstantBound64(BoundType type, unsigned pos) const
The same, but casts to int64_t.
unsigned appendVar(VarKind kind, unsigned num=1)
Append num variables of the specified kind after the last variable of that kind.
unsigned getNumDimAndSymbolVars() const
bool isEmpty() const
Checks for emptiness by performing variable elimination on all variables, running the GCD test on eac...
unsigned getNumCols() const
Returns the number of columns in the constraint system.
void removeInequality(unsigned pos)
void projectOut(unsigned pos, unsigned num)
Projects out (aka eliminates) num variables starting at position pos.
void addInequality(ArrayRef< DynamicAPInt > inEq)
Adds an inequality (>= 0) from the coefficients specified in inEq.
unsigned getNumInequalities() const
unsigned getNumDimVars() const
The OpAsmOpInterface, see OpAsmInterface.td for more details.
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given memref value.
BoundType
The type of bound: equal, lower bound or upper bound.
bool operator>(const Fraction &x, const Fraction &y)
bool operator<(const Fraction &x, const Fraction &y)
bool operator>=(const Fraction &x, const Fraction &y)
bool operator<=(const Fraction &x, const Fraction &y)
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.
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 > >> ValueDimList
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
AffineMap foldAttributesIntoMap(Builder &b, AffineMap map, ArrayRef< OpFoldResult > operands, SmallVector< Value > &remainingValues)
Fold all attributes among the given operands into the affine map.