[llvm] [ConstraintElim] Try to get induction step/start/flags from IR (NFC-ish) (PR #209284)

Florian Hahn via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 05:22:34 PDT 2026


https://github.com/fhahn updated https://github.com/llvm/llvm-project/pull/209284

>From 6c6d35d9209844d965a1d60228b34bfeea73b9ec Mon Sep 17 00:00:00 2001
From: Florian Hahn <flo at fhahn.com>
Date: Thu, 9 Jul 2026 17:52:28 +0100
Subject: [PATCH] [ConstraintElim] Try to get induction step/start/flags from
 IR (NFC-ish)

Handle the common integer inductions where the increment is plain add
PN, C by looking at IR instead of querying SCEV. There's effectively no
re-use between SCEV expression created in ConstraintElimination, and
handling simple cases in IR can avoid unnecessary, expensive SCEV
queries.

Should be NFC-ish. We miss trivial folding of the start value (see
regressed test), but that should not happen in end-to-end pipeline.

Improves compile-time for some workloads, ClamAV, SPASS:
https://llvm-compile-time-tracker.com/compare.php?from=cf41975d04e0a7f5bb0d62dbafa7da6e4c9e3a3&to=ad40c6f15d9cfced4a07c0c9e165b2e773c3c116&stat=instructions:u

I am working on follow-p changes that will handle more cases, where
avoiding unnecessary queries will be more important.
---
 .../Scalar/ConstraintElimination.cpp          | 84 ++++++++++---------
 .../monotonic-int-phis-wrapping.ll            |  3 +-
 2 files changed, 47 insertions(+), 40 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 5c0377c454dd5..6136923d798ea 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
 #include "llvm/Analysis/ScalarEvolution.h"
 #include "llvm/Analysis/ScalarEvolutionExpressions.h"
+#include "llvm/Analysis/ScalarEvolutionPatternMatch.h"
 #include "llvm/Analysis/TargetLibraryInfo.h"
 #include "llvm/Analysis/ValueTracking.h"
 #include "llvm/IR/DataLayout.h"
@@ -48,6 +49,7 @@
 
 using namespace llvm;
 using namespace PatternMatch;
+using namespace SCEVPatternMatch;
 
 #define DEBUG_TYPE "constraint-elimination"
 
@@ -970,53 +972,46 @@ void State::addInfoForInductions(BasicBlock &BB) {
     return;
 
   BasicBlock *LoopPred = L->getLoopPredecessor();
-  if (!LoopPred || !L->isLoopInvariant(B))
+  if (!LoopPred || !L->isLoopInvariant(B) || !SE.isSCEVable(PN->getType()))
     return;
 
-  auto *AR = dyn_cast_or_null<SCEVAddRecExpr>(SE.getSCEV(PN));
-  if (!AR || AR->getLoop() != L)
-    return;
-
-  const SCEV *StartSCEV = AR->getStart();
-  Value *StartValue = nullptr;
-  if (auto *C = dyn_cast<SCEVConstant>(StartSCEV)) {
-    StartValue = C->getValue();
+  Value *StartValue = PN->getIncomingValueForBlock(LoopPred);
+  BasicBlock *BackedgeBB = PN->getIncomingBlock(0) == LoopPred
+                               ? PN->getIncomingBlock(1)
+                               : PN->getIncomingBlock(0);
+  Value *Backedge = PN->getIncomingValueForBlock(BackedgeBB);
+  const APInt *StepOffset;
+  // Monotonicity is only used if the step is non-negative, so it reduces to the
+  // induction wrap flags.
+  bool MonotonicallyIncreasingUnsigned, MonotonicallyIncreasingSigned;
+  const SCEV *StartSCEV = nullptr;
+  if (match(Backedge, m_c_Add(m_Specific(PN), m_APInt(StepOffset)))) {
+    auto *Inc = cast<OverflowingBinaryOperator>(Backedge);
+    MonotonicallyIncreasingUnsigned = Inc->hasNoUnsignedWrap();
+    MonotonicallyIncreasingSigned = Inc->hasNoSignedWrap();
   } else {
-    StartValue = PN->getIncomingValueForBlock(LoopPred);
-    assert(SE.getSCEV(StartValue) == StartSCEV && "inconsistent start value");
+    const SCEV *Expr = SE.getSCEV(PN);
+    if (!match(Expr,
+               m_scev_AffineAddRec(m_SCEV(StartSCEV), m_scev_APInt(StepOffset),
+                                   m_SpecificLoop(L))))
+      return;
+    MonotonicallyIncreasingUnsigned =
+        cast<SCEVAddRecExpr>(Expr)->hasNoUnsignedWrap();
+    MonotonicallyIncreasingSigned =
+        cast<SCEVAddRecExpr>(Expr)->hasNoSignedWrap();
   }
 
   DomTreeNode *DTN = DT.getNode(InLoopSucc);
-  auto IncUnsigned = SE.getMonotonicPredicateType(AR, CmpInst::ICMP_UGT);
-  auto IncSigned = SE.getMonotonicPredicateType(AR, CmpInst::ICMP_SGT);
-  bool MonotonicallyIncreasingUnsigned =
-      IncUnsigned == ScalarEvolution::MonotonicallyIncreasing;
-  bool MonotonicallyIncreasingSigned =
-      IncSigned == ScalarEvolution::MonotonicallyIncreasing;
-  // If SCEV guarantees that AR does not wrap, PN >= StartValue can be added
-  // unconditionally.
-  if (MonotonicallyIncreasingUnsigned)
-    WorkList.push_back(
-        FactOrCheck::getConditionFact(DTN, CmpInst::ICMP_UGE, PN, StartValue));
-  if (MonotonicallyIncreasingSigned)
-    WorkList.push_back(
-        FactOrCheck::getConditionFact(DTN, CmpInst::ICMP_SGE, PN, StartValue));
-
-  APInt StepOffset;
-  if (auto *C = dyn_cast<SCEVConstant>(AR->getStepRecurrence(SE)))
-    StepOffset = C->getAPInt();
-  else
-    return;
 
   // If we looked through `PN + C`, only derive facts when that add is
   // really the induction's post-increment.
-  if (IncStep && (*IncStep != StepOffset || StepOffset.isNegative()))
+  if (IncStep && (*IncStep != *StepOffset || StepOffset->isNegative()))
     return;
 
   // Handle negative steps.
-  if (StepOffset.isNegative()) {
+  if (StepOffset->isNegative()) {
     // TODO: Extend to allow steps > -1.
-    if (!(-StepOffset).isOne())
+    if (!(-*StepOffset).isOne())
       return;
 
     // AR may wrap.
@@ -1039,17 +1034,28 @@ void State::addInfoForInductions(BasicBlock &BB) {
     return;
   }
 
+  // If the induction is known not to wrap, PN >= StartValue can be added
+  // unconditionally.
+  if (MonotonicallyIncreasingUnsigned)
+    WorkList.push_back(
+        FactOrCheck::getConditionFact(DTN, CmpInst::ICMP_UGE, PN, StartValue));
+  if (MonotonicallyIncreasingSigned)
+    WorkList.push_back(
+        FactOrCheck::getConditionFact(DTN, CmpInst::ICMP_SGE, PN, StartValue));
+
   // Make sure AR either steps by 1 or that the value we compare against is a
   // GEP based on the same start value and all offsets are a multiple of the
   // step size, to guarantee that the induction will reach the value.
-  if (StepOffset.isZero() || StepOffset.isNegative())
+  if (StepOffset->isZero() || StepOffset->isNegative())
     return;
 
-  if (!StepOffset.isOne()) {
+  if (!StepOffset->isOne()) {
     // Check whether B-Start is known to be a multiple of StepOffset.
+    if (!StartSCEV)
+      StartSCEV = SE.getSCEV(StartValue);
     const SCEV *BMinusStart = SE.getMinusSCEV(SE.getSCEV(B), StartSCEV);
     if (isa<SCEVCouldNotCompute>(BMinusStart) ||
-        !SE.getConstantMultiple(BMinusStart).urem(StepOffset).isZero())
+        !SE.getConstantMultiple(BMinusStart).urem(*StepOffset).isZero())
       return;
   }
 
@@ -1060,7 +1066,7 @@ void State::addInfoForInductions(BasicBlock &BB) {
     if (!StartC)
       return;
     bool Overflow = false;
-    APInt Sum = StartC->getValue().uadd_ov(StepOffset, Overflow);
+    APInt Sum = StartC->getValue().uadd_ov(*StepOffset, Overflow);
     if (Overflow)
       return;
     LowerBound = ConstantInt::get(StartValue->getType(), Sum);
@@ -1090,7 +1096,7 @@ void State::addInfoForInductions(BasicBlock &BB) {
   // Try to add condition from header to the dedicated exit blocks. When exiting
   // either with EQ or NE in the header, we know that the induction value must
   // be u<= B, as other exits may only exit earlier.
-  assert(!StepOffset.isNegative() && "induction must be increasing");
+  assert(!StepOffset->isNegative() && "induction must be increasing");
   assert((Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) &&
          "unsupported predicate");
   ConditionTy Precond = {CmpInst::ICMP_ULE, LowerBound, B};
diff --git a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-wrapping.ll b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-wrapping.ll
index 17aa4d54abfba..ab77f54533626 100644
--- a/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-wrapping.ll
+++ b/llvm/test/Transforms/ConstraintElimination/monotonic-int-phis-wrapping.ll
@@ -96,7 +96,8 @@ define void @test_iv_nuw_nsw_2_uge_start(i8 %len.n, i8 %a) {
 ; CHECK-NEXT:    [[C_2:%.*]] = call i1 @cond()
 ; CHECK-NEXT:    br i1 [[C_2]], label [[LOOP_LATCH]], label [[EXIT]]
 ; CHECK:       loop.latch:
-; CHECK-NEXT:    call void @use.i1(i1 true)
+; CHECK-NEXT:    [[T_1:%.*]] = icmp uge i8 [[IV]], -1
+; CHECK-NEXT:    call void @use.i1(i1 [[T_1]])
 ; CHECK-NEXT:    [[IV_NEXT]] = add nuw nsw i8 [[IV]], 1
 ; CHECK-NEXT:    br label [[LOOP_HEADER]]
 ; CHECK:       exit:



More information about the llvm-commits mailing list