[spirv-reader][ir] Handle value propagation into the continue block.

A value declared in the body of a loop can propagate into the continuing
block without an `OpPhi`, in the case there is only one branch into the
continuing block. This currently breaks the propagation logic as we have
to pass the value as a block parameter and add as an operand to the
terminator instead of pushing through the control instructions.

Bug: 42250952
Change-Id: I348e3c5984907d7d9368054d2d3dd17c685b279b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/244154
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/multi_in_block.cc b/src/tint/lang/core/ir/multi_in_block.cc
index a26ea2d..88d0188 100644
--- a/src/tint/lang/core/ir/multi_in_block.cc
+++ b/src/tint/lang/core/ir/multi_in_block.cc
@@ -75,6 +75,11 @@
     }
 }
 
+void MultiInBlock::AddParam(BlockParam* param) {
+    params_.Push(param);
+    param->SetBlock(this);
+}
+
 void MultiInBlock::AddInboundSiblingBranch(ir::Terminator* node) {
     TINT_ASSERT(node != nullptr);
     inbound_sibling_branches_.Push(node);
diff --git a/src/tint/lang/core/ir/multi_in_block.h b/src/tint/lang/core/ir/multi_in_block.h
index 443e716..ecc0e5b 100644
--- a/src/tint/lang/core/ir/multi_in_block.h
+++ b/src/tint/lang/core/ir/multi_in_block.h
@@ -58,6 +58,10 @@
     /// @param params the params for the block
     void SetParams(std::initializer_list<BlockParam*> params);
 
+    /// Adds the param to the block
+    /// @param param the param for the block
+    void AddParam(BlockParam* param);
+
     /// @returns the params to the block
     VectorRef<BlockParam*> Params() { return params_; }
 
diff --git a/src/tint/lang/spirv/reader/parser/branch_test.cc b/src/tint/lang/spirv/reader/parser/branch_test.cc
index 8767950..d0fc55d 100644
--- a/src/tint/lang/spirv/reader/parser/branch_test.cc
+++ b/src/tint/lang/spirv/reader/parser/branch_test.cc
@@ -2261,6 +2261,64 @@
 )");
 }
 
+TEST_F(SpirvParserTest, Loop_ContinueUseBodyValue) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+        %i32 = OpTypeInt 32 1
+       %bool = OpTypeBool
+        %one = OpConstant %i32 1
+        %two = OpConstant %i32 2
+      %three = OpConstant %i32 3
+       %true = OpConstantTrue %bool
+      %false = OpConstantFalse %bool
+    %ep_type = OpTypeFunction %void
+       %main = OpFunction %void None %ep_type
+         %10 = OpLabel
+               OpBranch %20
+         %20 = OpLabel
+               OpLoopMerge %99 %50 None
+               OpBranchConditional %true %30 %99
+         %30 = OpLabel
+         %40 = OpIAdd %i32 %one %two
+               OpBranch %50
+         %50 = OpLabel
+         %41 = OpIAdd %i32 %40 %40
+               OpBranch %20
+         %99 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    loop [b: $B2, c: $B3] {  # loop_1
+      $B2: {  # body
+        if true [t: $B4, f: $B5] {  # if_1
+          $B4: {  # true
+            %2:i32 = spirv.add<i32> 1i, 2i
+            continue %2  # -> $B3
+          }
+          $B5: {  # false
+            exit_loop  # loop_1
+          }
+        }
+        unreachable
+      }
+      $B3 (%3:i32): {  # continuing
+        %4:i32 = spirv.add<i32> %3, %3
+        next_iteration  # -> $B2
+      }
+    }
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvParserTest, Loop_Body_Switch) {
     EXPECT_IR(R"(
                OpCapability Shader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 21605a1..872c48b 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -725,10 +725,42 @@
                 break;
             }
 
-            auto* ctrl = blk->Parent();
-
             TINT_ASSERT(blk->Terminator());
 
+            core::ir::ControlInstruction* ctrl = tint::Switch(
+                blk->Terminator(),  //
+                [&](core::ir::ExitIf* ei) { return ei->If(); },
+                [&](core::ir::ExitSwitch* es) { return es->Switch(); },
+                [&](core::ir::ExitLoop* el) { return el->Loop(); },
+                [&](core::ir::Continue* cont) {
+                    // The propagation is going through a `continue`. This means
+                    // this is the only path to the continuing block, but it also
+                    // means we're current in the continuing block. We can't do
+                    // normal propagation here, we have to pass a block param
+                    // instead.
+
+                    auto* param = b_.BlockParam(src->Type());
+
+                    // We're in the continuing block, so make the block param available in the
+                    // scope.
+                    id_stack_.back().insert(id);
+
+                    auto* loop = cont->Loop();
+                    loop->Continuing()->AddParam(param);
+
+                    cont->PushOperand(src);
+
+                    // Set the src to the param so we return param as the new
+                    // value.
+                    src = param;
+                    return nullptr;
+                },  //
+                TINT_ICE_ON_NO_MATCH);
+
+            if (!ctrl) {
+                break;
+            }
+
             // Add ourselves as part of the terminator return value
             blk->Terminator()->PushOperand(src);
             // Add a new result to the control instruction
@@ -1441,8 +1473,25 @@
             auto* loop = StopWalkingAt(src.id())->As<core::ir::Loop>();
             TINT_ASSERT(loop);
 
+            auto continue_id = loop_merge_inst->GetSingleWordInOperand(1);
+            if (continue_id != src.id()) {
+                const auto& bb_continue = current_spirv_function_->FindBlock(continue_id);
+
+                // Emit the continuing block.
+                EmitBlockParent(loop->Continuing(), *bb_continue);
+            }
+
+            // Add the body terminator after processing the continuing block in
+            // case we've propagated values through the continue.
             if (!loop->Body()->Terminator()) {
-                loop->Body()->Append(b_.Continue(loop));
+                if (loop->Continuing()->Params().IsEmpty()) {
+                    loop->Body()->Append(b_.Continue(loop));
+                } else {
+                    loop->Body()->Append(b_.Unreachable());
+                }
+            }
+            if (!loop->Continuing()->Terminator()) {
+                loop->Continuing()->Append(b_.NextIteration(loop));
             }
 
             current_blocks_.erase(loop->Body());
@@ -2037,15 +2086,6 @@
         walk_stop_blocks_.insert({merge_id, loop});
         if (continue_id != header_id) {
             walk_stop_blocks_.insert({continue_id, loop});
-
-            const auto& bb_continue = current_spirv_function_->FindBlock(continue_id);
-
-            // Emit the continuing block.
-            EmitBlockParent(loop->Continuing(), *bb_continue);
-        }
-
-        if (!loop->Continuing()->Terminator()) {
-            loop->Continuing()->Append(b_.NextIteration(loop));
         }
 
         // The remainder of the loop body will process when we hit the