FlatAffineConstraints API cleanup; add normalizeConstraintsByGCD().
authorUday Bondhugula <bondhugula@google.com>
Mon, 10 Dec 2018 20:59:53 +0000 (12:59 -0800)
committerjpienaar <jpienaar@google.com>
Fri, 29 Mar 2019 21:24:37 +0000 (14:24 -0700)
- 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

mlir/include/mlir/Analysis/AffineStructures.h
mlir/lib/Analysis/AffineStructures.cpp
mlir/lib/Analysis/Utils.cpp
mlir/lib/Transforms/SimplifyAffineExpr.cpp
mlir/test/Transforms/simplify.mlir

index 107ac8c694c971ad15813e1b8069217a1ec177d3..f4e08a3326e86634a68bbc9c7a33c75170321dd7 100644 (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.
index e07c37e4d94918d19583a49a5caf88dbfa074a89..849bfbda138fc45d501d0cbb29f8c2cad8d40a1a 100644 (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) {
index c0733cb09068067f6af56e312aa7288f53bc7af9..ee2fd053b367f89eddf2c4b71803ef4d167566be 100644 (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.
index 06f3f8f44e2a5b354d28c3e9d8ece62617717de4..a862ec4471a5b95cad7ade1a3f6b1147012e794e 100644 (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())
index ffc4857c35dde9b431d0424bde3c85962984cb1f..ce5c5ece363a0113b5ae4f0dde3ee048f2b8ecb9 100644 (file)
 
 // 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"() : () -> ()