//===- IntegerRelation.h - MLIR IntegerRelation Class ---------*- 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 // //===----------------------------------------------------------------------===// // // A class to represent a relation over integer tuples. A relation is // represented as a constraint system over a space of tuples of integer valued // variables supporting symbolic variables and existential quantification. // //===----------------------------------------------------------------------===// #ifndef MLIR_ANALYSIS_PRESBURGER_INTEGERRELATION_H #define MLIR_ANALYSIS_PRESBURGER_INTEGERRELATION_H #include "mlir/Analysis/Presburger/Fraction.h" #include "mlir/Analysis/Presburger/Matrix.h" #include "mlir/Analysis/Presburger/PresburgerSpace.h" #include "mlir/Analysis/Presburger/Utils.h" #include "llvm/ADT/DynamicAPInt.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/LogicalResult.h" #include namespace mlir { namespace presburger { using llvm::DynamicAPInt; using llvm::failure; using llvm::int64fromDynamicAPInt; using llvm::LogicalResult; using llvm::SmallVectorImpl; using llvm::success; class IntegerRelation; class IntegerPolyhedron; class PresburgerSet; class PresburgerRelation; struct SymbolicLexOpt; /// The type of bound: equal, lower bound or upper bound. enum class BoundType { EQ, LB, UB }; /// An IntegerRelation represents the set of points from a PresburgerSpace that /// satisfy a list of affine constraints. Affine constraints can be inequalities /// or equalities in the form: /// /// Inequality: c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} + c_n >= 0 /// Equality : c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} + c_n == 0 /// /// where c_0, c_1, ..., c_n are integers and n is the total number of /// variables in the space. /// /// Such a relation corresponds to the set of integer points lying in a convex /// polyhedron. For example, consider the relation: /// (x) -> (y) : (1 <= x <= 7, x = 2y) /// These can be thought of as points in the polyhedron: /// (x, y) : (1 <= x <= 7, x = 2y) /// This relation contains the pairs (2, 1), (4, 2), and (6, 3). /// /// Since IntegerRelation makes a distinction between dimensions, VarKind::Range /// and VarKind::Domain should be used to refer to dimension variables. class IntegerRelation { public: /// All derived classes of IntegerRelation. enum class Kind { IntegerRelation, IntegerPolyhedron, FlatLinearConstraints, FlatLinearValueConstraints, FlatAffineValueConstraints, FlatAffineRelation }; /// Constructs a relation reserving memory for the specified number /// of constraints and variables. IntegerRelation(unsigned numReservedInequalities, unsigned numReservedEqualities, unsigned numReservedCols, const PresburgerSpace &space) : space(space), equalities(0, space.getNumVars() + 1, numReservedEqualities, numReservedCols), inequalities(0, space.getNumVars() + 1, numReservedInequalities, numReservedCols) { assert(numReservedCols >= space.getNumVars() + 1); } /// Constructs a relation with the specified number of dimensions and symbols. explicit IntegerRelation(const PresburgerSpace &space) : IntegerRelation(/*numReservedInequalities=*/0, /*numReservedEqualities=*/0, /*numReservedCols=*/space.getNumVars() + 1, space) {} virtual ~IntegerRelation() = default; /// Return a system with no constraints, i.e., one which is satisfied by all /// points. static IntegerRelation getUniverse(const PresburgerSpace &space) { return IntegerRelation(space); } /// Return an empty system containing an invalid equation 0 = 1. static IntegerRelation getEmpty(const PresburgerSpace &space) { IntegerRelation result(0, 1, space.getNumVars() + 1, space); SmallVector invalidEq(space.getNumVars() + 1, 0); invalidEq.back() = 1; result.addEquality(invalidEq); return result; } /// Return the kind of this IntegerRelation. virtual Kind getKind() const { return Kind::IntegerRelation; } static bool classof(const IntegerRelation *cst) { return true; } // Clones this object. std::unique_ptr clone() const; /// Returns a reference to the underlying space. const PresburgerSpace &getSpace() const { return space; } /// Set the space to `oSpace`, which should have the same number of ids as /// the current space. void setSpace(const PresburgerSpace &oSpace); /// Set the space to `oSpace`, which should not have any local ids. /// `oSpace` can have fewer ids than the current space; in that case, the /// the extra ids in `this` that are not accounted for by `oSpace` will be /// considered as local ids. `oSpace` should not have more ids than the /// current space; this will result in an assert failure. void setSpaceExceptLocals(const PresburgerSpace &oSpace); /// Set the identifier for the ith variable of the specified kind of the /// IntegerRelation's PresburgerSpace. The index is relative to the kind of /// the variable. void setId(VarKind kind, unsigned i, Identifier id); void resetIds() { space.resetIds(); } /// Get the identifiers for the variables of specified varKind. Calls resetIds /// on the relations space if identifiers are not enabled. ArrayRef getIds(VarKind kind); /// Returns a copy of the space without locals. PresburgerSpace getSpaceWithoutLocals() const { return PresburgerSpace::getRelationSpace(space.getNumDomainVars(), space.getNumRangeVars(), space.getNumSymbolVars()); } /// Appends constraints from `other` into `this`. This is equivalent to an /// intersection with no simplification of any sort attempted. void append(const IntegerRelation &other); /// Finds an equality that equates the specified variable to a constant. /// Returns the position of the equality row. If 'symbolic' is set to true, /// symbols are also treated like a constant, i.e., an affine function of the /// symbols is also treated like a constant. Returns -1 if such an equality /// could not be found. int findEqualityToConstant(unsigned pos, bool symbolic = false) const; /// Return the intersection of the two relations. /// If there are locals, they will be merged. IntegerRelation intersect(IntegerRelation other) const; /// Return whether `this` and `other` are equal. This is integer-exact /// and somewhat expensive, since it uses the integer emptiness check /// (see IntegerRelation::findIntegerSample()). bool isEqual(const IntegerRelation &other) const; /// Perform a quick equality check on `this` and `other`. The relations are /// equal if the check return true, but may or may not be equal if the check /// returns false. The equality check is performed in a plain manner, by /// comparing if all the equalities and inequalities in `this` and `other` /// are the same. bool isObviouslyEqual(const IntegerRelation &other) const; /// Return whether this is a subset of the given IntegerRelation. This is /// integer-exact and somewhat expensive, since it uses the integer emptiness /// check (see IntegerRelation::findIntegerSample()). bool isSubsetOf(const IntegerRelation &other) const; /// Returns the value at the specified equality row and column. inline DynamicAPInt atEq(unsigned i, unsigned j) const { return equalities(i, j); } /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. inline int64_t atEq64(unsigned i, unsigned j) const { return int64_t(equalities(i, j)); } inline DynamicAPInt &atEq(unsigned i, unsigned j) { return equalities(i, j); } /// Returns the value at the specified inequality row and column. inline DynamicAPInt atIneq(unsigned i, unsigned j) const { return inequalities(i, j); } /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. inline int64_t atIneq64(unsigned i, unsigned j) const { return int64_t(inequalities(i, j)); } inline DynamicAPInt &atIneq(unsigned i, unsigned j) { return inequalities(i, j); } unsigned getNumConstraints() const { return getNumInequalities() + getNumEqualities(); } /// Unified indexing into the constraints. Index into the inequalities /// if i < getNumInequalities() and into the equalities otherwise. inline int64_t atConstraint64(unsigned i, unsigned j) const { assert(i < getNumConstraints()); unsigned numIneqs = getNumInequalities(); return i < numIneqs ? atIneq64(i, j) : atEq64(i - numIneqs, j); } inline DynamicAPInt &atConstraint(unsigned i, unsigned j) { assert(i < getNumConstraints()); unsigned numIneqs = getNumInequalities(); return i < numIneqs ? atIneq(i, j) : atEq(i - numIneqs, j); } unsigned getNumDomainVars() const { return space.getNumDomainVars(); } unsigned getNumRangeVars() const { return space.getNumRangeVars(); } unsigned getNumSymbolVars() const { return space.getNumSymbolVars(); } unsigned getNumLocalVars() const { return space.getNumLocalVars(); } unsigned getNumDimVars() const { return space.getNumDimVars(); } unsigned getNumDimAndSymbolVars() const { return space.getNumDimAndSymbolVars(); } unsigned getNumVars() const { return space.getNumVars(); } /// Returns the number of columns in the constraint system. inline unsigned getNumCols() const { return space.getNumVars() + 1; } inline unsigned getNumEqualities() const { return equalities.getNumRows(); } inline unsigned getNumInequalities() const { return inequalities.getNumRows(); } inline unsigned getNumReservedEqualities() const { return equalities.getNumReservedRows(); } inline unsigned getNumReservedInequalities() const { return inequalities.getNumReservedRows(); } inline ArrayRef getEquality(unsigned idx) const { return equalities.getRow(idx); } inline ArrayRef getInequality(unsigned idx) const { return inequalities.getRow(idx); } /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. inline SmallVector getEquality64(unsigned idx) const { return getInt64Vec(equalities.getRow(idx)); } inline SmallVector getInequality64(unsigned idx) const { return getInt64Vec(inequalities.getRow(idx)); } inline IntMatrix getInequalities() const { return inequalities; } /// Get the number of vars of the specified kind. unsigned getNumVarKind(VarKind kind) const { return space.getNumVarKind(kind); } /// Return the index at which the specified kind of vars starts. unsigned getVarKindOffset(VarKind kind) const { return space.getVarKindOffset(kind); } /// Return the index at Which the specified kind of vars ends. unsigned getVarKindEnd(VarKind kind) const { return space.getVarKindEnd(kind); } /// Return an interator over the variables of the specified kind /// starting at the relevant offset. The return type is auto in /// keeping with the convention for iterators. auto iterVarKind(VarKind kind) const { return llvm::seq(getVarKindOffset(kind), getVarKindEnd(kind)); } /// Get the number of elements of the specified kind in the range /// [varStart, varLimit). unsigned getVarKindOverlap(VarKind kind, unsigned varStart, unsigned varLimit) const { return space.getVarKindOverlap(kind, varStart, varLimit); } /// Return the VarKind of the var at the specified position. VarKind getVarKindAt(unsigned pos) const { return space.getVarKindAt(pos); } /// The struct CountsSnapshot stores the count of each VarKind, and also of /// each constraint type. getCounts() returns a CountsSnapshot object /// describing the current state of the IntegerRelation. truncate() truncates /// all vars of each VarKind and all constraints of both kinds beyond the /// counts in the specified CountsSnapshot object. This can be used to achieve /// rudimentary rollback support. As long as none of the existing constraints /// or vars are disturbed, and only additional vars or constraints are added, /// this addition can be rolled back using truncate. struct CountsSnapshot { public: CountsSnapshot(const PresburgerSpace &space, unsigned numIneqs, unsigned numEqs) : space(space), numIneqs(numIneqs), numEqs(numEqs) {} const PresburgerSpace &getSpace() const { return space; }; unsigned getNumIneqs() const { return numIneqs; } unsigned getNumEqs() const { return numEqs; } private: PresburgerSpace space; unsigned numIneqs, numEqs; }; CountsSnapshot getCounts() const; void truncate(const CountsSnapshot &counts); /// Insert `num` variables of the specified kind at position `pos`. /// Positions are relative to the kind of variable. The coefficient columns /// corresponding to the added variables are initialized to zero. Return the /// absolute column position (i.e., not relative to the kind of variable) /// of the first added variable. virtual unsigned insertVar(VarKind kind, unsigned pos, unsigned num = 1); /// Append `num` variables of the specified kind after the last variable /// of that kind. The coefficient columns corresponding to the added variables /// are initialized to zero. Return the absolute column position (i.e., not /// relative to the kind of variable) of the first appended variable. unsigned appendVar(VarKind kind, unsigned num = 1); /// Adds an inequality (>= 0) from the coefficients specified in `inEq`. void addInequality(ArrayRef inEq); void addInequality(ArrayRef inEq) { addInequality(getDynamicAPIntVec(inEq)); } /// Adds an equality from the coefficients specified in `eq`. void addEquality(ArrayRef eq); void addEquality(ArrayRef eq) { addEquality(getDynamicAPIntVec(eq)); } /// Eliminate the `posB^th` local variable, replacing every instance of it /// with the `posA^th` local variable. This should be used when the two /// local variables are known to always take the same values. virtual void eliminateRedundantLocalVar(unsigned posA, unsigned posB); /// Removes variables of the specified kind with the specified pos (or /// within the specified range) from the system. The specified location is /// relative to the first variable of the specified kind. void removeVar(VarKind kind, unsigned pos); virtual void removeVarRange(VarKind kind, unsigned varStart, unsigned varLimit); /// Removes the specified variable from the system. void removeVar(unsigned pos); void removeEquality(unsigned pos); void removeInequality(unsigned pos); void removeConstraint(unsigned pos); /// Remove the (in)equalities at positions [start, end). void removeEqualityRange(unsigned start, unsigned end); void removeInequalityRange(unsigned start, unsigned end); /// Get the lexicographically minimum rational point satisfying the /// constraints. Returns an empty optional if the relation is empty or if /// the lexmin is unbounded. Symbols are not supported and will result in /// assert-failure. Note that Domain is minimized first, then range. MaybeOptimum> findRationalLexMin() const; /// Same as above, but returns lexicographically minimal integer point. /// Note: this should be used only when the lexmin is really required. /// For a generic integer sampling operation, findIntegerSample is more /// robust and should be preferred. Note that Domain is minimized first, then /// range. MaybeOptimum> findIntegerLexMin() const; /// Swap the posA^th variable with the posB^th variable. virtual void swapVar(unsigned posA, unsigned posB); /// Removes all equalities and inequalities. void clearConstraints(); /// Sets the `values.size()` variables starting at `po`s to the specified /// values and removes them. void setAndEliminate(unsigned pos, ArrayRef values); void setAndEliminate(unsigned pos, ArrayRef values) { setAndEliminate(pos, getDynamicAPIntVec(values)); } /// Replaces the contents of this IntegerRelation with `other`. virtual void clearAndCopyFrom(const IntegerRelation &other); /// Gather positions of all lower and upper bounds of the variable at `pos`, /// and optionally any equalities on it. In addition, the bounds are to be /// independent of variables in position range [`offset`, `offset` + `num`). void getLowerAndUpperBoundIndices(unsigned pos, SmallVectorImpl *lbIndices, SmallVectorImpl *ubIndices, SmallVectorImpl *eqIndices = nullptr, unsigned offset = 0, unsigned num = 0) const; /// Checks for emptiness by performing variable elimination on all /// variables, running the GCD test on each equality constraint, and /// checking for invalid constraints. Returns true if the GCD test fails for /// any equality, or if any invalid constraints are discovered on any row. /// Returns false otherwise. bool isEmpty() const; /// Performs GCD checks and invalid constraint checks. bool isObviouslyEmpty() const; /// Runs the GCD test on all equality constraints. Returns true if this test /// fails on any equality. Returns false otherwise. /// This test can be used to disprove the existence of a solution. If it /// returns true, no integer solution to the equality constraints can exist. bool isEmptyByGCDTest() const; /// Returns true if the set of constraints is found to have no solution, /// false if a solution exists. Uses the same algorithm as /// `findIntegerSample`. bool isIntegerEmpty() const; /// Returns a matrix where each row is a vector along which the polytope is /// bounded. The span of the returned vectors is guaranteed to contain all /// such vectors. The returned vectors are NOT guaranteed to be linearly /// independent. This function should not be called on empty sets. IntMatrix getBoundedDirections() const; /// Find an integer sample point satisfying the constraints using a /// branch and bound algorithm with generalized basis reduction, with some /// additional processing using Simplex for unbounded sets. /// /// Returns an integer sample point if one exists, or an empty Optional /// otherwise. The returned value also includes values of local ids. std::optional> findIntegerSample() const; /// Compute an overapproximation of the number of integer points in the /// relation. Symbol vars currently not supported. If the computed /// overapproximation is infinite, an empty optional is returned. std::optional computeVolume() const; /// Returns true if the given point satisfies the constraints, or false /// otherwise. Takes the values of all vars including locals. bool containsPoint(ArrayRef point) const; bool containsPoint(ArrayRef point) const { return containsPoint(getDynamicAPIntVec(point)); } /// Given the values of non-local vars, return a satisfying assignment to the /// local if one exists, or an empty optional otherwise. std::optional> containsPointNoLocal(ArrayRef point) const; std::optional> containsPointNoLocal(ArrayRef point) const { return containsPointNoLocal(getDynamicAPIntVec(point)); } /// Returns a `DivisonRepr` representing the division representation of local /// variables in the constraint system. /// /// If `repr` is not `nullptr`, the equality and pairs of inequality /// constraints identified by their position indices using which an explicit /// representation for each local variable can be computed are set in `repr` /// in the form of a `MaybeLocalRepr` struct. If no such inequality /// pair/equality can be found, the kind attribute in `MaybeLocalRepr` is set /// to None. DivisionRepr getLocalReprs(std::vector *repr = nullptr) const; /// Adds a constant bound for the specified variable. void addBound(BoundType type, unsigned pos, const DynamicAPInt &value); void addBound(BoundType type, unsigned pos, int64_t value) { addBound(type, pos, DynamicAPInt(value)); } /// Adds a constant bound for the specified expression. void addBound(BoundType type, ArrayRef expr, const DynamicAPInt &value); void addBound(BoundType type, ArrayRef expr, int64_t value) { addBound(type, getDynamicAPIntVec(expr), DynamicAPInt(value)); } /// Adds a new local variable as the floordiv of an affine function of other /// variables, the coefficients of which are provided in `dividend` and with /// respect to a positive constant `divisor`. Two constraints are added to the /// system to capture equivalence with the floordiv: /// q = dividend floordiv c <=> c*q <= dividend <= c*q + c - 1. /// Returns the column position of the new local variable. unsigned addLocalFloorDiv(ArrayRef dividend, const DynamicAPInt &divisor); unsigned addLocalFloorDiv(ArrayRef dividend, int64_t divisor) { return addLocalFloorDiv(getDynamicAPIntVec(dividend), DynamicAPInt(divisor)); } /// Adds a new local variable as the modulus of an affine function of other /// variables, the coefficients of which are provided in `exprs`. The modulus /// is with respect to a positive constant `modulus`. The function returns the /// absolute index of the new local variable representing the result of the /// modulus operation. Two new local variables are added to the system, one /// representing the floor div with respect to the modulus and one /// representing the mod. Three constraints are added to the system to capture /// the equivalance. The first two are required to compute the result of the /// floor division `q`, and the third computes the equality relation: /// result = exprs - modulus * q. unsigned addLocalModulo(ArrayRef exprs, const DynamicAPInt &modulus); unsigned addLocalModulo(ArrayRef exprs, int64_t modulus) { return addLocalModulo(getDynamicAPIntVec(exprs), DynamicAPInt(modulus)); } /// Projects out (aka eliminates) `num` variables starting at position /// `pos`. The resulting constraint system is the shadow along the dimensions /// that still exist. This method may not always be integer exact. // TODO: deal with integer exactness when necessary - can return a value to // mark exactness for example. void projectOut(unsigned pos, unsigned num); inline void projectOut(unsigned pos) { return projectOut(pos, 1); } /// The function removes some constraints that do not impose any bound on the /// specified variable. /// /// The set of constraints (equations/inequalities) can be modeled as an /// undirected graph where: /// 1. Variables are the nodes. /// 2. Constraints are the edges connecting those nodes. /// /// Variables and constraints belonging to different connected components /// are irrelevant to each other. This property allows for safe pruning of /// constraints. /// /// For example, given the following constraints: /// - Inequalities: (1) d0 + d1 > 0, (2) d1 >= 2, (3) d4 > 5 /// - Equalities: (4) d3 + d4 = 1, (5) d0 - d2 = 3 /// /// These form two connected components: /// - Component 1: {d0, d1, d2} (related by constraints 1, 2, 5) /// - Component 2: {d3, d4} (related by constraint 4) /// /// If we are querying the bound of variable `d0`, constraints related to /// Component 2 (e.g., constraints 3 and 4) can be safely pruned as they /// have no impact on the solution space of Component 1. /// This function prunes irrelevant constraints by identifying all variables /// and constraints that belong to the same connected component as the /// target variable. void pruneOrthogonalConstraints(unsigned pos); /// Tries to fold the specified variable to a constant using a trivial /// equality detection; if successful, the constant is substituted for the /// variable everywhere in the constraint system and then removed from the /// system. LogicalResult constantFoldVar(unsigned pos); /// This method calls `constantFoldVar` for the specified range of variables, /// `num` variables starting at position `pos`. void constantFoldVarRange(unsigned pos, unsigned num); /// Updates the constraints to be the smallest bounding (enclosing) box that /// contains the points of `this` set and that of `other`, with the symbols /// being treated specially. For each of the dimensions, the min of the lower /// bounds (symbolic) and the max of the upper bounds (symbolic) is computed /// to determine such a bounding box. `other` is expected to have the same /// dimensional variables as this constraint system (in the same order). /// /// E.g.: /// 1) this = {0 <= d0 <= 127}, /// other = {16 <= d0 <= 192}, /// output = {0 <= d0 <= 192} /// 2) this = {s0 + 5 <= d0 <= s0 + 20}, /// other = {s0 + 1 <= d0 <= s0 + 9}, /// output = {s0 + 1 <= d0 <= s0 + 20} /// 3) this = {0 <= d0 <= 5, 1 <= d1 <= 9} /// other = {2 <= d0 <= 6, 5 <= d1 <= 15}, /// output = {0 <= d0 <= 6, 1 <= d1 <= 15} LogicalResult unionBoundingBox(const IntegerRelation &other); /// Returns the smallest known constant bound for the extent of the specified /// variable (pos^th), i.e., the smallest known constant that is greater /// than or equal to 'exclusive upper bound' - 'lower bound' of the /// variable. This constant bound is guaranteed to be non-negative. Returns /// std::nullopt if it's not a constant. This method employs trivial (low /// complexity / cost) checks and detection. Symbolic variables are treated /// specially, i.e., it looks for constant differences between affine /// expressions involving only the symbolic variables. `lb` and `ub` (along /// with the `boundFloorDivisor`) are set to represent the lower and upper /// bound associated with the constant difference: `lb`, `ub` have the /// coefficients, and `boundFloorDivisor`, their divisor. `minLbPos` and /// `minUbPos` if non-null are set to the position of the constant lower bound /// and upper bound respectively (to the same if they are from an /// equality). Ex: if the lower bound is [(s0 + s2 - 1) floordiv 32] for a /// system with three symbolic variables, *lb = [1, 0, 1], lbDivisor = 32. See /// comments at function definition for examples. std::optional getConstantBoundOnDimSize( unsigned pos, SmallVectorImpl *lb = nullptr, DynamicAPInt *boundFloorDivisor = nullptr, SmallVectorImpl *ub = nullptr, unsigned *minLbPos = nullptr, unsigned *minUbPos = nullptr) const; /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. std::optional getConstantBoundOnDimSize64( unsigned pos, SmallVectorImpl *lb = nullptr, int64_t *boundFloorDivisor = nullptr, SmallVectorImpl *ub = nullptr, unsigned *minLbPos = nullptr, unsigned *minUbPos = nullptr) const { SmallVector ubDynamicAPInt, lbDynamicAPInt; DynamicAPInt boundFloorDivisorDynamicAPInt; std::optional result = getConstantBoundOnDimSize( pos, &lbDynamicAPInt, &boundFloorDivisorDynamicAPInt, &ubDynamicAPInt, minLbPos, minUbPos); if (lb) *lb = getInt64Vec(lbDynamicAPInt); if (ub) *ub = getInt64Vec(ubDynamicAPInt); if (boundFloorDivisor) *boundFloorDivisor = static_cast(boundFloorDivisorDynamicAPInt); return llvm::transformOptional(result, int64fromDynamicAPInt); } /// Returns the constant bound for the pos^th variable if there is one; /// std::nullopt otherwise. std::optional getConstantBound(BoundType type, unsigned pos) const; /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. std::optional getConstantBound64(BoundType type, unsigned pos) const { return llvm::transformOptional(getConstantBound(type, pos), int64fromDynamicAPInt); } /// Removes constraints that are independent of (i.e., do not have a /// coefficient) variables in the range [pos, pos + num). void removeIndependentConstraints(unsigned pos, unsigned num); /// Returns true if the set can be trivially detected as being /// hyper-rectangular on the specified contiguous set of variables. bool isHyperRectangular(unsigned pos, unsigned num) const; /// Removes duplicate constraints, trivially true constraints, and constraints /// that can be detected as redundant as a result of differing only in their /// constant term part. A constraint of the form >= 0 /// is considered trivially true. This method is a linear time method on the /// constraints, does a single scan, and updates in place. It also normalizes /// constraints by their GCD and performs GCD tightening on inequalities. void removeTrivialRedundancy(); /// A more expensive check than `removeTrivialRedundancy` to detect redundant /// inequalities. void removeRedundantInequalities(); /// Removes redundant constraints using Simplex. Although the algorithm can /// theoretically take exponential time in the worst case (rare), it is known /// to perform much better in the average case. If V is the number of vertices /// in the polytope and C is the number of constraints, the algorithm takes /// O(VC) time. void removeRedundantConstraints(); void removeDuplicateDivs(); /// Simplify the constraint system by removing canonicalizing constraints and /// removing redundant constraints. void simplify(); /// Converts variables of kind srcKind in the range [varStart, varLimit) to /// variables of kind dstKind. If `pos` is given, the variables are placed at /// position `pos` of dstKind, otherwise they are placed after all the other /// variables of kind dstKind. The internal ordering among the moved variables /// is preserved. void convertVarKind(VarKind srcKind, unsigned varStart, unsigned varLimit, VarKind dstKind, unsigned pos); void convertVarKind(VarKind srcKind, unsigned varStart, unsigned varLimit, VarKind dstKind) { convertVarKind(srcKind, varStart, varLimit, dstKind, getNumVarKind(dstKind)); } void convertToLocal(VarKind kind, unsigned varStart, unsigned varLimit) { convertVarKind(kind, varStart, varLimit, VarKind::Local); } /// Merge and align symbol variables of `this` and `other` with respect to /// identifiers. After this operation the symbol variables of both relations /// have the same identifiers in the same order. void mergeAndAlignSymbols(IntegerRelation &other); /// Adds additional local vars to the sets such that they both have the union /// of the local vars in each set, without changing the set of points that /// lie in `this` and `other`. /// /// While taking union, if a local var in `other` has a division /// representation which is a duplicate of division representation, of another /// local var, it is not added to the final union of local vars and is instead /// merged. The new ordering of local vars is: /// /// [Local vars of `this`] [Non-merged local vars of `other`] /// /// The relative ordering of local vars is same as before. /// /// After merging, if the `i^th` local variable in one set has a known /// division representation, then the `i^th` local variable in the other set /// either has the same division representation or no known division /// representation. /// /// The spaces of both relations should be compatible. /// /// Returns the number of non-merged local vars of `other`, i.e. the number of /// locals that have been added to `this`. unsigned mergeLocalVars(IntegerRelation &other); /// Check whether all local ids have a division representation. bool hasOnlyDivLocals() const; /// Changes the partition between dimensions and symbols. Depending on the new /// symbol count, either a chunk of dimensional variables immediately before /// the split become symbols, or some of the symbols immediately after the /// split become dimensions. void setDimSymbolSeparation(unsigned newSymbolCount) { space.setVarSymbolSeparation(newSymbolCount); } /// Return a set corresponding to all points in the domain of the relation. IntegerPolyhedron getDomainSet() const; /// Return a set corresponding to all points in the range of the relation. IntegerPolyhedron getRangeSet() const; /// Intersect the given `poly` with the domain in-place. /// /// Formally, let the relation `this` be R: A -> B and poly is C, then this /// operation modifies R to be (A intersection C) -> B. void intersectDomain(const IntegerPolyhedron &poly); /// Intersect the given `poly` with the range in-place. /// /// Formally, let the relation `this` be R: A -> B and poly is C, then this /// operation modifies R to be A -> (B intersection C). void intersectRange(const IntegerPolyhedron &poly); /// Invert the relation i.e., swap its domain and range. /// /// Formally, let the relation `this` be R: A -> B, then this operation /// modifies R to be B -> A. void inverse(); /// Let the relation `this` be R1, and the relation `rel` be R2. Modifies R1 /// to be the composition of R1 and R2: R1;R2. /// /// Formally, if R1: A -> B, and R2: B -> C, then this function returns a /// relation R3: A -> C such that a point (a, c) belongs to R3 iff there /// exists b such that (a, b) is in R1 and, (b, c) is in R2. void compose(const IntegerRelation &rel); /// Given a relation `rel`, apply the relation to the domain of this relation. /// /// R1: i -> j : (0 <= i < 2, j = i) /// R2: i -> k : (k = i floordiv 2) /// R3: k -> j : (0 <= k < 1, 2k <= j <= 2k + 1) /// /// R1 = {(0, 0), (1, 1)}. R2 maps both 0 and 1 to 0. /// So R3 = {(0, 0), (0, 1)}. /// /// Formally, R1.applyDomain(R2) = R2.inverse().compose(R1). void applyDomain(const IntegerRelation &rel); /// Given a relation `rel`, apply the relation to the range of this relation. /// /// Formally, R1.applyRange(R2) is the same as R1.compose(R2) but we provide /// this for uniformity with `applyDomain`. void applyRange(const IntegerRelation &rel); /// Let the relation `this` be R1, and the relation `rel` be R2. Requires /// R1 and R2 to have the same domain. /// /// Let R3 be the rangeProduct of R1 and R2. Then x R3 (y, z) iff /// (x R1 y and x R2 z). /// /// Example: /// /// R1: (i, j) -> k : f(i, j, k) = 0 /// R2: (i, j) -> l : g(i, j, l) = 0 /// R1.rangeProduct(R2): (i, j) -> (k, l) : f(i, j, k) = 0 and g(i, j, l) = 0 IntegerRelation rangeProduct(const IntegerRelation &rel); /// Given a relation `other: (A -> B)`, this operation merges the symbol and /// local variables and then takes the composition of `other` on `this: (B -> /// C)`. The resulting relation represents tuples of the form: `A -> C`. void mergeAndCompose(const IntegerRelation &other); /// Compute an equivalent representation of the same set, such that all local /// vars in all disjuncts have division representations. This representation /// may involve local vars that correspond to divisions, and may also be a /// union of convex disjuncts. PresburgerRelation computeReprWithOnlyDivLocals() const; /// Compute the symbolic integer lexmin of the relation. /// /// This finds, for every assignment to the symbols and domain, /// the lexicographically minimum value attained by the range. /// /// For example, the symbolic lexmin of the set /// /// (x, y)[a, b, c] : (a <= x, b <= x, x <= c) /// /// can be written as /// /// x = a if b <= a, a <= c /// x = b if a < b, b <= c /// /// This function is stored in the `lexopt` function in the result. /// Some assignments to the symbols might make the set empty. /// Such points are not part of the function's domain. /// In the above example, this happens when max(a, b) > c. /// /// For some values of the symbols, the lexmin may be unbounded. /// `SymbolicLexOpt` stores these parts of the symbolic domain in a separate /// `PresburgerSet`, `unboundedDomain`. SymbolicLexOpt findSymbolicIntegerLexMin() const; /// Same as findSymbolicIntegerLexMin but produces lexmax instead of lexmin SymbolicLexOpt findSymbolicIntegerLexMax() const; /// Finds a constraint with a non-zero coefficient at `colIdx` in equality /// (isEq=true) or inequality (isEq=false) constraints. Returns the position /// of the row if it was found or none otherwise. std::optional findConstraintWithNonZeroAt(unsigned colIdx, bool isEq) const; /// Return the set difference of this set and the given set, i.e., /// return `this \ set`. PresburgerRelation subtract(const PresburgerRelation &set) const; // Remove equalities which have only zero coefficients. void removeTrivialEqualities(); // Verify whether the relation is full-dimensional, i.e., // no equality holds for the relation. // // If there are no variables, it always returns true. // If there is at least one variable and the relation is empty, it returns // false. bool isFullDim(); void print(raw_ostream &os) const; void dump() const; protected: /// Checks all rows of equality/inequality constraints for trivial /// contradictions (for example: 1 == 0, 0 >= 1), which may have surfaced /// after elimination. Returns true if an invalid constraint is found; /// false otherwise. bool hasInvalidConstraint() const; /// Returns the constant lower bound if isLower is true, and the upper /// bound if isLower is false. template std::optional computeConstantLowerOrUpperBound(unsigned pos); /// The same, but casts to int64_t. This is unsafe and will assert-fail if the /// value does not fit in an int64_t. template std::optional computeConstantLowerOrUpperBound64(unsigned pos) { return computeConstantLowerOrUpperBound(pos).map( int64fromDynamicAPInt); } /// Eliminates a single variable at `position` from equality and inequality /// constraints. Returns `success` if the variable was eliminated, and /// `failure` otherwise. inline LogicalResult gaussianEliminateVar(unsigned position) { return success(gaussianEliminateVars(position, position + 1) == 1); } /// Removes local variables using equalities. Each equality is checked if it /// can be reduced to the form: `e = affine-expr`, where `e` is a local /// variable and `affine-expr` is an affine expression not containing `e`. /// If an equality satisfies this form, the local variable is replaced in /// each constraint and then removed. The equality used to replace this local /// variable is also removed. void removeRedundantLocalVars(); /// Eliminates variables from equality and inequality constraints /// in column range [posStart, posLimit). /// Returns the number of variables eliminated. unsigned gaussianEliminateVars(unsigned posStart, unsigned posLimit); /// Perform a Gaussian elimination operation to reduce all equations to /// standard form. Returns whether the constraint system was modified. bool gaussianEliminate(); /// Eliminates the variable at the specified position using Fourier-Motzkin /// variable elimination, but uses Gaussian elimination if there is an /// equality involving that variable. If the result of the elimination is /// integer exact, `*isResultIntegerExact` is set to true. If `darkShadow` is /// set to true, a potential under approximation (subset) of the rational /// shadow / exact integer shadow is computed. // See implementation comments for more details. virtual void fourierMotzkinEliminate(unsigned pos, bool darkShadow = false, bool *isResultIntegerExact = nullptr); /// Tightens inequalities given that we are dealing with integer spaces. This /// is similar to the GCD test but applied to inequalities. The constant term /// can be reduced to the preceding multiple of the GCD of the coefficients, /// i.e., /// 64*i - 100 >= 0 => 64*i - 128 >= 0 (since 'i' is an integer). This is a /// fast method (linear in the number of coefficients). void gcdTightenInequalities(); /// Normalized each constraints by the GCD of its coefficients. void normalizeConstraintsByGCD(); /// Returns true if the pos^th column is all zero for both inequalities and /// equalities. bool isColZero(unsigned pos) const; /// Checks for identical inequalities and eliminates redundant inequalities. /// Returns whether the constraint system was modified. bool removeDuplicateConstraints(); /// Returns false if the fields corresponding to various variable counts, or /// equality/inequality buffer sizes aren't consistent; true otherwise. This /// is meant to be used within an assert internally. virtual bool hasConsistentState() const; /// Prints the number of constraints, dimensions, symbols and locals in the /// IntegerRelation. virtual void printSpace(raw_ostream &os) const; /// Removes variables in the column range [varStart, varLimit), and copies any /// remaining valid data into place, updates member variables, and resizes /// arrays as needed. void removeVarRange(unsigned varStart, unsigned varLimit); /// Truncate the vars of the specified kind to the specified number by /// dropping some vars at the end. `num` must be less than the current number. void truncateVarKind(VarKind kind, unsigned num); /// Truncate the vars to the number in the space of the specified /// CountsSnapshot. void truncateVarKind(VarKind kind, const CountsSnapshot &counts); /// A parameter that controls detection of an unrealistic number of /// constraints. If the number of constraints is this many times the number of /// variables, we consider such a system out of line with the intended use /// case of IntegerRelation. // The rationale for 32 is that in the typical simplest of cases, an // variable is expected to have one lower bound and one upper bound // constraint. With a level of tiling or a connection to another variable // through a div or mod, an extra pair of bounds gets added. As a limit, we // don't expect a variable to have more than 32 lower/upper/equality // constraints. This is conservatively set low and can be raised if needed. constexpr static unsigned kExplosionFactor = 32; PresburgerSpace space; /// Coefficients of affine equalities (in == 0 form). IntMatrix equalities; /// Coefficients of affine inequalities (in >= 0 form). IntMatrix inequalities; }; inline raw_ostream &operator<<(raw_ostream &os, const IntegerRelation &rel) { rel.print(os); return os; } /// An IntegerPolyhedron represents the set of points from a PresburgerSpace /// that satisfy a list of affine constraints. Affine constraints can be /// inequalities or equalities in the form: /// /// Inequality: c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} + c_n >= 0 /// Equality : c_0*x_0 + c_1*x_1 + .... + c_{n-1}*x_{n-1} + c_n == 0 /// /// where c_0, c_1, ..., c_n are integers and n is the total number of /// variables in the space. /// /// An IntegerPolyhedron is similar to an IntegerRelation but it does not make a /// distinction between Domain and Range variables. Internally, /// IntegerPolyhedron is implemented as a IntegerRelation with zero domain vars. /// /// Since IntegerPolyhedron does not make a distinction between kinds of /// dimensions, VarKind::SetDim should be used to refer to dimension /// variables. class IntegerPolyhedron : public IntegerRelation { public: /// Constructs a set reserving memory for the specified number /// of constraints and variables. IntegerPolyhedron(unsigned numReservedInequalities, unsigned numReservedEqualities, unsigned numReservedCols, const PresburgerSpace &space) : IntegerRelation(numReservedInequalities, numReservedEqualities, numReservedCols, space) { assert(space.getNumDomainVars() == 0 && "Number of domain vars should be zero in Set kind space."); } /// Constructs a relation with the specified number of dimensions and /// symbols. explicit IntegerPolyhedron(const PresburgerSpace &space) : IntegerPolyhedron(/*numReservedInequalities=*/0, /*numReservedEqualities=*/0, /*numReservedCols=*/space.getNumVars() + 1, space) {} /// Constructs a relation with the specified number of dimensions and symbols /// and adds the given inequalities. explicit IntegerPolyhedron(const PresburgerSpace &space, const IntMatrix &inequalities) : IntegerPolyhedron(space) { for (unsigned i = 0, e = inequalities.getNumRows(); i < e; i++) addInequality(inequalities.getRow(i)); } /// Constructs a relation with the specified number of dimensions and symbols /// and adds the given inequalities, after normalizing row-wise to integer /// values. explicit IntegerPolyhedron(const PresburgerSpace &space, const FracMatrix &inequalities) : IntegerPolyhedron(space) { IntMatrix ineqsNormalized = inequalities.normalizeRows(); for (unsigned i = 0, e = inequalities.getNumRows(); i < e; i++) addInequality(ineqsNormalized.getRow(i)); } /// Construct a set from an IntegerRelation. The relation should have /// no domain vars. explicit IntegerPolyhedron(const IntegerRelation &rel) : IntegerRelation(rel) { assert(space.getNumDomainVars() == 0 && "Number of domain vars should be zero in Set kind space."); } /// Construct a set from an IntegerRelation, but instead of creating a copy, /// use move constructor. The relation should have no domain vars. explicit IntegerPolyhedron(IntegerRelation &&rel) : IntegerRelation(rel) { assert(space.getNumDomainVars() == 0 && "Number of domain vars should be zero in Set kind space."); } /// Return a system with no constraints, i.e., one which is satisfied by all /// points. static IntegerPolyhedron getUniverse(const PresburgerSpace &space) { return IntegerPolyhedron(space); } /// Return the kind of this IntegerRelation. Kind getKind() const override { return Kind::IntegerPolyhedron; } static bool classof(const IntegerRelation *cst) { return cst->getKind() >= Kind::IntegerPolyhedron && cst->getKind() <= Kind::FlatAffineRelation; } // Clones this object. std::unique_ptr clone() const; /// Insert `num` variables of the specified kind at position `pos`. /// Positions are relative to the kind of variable. Return the absolute /// column position (i.e., not relative to the kind of variable) of the /// first added variable. unsigned insertVar(VarKind kind, unsigned pos, unsigned num = 1) override; /// Return the intersection of the two relations. /// If there are locals, they will be merged. IntegerPolyhedron intersect(const IntegerPolyhedron &other) const; /// Return the set difference of this set and the given set, i.e., /// return `this \ set`. PresburgerSet subtract(const PresburgerSet &other) const; }; } // namespace presburger } // namespace mlir #endif // MLIR_ANALYSIS_PRESBURGER_INTEGERRELATION_H