[llvm] [ConstraintElim] Divide rows by the GCD of their coefficients. (PR #228765)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 13:21:48 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: Florian Hahn (fhahn)

<details>
<summary>Changes</summary>

Divide the variable coefficients of each row handled in the system by
their greatest common divisor. That includes rows added for facts, rows
added during Fourier-Motzkin elimination and queries.

As all variables are integers, the resulting rows are equivalent, but
smaller coefficients make overflows less likely (which lead to bail out
while solving).

This enables a few additional folds:
https://github.com/dtcxzyw/llvm-opt-benchmark-nightly/pull/1569

Compile-time impact is in the noise:
https://llvm-compile-time-tracker.com/compare.php?from=7516fa2ca8a47c15f5f31265f05c1157b56bb735&to=d76271672937521a634f56efd5219a275f139cf5&stat=instructions:u

---
Full diff: https://github.com/llvm/llvm-project/pull/228765.diff


6 Files Affected:

- (modified) llvm/include/llvm/Analysis/ConstraintSystem.h (+5) 
- (modified) llvm/lib/Analysis/ConstraintSystem.cpp (+24) 
- (modified) llvm/lib/Transforms/Scalar/ConstraintElimination.cpp (+7-4) 
- (modified) llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll (+26) 
- (modified) llvm/test/Transforms/ConstraintElimination/max-row-limit.ll (+81) 
- (modified) llvm/test/Transforms/ConstraintElimination/shl.ll (+46) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/ConstraintSystem.h b/llvm/include/llvm/Analysis/ConstraintSystem.h
index c14e0fbd8b9cf..33941a3b25022 100644
--- a/llvm/include/llvm/Analysis/ConstraintSystem.h
+++ b/llvm/include/llvm/Analysis/ConstraintSystem.h
@@ -104,6 +104,7 @@ class ConstraintSystem {
     for (const Entry &E : R)
       if (E.Coefficient != 0)
         NewRow.push_back(E);
+    normalize(NewRow);
     return true;
   }
 
@@ -115,6 +116,10 @@ class ConstraintSystem {
   /// Returns true if there may be a solution for the constraints in the system.
   LLVM_ABI bool mayHaveSolution();
 
+  /// Divide the variable coefficients of \p R by their greatest common divisor
+  /// G and round the constant down to a multiple of G.
+  LLVM_ABI static void normalize(MutableArrayRef<Entry> R);
+
   static RowTy negate(RowTy R) {
     assert(hasConstantEntry(R) && "row must have a constant entry");
     // The negated constraint R is obtained by multiplying by -1 and adding 1 to
diff --git a/llvm/lib/Analysis/ConstraintSystem.cpp b/llvm/lib/Analysis/ConstraintSystem.cpp
index 1aabb648a3638..2c239ff3c402f 100644
--- a/llvm/lib/Analysis/ConstraintSystem.cpp
+++ b/llvm/lib/Analysis/ConstraintSystem.cpp
@@ -14,6 +14,7 @@
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/MathExtras.h"
 
+#include <numeric>
 #include <string>
 
 using namespace llvm;
@@ -143,6 +144,7 @@ bool ConstraintSystem::eliminateUsingFM() {
       }
       if (Overflow || NR.empty())
         continue;
+      normalize(NR);
       Constraints.push_back(std::move(NR));
       // Give up if the new system gets too big.
       if (Constraints.size() > 500)
@@ -154,6 +156,26 @@ bool ConstraintSystem::eliminateUsingFM() {
   return true;
 }
 
+void ConstraintSystem::normalize(MutableArrayRef<Entry> R) {
+  // The sum of the variable terms is a multiple of G, so it is <= C iff it is
+  // <= floor(C / G) * G.
+  int64_t G = 0;
+  for (const Entry &E : R) {
+    if (E.Id == 0)
+      continue;
+    if (E.Coefficient == std::numeric_limits<int64_t>::min())
+      return;
+    G = std::gcd(G, E.Coefficient);
+    if (G == 1)
+      return;
+  }
+  if (G == 0)
+    return;
+  for (Entry &E : R)
+    E.Coefficient =
+        E.Id == 0 ? divideFloorSigned(E.Coefficient, G) : E.Coefficient / G;
+}
+
 bool ConstraintSystem::mayHaveSolutionImpl() {
   while (!Constraints.empty() && NumVariables > 0) {
     if (!eliminateUsingFM())
@@ -300,6 +322,8 @@ bool ConstraintSystem::isConditionImplied(RowTy R) const {
   if (isConstantOnly(R))
     return getConstant(R) >= 0;
 
+  normalize(R);
+
   // R is trivially implied if a single row of the system implies it.
   if (isImpliedBySingleRow(R))
     return true;
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 16ecfd3a76068..ec59a29111e4b 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -2270,10 +2270,13 @@ void ConstraintInfo::addFactImpl(CmpInst::Predicate Pred, Value *A, Value *B,
 
   auto &CSToUse = getCS(R.IsSigned);
   // A row implied by a single existing row adds no information. Rows in the
-  // system are removed in reverse order, so the existing row outlives R.
-  if (!R.isEq() && NewVariables.empty() &&
-      CSToUse.isImpliedBySingleRow(R.Coefficients))
-    return;
+  // system are removed in reverse order, so the existing row outlives R. Rows
+  // in the system are normalized, so normalize R before comparing.
+  if (!R.isEq() && NewVariables.empty()) {
+    ConstraintSystem::normalize(R.Coefficients);
+    if (CSToUse.isImpliedBySingleRow(R.Coefficients))
+      return;
+  }
   LLVM_DEBUG(dbgs() << "Adding '"; dumpUnpackedICmp(dbgs(), Pred, A, B);
              dbgs() << "'\n");
   bool Added = CSToUse.addRow(R.Coefficients, R.NumVars);
diff --git a/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll b/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
index 70c3a6480d3f1..6e4f016c6ab4d 100644
--- a/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
+++ b/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
@@ -456,4 +456,30 @@ entry:
   ret i1 %c.3
 }
 
+; Combining the rows with coefficients of 2^33 during Fourier-Motzkin
+; elimination would overflow, unless the rows are divided by their GCD first.
+define i1 @gcd_transitive_coefficients_overflow(i64 %x, i64 %y, i64 %z) {
+; CHECK-LABEL: @gcd_transitive_coefficients_overflow(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[S:%.*]] = shl nuw i64 [[X:%.*]], 33
+; CHECK-NEXT:    [[T:%.*]] = shl nuw i64 [[Y:%.*]], 33
+; CHECK-NEXT:    [[U:%.*]] = shl nuw i64 [[Z:%.*]], 33
+; CHECK-NEXT:    [[C_1:%.*]] = icmp ule i64 [[S]], [[T]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C_1]])
+; CHECK-NEXT:    [[C_2:%.*]] = icmp ule i64 [[T]], [[U]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C_2]])
+; CHECK-NEXT:    ret i1 true
+;
+entry:
+  %s = shl nuw i64 %x, 33
+  %t = shl nuw i64 %y, 33
+  %u = shl nuw i64 %z, 33
+  %c.1 = icmp ule i64 %s, %t
+  call void @llvm.assume(i1 %c.1)
+  %c.2 = icmp ule i64 %t, %u
+  call void @llvm.assume(i1 %c.2)
+  %c.3 = icmp ule i64 %s, %u
+  ret i1 %c.3
+}
+
 declare void @llvm.assume(i1)
diff --git a/llvm/test/Transforms/ConstraintElimination/max-row-limit.ll b/llvm/test/Transforms/ConstraintElimination/max-row-limit.ll
index 0e078109ed663..80a48d3e22e1d 100644
--- a/llvm/test/Transforms/ConstraintElimination/max-row-limit.ll
+++ b/llvm/test/Transforms/ConstraintElimination/max-row-limit.ll
@@ -75,5 +75,86 @@ bb5:
 exit:
   ret i1 false
 }
+; Facts that only differ by a common factor are the same row after dividing by the GCD.
+define i1 @max_row_limit_gcd_duplicate_fact(i8 %x, i8 %y, i8 %z, i8 %w0, i8 %w1) {
+; SIMP-LABEL: @max_row_limit_gcd_duplicate_fact(
+; SIMP-NEXT:  entry:
+; SIMP-NEXT:    [[A:%.*]] = shl nuw i8 [[X:%.*]], 1
+; SIMP-NEXT:    [[B:%.*]] = shl nuw i8 [[Y:%.*]], 1
+; SIMP-NEXT:    [[C_1:%.*]] = icmp ule i8 [[A]], [[B]]
+; SIMP-NEXT:    br i1 [[C_1]], label [[DUP:%.*]], label [[EXIT:%.*]]
+; SIMP:       dup:
+; SIMP-NEXT:    [[C_2:%.*]] = icmp ule i8 [[A]], [[B]]
+; SIMP-NEXT:    call void @llvm.assume(i1 [[C_2]])
+; SIMP-NEXT:    [[P_0:%.*]] = icmp ule i8 [[W0:%.*]], 10
+; SIMP-NEXT:    call void @llvm.assume(i1 [[P_0]])
+; SIMP-NEXT:    [[P_1:%.*]] = icmp ule i8 [[W1:%.*]], 10
+; SIMP-NEXT:    call void @llvm.assume(i1 [[P_1]])
+; SIMP-NEXT:    [[P_2:%.*]] = icmp ule i8 [[W1]], [[W0]]
+; SIMP-NEXT:    call void @llvm.assume(i1 [[P_2]])
+; SIMP-NEXT:    br label [[NEXT:%.*]]
+; SIMP:       next:
+; SIMP-NEXT:    [[C_3:%.*]] = icmp ule i8 [[Z:%.*]], 10
+; SIMP-NEXT:    br i1 [[C_3]], label [[USE:%.*]], label [[EXIT]]
+; SIMP:       use:
+; SIMP-NEXT:    ret i1 true
+; SIMP:       exit:
+; SIMP-NEXT:    ret i1 false
+;
+; NOSIMP-LABEL: @max_row_limit_gcd_duplicate_fact(
+; NOSIMP-NEXT:  entry:
+; NOSIMP-NEXT:    [[A:%.*]] = shl nuw i8 [[X:%.*]], 1
+; NOSIMP-NEXT:    [[B:%.*]] = shl nuw i8 [[Y:%.*]], 1
+; NOSIMP-NEXT:    [[C_1:%.*]] = icmp ule i8 [[A]], [[B]]
+; NOSIMP-NEXT:    br i1 [[C_1]], label [[DUP:%.*]], label [[EXIT:%.*]]
+; NOSIMP:       dup:
+; NOSIMP-NEXT:    [[C_2:%.*]] = icmp ule i8 [[A]], [[B]]
+; NOSIMP-NEXT:    call void @llvm.assume(i1 [[C_2]])
+; NOSIMP-NEXT:    [[P_0:%.*]] = icmp ule i8 [[W0:%.*]], 10
+; NOSIMP-NEXT:    call void @llvm.assume(i1 [[P_0]])
+; NOSIMP-NEXT:    [[P_1:%.*]] = icmp ule i8 [[W1:%.*]], 10
+; NOSIMP-NEXT:    call void @llvm.assume(i1 [[P_1]])
+; NOSIMP-NEXT:    [[P_2:%.*]] = icmp ule i8 [[W1]], [[W0]]
+; NOSIMP-NEXT:    call void @llvm.assume(i1 [[P_2]])
+; NOSIMP-NEXT:    br label [[NEXT:%.*]]
+; NOSIMP:       next:
+; NOSIMP-NEXT:    [[C_3:%.*]] = icmp ule i8 [[Z:%.*]], 10
+; NOSIMP-NEXT:    br i1 [[C_3]], label [[USE:%.*]], label [[EXIT]]
+; NOSIMP:       use:
+; NOSIMP-NEXT:    [[T:%.*]] = icmp ule i8 [[Z]], 11
+; NOSIMP-NEXT:    ret i1 [[T]]
+; NOSIMP:       exit:
+; NOSIMP-NEXT:    ret i1 false
+;
+entry:
+  %a = shl nuw i8 %x, 1
+  %b = shl nuw i8 %y, 1
+  %c.1 = icmp ule i8 %a, %b
+  br i1 %c.1, label %dup, label %exit
+
+dup:
+  %c.2 = icmp ule i8 %a, %b
+  call void @llvm.assume(i1 %c.2)
+  %p.0 = icmp ule i8 %w0, 10
+  call void @llvm.assume(i1 %p.0)
+  %p.1 = icmp ule i8 %w1, 10
+  call void @llvm.assume(i1 %p.1)
+  %p.2 = icmp ule i8 %w1, %w0
+  call void @llvm.assume(i1 %p.2)
+  br label %next
+
+next:
+  %c.3 = icmp ule i8 %z, 10
+  br i1 %c.3, label %use, label %exit
+
+use:
+  %t = icmp ule i8 %z, 11
+  ret i1 %t
+
+exit:
+  ret i1 false
+}
+
+declare void @llvm.assume(i1)
 ;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
 ; COMMON: {{.*}}
diff --git a/llvm/test/Transforms/ConstraintElimination/shl.ll b/llvm/test/Transforms/ConstraintElimination/shl.ll
index a09af84a64b56..7b9b3b5bb3bc2 100644
--- a/llvm/test/Transforms/ConstraintElimination/shl.ll
+++ b/llvm/test/Transforms/ConstraintElimination/shl.ll
@@ -1525,3 +1525,49 @@ entry:
   %c2 = icmp ule i64 %s, %b
   ret i1 %c2
 }
+
+; 2 * %x <= 2 * %y + 1 implies %x <= %y for integers.
+define i1 @shl_gcd_tighten(i8 %x, i8 %y) {
+; CHECK-LABEL: @shl_gcd_tighten(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[A:%.*]] = shl nuw i8 [[X:%.*]], 1
+; CHECK-NEXT:    [[B:%.*]] = shl nuw i8 [[Y:%.*]], 1
+; CHECK-NEXT:    [[B_1:%.*]] = add nuw i8 [[B]], 1
+; CHECK-NEXT:    [[C_1:%.*]] = icmp ule i8 [[A]], [[B_1]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C_1]])
+; CHECK-NEXT:    [[RES:%.*]] = xor i1 true, false
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+entry:
+  %a = shl nuw i8 %x, 1
+  %b = shl nuw i8 %y, 1
+  %b.1 = add nuw i8 %b, 1
+  %c.1 = icmp ule i8 %a, %b.1
+  call void @llvm.assume(i1 %c.1)
+  %t.1 = icmp ule i8 %x, %y
+  %f.1 = icmp ugt i8 %x, %y
+  %res = xor i1 %t.1, %f.1
+  ret i1 %res
+}
+
+; 2 * %x <= 2 * %y + 1 does not imply %x < %y.
+define i1 @shl_gcd_tighten_not_strict(i8 %x, i8 %y) {
+; CHECK-LABEL: @shl_gcd_tighten_not_strict(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[A:%.*]] = shl nuw i8 [[X:%.*]], 1
+; CHECK-NEXT:    [[B:%.*]] = shl nuw i8 [[Y:%.*]], 1
+; CHECK-NEXT:    [[B_1:%.*]] = add nuw i8 [[B]], 1
+; CHECK-NEXT:    [[C_1:%.*]] = icmp ule i8 [[A]], [[B_1]]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[C_1]])
+; CHECK-NEXT:    [[C_2:%.*]] = icmp ult i8 [[X]], [[Y]]
+; CHECK-NEXT:    ret i1 [[C_2]]
+;
+entry:
+  %a = shl nuw i8 %x, 1
+  %b = shl nuw i8 %y, 1
+  %b.1 = add nuw i8 %b, 1
+  %c.1 = icmp ule i8 %a, %b.1
+  call void @llvm.assume(i1 %c.1)
+  %c.2 = icmp ult i8 %x, %y
+  ret i1 %c.2
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/228765


More information about the llvm-commits mailing list