Skip to content

Commit 6f5c55f

Browse files
committed
Fix invalid SPIRV from continue inside switch inside for loop
When a `continue` statement appears inside a `switch` nested within a `for` loop, the `eliminateMultiLevelBreak` pass failed to detect the branch from inside the switch to the loop's continue block as a multi-level branch. This caused the raw IR branch to reach the SPIRV emitter, producing unstructured control flow that spirv-val rejects. Root cause: for `for`-loops, the continue block is distinct from the loop's target block. `populateExitBlocks()` stored `continueBlock` but did not add it to the region's `exitBlocks` list, so `mapExitBlockToRegion` never mapped it to the loop region, and `gatherInfo()` never flagged the branch as a multi-level branch. As a result, `eliminateContinueBlocksInFunc()` was never triggered to transform the IR into valid SPIRV structured form. Fix: when `continueBlock != targetBlock`, add `continueBlock` to `region->exitBlocks` so it participates in multi-level branch detection. This triggers `eliminateContinueBlocksInFunc()`, which wraps the loop body in an inner breakable region; the `continue` from inside the switch then becomes a break from that region — a valid SPIRV structured exit. Also fix a related leak: after processing a region, pop `continueBlock` from the global `exitBlocks` stack unconditionally, covering the while-loop case where `continueBlock == targetBlock` and it was not in `exitBlocks`. Fixes #9585, #10198.
1 parent 48d73cd commit 6f5c55f

File tree

3 files changed

+119
-1
lines changed

3 files changed

+119
-1
lines changed

source/slang/slang-ir-eliminate-multilevel-break.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,24 @@ struct EliminateMultiLevelBreakContext
7171
// If this is a loop, store the continue block.
7272
// We add it to the exitBlocks stack separately in collectBreakableRegionBlocks
7373
// so that nested constructs treat it as an exit point.
74-
if (as<IRLoop>(headerInst))
74+
if (auto loop = as<IRLoop>(headerInst))
75+
{
7576
continueBlock = getContinueBlock();
77+
// When the continue block is a distinct block from the loop's target block,
78+
// also add it to the region's exitBlocks. This ensures that branches from
79+
// nested constructs (e.g. a switch case with `continue` inside a for-loop)
80+
// to the loop's continue block are detected as multi-level branches and
81+
// handled correctly by this pass.
82+
//
83+
// When continueBlock == targetBlock (common for while-loops), we do NOT add
84+
// it here to exitBlocks, because the target block is already part of the
85+
// loop region's block set and adding it as an exit block would cause
86+
// duplicate key errors when building the block-to-region mapping.
87+
// In that case, we still push continueBlock to the exitBlocks stack in
88+
// collectBreakableRegionBlocks, so nested constructs treat it as an exit.
89+
if (continueBlock && continueBlock != loop->getTargetBlock())
90+
exitBlocks.add(continueBlock);
91+
}
7692
}
7793

7894
void replaceBreakBlock(IRBuilder* builder, IRBlock* block)
@@ -180,6 +196,10 @@ struct EliminateMultiLevelBreakContext
180196
// Pop the exit blocks.
181197
for (auto exitBlock : info.exitBlocks)
182198
exitBlocks.remove(exitBlock);
199+
// Also pop the continueBlock if it was pushed separately (when continueBlock ==
200+
// targetBlock it is not in info.exitBlocks but was still pushed to the stack above).
201+
if (info.continueBlock)
202+
exitBlocks.remove(info.continueBlock);
183203
}
184204

185205
void gatherInfo(IRGlobalValueWithCode* func)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Regression test for issue #10198:
2+
// "Invalid SPIRV: `continue` inside `switch` inside `for` produces unstructured control flow"
3+
//
4+
// A `continue` inside a switch case inside a `for` loop was producing invalid SPIRV.
5+
// The root cause was in eliminateMultiLevelBreak: the loop's continue block (which is
6+
// a distinct block from the loop's target in a for-loop) was not being added to the
7+
// region's exitBlocks, so branches from inside the switch to the continue block were
8+
// not detected as multi-level branches requiring transformation.
9+
//
10+
// This test verifies both correctness and that SPIRV validation passes.
11+
12+
//TEST:SIMPLE(filecheck=CHECK): -target spirv -stage compute -entry computeMain -emit-spirv-directly
13+
//TEST:COMPARE_COMPUTE(filecheck-buffer=CBUF): -output-using-type -cpu
14+
15+
// CHECK: OpEntryPoint
16+
17+
//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer
18+
RWStructuredBuffer<int> outputBuffer;
19+
20+
int test(int value)
21+
{
22+
int result = 0;
23+
for (int i = 0; i < 3; i++)
24+
{
25+
switch (value)
26+
{
27+
case 0:
28+
result += 1;
29+
continue; // triggers the bug: continue from switch inside for-loop
30+
case 1:
31+
result += 2;
32+
break;
33+
default:
34+
result += 3;
35+
break;
36+
}
37+
result += 10;
38+
}
39+
return result;
40+
}
41+
42+
[shader("compute")]
43+
[numthreads(1, 1, 1)]
44+
void computeMain()
45+
{
46+
// test(0): case 0 hits, result += 1, continue (skips +10) -> 3 iterations -> result = 3
47+
// test(1): case 1 hits, result += 2, break, then +10 -> 3 iterations -> result = 36
48+
// test(2): default hits, result += 3, break, then +10 -> 3 iterations -> result = 39
49+
// total = 3 + 36 + 39 = 78
50+
outputBuffer[0] = test(0) + test(1) + test(2);
51+
// CBUF: 78
52+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Regression test for issue #9585:
2+
// "For loop `continue` inside a `switch` breaks structured control flow"
3+
//
4+
// A `continue` inside a switch (with no other cases) inside a `for` loop was producing
5+
// invalid SPIRV. This is the simpler variant of #10198 — same root cause, same fix.
6+
//
7+
// Fixed in slang-ir-eliminate-multilevel-break.cpp by adding the loop's continueBlock
8+
// to region->exitBlocks when continueBlock != targetBlock (for-loops), so that branches
9+
// to it from inside nested switch constructs are treated as multi-level branches and
10+
// transformed into valid SPIRV structured control flow via eliminateContinueBlocksInFunc.
11+
12+
//TEST:SIMPLE(filecheck=CHECK): -target spirv -stage compute -entry computeMain -emit-spirv-directly
13+
//TEST:COMPARE_COMPUTE(filecheck-buffer=CBUF): -output-using-type -cpu
14+
15+
// CHECK: OpEntryPoint
16+
17+
//TEST_INPUT:ubuffer(data=[0], stride=4):out,name=outputBuffer
18+
RWStructuredBuffer<int> outputBuffer;
19+
20+
[shader("compute")]
21+
[numthreads(1, 1, 1)]
22+
void computeMain()
23+
{
24+
int sum = 0;
25+
26+
for (int i = 0; i < 3; ++i)
27+
{
28+
sum += 1;
29+
30+
switch (i)
31+
{
32+
case 0:
33+
{
34+
continue; // continue from switch inside for-loop: the bug
35+
}
36+
}
37+
38+
sum += 10; // skipped when i==0 due to continue, executed for i==1,2
39+
}
40+
41+
// i=0: sum += 1, continue (skip +10) -> sum=1
42+
// i=1: sum += 1, no case match, sum += 10 -> sum=12
43+
// i=2: sum += 1, no case match, sum += 10 -> sum=23
44+
outputBuffer[0] = sum;
45+
// CBUF: 23
46+
}

0 commit comments

Comments
 (0)