[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