[llvm] [SimplifyCFG] Hoist common code when succ is unreachable block (PR #165570)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Wed Oct 29 08:33:55 PDT 2025


https://github.com/Camsyn updated https://github.com/llvm/llvm-project/pull/165570

>From 29e07b8ef547879b4de855984d92cb4a5f6cf634 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 29 Oct 2025 21:43:11 +0800
Subject: [PATCH 1/4] Add a test before commit

---
 .../SimplifyCFG/hoist-common-code.ll          | 133 ++++++++++++++++++
 1 file changed, 133 insertions(+)

diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
index 8ce94d1cf5b4e..114e143502a00 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
@@ -185,8 +185,13 @@ else:
 
 define void @test_icmp_complex(i1 %c, i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_icmp_complex(
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
+; CHECK:       if:
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp ult i32 [[A:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    br i1 [[CMP1]], label [[IF2:%.*]], label [[ELSE2:%.*]]
+; CHECK:       else:
+; CHECK-NEXT:    [[CMP2:%.*]] = icmp ugt i32 [[B]], [[A]]
+; CHECK-NEXT:    br i1 [[CMP2]], label [[IF2]], label [[ELSE2]]
 ; CHECK:       common.ret:
 ; CHECK-NEXT:    ret void
 ; CHECK:       if2:
@@ -486,3 +491,131 @@ else:
   call void @bar()
   ret float %op2
 }
+
+define void @test_switch_with_unreachable_block_as_default(i1 %c, i32 %x, ptr %ptr) {
+; CHECK-LABEL: @test_switch_with_unreachable_block_as_default(
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[SW1:%.*]], label [[SW2:%.*]]
+; CHECK:       sw1:
+; CHECK-NEXT:    switch i32 [[X:%.*]], label [[UNREACHABLE:%.*]] [
+; CHECK-NEXT:      i32 1, label [[COMMON_RET:%.*]]
+; CHECK-NEXT:      i32 2, label [[BAR:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       sw2:
+; CHECK-NEXT:    switch i32 [[X]], label [[UNREACHABLE]] [
+; CHECK-NEXT:      i32 1, label [[BB1:%.*]]
+; CHECK-NEXT:      i32 2, label [[BB2:%.*]]
+; CHECK-NEXT:      i32 3, label [[BB3:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       bb1:
+; CHECK-NEXT:    store i64 42, ptr [[PTR:%.*]], align 4
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       bb2:
+; CHECK-NEXT:    store i64 42, ptr [[PTR]], align 4
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       bb3:
+; CHECK-NEXT:    store i64 42, ptr [[PTR]], align 4
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       unreachable:
+; CHECK-NEXT:    unreachable
+; CHECK:       bar:
+; CHECK-NEXT:    call void @bar()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  br i1 %c, label %sw1, label %sw2
+
+sw1:
+  ; This switch only exists to have an %unreachable block with multiple predecessors.
+  switch i32 %x, label %unreachable [
+  i32 1, label %foo
+  i32 2, label %bar
+  ]
+
+sw2:
+  switch i32 %x, label %unreachable [
+  i32 1, label %bb1
+  i32 2, label %bb2
+  i32 3, label %bb3
+  ]
+
+bb1:
+  store i64 42, ptr %ptr
+  ret void
+
+bb2:
+  store i64 42, ptr %ptr
+  ret void
+
+bb3:
+  store i64 42, ptr %ptr
+  ret void
+
+unreachable:
+  unreachable
+
+foo:
+  ret void
+
+bar:
+  call void @bar()
+  ret void
+}
+
+define void @test_switch_with_unreachable_block_as_case(i1 %c, i32 %x, ptr %ptr) {
+; CHECK-LABEL: @test_switch_with_unreachable_block_as_case(
+; CHECK-NEXT:    br i1 [[C:%.*]], label [[SW1:%.*]], label [[SW2:%.*]]
+; CHECK:       sw1:
+; CHECK-NEXT:    switch i32 [[X:%.*]], label [[UNREACHABLE:%.*]] [
+; CHECK-NEXT:      i32 1, label [[COMMON_RET:%.*]]
+; CHECK-NEXT:      i32 2, label [[BAR:%.*]]
+; CHECK-NEXT:    ]
+; CHECK:       sw2:
+; CHECK-NEXT:    store i64 42, ptr [[PTR:%.*]], align 4
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       unreachable:
+; CHECK-NEXT:    unreachable
+; CHECK:       bar:
+; CHECK-NEXT:    call void @bar()
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  br i1 %c, label %sw1, label %sw2
+
+sw1:
+  ; This switch only exists to have an %unreachable block with multiple predecessors.
+  switch i32 %x, label %unreachable [
+  i32 1, label %foo
+  i32 2, label %bar
+  ]
+
+sw2:
+  switch i32 %x, label %bb3 [
+  i32 1, label %bb1
+  i32 2, label %bb2
+  i32 3, label %unreachable
+  ]
+
+bb1:
+  store i64 42, ptr %ptr
+  ret void
+
+bb2:
+  store i64 42, ptr %ptr
+  ret void
+
+bb3:
+  store i64 42, ptr %ptr
+  ret void
+
+unreachable:
+  unreachable
+
+foo:
+  ret void
+
+bar:
+  call void @bar()
+  ret void
+}

>From cc6ca090a26e841246b89777b6f0bf4f84428ab0 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 29 Oct 2025 21:47:05 +0800
Subject: [PATCH 2/4] [SimplifyCFG] Hoist common code when succ is unreachable
 block

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index d831c2737e5f8..7244ffbe344e6 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1866,10 +1866,19 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
   // If either of the blocks has it's address taken, then we can't do this fold,
   // because the code we'd hoist would no longer run when we jump into the block
   // by it's address.
-  for (auto *Succ : successors(BB))
-    if (Succ->hasAddressTaken() || !Succ->getSinglePredecessor())
+  for (auto *Succ : successors(BB)) {
+    if (Succ->hasAddressTaken())
       return false;
-
+    if (Succ->getSinglePredecessor())
+      continue;
+    // If Succ has >1 predecessors, continue to check if the Succ is terminated
+    // by an `unreachable` inst. Since executing `unreachable` inst is an UB, we
+    // can relax the condition based on the assumptiom that the program would
+    // never enter Succ and trigger an UB.
+    if (isa<UnreachableInst>(Succ->getTerminator()))
+      continue;
+    return false;
+  }
   // The second of pair is a SkipFlags bitmask.
   using SuccIterPair = std::pair<BasicBlock::iterator, unsigned>;
   SmallVector<SuccIterPair, 8> SuccIterPairs;

>From 779580aed02a17d7e8b979e54cfa2fe17b780df1 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 29 Oct 2025 21:47:52 +0800
Subject: [PATCH 3/4] Regenerate the test after commit

---
 .../SimplifyCFG/hoist-common-code.ll          | 21 ++-----------------
 1 file changed, 2 insertions(+), 19 deletions(-)

diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
index 114e143502a00..98c0599ab209c 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
@@ -185,13 +185,8 @@ else:
 
 define void @test_icmp_complex(i1 %c, i32 %a, i32 %b) {
 ; CHECK-LABEL: @test_icmp_complex(
-; CHECK-NEXT:    br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
-; CHECK:       if:
 ; CHECK-NEXT:    [[CMP1:%.*]] = icmp ult i32 [[A:%.*]], [[B:%.*]]
 ; CHECK-NEXT:    br i1 [[CMP1]], label [[IF2:%.*]], label [[ELSE2:%.*]]
-; CHECK:       else:
-; CHECK-NEXT:    [[CMP2:%.*]] = icmp ugt i32 [[B]], [[A]]
-; CHECK-NEXT:    br i1 [[CMP2]], label [[IF2]], label [[ELSE2]]
 ; CHECK:       common.ret:
 ; CHECK-NEXT:    ret void
 ; CHECK:       if2:
@@ -501,22 +496,10 @@ define void @test_switch_with_unreachable_block_as_default(i1 %c, i32 %x, ptr %p
 ; CHECK-NEXT:      i32 2, label [[BAR:%.*]]
 ; CHECK-NEXT:    ]
 ; CHECK:       sw2:
-; CHECK-NEXT:    switch i32 [[X]], label [[UNREACHABLE]] [
-; CHECK-NEXT:      i32 1, label [[BB1:%.*]]
-; CHECK-NEXT:      i32 2, label [[BB2:%.*]]
-; CHECK-NEXT:      i32 3, label [[BB3:%.*]]
-; CHECK-NEXT:    ]
-; CHECK:       common.ret:
-; CHECK-NEXT:    ret void
-; CHECK:       bb1:
 ; CHECK-NEXT:    store i64 42, ptr [[PTR:%.*]], align 4
 ; CHECK-NEXT:    br label [[COMMON_RET]]
-; CHECK:       bb2:
-; CHECK-NEXT:    store i64 42, ptr [[PTR]], align 4
-; CHECK-NEXT:    br label [[COMMON_RET]]
-; CHECK:       bb3:
-; CHECK-NEXT:    store i64 42, ptr [[PTR]], align 4
-; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
 ; CHECK:       unreachable:
 ; CHECK-NEXT:    unreachable
 ; CHECK:       bar:

>From 4d51e3a45203c16c935b172d96c8f2a42df3b531 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 29 Oct 2025 23:33:23 +0800
Subject: [PATCH 4/4] Check the first inst rather than the last one

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 7244ffbe344e6..9680e4fdbec8d 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1875,7 +1875,7 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
     // by an `unreachable` inst. Since executing `unreachable` inst is an UB, we
     // can relax the condition based on the assumptiom that the program would
     // never enter Succ and trigger an UB.
-    if (isa<UnreachableInst>(Succ->getTerminator()))
+    if (isa<UnreachableInst>(Succ->getFirstNonPHIOrDbgOrLifetime()))
       continue;
     return false;
   }



More information about the llvm-commits mailing list