[llvm] [SCEV] Compute post-inc non-strict exit counts via pre-inc form (PR #215990)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 01:52:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: yasmincs

<details>
<summary>Changes</summary>

For some non-unit-stride, early exit loops with a post-inc non-strict latch compare, SCEV can compute an exact latch exit count for the pre-inc form but not for the equivalent post-inc form, even though both encode the same information. This patch retries exit-count analysis using a pre-inc sibling AddRec when the usual `X < (Y+1)` path does not yield an exact count. If that still does not find an exact count, any max BTC already computed by the direct analysis is preserved.

This came up while working on an InstCombine change that keeps multi-use post-inc latch icmps (so wrap flags remain available to later passes). With post-inc form preserved, some loops that were previously rewritten to pre-inc lose an exact latch exit count unless SCEV can analyze the post-inc shape directly. See #<!-- -->215989 for that change and the broader motivation.

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


3 Files Affected:

- (modified) llvm/include/llvm/Analysis/ScalarEvolution.h (+7) 
- (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+84-3) 
- (modified) llvm/test/Analysis/ScalarEvolution/exit-count-non-strict.ll (+36) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 50af763614a31..7207d189ebf73 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -2186,6 +2186,13 @@ class ScalarEvolution {
   /// CouldNotCompute.
   ExitLimit howFarToNonZero(const SCEV *V, const Loop *L);
 
+  /// Try to compute the number of times a non-strict post-inc comparison
+  /// executes by expressing it as a strict comparison of the pre-inc sibling.
+  ExitLimit howManyLessThansViaPreInc(const SCEV *LHS, const SCEV *RHS,
+                                      const Loop *L, bool IsSigned,
+                                      bool ControlsOnlyExit,
+                                      bool AllowPredicates);
+
   /// Return the number of times an exit condition containing the specified
   /// less-than comparison will execute.  If not computable, return
   /// CouldNotCompute.
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index c0cdce982e623..e3b6c20fd8371 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -9497,6 +9497,12 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
 
   bool ControllingFiniteLoop = ControlsOnlyExit && loopHasNoAbnormalExits(L) &&
                                loopIsFiniteByAssumption(L);
+
+  // Preserve the original operands
+  SCEVUse OrigLHS = LHS;
+  SCEVUse OrigRHS = RHS;
+  CmpPredicate OrigPred = Pred;
+
   // Simplify the operands before analyzing them.
   (void)SimplifyICmpOperands(Pred, LHS, RHS, /*Depth=*/0);
 
@@ -9592,7 +9598,9 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
     break;
   }
   case ICmpInst::ICMP_SLE:
-  case ICmpInst::ICMP_ULE:
+  case ICmpInst::ICMP_ULE: {
+    bool IsSigned = ICmpInst::isSigned(Pred);
+
     // Since the loop is finite, an invariant RHS cannot include the boundary
     // value, otherwise it would loop forever.
     if (!EnableFiniteLoopControl || !ControllingFiniteLoop ||
@@ -9607,7 +9615,7 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
       // likely that we use a legal type.
       auto *NewType =
           Type::getIntNTy(OldType->getContext(), OldType->getBitWidth() * 2);
-      if (ICmpInst::isSigned(Pred)) {
+      if (IsSigned) {
         LHS = getSignExtendExpr(LHS, NewType);
         RHS = getSignExtendExpr(RHS, NewType);
       } else {
@@ -9616,12 +9624,46 @@ ScalarEvolution::ExitLimit ScalarEvolution::computeExitLimitFromICmp(
       }
     }
     RHS = getAddExpr(getOne(RHS->getType()), RHS);
-    [[fallthrough]];
+    ExitLimit EL = howManyLessThans(LHS, RHS, L, IsSigned, ControlsOnlyExit,
+                                    AllowPredicates);
+    if (EL.hasFullInfo())
+      return EL;
+
+    // X < (Y+1) failed to get an exact count. Try the pre-inc form.
+    ExitLimit PreEL = howManyLessThansViaPreInc(
+        OrigLHS, OrigRHS, L, IsSigned, ControlsOnlyExit, AllowPredicates);
+    if (PreEL.hasFullInfo())
+      return PreEL;
+
+    // Neither form found an exact count. Preserve whatever partial (e.g. max)
+    // information the direct analysis produced.
+    if (EL.hasAnyInfo())
+      return EL;
+    if (PreEL.hasAnyInfo())
+      return PreEL;
+    break;
+  }
   case ICmpInst::ICMP_SLT:
   case ICmpInst::ICMP_ULT: { // while (X < Y)
     bool IsSigned = ICmpInst::isSigned(Pred);
     ExitLimit EL = howManyLessThans(LHS, RHS, L, IsSigned, ControlsOnlyExit,
                                     AllowPredicates);
+    if (EL.hasFullInfo())
+      return EL;
+
+    // SimplifyICmpOperands may have rewritten an original X <= Y into
+    // X < (Y + 1). Retry with the pre-inc form of the original compare.
+    if (OrigPred == ICmpInst::ICMP_ULE || OrigPred == ICmpInst::ICMP_SLE) {
+      ExitLimit PreEL = howManyLessThansViaPreInc(
+          OrigLHS, OrigRHS, L, ICmpInst::isSigned(OrigPred), ControlsOnlyExit,
+          AllowPredicates);
+      if (PreEL.hasFullInfo())
+        return PreEL;
+      if (!EL.hasAnyInfo() && PreEL.hasAnyInfo())
+        EL = PreEL;
+    }
+
+    // Preserve whatever partial (e.g. max) information was found.
     if (EL.hasAnyInfo())
       return EL;
     break;
@@ -13317,6 +13359,45 @@ const SCEV *ScalarEvolution::computeMaxBECountForLT(const SCEV *Start,
                          getConstant(StrideForMaxBECount) /* Step */);
 }
 
+ScalarEvolution::ExitLimit ScalarEvolution::howManyLessThansViaPreInc(
+    const SCEV *LHS, const SCEV *RHS, const Loop *L, bool IsSigned,
+    bool ControlsOnlyExit, bool AllowPredicates) {
+  if (!isLoopInvariant(RHS, L))
+    return getCouldNotCompute();
+
+  const auto *AR = dyn_cast<SCEVAddRecExpr>(LHS);
+  SCEV::NoWrapFlags NoWrapFlag = IsSigned ? SCEV::FlagNSW : SCEV::FlagNUW;
+  if (!AR || AR->getLoop() != L || !AR->isAffine() ||
+      !AR->getType()->isIntegerTy() || !AR->getNoWrapFlags(NoWrapFlag))
+    return getCouldNotCompute();
+
+  const SCEV *Step = AR->getStepRecurrence(*this);
+  const auto *StepC = dyn_cast<SCEVConstant>(Step);
+  if (!StepC || !StepC->getAPInt().isStrictlyPositive() ||
+      !willNotOverflow(Instruction::Sub, IsSigned, AR->getStart(), Step))
+    return getCouldNotCompute();
+
+  const SCEV *PreStart = getMinusSCEV(AR->getStart(), Step);
+  // Do not transfer nowrap flags from the post-inc AddRec to its sibling.
+  const SCEV *PreAR = getAddRecExpr(PreStart, Step, L, SCEV::FlagAnyWrap);
+
+  const APInt &StepV = StepC->getAPInt();
+  auto IsMultipleOfStep = [&](const SCEV *S) {
+    if (StepV.isOne())
+      return true;
+    if (StepV.isPowerOf2())
+      return getMinTrailingZeros(S) >= StepV.logBase2();
+    return getConstantMultiple(S).urem(StepV).isZero();
+  };
+  if (!IsMultipleOfStep(PreAR) || !IsMultipleOfStep(RHS))
+    return getCouldNotCompute();
+
+  // With both sides aligned to Step, this is the unit-stride identity
+  // (X + 1) <= Y  <=>  X < Y, scaled by Step.
+  return howManyLessThans(PreAR, RHS, L, IsSigned, ControlsOnlyExit,
+                          AllowPredicates);
+}
+
 ScalarEvolution::ExitLimit
 ScalarEvolution::howManyLessThans(const SCEV *LHS, const SCEV *RHS,
                                   const Loop *L, bool IsSigned,
diff --git a/llvm/test/Analysis/ScalarEvolution/exit-count-non-strict.ll b/llvm/test/Analysis/ScalarEvolution/exit-count-non-strict.ll
index 1e15d2d0d6461..401c03e23e459 100644
--- a/llvm/test/Analysis/ScalarEvolution/exit-count-non-strict.ll
+++ b/llvm/test/Analysis/ScalarEvolution/exit-count-non-strict.ll
@@ -499,3 +499,39 @@ latch:
 exit:
   ret void
 }
+
+define i1 @postinc_step64_aligned_early_exit(ptr %bits, i64 %n) {
+; CHECK-LABEL: 'postinc_step64_aligned_early_exit'
+; CHECK-NEXT:  Determining loop execution counts for: @postinc_step64_aligned_early_exit
+; CHECK-NEXT:  Loop %header: <multiple exits> Unpredictable backedge-taken count.
+; CHECK-NEXT:    exit count for header: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:    exit count for latch: ((63 + (64 * (%n /u 64))<nuw>)<nuw><nsw> /u 64)
+; CHECK-NEXT:  Loop %header: constant max backedge-taken count is i64 288230376151711743
+; CHECK-NEXT:  Loop %header: symbolic max backedge-taken count is ((63 + (64 * (%n /u 64))<nuw>)<nuw><nsw> /u 64)
+; CHECK-NEXT:    symbolic max exit count for header: ***COULDNOTCOMPUTE***
+; CHECK-NEXT:    symbolic max exit count for latch: ((63 + (64 * (%n /u 64))<nuw>)<nuw><nsw> /u 64)
+;
+entry:
+  %limit = and i64 %n, -64
+  %empty = icmp eq i64 %limit, 0
+  br i1 %empty, label %exit.false, label %header
+
+header:
+  %iv = phi i64 [ 0, %entry ], [ %next, %latch ]
+  %idx = lshr exact i64 %iv, 3
+  %ptr = getelementptr inbounds i8, ptr %bits, i64 %idx
+  %val = load i64, ptr %ptr, align 8
+  %all.ones = icmp eq i64 %val, -1
+  br i1 %all.ones, label %latch, label %exit.false
+
+latch:
+  %next = add nuw nsw i64 %iv, 64
+  %done = icmp ugt i64 %next, %limit
+  br i1 %done, label %exit.true, label %header
+
+exit.true:
+  ret i1 true
+
+exit.false:
+  ret i1 false
+}

``````````

</details>


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


More information about the llvm-commits mailing list