[llvm] [InstCombine]Fold Phi Zext's to a singular Zext (PR #214045)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 4 12:51:10 PDT 2026
https://github.com/Logans-olo updated https://github.com/llvm/llvm-project/pull/214045
>From e52e50c69b7b303d8bb446bc79244d425f4c6108 Mon Sep 17 00:00:00 2001
From: Logan Solonche <lsolonch at purdue.edu>
Date: Tue, 4 Aug 2026 15:04:51 -0400
Subject: [PATCH 1/2] [InstCombine] Pre-commit test for switch+phi to zext fold
(NFC)
---
.../InstCombine/switch-phi-to-zext.ll | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
diff --git a/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll b/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
new file mode 100644
index 0000000000000..c221f0f4ce04d
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
@@ -0,0 +1,38 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+; TODO: This Switch + phi statement is equivalent to "zext i16 %x to i64"
+; It should fold down, but right now it doesn't
+define i64 @switch_phi_to_zext(i16 %x) {
+; CHECK-LABEL: define i64 @switch_phi_to_zext(
+; CHECK-SAME: i16 [[X:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: switch i16 [[X]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT: i16 0, label %[[MERGE:.*]]
+; CHECK-NEXT: i16 -1, label %[[MAX:.*]]
+; CHECK-NEXT: ]
+; CHECK: [[DEFAULT]]:
+; CHECK-NEXT: [[EXT:%.*]] = zext i16 [[X]] to i64
+; CHECK-NEXT: br label %[[MERGE]]
+; CHECK: [[MAX]]:
+; CHECK-NEXT: br label %[[MERGE]]
+; CHECK: [[MERGE]]:
+; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ 65535, %[[MAX]] ], [ [[EXT]], %[[DEFAULT]] ]
+; CHECK-NEXT: ret i64 [[RESULT]]
+;
+entry:
+ switch i16 %x, label %default [
+ i16 0, label %merge
+ i16 -1, label %max
+ ]
+
+default:
+ %ext = zext i16 %x to i64
+ br label %merge
+
+max:
+ br label %merge
+
+merge:
+ %result = phi i64 [ 0, %entry ], [ 65535, %max ], [ %ext, %default ]
+ ret i64 %result
+}
>From 88d7489db68d482f6fac4b5833524ee57a14e7f4 Mon Sep 17 00:00:00 2001
From: Logan Solonche <lsolonch at purdue.edu>
Date: Tue, 4 Aug 2026 15:28:20 -0400
Subject: [PATCH 2/2] [InstCombine] Fold switch feeding a phi of zext(case)
into a single zext
---
.../InstCombine/InstCombineInternal.h | 2 +-
.../Transforms/InstCombine/InstCombinePHI.cpp | 73 +++++++++++++++++++
.../InstCombine/switch-phi-to-zext.ll | 27 +++----
3 files changed, 86 insertions(+), 16 deletions(-)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..c4bde0803172f 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -699,7 +699,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
Instruction *foldPHIArgLoadIntoPHI(PHINode &PN);
Instruction *foldPHIArgZextsIntoPHI(PHINode &PN);
Instruction *foldPHIArgIntToPtrToPHI(PHINode &PN);
-
+ Instruction *foldPHIArgZextIntoZext(PHINode &PN);
/// If the phi is within a phi web, which is formed by the def-use chain
/// of phis and all the phis in the web are only used in the other phis.
/// In this case, these phis are dead and we will remove all of them.
diff --git a/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp b/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp
index ce3175c4936de..9149a79ec1abb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp
@@ -858,7 +858,77 @@ Instruction *InstCombinerImpl::foldPHIArgZextsIntoPHI(PHINode &Phi) {
CI->setDebugLoc(DebugLoc::getDropped());
return CI;
}
+Instruction *InstCombinerImpl::foldPHIArgZextIntoZext(PHINode &PN) {
+ // Cannot create new instruction after an EHPad
+ ZExtInst *Template = nullptr;
+ if (Instruction *TI = PN.getParent()->getTerminator())
+ if (TI->isEHPad())
+ return nullptr;
+
+ // Now there is guaranteed to be an insertion point
+ for (Value *V : PN.incoming_values()) {
+ if (auto *Z = dyn_cast<ZExtInst>(V)) {
+ Template = Z;
+ break;
+ }
+ }
+
+ if (!Template)
+ return nullptr;
+ Value *cond = Template->getOperand(0);
+
+ for (unsigned int i = 0; i < PN.getNumIncomingValues(); i++) {
+ SwitchInst *SI = nullptr;
+ BasicBlock *EdgeTarget = nullptr;
+ /* Place them here for soundness sake */
+
+ Value *V = PN.getIncomingValue(i);
+ if (V == Template) {
+ continue;
+ } // Just zext(cond)
+
+ BasicBlock *BB = PN.getIncomingBlock(i);
+
+ if (auto *S = dyn_cast<SwitchInst>(BB->getTerminator())) {
+ SI = S;
+ EdgeTarget = PN.getParent();
+ } // Base case: switch goes straight to Phi Block
+ else if (BasicBlock *Pred = BB->getSinglePredecessor()) {
+ SI = dyn_cast<SwitchInst>(Pred->getTerminator());
+ EdgeTarget = BB;
+ } // Switch can arrive at BB via a different block
+ //
+ if (!SI || SI->getCondition() != cond)
+ return nullptr;
+ if (SI->getDefaultDest() == EdgeTarget)
+ return nullptr; // x is unconstrained on the default path
+ //
+ // EdgeTarget is a non-default successor of the switch, so at least one case
+ // targets it
+ auto *VC = dyn_cast<ConstantInt>(V);
+ if (!VC)
+ return nullptr;
+
+ // Now for actual folding checks
+ for (auto &Case : SI->cases()) {
+ ConstantInt *CV = Case.getCaseValue(); // x's value on this case (i16)
+ if (Case.getCaseSuccessor() != EdgeTarget)
+ continue;
+ // does zext(CV) == V
+ if (CV->getValue().zext(VC->getBitWidth()) != VC->getValue())
+ return nullptr;
+ }
+ } /* Find the switch terminator and confirm its conditioned on cond, same as
+ zext */
+ if (!DT.dominates(cond, &PN)) // Moved here to solve regressions
+ return nullptr;
+ Builder.SetInsertPoint(PN.getParent()->getFirstInsertionPt());
+ // We can now set the insert point because we know non-null
+
+ Value *newZext = Builder.CreateZExt(cond, PN.getType());
+ return replaceInstUsesWith(PN, newZext);
+}
/// If all operands to a PHI node are the same "unary" operator and they all are
/// only used by the PHI, PHI together their inputs, and do the operation once,
/// to the result of the PHI.
@@ -1408,6 +1478,9 @@ Instruction *InstCombinerImpl::visitPHINode(PHINode &PN) {
if (Instruction *Result = foldPHIArgZextsIntoPHI(PN))
return Result;
+ if (Instruction *Result = foldPHIArgZextIntoZext(PN))
+ return Result;
+
if (Instruction *Result = foldPHIArgIntToPtrToPHI(PN))
return Result;
diff --git a/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll b/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
index c221f0f4ce04d..e9c2d07afaf3b 100644
--- a/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
+++ b/llvm/test/Transforms/InstCombine/switch-phi-to-zext.ll
@@ -1,23 +1,20 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt < %s -passes=instcombine -S | FileCheck %s
-; TODO: This Switch + phi statement is equivalent to "zext i16 %x to i64"
-; It should fold down, but right now it doesn't
+; This Switch + phi statement is equivalent to "zext i16 %x to i64"
define i64 @switch_phi_to_zext(i16 %x) {
-; CHECK-LABEL: define i64 @switch_phi_to_zext(
-; CHECK-SAME: i16 [[X:%.*]]) {
-; CHECK-NEXT: [[ENTRY:.*]]:
-; CHECK-NEXT: switch i16 [[X]], label %[[DEFAULT:.*]] [
-; CHECK-NEXT: i16 0, label %[[MERGE:.*]]
-; CHECK-NEXT: i16 -1, label %[[MAX:.*]]
+; CHECK-LABEL: @switch_phi_to_zext(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: switch i16 [[X:%.*]], label [[DEFAULT:%.*]] [
+; CHECK-NEXT: i16 0, label [[MERGE:%.*]]
+; CHECK-NEXT: i16 -1, label [[MAX:%.*]]
; CHECK-NEXT: ]
-; CHECK: [[DEFAULT]]:
+; CHECK: default:
+; CHECK-NEXT: br label [[MERGE]]
+; CHECK: max:
+; CHECK-NEXT: br label [[MERGE]]
+; CHECK: merge:
; CHECK-NEXT: [[EXT:%.*]] = zext i16 [[X]] to i64
-; CHECK-NEXT: br label %[[MERGE]]
-; CHECK: [[MAX]]:
-; CHECK-NEXT: br label %[[MERGE]]
-; CHECK: [[MERGE]]:
-; CHECK-NEXT: [[RESULT:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ 65535, %[[MAX]] ], [ [[EXT]], %[[DEFAULT]] ]
-; CHECK-NEXT: ret i64 [[RESULT]]
+; CHECK-NEXT: ret i64 [[EXT]]
;
entry:
switch i16 %x, label %default [
More information about the llvm-commits
mailing list