[tint][ir] Add ControlInstruction

To sit between ir::Instruction and [ir::If, ir::Loop, ir::Switch].

All of these instructions are special in that they hold their own
blocks, and they do not have block arguments.

Add a Parent() accessor on blocks, so there's a way to get the
FlowControlInstruction from the block.

Bug: tint:1718
Change-Id: Ibd1c6fc1a1d0153aa58891e9c4f5f3d95b0061d7
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/136281
Reviewed-by: dan sinclair <dsinclair@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index c33d81d..44e60a3 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -1236,6 +1236,8 @@
       "ir/construct.h",
       "ir/continue.cc",
       "ir/continue.h",
+      "ir/control_instruction.cc",
+      "ir/control_instruction.h",
       "ir/convert.cc",
       "ir/convert.h",
       "ir/disassembler.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index b1209ec..caef246 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -743,6 +743,8 @@
     ir/construct.h
     ir/continue.cc
     ir/continue.h
+    ir/control_instruction.cc
+    ir/control_instruction.h
     ir/convert.cc
     ir/convert.h
     ir/disassembler.cc
diff --git a/src/tint/ir/block.h b/src/tint/ir/block.h
index 631473b..1da6fb9 100644
--- a/src/tint/ir/block.h
+++ b/src/tint/ir/block.h
@@ -22,6 +22,11 @@
 #include "src/tint/ir/instruction.h"
 #include "src/tint/utils/vector.h"
 
+// Forward declarations
+namespace tint::ir {
+class ControlInstruction;
+}  // namespace tint::ir
+
 namespace tint::ir {
 
 /// A block of statements. The instructions in the block are a linear list of instructions to
@@ -141,6 +146,12 @@
     /// @param node the node to add
     void AddInboundBranch(ir::Branch* node);
 
+    /// @return the parent instruction that owns this block
+    ControlInstruction* Parent() const { return parent_; }
+
+    /// @param parent the parent instruction that owns this block
+    void SetParent(ControlInstruction* parent) { parent_ = parent; }
+
   private:
     struct {
         Instruction* first = nullptr;
@@ -156,6 +167,8 @@
     ///   - Node is a merge target outside control flow (e.g. an if that returns in both branches)
     ///   - Node is a continue target outside control flow (e.g. a loop that returns)
     utils::Vector<ir::Branch*, 2> inbound_branches_;
+
+    ControlInstruction* parent_ = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/break_if.cc b/src/tint/ir/break_if.cc
index 1168c54..644502b 100644
--- a/src/tint/ir/break_if.cc
+++ b/src/tint/ir/break_if.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/loop.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::BreakIf);
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 0b7849c..fdf11b9 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -64,6 +64,7 @@
 
     Block* b = s->Cases().Back().Start();
     b->AddInboundBranch(s);
+    b->SetParent(s);
     return b;
 }
 
diff --git a/src/tint/ir/continue.cc b/src/tint/ir/continue.cc
index bf8cbf1..a7282ac 100644
--- a/src/tint/ir/continue.cc
+++ b/src/tint/ir/continue.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/loop.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Continue);
diff --git a/src/tint/ir/control_instruction.cc b/src/tint/ir/control_instruction.cc
new file mode 100644
index 0000000..efcac64
--- /dev/null
+++ b/src/tint/ir/control_instruction.cc
@@ -0,0 +1,23 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/control_instruction.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::ControlInstruction);
+
+namespace tint::ir {
+
+ControlInstruction::~ControlInstruction() = default;
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/control_instruction.h b/src/tint/ir/control_instruction.h
new file mode 100644
index 0000000..cd0e7d3
--- /dev/null
+++ b/src/tint/ir/control_instruction.h
@@ -0,0 +1,32 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_CONTROL_INSTRUCTION_H_
+#define SRC_TINT_IR_CONTROL_INSTRUCTION_H_
+
+#include "src/tint/ir/branch.h"
+
+namespace tint::ir {
+
+/// Base class of instructions that perform branches to two or more blocks, owned by the
+/// ControlInstruction.
+class ControlInstruction : public utils::Castable<ControlInstruction, Branch> {
+  public:
+    /// Destructor
+    ~ControlInstruction() override;
+};
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CONTROL_INSTRUCTION_H_
diff --git a/src/tint/ir/exit_if.cc b/src/tint/ir/exit_if.cc
index 11cbd74..44a7002 100644
--- a/src/tint/ir/exit_if.cc
+++ b/src/tint/ir/exit_if.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/if.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::ExitIf);
diff --git a/src/tint/ir/exit_loop.cc b/src/tint/ir/exit_loop.cc
index 01b7b8a..729e466 100644
--- a/src/tint/ir/exit_loop.cc
+++ b/src/tint/ir/exit_loop.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/loop.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::ExitLoop);
diff --git a/src/tint/ir/exit_switch.cc b/src/tint/ir/exit_switch.cc
index f63e791..29c6a24 100644
--- a/src/tint/ir/exit_switch.cc
+++ b/src/tint/ir/exit_switch.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/switch.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::ExitSwitch);
diff --git a/src/tint/ir/from_program.cc b/src/tint/ir/from_program.cc
index 5b6bc44..ad4d29f 100644
--- a/src/tint/ir/from_program.cc
+++ b/src/tint/ir/from_program.cc
@@ -145,8 +145,8 @@
         /* dst */ {builder_.ir.constant_values},
     };
 
-    /// The stack of control blocks.
-    utils::Vector<Branch*, 8> control_stack_;
+    /// The stack of flow control instructions.
+    utils::Vector<ControlInstruction*, 8> control_stack_;
 
     /// The current block for expressions.
     Block* current_block_ = nullptr;
@@ -162,7 +162,9 @@
 
     class ControlStackScope {
       public:
-        ControlStackScope(Impl* impl, Branch* b) : impl_(impl) { impl_->control_stack_.Push(b); }
+        ControlStackScope(Impl* impl, ControlInstruction* b) : impl_(impl) {
+            impl_->control_stack_.Push(b);
+        }
 
         ~ControlStackScope() { impl_->control_stack_.Pop(); }
 
diff --git a/src/tint/ir/if.cc b/src/tint/ir/if.cc
index 5d0ac69..29bcbb2 100644
--- a/src/tint/ir/if.cc
+++ b/src/tint/ir/if.cc
@@ -16,6 +16,8 @@
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::If);
 
+#include "src/tint/ir/block.h"
+
 namespace tint::ir {
 
 If::If(Value* cond, ir::Block* t, ir::Block* f, ir::Block* m) : true_(t), false_(f), merge_(m) {
@@ -27,9 +29,14 @@
     AddOperand(cond);
     if (true_) {
         true_->AddInboundBranch(this);
+        true_->SetParent(this);
     }
     if (false_) {
         false_->AddInboundBranch(this);
+        false_->SetParent(this);
+    }
+    if (merge_) {
+        merge_->SetParent(this);
     }
 }
 
diff --git a/src/tint/ir/if.h b/src/tint/ir/if.h
index 18013fc..b3755e1 100644
--- a/src/tint/ir/if.h
+++ b/src/tint/ir/if.h
@@ -15,19 +15,30 @@
 #ifndef SRC_TINT_IR_IF_H_
 #define SRC_TINT_IR_IF_H_
 
-#include "src/tint/ir/block.h"
-#include "src/tint/ir/branch.h"
-#include "src/tint/ir/value.h"
-
-// Forward declarations
-namespace tint::ir {
-class Block;
-}  // namespace tint::ir
+#include "src/tint/ir/control_instruction.h"
 
 namespace tint::ir {
 
-/// An if instruction
-class If : public utils::Castable<If, Branch> {
+/// If instruction.
+///
+/// ```
+///                   in
+///                    ┃
+///         ┏━━━━━━━━━━┻━━━━━━━━━━┓
+///         ▼                     ▼
+///    ┌────────────┐      ┌────────────┐
+///    │  True      │      │  False     │
+///    | (optional) |      | (optional) |
+///    └────────────┘      └────────────┘
+///  ExitIf ┃     ┌──────────┐     ┃ ExitIf
+///         ┗━━━━▶│  Merge   │◀━━━━┛
+///               │(optional)│
+///               └──────────┘
+///                    ┃
+///                    ▼
+///                   out
+/// ```
+class If : public utils::Castable<If, ControlInstruction> {
   public:
     /// Constructor
     /// @param cond the if condition
diff --git a/src/tint/ir/if_test.cc b/src/tint/ir/if_test.cc
index 27f1df7..4d5bcdf 100644
--- a/src/tint/ir/if_test.cc
+++ b/src/tint/ir/if_test.cc
@@ -29,6 +29,14 @@
     EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{if_, 0u}));
 }
 
+TEST_F(IR_IfTest, Parent) {
+    auto* cond = b.Constant(true);
+    auto* if_ = b.CreateIf(cond);
+    EXPECT_EQ(if_->True()->Parent(), if_);
+    EXPECT_EQ(if_->False()->Parent(), if_);
+    EXPECT_EQ(if_->Merge()->Parent(), if_);
+}
+
 TEST_F(IR_IfTest, Fail_NullCondition) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/loop.cc b/src/tint/ir/loop.cc
index 6b551dd..37c4f00 100644
--- a/src/tint/ir/loop.cc
+++ b/src/tint/ir/loop.cc
@@ -16,6 +16,8 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Loop);
 
 namespace tint::ir {
@@ -26,8 +28,25 @@
     TINT_ASSERT(IR, body_);
     TINT_ASSERT(IR, continuing_);
     TINT_ASSERT(IR, merge_);
+
+    if (initializer_) {
+        initializer_->SetParent(this);
+    }
+    if (body_) {
+        body_->SetParent(this);
+    }
+    if (continuing_) {
+        continuing_->SetParent(this);
+    }
+    if (merge_) {
+        merge_->SetParent(this);
+    }
 }
 
 Loop::~Loop() = default;
 
+bool Loop::HasInitializer() const {
+    return initializer_->HasBranchTarget();
+}
+
 }  // namespace tint::ir
diff --git a/src/tint/ir/loop.h b/src/tint/ir/loop.h
index 6989dba..50c69c7 100644
--- a/src/tint/ir/loop.h
+++ b/src/tint/ir/loop.h
@@ -15,43 +15,47 @@
 #ifndef SRC_TINT_IR_LOOP_H_
 #define SRC_TINT_IR_LOOP_H_
 
-#include "src/tint/ir/block.h"
-#include "src/tint/ir/branch.h"
+#include "src/tint/ir/control_instruction.h"
 
 namespace tint::ir {
 
 /// Loop instruction.
 ///
 /// ```
-///                        in
-///                         ┃
-///                         ┣━━━━━━━━━━━━━━━━┓
-///                         ▼                ┃
-///             ┌─────────────────────────┐  ┃
-///             │   loop->Initializer()   │  ┃
-///             │       (optional)        │  ┃
-///             └─────────────────────────┘  ┃
-///           NextIteration ┃                ┃
-///                         ┃◀━━━━━━━━━━━━━━━┫
-///                         ▼                ┃
-///             ┌─────────────────────────┐  ┃
-///          ┏━━│       loop->Body()      │  ┃
-///          ┃  └─────────────────────────┘  ┃
-///          ┃     Continue ┃                ┃ NextIteration
-///          ┃              ▼                ┃
-///          ┃  ┌─────────────────────────┐  ┃ BreakIf(false)
-/// ExitLoop ┃  │   loop->Continuing()    │━━┛
-///          ┃  └─────────────────────────┘
-///          ┃              ┃
-///          ┃              ┃ BreakIf(true)
-///          ┗━━━━━━━━━━━━━▶┃
-///                         ▼
-///             ┌─────────────────────────┐
-///             │      loop->Merge()      │
-///             └─────────────────────────┘
+///                     in
+///                      ┃
+///                      ┣━━━━━━━━━━━┓
+///                      ▼           ┃
+///             ┌─────────────────┐  ┃
+///             │   Initializer   │  ┃
+///             │    (optional)   │  ┃
+///             └─────────────────┘  ┃
+///        NextIteration ┃           ┃
+///                      ┃◀━━━━━━━━━━┫
+///                      ▼           ┃
+///             ┌─────────────────┐  ┃
+///          ┏━━│       Body      │  ┃
+///          ┃  └─────────────────┘  ┃
+///          ┃  Continue ┃           ┃ NextIteration
+///          ┃           ▼           ┃
+///          ┃  ┌─────────────────┐  ┃ BreakIf(false)
+/// ExitLoop ┃  │   Continuing    │━━┛
+///             │  (optional)     │
+///          ┃  └─────────────────┘
+///          ┃           ┃
+///          ┃           ┃ BreakIf(true)
+///          ┗━━━━━━━━━━▶┃
+///                      ▼
+///             ┌────────────────┐
+///             │     Merge      │
+///             │  (optional)    │
+///             └────────────────┘
+///                      ┃
+///                      ▼
+///                     out
 ///
 /// ```
-class Loop : public utils::Castable<Loop, Branch> {
+class Loop : public utils::Castable<Loop, ControlInstruction> {
   public:
     /// Constructor
     /// @param i the initializer block
@@ -68,7 +72,7 @@
 
     /// @returns true if the loop uses an initializer block. If true, then the Loop first branches
     /// to the initializer block, otherwise it first branches to the body block.
-    bool HasInitializer() const { return initializer_->HasBranchTarget(); }
+    bool HasInitializer() const;
 
     /// @returns the switch start block
     const ir::Block* Body() const { return body_; }
diff --git a/src/tint/ir/loop_test.cc b/src/tint/ir/loop_test.cc
index 53776f1..e51a005 100644
--- a/src/tint/ir/loop_test.cc
+++ b/src/tint/ir/loop_test.cc
@@ -22,6 +22,14 @@
 using namespace tint::number_suffixes;  // NOLINT
 using IR_LoopTest = IRTestHelper;
 
+TEST_F(IR_LoopTest, Parent) {
+    auto* loop = b.CreateLoop();
+    EXPECT_EQ(loop->Initializer()->Parent(), loop);
+    EXPECT_EQ(loop->Body()->Parent(), loop);
+    EXPECT_EQ(loop->Continuing()->Parent(), loop);
+    EXPECT_EQ(loop->Merge()->Parent(), loop);
+}
+
 TEST_F(IR_LoopTest, Fail_NullInitializerBlock) {
     EXPECT_FATAL_FAILURE(
         {
diff --git a/src/tint/ir/next_iteration.cc b/src/tint/ir/next_iteration.cc
index 0d18617..b1bf620 100644
--- a/src/tint/ir/next_iteration.cc
+++ b/src/tint/ir/next_iteration.cc
@@ -16,6 +16,7 @@
 
 #include <utility>
 
+#include "src/tint/ir/block.h"
 #include "src/tint/ir/loop.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::NextIteration);
diff --git a/src/tint/ir/switch.cc b/src/tint/ir/switch.cc
index b7b056a..68137ac 100644
--- a/src/tint/ir/switch.cc
+++ b/src/tint/ir/switch.cc
@@ -16,6 +16,8 @@
 
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Switch);
 
+#include "src/tint/ir/block.h"
+
 namespace tint::ir {
 
 Switch::Switch(Value* cond, ir::Block* m) : merge_(m) {
@@ -23,6 +25,10 @@
     TINT_ASSERT(IR, merge_);
 
     AddOperand(cond);
+
+    if (merge_) {
+        merge_->SetParent(this);
+    }
 }
 
 Switch::~Switch() = default;
diff --git a/src/tint/ir/switch.h b/src/tint/ir/switch.h
index f8d88a9..b065074 100644
--- a/src/tint/ir/switch.h
+++ b/src/tint/ir/switch.h
@@ -15,15 +15,35 @@
 #ifndef SRC_TINT_IR_SWITCH_H_
 #define SRC_TINT_IR_SWITCH_H_
 
-#include "src/tint/ir/block.h"
-#include "src/tint/ir/branch.h"
-#include "src/tint/ir/constant.h"
-#include "src/tint/ir/value.h"
+#include "src/tint/ir/control_instruction.h"
+
+// Forward declarations
+namespace tint::ir {
+class Constant;
+}  // namespace tint::ir
 
 namespace tint::ir {
-
-/// Flow node representing a switch statement
-class Switch : public utils::Castable<Switch, Branch> {
+/// Switch instruction.
+///
+/// ```
+///                           in
+///                            ┃
+///     ╌╌╌╌╌╌╌╌┲━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━┱╌╌╌╌╌╌╌╌
+///             ▼              ▼              ▼
+///        ┌────────┐     ┌────────┐     ┌────────┐
+///        │ Case A │     │ Case B │     │ Case C │
+///        └────────┘     └────────┘     └────────┘
+///  ExitSwitch ┃   ExitSwitch ┃   ExitSwitch ┃
+///             ┃              ▼              ┃
+///             ┃       ┌────────────┐        ┃
+///     ╌╌╌╌╌╌╌╌┺━━━━━━▶│ Merge      │◀━━━━━━━┹╌╌╌╌╌╌╌╌
+///                     │ (optional) │
+///                     └────────────┘
+///                            ┃
+///                            ▼
+///                           out
+/// ```
+class Switch : public utils::Castable<Switch, ControlInstruction> {
   public:
     /// A case selector
     struct CaseSelector {
diff --git a/src/tint/ir/switch_test.cc b/src/tint/ir/switch_test.cc
index 3ea47fb..cbcf11a 100644
--- a/src/tint/ir/switch_test.cc
+++ b/src/tint/ir/switch_test.cc
@@ -30,6 +30,13 @@
     EXPECT_THAT(cond->Usages(), testing::UnorderedElementsAre(Usage{switch_, 0u}));
 }
 
+TEST_F(IR_SwitchTest, Parent) {
+    auto* switch_ = b.CreateSwitch(b.Constant(1_i));
+    b.CreateCase(switch_, utils::Vector{Switch::CaseSelector{nullptr}});
+    EXPECT_THAT(switch_->Merge()->Parent(), switch_);
+    EXPECT_THAT(switch_->Cases().Front().Start()->Parent(), switch_);
+}
+
 TEST_F(IR_SwitchTest, Fail_NullCondition) {
     EXPECT_FATAL_FAILURE(
         {