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