[llvm] [LLE][LoopVersioning] Form LCSSA in Loop Versioning Pass before extending exit PHIs (PR #202296)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 8 02:06:02 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Amit Tiwari (loopacino)

<details>
<summary>Changes</summary>

LoopVersioning utility `versionLoop()` expects the input form to be LCSSA. However, passes like Loop distribution, Loop-load-elimination (in this case, LLE) etc., may invoke versioning with a non-LCSSA IR. Versioning updates exit-block PHIs to handle both the original loop and the cloned loop. However, a raw non-LCSSA use in the exit block (such as `switch i1 %C`) still points to the original loop’s `%C`. On the cloned-loop path, control reaches the exit block without executing the original `%C`, resulting in error: `Instruction does not dominate all uses!`.

The fix is to form LCSSA at the start of `LoopVersioning::versionLoop()`.

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


2 Files Affected:

- (modified) llvm/lib/Transforms/Utils/LoopVersioning.cpp (+4-3) 
- (added) llvm/test/Transforms/LoopLoadElim/non-lcssa-input.ll (+40) 


``````````diff
diff --git a/llvm/lib/Transforms/Utils/LoopVersioning.cpp b/llvm/lib/Transforms/Utils/LoopVersioning.cpp
index b90466a8c49cf..61ef945bb8cbd 100644
--- a/llvm/lib/Transforms/Utils/LoopVersioning.cpp
+++ b/llvm/lib/Transforms/Utils/LoopVersioning.cpp
@@ -53,6 +53,10 @@ void LoopVersioning::versionLoop(
   assert(VersionedLoop->isLoopSimplifyForm() &&
          "Loop is not in loop-simplify form");
 
+  // Form LCSSA on the input loop before doing anything else.
+  if (!VersionedLoop->isLCSSAForm(*DT))
+    formLCSSARecursively(*VersionedLoop, *DT, LI, SE);
+
   Value *MemRuntimeCheck;
   Value *SCEVRuntimeCheck;
   Value *RuntimeCheck = nullptr;
@@ -292,9 +296,6 @@ bool runImpl(LoopInfo *LI, LoopAccessInfoManager &LAIs, DominatorTree *DT,
     if (!LAI.hasConvergentOp() &&
         (LAI.getNumRuntimePointerChecks() ||
          !LAI.getPSE().getPredicate().isAlwaysTrue())) {
-      if (!L->isLCSSAForm(*DT))
-       formLCSSARecursively(*L, *DT, LI, SE);
-
       LoopVersioning LVer(LAI, LAI.getRuntimePointerChecking()->getChecks(), L,
                           LI, DT, SE);
       LVer.versionLoop();
diff --git a/llvm/test/Transforms/LoopLoadElim/non-lcssa-input.ll b/llvm/test/Transforms/LoopLoadElim/non-lcssa-input.ll
new file mode 100644
index 0000000000000..1e31c17c7f416
--- /dev/null
+++ b/llvm/test/Transforms/LoopLoadElim/non-lcssa-input.ll
@@ -0,0 +1,40 @@
+; RUN: opt -passes=loop-load-elim -S < %s | FileCheck %s
+
+; LoopLoadElimination could call LoopVersioning on non-LCSSA loops.
+; A direct exit-block use of %C then remained tied to the original loop,
+; which stopped dominating the shared exit after cloning. Forming LCSSA in
+; versionLoop() rewrites the branch through an exit PHI; Usage of raw %C and %C.exit is needed
+; to trigger the old buggy path.
+
+define void @non_lcssa_exit_use(ptr nocapture %a, i64 %n) {
+; CHECK-LABEL: @non_lcssa_exit_use(
+; CHECK:       for.body.lver.check:
+; CHECK:       for.end:
+; CHECK:         %C.lcssa = phi i1
+; CHECK:         br i1 %C.lcssa,
+; CHECK-NOT:     br i1 %C,
+entry:
+  %G = getelementptr i32, ptr %a, i64 -1
+  br label %for.body
+
+for.body:
+  %iv = phi i64 [ %iv.next, %for.body ], [ 0, %entry ]
+  %arrayidx = getelementptr inbounds i32, ptr %G, i64 %iv
+  %load = load i32, ptr %arrayidx, align 4
+  %mul = mul i32 %load, 3
+  %arrayidx2 = getelementptr inbounds i32, ptr %a, i64 %iv
+  store i32 %mul, ptr %arrayidx2, align 4
+  %iv.next = add i64 %iv, 1
+  %C = icmp sgt i64 %iv.next, %n
+  br i1 %C, label %for.end, label %for.body
+
+for.end:
+  %C.exit = phi i1 [ %C, %for.body ]
+  br i1 %C, label %exit.a, label %exit.b
+
+exit.a:
+  ret void
+
+exit.b:
+  ret void
+}

``````````

</details>


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


More information about the llvm-commits mailing list