FlatAffineConstraints API cleanup; add normalizeConstraintsByGCD().

- add method normalizeConstraintsByGCD
- call normalizeConstraintsByGCD() and GCDTightenInequalities() at the end of
  projectOut.
- remove call to GCDTightenInequalities() from getMemRefRegion
- change isEmpty() to check isEmptyByGCDTest() / hasInvalidConstraint() each
  time an identifier is eliminated (to detect emptiness early).
- make FourierMotzkinEliminate, gaussianEliminateId(s),
  GCDTightenInequalities() private
- improve / update stale comments

PiperOrigin-RevId: 224866741
This commit is contained in:
Uday Bondhugula 2018-12-10 12:59:53 -08:00 committed by jpienaar
parent 2ef57806ba
commit 6757fb151d
5 changed files with 104 additions and 70 deletions

View File

@ -202,7 +202,11 @@ class IntegerValueSet {
// This will lead to a single equality in 'set'.
explicit IntegerValueSet(const AffineValueMap &avm);
/// Returns true if this integer set is empty.
/// Returns true if this integer set is determined to be empty. Emptiness is
/// checked by by eliminating identifiers successively (through either
/// Gaussian or Fourier-Motzkin) while using the GCD test and a trivial
/// constraint check. Returns 'true' if the constaint system is found to be
/// empty; false otherwise.
bool isEmpty() const;
bool getNumDims() const { return set.getNumDims(); }
@ -319,24 +323,6 @@ public:
// returns true, no integer solution to the equality constraints can exist.
bool isEmptyByGCDTest() const;
/// 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();
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated.
// Returns 'false' otherwise.
bool gaussianEliminateId(unsigned position);
// Eliminates identifiers from equality and inequality constraints
// in column range [posStart, posLimit).
// Returns the number of variables eliminated.
unsigned gaussianEliminateIds(unsigned posStart, unsigned posLimit);
// Clones this object.
std::unique_ptr<FlatAffineConstraints> clone() const;
@ -445,21 +431,13 @@ public:
/// if the composition fails (when vMap is a semi-affine map).
bool composeMap(AffineValueMap *vMap, unsigned pos = 0);
/// 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.
void FourierMotzkinEliminate(unsigned pos, bool darkShadow = false,
bool *isResultIntegerExact = nullptr);
/// Projects out (aka eliminates) 'num' identifiers 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(bondhugula): 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); }
/// Projects out the identifier that is associate with MLValue *.
void projectOut(MLValue *id);
@ -538,8 +516,8 @@ public:
SmallVectorImpl<AffineMap> *ubs,
MLIRContext *context);
/// Returns true if the set is hyper-rectangular on the specified contiguous
/// set of identifiers.
/// Returns true if the set can be trivially detected as being
/// hyper-rectangular on the specified contiguous set of identifiers.
bool isHyperRectangular(unsigned pos, unsigned num) const;
// More expensive ones.
@ -560,6 +538,39 @@ private:
/// 'false'otherwise.
bool hasInvalidConstraint() const;
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated, and false
// otherwise.
inline bool gaussianEliminateId(unsigned position) {
return gaussianEliminateIds(position, position + 1) == 1;
}
// Eliminates identifiers from equality and inequality constraints
// in column range [posStart, posLimit).
// Returns the number of variables eliminated.
unsigned gaussianEliminateIds(unsigned posStart, unsigned posLimit);
/// Eliminates identifier at the specified position using Fourier-Motzkin
/// variable elimination, but uses Gaussian elimination if there is an
/// equality involving that identifier. 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.
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();
/// Removes identifiers in column range [idStart, idLimit), and copies any
/// remaining valid data into place, updates member variables, and resizes
/// arrays as needed.

View File

@ -772,8 +772,9 @@ findConstraintWithNonZeroAt(const FlatAffineConstraints &constraints,
// Normalizes the coefficient values across all columns in 'rowIDx' by their
// GCD in equality or inequality contraints as specified by 'isEq'.
template <bool isEq>
static void normalizeConstraintByGCD(FlatAffineConstraints *constraints,
unsigned rowIdx, bool isEq) {
unsigned rowIdx) {
auto at = [&](unsigned colIdx) -> int64_t {
return isEq ? constraints->atEq(rowIdx, colIdx)
: constraints->atIneq(rowIdx, colIdx);
@ -791,6 +792,15 @@ static void normalizeConstraintByGCD(FlatAffineConstraints *constraints,
}
}
void FlatAffineConstraints::normalizeConstraintsByGCD() {
for (unsigned i = 0, e = getNumEqualities(); i < e; ++i) {
normalizeConstraintByGCD</*isEq=*/true>(this, i);
}
for (unsigned i = 0, e = getNumInequalities(); i < e; ++i) {
normalizeConstraintByGCD</*isEq=*/false>(this, i);
}
}
bool FlatAffineConstraints::hasConsistentState() const {
if (inequalities.size() != getNumInequalities() * numReservedCols)
return false;
@ -809,7 +819,7 @@ bool FlatAffineConstraints::hasConsistentState() const {
/// 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.
/// 'false' otherwise.
bool FlatAffineConstraints::hasInvalidConstraint() const {
assert(hasConsistentState());
auto check = [&](bool isEq) -> bool {
@ -925,20 +935,32 @@ void FlatAffineConstraints::removeIdRange(unsigned idStart, unsigned idLimit) {
// No resize necessary. numReservedCols remains the same.
}
// Performs variable elimination on all identifiers, runs the GCD test on
// 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.
// Checks for emptiness of the set by eliminating identifiers successively and
// using the GCD test (on all equality constraints) and checking for trivially
// invalid constraints. Returns 'true' if the constaint system is found to be
// empty; false otherwise.
bool FlatAffineConstraints::isEmpty() const {
if (isEmptyByGCDTest())
if (isEmptyByGCDTest() || hasInvalidConstraint())
return true;
auto tmpCst = clone();
if (tmpCst->gaussianEliminateIds(0, numIds) < numIds) {
for (unsigned i = 0, e = tmpCst->getNumIds(); i < e; i++)
for (unsigned i = 0, e = tmpCst->getNumIds(); i < e; i++) {
// We check emptiness through trivial checks after eliminating each ID to
// detect emptiness early. Since the checks isEmptyByGCDTest() and
// hasInvalidConstraint() are linear time and single sweep on the constraint
// buffer, this appears reasonable - but can optimize in the future.
if (tmpCst->gaussianEliminateId(0)) {
if (tmpCst->hasInvalidConstraint() || tmpCst->isEmptyByGCDTest())
return true;
} else {
tmpCst->FourierMotzkinEliminate(0);
// If the variable couldn't be eliminated by Gaussian, FM wouldn't have
// modified the equalities in any way. So no need to again run GCD test.
// Check for trivial invalid constraints.
if (tmpCst->hasInvalidConstraint())
return true;
}
}
if (tmpCst->hasInvalidConstraint())
return true;
return false;
}
@ -974,7 +996,7 @@ bool FlatAffineConstraints::isEmptyByGCDTest() const {
}
/// 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
/// analogous 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.
@ -996,13 +1018,6 @@ void FlatAffineConstraints::GCDTightenInequalities() {
}
}
// Eliminates a single identifier at 'position' from equality and inequality
// constraints. Returns 'true' if the identifier was eliminated.
// Returns 'false' otherwise.
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::gaussianEliminateIds(unsigned posStart,
@ -1025,7 +1040,8 @@ unsigned FlatAffineConstraints::gaussianEliminateIds(unsigned posStart,
// No pivot row in equalities with non-zero at 'pivotCol'.
if (!findConstraintWithNonZeroAt(*this, pivotCol, /*isEq=*/false,
pivotRow)) {
// If inequalities are also non-zero in 'pivotCol' it can be eliminated.
// If inequalities are also non-zero in 'pivotCol', it can be
// eliminated.
continue;
}
break;
@ -1035,14 +1051,14 @@ unsigned FlatAffineConstraints::gaussianEliminateIds(unsigned posStart,
for (unsigned i = 0, e = getNumEqualities(); i < e; ++i) {
eliminateFromConstraint(this, i, pivotRow, pivotCol, posStart,
/*isEq=*/true);
normalizeConstraintByGCD(this, i, /*isEq=*/true);
normalizeConstraintByGCD</*isEq=*/true>(this, i);
}
// Eliminate identifier at 'pivotCol' from each inequality row.
for (unsigned i = 0, e = getNumInequalities(); i < e; ++i) {
eliminateFromConstraint(this, i, pivotRow, pivotCol, posStart,
/*isEq=*/false);
normalizeConstraintByGCD(this, i, /*isEq=*/false);
normalizeConstraintByGCD</*isEq=*/false>(this, i);
}
removeEquality(pivotRow);
}
@ -1289,7 +1305,7 @@ bool FlatAffineConstraints::getDimensionBounds(unsigned pos, unsigned num,
return false;
(*lbs)[i] = AffineMap::getConstantMap(lb.getValue(), context);
(*ubs)[i] = AffineMap::getConstantMap(ub.getValue(), context);
projectOut(i, 1);
projectOut(i);
}
return true;
}
@ -1564,7 +1580,7 @@ void FlatAffineConstraints::print(raw_ostream &os) const {
void FlatAffineConstraints::dump() const { print(llvm::errs()); }
void FlatAffineConstraints::removeDuplicates() {
// TODO: remove redundant constraints.
// TODO(mlir-team): remove redundant constraints.
}
void FlatAffineConstraints::clearAndCopyFrom(
@ -1647,9 +1663,6 @@ void FlatAffineConstraints::FourierMotzkinEliminate(
assert(pos < getNumIds() && "invalid position");
assert(hasConsistentState());
// A fast linear time tightening.
GCDTightenInequalities();
// Check if this identifier can be eliminated through a substitution.
for (unsigned r = 0, e = getNumEqualities(); r < e; r++) {
if (atEq(r, pos) != 0) {
@ -1663,6 +1676,9 @@ void FlatAffineConstraints::FourierMotzkinEliminate(
}
}
// A fast linear time tightening.
GCDTightenInequalities();
// Check if the identifier appears at all in any of the inequalities.
unsigned r, e;
for (r = 0, e = getNumInequalities(); r < e; r++) {
@ -1800,14 +1816,19 @@ void FlatAffineConstraints::FourierMotzkinEliminate(
}
void FlatAffineConstraints::projectOut(unsigned pos, unsigned num) {
// 'pos' can be at most getNumCols() - 2.
if (num == 0)
return;
// 'pos' can be at most getNumCols() - 2.
assert(pos <= getNumCols() - 2 && "invalid position");
assert(pos + num < getNumCols() && "invalid range");
for (unsigned i = 0; i < num; i++) {
for (unsigned i = 0; i < num; i++)
FourierMotzkinEliminate(pos);
}
// Fast/trivial simplifications.
normalizeConstraintsByGCD();
GCDTightenInequalities();
}
void FlatAffineConstraints::projectOut(MLValue *id) {

View File

@ -225,9 +225,6 @@ bool mlir::getMemRefRegion(OperationStmt *opStmt, unsigned loopDepth,
regionCst->getNumSymbolIds(),
regionCst->getNumLocalIds());
// Tighten the set.
regionCst->GCDTightenInequalities();
// Set all identifiers appearing after the first 'rank' identifiers as
// symbolic identifiers - so that the ones correspoding to the memref
// dimensions are the dimensional identifiers for the memref region.

View File

@ -60,6 +60,8 @@ FunctionPass *mlir::createSimplifyAffineStructuresPass() {
return new SimplifyAffineStructures();
}
/// Performs basic integer set simplifications. Checks if it's empty, and
/// replaces it with the canonical empty set if it is.
static IntegerSet simplifyIntegerSet(IntegerSet set) {
FlatAffineConstraints fac(set);
if (fac.isEmpty())

View File

@ -44,17 +44,17 @@
// Set for test case: test_gaussian_elimination_non_empty_set4
#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)
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 invalid constraints to previous non-empty set to make it empty.
// Set for test case: test_gaussian_elimination_empty_set5
#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,
d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0,
d0 - 1 == 0, d0 + 2 == 0)
d0 * 11 + d1 * 7 - s0 * 5 + s1 == 0,
d0 * 7 + d1 * 5 + s0 * 11 + s1 == 0,
d0 - 1 == 0, d0 + 2 == 0)
mlfunc @test() {
for %n0 = 0 to 127 {
@ -200,11 +200,14 @@ mlfunc @test_empty_set(%N : index) {
"foo"() : () -> ()
}
// Same as above but with a combination of multiple identifiers. 4*d0 +
// 8*d1 here is a multiple of 4, and so can't lie between 9 and 11.
// 8*d1 here is a multiple of 4, and so can't lie between 9 and 11. GCD
// tightening will tighten constraints to 4*d0 + 8*d1 >= 12 and 4*d0 +
// 8*d1 <= 8; hence infeasible.
// CHECK: if [[SET_EMPTY_2D]](%i2, %i3)
if (d0, d1) : (4*d0 + 8*d1 - 9 >= 0, -4*d0 - 8*d1 + 11 >= 0)(%k, %l) {
"foo"() : () -> ()
}
// Same as above but with equalities added into the mix.
// CHECK: if [[SET_EMPTY_3D]](%i2, %i2, %i3)
if (d0, d1, d2) : (d0 - 4*d2 == 0, d0 + 8*d1 - 9 >= 0, -d0 - 8*d1 + 11 >= 0)(%k, %k, %l) {
"foo"() : () -> ()