[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(
{