[spirv-reader][ir] Handle propagation through multiple exits.

It's possible to setup a block where a value which needs to be
propagated dominates multiple blocks which jump to a continuing block.
In this case, we need to pass the propagated value through all of the
exits instead of just a single exit.

Change-Id: I8e4c37f37340899159a3d838bb2f38287efa924f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245334
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/spirv/reader/parser/branch_test.cc b/src/tint/lang/spirv/reader/parser/branch_test.cc
index a10d2df..566c562 100644
--- a/src/tint/lang/spirv/reader/parser/branch_test.cc
+++ b/src/tint/lang/spirv/reader/parser/branch_test.cc
@@ -503,6 +503,78 @@
 )");
 }
 
+TEST_F(SpirvParserTest, BranchConditional_HoistingMultiExit) {
+    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
+       %true = OpConstantTrue %bool
+    %ep_type = OpTypeFunction %void
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpBranch %10
+         %10 = OpLabel
+               OpLoopMerge %merge %cont None
+               OpBranch %20
+         %20 = OpLabel
+        %foo = OpIAdd %i32 %one %two
+               OpSelectionMerge %50 None
+               OpBranchConditional %true %30 %40
+         %30 = OpLabel
+               OpBranch %merge
+         %40 = OpLabel
+               OpBranch %merge
+         %50 = OpLabel
+               OpUnreachable
+       %cont = OpLabel
+               OpBranch %10
+      %merge = OpLabel
+       %foo2 = OpCopyObject %i32 %foo
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = loop [b: $B2, c: $B3] {  # loop_1
+      $B2: {  # body
+        %3:i32 = spirv.add<i32> 1i, 2i
+        if true [t: $B4, f: $B5] {  # if_1
+          $B4: {  # true
+            exit_loop %3  # loop_1
+          }
+          $B5: {  # false
+            exit_loop %3  # loop_1
+          }
+        }
+        if true [t: $B6, f: $B7] {  # if_2
+          $B6: {  # true
+            %4:i32 = let %3
+            ret
+          }
+          $B7: {  # false
+            unreachable
+          }
+        }
+        unreachable
+      }
+      $B3: {  # continuing
+        next_iteration  # -> $B2
+      }
+    }
+    %5:i32 = let %2
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvParserTest, BranchConditional_HoistingIntoNested) {
     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 893370d..7801b9c 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -750,19 +750,20 @@
 
                     cont->PushOperand(src);
 
-                    // Set the src to the param so we return param as the new
-                    // value.
+                    // Set `src` as the `param` so it's returned as the new value
                     src = param;
                     return nullptr;
-                },  //
+                },                                                      //
+                [&](core::ir::Unreachable*) { return blk->Parent(); },  //
                 TINT_ICE_ON_NO_MATCH);
-
             if (!ctrl) {
                 break;
             }
 
-            // Add ourselves as part of the terminator return value
-            blk->Terminator()->PushOperand(src);
+            for (auto& exit : ctrl->Exits()) {
+                exit->PushOperand(src);
+            }
+
             // Add a new result to the control instruction
             ctrl->AddResult(b_.InstructionResult(src->Type()));
             // The source instruction is now the control result we just inserted