[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