[llvm] [LoopInterchange] Reject inner preheader PHIs with non-identical inco… (PR #203842)
Madhur Amilkanthwar via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 15 01:29:26 PDT 2026
https://github.com/madhur13490 created https://github.com/llvm/llvm-project/pull/203842
…ming values
When the outer header branches to the inner preheader via duplicate edges, the preheader can contain PHIs with more than one incoming entry. The transform removes such PHIs by substituting the first incoming value for all uses, which is only correct when every incoming value is the same. Bail out of interchange when a preheader PHI has distinct incoming values.
Also relax the assert in the same code path to match: accept equivalent multi-entry PHIs instead of requiring exactly one entry.
Fixes #203466
>From 66eb4a420b93fa69dcba8abc4e4ad74ddf2d7b82 Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 15 Jun 2026 01:12:44 -0700
Subject: [PATCH] [LoopInterchange] Reject inner preheader PHIs with
non-identical incoming values
When the outer header branches to the inner preheader via duplicate edges,
the preheader can contain PHIs with more than one incoming entry. The
transform removes such PHIs by substituting the first incoming value for
all uses, which is only correct when every incoming value is the same.
Bail out of interchange when a preheader PHI has distinct incoming values.
Also relax the assert in the same code path to match: accept equivalent
multi-entry PHIs instead of requiring exactly one entry.
Fixes #203466
---
.../lib/Transforms/Scalar/LoopInterchange.cpp | 23 +++++-
.../inner-preheader-multi-entry-phi.ll | 72 +++++++++++++++++++
2 files changed, 93 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Transforms/LoopInterchange/inner-preheader-multi-entry-phi.ll
diff --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index 3da4e99860e45..1478ee4d9cffc 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -1531,6 +1531,25 @@ bool LoopInterchangeLegality::canInterchangeLoops(unsigned InnerLoopId,
return false;
}
+ BasicBlock *InnerLoopPreHeader = InnerLoop->getLoopPreheader();
+ // Inner-preheader PHIs are removed by substituting each with its first
+ // incoming value. That is only correct when all incoming values are the
+ // same; a PHI with different incoming values would produce the wrong result.
+ if (InnerLoopPreHeader != OuterLoop->getHeader() &&
+ any_of(InnerLoopPreHeader->phis(),
+ [](PHINode &PHI) { return !all_equal(PHI.incoming_values()); })) {
+ LLVM_DEBUG(dbgs() << "Found unsupported PHI nodes in inner loop "
+ "preheader.\n");
+ ORE->emit([&]() {
+ return OptimizationRemarkMissed(DEBUG_TYPE, "UnsupportedPreheaderPHI",
+ InnerLoop->getStartLoc(),
+ InnerLoop->getHeader())
+ << "Cannot interchange loops because unsupported PHI nodes found "
+ "in inner loop preheader.";
+ });
+ return false;
+ }
+
if (!areInnerLoopLatchPHIsSupported(OuterLoop, InnerLoop)) {
LLVM_DEBUG(dbgs() << "Found unsupported PHI nodes in inner loop latch.\n");
ORE->emit([&]() {
@@ -2159,8 +2178,8 @@ bool LoopInterchangeTransform::transform(
if (InnerLoopPreHeader != OuterLoopHeader) {
// Eliminate PHIs in the inner-loop preheader.
for (PHINode &P : make_early_inc_range(InnerLoopPreHeader->phis())) {
- assert(P.getNumIncomingValues() == 1 &&
- "Expected single-incoming PHIs in inner loop preheader");
+ assert(all_equal(P.incoming_values()) &&
+ "Expected equivalent incoming values in inner loop preheader");
P.replaceAllUsesWith(P.getIncomingValue(0));
P.eraseFromParent();
}
diff --git a/llvm/test/Transforms/LoopInterchange/inner-preheader-multi-entry-phi.ll b/llvm/test/Transforms/LoopInterchange/inner-preheader-multi-entry-phi.ll
new file mode 100644
index 0000000000000..7d88d48bdee3a
--- /dev/null
+++ b/llvm/test/Transforms/LoopInterchange/inner-preheader-multi-entry-phi.ll
@@ -0,0 +1,72 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=loop-interchange -loop-interchange-profitabilities=ignore \
+; RUN: -verify-dom-info -verify-loop-info -verify-loop-lcssa -S %s \
+; RUN: | FileCheck %s
+
+; The outer header branches to the inner preheader via duplicate edges, giving
+; the preheader a PHI with two identical incoming entries. Interchange must
+; succeed: both entries carry the same value so the PHI can be safely removed.
+
+define void @multi_entry_phi(ptr noalias %A, i1 %c) {
+; CHECK-LABEL: define void @multi_entry_phi(
+; CHECK-SAME: ptr noalias [[A:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: br label %[[INNER_PH:.*]]
+; CHECK: [[OUTER_HEADER_PREHEADER:.*]]:
+; CHECK-NEXT: br label %[[OUTER_HEADER:.*]]
+; CHECK: [[OUTER_HEADER]]:
+; CHECK-NEXT: [[I:%.*]] = phi i64 [ [[I_INC:%.*]], %[[OUTER_LATCH:.*]] ], [ 0, %[[OUTER_HEADER_PREHEADER]] ]
+; CHECK-NEXT: br i1 [[C]], label %[[INNER_SPLIT1:.*]], label %[[INNER_SPLIT1]]
+; CHECK: [[INNER_PH]]:
+; CHECK-NEXT: br label %[[INNER:.*]]
+; CHECK: [[INNER]]:
+; CHECK-NEXT: [[J:%.*]] = phi i64 [ 0, %[[INNER_PH]] ], [ [[TMP0:%.*]], %[[INNER_SPLIT:.*]] ]
+; CHECK-NEXT: br label %[[OUTER_HEADER_PREHEADER]]
+; CHECK: [[INNER_SPLIT1]]:
+; CHECK-NEXT: [[IDX:%.*]] = mul i64 [[J]], 10
+; CHECK-NEXT: [[OFF:%.*]] = add i64 [[IDX]], [[I]]
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr inbounds i32, ptr [[A]], i64 [[OFF]]
+; CHECK-NEXT: store i32 0, ptr [[GEP]], align 4
+; CHECK-NEXT: [[J_INC:%.*]] = add i64 [[J]], 1
+; CHECK-NEXT: [[EC_J:%.*]] = icmp eq i64 [[J_INC]], 10
+; CHECK-NEXT: br label %[[OUTER_LATCH]]
+; CHECK: [[INNER_SPLIT]]:
+; CHECK-NEXT: [[TMP0]] = add i64 [[J]], 1
+; CHECK-NEXT: [[TMP1:%.*]] = icmp eq i64 [[TMP0]], 10
+; CHECK-NEXT: br i1 [[TMP1]], label %[[EXIT:.*]], label %[[INNER]]
+; CHECK: [[OUTER_LATCH]]:
+; CHECK-NEXT: [[I_INC]] = add i64 [[I]], 1
+; CHECK-NEXT: [[EC_I:%.*]] = icmp eq i64 [[I_INC]], 10
+; CHECK-NEXT: br i1 [[EC_I]], label %[[INNER_SPLIT]], label %[[OUTER_HEADER]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %outer.header
+
+outer.header:
+ %i = phi i64 [ 0, %entry ], [ %i.inc, %outer.latch ]
+ br i1 %c, label %inner.ph, label %inner.ph
+
+inner.ph:
+ %p = phi i64 [ 42, %outer.header ], [ 42, %outer.header ]
+ br label %inner
+
+inner:
+ %j = phi i64 [ 0, %inner.ph ], [ %j.inc, %inner ]
+ %idx = mul i64 %j, 10
+ %off = add i64 %idx, %i
+ %gep = getelementptr inbounds i32, ptr %A, i64 %off
+ store i32 0, ptr %gep, align 4
+ %j.inc = add i64 %j, 1
+ %ec.j = icmp eq i64 %j.inc, 10
+ br i1 %ec.j, label %outer.latch, label %inner
+
+outer.latch:
+ %i.inc = add i64 %i, 1
+ %ec.i = icmp eq i64 %i.inc, 10
+ br i1 %ec.i, label %exit, label %outer.header
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list