[ir] Validate loop body with params has initializer
When a loop body block has incoming parameters, there must be an
initializer block in that loop to provide the values for the first
iteration.
Change-Id: Ie22ca8a2b67969deb6ec3e20c6bb781a1cda403d
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190281
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index f42eeb5..f24b8eb 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -362,6 +362,10 @@
/// @param l the loop to validate
void CheckLoop(const Loop* l);
+ /// Validates the loop body block
+ /// @param l the loop to validate
+ void CheckLoopBody(const Loop* l);
+
/// Validates the loop continuing block
/// @param l the loop to validate
void CheckLoopContinuing(const Loop* l);
@@ -1217,13 +1221,25 @@
});
}
- tasks_.Push([this, l] { BeginBlock(l->Body()); });
+ tasks_.Push([this, l] {
+ CheckLoopBody(l);
+ BeginBlock(l->Body());
+ });
if (!l->Initializer()->IsEmpty()) {
tasks_.Push([this, l] { BeginBlock(l->Initializer()); });
}
tasks_.Push([this, l] { control_stack_.Push(l); });
}
+void Validator::CheckLoopBody(const Loop* loop) {
+ // If the body block has parameters, there must be an initializer block.
+ if (!loop->Body()->Params().IsEmpty()) {
+ if (!loop->HasInitializer()) {
+ AddError(loop) << "loop with body block parameters must have an initializer";
+ }
+ }
+}
+
void Validator::CheckLoopContinuing(const Loop* loop) {
if (!loop->HasContinuing()) {
return;
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 39029fb..0ef9fb9 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -528,6 +528,7 @@
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
+ b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.ExitLoop(l); });
b.Return(f);
@@ -538,15 +539,18 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:4:12 error: destroyed parameter found in block parameter list
- $B2 (%my_param:f32): { # body
+ R"(:7:12 error: destroyed parameter found in block parameter list
+ $B3 (%my_param:f32): { # body
^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%my_param:f32): { # body
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration undef # -> $B3
+ }
+ $B3 (%my_param:f32): { # body
exit_loop # loop_1
}
}
@@ -562,6 +566,7 @@
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
+ b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.ExitLoop(l); });
b.Return(f);
@@ -572,15 +577,18 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:4:12 error: block parameter has nullptr parent block
- $B2 (%my_param:f32): { # body
+ R"(:7:12 error: block parameter has nullptr parent block
+ $B3 (%my_param:f32): { # body
^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%my_param:f32): { # body
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration undef # -> $B3
+ }
+ $B3 (%my_param:f32): { # body
exit_loop # loop_1
}
}
@@ -596,6 +604,7 @@
auto* p = b.BlockParam("my_param", ty.f32());
b.Append(f->Block(), [&] {
auto* l = b.Loop();
+ b.Append(l->Initializer(), [&] { b.NextIteration(l, nullptr); });
l->Body()->SetParams({p});
b.Append(l->Body(), [&] { b.Continue(l, p); });
l->Continuing()->SetParams({p});
@@ -606,23 +615,26 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:4:12 error: block parameter has incorrect parent block
- $B2 (%my_param:f32): { # body
+ R"(:7:12 error: block parameter has incorrect parent block
+ $B3 (%my_param:f32): { # body
^^^^^^^^^
-:7:7 note: parent block declared here
- $B3 (%my_param:f32): { # continuing
+:10:7 note: parent block declared here
+ $B4 (%my_param:f32): { # continuing
^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- loop [b: $B2, c: $B3] { # loop_1
- $B2 (%my_param:f32): { # body
- continue %my_param # -> $B3
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ next_iteration undef # -> $B3
}
- $B3 (%my_param:f32): { # continuing
- next_iteration %my_param # -> $B2
+ $B3 (%my_param:f32): { # body
+ continue %my_param # -> $B4
+ }
+ $B4 (%my_param:f32): { # continuing
+ next_iteration %my_param # -> $B3
}
}
ret
@@ -3179,6 +3191,40 @@
ASSERT_EQ(res, Success);
}
+TEST_F(IR_ValidatorTest, LoopBodyParamsWithoutInitializer) {
+ auto* f = b.Function("my_func", ty.void_());
+ b.Append(f->Block(), [&] {
+ auto* loop = b.Loop();
+ loop->Body()->SetParams({b.BlockParam<i32>(), b.BlockParam<i32>()});
+ b.Append(loop->Body(), [&] { b.ExitLoop(loop); });
+ b.Return(f);
+ });
+
+ auto res = ir::Validate(mod);
+ ASSERT_NE(res, Success);
+ EXPECT_EQ(res.Failure().reason.Str(),
+ R"(:3:5 error: loop: loop with body block parameters must have an initializer
+ loop [b: $B2] { # loop_1
+ ^^^^^^^^^^^^^
+
+:2:3 note: in block
+ $B1: {
+ ^^^
+
+note: # Disassembly
+%my_func = func():void {
+ $B1: {
+ loop [b: $B2] { # loop_1
+ $B2 (%2:i32, %3:i32): { # body
+ exit_loop # loop_1
+ }
+ }
+ ret
+ }
+}
+)");
+}
+
TEST_F(IR_ValidatorTest, ContinuingUseValueBeforeContinue) {
auto* f = b.Function("my_func", ty.void_());
auto* value = b.Let("value", 1_i);
@@ -3306,6 +3352,7 @@
b.Append(f->Block(), [&] {
auto* loop = b.Loop();
loop->Body()->SetParams({b.BlockParam<i32>(), b.BlockParam<i32>()});
+ b.Append(loop->Initializer(), [&] { b.NextIteration(loop, nullptr, nullptr); });
b.Append(loop->Body(), [&] { b.Continue(loop); });
b.Append(loop->Continuing(), [&] { b.BreakIf(loop, true, Empty, Empty); });
b.Return(f);
@@ -3314,27 +3361,30 @@
auto res = ir::Validate(mod);
ASSERT_NE(res, Success);
EXPECT_EQ(res.Failure().reason.Str(),
- R"(:8:9 error: break_if: provides 0 values but 'loop' block $B2 expects 2 values
- break_if true # -> [t: exit_loop loop_1, f: $B2]
+ R"(:11:9 error: break_if: provides 0 values but 'loop' block $B3 expects 2 values
+ break_if true # -> [t: exit_loop loop_1, f: $B3]
^^^^^^^^^^^^^
-:7:7 note: in block
- $B3: { # continuing
+:10:7 note: in block
+ $B4: { # continuing
^^^
-:4:7 note: 'loop' block $B2 declared here
- $B2 (%2:i32, %3:i32): { # body
+:7:7 note: 'loop' block $B3 declared here
+ $B3 (%2:i32, %3:i32): { # body
^^^^^^^^^^^^^^^^^^^^
note: # Disassembly
%my_func = func():void {
$B1: {
- loop [b: $B2, c: $B3] { # loop_1
- $B2 (%2:i32, %3:i32): { # body
- continue # -> $B3
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ next_iteration undef, undef # -> $B3
}
- $B3: { # continuing
- break_if true # -> [t: exit_loop loop_1, f: $B2]
+ $B3 (%2:i32, %3:i32): { # body
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ break_if true # -> [t: exit_loop loop_1, f: $B3]
}
}
ret
@@ -3349,6 +3399,8 @@
auto* loop = b.Loop();
loop->Body()->SetParams(
{b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Initializer(),
+ [&] { b.NextIteration(loop, nullptr, nullptr, nullptr, nullptr); });
b.Append(loop->Body(), [&] { b.Continue(loop); });
b.Append(loop->Continuing(),
[&] { b.BreakIf(loop, true, b.Values(1_i, 2_i, 3_f, false), Empty); });
@@ -3359,39 +3411,42 @@
ASSERT_NE(res, Success);
EXPECT_EQ(
res.Failure().reason.Str(),
- R"(:8:45 error: break_if: operand with type 'i32' does not match 'loop' block $B2 target type 'f32'
- break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+ R"(:11:45 error: break_if: operand with type 'i32' does not match 'loop' block $B3 target type 'f32'
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B3]
^^
-:7:7 note: in block
- $B3: { # continuing
+:10:7 note: in block
+ $B4: { # continuing
^^^
-:4:20 note: %3 declared here
- $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+:7:20 note: %3 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
^^
-:8:49 error: break_if: operand with type 'f32' does not match 'loop' block $B2 target type 'u32'
- break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+:11:49 error: break_if: operand with type 'f32' does not match 'loop' block $B3 target type 'u32'
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B3]
^^^^
-:7:7 note: in block
- $B3: { # continuing
+:10:7 note: in block
+ $B4: { # continuing
^^^
-:4:28 note: %4 declared here
- $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+:7:28 note: %4 declared here
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
^^
note: # Disassembly
%my_func = func():void {
$B1: {
- loop [b: $B2, c: $B3] { # loop_1
- $B2 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
- continue # -> $B3
+ loop [i: $B2, b: $B3, c: $B4] { # loop_1
+ $B2: { # initializer
+ next_iteration undef, undef, undef, undef # -> $B3
}
- $B3: { # continuing
- break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B2]
+ $B3 (%2:i32, %3:f32, %4:u32, %5:bool): { # body
+ continue # -> $B4
+ }
+ $B4: { # continuing
+ break_if true next_iteration: [ 1i, 2i, 3.0f ] # -> [t: exit_loop loop_1, f: $B3]
}
}
ret
@@ -3406,6 +3461,8 @@
auto* loop = b.Loop();
loop->Body()->SetParams(
{b.BlockParam<i32>(), b.BlockParam<f32>(), b.BlockParam<u32>(), b.BlockParam<bool>()});
+ b.Append(loop->Initializer(),
+ [&] { b.NextIteration(loop, nullptr, nullptr, nullptr, nullptr); });
b.Append(loop->Body(), [&] { b.Continue(loop); });
b.Append(loop->Continuing(),
[&] { b.BreakIf(loop, true, b.Values(1_i, 2_f, 3_u, false), Empty); });
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
index e1a9da3..e774858 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
@@ -566,6 +566,9 @@
func->SetParams({cond, idx_a, idx_b});
b.Append(func->Block(), [&] { //
auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] { //
+ b.NextIteration(loop, b.Splat(arr->Type(), 0_i));
+ });
loop->Body()->SetParams({arr});
b.Append(loop->Body(), [&] {
auto* if_ = b.If(cond);
@@ -583,14 +586,17 @@
auto* src = R"(
%func = func(%2:bool, %3:i32, %4:i32):i32 {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%5:array<i32, 4>): { # body
- if %2 [t: $B3, f: $B4] { # if_1
- $B3: { # true
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration array<i32, 4>(0i) # -> $B3
+ }
+ $B3 (%5:array<i32, 4>): { # body
+ if %2 [t: $B4, f: $B5] { # if_1
+ $B4: { # true
%6:i32 = access %5, %3
ret %6
}
- $B4: { # false
+ $B5: { # false
%7:i32 = access %5, %4
ret %7
}
@@ -607,16 +613,19 @@
auto* expect = R"(
%func = func(%2:bool, %3:i32, %4:i32):i32 {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%5:array<i32, 4>): { # body
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration array<i32, 4>(0i) # -> $B3
+ }
+ $B3 (%5:array<i32, 4>): { # body
%6:ptr<function, array<i32, 4>, read_write> = var, %5
- if %2 [t: $B3, f: $B4] { # if_1
- $B3: { # true
+ if %2 [t: $B4, f: $B5] { # if_1
+ $B4: { # true
%7:ptr<function, i32, read_write> = access %6, %3
%8:i32 = load %7
ret %8
}
- $B4: { # false
+ $B5: { # false
%9:ptr<function, i32, read_write> = access %6, %4
%10:i32 = load %9
ret %10
@@ -637,7 +646,8 @@
TEST_F(SpirvWriter_VarForDynamicIndexTest,
MultipleAccessesToBlockParam_FromDifferentBlocks_WithLeadingConstantIndex) {
- auto* arr = b.BlockParam(ty.array(ty.array<i32, 4>(), 4));
+ auto* inner_ty = ty.array<i32, 4>();
+ auto* arr = b.BlockParam(ty.array(inner_ty, 4));
auto* cond = b.FunctionParam(ty.bool_());
auto* idx_a = b.FunctionParam(ty.i32());
auto* idx_b = b.FunctionParam(ty.i32());
@@ -645,6 +655,9 @@
func->SetParams({cond, idx_a, idx_b});
b.Append(func->Block(), [&] { //
auto* loop = b.Loop();
+ b.Append(loop->Initializer(), [&] { //
+ b.NextIteration(loop, b.Splat(arr->Type(), b.Splat(inner_ty, 0_i)));
+ });
loop->Body()->SetParams({arr});
b.Append(loop->Body(), [&] {
auto* if_ = b.If(cond);
@@ -662,14 +675,17 @@
auto* src = R"(
%func = func(%2:bool, %3:i32, %4:i32):i32 {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%5:array<array<i32, 4>, 4>): { # body
- if %2 [t: $B3, f: $B4] { # if_1
- $B3: { # true
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration array<array<i32, 4>, 4>(array<i32, 4>(0i)) # -> $B3
+ }
+ $B3 (%5:array<array<i32, 4>, 4>): { # body
+ if %2 [t: $B4, f: $B5] { # if_1
+ $B4: { # true
%6:i32 = access %5, 0u, %3
ret %6
}
- $B4: { # false
+ $B5: { # false
%7:i32 = access %5, 0u, %4
ret %7
}
@@ -686,17 +702,20 @@
auto* expect = R"(
%func = func(%2:bool, %3:i32, %4:i32):i32 {
$B1: {
- loop [b: $B2] { # loop_1
- $B2 (%5:array<array<i32, 4>, 4>): { # body
+ loop [i: $B2, b: $B3] { # loop_1
+ $B2: { # initializer
+ next_iteration array<array<i32, 4>, 4>(array<i32, 4>(0i)) # -> $B3
+ }
+ $B3 (%5:array<array<i32, 4>, 4>): { # body
%6:array<i32, 4> = access %5, 0u
%7:ptr<function, array<i32, 4>, read_write> = var, %6
- if %2 [t: $B3, f: $B4] { # if_1
- $B3: { # true
+ if %2 [t: $B4, f: $B5] { # if_1
+ $B4: { # true
%8:ptr<function, i32, read_write> = access %7, %3
%9:i32 = load %8
ret %9
}
- $B4: { # false
+ $B5: { # false
%10:ptr<function, i32, read_write> = access %7, %4
%11:i32 = load %10
ret %11