[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