[llvm] [SCEV] Re-fold cached adds after instruction hoisting (PR #218086)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 19:11:12 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-analysis

Author: SomeFlyingThing

<details>
<summary>Changes</summary>

Loop::makeLoopInvariant only invalidated block and loop dispositions. This left dependent value SCEVs cached, and getAddExpr could return an old AddExpr before retrying addrec folding after an operand became loop invariant.

Forget dependent value SCEVs when an instruction moves and bypass the early cached-add return when an operand can now be folded into an addrec.

Fixes #<!-- -->214665.

this pr was LLM assisted 

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


3 Files Affected:

- (modified) llvm/lib/Analysis/LoopInfo.cpp (+3-1) 
- (modified) llvm/lib/Analysis/ScalarEvolution.cpp (+17-1) 
- (added) llvm/test/Transforms/LoopDeletion/invalidate-scev-after-partial-hoisting.ll (+55) 


``````````diff
diff --git a/llvm/lib/Analysis/LoopInfo.cpp b/llvm/lib/Analysis/LoopInfo.cpp
index 613e9887f3c45..f898276c7730a 100644
--- a/llvm/lib/Analysis/LoopInfo.cpp
+++ b/llvm/lib/Analysis/LoopInfo.cpp
@@ -138,8 +138,10 @@ bool Loop::makeLoopInvariant(Instruction *I, bool &Changed,
   if (ProfileMetadataToPreserve.empty() && isa<SelectInst>(I))
     setExplicitlyUnknownBranchWeightsIfProfiled(*I, "LoopInfo");
 
+  // Moving I may make SCEVs of its users more precisely canonicalizable, for
+  // example by allowing an invariant operand to be folded into an addrec.
   if (SE)
-    SE->forgetBlockAndLoopDispositions(I);
+    SE->forgetValue(I);
 
   Changed = true;
   return true;
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 6095f59ec1413..439cbe4a02c79 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -2558,6 +2558,19 @@ bool ScalarEvolution::isAvailableAtLoopEntry(const SCEV *S, const Loop *L) {
   return isLoopInvariant(S, L) && properlyDominates(S, L->getHeader());
 }
 
+static bool hasAddRecWithLoopInvariantOperand(ScalarEvolution &SE,
+                                              ArrayRef<SCEVUse> Ops) {
+  return any_of(Ops, [&](const SCEV *Op) {
+    const auto *AddRec = dyn_cast<SCEVAddRecExpr>(Op);
+    if (!AddRec)
+      return false;
+    return any_of(Ops, [&](const SCEV *Other) {
+      return Other != AddRec &&
+             SE.isAvailableAtLoopEntry(Other, AddRec->getLoop());
+    });
+  });
+}
+
 /// Get a canonical add expression, or something simpler if possible.
 const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
                                         SCEV::NoWrapFlags OrigFlags,
@@ -2595,7 +2608,10 @@ const SCEV *ScalarEvolution::getAddExpr(SmallVectorImpl<SCEVUse> &Ops,
   if (Depth > MaxArithDepth || hasHugeExpression(Ops))
     return getOrCreateAddExpr(Ops, ComputeFlags(Ops));
 
-  if (SCEV *S = findExistingSCEVInCache(scAddExpr, Ops)) {
+  // A loop transform may have made an operand newly available at the addrec's
+  // entry. Retry folding rather than returning the less precise cached add.
+  if (SCEV *S = findExistingSCEVInCache(scAddExpr, Ops);
+      S && !hasAddRecWithLoopInvariantOperand(*this, Ops)) {
     // Don't strengthen flags if we have no new information.
     SCEVAddExpr *Add = static_cast<SCEVAddExpr *>(S);
     if (Add->getNoWrapFlags(OrigFlags) != OrigFlags)
diff --git a/llvm/test/Transforms/LoopDeletion/invalidate-scev-after-partial-hoisting.ll b/llvm/test/Transforms/LoopDeletion/invalidate-scev-after-partial-hoisting.ll
new file mode 100644
index 0000000000000..03c5538468bd5
--- /dev/null
+++ b/llvm/test/Transforms/LoopDeletion/invalidate-scev-after-partial-hoisting.ll
@@ -0,0 +1,55 @@
+; RUN: opt -passes='function(loop(indvars)),function(loop(loop-deletion)),function(loop(loop-reduce))' \
+; RUN:   -S %s | FileCheck %s
+
+; LoopDeletion partially hoists the computation of %conv7 while checking
+; whether the inner loop is dead. Recompute SCEVs that use the newly invariant
+; value so LSR can recognize %sext as an add recurrence.
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+declare void @sideeffect(i32)
+
+define void @test(i32 %val, i1 %c1) {
+; CHECK-LABEL: define void @test(
+; CHECK:       for.cond:
+; CHECK:         [[START0:%.*]] = shl nuw nsw i32 %conv7, 24
+; CHECK-NEXT:    [[START:%.*]] = add nuw nsw i32 [[START0]], 16777216
+; CHECK:       for.body6:
+; CHECK-NEXT:    [[SHIFT_IV:%.*]] = phi i32 [ [[SHIFT_IV_NEXT:%.*]], %for.body6 ], [ [[START]], %for.cond ]
+; CHECK:         %conv8 = ashr exact i32 [[SHIFT_IV]], 24
+; CHECK:         [[SHIFT_IV_NEXT]] = add i32 [[SHIFT_IV]], 16777216
+; CHECK-NOT:     %h.039 =
+; CHECK-NOT:     %inc =
+; CHECK:         ret void
+;
+entry:
+  br label %for.cond
+
+for.cond:
+  %f.0 = phi i32 [ 20, %entry ], [ 0, %for.inc11 ]
+  br label %for.body6
+
+for.body6:
+  %h.039 = phi i32 [ 1, %for.cond ], [ %inc, %for.body6 ]
+  %g.138 = phi i32 [ 0, %for.cond ], [ %and, %for.body6 ]
+  %cmp = icmp eq i32 %val, -1
+  %conv7 = zext i1 %cmp to i32
+  %add.i = add nsw i32 %conv7, %h.039
+  %sext = shl i32 %add.i, 24
+  %conv8 = ashr exact i32 %sext, 24
+  %cmp9 = icmp eq i32 %conv8, %f.0
+  %conv10 = zext i1 %cmp9 to i32
+  %and = add i32 %conv10, %g.138
+  %inc = add i32 %h.039, 1
+  %exitcond = icmp eq i32 %inc, 20000
+  br i1 %exitcond, label %for.inc11, label %for.body6
+
+for.inc11:
+  %and.lcssa = phi i32 [ %and, %for.body6 ]
+  call void @sideeffect(i32 %and.lcssa)
+  br i1 %c1, label %for.cond, label %done
+
+done:
+  ret void
+}

``````````

</details>


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


More information about the llvm-commits mailing list