[llvm] 666f22b - [SPIRV] Support structurizing nested break chains (#222459)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 24 10:47:52 PDT 2026


Author: Chris B
Date: 2026-09-24T12:47:45-05:00
New Revision: 666f22b6aa5b449bc5a4f2348f912faa1012909b

URL: https://github.com/llvm/llvm-project/commit/666f22b6aa5b449bc5a4f2348f912faa1012909b
DIFF: https://github.com/llvm/llvm-project/commit/666f22b6aa5b449bc5a4f2348f912faa1012909b.diff

LOG: [SPIRV] Support structurizing nested break chains (#222459)

This PR updates the SPIRVStructurizer to handle nested break chains.
This pattern occurs when switch statements are lowered instead as branch
instructions and break statements need to exit out to an outer
containing block.

This is a required change to support #112056.

Assisted-by: GitHub Copilot

Added: 
    llvm/test/CodeGen/SPIRV/structurizer/branch-no-switch.ll

Modified: 
    llvm/lib/Target/SPIRV/SPIRVStructurizer.cpp
    llvm/test/CodeGen/SPIRV/structurizer/cf.for.short-circuited-cond.ll
    llvm/test/CodeGen/SPIRV/structurizer/cf.while.short-circuited-cond.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/SPIRV/SPIRVStructurizer.cpp b/llvm/lib/Target/SPIRV/SPIRVStructurizer.cpp
index 9ac7ac76a9a95..69427c28630a7 100644
--- a/llvm/lib/Target/SPIRV/SPIRVStructurizer.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVStructurizer.cpp
@@ -163,7 +163,8 @@ struct HeaderMergeContinueBlocks {
 };
 
 // Do a preorder traversal of the CFG starting from the BB |Start|.
-// point. Calls |op| on each basic block encountered during the traversal.
+// Calls |op| on each basic block encountered during the traversal. Returning
+// false prunes that block's successors without stopping other pending paths.
 static void visit(BasicBlock &Start, std::function<bool(BasicBlock *)> op) {
   std::stack<BasicBlock *> ToVisit;
   SmallPtrSet<BasicBlock *, 8> Seen;
@@ -311,7 +312,7 @@ class SPIRVStructurizerImpl {
       const DomTreeBuilder::BBDomTree &DT = getDT();
       assert(DT.dominates(Header, Merge));
       std::vector<BasicBlock *> Output;
-      POV->partialOrderVisit(*Header, [&](BasicBlock *BB) {
+      visit(*Header, [&](BasicBlock *BB) {
         if (BB == Merge)
           return false;
         if (DT.dominates(Merge, BB) || !DT.dominates(Header, BB))
@@ -338,7 +339,7 @@ class SPIRVStructurizerImpl {
       }
 
       std::vector<BasicBlock *> Output;
-      POV->partialOrderVisit(*Node->Header, [&](BasicBlock *BB) {
+      visit(*Node->Header, [&](BasicBlock *BB) {
         if (OutsideBlocks.count(BB) != 0)
           return false;
         if (DT.dominates(Node->Merge, BB) || !DT.dominates(Node->Header, BB))

diff  --git a/llvm/test/CodeGen/SPIRV/structurizer/branch-no-switch.ll b/llvm/test/CodeGen/SPIRV/structurizer/branch-no-switch.ll
new file mode 100644
index 0000000000000..eb2438eb3e2f0
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/structurizer/branch-no-switch.ll
@@ -0,0 +1,165 @@
+; RUN: llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - | FileCheck %s --implicit-check-not=OpSwitch
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-unknown-vulkan-compute %s -o - -filetype=obj | spirv-val %}
+
+; These CFGs model the conditional branch chains which are roughly equivalent
+; to switch statements but without using switch instructions. In particular, the
+; break-only chain used to make an outer selection merge branch into a nested
+; selection's merge block.
+
+; CHECK-DAG: OpName %[[FALLTHROUGH:[0-9]+]] "fallthrough"
+; CHECK-DAG: OpName %[[BREAKS:[0-9]+]] "breaks"
+; CHECK-DAG: OpName %[[MIXED:[0-9]+]] "mixed"
+
+; CHECK: %[[FALLTHROUGH]] = OpFunction
+; CHECK-COUNT-3: OpSelectionMerge
+; CHECK: OpFunctionEnd
+define i32 @fallthrough(i32 %value) {
+entry:
+  br label %sw.dispatch
+
+sw.dispatch:
+  %case0 = icmp eq i32 %value, 0
+  br i1 %case0, label %sw.bb0, label %sw.next0
+
+sw.bb0:
+  br label %sw.next0
+
+sw.next0:
+  %fellthrough0 = phi i1 [ true, %sw.bb0 ], [ false, %sw.dispatch ]
+  %case1 = icmp eq i32 %value, 1
+  %take1 = or i1 %fellthrough0, %case1
+  br i1 %take1, label %sw.bb1, label %sw.next1
+
+sw.bb1:
+  br label %sw.next1
+
+sw.next1:
+  %fellthrough1 = phi i1 [ true, %sw.bb1 ], [ false, %sw.next0 ]
+  %not.case0 = icmp ne i32 %value, 0
+  %not.case1 = icmp ne i32 %value, 1
+  %take.default = and i1 %not.case0, %not.case1
+  %take2 = or i1 %fellthrough1, %take.default
+  br i1 %take2, label %sw.default, label %sw.epilog
+
+sw.default:
+  br label %sw.epilog
+
+sw.epilog:
+  %result = phi i32 [ 7, %sw.default ], [ 0, %sw.next1 ]
+  ret i32 %result
+}
+
+; CHECK: %[[BREAKS]] = OpFunction
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpSelectionMerge %[[BREAK_EPILOG:[0-9]+]] None
+; CHECK-NEXT: OpBranchConditional %{{[0-9]+}} %[[BREAK_BB0:[0-9]+]] %[[BREAK_NEXT0:[0-9]+]]
+; CHECK: %[[BREAK_NEXT0]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpSelectionMerge %[[BREAK_NEXT0_EXIT:[0-9]+]] None
+; CHECK-NEXT: OpBranchConditional %{{[0-9]+}} %[[BREAK_NEXT0_EXIT]] %[[BREAK_NEXT1:[0-9]+]]
+; CHECK: %[[BREAK_NEXT1]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpSelectionMerge %[[BREAK_NEXT1_EXIT:[0-9]+]] None
+; CHECK-NEXT: OpBranchConditional %{{[0-9]+}} %[[BREAK_NEXT1_EXIT]] %[[BREAK_CLEANUP:[0-9]+]]
+; CHECK: %[[BREAK_CLEANUP]] = OpLabel
+; CHECK: OpBranch %[[BREAK_NEXT1_EXIT]]
+; CHECK: %[[BREAK_NEXT1_EXIT]] = OpLabel
+; CHECK: OpBranchConditional %{{[0-9]+}} %[[BREAK_DEFAULT:[0-9]+]] %[[BREAK_NEXT0_EXIT]]
+; CHECK: %[[BREAK_DEFAULT]] = OpLabel
+; CHECK: OpBranch %[[BREAK_NEXT0_EXIT]]
+; CHECK: %[[BREAK_NEXT0_EXIT]] = OpLabel
+; CHECK: OpBranchConditional %{{[0-9]+}} %[[BREAK_BB1:[0-9]+]] %[[BREAK_EPILOG]]
+; CHECK: %[[BREAK_BB1]] = OpLabel
+; CHECK: OpBranch %[[BREAK_EPILOG]]
+; CHECK: %[[BREAK_BB0]] = OpLabel
+; CHECK: OpBranch %[[BREAK_EPILOG]]
+; CHECK: %[[BREAK_EPILOG]] = OpLabel
+; CHECK-NOT: OpSelectionMerge
+; CHECK: OpFunctionEnd
+define i32 @breaks(i32 %value) {
+entry:
+  br label %sw.dispatch
+
+sw.dispatch:
+  %case0 = icmp eq i32 %value, 0
+  br i1 %case0, label %sw.bb0, label %sw.next0
+
+sw.bb0:
+  br label %sw.epilog
+
+sw.next0:
+  %case1 = icmp eq i32 %value, 1
+  br i1 %case1, label %sw.bb1, label %sw.next1
+
+sw.bb1:
+  br label %sw.epilog
+
+sw.next1:
+  %not.case0 = icmp ne i32 %value, 0
+  %not.case1 = icmp ne i32 %value, 1
+  %take.default = and i1 %not.case0, %not.case1
+  br i1 %take.default, label %sw.default, label %sw.cleanup
+
+sw.default:
+  br label %sw.epilog
+
+sw.cleanup:
+  br label %sw.epilog
+
+sw.epilog:
+  %result = phi i32 [ 1, %sw.bb0 ], [ 2, %sw.bb1 ],
+                    [ 4, %sw.default ], [ 0, %sw.cleanup ]
+  ret i32 %result
+}
+
+; CHECK: %[[MIXED]] = OpFunction
+; CHECK-COUNT-4: OpSelectionMerge
+; CHECK: OpFunctionEnd
+define i32 @mixed(i32 %value) {
+entry:
+  br label %sw.dispatch
+
+sw.dispatch:
+  %case0 = icmp eq i32 %value, 0
+  br i1 %case0, label %sw.bb0, label %sw.next0
+
+sw.bb0:
+  br label %sw.next0
+
+sw.next0:
+  %fellthrough0 = phi i1 [ true, %sw.bb0 ], [ false, %sw.dispatch ]
+  %case1 = icmp eq i32 %value, 1
+  %take1 = or i1 %fellthrough0, %case1
+  br i1 %take1, label %sw.bb1, label %sw.next1
+
+sw.bb1:
+  br label %sw.epilog
+
+sw.next1:
+  %case2 = icmp eq i32 %value, 2
+  br i1 %case2, label %sw.bb2, label %sw.next2
+
+sw.bb2:
+  br label %sw.next2
+
+sw.next2:
+  %fellthrough2 = phi i1 [ true, %sw.bb2 ], [ false, %sw.next1 ]
+  %not.case0 = icmp ne i32 %value, 0
+  %not.case1 = icmp ne i32 %value, 1
+  %not.case2 = icmp ne i32 %value, 2
+  %not.case01 = and i1 %not.case0, %not.case1
+  %take.default = and i1 %not.case01, %not.case2
+  %take3 = or i1 %fellthrough2, %take.default
+  br i1 %take3, label %sw.default, label %sw.cleanup
+
+sw.default:
+  br label %sw.epilog
+
+sw.cleanup:
+  br label %sw.epilog
+
+sw.epilog:
+  %result = phi i32 [ 3, %sw.bb1 ], [ 12, %sw.default ],
+                    [ 0, %sw.cleanup ]
+  ret i32 %result
+}

diff  --git a/llvm/test/CodeGen/SPIRV/structurizer/cf.for.short-circuited-cond.ll b/llvm/test/CodeGen/SPIRV/structurizer/cf.for.short-circuited-cond.ll
index a5db8f53d2b35..e583f5957fcae 100644
--- a/llvm/test/CodeGen/SPIRV/structurizer/cf.for.short-circuited-cond.ll
+++ b/llvm/test/CodeGen/SPIRV/structurizer/cf.for.short-circuited-cond.ll
@@ -86,7 +86,9 @@
 
             ; CHECK:        %[[#bb113]] = OpLabel
             ; CHECK:                      OpSelectionMerge %[[#bb116:]] None
-            ; CHECK:                      OpBranchConditional %[[#]] %[[#bb117:]] %[[#bb116]]
+            ; CHECK:                      OpBranchConditional %[[#]] %[[#bb117:]] %[[#unreachable:]]
+              ; CHECK:   %[[#unreachable]] = OpLabel
+              ; CHECK-NEXT:                OpUnreachable
               ; CHECK:        %[[#bb117]] = OpLabel
               ; CHECK:                      OpSelectionMerge %[[#bb118:]] None
               ; CHECK:                      OpBranchConditional %[[#]] %[[#bb119:]] %[[#bb120:]]
@@ -107,16 +109,8 @@
                   ; CHECK:        %[[#bb129]] = OpLabel
                   ; CHECK:                      OpBranch %[[#bb116]]
                   ; CHECK:        %[[#bb116]] = OpLabel
-                  ; CHECK:                      OpBranchConditional %[[#]] %[[#bb112]] %[[#bb133:]]
-                    ; CHECK:        %[[#bb133]] = OpLabel
-                    ; CHECK:                      OpBranch %[[#bb112]]
+                  ; CHECK:                      OpBranch %[[#bb112]]
                     ; CHECK:        %[[#bb112]] = OpLabel
-                    ; CHECK:                      OpSelectionMerge %[[#bb136:]] None
-                    ; CHECK:                      OpBranchConditional %[[#]] %[[#bb136]] %[[#bb137:]]
-                    ; CHECK:        %[[#bb137]] = OpLabel
-                    ; CHECK-NEXT:                 OpUnreachable
-
-                    ; CHECK:        %[[#bb136]] = OpLabel
                     ; CHECK:                      OpBranchConditional %[[#]] %[[#bb138:]] %[[#bb109]]
                       ; CHECK:        %[[#bb109]] = OpLabel
                       ; CHECK:                      OpBranch %[[#bb139:]]
@@ -423,5 +417,3 @@ attributes #3 = { convergent }
 !0 = !{i32 1, !"wchar_size", i32 4}
 !1 = !{i32 4, !"dx.disable_optimizations", i32 1}
 !2 = !{i32 7, !"frame-pointer", i32 2}
-
-

diff  --git a/llvm/test/CodeGen/SPIRV/structurizer/cf.while.short-circuited-cond.ll b/llvm/test/CodeGen/SPIRV/structurizer/cf.while.short-circuited-cond.ll
index 7f77b6da4ed46..ae0f5d07be537 100644
--- a/llvm/test/CodeGen/SPIRV/structurizer/cf.while.short-circuited-cond.ll
+++ b/llvm/test/CodeGen/SPIRV/structurizer/cf.while.short-circuited-cond.ll
@@ -62,7 +62,9 @@
 ; CHECK:                  OpBranch %[[#bb106]]
 ; CHECK:    %[[#bb107]] = OpLabel
 ; CHECK:                  OpSelectionMerge %[[#bb109:]] None
-; CHECK:                  OpBranchConditional %[[#]] %[[#bb110:]] %[[#bb109]]
+; CHECK:                  OpBranchConditional %[[#]] %[[#bb110:]] %[[#unreachable:]]
+; CHECK:   %[[#unreachable]] = OpLabel
+; CHECK-NEXT:              OpUnreachable
 ; CHECK:    %[[#bb110]] = OpLabel
 ; CHECK:                  OpSelectionMerge %[[#bb111:]] None
 ; CHECK:                  OpBranchConditional %[[#]] %[[#bb112:]] %[[#bb113:]]
@@ -82,14 +84,8 @@
 ; CHECK:    %[[#bb117]] = OpLabel
 ; CHECK:                  OpBranch %[[#bb109]]
 ; CHECK:    %[[#bb109]] = OpLabel
-; CHECK:                  OpBranchConditional %[[#]] %[[#bb106]] %[[#bb118:]]
-; CHECK:    %[[#bb118]] = OpLabel
 ; CHECK:                  OpBranch %[[#bb106]]
 ; CHECK:    %[[#bb106]] = OpLabel
-; CHECK:                  OpSelectionMerge %[[#bb119:]] None
-; CHECK:                  OpBranchConditional %[[#]] %[[#bb119]] %[[#bb120:]]
-; CHECK:    %[[#bb120]] = OpLabel
-; CHECK:    %[[#bb119]] = OpLabel
 ; CHECK:                  OpBranchConditional %[[#]] %[[#bb104]] %[[#bb103]]
 ; CHECK:    %[[#bb103]] = OpLabel
 ; CHECK:                  OpBranch %[[#bb121:]]
@@ -303,4 +299,3 @@ attributes #2 = { convergent nocallback nofree nosync nounwind willreturn memory
 !1 = !{i32 4, !"dx.disable_optimizations", i32 1}
 !2 = !{i32 7, !"frame-pointer", i32 2}
 
-


        


More information about the llvm-commits mailing list