[llvm] [ConstraintElim] Divide rows by the GCD of their coefficients. (PR #228765)
Florian Hahn via llvm-commits
llvm-commits at lists.llvm.org
Sat Oct 3 13:20:46 PDT 2026
https://github.com/fhahn created https://github.com/llvm/llvm-project/pull/228765
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
>From 8644bc64e94adc6f4e3be660ccc75d7552bc85d2 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Sat, 3 Oct 2026 15:27:34 +0100
Subject: [PATCH 1/2] [ConstraintElim] Add tests for dividing rows by the GCD
of their coefficients.
Precommit tests for a follow-up change.
---
.../large-constant-ints.ll | 27 +++++++
.../ConstraintElimination/max-row-limit.ll | 81 +++++++++++++++++++
.../Transforms/ConstraintElimination/shl.ll | 46 +++++++++++
3 files changed, 154 insertions(+)
diff --git a/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll b/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
index 70c3a6480d3f103..4e2d58b588eb2b7 100644
--- a/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
+++ b/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
@@ -456,4 +456,31 @@ 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: [[C_3:%.*]] = icmp ule i64 [[S]], [[U]]
+; CHECK-NEXT: ret i1 [[C_3]]
+;
+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 0e078109ed6637d..80a48d3e22e1d4a 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 a09af84a64b5641..7b9b3b5bb3bc256 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
+}
>From ac124acebb84eca903e7f9f9aec794bcba0182f4 Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Wed, 30 Sep 2026 11:52:52 +0100
Subject: [PATCH 2/2] [ConstraintElim] Divide rows by the GCD of their
coefficients.
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
---
llvm/include/llvm/Analysis/ConstraintSystem.h | 5 ++++
llvm/lib/Analysis/ConstraintSystem.cpp | 24 +++++++++++++++++++
.../Scalar/ConstraintElimination.cpp | 11 +++++----
.../large-constant-ints.ll | 3 +--
4 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/llvm/include/llvm/Analysis/ConstraintSystem.h b/llvm/include/llvm/Analysis/ConstraintSystem.h
index c14e0fbd8b9cfb8..33941a3b25022d6 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 1aabb648a3638db..2c239ff3c402f3a 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 16ecfd3a7606832..ec59a29111e4b84 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 4e2d58b588eb2b7..6e4f016c6ab4d50 100644
--- a/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
+++ b/llvm/test/Transforms/ConstraintElimination/large-constant-ints.ll
@@ -468,8 +468,7 @@ define i1 @gcd_transitive_coefficients_overflow(i64 %x, i64 %y, i64 %z) {
; 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: [[C_3:%.*]] = icmp ule i64 [[S]], [[U]]
-; CHECK-NEXT: ret i1 [[C_3]]
+; CHECK-NEXT: ret i1 true
;
entry:
%s = shl nuw i64 %x, 33
More information about the llvm-commits
mailing list