Implement switch in SkVM.
SkVM implements switches as a pseudo-loop; breaks are handled with the
condition mask just like a for loop. Fallthrough is handled via a
scratch Value in a temporary slot. `writeStore` neeeded to be refactored
to support writing into slot(s) without an associated Variable.
At IR generation time, SwitchStatements are now emitted without error
even in strict-ES2 mode. The GLSL code generator currently reports these
as an error in strict-ES2 mode, but this will be fixed in a followup
coming shortly (the switch will be rewritten as ifs inside a one-shot
loop, similar to our IR-rewrite strategy).
Change-Id: I5507257246c42a35d2f46b4b9a89492a5ffeff9b
Bug: skia:12450
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/451421
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/gn/sksl_tests.gni b/gn/sksl_tests.gni
index b9f9255..daeb765 100644
--- a/gn/sksl_tests.gni
+++ b/gn/sksl_tests.gni
@@ -560,6 +560,9 @@
"/sksl/runtime/PrecisionQualifiers.rts",
"/sksl/runtime/SampleWithExplicitCoord.rts",
"/sksl/runtime/Switch.rts",
+ "/sksl/runtime/SwitchDefaultOnly.rts",
+ "/sksl/runtime/SwitchWithFallthrough.rts",
+ "/sksl/runtime/SwitchWithLoops.rts",
"/sksl/runtime/VectorIndexing.rts",
]
diff --git a/resources/sksl/runtime/SwitchDefaultOnly.rts b/resources/sksl/runtime/SwitchDefaultOnly.rts
new file mode 100644
index 0000000..3dd5760
--- /dev/null
+++ b/resources/sksl/runtime/SwitchDefaultOnly.rts
@@ -0,0 +1,7 @@
+uniform half4 colorGreen, colorRed;
+
+half4 main(float2 coords) {
+ switch (int(colorGreen.g)) {
+ default: return colorGreen;
+ }
+}
diff --git a/resources/sksl/runtime/SwitchWithFallthrough.rts b/resources/sksl/runtime/SwitchWithFallthrough.rts
new file mode 100644
index 0000000..04d5f05
--- /dev/null
+++ b/resources/sksl/runtime/SwitchWithFallthrough.rts
@@ -0,0 +1,29 @@
+uniform half4 colorGreen, colorRed;
+
+bool switch_fallthrough(int value) {
+ bool ok = false;
+ switch (value) {
+ case 2: break;
+ case 1:
+ case 0: ok = true; break;
+ default: break;
+ }
+ return ok;
+}
+
+bool switch_fallthrough_twice(int value) {
+ bool ok = false;
+ switch (value) {
+ case 0: break;
+ case 1:
+ case 2:
+ case 3: ok = true; break;
+ default: break;
+ }
+ return ok;
+}
+
+half4 main(float2 coords) {
+ int x = int(colorGreen.g);
+ return (switch_fallthrough(x) && switch_fallthrough_twice(x)) ? colorGreen : colorRed;
+}
diff --git a/resources/sksl/runtime/SwitchWithLoops.rts b/resources/sksl/runtime/SwitchWithLoops.rts
new file mode 100644
index 0000000..1a43043
--- /dev/null
+++ b/resources/sksl/runtime/SwitchWithLoops.rts
@@ -0,0 +1,38 @@
+uniform half4 colorGreen, colorRed;
+
+bool switch_with_break_in_loop(int x) {
+ int val = 0;
+ switch (x) {
+ case 1: for (int i=0; i<10; ++i) { ++val; break; ++val; }
+ default: ++val;
+ }
+ return val == 2;
+}
+
+bool switch_with_continue_in_loop(int x) {
+ int val = 0;
+ switch (x) {
+ case 1: for (int i=0; i<10; ++i) { ++val; continue; ++val; }
+ default: ++val;
+ }
+ return val == 11;
+}
+
+bool loop_with_break_in_switch(int x) {
+ int val = 0;
+ for (int i=0; i<10; ++i) {
+ switch (x) {
+ case 1: ++val; break;
+ default: return false;
+ }
+ ++val;
+ }
+ return val == 20;
+}
+
+half4 main(float2 coords) {
+ int x = int(colorGreen.g);
+ return (switch_with_break_in_loop(x) &&
+ switch_with_continue_in_loop(x) &&
+ loop_with_break_in_switch(x)) ? colorGreen : colorRed;
+}
diff --git a/resources/sksl/runtime_errors/IllegalStatements.rts b/resources/sksl/runtime_errors/IllegalStatements.rts
index faa27a0..dbd1808 100644
--- a/resources/sksl/runtime_errors/IllegalStatements.rts
+++ b/resources/sksl/runtime_errors/IllegalStatements.rts
@@ -1,9 +1,7 @@
-// Expect 4 errors
+// Expect 3 errors
void discard_stmt() { discard; }
int do_loop(int x) { do { x++; } while(x < 1); return x; }
int while_loop(int x) { while (x < 1) { x++; } return x; }
-
-int switch_stmt(int x) { switch (x) { case 0: return 1; default: return x; } }
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index 2d2fc02..8a7d5d7 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -724,8 +724,6 @@
break;
case Statement::Kind::kDo:
- case Statement::Kind::kSwitch:
- case Statement::Kind::kSwitchCase:
SkDEBUGFAIL("encountered a statement that shouldn't exist in an ES2 program");
break;
diff --git a/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp b/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp
index 5d0bc14..d4479db 100644
--- a/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLGLSLCodeGenerator.cpp
@@ -1354,6 +1354,11 @@
}
void GLSLCodeGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ if (fProgram.fConfig->strictES2Mode()) {
+ // TODO(skia:12450): write switch compatibility code
+ fContext.fErrors->error(s.fOffset, "switch statements are not supported");
+ }
+
this->write("switch (");
this->writeExpression(*s.value(), Precedence::kTopLevel);
this->writeLine(") {");
diff --git a/src/sksl/codegen/SkSLVMCodeGenerator.cpp b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
index ce46813..c77e7e3 100644
--- a/src/sksl/codegen/SkSLVMCodeGenerator.cpp
+++ b/src/sksl/codegen/SkSLVMCodeGenerator.cpp
@@ -213,9 +213,11 @@
void writeForStatement(const ForStatement& f);
void writeIfStatement(const IfStatement& stmt);
void writeReturnStatement(const ReturnStatement& r);
+ void writeSwitchStatement(const SwitchStatement& s);
void writeVarDeclaration(const VarDeclaration& decl);
Value writeStore(const Expression& lhs, const Value& rhs);
+ void writeStore(SkSpan<size_t> slots, const Value& rhs);
Value writeMatrixInverse2x2(const Value& m);
Value writeMatrixInverse3x3(const Value& m);
@@ -343,7 +345,7 @@
fSlots[slot + 3] = fBuilder->splat(1.0f).id;
break;
default:
- SkDEBUGFAIL("Unsupported builtin");
+ SkDEBUGFAILF("Unsupported builtin %d", builtin);
}
continue;
}
@@ -1456,14 +1458,25 @@
// When we get here, 'slots' are all relative to the first slot holding 'var's storage
const Variable& var = *expr->as<VariableReference>().variable();
size_t varSlot = this->getSlot(var);
+ for (size_t& slot : slots) {
+ SkASSERT(slot < var.type().slotCount());
+ slot += varSlot;
+ }
+
+ // `slots` are now absolute indices into `fSlots`.
+ this->writeStore(SkMakeSpan(slots), rhs);
+ return rhs;
+}
+
+void SkVMGenerator::writeStore(SkSpan<size_t> slots, const Value& rhs) {
+ SkASSERT(rhs.slots() == slots.size());
+
skvm::I32 mask = this->mask();
for (size_t i = rhs.slots(); i --> 0;) {
- SkASSERT(slots[i] < var.type().slotCount());
- skvm::F32 curr = f32(fSlots[varSlot + slots[i]]),
+ skvm::F32 curr = f32(fSlots[slots[i]]),
next = f32(rhs[i]);
- fSlots[varSlot + slots[i]] = select(mask, next, curr).id;
+ fSlots[slots[i]] = select(mask, next, curr).id;
}
- return rhs;
}
void SkVMGenerator::writeBlock(const Block& b) {
@@ -1542,6 +1555,47 @@
currentFunction().fReturned |= returnsHere;
}
+void SkVMGenerator::writeSwitchStatement(const SwitchStatement& s) {
+ skvm::Val falseValue = fBuilder->splat( 0).id;
+ skvm::Val trueValue = fBuilder->splat(~0).id;
+
+ // Create a new slot for the "switchFallthough" scratch variable, initialized to false.
+ size_t switchFallthroughSlot = fSlots.size();
+ fSlots.push_back(falseValue);
+
+ // Loop masks behave just like for statements. When a break is encountered, it masks off all
+ // lanes for the rest of the body of the switch.
+ skvm::I32 oldLoopMask = fLoopMask;
+ Value switchValue = this->writeExpression(*s.value());
+
+ for (const std::unique_ptr<Statement>& stmt : s.cases()) {
+ const SwitchCase& c = stmt->as<SwitchCase>();
+ if (c.value()) {
+ Value caseValue = this->writeExpression(*c.value());
+
+ // We want to execute this switch case if we're falling through from a previous case, or
+ // if the case value matches.
+ Value switchFallthroughValue(1);
+ switchFallthroughValue[0] = fSlots[switchFallthroughSlot];
+
+ Value condition = i32(switchFallthroughValue) | (i32(caseValue) == i32(switchValue));
+ ScopedCondition conditionalCaseBlock(this, i32(condition));
+ this->writeStatement(*c.statement());
+
+ // We always set the fallthrough flag after a case block (`break` still works to stop
+ // the flow of execution regardless).
+ this->writeStore(SkMakeSpan(&switchFallthroughSlot, 1), i32(trueValue));
+ } else {
+ // This is the default case. Since it's always last, we can just dump in the code.
+ this->writeStatement(*c.statement());
+ }
+ }
+
+ // Restore state.
+ fLoopMask = oldLoopMask;
+ fSlots.pop_back();
+}
+
void SkVMGenerator::writeVarDeclaration(const VarDeclaration& decl) {
size_t slot = this->getSlot(decl.var()),
nslots = decl.var().type().slotCount();
@@ -1575,12 +1629,14 @@
case Statement::Kind::kReturn:
this->writeReturnStatement(s.as<ReturnStatement>());
break;
+ case Statement::Kind::kSwitch:
+ this->writeSwitchStatement(s.as<SwitchStatement>());
+ break;
case Statement::Kind::kVarDeclaration:
this->writeVarDeclaration(s.as<VarDeclaration>());
break;
case Statement::Kind::kDiscard:
case Statement::Kind::kDo:
- case Statement::Kind::kSwitch:
SkDEBUGFAIL("Unsupported control flow");
break;
case Statement::Kind::kInlineMarker:
diff --git a/src/sksl/ir/SkSLSwitchStatement.cpp b/src/sksl/ir/SkSLSwitchStatement.cpp
index 6ad93e6..c41c52f 100644
--- a/src/sksl/ir/SkSLSwitchStatement.cpp
+++ b/src/sksl/ir/SkSLSwitchStatement.cpp
@@ -172,10 +172,6 @@
StatementArray caseStatements,
std::shared_ptr<SymbolTable> symbolTable) {
SkASSERT(caseValues.size() == caseStatements.size());
- if (context.fConfig->strictES2Mode()) {
- context.fErrors->error(offset, "switch statements are not supported");
- return nullptr;
- }
value = context.fTypes.fInt->coerceExpression(std::move(value), context);
if (!value) {
diff --git a/tests/sksl/runtime/Switch.skvm b/tests/sksl/runtime/Switch.skvm
index 9df38e1..fcf5ff5 100644
--- a/tests/sksl/runtime/Switch.skvm
+++ b/tests/sksl/runtime/Switch.skvm
@@ -1,4 +1,38 @@
-### Compilation failed:
-
-error: 6: switch statements are not supported
-1 error
+17 registers, 36 instructions:
+0 r0 = uniform32 ptr0 4
+1 r1 = uniform32 ptr0 8
+2 r2 = uniform32 ptr0 C
+3 r3 = uniform32 ptr0 10
+4 r4 = uniform32 ptr0 14
+5 r5 = uniform32 ptr0 18
+6 r6 = uniform32 ptr0 1C
+7 r7 = uniform32 ptr0 20
+8 r8 = splat 0 (0)
+9 r9 = splat FFFFFFFF (nan)
+10 r10 = trunc r1
+11 r8 = eq_i32 r8 r10
+12 r11 = bit_and r7 r8
+13 r12 = bit_and r6 r8
+14 r13 = bit_and r5 r8
+15 r14 = bit_and r4 r8
+16 r15 = bit_xor r9 r8
+17 r8 = bit_and r8 r15
+18 r16 = splat 1 (1.4012985e-45)
+19 r10 = eq_i32 r16 r10
+20 r10 = bit_or r8 r10
+21 r10 = bit_and r10 r15
+22 r11 = select r10 r3 r11
+23 r12 = select r10 r2 r12
+24 r13 = select r10 r1 r13
+25 r14 = select r10 r0 r14
+26 r10 = bit_xor r9 r10
+27 r10 = bit_and r15 r10
+28 r11 = select r10 r7 r11
+29 r12 = select r10 r6 r12
+30 r13 = select r10 r5 r13
+31 r14 = select r10 r4 r14
+loop:
+32 store32 ptr1 r14
+33 store32 ptr2 r13
+34 store32 ptr3 r12
+35 store32 ptr4 r11
diff --git a/tests/sksl/runtime/Switch.stage b/tests/sksl/runtime/Switch.stage
index 9df38e1..6d58e81 100644
--- a/tests/sksl/runtime/Switch.stage
+++ b/tests/sksl/runtime/Switch.stage
@@ -1,4 +1,16 @@
-### Compilation failed:
-
-error: 6: switch statements are not supported
-1 error
+uniform half4 colorGreen;
+uniform half4 colorRed;
+half4 main(float2 coords)
+{
+ half4 color;
+ switch (int(colorGreen.y))
+ {
+ case 0:color = colorRed;
+ break;
+ case 1:color = colorGreen;
+ break;
+ default:color = colorRed;
+ break;
+ }
+ return half4(color);
+}
diff --git a/tests/sksl/runtime/SwitchDefaultOnly.skvm b/tests/sksl/runtime/SwitchDefaultOnly.skvm
new file mode 100644
index 0000000..8f00020
--- /dev/null
+++ b/tests/sksl/runtime/SwitchDefaultOnly.skvm
@@ -0,0 +1,10 @@
+4 registers, 8 instructions:
+0 r0 = uniform32 ptr0 4
+1 r1 = uniform32 ptr0 8
+2 r2 = uniform32 ptr0 C
+3 r3 = uniform32 ptr0 10
+loop:
+4 store32 ptr1 r0
+5 store32 ptr2 r1
+6 store32 ptr3 r2
+7 store32 ptr4 r3
diff --git a/tests/sksl/runtime/SwitchDefaultOnly.stage b/tests/sksl/runtime/SwitchDefaultOnly.stage
new file mode 100644
index 0000000..9ff0ff1
--- /dev/null
+++ b/tests/sksl/runtime/SwitchDefaultOnly.stage
@@ -0,0 +1,9 @@
+uniform half4 colorGreen;
+uniform half4 colorRed;
+half4 main(float2 coords)
+{
+ switch (int(colorGreen.y))
+ {
+ default:return half4(colorGreen);
+ }
+}
diff --git a/tests/sksl/runtime/SwitchWithFallthrough.skvm b/tests/sksl/runtime/SwitchWithFallthrough.skvm
new file mode 100644
index 0000000..d4cc98e
--- /dev/null
+++ b/tests/sksl/runtime/SwitchWithFallthrough.skvm
@@ -0,0 +1,51 @@
+16 registers, 49 instructions:
+0 r0 = uniform32 ptr0 4
+1 r1 = uniform32 ptr0 8
+2 r2 = uniform32 ptr0 C
+3 r3 = uniform32 ptr0 10
+4 r4 = uniform32 ptr0 14
+5 r5 = uniform32 ptr0 18
+6 r6 = uniform32 ptr0 1C
+7 r7 = uniform32 ptr0 20
+8 r8 = splat 0 (0)
+9 r9 = splat FFFFFFFF (nan)
+10 r10 = trunc r1
+11 r11 = splat 2 (2.8025969e-45)
+12 r11 = eq_i32 r11 r10
+13 r12 = bit_xor r9 r11
+14 r13 = bit_and r11 r12
+15 r14 = splat 1 (1.4012985e-45)
+16 r14 = eq_i32 r14 r10
+17 r15 = bit_or r13 r14
+18 r15 = bit_and r15 r12
+19 r13 = select r15 r9 r13
+20 r8 = eq_i32 r8 r10
+21 r13 = bit_or r13 r8
+22 r12 = bit_and r13 r12
+23 r8 = bit_and r12 r8
+24 r13 = bit_xor r9 r8
+25 r8 = bit_and r8 r13
+26 r14 = bit_or r8 r14
+27 r14 = bit_and r12 r14
+28 r14 = bit_and r14 r13
+29 r8 = select r14 r9 r8
+30 r11 = bit_or r8 r11
+31 r11 = bit_and r12 r11
+32 r11 = bit_and r11 r13
+33 r8 = select r11 r9 r8
+34 r9 = splat 3 (4.2038954e-45)
+35 r10 = eq_i32 r9 r10
+36 r10 = bit_or r8 r10
+37 r10 = bit_and r12 r10
+38 r13 = bit_and r10 r13
+39 r13 = bit_and r13 r12
+40 r13 = bit_and r12 r13
+41 r4 = select r13 r0 r4
+42 r5 = select r13 r1 r5
+43 r6 = select r13 r2 r6
+44 r7 = select r13 r3 r7
+loop:
+45 store32 ptr1 r4
+46 store32 ptr2 r5
+47 store32 ptr3 r6
+48 store32 ptr4 r7
diff --git a/tests/sksl/runtime/SwitchWithFallthrough.stage b/tests/sksl/runtime/SwitchWithFallthrough.stage
new file mode 100644
index 0000000..0f5442e
--- /dev/null
+++ b/tests/sksl/runtime/SwitchWithFallthrough.stage
@@ -0,0 +1,27 @@
+uniform half4 colorGreen;
+uniform half4 colorRed;
+bool switch_fallthrough_twice_0(int value)
+{
+ bool ok = false;
+ switch (value)
+ {
+ case 0:break;
+ case 1:case 2:case 3:ok = true;
+ break;
+ default:break;
+ }
+ return ok;
+}
+half4 main(float2 coords)
+{
+ int x = int(colorGreen.y);
+ bool _0_ok = false;
+ switch (x)
+ {
+ case 2:break;
+ case 1:case 0:_0_ok = true;
+ break;
+ default:break;
+ }
+ return half4(_0_ok && switch_fallthrough_twice_0(x) ? colorGreen : colorRed);
+}
diff --git a/tests/sksl/runtime/SwitchWithLoops.skvm b/tests/sksl/runtime/SwitchWithLoops.skvm
new file mode 100644
index 0000000..565322b
--- /dev/null
+++ b/tests/sksl/runtime/SwitchWithLoops.skvm
@@ -0,0 +1,297 @@
+18 registers, 295 instructions:
+0 r0 = uniform32 ptr0 4
+1 r1 = uniform32 ptr0 8
+2 r2 = uniform32 ptr0 C
+3 r3 = uniform32 ptr0 10
+4 r4 = uniform32 ptr0 14
+5 r5 = uniform32 ptr0 18
+6 r6 = uniform32 ptr0 1C
+7 r7 = uniform32 ptr0 20
+8 r8 = splat FFFFFFFF (nan)
+9 r9 = trunc r1
+10 r10 = splat 1 (1.4012985e-45)
+11 r9 = eq_i32 r10 r9
+12 r11 = bit_and r10 r9
+13 r12 = bit_xor r8 r9
+14 r13 = add_i32 r11 r10
+15 r14 = bit_and r9 r12
+16 r11 = select r14 r13 r11
+17 r13 = add_i32 r11 r10
+18 r11 = select r14 r13 r11
+19 r14 = bit_xor r8 r14
+20 r14 = bit_and r12 r14
+21 r12 = add_i32 r11 r10
+22 r13 = bit_and r9 r14
+23 r11 = select r13 r12 r11
+24 r12 = splat 2 (2.8025969e-45)
+25 r15 = add_i32 r11 r10
+26 r11 = select r13 r15 r11
+27 r13 = bit_xor r8 r13
+28 r13 = bit_and r14 r13
+29 r14 = add_i32 r11 r10
+30 r15 = bit_and r9 r13
+31 r11 = select r15 r14 r11
+32 r14 = add_i32 r11 r10
+33 r11 = select r15 r14 r11
+34 r15 = bit_xor r8 r15
+35 r15 = bit_and r13 r15
+36 r13 = add_i32 r11 r10
+37 r14 = bit_and r9 r15
+38 r11 = select r14 r13 r11
+39 r13 = add_i32 r11 r10
+40 r11 = select r14 r13 r11
+41 r14 = bit_xor r8 r14
+42 r14 = bit_and r15 r14
+43 r15 = add_i32 r11 r10
+44 r13 = bit_and r9 r14
+45 r11 = select r13 r15 r11
+46 r15 = add_i32 r11 r10
+47 r11 = select r13 r15 r11
+48 r13 = bit_xor r8 r13
+49 r13 = bit_and r14 r13
+50 r14 = add_i32 r11 r10
+51 r15 = bit_and r9 r13
+52 r11 = select r15 r14 r11
+53 r14 = add_i32 r11 r10
+54 r11 = select r15 r14 r11
+55 r15 = bit_xor r8 r15
+56 r15 = bit_and r13 r15
+57 r13 = add_i32 r11 r10
+58 r14 = bit_and r9 r15
+59 r11 = select r14 r13 r11
+60 r13 = add_i32 r11 r10
+61 r11 = select r14 r13 r11
+62 r14 = bit_xor r8 r14
+63 r14 = bit_and r15 r14
+64 r15 = add_i32 r11 r10
+65 r13 = bit_and r9 r14
+66 r11 = select r13 r15 r11
+67 r15 = add_i32 r11 r10
+68 r11 = select r13 r15 r11
+69 r13 = bit_xor r8 r13
+70 r13 = bit_and r14 r13
+71 r14 = add_i32 r11 r10
+72 r15 = bit_and r9 r13
+73 r11 = select r15 r14 r11
+74 r14 = add_i32 r11 r10
+75 r11 = select r15 r14 r11
+76 r15 = bit_xor r8 r15
+77 r15 = bit_and r13 r15
+78 r13 = add_i32 r11 r10
+79 r15 = bit_and r9 r15
+80 r11 = select r15 r13 r11
+81 r11 = add_i32 r11 r10
+82 r12 = eq_i32 r11 r12
+83 r11 = bit_and r12 r9
+84 r13 = bit_and r10 r11
+85 r15 = bit_xor r8 r11
+86 r14 = add_i32 r13 r10
+87 r16 = bit_and r11 r15
+88 r13 = select r16 r14 r13
+89 r15 = bit_or r15 r11
+90 r14 = add_i32 r13 r10
+91 r16 = bit_and r11 r15
+92 r13 = select r16 r14 r13
+93 r14 = bit_xor r8 r16
+94 r14 = bit_and r15 r14
+95 r15 = add_i32 r13 r10
+96 r17 = bit_and r11 r14
+97 r13 = select r17 r15 r13
+98 r16 = bit_or r14 r16
+99 r14 = add_i32 r13 r10
+100 r15 = bit_and r11 r16
+101 r13 = select r15 r14 r13
+102 r14 = bit_xor r8 r15
+103 r14 = bit_and r16 r14
+104 r16 = add_i32 r13 r10
+105 r17 = bit_and r11 r14
+106 r13 = select r17 r16 r13
+107 r15 = bit_or r14 r15
+108 r14 = add_i32 r13 r10
+109 r16 = bit_and r11 r15
+110 r13 = select r16 r14 r13
+111 r14 = bit_xor r8 r16
+112 r14 = bit_and r15 r14
+113 r15 = add_i32 r13 r10
+114 r17 = bit_and r11 r14
+115 r13 = select r17 r15 r13
+116 r16 = bit_or r14 r16
+117 r14 = add_i32 r13 r10
+118 r15 = bit_and r11 r16
+119 r13 = select r15 r14 r13
+120 r14 = bit_xor r8 r15
+121 r14 = bit_and r16 r14
+122 r16 = add_i32 r13 r10
+123 r17 = bit_and r11 r14
+124 r13 = select r17 r16 r13
+125 r15 = bit_or r14 r15
+126 r14 = add_i32 r13 r10
+127 r16 = bit_and r11 r15
+128 r13 = select r16 r14 r13
+129 r14 = bit_xor r8 r16
+130 r14 = bit_and r15 r14
+131 r15 = add_i32 r13 r10
+132 r17 = bit_and r11 r14
+133 r13 = select r17 r15 r13
+134 r16 = bit_or r14 r16
+135 r14 = add_i32 r13 r10
+136 r15 = bit_and r11 r16
+137 r13 = select r15 r14 r13
+138 r14 = bit_xor r8 r15
+139 r14 = bit_and r16 r14
+140 r16 = add_i32 r13 r10
+141 r17 = bit_and r11 r14
+142 r13 = select r17 r16 r13
+143 r15 = bit_or r14 r15
+144 r14 = add_i32 r13 r10
+145 r16 = bit_and r11 r15
+146 r13 = select r16 r14 r13
+147 r14 = bit_xor r8 r16
+148 r14 = bit_and r15 r14
+149 r15 = add_i32 r13 r10
+150 r17 = bit_and r11 r14
+151 r13 = select r17 r15 r13
+152 r16 = bit_or r14 r16
+153 r14 = add_i32 r13 r10
+154 r15 = bit_and r11 r16
+155 r13 = select r15 r14 r13
+156 r14 = bit_xor r8 r15
+157 r14 = bit_and r16 r14
+158 r16 = add_i32 r13 r10
+159 r17 = bit_and r11 r14
+160 r13 = select r17 r16 r13
+161 r15 = bit_or r14 r15
+162 r14 = add_i32 r13 r10
+163 r16 = bit_and r11 r15
+164 r13 = select r16 r14 r13
+165 r16 = bit_xor r8 r16
+166 r16 = bit_and r15 r16
+167 r15 = add_i32 r13 r10
+168 r16 = bit_and r11 r16
+169 r13 = select r16 r15 r13
+170 r15 = add_i32 r13 r10
+171 r13 = select r12 r15 r13
+172 r15 = splat B (1.5414283e-44)
+173 r15 = eq_i32 r13 r15
+174 r15 = bit_and r15 r12
+175 r15 = bit_and r12 r15
+176 r9 = bit_and r15 r9
+177 r12 = bit_and r10 r9
+178 r13 = bit_xor r8 r9
+179 r13 = bit_and r15 r13
+180 r16 = add_i32 r12 r10
+181 r11 = bit_xor r8 r13
+182 r14 = bit_and r15 r11
+183 r12 = select r14 r16 r12
+184 r16 = add_i32 r12 r10
+185 r14 = bit_and r9 r11
+186 r12 = select r14 r16 r12
+187 r14 = bit_xor r8 r14
+188 r14 = bit_and r15 r14
+189 r11 = bit_and r14 r11
+190 r11 = bit_or r13 r11
+191 r13 = add_i32 r12 r10
+192 r14 = bit_xor r8 r11
+193 r16 = bit_and r15 r14
+194 r12 = select r16 r13 r12
+195 r13 = add_i32 r12 r10
+196 r16 = bit_and r9 r14
+197 r12 = select r16 r13 r12
+198 r16 = bit_xor r8 r16
+199 r16 = bit_and r15 r16
+200 r14 = bit_and r16 r14
+201 r14 = bit_or r11 r14
+202 r11 = add_i32 r12 r10
+203 r16 = bit_xor r8 r14
+204 r13 = bit_and r15 r16
+205 r12 = select r13 r11 r12
+206 r11 = add_i32 r12 r10
+207 r13 = bit_and r9 r16
+208 r12 = select r13 r11 r12
+209 r13 = bit_xor r8 r13
+210 r13 = bit_and r15 r13
+211 r16 = bit_and r13 r16
+212 r16 = bit_or r14 r16
+213 r14 = add_i32 r12 r10
+214 r13 = bit_xor r8 r16
+215 r11 = bit_and r15 r13
+216 r12 = select r11 r14 r12
+217 r14 = add_i32 r12 r10
+218 r11 = bit_and r9 r13
+219 r12 = select r11 r14 r12
+220 r11 = bit_xor r8 r11
+221 r11 = bit_and r15 r11
+222 r13 = bit_and r11 r13
+223 r13 = bit_or r16 r13
+224 r16 = add_i32 r12 r10
+225 r11 = bit_xor r8 r13
+226 r14 = bit_and r15 r11
+227 r12 = select r14 r16 r12
+228 r16 = add_i32 r12 r10
+229 r14 = bit_and r9 r11
+230 r12 = select r14 r16 r12
+231 r14 = bit_xor r8 r14
+232 r14 = bit_and r15 r14
+233 r11 = bit_and r14 r11
+234 r11 = bit_or r13 r11
+235 r13 = add_i32 r12 r10
+236 r14 = bit_xor r8 r11
+237 r16 = bit_and r15 r14
+238 r12 = select r16 r13 r12
+239 r13 = add_i32 r12 r10
+240 r16 = bit_and r9 r14
+241 r12 = select r16 r13 r12
+242 r16 = bit_xor r8 r16
+243 r16 = bit_and r15 r16
+244 r14 = bit_and r16 r14
+245 r14 = bit_or r11 r14
+246 r11 = add_i32 r12 r10
+247 r16 = bit_xor r8 r14
+248 r13 = bit_and r15 r16
+249 r12 = select r13 r11 r12
+250 r11 = add_i32 r12 r10
+251 r13 = bit_and r9 r16
+252 r12 = select r13 r11 r12
+253 r13 = bit_xor r8 r13
+254 r13 = bit_and r15 r13
+255 r16 = bit_and r13 r16
+256 r16 = bit_or r14 r16
+257 r14 = add_i32 r12 r10
+258 r13 = bit_xor r8 r16
+259 r11 = bit_and r15 r13
+260 r12 = select r11 r14 r12
+261 r14 = add_i32 r12 r10
+262 r11 = bit_and r9 r13
+263 r12 = select r11 r14 r12
+264 r11 = bit_xor r8 r11
+265 r11 = bit_and r15 r11
+266 r13 = bit_and r11 r13
+267 r13 = bit_or r16 r13
+268 r16 = add_i32 r12 r10
+269 r11 = bit_xor r8 r13
+270 r14 = bit_and r15 r11
+271 r12 = select r14 r16 r12
+272 r16 = add_i32 r12 r10
+273 r9 = bit_and r9 r11
+274 r12 = select r9 r16 r12
+275 r9 = bit_xor r8 r9
+276 r9 = bit_and r15 r9
+277 r11 = bit_and r9 r11
+278 r11 = bit_or r13 r11
+279 r10 = add_i32 r12 r10
+280 r11 = bit_xor r8 r11
+281 r11 = bit_and r15 r11
+282 r12 = select r11 r10 r12
+283 r10 = splat 14 (2.8025969e-44)
+284 r10 = eq_i32 r12 r10
+285 r11 = bit_and r10 r11
+286 r11 = bit_and r15 r11
+287 r4 = select r11 r0 r4
+288 r5 = select r11 r1 r5
+289 r6 = select r11 r2 r6
+290 r7 = select r11 r3 r7
+loop:
+291 store32 ptr1 r4
+292 store32 ptr2 r5
+293 store32 ptr3 r6
+294 store32 ptr4 r7
diff --git a/tests/sksl/runtime/SwitchWithLoops.stage b/tests/sksl/runtime/SwitchWithLoops.stage
new file mode 100644
index 0000000..87cb179
--- /dev/null
+++ b/tests/sksl/runtime/SwitchWithLoops.stage
@@ -0,0 +1,48 @@
+uniform half4 colorGreen;
+uniform half4 colorRed;
+bool switch_with_continue_in_loop_0(int x)
+{
+ int val = 0;
+ switch (x)
+ {
+ case 1:for (int i = 0;i < 10; ++i)
+ {
+ ++val;
+ continue;
+ ++val;
+ }
+ default:++val;
+ }
+ return val == 11;
+}
+bool loop_with_break_in_switch_0(int x)
+{
+ int val = 0;
+ for (int i = 0;i < 10; ++i)
+ {
+ switch (x)
+ {
+ case 1:++val;
+ break;
+ default:return false;
+ }
+ ++val;
+ }
+ return val == 20;
+}
+half4 main(float2 coords)
+{
+ int x = int(colorGreen.y);
+ int _0_val = 0;
+ switch (x)
+ {
+ case 1:for (int _1_i = 0;_1_i < 10; ++_1_i)
+ {
+ ++_0_val;
+ break;
+ ++_0_val;
+ }
+ default:++_0_val;
+ }
+ return half4((_0_val == 2 && switch_with_continue_in_loop_0(x)) && loop_with_break_in_switch_0(x) ? colorGreen : colorRed);
+}
diff --git a/tests/sksl/runtime_errors/IllegalStatements.skvm b/tests/sksl/runtime_errors/IllegalStatements.skvm
index abea417..9bfd33d 100644
--- a/tests/sksl/runtime_errors/IllegalStatements.skvm
+++ b/tests/sksl/runtime_errors/IllegalStatements.skvm
@@ -3,6 +3,4 @@
error: 3: discard statement is only permitted in fragment shaders
error: 5: do-while loops are not supported
error: 7: while loops are not supported
-error: 9: switch statements are not supported
-error: 9: function 'switch_stmt' can exit without returning a value
-5 errors
+3 errors