[spirv-reader][ir] Fix propagation when jumping if controls.

It's possible to jump the exit of an `if` and go straight to the exit of
a `loop` (a `break`). In that case, if we're propagating something from
inside the `if` we do not pass the result up through the `if` but
instead pass it directly through the `exit_loop` and return from the
`loop`. This is done by checking if the control has any exits before
attaching the propagated result.

Bug: 422539666
Change-Id: I03484a38c2758c7d39c0fe2310c649042e6e40b6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/245757
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/parser/branch_test.cc b/src/tint/lang/spirv/reader/parser/branch_test.cc
index a0d7b17..f1d14c9 100644
--- a/src/tint/lang/spirv/reader/parser/branch_test.cc
+++ b/src/tint/lang/spirv/reader/parser/branch_test.cc
@@ -566,6 +566,83 @@
 )");
 }
 
+TEST_F(SpirvParserTest, BranchConditional_HoistingMultiExit_FromMiddle) {
+    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 %99 %80 None
+               OpBranch %20
+         %20 = OpLabel
+               OpSelectionMerge %50 None
+               OpBranchConditional %true %30 %30
+         %30 = OpLabel
+        %foo = OpIAdd %i32 %one %two
+               OpSelectionMerge %60 None
+               OpBranchConditional %true %65 %70
+         %65 = OpLabel
+               OpBranch %99
+         %70 = OpLabel
+               OpBranch %99
+         %60 = OpLabel
+               OpUnreachable
+         %50 = OpLabel
+               OpUnreachable
+         %80 = OpLabel
+               OpBranch %10
+         %99 = 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:bool = or true, true
+        if %3 [t: $B4, f: $B5] {  # if_1
+          $B4: {  # true
+            %4:i32 = spirv.add<i32> 1i, 2i
+            if true [t: $B6, f: $B7] {  # if_2
+              $B6: {  # true
+                exit_loop %4  # loop_1
+              }
+              $B7: {  # false
+                exit_loop %4  # loop_1
+              }
+            }
+            unreachable
+          }
+          $B5: {  # 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 84a3a22..54be1de 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -839,7 +839,8 @@
                         // 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) {
@@ -851,6 +852,15 @@
                 }
             }
 
+            // If this control has no exits, then we don't need to add the result through the
+            // control as we're jumping over the control to its parent control. (This is an
+            // `if` inside a `loop` where the `if` is doing a `break`).
+            if (ctrl->Exits().IsEmpty()) {
+                TINT_ASSERT(ctrl->Is<core::ir::If>());
+                blk = ctrl->Block();
+                continue;
+            }
+
             // 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