[llvm] [IndVars] Invalidate stale SCEV exit condition values (PR #207897)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 6 22:09:41 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Jaeuk Lee (skku970412)

<details>
<summary>Changes</summary>

This fixes a wrong-code miscompile where IndVarSimplify folded a loop exit
using stale ScalarEvolution information from earlier loop passes in the same
loop adaptor run.

In the reduced case, the outer loop exit depends on loop-carried state through
the `%loadedv9.not`/`%ov6.0` phis. After earlier inner-loop transforms, SCEV
still had cached exit-condition/value information. The outer loop
IndVarSimplify then treated the `%lbl_b10` exit as taken on the first
iteration and removed the `@<!-- -->__chk = 5` path.

Invalidate the current loop's cached exit counts and the SCEV expressions
feeding loop-exiting branch conditions at the start of IndVarSimplify, before
the pass rebuilds and uses SCEV information for the current IR.

Fixes #<!-- -->207744.

Tests:
- `llvm-lit -q llvm/test/Transforms/IndVarSimplify/pr207744.ll`
- `llvm-lit -q llvm/test/Transforms/IndVarSimplify`


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


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/IndVarSimplify.cpp (+34) 
- (added) llvm/test/Transforms/IndVarSimplify/pr207744.ll (+105) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
index c92efadded635..1e4a91d9af49c 100644
--- a/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
+++ b/llvm/lib/Transforms/Scalar/IndVarSimplify.cpp
@@ -152,6 +152,7 @@ class IndVarSimplify {
   bool canonicalizeExitCondition(Loop *L);
   /// Try to eliminate loop exits based on analyzeable exit counts
   bool optimizeLoopExits(Loop *L, SCEVExpander &Rewriter);
+  void forgetLoopExitConditionValues(Loop *L);
   /// Try to form loop invariant tests for loop exits by changing how many
   /// iterations of the loop run when that is unobservable.
   bool predicateLoopExits(Loop *L, SCEVExpander &Rewriter);
@@ -1654,6 +1655,34 @@ bool IndVarSimplify::canonicalizeExitCondition(Loop *L) {
   return Changed;
 }
 
+void IndVarSimplify::forgetLoopExitConditionValues(Loop *L) {
+  SE->forgetLoop(L);
+
+  SmallVector<BasicBlock *, 16> ExitingBlocks;
+  L->getExitingBlocks(ExitingBlocks);
+  for (BasicBlock *ExitingBB : ExitingBlocks) {
+    auto *BI = dyn_cast<CondBrInst>(ExitingBB->getTerminator());
+    if (!BI)
+      continue;
+
+    SmallVector<Value *, 8> Worklist({BI->getCondition()});
+    SmallPtrSet<Value *, 8> Visited;
+    while (!Worklist.empty()) {
+      Value *V = Worklist.pop_back_val();
+      if (!Visited.insert(V).second)
+        continue;
+
+      auto *I = dyn_cast<Instruction>(V);
+      if (!I || !L->contains(I))
+        continue;
+
+      SE->forgetValue(I);
+      for (Value *Op : I->operands())
+        Worklist.push_back(Op);
+    }
+  }
+}
+
 bool IndVarSimplify::optimizeLoopExits(Loop *L, SCEVExpander &Rewriter) {
   SmallVector<BasicBlock*, 16> ExitingBlocks;
   L->getExitingBlocks(ExitingBlocks);
@@ -2067,6 +2096,11 @@ bool IndVarSimplify::run(Loop *L) {
   if (!L->isLoopSimplifyForm())
     return false;
 
+  // The loop may have been changed by earlier loop passes in the same adaptor
+  // run. Make sure this pass does not reason about exit conditions using stale
+  // cached exit counts or value expressions.
+  forgetLoopExitConditionValues(L);
+
   bool Changed = false;
   // If there are any floating-point recurrences, attempt to
   // transform them to use integer recurrences.
diff --git a/llvm/test/Transforms/IndVarSimplify/pr207744.ll b/llvm/test/Transforms/IndVarSimplify/pr207744.ll
new file mode 100644
index 0000000000000..e1c60822a1dd8
--- /dev/null
+++ b/llvm/test/Transforms/IndVarSimplify/pr207744.ll
@@ -0,0 +1,105 @@
+; RUN: opt -passes='default<O3>' -S %s | FileCheck %s
+
+ at g0 = dso_local local_unnamed_addr global i64 0, align 8
+ at g15 = dso_local local_unnamed_addr global i64 0, align 8
+ at g14 = dso_local local_unnamed_addr global i64 0, align 8
+ at g5 = dso_local local_unnamed_addr global i64 0, align 8
+ at f4_c7 = dso_local local_unnamed_addr global i8 0, align 1
+ at g8 = dso_local local_unnamed_addr global i64 0, align 8
+ at f4_c15 = dso_local local_unnamed_addr global i8 0, align 1
+ at f4_c8 = dso_local local_unnamed_addr global i8 0, align 1
+ at __chk = dso_local local_unnamed_addr global i64 0, align 8
+ at .str = private unnamed_addr constant [20 x i8] c"checksum=0x%016llx\0A\00", align 1
+
+; CHECK-LABEL: define dso_local void @f4(
+; CHECK: store i64 5, ptr @__chk
+; CHECK-LABEL: define dso_local noundef i32 @main(
+define dso_local void @f4() local_unnamed_addr #0 {
+entry:
+  %0 = load i64, ptr @g0, align 8
+  %cmp.not = icmp eq i64 %0, 908375363948206739
+  br i1 %cmp.not, label %if.end, label %if.then
+
+if.then:
+  store i64 1, ptr @g15, align 8
+  br label %if.end
+
+if.end:
+  %1 = load i64, ptr @g15, align 8
+  store i64 %1, ptr @g14, align 8
+  store i64 %1, ptr @g0, align 8
+  br label %lbl_b5
+
+lbl_b5:
+  %bb13.0 = phi i32 [ 0, %if.end ], [ %bb13.1.lcssa, %if.then8 ]
+  %loadedv9.not = phi i1 [ true, %if.end ], [ false, %if.then8 ]
+  %ov6.0 = phi i8 [ 0, %if.end ], [ 1, %if.then8 ]
+  br label %lbl_b10
+
+lbl_b10:
+  %loadedv.not = phi i1 [ true, %lbl_b5 ], [ false, %lbl_b10 ]
+  %2 = phi i8 [ 0, %lbl_b5 ], [ 1, %lbl_b10 ]
+  %3 = phi i64 [ %1, %lbl_b5 ], [ 0, %lbl_b10 ]
+  %bb13.1 = phi i32 [ %bb13.0, %lbl_b5 ], [ %5, %lbl_b10 ]
+  %conv1 = trunc i64 %3 to i32
+  %4 = tail call range(i32 0, 33) i32 @llvm.ctpop.i32(i32 %conv1)
+  %5 = and i32 %4, 1
+  br i1 %loadedv.not, label %lbl_b10, label %if.then3
+
+if.then3:
+  %.lcssa20 = phi i8 [ %2, %lbl_b10 ]
+  %.lcssa18 = phi i64 [ %3, %lbl_b10 ]
+  %bb13.1.lcssa = phi i32 [ %bb13.1, %lbl_b10 ]
+  %.lcssa = phi i32 [ %5, %lbl_b10 ]
+  %tobool4.not = icmp eq i32 %bb13.1.lcssa, 0
+  br i1 %tobool4.not, label %return.loopexit, label %if.then8
+
+if.then8:
+  br i1 %loadedv9.not, label %lbl_b5, label %if.then10
+
+if.then10:
+  %ov6.0.lcssa22 = phi i8 [ %ov6.0, %if.then8 ]
+  %.lcssa20.lcssa21 = phi i8 [ %.lcssa20, %if.then8 ]
+  %.lcssa18.lcssa19 = phi i64 [ %.lcssa18, %if.then8 ]
+  %bb13.1.lcssa.lcssa17 = phi i32 [ %bb13.1.lcssa, %if.then8 ]
+  %.lcssa.lcssa16 = phi i32 [ %.lcssa, %if.then8 ]
+  %conv2.le.le = zext nneg i32 %.lcssa.lcssa16 to i64
+  %storedv5.le = trunc nuw i32 %bb13.1.lcssa.lcssa17 to i8
+  store i64 %.lcssa18.lcssa19, ptr @g5, align 8
+  store i8 %.lcssa20.lcssa21, ptr @f4_c7, align 1
+  store i64 %conv2.le.le, ptr @g8, align 8
+  store i8 %ov6.0.lcssa22, ptr @f4_c15, align 1
+  store i8 %storedv5.le, ptr @f4_c8, align 1
+  store i64 5, ptr @__chk, align 8
+  br label %return
+
+return.loopexit:
+  %ov6.0.lcssa = phi i8 [ %ov6.0, %if.then3 ]
+  %.lcssa20.lcssa = phi i8 [ %.lcssa20, %if.then3 ]
+  %.lcssa18.lcssa = phi i64 [ %.lcssa18, %if.then3 ]
+  %.lcssa.lcssa = phi i32 [ %.lcssa, %if.then3 ]
+  %conv2.le.le14 = zext nneg i32 %.lcssa.lcssa to i64
+  store i64 %.lcssa18.lcssa, ptr @g5, align 8
+  store i8 %.lcssa20.lcssa, ptr @f4_c7, align 1
+  store i64 %conv2.le.le14, ptr @g8, align 8
+  store i8 %ov6.0.lcssa, ptr @f4_c15, align 1
+  store i8 0, ptr @f4_c8, align 1
+  br label %return
+
+return:
+  ret void
+}
+
+declare i32 @llvm.ctpop.i32(i32)
+
+define dso_local noundef i32 @main() local_unnamed_addr #0 {
+entry:
+  call void @f4()
+  %0 = load i64, ptr @__chk, align 8
+  %call = call i32 (ptr, ...) @printf(ptr noundef nonnull dereferenceable(1) @.str, i64 noundef %0)
+  ret i32 0
+}
+
+declare noundef i32 @printf(ptr noundef readonly captures(none), ...)
+
+attributes #0 = { noinline nounwind uwtable }

``````````

</details>


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


More information about the llvm-commits mailing list