//===- SDBM.cpp - MLIR SDBM implementation --------------------------------===// // // 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 striped difference-bound matrix (SDBM) is a set in Z^N (or R^N) defined // as {(x_1, ... x_n) | f(x_1, ... x_n) >= 0} where f is an SDBM expression. // //===----------------------------------------------------------------------===// #include "mlir/Dialect/SDBM/SDBM.h" #include "mlir/Dialect/SDBM/SDBMExpr.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SetVector.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" using namespace mlir; // Helper function for SDBM construction that collects information necessary to // start building an SDBM in one sweep. In particular, it records the largest // position of a dimension in `dim`, that of a symbol in `symbol` as well as // collects all unique stripe expressions in `stripes`. Uses SetVector to // ensure these expressions always have the same order. static void collectSDBMBuildInfo(SDBMExpr expr, int &dim, int &symbol, llvm::SmallSetVector &stripes) { struct Visitor : public SDBMVisitor { void visitDim(SDBMDimExpr dimExpr) { int p = dimExpr.getPosition(); if (p > maxDimPosition) maxDimPosition = p; } void visitSymbol(SDBMSymbolExpr symbExpr) { int p = symbExpr.getPosition(); if (p > maxSymbPosition) maxSymbPosition = p; } void visitStripe(SDBMStripeExpr stripeExpr) { stripes.insert(stripeExpr); } Visitor(llvm::SmallSetVector &stripes) : stripes(stripes) {} int maxDimPosition = -1; int maxSymbPosition = -1; llvm::SmallSetVector &stripes; }; Visitor visitor(stripes); visitor.walkPostorder(expr); dim = std::max(dim, visitor.maxDimPosition); symbol = std::max(symbol, visitor.maxSymbPosition); } namespace { // Utility class for SDBMBuilder. Represents a value that can be inserted in // the SDB matrix that corresponds to "v0 - v1 + C <= 0", where v0 and v1 is // any combination of the positive and negative positions. Since multiple // variables can be declared equal to the same stripe expression, the // constraints on this expression must be reflected to all these variables. For // example, if // d0 = s0 # 42 // d1 = s0 # 42 // d2 = s1 # 2 // d3 = s1 # 2 // the constraint // s0 # 42 - s1 # 2 <= C // should be reflected in the DB matrix as // d0 - d2 <= C // d1 - d2 <= C // d0 - d3 <= C // d1 - d3 <= C // since the DB matrix has no knowledge of the transitive equality between d0, // d1 and s0 # 42 as well as between d2, d3 and s1 # 2. This knowledge can be // obtained by computing a transitive closure, which is impossible until the // DBM is actually built. struct SDBMBuilderResult { // Positions in the matrix of the variables taken with the "+" sign in the // difference expression, 0 if it is a constant rather than a variable. SmallVector positivePos; // Positions in the matrix of the variables taken with the "-" sign in the // difference expression, 0 if it is a constant rather than a variable. SmallVector negativePos; // Constant value in the difference expression. int64_t value = 0; }; // Visitor for building an SDBM from SDBM expressions. After traversing an SDBM // expression, produces an update to the SDB matrix specifying the positions in // the matrix and the negated value that should be stored. Both the positive // and the negative positions may be lists of indices in cases where multiple // variables are equal to the same stripe expression. In such cases, the update // applies to the cross product of positions because elements involved in the // update are (transitively) equal and should have the same constraints, but we // may not have an explicit equality for them. struct SDBMBuilder : public SDBMVisitor { public: // A difference expression produces both the positive and the negative // coordinate in the matrix, recursively traversing the LHS and the RHS. The // value is the difference between values obtained from LHS and RHS. SDBMBuilderResult visitDiff(SDBMDiffExpr diffExpr) { auto lhs = visit(diffExpr.getLHS()); auto rhs = visit(diffExpr.getRHS()); assert(lhs.negativePos.size() == 1 && lhs.negativePos[0] == 0 && "unexpected negative expression in a difference expression"); assert(rhs.negativePos.size() == 1 && lhs.negativePos[0] == 0 && "unexpected negative expression in a difference expression"); SDBMBuilderResult result; result.positivePos = lhs.positivePos; result.negativePos = rhs.positivePos; result.value = lhs.value - rhs.value; return result; } // An input expression is always taken with the "+" sign and therefore // produces a positive coordinate keeping the negative coordinate zero for an // eventual constant. SDBMBuilderResult visitInput(SDBMInputExpr expr) { SDBMBuilderResult r; r.positivePos.push_back(linearPosition(expr)); r.negativePos.push_back(0); return r; } // A stripe expression is always equal to one or more variables, which may be // temporaries, and appears with a "+" sign in the SDBM expression tree. Take // the positions of the corresponding variables as positive coordinates. SDBMBuilderResult visitStripe(SDBMStripeExpr expr) { SDBMBuilderResult r; assert(pointExprToStripe.count(expr)); r.positivePos = pointExprToStripe[expr]; r.negativePos.push_back(0); return r; } // A constant expression has both coordinates at zero. SDBMBuilderResult visitConstant(SDBMConstantExpr expr) { SDBMBuilderResult r; r.positivePos.push_back(0); r.negativePos.push_back(0); r.value = expr.getValue(); return r; } // A negation expression swaps the positive and the negative coordinates // and also negates the constant value. SDBMBuilderResult visitNeg(SDBMNegExpr expr) { SDBMBuilderResult result = visit(expr.getVar()); std::swap(result.positivePos, result.negativePos); result.value = -result.value; return result; } // The RHS of a sum expression must be a constant and therefore must have both // positive and negative coordinates at zero. Take the sum of the values // between LHS and RHS and keep LHS coordinates. SDBMBuilderResult visitSum(SDBMSumExpr expr) { auto lhs = visit(expr.getLHS()); auto rhs = visit(expr.getRHS()); for (auto pos : rhs.negativePos) { (void)pos; assert(pos == 0 && "unexpected variable on the RHS of SDBM sum"); } for (auto pos : rhs.positivePos) { (void)pos; assert(pos == 0 && "unexpected variable on the RHS of SDBM sum"); } lhs.value += rhs.value; return lhs; } SDBMBuilder(DenseMap> &pointExprToStripe, function_ref callback) : pointExprToStripe(pointExprToStripe), linearPosition(callback) {} DenseMap> &pointExprToStripe; function_ref linearPosition; }; } // namespace SDBM SDBM::get(ArrayRef inequalities, ArrayRef equalities) { SDBM result; // TODO: consider detecting equalities in the list of inequalities. // This is potentially expensive and requires to // - create a list of negated inequalities (may allocate under lock); // - perform a pairwise comparison of direct and negated inequalities; // - copy the lists of equalities and inequalities, and move entries between // them; // only for the purpose of sparing a temporary variable in cases where an // implicit equality between a variable and a stripe expression is present in // the input. // Do the first sweep over (in)equalities to collect the information necessary // to allocate the SDB matrix (number of dimensions, symbol and temporary // variables required for stripe expressions). llvm::SmallSetVector stripes; int maxDim = -1; int maxSymbol = -1; for (auto expr : inequalities) collectSDBMBuildInfo(expr, maxDim, maxSymbol, stripes); for (auto expr : equalities) collectSDBMBuildInfo(expr, maxDim, maxSymbol, stripes); // Indexing of dimensions starts with 0, obtain the number of dimensions by // incrementing the maximal position of the dimension seen in expressions. result.numDims = maxDim + 1; result.numSymbols = maxSymbol + 1; result.numTemporaries = 0; // Helper function that returns the position of the variable represented by // an SDBM input expression. auto linearPosition = [result](SDBMInputExpr expr) { if (expr.isa()) return result.getDimPosition(expr.getPosition()); return result.getSymbolPosition(expr.getPosition()); }; // Check if some stripe expressions are equal to another variable. In // particular, look for the equalities of the form // d0 - stripe-expression = 0, or // stripe-expression - d0 = 0. // There may be multiple variables that are equal to the same stripe // expression. Keep track of those in pointExprToStripe. // There may also be multiple stripe expressions equal to the same variable. // Introduce a temporary variable for each of those. DenseMap> pointExprToStripe; unsigned numTemporaries = 0; auto updateStripePointMaps = [&numTemporaries, &result, &pointExprToStripe, linearPosition](SDBMInputExpr input, SDBMExpr expr) { unsigned position = linearPosition(input); if (result.stripeToPoint.count(position) && result.stripeToPoint[position] != expr) { position = result.getNumVariables() + numTemporaries++; } pointExprToStripe[expr].push_back(position); result.stripeToPoint.insert(std::make_pair(position, expr)); }; for (auto eq : equalities) { auto diffExpr = eq.dyn_cast(); if (!diffExpr) continue; auto lhs = diffExpr.getLHS(); auto rhs = diffExpr.getRHS(); auto lhsInput = lhs.dyn_cast(); auto rhsInput = rhs.dyn_cast(); if (lhsInput && stripes.count(rhs)) updateStripePointMaps(lhsInput, rhs); if (rhsInput && stripes.count(lhs)) updateStripePointMaps(rhsInput, lhs); } // Assign the remaining stripe expressions to temporary variables. These // expressions are the ones that could not be associated with an existing // variable in the previous step. for (auto expr : stripes) { if (pointExprToStripe.count(expr)) continue; unsigned position = result.getNumVariables() + numTemporaries++; pointExprToStripe[expr].push_back(position); result.stripeToPoint.insert(std::make_pair(position, expr)); } // Create the DBM matrix, initialized to infinity values for the least tight // possible bound (x - y <= infinity is always true). result.numTemporaries = numTemporaries; result.matrix.resize(result.getNumVariables() * result.getNumVariables(), IntInfty::infinity()); SDBMBuilder builder(pointExprToStripe, linearPosition); // Only keep the tightest constraint. Since we transform everything into // less-than-or-equals-to inequalities, keep the smallest constant. For // example, if we have d0 - d1 <= 42 and d0 - d1 <= 2, we keep the latter. // Note that the input expressions are in the shape of d0 - d1 + -42 <= 0 // so we negate the value before storing it. // In case where the positive and the negative positions are equal, the // corresponding expression has the form d0 - d0 + -42 <= 0. If the constant // value is positive, the set defined by SDBM is trivially empty. We store // this value anyway and continue processing to maintain the correspondence // between the matrix form and the list-of-SDBMExpr form. // TODO: we may want to reconsider this once we have canonicalization // or simplification in place auto updateMatrix = [](SDBM &sdbm, const SDBMBuilderResult &r) { for (auto positivePos : r.positivePos) { for (auto negativePos : r.negativePos) { auto &m = sdbm.at(negativePos, positivePos); m = m < -r.value ? m : -r.value; } } }; // Do the second sweep on (in)equalities, updating the SDB matrix to reflect // the constraints. for (auto ineq : inequalities) updateMatrix(result, builder.visit(ineq)); // An equality f(x) = 0 is represented as a pair of inequalities {f(x) >= 0; // f(x) <= 0} or, alternatively, {-f(x) <= 0 and f(x) <= 0}. for (auto eq : equalities) { updateMatrix(result, builder.visit(eq)); updateMatrix(result, builder.visit(-eq)); } // Add the inequalities induced by stripe equalities. // t = x # C => t <= x <= t + C - 1 // which is equivalent to // {t - x <= 0; // x - t - (C - 1) <= 0}. for (const auto &pair : result.stripeToPoint) { auto stripe = pair.second.cast(); SDBMBuilderResult update = builder.visit(stripe.getLHS()); assert(update.negativePos.size() == 1 && update.negativePos[0] == 0 && "unexpected negated variable in stripe expression"); assert(update.value == 0 && "unexpected non-zero value in stripe expression"); update.negativePos.clear(); update.negativePos.push_back(pair.first); update.value = -(stripe.getStripeFactor().getValue() - 1); updateMatrix(result, update); std::swap(update.negativePos, update.positivePos); update.value = 0; updateMatrix(result, update); } return result; } // Given a row and a column position in the square DBM, insert one equality // or up to two inequalities that correspond the entries (col, row) and (row, // col) in the DBM. `rowExpr` and `colExpr` contain the expressions such that // colExpr - rowExpr <= V where V is the value at (row, col) in the DBM. // If one of the expressions is derived from another using a stripe operation, // check if the inequalities induced by the stripe operation subsume the // inequalities defined in the DBM and if so, elide these inequalities. void SDBM::convertDBMElement(unsigned row, unsigned col, SDBMTermExpr rowExpr, SDBMTermExpr colExpr, SmallVectorImpl &inequalities, SmallVectorImpl &equalities) { using ops_assertions::operator+; using ops_assertions::operator-; auto diffIJValue = at(col, row); auto diffJIValue = at(row, col); // If symmetric entries are opposite, the corresponding expressions are equal. if (diffIJValue.isFinite() && diffIJValue.getValue() == -diffJIValue.getValue()) { equalities.push_back(rowExpr - colExpr - diffIJValue.getValue()); return; } // Given an inequality x0 - x1 <= A, check if x0 is a stripe variable derived // from x1: x0 = x1 # B. If so, it would imply the constraints // x0 <= x1 <= x0 + (B - 1) <=> x0 - x1 <= 0 and x1 - x0 <= (B - 1). // Therefore, if A >= 0, this inequality is subsumed by that implied // by the stripe equality and thus can be elided. // Similarly, check if x1 is a stripe variable derived from x0: x1 = x0 # C. // If so, it would imply the constraints x1 <= x0 <= x1 + (C - 1) <=> // <=> x1 - x0 <= 0 and x0 - x1 <= (C - 1). Therefore, if A >= (C - 1), this // inequality can be elided. // // Note: x0 and x1 may be a stripe expressions themselves, we rely on stripe // expressions being stored without temporaries on the RHS and being passed // into this function as is. auto canElide = [this](unsigned x0, unsigned x1, SDBMExpr x0Expr, SDBMExpr x1Expr, int64_t value) { if (stripeToPoint.count(x0)) { auto stripe = stripeToPoint[x0].cast(); SDBMDirectExpr var = stripe.getLHS(); if (x1Expr == var && value >= 0) return true; } if (stripeToPoint.count(x1)) { auto stripe = stripeToPoint[x1].cast(); SDBMDirectExpr var = stripe.getLHS(); if (x0Expr == var && value >= stripe.getStripeFactor().getValue() - 1) return true; } return false; }; // Check row - col. if (diffIJValue.isFinite() && !canElide(row, col, rowExpr, colExpr, diffIJValue.getValue())) { inequalities.push_back(rowExpr - colExpr - diffIJValue.getValue()); } // Check col - row. if (diffJIValue.isFinite() && !canElide(col, row, colExpr, rowExpr, diffJIValue.getValue())) { inequalities.push_back(colExpr - rowExpr - diffJIValue.getValue()); } } // The values on the main diagonal correspond to the upper bound on the // difference between a variable and itself: d0 - d0 <= C, or alternatively // to -C <= 0. Only construct the inequalities when C is negative, which // are trivially false but necessary for the returned system of inequalities // to indicate that the set it defines is empty. void SDBM::convertDBMDiagonalElement(unsigned pos, SDBMTermExpr expr, SmallVectorImpl &inequalities) { auto selfDifference = at(pos, pos); if (selfDifference.isFinite() && selfDifference < 0) { auto selfDifferenceValueExpr = SDBMConstantExpr::get(expr.getDialect(), -selfDifference.getValue()); inequalities.push_back(selfDifferenceValueExpr); } } void SDBM::getSDBMExpressions(SDBMDialect *dialect, SmallVectorImpl &inequalities, SmallVectorImpl &equalities) { using ops_assertions::operator-; using ops_assertions::operator+; // Helper function that creates an SDBMInputExpr given the linearized position // of variable in the DBM. auto getInput = [dialect, this](unsigned matrixPos) -> SDBMInputExpr { if (matrixPos < numDims) return SDBMDimExpr::get(dialect, matrixPos); return SDBMSymbolExpr::get(dialect, matrixPos - numDims); }; // The top-left value corresponds to inequality 0 <= C. If C is negative, the // set defined by SDBM is trivially empty and we add the constraint -C <= 0 to // the list of inequalities. Otherwise, the constraint is trivially true and // we ignore it. auto difference = at(0, 0); if (difference.isFinite() && difference < 0) { inequalities.push_back( SDBMConstantExpr::get(dialect, -difference.getValue())); } // Traverse the segment of the matrix that involves non-temporary variables. unsigned numTrueVariables = numDims + numSymbols; for (unsigned i = 0; i < numTrueVariables; ++i) { // The first row and column represent numerical upper and lower bound on // each variable. Transform them into inequalities if they are finite. auto upperBound = at(0, 1 + i); auto lowerBound = at(1 + i, 0); auto inputExpr = getInput(i); if (upperBound.isFinite() && upperBound.getValue() == -lowerBound.getValue()) { equalities.push_back(inputExpr - upperBound.getValue()); } else if (upperBound.isFinite()) { inequalities.push_back(inputExpr - upperBound.getValue()); } else if (lowerBound.isFinite()) { inequalities.push_back(-inputExpr - lowerBound.getValue()); } // Introduce trivially false inequalities if required by diagonal elements. convertDBMDiagonalElement(1 + i, inputExpr, inequalities); // Introduce equalities or inequalities between non-temporary variables. for (unsigned j = 0; j < i; ++j) { convertDBMElement(1 + i, 1 + j, getInput(i), getInput(j), inequalities, equalities); } } // Add equalities for stripe expressions that define non-temporary // variables. Temporary variables will be substituted into their uses and // should not appear in the resulting equalities. for (const auto &stripePair : stripeToPoint) { unsigned position = stripePair.first; if (position < 1 + numTrueVariables) { equalities.push_back(getInput(position - 1) - stripePair.second); } } // Add equalities / inequalities involving temporaries by replacing the // temporaries with stripe expressions that define them. for (unsigned i = 1 + numTrueVariables, e = getNumVariables(); i < e; ++i) { // Mixed constraints involving one temporary (j) and one non-temporary (i) // variable. for (unsigned j = 0; j < numTrueVariables; ++j) { convertDBMElement(i, 1 + j, stripeToPoint[i].cast(), getInput(j), inequalities, equalities); } // Constraints involving only temporary variables. for (unsigned j = 1 + numTrueVariables; j < i; ++j) { convertDBMElement(i, j, stripeToPoint[i].cast(), stripeToPoint[j].cast(), inequalities, equalities); } // Introduce trivially false inequalities if required by diagonal elements. convertDBMDiagonalElement(i, stripeToPoint[i].cast(), inequalities); } } void SDBM::print(raw_ostream &os) { unsigned numVariables = getNumVariables(); // Helper function that prints the name of the variable given its linearized // position in the DBM. auto getVarName = [this](unsigned matrixPos) -> std::string { if (matrixPos == 0) return "cst"; matrixPos -= 1; if (matrixPos < numDims) return std::string(llvm::formatv("d{0}", matrixPos)); matrixPos -= numDims; if (matrixPos < numSymbols) return std::string(llvm::formatv("s{0}", matrixPos)); matrixPos -= numSymbols; return std::string(llvm::formatv("t{0}", matrixPos)); }; // Header row. os << " cst"; for (unsigned i = 1; i < numVariables; ++i) { os << llvm::formatv(" {0,4}", getVarName(i)); } os << '\n'; // Data rows. for (unsigned i = 0; i < numVariables; ++i) { os << llvm::formatv("{0,-4}", getVarName(i)); for (unsigned j = 0; j < numVariables; ++j) { IntInfty value = operator()(i, j); if (!value.isFinite()) os << " inf"; else os << llvm::formatv(" {0,4}", value.getValue()); } os << '\n'; } // Explanation of temporaries. for (const auto &pair : stripeToPoint) { os << getVarName(pair.first) << " = "; pair.second.print(os); os << '\n'; } } void SDBM::dump() { print(llvm::errs()); }