[ir] Track BreakIf as a loop exit

This fixes an issue where the SPIR-V writer would not add OpPhi
operands for results coming from a break_if instruction, as it was not
considered to be an 'exit' from a loop.

Change-Id: I0723d209d078e52e969e79986e4adbfbdde3d885
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/190463
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/break_if.cc b/src/tint/lang/core/ir/break_if.cc
index efb2990..72ad55b 100644
--- a/src/tint/lang/core/ir/break_if.cc
+++ b/src/tint/lang/core/ir/break_if.cc
@@ -55,6 +55,7 @@
 
     if (loop_) {
         loop_->Body()->AddInboundSiblingBranch(this);
+        SetControlInstruction(loop_);
     }
 }
 
@@ -72,6 +73,7 @@
         loop_->Body()->RemoveInboundSiblingBranch(this);
     }
     loop_ = loop;
+    SetControlInstruction(loop);
     if (loop) {
         loop->Body()->AddInboundSiblingBranch(this);
     }
diff --git a/src/tint/lang/core/ir/break_if.h b/src/tint/lang/core/ir/break_if.h
index 38bf9d8..47533ec 100644
--- a/src/tint/lang/core/ir/break_if.h
+++ b/src/tint/lang/core/ir/break_if.h
@@ -30,7 +30,7 @@
 
 #include <string>
 
-#include "src/tint/lang/core/ir/terminator.h"
+#include "src/tint/lang/core/ir/exit.h"
 #include "src/tint/lang/core/ir/value.h"
 #include "src/tint/utils/containers/const_propagating_ptr.h"
 #include "src/tint/utils/rtti/castable.h"
@@ -42,8 +42,8 @@
 
 namespace tint::core::ir {
 
-/// A break-if iteration instruction.
-class BreakIf final : public Castable<BreakIf, Terminator> {
+/// A break-if terminator instruction.
+class BreakIf final : public Castable<BreakIf, Exit> {
   public:
     /// The offset in Operands() for the condition
     static constexpr size_t kConditionOperandOffset = 0;
diff --git a/src/tint/lang/core/ir/break_if_test.cc b/src/tint/lang/core/ir/break_if_test.cc
index d472c47..c32f00c 100644
--- a/src/tint/lang/core/ir/break_if_test.cc
+++ b/src/tint/lang/core/ir/break_if_test.cc
@@ -113,5 +113,20 @@
     EXPECT_EQ(0u, args.Length());
 }
 
+TEST_F(IR_BreakIfTest, SetLoop) {
+    auto* loop1 = b.Loop();
+    auto* loop2 = b.Loop();
+    auto* cond = b.Constant(true);
+    auto* arg1 = b.Constant(1_u);
+    auto* arg2 = b.Constant(2_u);
+
+    auto* brk = b.BreakIf(loop1, cond, arg1, arg2);
+    EXPECT_THAT(loop1->Exits(), testing::ElementsAre(brk));
+
+    brk->SetLoop(loop2);
+    EXPECT_TRUE(loop1->Exits().IsEmpty());
+    EXPECT_THAT(loop2->Exits(), testing::ElementsAre(brk));
+}
+
 }  // namespace
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/spirv/writer/loop_test.cc b/src/tint/lang/spirv/writer/loop_test.cc
index a5640fa..1631549 100644
--- a/src/tint/lang/spirv/writer/loop_test.cc
+++ b/src/tint/lang/spirv/writer/loop_test.cc
@@ -664,5 +664,69 @@
 )");
 }
 
+TEST_F(SpirvWriterTest, Loop_ExitValue_BreakIf) {
+    auto* func = b.Function("foo", ty.i32());
+    b.Append(func->Block(), [&] {
+        auto* result = b.InstructionResult(ty.i32());
+        auto* loop = b.Loop();
+        loop->SetResults(Vector{result});
+        b.Append(loop->Body(), [&] {  //
+            auto* if_ = b.If(false);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop, 1_i);
+            });
+            b.Continue(loop);
+
+            b.Append(loop->Continuing(), [&] {  //
+                b.BreakIf(loop, true, Empty, 42_i);
+            });
+        });
+        b.Return(func, result);
+    });
+
+    EXPECT_EQ(IR(), R"(
+%foo = func():i32 {
+  $B1: {
+    %2:i32 = loop [b: $B2, c: $B3] {  # loop_1
+      $B2: {  # body
+        if false [t: $B4] {  # if_1
+          $B4: {  # true
+            exit_loop 1i  # loop_1
+          }
+        }
+        continue  # -> $B3
+      }
+      $B3: {  # continuing
+        break_if true exit_loop: [ 42i ]  # -> [t: exit_loop loop_1, f: $B2]
+      }
+    }
+    ret %2
+  }
+}
+)");
+
+    ASSERT_TRUE(Generate()) << Error() << output_;
+    EXPECT_INST(R"(
+          %4 = OpLabel
+               OpBranch %7
+          %7 = OpLabel
+               OpLoopMerge %8 %6 None
+               OpBranch %5
+          %5 = OpLabel
+               OpSelectionMerge %9 None
+               OpBranchConditional %false %10 %9
+         %10 = OpLabel
+               OpBranch %8
+          %9 = OpLabel
+               OpBranch %6
+          %6 = OpLabel
+               OpBranchConditional %true %8 %7
+          %8 = OpLabel
+         %14 = OpPhi %int %int_42 %6 %int_1 %10
+               OpReturnValue %14
+               OpFunctionEnd
+)");
+}
+
 }  // namespace
 }  // namespace tint::spirv::writer