/// Construct a constraint system reserving memory for the specified number of
/// constraints and identifiers..
FlatAffineConstraints(unsigned numReservedInequalities,
- unsigned numReservedEqualities, unsigned numReservedIds)
+ unsigned numReservedEqualities,
+ unsigned numReservedCols, unsigned numDims = 0,
+ unsigned numSymbols = 0, unsigned numLocals = 0)
: numReservedEqualities(numReservedEqualities),
- numReservedInequalities(numReservedInequalities),
- numReservedIds(numReservedIds) {
- equalities.reserve(numReservedIds * numReservedEqualities);
- inequalities.reserve(numReservedIds * numReservedInequalities);
+ numReservedInequalities(numReservedInequalities), numDims(numDims),
+ numSymbols(numSymbols) {
+ assert(numReservedCols >= 1 && "minimum 1 column");
+ equalities.reserve(numReservedCols * numReservedEqualities);
+ inequalities.reserve(numReservedCols * numReservedInequalities);
+ numIds = numDims + numSymbols + numLocals;
}
explicit FlatAffineConstraints(const HyperRectangularSet &set);
// TODO(bondhugula)
explicit FlatAffineConstraints(const IntegerValueSet &set);
+ FlatAffineConstraints(const FlatAffineConstraints &other);
+
FlatAffineConstraints(ArrayRef<const AffineValueMap *> avmRef,
- const IntegerSet &set);
+ IntegerSet set);
FlatAffineConstraints(const MutableAffineMap &map);
// constraints.
// Returns true if the GCD test fails for any equality, or if any invalid
// constraints are discovered on any row. Returns false otherwise.
- // TODO(andydavis) Change this method to operate on cloned constraints.
- bool isEmpty();
+ bool isEmpty() const;
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated.
// Returns 'false' otherwise.
- bool eliminateIdentifier(unsigned position);
+ bool gaussianEliminateId(unsigned position);
// Eliminates identifiers from equality and inequality constraints
// in column range [posStart, posLimit).
// Returns the number of variables eliminated.
- unsigned eliminateIdentifiers(unsigned posStart, unsigned posLimit);
+ unsigned gaussianEliminateIds(unsigned posStart, unsigned posLimit);
+ // Clones this object.
+ std::unique_ptr<FlatAffineConstraints> clone() const;
+
+ /// Returns the value at the specified equality row and column.
inline int64_t atEq(unsigned i, unsigned j) const {
return equalities[i * (numIds + 1) + j];
}
-
inline int64_t &atEq(unsigned i, unsigned j) {
return equalities[i * (numIds + 1) + j];
}
return inequalities.size() / getNumCols();
}
- ArrayRef<int64_t> getEquality(unsigned idx) {
+ inline ArrayRef<int64_t> getEquality(unsigned idx) const {
return ArrayRef<int64_t>(&equalities[idx * getNumCols()], getNumCols());
}
- ArrayRef<int64_t> getInequality(unsigned idx) {
+ inline ArrayRef<int64_t> getInequality(unsigned idx) const {
return ArrayRef<int64_t>(&inequalities[idx * getNumCols()], getNumCols());
}
void addSymbolId(unsigned pos);
void addLocalId(unsigned pos);
+ /// Eliminates identifier at the specified position using Fourier-Motzkin
+ /// variable elimination. 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.
+ bool FourierMotzkinEliminate(unsigned pos, bool darkShadow = false,
+ bool *isResultIntegerExact = nullptr);
+
void removeId(IdKind idKind, unsigned pos);
+ void removeId(unsigned pos);
+
+ void removeDim(unsigned pos);
void removeEquality(unsigned pos);
void removeInequality(unsigned pos);
unsigned getNumConstraints() const {
- return equalities.size() + inequalities.size();
+ return getNumInequalities() + getNumEqualities();
}
inline unsigned getNumIds() const { return numIds; }
inline unsigned getNumResultDimIds() const { return numResultDims; }
return numIds - numResultDims - numDims - numSymbols;
}
+ /// Clears this list of constraints and copies other into it.
+ void clearAndCopyFrom(const FlatAffineConstraints &other);
+
+ // More expensive ones.
+ void removeDuplicates();
+
void print(raw_ostream &os) const;
void dump() const;
using ImplType = detail::AffineMapStorage;
explicit AffineMap(ImplType *map = nullptr) : map(map) {}
- static AffineMap Invalid() { return AffineMap(nullptr); }
+ static AffineMap Null() { return AffineMap(nullptr); }
static AffineMap get(unsigned dimCount, unsigned symbolCount,
ArrayRef<AffineExpr> results,
explicit IntegerSet(ImplType *set = nullptr) : set(set) {}
+ IntegerSet &operator=(const IntegerSet other) {
+ set = other.set;
+ return *this;
+ }
+
static IntegerSet get(unsigned dimCount, unsigned symbolCount,
ArrayRef<AffineExpr> constraints,
- ArrayRef<bool> eqFlags, MLIRContext *context);
+ ArrayRef<bool> eqFlags);
- // Returns a canonical empty IntegerSet (i.e. a set with no integer points).
+ // Returns the canonical empty IntegerSet (i.e. a set with no integer points).
static IntegerSet getEmptySet(unsigned numDims, unsigned numSymbols,
MLIRContext *context) {
auto one = getAffineConstantExpr(1, context);
/* 1 == 0 */
- return get(numDims, numSymbols, one, true, context);
+ return get(numDims, numSymbols, one, true);
}
+ /// Returns true if this is the canonical integer set.
+ bool isEmptyIntegerSet() const;
+
+ static IntegerSet Null() { return IntegerSet(nullptr); }
+
explicit operator bool() { return set; }
bool operator==(IntegerSet other) const { return set == other.set; }
private:
ImplType *set;
+ /// Sets with constraints fewer than kUniquingThreshold are uniqued.
+ constexpr static unsigned kUniquingThreshold = 4;
};
// Make AffineExpr hashable.
class AffineCondition {
public:
const IfStmt *getIfStmt() const { return &stmt; }
- IntegerSet getSet() const { return set; }
+ IntegerSet getIntegerSet() const { return set; }
private:
// 'if' statement that contains this affine condition.
#define MLIR_SUPPORT_MATHEXTRAS_H_
#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/APInt.h"
namespace mlir {
/// line if provided.
MLFunctionPass *createLoopUnrollAndJamPass(int unrollJamFactor = -1);
-/// Creates an affine expression simplification pass.
-FunctionPass *createSimplifyAffineExprPass();
+/// Creates an simplification pass for affine structures.
+FunctionPass *createSimplifyAffineStructuresPass();
/// Creates a pass to pipeline explicit movement of data across levels of the
/// memory hierarchy.
// extended to add additional indices at any position.
bool replaceAllMemRefUsesWith(const MLValue *oldMemRef, MLValue *newMemRef,
llvm::ArrayRef<MLValue *> extraIndices,
- AffineMap indexRemap = AffineMap::Invalid());
+ AffineMap indexRemap = AffineMap::Null());
/// Creates and inserts into 'builder' a new AffineApplyOp, with the number of
/// its results equal to the number of operands, as a composition
// TODO(bondhugula): only pure affine for now. The simplification here can be
// extended to semi-affine maps in the future.
if (!expr.isPureAffine())
- return nullptr;
+ return expr;
AffineExprFlattener flattener(numDims, numSymbols, expr.getContext());
flattener.walkPostOrder(expr);
#include "mlir/Analysis/AffineStructures.h"
#include "mlir/Analysis/AffineAnalysis.h"
-#include "mlir/IR/AffineExpr.h"
#include "mlir/IR/AffineExprVisitor.h"
-#include "mlir/IR/AffineMap.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/IntegerSet.h"
#include "mlir/IR/MLValue.h"
#include "mlir/Support/MathExtras.h"
-#include "llvm/ADT/DenseMap.h"
+#include "third_party/llvm/llvm/projects/google-mlir/include/mlir/Analysis/AffineStructures.h"
#include "llvm/ADT/DenseSet.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/raw_ostream.h"
+#define DEBUG_TYPE "affine-structures"
+
using namespace mlir;
using namespace llvm;
AffineValueMap::~AffineValueMap() {}
+//===----------------------------------------------------------------------===//
+// FlatAffineConstraints.
+//===----------------------------------------------------------------------===//
+
+// Copy constructor.
+FlatAffineConstraints::FlatAffineConstraints(
+ const FlatAffineConstraints &other) {
+ numReservedEqualities = other.numReservedEqualities;
+ numReservedInequalities = other.numReservedInequalities;
+ numIds = other.getNumIds();
+ numDims = other.getNumDimIds();
+ numSymbols = other.getNumSymbolIds();
+
+ equalities.reserve(numReservedEqualities * getNumCols());
+ inequalities.reserve(numReservedEqualities * getNumCols());
+
+ for (unsigned r = 0, e = other.getNumInequalities(); r < e; r++) {
+ addInequality(other.getInequality(r));
+ }
+ for (unsigned r = 0, e = other.getNumEqualities(); r < e; r++) {
+ addEquality(other.getEquality(r));
+ }
+}
+
+// Clones this object.
+std::unique_ptr<FlatAffineConstraints> FlatAffineConstraints::clone() const {
+ return std::make_unique<FlatAffineConstraints>(*this);
+}
+
+// Construct from an IntegerSet.
FlatAffineConstraints::FlatAffineConstraints(IntegerSet set)
- : numReservedEqualities(0), numReservedInequalities(0), numReservedIds(0),
+ : numReservedEqualities(set.getNumEqualities()),
+ numReservedInequalities(set.getNumInequalities()), numReservedIds(0),
numIds(set.getNumDims() + set.getNumSymbols()), numDims(set.getNumDims()),
numSymbols(set.getNumSymbols()) {
- unsigned numConstraints = set.getNumConstraints();
- for (unsigned i = 0; i < numConstraints; ++i) {
+ equalities.reserve(set.getNumEqualities() * getNumCols());
+ inequalities.reserve(set.getNumInequalities() * getNumCols());
+
+ for (unsigned i = 0, e = set.getNumConstraints(); i < e; ++i) {
AffineExpr expr = set.getConstraint(i);
SmallVector<int64_t, 4> flattenedExpr;
getFlattenedAffineExpr(expr, set.getNumDims(), set.getNumSymbols(),
}
}
}
-
// Searches for a constraint with a non-zero coefficient at 'colIdx' in
// equality (isEq=true) or inequality (isEq=false) constraints.
// Returns true and sets row found in search in 'rowIdx'.
// all equality constraint rows, and checks the constraint validity.
// Returns 'true' if the GCD test fails on any row, or if any invalid
// constraint is detected. Returns 'false' otherwise.
-bool FlatAffineConstraints::isEmpty() {
- if (eliminateIdentifiers(0, numIds) == 0)
- return false;
- if (isEmptyByGCDTest(*this))
+bool FlatAffineConstraints::isEmpty() const {
+ auto tmpCst = clone();
+ if (tmpCst->gaussianEliminateIds(0, numIds) < numIds) {
+ for (unsigned i = 0, e = tmpCst->getNumIds(); i < e; i++)
+ if (!tmpCst->FourierMotzkinEliminate(0))
+ return false;
+ }
+ if (isEmptyByGCDTest(*tmpCst))
return true;
- if (hasInvalidConstraint(*this))
+ if (hasInvalidConstraint(*tmpCst))
return true;
return false;
}
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated.
// Returns 'false' otherwise.
-bool FlatAffineConstraints::eliminateIdentifier(unsigned position) {
- return eliminateIdentifiers(position, position + 1) == 1;
+bool FlatAffineConstraints::gaussianEliminateId(unsigned position) {
+ return gaussianEliminateIds(position, position + 1) == 1;
}
// Eliminates all identifer variables in column range [posStart, posLimit).
// Returns the number of variables eliminated.
-unsigned FlatAffineConstraints::eliminateIdentifiers(unsigned posStart,
+unsigned FlatAffineConstraints::gaussianEliminateIds(unsigned posStart,
unsigned posLimit) {
// Return if identifier positions to eliminate are out of range.
if (posStart >= posLimit || posLimit > numIds)
}
void FlatAffineConstraints::dump() const { print(llvm::errs()); }
+
+void FlatAffineConstraints::removeDuplicates() {
+ // TODO: remove redundant constraints.
+}
+
+void FlatAffineConstraints::clearAndCopyFrom(
+ const FlatAffineConstraints &other) {
+ FlatAffineConstraints copy(other);
+ std::swap(*this, copy);
+}
+
+void FlatAffineConstraints::removeId(unsigned pos) {
+ assert(pos >= 0 && pos < getNumIds());
+
+ for (unsigned r = 0; r < getNumInequalities(); r++) {
+ for (unsigned c = pos; c < getNumCols() - 1; c++) {
+ atIneq(r, c) = atIneq(r, c + 1);
+ }
+ }
+
+ for (unsigned r = 0; r < getNumEqualities(); r++) {
+ for (unsigned c = pos; c < getNumCols() - 1; c++) {
+ atEq(r, c) = atEq(r, c + 1);
+ }
+ }
+
+ if (pos < numDims)
+ numDims--;
+ else if (pos < numSymbols)
+ numSymbols--;
+ numIds--;
+}
+
+static std::pair<unsigned, unsigned>
+getNewNumDimsSymbols(unsigned pos, const FlatAffineConstraints &cst) {
+ unsigned numDims = cst.getNumDimIds();
+ unsigned numSymbols = cst.getNumSymbolIds();
+ unsigned newNumDims, newNumSymbols;
+ if (pos < numDims) {
+ newNumDims = numDims - 1;
+ newNumSymbols = numSymbols;
+ } else if (pos < numDims + numSymbols) {
+ assert(numSymbols >= 1);
+ newNumDims = numDims;
+ newNumSymbols = numSymbols - 1;
+ } else {
+ newNumDims = numDims;
+ newNumSymbols = numSymbols;
+ }
+ return {newNumDims, newNumSymbols};
+}
+
+/// Eliminates identifier at the specified position using Fourier-Motzkin
+/// variable elimination. This technique is exact for rational spaces but
+/// conservative (in "rare" cases) for integer spaces. The operation corresponds
+/// to a projection operation yielding the (convex) set of integer points
+/// contained in the rational shadow of the set. An emptiness test that relies
+/// on this method will guarantee emptiness, i.e., it disproves the existence of
+/// a solution if it says it's empty.
+/// If a non-null isResultIntegerExact is passed, it is set to true if the
+/// result is also integer exact. If it's set to false, the obtained solution
+/// *may* not be exact, i.e., it may contain integer points that do not have an
+/// integer pre-image in the original set.
+///
+/// Eg:
+/// j >= 0, j <= i + 1
+/// i >= 0, i <= N + 1
+/// Eliminating i yields,
+/// j >= 0, 0 <= N + 1, j - 1 <= N + 1
+///
+/// If darkShadow = true, this method computes the dark shadow on elimination;
+/// the dark shadow is a convex integer subset of the exact integer shadow. A
+/// non-empty dark shadow proves the existence of an integer solution. The
+/// elimination in such a case could however be an under-approximation, and thus
+/// should not be used for scanning sets or used by itself for dependence
+/// checking.
+///
+/// Eg: 2-d set, * represents grid points, 'o' represents a point in the set.
+/// ^
+/// |
+/// | * * * * o o
+/// i | * * o o o o
+/// | o * * * * *
+/// --------------->
+/// j ->
+///
+/// Eliminating i from this system (projecting on the j dimension):
+/// rational shadow / integer light shadow: 1 <= j <= 6
+/// dark shadow: 3 <= j <= 6
+/// exact integer shadow: j = 1 \union 3 <= j <= 6
+/// holes/splinters: j = 2
+///
+/// darkShadow = false, isResultIntegerExact = nullptr are default values.
+// TODO(bondhugula): a slight modification to yield dark shadow version of FM
+// (tightened), which can prove the existence of a solution if there is one.
+bool FlatAffineConstraints::FourierMotzkinEliminate(
+ unsigned pos, bool darkShadow, bool *isResultIntegerExact) {
+ assert(pos < getNumIds() && "invalid position");
+ LLVM_DEBUG(llvm::dbgs() << "FM Input:\n");
+ LLVM_DEBUG(dump());
+
+ // Check if this identifier can be eliminated through a substitution.
+ for (unsigned r = 0; r < getNumEqualities(); r++) {
+ if (atIneq(r, pos) != 0) {
+ // Use Gaussian elimination here (since we have an equality).
+ bool ret = gaussianEliminateId(pos);
+ assert(ret && "Gaussian elimination guaranteed to succeed");
+ return ret;
+ }
+ }
+
+ // Check if the identifier appears at all in any of the inequalities.
+ unsigned r, e;
+ for (r = 0, e = getNumInequalities(); r < e; r++) {
+ if (atIneq(r, pos) != 0)
+ break;
+ }
+ if (r == getNumInequalities()) {
+ // If it doesn't appear, just remove the column and return.
+ // TODO(andydavis,bondhugula): refactor removeColumns to use it from here.
+ removeId(pos);
+ LLVM_DEBUG(llvm::dbgs() << "FM output:\n");
+ LLVM_DEBUG(dump());
+ return true;
+ }
+
+ // Positions of constraints that are lower bounds on the variable.
+ SmallVector<unsigned, 4> lbIndices;
+ // Positions of constraints that are lower bounds on the variable.
+ SmallVector<unsigned, 4> ubIndices;
+ // Positions of constraints that do not involve the variable.
+ std::vector<unsigned> nbIndices;
+ nbIndices.reserve(getNumInequalities());
+
+ // Gather all lower bounds and upper bounds of the variable. Since the
+ // canonical form c_1*x_1 + c_2*x_2 + ... + c_0 >= 0, a constraint is a lower
+ // bound for x_i if c_i >= 1, and an upper bound if c_i <= -1.
+ for (unsigned r = 0, e = getNumInequalities(); r < e; r++) {
+ if (atIneq(r, pos) == 0) {
+ // Id does not appear in bound.
+ nbIndices.push_back(r);
+ } else if (atIneq(r, pos) >= 1) {
+ // Lower bound.
+ lbIndices.push_back(r);
+ } else {
+ // Upper bound.
+ ubIndices.push_back(r);
+ }
+ }
+
+ // Set the number of dimensions, symbols in the resulting system.
+ const auto &dimsSymbols = getNewNumDimsSymbols(pos, *this);
+ unsigned newNumDims = dimsSymbols.first;
+ unsigned newNumSymbols = dimsSymbols.second;
+
+ /// Create the new system which has one identifier less.
+ FlatAffineConstraints newFac(
+ lbIndices.size() * ubIndices.size() + nbIndices.size(),
+ getNumEqualities(), getNumCols() - 1, newNumDims, newNumSymbols,
+ /*numLocals=*/getNumIds() - 1 - newNumDims - newNumSymbols);
+
+ // This will be used to check if the elimination was integer exact.
+ unsigned lcmProducts = 1;
+
+ // Let x be the variable we are eliminating.
+ // For each lower bound, lb <= c_l*x, and each upper bound c_u*x <= ub, (note
+ // that c_l, c_u >= 1) we have:
+ // lb*lcm(c_l, c_u)/c_l <= lcm(c_l, c_u)*x <= ub*lcm(c_l, c_u)/c_u
+ // We thus generate a constraint:
+ // lcm(c_l, c_u)/c_l*lb <= lcm(c_l, c_u)/c_u*ub.
+ // Note if c_l = c_u = 1, all integer points captured by the resulting
+ // constraint correspond to integer points in the original system (i.e., they
+ // have integer pre-images). Hence, if the lcm's are all 1, the elimination is
+ // integer exact.
+ for (auto ubPos : ubIndices) {
+ for (auto lbPos : lbIndices) {
+ SmallVector<int64_t, 4> ineq;
+ ineq.reserve(newFac.getNumCols());
+ int64_t lbCoeff = atIneq(lbPos, pos);
+ // Note that in the comments above, ubCoeff is the negation of the
+ // coefficient in the canonical form as the view taken here is that of the
+ // term being moved to the other size of '>='.
+ int64_t ubCoeff = -atIneq(ubPos, pos);
+ // TODO(bondhugula): refactor this loop to avoid all branches inside.
+ for (unsigned l = 0, e = getNumCols(); l < e; l++) {
+ if (l == pos)
+ continue;
+ assert(lbCoeff >= 1 && ubCoeff >= 1 && "bounds wrongly identified");
+ int64_t lcm = mlir::lcm(lbCoeff, ubCoeff);
+ ineq.push_back(atIneq(ubPos, l) * (lcm / ubCoeff) +
+ atIneq(lbPos, l) * (lcm / lbCoeff));
+ lcmProducts *= lcm;
+ }
+ if (darkShadow) {
+ // The dark shadow is a convex subset of the exact integer shadow. If
+ // there is a point here, it proves the existence of a solution.
+ ineq[ineq.size() - 1] += lbCoeff * ubCoeff - lbCoeff - ubCoeff + 1;
+ }
+ // TODO: we need to have a way to add inequalities in-place in
+ // FlatAffineConstraints instead of creating and copying over.
+ newFac.addInequality(ineq);
+ }
+ }
+
+ if (lcmProducts == 1 && isResultIntegerExact)
+ *isResultIntegerExact = 1;
+
+ // Copy over the constraints not involving this variable.
+ for (auto nbPos : nbIndices) {
+ SmallVector<int64_t, 4> ineq;
+ ineq.reserve(getNumCols() - 1);
+ for (unsigned l = 0, e = getNumCols(); l < e; l++) {
+ if (l == pos)
+ continue;
+ ineq.push_back(atIneq(nbPos, l));
+ }
+ newFac.addInequality(ineq);
+ }
+
+ assert(newFac.getNumConstraints() ==
+ lbIndices.size() * ubIndices.size() + nbIndices.size());
+
+ // Copy over the equalities.
+ for (unsigned r = 0, e = getNumEqualities(); r < e; r++) {
+ SmallVector<int64_t, 4> eq;
+ eq.reserve(newFac.getNumCols());
+ for (unsigned l = 0, e = getNumCols(); l < e; l++) {
+ if (l == pos)
+ continue;
+ eq.push_back(atEq(r, l));
+ }
+ newFac.addEquality(eq);
+ }
+
+ newFac.removeDuplicates();
+ clearAndCopyFrom(newFac);
+ LLVM_DEBUG(llvm::dbgs() << "FM output:\n");
+ LLVM_DEBUG(dump());
+ return true;
+}
unsigned AffineMap::getNumDims() const { return map->numDims; }
unsigned AffineMap::getNumSymbols() const { return map->numSymbols; }
-unsigned AffineMap::getNumResults() const { return map->numResults; }
+unsigned AffineMap::getNumResults() const { return map->results.size(); }
unsigned AffineMap::getNumInputs() const {
return map->numDims + map->numSymbols;
}
struct AffineMapStorage {
unsigned numDims;
unsigned numSymbols;
- unsigned numResults;
/// The affine expressions for this (multi-dimensional) map.
/// TODO: use trailing objects for this.
IntegerSet Builder::getIntegerSet(unsigned dimCount, unsigned symbolCount,
ArrayRef<AffineExpr> constraints,
ArrayRef<bool> isEq) {
- return IntegerSet::get(dimCount, symbolCount, constraints, isEq, context);
+ return IntegerSet::get(dimCount, symbolCount, constraints, isEq);
}
AffineMap Builder::getConstantAffineMap(int64_t val) {
unsigned IntegerSet::getNumOperands() const {
return set->dimCount + set->symbolCount;
}
-unsigned IntegerSet::getNumConstraints() const { return set->numConstraints; }
+
+unsigned IntegerSet::getNumConstraints() const {
+ return set->constraints.size();
+}
unsigned IntegerSet::getNumEqualities() const {
unsigned numEqualities = 0;
return getNumConstraints() - getNumEqualities();
}
+bool IntegerSet::isEmptyIntegerSet() const {
+ // This will only work if uniqui'ing is on.
+ static_assert(kUniquingThreshold >= 1,
+ "uniquing threshold should be at least one");
+ return *this == getEmptySet(set->dimCount, set->symbolCount, getContext());
+}
+
ArrayRef<AffineExpr> IntegerSet::getConstraints() const {
return set->constraints;
}
struct IntegerSetStorage {
unsigned dimCount;
unsigned symbolCount;
- unsigned numConstraints;
/// Array of affine constraints: a constraint is either an equality
/// (affine_expr == 0) or an inequality (affine_expr >= 0).
}
};
+struct IntegerSetKeyInfo : DenseMapInfo<IntegerSet> {
+ // Integer sets are uniqued based on their dim/symbol counts, affine
+ // expressions appearing in the LHS of constraints, and eqFlags.
+ using KeyTy =
+ std::tuple<unsigned, unsigned, ArrayRef<AffineExpr>, ArrayRef<bool>>;
+ using DenseMapInfo<IntegerSet>::getHashValue;
+ using DenseMapInfo<IntegerSet>::isEqual;
+
+ static unsigned getHashValue(KeyTy key) {
+ return hash_combine(
+ std::get<0>(key), std::get<1>(key),
+ hash_combine_range(std::get<2>(key).begin(), std::get<2>(key).end()),
+ hash_combine_range(std::get<3>(key).begin(), std::get<3>(key).end()));
+ }
+
+ static bool isEqual(const KeyTy &lhs, IntegerSet rhs) {
+ if (rhs == getEmptyKey() || rhs == getTombstoneKey())
+ return false;
+ return lhs == std::make_tuple(rhs.getNumDims(), rhs.getNumSymbols(),
+ rhs.getConstraints(), rhs.getEqFlags());
+ }
+};
+
struct VectorTypeKeyInfo : DenseMapInfo<VectorType *> {
// Vectors are uniqued based on their element type and shape.
using KeyTy = std::pair<Type *, ArrayRef<int>>;
using AffineMapSet = DenseSet<AffineMap, AffineMapKeyInfo>;
AffineMapSet affineMaps;
+ // Integer set uniquing.
+ using IntegerSets = DenseSet<IntegerSet, IntegerSetKeyInfo>;
+ IntegerSets integerSets;
+
// Affine binary op expression uniquing. Figure out uniquing of dimensional
// or symbolic identifiers.
DenseMap<std::tuple<unsigned, AffineExpr, AffineExpr>, AffineExpr>
rangeSizes = impl.copyInto(rangeSizes);
// Initialize the memory using placement new.
- new (res) detail::AffineMapStorage{dimCount, symbolCount,
- static_cast<unsigned>(results.size()),
- results, rangeSizes};
+ new (res)
+ detail::AffineMapStorage{dimCount, symbolCount, results, rangeSizes};
// Cache and return it.
return *existing.first = AffineMap(res);
//===----------------------------------------------------------------------===//
// Integer Sets: these are allocated into the bump pointer, and are immutable.
-// But they aren't uniqued like AffineMap's; there isn't an advantage to.
+// Unlike AffineMap's, these are uniqued only if they are small.
//===----------------------------------------------------------------------===//
IntegerSet IntegerSet::get(unsigned dimCount, unsigned symbolCount,
ArrayRef<AffineExpr> constraints,
- ArrayRef<bool> eqFlags, MLIRContext *context) {
- assert(eqFlags.size() == constraints.size());
+ ArrayRef<bool> eqFlags) {
+ // The number of constraints can't be zero.
+ assert(!constraints.empty());
+ assert(constraints.size() == eqFlags.size());
- auto &impl = context->getImpl();
+ bool unique = constraints.size() < IntegerSet::kUniquingThreshold;
- // Allocate them into the bump pointer.
- auto *res = impl.allocator.Allocate<IntegerSetStorage>();
+ auto &impl = constraints[0].getContext()->getImpl();
- // Copy the equalities and inequalities into the bump pointer.
- constraints = impl.copyInto(ArrayRef<AffineExpr>(constraints));
- eqFlags = impl.copyInto(ArrayRef<bool>(eqFlags));
+ std::pair<DenseSet<IntegerSet, IntegerSetKeyInfo>::Iterator, bool> existing;
+ if (unique) {
+ // Check if we already have this integer set.
+ auto key = std::make_tuple(dimCount, symbolCount, constraints, eqFlags);
+ existing = impl.integerSets.insert_as(IntegerSet(nullptr), key);
+
+ // If we already have it, return that value.
+ if (!existing.second)
+ return *existing.first;
+ }
+
+ // On the first use, we allocate them into the bump pointer.
+ auto *res = impl.allocator.Allocate<detail::IntegerSetStorage>();
+
+ // Copy the results and equality flags into the bump pointer.
+ constraints = impl.copyInto(constraints);
+ eqFlags = impl.copyInto(eqFlags);
// Initialize the memory using placement new.
- res = new (res) IntegerSetStorage{dimCount, symbolCount,
- static_cast<unsigned>(constraints.size()),
- constraints, eqFlags};
+ new (res)
+ detail::IntegerSetStorage{dimCount, symbolCount, constraints, eqFlags};
+
+ if (unique)
+ // Cache and return it.
+ return *existing.first = IntegerSet(res);
return IntegerSet(res);
}
// List of dimensional identifiers.
if (parseDimIdList(numDims))
- return AffineMap::Invalid();
+ return AffineMap::Null();
// Symbols are optional.
if (getToken().is(Token::l_square)) {
if (parseSymbolIdList(numSymbols))
- return AffineMap::Invalid();
+ return AffineMap::Null();
}
if (parseToken(Token::arrow, "expected '->' or '['") ||
parseToken(Token::l_paren, "expected '(' at start of affine map range"))
- return AffineMap::Invalid();
+ return AffineMap::Null();
SmallVector<AffineExpr, 4> exprs;
auto parseElt = [&]() -> ParseResult {
// 1-d affine expressions); the list cannot be empty. Grammar:
// multi-dim-affine-expr ::= `(` affine-expr (`,` affine-expr)* `)
if (parseCommaSeparatedListUntil(Token::r_paren, parseElt, false))
- return AffineMap::Invalid();
+ return AffineMap::Null();
// Parse optional range sizes.
// range-sizes ::= (`size` `(` dim-size (`,` dim-size)* `)`)?
// Location of the l_paren token (if it exists) for error reporting later.
auto loc = getToken().getLoc();
if (parseToken(Token::l_paren, "expected '(' at start of affine map range"))
- return AffineMap::Invalid();
+ return AffineMap::Null();
auto parseRangeSize = [&]() -> ParseResult {
auto loc = getToken().getLoc();
};
if (parseCommaSeparatedListUntil(Token::r_paren, parseRangeSize, false))
- return AffineMap::Invalid();
+ return AffineMap::Null();
if (exprs.size() > rangeSizes.size())
return (emitError(loc, "fewer range sizes than range expressions"),
- AffineMap::Invalid());
+ AffineMap::Null());
if (exprs.size() < rangeSizes.size())
return (emitError(loc, "more range sizes than range expressions"),
- AffineMap::Invalid());
+ AffineMap::Null());
}
// Parsed a valid affine map.
StringRef affineMapId = getTokenSpelling().drop_front();
if (getState().affineMapDefinitions.count(affineMapId) == 0)
return (emitError("undefined affine map id '" + affineMapId + "'"),
- AffineMap::Invalid());
+ AffineMap::Null());
consumeToken(Token::hash_identifier);
return getState().affineMapDefinitions[affineMapId];
}
// Single result lower bound map only.
if (lbMap.getNumResults() != 1)
- return AffineMap::Invalid();
+ return AffineMap::Null();
// Sometimes, the trip count cannot be expressed as an affine expression.
auto tripCount = getTripCountExpr(forStmt);
if (!tripCount)
- return AffineMap::Invalid();
+ return AffineMap::Null();
AffineExpr lb(lbMap.getResult(0));
unsigned step = forStmt.getStep();
// Single result lower bound map only.
if (lbMap.getNumResults() != 1)
- return AffineMap::Invalid();
+ return AffineMap::Null();
// Sometimes the trip count cannot be expressed as an affine expression.
AffineExpr tripCount(getTripCountExpr(forStmt));
if (!tripCount)
- return AffineMap::Invalid();
+ return AffineMap::Null();
AffineExpr lb(lbMap.getResult(0));
unsigned step = forStmt.getStep();
// for this yet? TODO(someone).
PassResult runOnCFGFunction(CFGFunction *f) { return success(); }
- void visitOperationStmt(OperationStmt *stmt);
void visitIfStmt(IfStmt *ifStmt);
+ void visitOperationStmt(OperationStmt *opStmt);
};
} // end anonymous namespace
-FunctionPass *mlir::createSimplifyAffineExprPass() {
+FunctionPass *mlir::createSimplifyAffineStructuresPass() {
return new SimplifyAffineStructures();
}
}
void SimplifyAffineStructures::visitIfStmt(IfStmt *ifStmt) {
- auto set = ifStmt->getCondition().getSet();
- IntegerSet simplified = simplifyIntegerSet(set);
- ifStmt->setIntegerSet(simplified);
+ auto set = ifStmt->getCondition().getIntegerSet();
+ ifStmt->setIntegerSet(simplifyIntegerSet(set));
}
void SimplifyAffineStructures::visitOperationStmt(OperationStmt *opStmt) {
-// RUN: mlir-opt %s -simplify-affine-expr | FileCheck %s
+// RUN: mlir-opt %s -simplify-affine-structures | FileCheck %s
// CHECK: #map{{[0-9]+}} = (d0, d1) -> (0, 0)
#map0 = (d0, d1) -> ((d0 - d0 mod 4) mod 4, (d0 - d0 mod 128 - 64) mod 64)
// CHECK: #map{{[0-9]+}} = (d0, d1) -> (d0 - (d0 floordiv 8) * 8, (d1 floordiv 8) * 8)
#map6 = (d0, d1) -> (d0 mod 8, d1 - d1 mod 8)
-// Set for test case: test_gaussian_elimination_empty_set0
// CHECK: @@set0 = (d0, d1) : (1 == 0)
-@@set0 = (d0, d1) : (2 == 0)
-
-// Set for test case: test_gaussian_elimination_empty_set1
-// CHECK: @@set1 = (d0, d1) : (1 == 0)
-@@set1 = (d0, d1) : (1 >= 0, -1 >= 0)
+// CHECK: @@set1 = (d0, d1) : (d0 - 100 == 0, d1 - 10 == 0, d0 * -1 + 100 >= 0, d1 >= 0, d1 + 101 >= 0)
+// CHECK: @@set2 = (d0, d1)[s0, s1] : (1 == 0)
+// CHECK: @@set3 = (d0, d1)[s0, s1] : (d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0, d0 * 5 - d1 * 11 + s0 * 7 + s1 == 0, d0 * 11 + d1 * 7 - s0 * 5 + s1 == 0, d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0)
+// CHECK: @@set4 = (d0) : (1 == 0)
+// CHECK: @@set5 = (d0)[s0, s1] : (1 == 0)
+// CHECK: @@set6 = (d0, d1, d2) : (1 == 0)
// Set for test case: test_gaussian_elimination_non_empty_set2
-// CHECK: @@set2 = (d0, d1) : (d0 - 100 == 0, d1 - 10 == 0, d0 * -1 + 100 >= 0, d1 >= 0, d1 + 101 >= 0)
+// @@set2 = (d0, d1) : (d0 - 100 == 0, d1 - 10 == 0, d0 * -1 + 100 >= 0, d1 >= 0, d1 + 101 >= 0)
@@set2 = (d0, d1) : (d0 - 100 == 0, d1 - 10 == 0, -d0 + 100 >= 0, d1 >= 0, d1 + 101 >= 0)
// Set for test case: test_gaussian_elimination_empty_set3
-// CHECK: @@set3 = (d0, d1)[s0, s1] : (1 == 0)
+// @@set3 = (d0, d1)[s0, s1] : (1 == 0)
@@set3 = (d0, d1)[s0, s1] : (d0 - s0 == 0, d0 + s0 == 0, s0 - 1 == 0)
// Set for test case: test_gaussian_elimination_non_empty_set4
-// CHECK: @@set4 = (d0, d1)[s0, s1] : (d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0, d0 * 5 - d1 * 11 + s0 * 7 + s1 == 0, d0 * 11 + d1 * 7 - s0 * 5 + s1 == 0, d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0)
@@set4 = (d0, d1)[s0, s1] : (d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0,
d0 * 5 - d1 * 11 + s0 * 7 + s1 == 0,
d0 * 11 + d1 * 7 - s0 * 5 + s1 == 0,
d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0)
-// Add invalide constraints to previous non-empty set to make it empty.
+// Add invalid constraints to previous non-empty set to make it empty.
// Set for test case: test_gaussian_elimination_empty_set5
-// CHECK: @@set5 = (d0, d1)[s0, s1] : (1 == 0)
@@set5 = (d0, d1)[s0, s1] : (d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0,
d0 * 5 - d1 * 11 + s0 * 7 + s1 == 0,
d0 * 11 + d1 * 7 - s0 * 5 + s1 == 0,
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
// CHECK: @@set0(%i0, %i1)
- if @@set0(%i0, %i1) {
+ if (d0, d1) : (2 == 0)(%i0, %i1) {
}
}
}
mlfunc @test_gaussian_elimination_empty_set1() {
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
- // CHECK: @@set1(%i0, %i1)
- if @@set1(%i0, %i1) {
+ // CHECK: @@set0(%i0, %i1)
+ if (d0, d1) : (1 >= 0, -1 >= 0) (%i0, %i1) {
}
}
}
mlfunc @test_gaussian_elimination_non_empty_set2() {
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
- // CHECK: @@set2(%i0, %i1)
+ // CHECK: @@set1(%i0, %i1)
if @@set2(%i0, %i1) {
}
}
%c11 = constant 11 : index
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
- // CHECK: @@set3(%i0, %i1)[%c7, %c11]
+ // CHECK: @@set2(%i0, %i1)[%c7, %c11]
if @@set3(%i0, %i1)[%c7, %c11] {
}
}
%c11 = constant 11 : index
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
- // CHECK: @@set4(%i0, %i1)[%c7, %c11]
+ // CHECK: @@set3(%i0, %i1)[%c7, %c11]
if @@set4(%i0, %i1)[%c7, %c11] {
}
}
%c11 = constant 11 : index
for %i0 = 1 to 10 {
for %i1 = 1 to 100 {
- // CHECK: @@set5(%i0, %i1)[%c7, %c11]
+ // CHECK: @@set2(%i0, %i1)[%c7, %c11]
if @@set5(%i0, %i1)[%c7, %c11] {
}
}
}
return
-}
\ No newline at end of file
+}
+
+// CHECK-LABEL: mlfunc @test_fourier_motzkin(%arg0 : index) {
+mlfunc @test_fourier_motzkin(%N : index) {
+ for %i = 0 to 10 {
+ for %j = 0 to 10 {
+ // CHECK: if @@set0(%i0, %i1)
+ if (d0, d1) : (d0 - d1 >= 0, d1 - d0 - 1 >= 0)(%i, %j) {
+ "foo"() : () -> ()
+ }
+ // CHECK: if @@set4(%i0)
+ if (d0) : (d0 >= 0, -d0 - 1 >= 0)(%i) {
+ "bar"() : () -> ()
+ }
+ // CHECK: if @@set4(%i0)
+ if (d0) : (d0 >= 0, -d0 - 1 >= 0)(%i) {
+ "foo"() : () -> ()
+ }
+ // CHECK: if @@set5(%i0)[%arg0, %arg0]
+ if (d0)[s0, s1] : (d0 >= 0, -d0 + s0 - 1 >= 0, -s0 >= 0)(%i)[%N, %N] {
+ "bar"() : () -> ()
+ }
+ // CHECK: if @@set6(%i0, %i1, %arg0)
+ // The set below implies d0 = d1; so d1 >= d0, but d0 >= d1 + 1.
+ if (d0, d1, d2) : (d0 - d1 == 0, d2 - d0 >= 0, d0 - d1 - 1 >= 0)(%i, %j, %N) {
+ "foo"() : () -> ()
+ }
+ }
+ }
+ return
+}
LoopUnrollAndJam,
PipelineDataTransfer,
PrintCFGGraph,
- SimplifyAffineExpr,
+ SimplifyAffineStructures,
TFRaiseControlFlow,
XLALower,
};
"explicitly managed levels of the memory hierarchy"),
clEnumValN(PrintCFGGraph, "print-cfg-graph",
"Print CFG graph per function"),
- clEnumValN(SimplifyAffineExpr, "simplify-affine-expr",
+ clEnumValN(SimplifyAffineStructures, "simplify-affine-structures",
"Simplify affine expressions"),
clEnumValN(TFRaiseControlFlow, "tf-raise-control-flow",
"Dynamic TensorFlow Switch/Match nodes to a CFG"),
case PrintCFGGraph:
pass = createPrintCFGGraphPass();
break;
- case SimplifyAffineExpr:
- pass = createSimplifyAffineExprPass();
+ case SimplifyAffineStructures:
+ pass = createSimplifyAffineStructuresPass();
break;
case TFRaiseControlFlow:
pass = createRaiseTFControlFlowPass();