[ir] Add switch validation.
This CL adds IR validation for switch conditions and some basic case
requirements.
Change-Id: Ib7e549a7b206f790d5a7d3e22e59fbd8b1d932af
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/195074
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 028ad64..4e37b23 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -1336,10 +1336,27 @@
}
void Validator::CheckSwitch(const Switch* s) {
+ CheckOperandNotNull(s, s->Condition(), If::kConditionOperandOffset);
+
+ if (s->Condition() && !s->Condition()->Type()->is_integer_scalar()) {
+ AddError(s, Switch::kConditionOperandOffset) << "condition type must be an integer scalar";
+ }
+
tasks_.Push([this] { control_stack_.Pop(); });
+ bool found_default = false;
for (auto& cse : s->Cases()) {
QueueBlock(cse.block);
+
+ for (const auto& sel : cse.selectors) {
+ if (sel.IsDefault()) {
+ found_default = true;
+ }
+ }
+ }
+
+ if (!found_default) {
+ AddError(s) << "missing default case for switch";
}
tasks_.Push([this, s] { control_stack_.Push(s); });
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index aaba721..3e36705 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -2346,7 +2346,7 @@
b.ExitIf(if_outer);
});
- auto* c = b.Case(switch_inner, {b.Constant(1_i)});
+ auto* c = b.Case(switch_inner, {b.Constant(1_i), nullptr});
b.Append(c, [&] { b.ExitIf(if_outer); });
b.Append(f->Block(), [&] {
@@ -2366,15 +2366,15 @@
^^^
:5:9 note: first control instruction jumped
- switch 1i [c: (1i, $B3)] { # switch_1
- ^^^^^^^^^^^^^^^^^^^^^^^^
+ switch 1i [c: (1i default, $B3)] { # switch_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
if true [t: $B2] { # if_1
$B2: { # true
- switch 1i [c: (1i, $B3)] { # switch_1
+ switch 1i [c: (1i default, $B3)] { # switch_1
$B3: { # case
exit_if # if_1
}
@@ -2441,7 +2441,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* def = b.DefaultCase(switch_);
def->Append(b.ExitSwitch(switch_));
@@ -2455,7 +2455,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_NullSwitch) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* def = b.DefaultCase(switch_);
def->Append(mod.allocators.instructions.Create<ExitSwitch>(nullptr));
@@ -2479,7 +2479,7 @@
note: # Disassembly
%my_func = func():void {
$B1: {
- switch true [c: (default, $B2)] { # switch_1
+ switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
exit_switch # undef
}
@@ -2491,7 +2491,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_LessOperandsThenSwitchParams) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* r1 = b.InstructionResult(ty.i32());
auto* r2 = b.InstructionResult(ty.f32());
@@ -2517,13 +2517,13 @@
^^^
:3:5 note: 'switch' declared here
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
exit_switch 1i # switch_1
}
@@ -2535,7 +2535,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_MoreOperandsThenSwitchParams) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* r1 = b.InstructionResult(ty.i32());
auto* r2 = b.InstructionResult(ty.f32());
switch_->SetResults(Vector{r1, r2});
@@ -2560,13 +2560,13 @@
^^^
:3:5 note: 'switch' declared here
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
exit_switch 1i, 2.0f, 3i # switch_1
}
@@ -2578,7 +2578,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_WithResult) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* r1 = b.InstructionResult(ty.i32());
auto* r2 = b.InstructionResult(ty.f32());
switch_->SetResults(Vector{r1, r2});
@@ -2596,7 +2596,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_IncorrectResultType) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* r1 = b.InstructionResult(ty.i32());
auto* r2 = b.InstructionResult(ty.f32());
switch_->SetResults(Vector{r1, r2});
@@ -2622,13 +2622,13 @@
^^^
:3:13 note: %3 declared here
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- %2:i32, %3:f32 = switch true [c: (default, $B2)] { # switch_1
+ %2:i32, %3:f32 = switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
exit_switch 1i, 2i # switch_1
}
@@ -2640,7 +2640,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_NotInParentSwitch) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* f = b.Function("my_func", ty.void_());
@@ -2668,7 +2668,7 @@
note: # Disassembly
%my_func = func():void {
$B1: {
- switch true [c: (default, $B2)] { # switch_1
+ switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
ret
}
@@ -2694,7 +2694,7 @@
// }
// break;
// }
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* f = b.Function("my_func", ty.void_());
@@ -2717,11 +2717,11 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_InvalidJumpOverSwitch) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* def = b.DefaultCase(switch_);
b.Append(def, [&] {
- auto* inner = b.Switch(false);
+ auto* inner = b.Switch(0_i);
b.ExitSwitch(switch_);
auto* inner_def = b.DefaultCase(inner);
@@ -2747,15 +2747,15 @@
^^^
:5:9 note: first control instruction jumped
- switch false [c: (default, $B3)] { # switch_2
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ switch 0i [c: (default, $B3)] { # switch_2
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- switch true [c: (default, $B2)] { # switch_1
+ switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
- switch false [c: (default, $B3)] { # switch_2
+ switch 0i [c: (default, $B3)] { # switch_2
$B3: { # case
exit_switch # switch_1
}
@@ -2770,7 +2770,7 @@
}
TEST_F(IR_ValidatorTest, ExitSwitch_InvalidJumpOverLoop) {
- auto* switch_ = b.Switch(true);
+ auto* switch_ = b.Switch(1_i);
auto* def = b.DefaultCase(switch_);
b.Append(def, [&] {
@@ -2804,7 +2804,7 @@
note: # Disassembly
%my_func = func():void {
$B1: {
- switch true [c: (default, $B2)] { # switch_1
+ switch 1i [c: (default, $B2)] { # switch_1
$B2: { # case
loop [b: $B3] { # loop_1
$B3: { # body
@@ -4068,7 +4068,7 @@
loop->Continuing()->Append(b.NextIteration(loop));
b.Append(loop->Body(), [&] {
- auto* inner = b.Switch(false);
+ auto* inner = b.Switch(1_i);
b.ExitLoop(loop);
auto* inner_def = b.DefaultCase(inner);
@@ -4094,15 +4094,15 @@
^^^
:5:9 note: first control instruction jumped
- switch false [c: (default, $B4)] { # switch_1
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ switch 1i [c: (default, $B4)] { # switch_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
loop [b: $B2, c: $B3] { # loop_1
$B2: { # body
- switch false [c: (default, $B4)] { # switch_1
+ switch 1i [c: (default, $B4)] { # switch_1
$B4: { # case
exit_loop # loop_1
}
@@ -5042,5 +5042,131 @@
testing::Values(RefTypeBuilder<i32>,
RefTypeBuilder<bool>,
RefTypeBuilder<vec4<f32>>)));
+
+TEST_F(IR_ValidatorTest, Switch_NoCondition) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ auto* s = b.ir.allocators.instructions.Create<ir::Switch>();
+ f->Block()->Append(s);
+ b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
+ f->Block()->Append(b.Return(f));
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(), R"(error: switch: operand is undefined
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ switch undef [c: (default, $B2)] { # switch_1
+ $B2: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Switch_ConditionPointer) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ b.Append(f->Block(), [&] {
+ auto* s = b.Switch(b.Var("a", b.Zero<i32>()));
+ b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(error: switch: condition type must be an integer scalar
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ %a:ptr<function, i32, read_write> = var, 0i
+ switch %a [c: (default, $B2)] { # switch_1
+ $B2: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Switch_NoCases) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ b.Append(f->Block(), [&] {
+ b.Switch(1_i);
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:5 error: switch: missing default case for switch
+ switch 1i [] { # switch_1
+ ^^^^^^^^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ switch 1i [] { # switch_1
+ }
+ ret
+ }
+}
+)");
+}
+
+TEST_F(IR_ValidatorTest, Switch_NoDefaultCase) {
+ auto* f = b.Function("my_func", ty.void_());
+
+ b.Append(f->Block(), [&] {
+ auto* s = b.Switch(1_i);
+ b.Append(b.Case(s, {b.Constant(0_i)}), [&] { b.ExitSwitch(s); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:5 error: switch: missing default case for switch
+ switch 1i [c: (0i, $B2)] { # switch_1
+ ^^^^^^^^^^^^^^^^^^^^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ switch 1i [c: (0i, $B2)] { # switch_1
+ $B2: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
} // namespace
} // namespace tint::core::ir