Files
gps/tools/local/circt/include/mlir/Interfaces/ValueBoundsOpInterface.h
T
2026-07-12 11:01:51 +03:00

468 lines
20 KiB
C++

//===- ValueBoundsOpInterface.h - Value Bounds ------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_INTERFACES_VALUEBOUNDSOPINTERFACE_H_
#define MLIR_INTERFACES_VALUEBOUNDSOPINTERFACE_H_
#include "mlir/Analysis/FlatLinearValueConstraints.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/DestinationStyleOpInterface.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/ExtensibleRTTI.h"
#include <queue>
namespace mlir {
class OffsetSizeAndStrideOpInterface;
/// A hyperrectangular slice, represented as a list of offsets, sizes and
/// strides.
class HyperrectangularSlice {
public:
HyperrectangularSlice(ArrayRef<OpFoldResult> offsets,
ArrayRef<OpFoldResult> sizes,
ArrayRef<OpFoldResult> strides);
/// Create a hyperrectangular slice with unit strides.
HyperrectangularSlice(ArrayRef<OpFoldResult> offsets,
ArrayRef<OpFoldResult> sizes);
/// Infer a hyperrectangular slice from `OffsetSizeAndStrideOpInterface`.
HyperrectangularSlice(OffsetSizeAndStrideOpInterface op);
ArrayRef<OpFoldResult> getMixedOffsets() const { return mixedOffsets; }
ArrayRef<OpFoldResult> getMixedSizes() const { return mixedSizes; }
ArrayRef<OpFoldResult> getMixedStrides() const { return mixedStrides; }
private:
SmallVector<OpFoldResult> mixedOffsets;
SmallVector<OpFoldResult> mixedSizes;
SmallVector<OpFoldResult> mixedStrides;
};
// Inline size chosen empirically based on compilation profiling.
// Profiled: 488K calls, avg=1.5+-0.5. N=2 covers >90% of cases inline.
using ValueDimList = SmallVector<std::pair<Value, std::optional<int64_t>>, 2>;
/// Options that control value bound computation.
struct ValueBoundsOptions {
/// By default, lower/equal bounds are closed and upper bounds are open. If
/// `closedUB` is set to "true", upper bounds are also closed.
bool closedUB = false;
/// If set to "true", integer-typed SSA values are treated like index-typed
/// SSA values. The value bounds infrastructure assumes that such integer
/// computations do not overflow. If set to "false", integer-typed SSA values
/// are rejected.
bool allowIntegerType = false;
};
/// A helper class to be used with `ValueBoundsOpInterface`. This class stores a
/// constraint system and mapping of constrained variables to index-typed values
/// or dimension sizes of shaped values.
///
/// Interface implementations of `ValueBoundsOpInterface` use `addBounds` to
/// insert constraints about their results and/or region block arguments into
/// the constraint set in the form of an AffineExpr. When a bound should be
/// expressed in terms of another value/dimension, `getExpr` can be used to
/// retrieve an AffineExpr that represents the specified value/dimension.
///
/// When a value/dimension is retrieved for the first time through `getExpr`,
/// it is added to an internal worklist. See `computeBound` for more details.
///
/// Note: Any modification of existing IR invalides the data stored in this
/// class. Adding new operations is allowed.
class ValueBoundsConstraintSet
: public llvm::RTTIExtends<ValueBoundsConstraintSet, llvm::RTTIRoot> {
protected:
/// Helper class that builds a bound for a shaped value dimension or
/// index-typed value.
class BoundBuilder {
public:
/// Specify a dimension, assuming that the underlying value is a shaped
/// value.
BoundBuilder &operator[](int64_t dim);
// These overloaded operators add lower/upper/equality bounds.
void operator<(AffineExpr expr);
void operator<=(AffineExpr expr);
void operator>(AffineExpr expr);
void operator>=(AffineExpr expr);
void operator==(AffineExpr expr);
void operator<(OpFoldResult ofr);
void operator<=(OpFoldResult ofr);
void operator>(OpFoldResult ofr);
void operator>=(OpFoldResult ofr);
void operator==(OpFoldResult ofr);
void operator<(int64_t i);
void operator<=(int64_t i);
void operator>(int64_t i);
void operator>=(int64_t i);
void operator==(int64_t i);
protected:
friend class ValueBoundsConstraintSet;
BoundBuilder(ValueBoundsConstraintSet &cstr, Value value)
: cstr(cstr), value(value) {}
private:
BoundBuilder(const BoundBuilder &) = delete;
BoundBuilder &operator=(const BoundBuilder &) = delete;
bool operator==(const BoundBuilder &) = delete;
bool operator!=(const BoundBuilder &) = delete;
ValueBoundsConstraintSet &cstr;
Value value;
std::optional<int64_t> dim;
};
public:
static char ID;
/// A variable that can be added to the constraint set as a "column". The
/// value bounds infrastructure can compute bounds for variables and compare
/// two variables.
///
/// Internally, a variable is represented as an affine map and operands.
class Variable {
public:
/// Construct a variable for an index-typed attribute or SSA value.
Variable(OpFoldResult ofr);
/// Construct a variable for an index-typed SSA value.
Variable(Value indexValue);
/// Construct a variable for a dimension of a shaped value.
Variable(Value shapedValue, int64_t dim);
/// Construct a variable for an index-typed attribute/SSA value or for a
/// dimension of a shaped value. A non-null dimension must be provided if
/// and only if `ofr` is a shaped value.
Variable(OpFoldResult ofr, std::optional<int64_t> dim);
/// Construct a variable for a map and its operands.
Variable(AffineMap map, ArrayRef<Variable> mapOperands);
Variable(AffineMap map, ValueRange mapOperands);
MLIRContext *getContext() const { return map.getContext(); }
/// Returns the affine map.
AffineMap getMap() const { return map; }
/// Returns the map operands.
ValueDimList &getOperands() { return mapOperands; }
const ValueDimList &getOperands() const { return mapOperands; }
private:
friend class ValueBoundsConstraintSet;
AffineMap map;
ValueDimList mapOperands;
};
/// The stop condition when traversing the backward slice of a shaped value/
/// index-type value. The traversal continues until the stop condition
/// evaluates to "true" for a value.
///
/// The first parameter of the function is the shaped value/index-typed
/// value. The second parameter is the dimension in case of a shaped value.
/// The third parameter is this constraint set.
using StopConditionFn = std::function<bool(
Value, std::optional<int64_t> /*dim*/, ValueBoundsConstraintSet &cstr)>;
/// Compute a bound for the given variable. The computed bound is stored in
/// `resultMap`. The operands of the bound are stored in `mapOperands`. An
/// operand is either an index-type SSA value or a shaped value and a
/// dimension.
///
/// The bound is computed in terms of values/dimensions for which
/// `stopCondition` evaluates to "true". To that end, the backward slice
/// (reverse use-def chain) of the given value is visited in a worklist-driven
/// manner and the constraint set is populated according to
/// `ValueBoundsOpInterface` for each visited value.
///
/// By default, lower/equal bounds are closed and upper bounds are open. If
/// `options.closedUB` is set to "true", upper bounds are also closed.
static LogicalResult
computeBound(AffineMap &resultMap, ValueDimList &mapOperands,
presburger::BoundType type, const Variable &var,
StopConditionFn stopCondition, ValueBoundsOptions options = {});
/// Compute a bound in terms of the values/dimensions in `dependencies`. The
/// computed bound consists of only constant terms and dependent values (or
/// dimension sizes thereof).
static LogicalResult computeDependentBound(AffineMap &resultMap,
ValueDimList &mapOperands,
presburger::BoundType type,
const Variable &var,
ValueDimList dependencies,
ValueBoundsOptions options = {});
/// Compute a bound in that is independent of all values in `independencies`.
///
/// Independencies are the opposite of dependencies. The computed bound does
/// not contain any SSA values that are part of `independencies`. E.g., this
/// function can be used to make ops hoistable from loops. To that end, ops
/// must be made independent of loop induction variables (in the case of "for"
/// loops). Loop induction variables are the independencies; they may not
/// appear in the computed bound.
static LogicalResult computeIndependentBound(AffineMap &resultMap,
ValueDimList &mapOperands,
presburger::BoundType type,
const Variable &var,
ValueRange independencies,
ValueBoundsOptions options = {});
/// Compute a constant bound for the given variable.
///
/// This function traverses the backward slice of the given operands in a
/// worklist-driven manner until `stopCondition` evaluates to "true". The
/// constraint set is populated according to `ValueBoundsOpInterface` for each
/// visited value. (No constraints are added for values for which the stop
/// condition evaluates to "true".)
///
/// The stop condition is optional: If none is specified, the backward slice
/// is traversed in a breadth-first manner until a constant bound could be
/// computed.
///
/// By default, lower/equal bounds are closed and upper bounds are open. If
/// `options.closedUB` is set to "true", upper bounds are also closed.
static FailureOr<int64_t>
computeConstantBound(presburger::BoundType type, const Variable &var,
const StopConditionFn &stopCondition = nullptr,
ValueBoundsOptions options = {});
/// Compute a constant delta between the given two values. Return "failure"
/// if a constant delta could not be determined.
///
/// `dim1`/`dim2` must be `nullopt` if and only if `value1`/`value2` are
/// index-typed.
static FailureOr<int64_t>
computeConstantDelta(Value value1, Value value2,
std::optional<int64_t> dim1 = std::nullopt,
std::optional<int64_t> dim2 = std::nullopt);
/// Traverse the IR starting from the given value/dim and populate constraints
/// as long as the stop condition holds. Also process all values/dims that are
/// already on the worklist.
void populateConstraints(Value value, std::optional<int64_t> dim);
/// Comparison operator for `ValueBoundsConstraintSet::compare`.
enum ComparisonOperator { LT, LE, EQ, GT, GE };
/// Populate constraints for lhs/rhs (until the stop condition is met). Then,
/// try to prove that, based on the current state of this constraint set
/// (i.e., without analyzing additional IR or adding new constraints), the
/// "lhs" value/dim is LE/LT/EQ/GT/GE than the "rhs" value/dim.
///
/// Return "true" if the specified relation between the two values/dims was
/// proven to hold. Return "false" if the specified relation could not be
/// proven. This could be because the specified relation does in fact not hold
/// or because there is not enough information in the constraint set. In other
/// words, if we do not know for sure, this function returns "false".
bool populateAndCompare(const Variable &lhs, ComparisonOperator cmp,
const Variable &rhs);
/// Return "true" if "lhs cmp rhs" was proven to hold. Return "false" if the
/// specified relation could not be proven. This could be because the
/// specified relation does in fact not hold or because there is not enough
/// information in the constraint set. In other words, if we do not know for
/// sure, this function returns "false".
///
/// This function keeps traversing the backward slice of lhs/rhs until could
/// prove the relation or until it ran out of IR.
static bool compare(const Variable &lhs, ComparisonOperator cmp,
const Variable &rhs);
/// This function is similar to `ValueBoundsConstraintSet::compare`, except
/// that it returns false if `!(lhs cmp rhs)`, and `failure` if neither the
/// relation nor its inverse relation could be proven.
static llvm::FailureOr<bool> strongCompare(const Variable &lhs,
ComparisonOperator cmp,
const Variable &rhs);
/// Compute whether the given variables are equal. Return "failure" if
/// equality could not be determined.
static FailureOr<bool> areEqual(const Variable &var1, const Variable &var2);
/// Return "true" if the given slices are guaranteed to be overlapping.
/// Return "false" if the given slices are guaranteed to be non-overlapping.
/// Return "failure" if unknown.
///
/// Slices are overlapping if for all dimensions:
/// * offset1 + size1 * stride1 <= offset2
/// * and offset2 + size2 * stride2 <= offset1
///
/// Slice are non-overlapping if the above constraint is not satisfied for
/// at least one dimension.
static FailureOr<bool>
areOverlappingSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1,
const HyperrectangularSlice &slice2);
/// Return "true" if the given slices are guaranteed to be equivalent.
/// Return "false" if the given slices are guaranteed to be non-equivalent.
/// Return "failure" if unknown.
///
/// Slices are equivalent if their offsets, sizes and strices are equal.
static FailureOr<bool>
areEquivalentSlices(MLIRContext *ctx, const HyperrectangularSlice &slice1,
const HyperrectangularSlice &slice2);
/// Add a bound for the given index-typed value or shaped value. This function
/// returns a builder that adds the bound.
BoundBuilder bound(Value value) { return BoundBuilder(*this, value); }
/// Return an expression that represents the given index-typed value or shaped
/// value dimension. If this value/dimension was not used so far, it is added
/// to the worklist.
///
/// `dim` must be `nullopt` if and only if the given value is of index type.
AffineExpr getExpr(Value value, std::optional<int64_t> dim = std::nullopt);
/// Return an expression that represents a constant or index-typed SSA value.
/// In case of a value, if this value was not used so far, it is added to the
/// worklist.
AffineExpr getExpr(OpFoldResult ofr);
/// Return an expression that represents a constant.
AffineExpr getExpr(int64_t constant);
/// Debugging only: Dump the constraint set and the column-to-value/dim
/// mapping to llvm::errs.
void dump() const;
protected:
/// Dimension identifier to indicate a value is index-typed. This is used for
/// internal data structures/API only.
static constexpr int64_t kIndexValue = -1;
/// An index-typed value or the dimension of a shaped-type value.
using ValueDim = std::pair<Value, int64_t>;
ValueBoundsConstraintSet(MLIRContext *ctx,
const StopConditionFn &stopCondition,
ValueBoundsOptions options = {},
bool addConservativeSemiAffineBounds = false);
/// Return "true" if, based on the current state of the constraint system,
/// "lhs cmp rhs" was proven to hold. Return "false" if the specified relation
/// could not be proven. This could be because the specified relation does in
/// fact not hold or because there is not enough information in the constraint
/// set. In other words, if we do not know for sure, this function returns
/// "false".
///
/// This function does not analyze any IR and does not populate any additional
/// constraints.
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 hold. It returns "false" if "!(lhs cmp rhs)"
/// can be proven. Otherwise, it returns `failure` if neither the relation nor
/// its inverse relation could be proven.
///
/// This function does not analyze any IR and does not populate any additional
/// constraints.
llvm::FailureOr<bool> strongComparePos(int64_t lhsPos, ComparisonOperator cmp,
int64_t rhsPos);
/// Given an affine map with a single result (and map operands), add a new
/// column to the constraint set that represents the result of the map.
/// Traverse additional IR starting from the map operands as needed (as long
/// as the stop condition is not satisfied). Also process all values/dims that
/// are already on the worklist. Return the position of the newly added
/// column.
int64_t populateConstraints(AffineMap map, ValueDimList mapOperands);
/// Iteratively process all elements on the worklist until an index-typed
/// value or shaped value meets `stopCondition`. Such values are not processed
/// any further.
void processWorklist();
/// Bound the given column in the underlying constraint set by the given
/// expression.
void addBound(presburger::BoundType type, int64_t pos, AffineExpr expr);
/// Return the column position of the given value/dimension. Asserts that the
/// value/dimension exists in the constraint set.
int64_t getPos(Value value, std::optional<int64_t> dim = std::nullopt) const;
/// Return an affine expression that represents column `pos` in the constraint
/// set.
AffineExpr getPosExpr(int64_t pos);
/// Return "true" if the given value/dim is mapped (i.e., has a corresponding
/// column in the constraint system).
bool isMapped(Value value, std::optional<int64_t> dim = std::nullopt) const;
/// Insert a value/dimension into the constraint set. If `isSymbol` is set to
/// "false", a dimension is added. The value/dimension is added to the
/// worklist if `addToWorklist` is set.
///
/// Note: There are certain affine restrictions wrt. dimensions. E.g., they
/// cannot be multiplied. Furthermore, bounds can only be queried for
/// dimensions but not for symbols.
int64_t insert(Value value, std::optional<int64_t> dim, bool isSymbol = true,
bool addToWorklist = true);
/// Insert an anonymous column into the constraint set. The column is not
/// bound to any value/dimension. If `isSymbol` is set to "false", a dimension
/// is added.
///
/// Note: There are certain affine restrictions wrt. dimensions. E.g., they
/// cannot be multiplied. Furthermore, bounds can only be queried for
/// dimensions but not for symbols.
int64_t insert(bool isSymbol = true);
/// Insert the given affine map and its bound operands as a new column in the
/// constraint system. Return the position of the new column. Any operands
/// that were not analyzed yet are put on the worklist.
int64_t insert(AffineMap map, const ValueDimList &operands,
bool isSymbol = true);
int64_t insert(const Variable &var, bool isSymbol = true);
/// Project out the given column in the constraint set.
void projectOut(int64_t pos);
/// Project out all columns for which the condition holds.
void projectOut(function_ref<bool(ValueDim)> condition);
void projectOutAnonymous(std::optional<int64_t> except = std::nullopt);
/// Mapping of columns to values/shape dimensions.
// Inline size chosen empirically based on compilation profiling.
// Profiled: 2.1M calls, avg=3.0+-1.9. N=4 covers ~70% of cases inline.
SmallVector<std::optional<ValueDim>, 4> positionToValueDim;
/// Reverse mapping of values/shape dimensions to columns.
DenseMap<ValueDim, int64_t> valueDimToPosition;
/// Worklist of values/shape dimensions that have not been processed yet.
std::queue<int64_t> worklist;
/// Constraint system of equalities and inequalities.
FlatLinearConstraints cstr;
/// Builder for constructing affine expressions.
Builder builder;
/// The current stop condition function.
StopConditionFn stopCondition = nullptr;
/// Options that control value bound computation.
ValueBoundsOptions options;
/// Should conservative bounds be added for semi-affine expressions.
bool addConservativeSemiAffineBounds = false;
};
} // namespace mlir
#include "mlir/Interfaces/ValueBoundsOpInterface.h.inc"
#endif // MLIR_INTERFACES_VALUEBOUNDSOPINTERFACE_H_