[llvm] Enhance simplifycfg hoist common inst (PR #165700)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 30 04:41:54 PDT 2025


https://github.com/Camsyn created https://github.com/llvm/llvm-project/pull/165700

Previously, hoistCommonCodeFromSuccessors did not support hoisting common code for multi-case destinations in `switch`.

However, if all the predecessors of a given Succ are the same (i.e., multi-case destination), it is safe to hoist the common code from Succ to Pred, which is what this PR does.

See discussion https://github.com/llvm/llvm-project/pull/165570#discussion_r2473290327.
Alive2 proof: https://alive2.llvm.org/ce/z/cYuczq
Promising optimization impact: 

>From 091cc77fb2993a9e3df4c3583da4a92ac9d5d166 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Thu, 30 Oct 2025 16:49:01 +0800
Subject: [PATCH 1/3] Add a test before commit

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

diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
index 98c0599ab209c..e70d6fb7d8bb2 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
@@ -602,3 +602,50 @@ bar:
   call void @bar()
   ret void
 }
+
+define void @test_switch_with_multicases_dest(i64 %i, ptr %Q) {
+; CHECK-LABEL: @test_switch_with_multicases_dest(
+; CHECK-NEXT:    switch i64 [[I:%.*]], label [[BB0:%.*]] [
+; CHECK-NEXT:      i64 1, label [[BB1:%.*]]
+; CHECK-NEXT:      i64 2, label [[BB2:%.*]]
+; CHECK-NEXT:      i64 3, label [[BB2]]
+; CHECK-NEXT:    ]
+; CHECK:       common.ret:
+; CHECK-NEXT:    ret void
+; CHECK:       bb0:
+; CHECK-NEXT:    store i32 1, ptr [[Q:%.*]], align 4
+; CHECK-NEXT:    [[A:%.*]] = load i32, ptr [[Q]], align 4
+; CHECK-NEXT:    call void @bar(i32 [[A]])
+; CHECK-NEXT:    br label [[COMMON_RET:%.*]]
+; CHECK:       bb1:
+; CHECK-NEXT:    store i32 1, ptr [[Q]], align 4
+; CHECK-NEXT:    [[B:%.*]] = load i32, ptr [[Q]], align 4
+; CHECK-NEXT:    call void @bar(i32 [[B]])
+; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK:       bb2:
+; CHECK-NEXT:    store i32 1, ptr [[Q]], align 4
+; CHECK-NEXT:    [[C:%.*]] = load i32, ptr [[Q]], align 4
+; CHECK-NEXT:    call void @bar(i32 [[C]])
+; CHECK-NEXT:    br label [[COMMON_RET]]
+;
+  switch i64 %i, label %bb0 [
+  i64 1, label %bb1
+  i64 2, label %bb2
+  i64 3, label %bb2
+  ]
+bb0:              ; preds = %0
+  store i32 1, ptr %Q
+  %A = load i32, ptr %Q               ; <i32> [#uses=1]
+  call void @bar( i32 %A )
+  ret void
+bb1:              ; preds = %0
+  store i32 1, ptr %Q
+  %B = load i32, ptr %Q               ; <i32> [#uses=1]
+  call void @bar( i32 %B )
+  ret void
+bb2:              ; preds = %0
+  store i32 1, ptr %Q
+  %C = load i32, ptr %Q               ; <i32> [#uses=1]
+  call void @bar( i32 %C )
+  ret void
+}

>From 9877726e958daffa8908f292fc7d3ebce853dd29 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Thu, 30 Oct 2025 19:32:45 +0800
Subject: [PATCH 2/3] [SimplifyCFG] Hoist common code for multi-cases dest

---
 llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 30 ++++++++++++++++-------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index b03fb6213d61c..4d2a840bf059f 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1866,10 +1866,18 @@ 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.
+  SmallVector<BasicBlock *, 4> UniqSuccessors;
+  UniqSuccessors.reserve(BB->getTerminator()->getNumSuccessors());
   for (auto *Succ : successors(BB)) {
     if (Succ->hasAddressTaken())
       return false;
-    if (Succ->getSinglePredecessor())
+    // Collect all unique successors, using vec instead of set to preserve
+    // order.
+    if (find(UniqSuccessors, Succ) == UniqSuccessors.end())
+      UniqSuccessors.push_back(Succ);
+    // Use getUniquePredecessor instead of getSinglePredecessor to support
+    // multi-cases successors in switch.
+    if (Succ->getUniquePredecessor())
       continue;
     // If Succ has >1 predecessors, continue to check if the Succ contains only
     // one `unreachable` inst. Since executing `unreachable` inst is an UB, we
@@ -1882,7 +1890,7 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
   // The second of pair is a SkipFlags bitmask.
   using SuccIterPair = std::pair<BasicBlock::iterator, unsigned>;
   SmallVector<SuccIterPair, 8> SuccIterPairs;
-  for (auto *Succ : successors(BB)) {
+  for (auto *Succ : UniqSuccessors) {
     BasicBlock::iterator SuccItr = Succ->begin();
     if (isa<PHINode>(*SuccItr))
       return false;
@@ -1893,19 +1901,19 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
     // Check if all instructions in the successor blocks match. This allows
     // hoisting all instructions and removing the blocks we are hoisting from,
     // so does not add any new instructions.
-    SmallVector<BasicBlock *> Succs = to_vector(successors(BB));
+
     // Check if sizes and terminators of all successors match.
-    bool AllSame = none_of(Succs, [&Succs](BasicBlock *Succ) {
-      Instruction *Term0 = Succs[0]->getTerminator();
+    bool AllSame = none_of(UniqSuccessors, [&UniqSuccessors](BasicBlock *Succ) {
+      Instruction *Term0 = UniqSuccessors[0]->getTerminator();
       Instruction *Term = Succ->getTerminator();
       return !Term->isSameOperationAs(Term0) ||
              !equal(Term->operands(), Term0->operands()) ||
-             Succs[0]->size() != Succ->size();
+             UniqSuccessors[0]->size() != Succ->size();
     });
     if (!AllSame)
       return false;
     if (AllSame) {
-      LockstepReverseIterator<true> LRI(Succs);
+      LockstepReverseIterator<true> LRI(UniqSuccessors);
       while (LRI.isValid()) {
         Instruction *I0 = (*LRI)[0];
         if (any_of(*LRI, [I0](Instruction *I) {
@@ -2156,9 +2164,13 @@ bool SimplifyCFGOpt::hoistSuccIdenticalTerminatorToSwitchOrIf(
       Updates.push_back({DominatorTree::Insert, TIParent, Succ});
   }
 
-  if (DTU)
-    for (BasicBlock *Succ : successors(TI))
+  if (DTU) {
+    // TI might be a switch with multi-cases destination, so we need to care for
+    // the duplication of successors.
+    SmallPtrSet<BasicBlock *, 4> UniqSuccs(llvm::from_range, successors(TI));
+    for (BasicBlock *Succ : UniqSuccs)
       Updates.push_back({DominatorTree::Delete, TIParent, Succ});
+  }
 
   eraseTerminatorAndDCECond(TI);
   if (DTU)

>From 93781e5daa2ad1474182be6513a24dc588f2106d Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Thu, 30 Oct 2025 19:33:28 +0800
Subject: [PATCH 3/3] Regenerate 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 e70d6fb7d8bb2..68103e874923e 100644
--- a/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
+++ b/llvm/test/Transforms/SimplifyCFG/hoist-common-code.ll
@@ -605,28 +605,11 @@ bar:
 
 define void @test_switch_with_multicases_dest(i64 %i, ptr %Q) {
 ; CHECK-LABEL: @test_switch_with_multicases_dest(
-; CHECK-NEXT:    switch i64 [[I:%.*]], label [[BB0:%.*]] [
-; CHECK-NEXT:      i64 1, label [[BB1:%.*]]
-; CHECK-NEXT:      i64 2, label [[BB2:%.*]]
-; CHECK-NEXT:      i64 3, label [[BB2]]
-; CHECK-NEXT:    ]
-; CHECK:       common.ret:
-; CHECK-NEXT:    ret void
-; CHECK:       bb0:
+; CHECK-NEXT:  common.ret:
 ; CHECK-NEXT:    store i32 1, ptr [[Q:%.*]], align 4
-; CHECK-NEXT:    [[A:%.*]] = load i32, ptr [[Q]], align 4
-; CHECK-NEXT:    call void @bar(i32 [[A]])
-; CHECK-NEXT:    br label [[COMMON_RET:%.*]]
-; CHECK:       bb1:
-; CHECK-NEXT:    store i32 1, ptr [[Q]], align 4
-; CHECK-NEXT:    [[B:%.*]] = load i32, ptr [[Q]], align 4
-; CHECK-NEXT:    call void @bar(i32 [[B]])
-; CHECK-NEXT:    br label [[COMMON_RET]]
-; CHECK:       bb2:
-; CHECK-NEXT:    store i32 1, ptr [[Q]], align 4
 ; CHECK-NEXT:    [[C:%.*]] = load i32, ptr [[Q]], align 4
 ; CHECK-NEXT:    call void @bar(i32 [[C]])
-; CHECK-NEXT:    br label [[COMMON_RET]]
+; CHECK-NEXT:    ret void
 ;
   switch i64 %i, label %bb0 [
   i64 1, label %bb1



More information about the llvm-commits mailing list