Start adding IR.
This CL starts adding the basics for an IR for Tint. The IR is
a lower level representation then the AST with several of the
restrictions removed. For example, the IR is mutable, is a DAG
instead of a tree, and removes a number of WGSL constructs to
simplify transforms.
Bug: tint:1718
Change-Id: I6a16ac3116cee31410896c3a0424af7b41f958c3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/105800
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/docs/tint/ir.md b/docs/tint/ir.md
new file mode 100644
index 0000000..b65154b
--- /dev/null
+++ b/docs/tint/ir.md
@@ -0,0 +1,411 @@
+# Intermediate Representation
+
+As Tint has grown the number of transforms on the AST has grown. This
+growth has lead to several issues:
+
+1. Transforms rebuild the AST and SEM which causes slowness
+1. Transforming in AST can be difficult as the AST is hard to work with
+
+In order to address these goals, an IR is being introduced into Tint.
+The IR is mutable, it holds the needed state in order to be transformed.
+The IR is also translatable back into AST. It will be possible to
+generate an AST, convert to IR, transform, and then rebuild a new AST.
+This round-trip ability provides a few features:
+
+1. Easy to integrate into current system by replacing AST transforms
+ piecemeal
+1. Easier to test as the resulting AST can be emitted as WGSL and
+ compared.
+
+The IR helps with the complexity of the AST transforms by limiting the
+representations seen in the IR form. For example, instead of `for`,
+`while` and `loop` constructs there is a single `loop` construct.
+`alias` and `static_assert` nodes are not emitted into IR. Dead code is
+eliminated during the IR construction.
+
+As the IR can convert into AST, we could potentially simplify the
+SPIRV-Reader by generating IR directly. The IR is closer to what SPIR-V
+looks like, so maybe a simpler transform.
+
+## Design
+
+The IR breaks down into two fundamental pieces, the control flow and the
+expression lists. While these can be thought of as separate pieces they
+are linked in that the control flow blocks contain the expression lists.
+A control flow block may use the result of an expression as the
+condition.
+
+The IR works together with the AST/SEM. There is an underlying
+assumption that the source `Program` will live as long as the IR. The IR
+holds pointers to data from the `Program`. This includes things like SEM
+types, variables, statements, etc.
+
+Transforming from AST to IR and back to AST is a lossy operation.
+The resulting AST when converting back will not be the same as the
+AST being provided. (e.g. all `for`, `while` and `loop` constructs coming
+in will become `while` loops going out). This is intentional as it
+greatly simplifies the number of things to consider in the IR. For
+instance:
+
+* No `alias` nodes
+* No `static_assert` nodes
+* All loops become `while` loops
+* `if` statements may all become `if/else`
+
+### Code Structure
+The code is contained in the `src/tint/ir` folder and is broken down
+into several classes. Note, the IR is a Tint _internal_ representation
+and these files should _never_ appear in the public API.
+
+#### Builder
+The `Builder` class provides useful helper routines for creating IR
+content. The Builder owns an `ir::Module`, it can be created with an
+existing Module by moving it into the builder. The Module is moved from
+the builder when it is complete.
+
+#### Module
+The top level of the IR is the `Module`. The module stores a list of
+`functions`, `entry_points`, allocators and various other bits of
+information needed by the IR. The `Module` also contains a pointer to
+the `Program` which the IR was created from. The `Program` must outlive
+the `Module`.
+
+The `Module` provides two methods from moving two and from a `Program`.
+The `Module::FromProgram` static method will take a `Program` and
+construct an `ir::Module` from the contents. The resulting module class
+then has a `ToProgram` method which will construct a new `Program` from
+the `Module` contents.
+
+#### BuilderImpl
+The `BuilderImpl` is internally used by the `Module` to do the
+conversion from a `Program` to a `Module`. This class should not be used
+outside the `src/tint/ir` folder.
+
+### Transforms
+Similar to the AST a transform system is available for IR. The transform
+has the same setup as the AST (and inherits from the same base transform
+class.)
+
+Note, not written yet.
+
+### Scoping
+The IR flattens scopes. This also means that the IR will rename shadow
+variables to be uniquely named in the larger scoped block.
+
+For an example of flattening:
+
+```
+{
+ var x = 1;
+ {
+ var y = 2;
+ }
+}
+```
+
+becomes:
+
+```
+{
+ var x = 1;
+ var y = 2;
+}
+```
+
+For an example of shadowing:
+
+```
+{
+ var x = 1;
+ if (true) {
+ var x = 2;
+ }
+}
+```
+
+becomes:
+
+```
+{
+ var x = 1;
+ if true {
+ var x_1 = 2;
+ }
+}
+```
+
+### Control Flow Blocks
+
+At the top level, the AST is broken into a series of control flow nodes.
+There are a limited set of flow nodes as compared to AST:
+
+1. Block
+1. Function
+1. If statement
+1. Loop statement
+1. Switch statement
+1. Terminator
+
+As the IR is built a stack of control flow blocks is maintained. The
+stack contains `function`, `loop`, `if` and `switch` control flow
+blocks. A `function` is always the bottom element in the flow control
+stack.
+
+The current instruction block is tracked. The tracking is reset to
+`nullptr` when a branch happens. This is used in the statement processing
+in order to eliminate dead code. If the current block does not exist, or
+has a branch target, then no further instructions can be added, which
+means all control flow has branched and any subsequent statements can be
+disregarded.
+
+Note, this does have the effect that the inspector _must_ be run to
+retrieve the module interface before converting to IR. This is because
+phony assignments in dead code add variables into the interface.
+
+```
+var<storage> b;
+
+fn a() {
+ return;
+ _ = b; // This pulls b into the module interface but would be
+ // dropped due to dead code removal.
+}
+```
+
+#### Control Flow Block
+A block is the simplest control flow node. It contains the instruction
+lists for a given linear section of codes. A block only has one branch
+statement which always happens at the end of the block. Note, the branch
+statement is implicit, it doesn't show up in the expression list but is
+encoded in the `branch_target`.
+
+In almost every case a block does not branch to another block. It will
+always branch to another control flow node. The exception to this rule
+is blocks branching to the function end block.
+
+#### Control Flow Function
+A function control flow block has two targets associated with it, the
+`start_target` and the `end_target`. Function flow starts at the
+`start_target` and ends just before the `end_target`. The `end_target`
+is always a terminator, it just marks the end of the function
+(a return is a branch to the function `end_target`).
+
+#### Control Flow If
+The if flow node is an `if-else` structure. There are no `else-if`
+entries, they get moved into the `else` of the `if`. The if control flow
+node has three targets, the `true_target`, `false_target` and possibly a
+`merge_target`.
+
+The `merge_target` is possibly `nullptr`. This can happen if both
+branches of the `if` call `return` for instance as the internal branches
+would jump to the function `end_target`.
+
+In all cases, the if node will have a `true_target` and a
+`false_target`, the target block maybe just a branch to the
+`merge_target` in the case where that branch of the if was empty.
+
+#### Control Flow Loop
+All of the loop structures in AST merge down to a single loop control
+flow node. The loop contains the `start_target`, `continuing_target` and
+a `merge_target`.
+
+In the case of a loop, the `merge_target` always exists, but may
+actually not exist in the control flow. The target is created in order
+to have a branch for `continue` to branch too, but if the loop body does
+a `return` then control flow may jump over that block completely.
+
+The chain of blocks from the `start_target`, as long as it does not
+`break` or `return` will branch to the `continuing_target`. The
+`continuing_target` will possibly branch to the `merge_target` and will
+branch to the `start_target` for the loop.
+
+A while loop is decomposed as listed in the WGSL spec:
+
+```
+while (a < b) {
+ c += 1;
+}
+```
+
+becomes:
+
+```
+loop {
+ if (!(a < b)) {
+ break;
+ }
+ c += 1;
+}
+```
+
+A for loop is decomposed as listed in the WGSL spec:
+```
+for (var i = 0; i < 10; i++) {
+ c += 1;
+}
+```
+
+becomes:
+
+```
+var i = 0;
+loop {
+ if (!(i < 10)) {
+ break;
+ }
+
+ c += 1;
+
+ continuing {
+ i++;
+ }
+}
+```
+
+#### Control Flow Switch
+The switch control flow has a target block for each of the
+`case/default` labels along with a `merge_target`. The `merge_target`
+while existing, maybe outside the control flow if all of the `case`
+branches `return`. The target exists in order to provide a `break`
+target.
+
+#### Control Flow Terminator
+The terminator control flow is only used as the `end_target` of a
+function. It does not contain instructions and is only used as a marker
+for the exit of a function.
+
+### Expression Lists.
+Note, this section isn't fully formed as this has not been written at
+this point.
+
+The expression lists are all in SSA form. The SSA variables will keep
+pointers back to the source AST variables in order for us to not require
+PHI nodes and to make it easier to move back out of SSA form.
+
+#### Expressions
+All expressions in IR are single operations. There are no complex
+expressions. Any complex expression in the AST is broke apart into the
+simpler single operation components.
+
+```
+var a = b + c - (4 * k);
+```
+
+becomes:
+
+```
+%t0 = b + c
+%t1 = 4 * k
+%v0 = %t0 - %t1
+```
+
+This also means that many of the short forms `i += 1`, `i++` get
+expanded into the longer form of `i = i + 1`.
+
+##### Short-Circuit Expressions
+The short-circuit expressions (e.g. `a && b`) will be convert into an
+`if` structure control flow.
+
+```
+let c = a() && b()
+```
+
+becomes
+
+```
+let c = a();
+if (c) {
+ c = b();
+}
+```
+
+#### Registers
+There are several types of registers used in the SSA form.
+
+1. Constant Register
+1. Temporary Register
+1. Variable Register
+1. Return Register
+1. Function Argument Register
+
+##### Constant Register
+The constant register `%c` holds a constant value. All values in IR are
+concrete, there are no abstract values as materialization has already
+happened. Each constant register holds a single constant value (e.g.
+`3.14`) and a pointee to the type (maybe? If needed.)
+
+##### Temporary Register
+The temporary register `%t` hold the results of a simple operation. The
+temporaries are created as complex expressions are broken down into
+pieces. The temporary register tracks the usage count for the register.
+This allows a portion of a calculation to be pulled out when rebuilding
+AST as a common calculation. If the temporary is used once it can be
+re-combine back into a large expression.
+
+##### Variable Register
+The variable register `%v` potentially holds a pointer back to source
+variables. So, while each value is written only once, if the pointer
+back to an AST variable exists we can rebuild the variable that value
+was originally created from and can assign back when converting to AST.
+
+##### Return Register
+Each function has a return register `%r` where the return value will be
+stored before the final block branches to the `end_target`.
+
+##### Function Argument Register
+The function argument registers `%a` are used to store the values being
+passed into a function call.
+
+#### Type Information
+The IR shares type information with the SEM. The types are the same, but
+they may exist in different block allocations. The SEM types will be
+re-used if they exist, but if the IR needs to create a new type it will
+be created in the IRs type block allocator.
+
+#### Loads / Stores and Deref
+Note, have not thought about this. We should probably have explicit
+load/store operations injected in the right spot, but don't know yet.
+
+## Alternatives
+Instead of going to a custom IR there are several possible other roads
+that could be travelled.
+
+### Mutable AST
+Tint originally contained a mutable AST. This was converted to immutable
+in order to allow processing over multiple threads and for safety
+properties. Those desires still hold, the AST is public API, and we want
+it to be as safe as possible, so keeping it immutable provides that
+guarantee.
+
+### Multiple Transforms With One Program Builder
+Instead of generating an immutable AST after each transform, running
+multiple transforms on the single program builder would remove some of
+the performance penalties of going to and from immutable AST. While this
+is true, the transforms use a combination of AST and SEM information.
+When they transform they _do not_ create new SEM information. That
+means, after a given transform, the SEM is out of date. In order to
+re-generate the SEM the resolver needs to be rerun. Supporting this
+would require being very careful on what transforms run together and
+how they modify the AST.
+
+### Adopt An Existing IR
+There are already several IRs in the while, Mesa has NIR, LLVM has
+LLVM IR. There are others, adopting one of those would remove the
+requirements of writing and maintaining our own IR. While that is true,
+there are several downsides to this re-use. The IRs are internal to the
+library, so the API isn't public, LLVM IR changes with each iteration of
+LLVM. This would require us to adapt the AST -> IR -> AST transform for
+each modification of the IR.
+
+They also end up being lower level then is strictly useful for us. While
+the IR in Tint is a simplified form, we still have to be able to go back
+to the high level structured form in order to emit the resulting HLSL,
+MSL, GLSL, etc. (Only SPIR-V is a good match for the lowered IR form).
+This transformation back is not a direction other IRs maybe interested
+in so may have lost information, or require re-determining (determining
+variables from SSA and PHI nodes for example).
+
+Other technical reasons are the maintenance of BUILD.gn and CMake files
+in order to integrate into our build systems, along with resulting
+binary size questions from pulling in external systems.
+
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 47b44a5..2399ee1 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -248,6 +248,26 @@
inspector/resource_binding.h
inspector/scalar.cc
inspector/scalar.h
+ ir/block.cc
+ ir/block.h
+ ir/builder.cc
+ ir/builder.h
+ ir/builder_impl.cc
+ ir/builder_impl.h
+ ir/flow_node.cc
+ ir/flow_node.h
+ ir/function.cc
+ ir/function.h
+ ir/if.cc
+ ir/if.h
+ ir/loop.cc
+ ir/loop.h
+ ir/module.cc
+ ir/module.h
+ ir/switch.cc
+ ir/switch.h
+ ir/terminator.cc
+ ir/terminator.h
number.cc
number.h
program_builder.cc
@@ -789,6 +809,8 @@
diagnostic/diagnostic_test.cc
diagnostic/formatter_test.cc
diagnostic/printer_test.cc
+ ir/builder_impl_test.cc
+ ir/test_helper.h
number_test.cc
program_builder_test.cc
program_test.cc
diff --git a/src/tint/diagnostic/diagnostic.h b/src/tint/diagnostic/diagnostic.h
index de57c99..3ea8a0d 100644
--- a/src/tint/diagnostic/diagnostic.h
+++ b/src/tint/diagnostic/diagnostic.h
@@ -38,6 +38,7 @@
AST,
Clone,
Inspector,
+ IR,
Program,
ProgramBuilder,
Reader,
diff --git a/src/tint/ir/block.cc b/src/tint/ir/block.cc
new file mode 100644
index 0000000..2030c19
--- /dev/null
+++ b/src/tint/ir/block.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/block.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Block);
+
+namespace tint::ir {
+
+Block::Block() : Base() {}
+
+Block::~Block() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/block.h b/src/tint/ir/block.h
new file mode 100644
index 0000000..022aff2
--- /dev/null
+++ b/src/tint/ir/block.h
@@ -0,0 +1,37 @@
+// Copyright 2022 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_BLOCK_H_
+#define SRC_TINT_IR_BLOCK_H_
+
+#include "src/tint/ir/flow_node.h"
+
+namespace tint::ir {
+
+/// A flow node comprising a block of statements. The instructions in the block are a linear list of
+/// instructions to execute. The block will branch at the end. The only blocks which do not branch
+/// are the end blocks of functions.
+class Block : public Castable<Block, FlowNode> {
+ public:
+ /// Constructor
+ Block();
+ ~Block() override;
+
+ /// The node this block branches too.
+ const FlowNode* branch_target = nullptr;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_BLOCK_H_
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
new file mode 100644
index 0000000..66b195b
--- /dev/null
+++ b/src/tint/ir/builder.cc
@@ -0,0 +1,73 @@
+// Copyright 2022 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/builder.h"
+
+#include <utility>
+
+#include "src/tint/ir/builder_impl.h"
+#include "src/tint/program.h"
+
+namespace tint::ir {
+
+Builder::Builder(const Program* prog) : ir(prog) {}
+
+Builder::Builder(Module&& mod) : ir(std::move(mod)) {}
+
+Builder::~Builder() = default;
+
+Block* Builder::CreateBlock() {
+ return ir.flow_nodes.Create<Block>();
+}
+
+Terminator* Builder::CreateTerminator() {
+ return ir.flow_nodes.Create<Terminator>();
+}
+
+Function* Builder::CreateFunction(const ast::Function* ast_func) {
+ auto* ir_func = ir.flow_nodes.Create<Function>(ast_func);
+ ir_func->start_target = CreateBlock();
+ ir_func->end_target = CreateTerminator();
+ return ir_func;
+}
+
+If* Builder::CreateIf(const ast::Statement* stmt, IfFlags flags) {
+ auto* ir_if = ir.flow_nodes.Create<If>(stmt);
+ ir_if->false_target = CreateBlock();
+ ir_if->true_target = CreateBlock();
+
+ if (flags == IfFlags::kCreateMerge) {
+ ir_if->merge_target = CreateBlock();
+ } else {
+ ir_if->merge_target = nullptr;
+ }
+ return ir_if;
+}
+
+Loop* Builder::CreateLoop(const ast::LoopStatement* stmt) {
+ auto* ir_loop = ir.flow_nodes.Create<Loop>(stmt);
+ ir_loop->start_target = CreateBlock();
+ ir_loop->continuing_target = CreateBlock();
+ ir_loop->merge_target = CreateBlock();
+
+ return ir_loop;
+}
+
+void Builder::Branch(Block* from, const FlowNode* to) {
+ TINT_ASSERT(IR, from);
+ TINT_ASSERT(IR, to);
+ from->branch_target = to;
+}
+
+} // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
new file mode 100644
index 0000000..531bcce
--- /dev/null
+++ b/src/tint/ir/builder.h
@@ -0,0 +1,86 @@
+// Copyright 2022 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_BUILDER_H_
+#define SRC_TINT_IR_BUILDER_H_
+
+#include "src/tint/ir/function.h"
+#include "src/tint/ir/if.h"
+#include "src/tint/ir/loop.h"
+#include "src/tint/ir/module.h"
+#include "src/tint/ir/switch.h"
+#include "src/tint/ir/terminator.h"
+
+// Forward Declarations
+namespace tint {
+class Program;
+} // namespace tint
+
+namespace tint::ir {
+
+/// Builds an ir::Module from a given Program
+class Builder {
+ public:
+ /// Constructor
+ /// @param prog the program this ir is associated with
+ explicit Builder(const Program* prog);
+ /// Constructor
+ /// @param mod the ir::Module to wrap with this builder
+ explicit Builder(Module&& mod);
+ /// Destructor
+ ~Builder();
+
+ /// @returns a new block flow node
+ Block* CreateBlock();
+
+ /// @returns a new terminator flow node
+ Terminator* CreateTerminator();
+
+ /// Creates a function flow node for the given ast::Function
+ /// @param func the ast::Function
+ /// @returns the flow node
+ Function* CreateFunction(const ast::Function* func);
+
+ /// Flags used for creation of if flow nodes
+ enum class IfFlags {
+ /// Do not create a merge node, `merge_target` will be `nullptr`
+ kSkipMerge,
+ /// Create the `merge_target` block
+ kCreateMerge,
+ };
+
+ /// Creates an if flow node for the given ast::IfStatement or ast::BreakIfStatement
+ /// @param stmt the ast::IfStatement or ast::BreakIfStatement
+ /// @param flags the if creation flags. By default the merge block will not be created, pass
+ /// IfFlags::kCreateMerge if creation is desired.
+ /// @returns the flow node
+ If* CreateIf(const ast::Statement* stmt, IfFlags flags = IfFlags::kSkipMerge);
+
+ /// Creates a loop flow node for the given ast::LoopStatement
+ /// @param stmt the ast::LoopStatement
+ /// @returns the flow node
+ Loop* CreateLoop(const ast::LoopStatement* stmt);
+
+ /// Branches the given block to the given flow node.
+ /// @param from the block to branch from
+ /// @param to the node to branch too
+ void Branch(Block* from, const FlowNode* to);
+
+ /// The IR module.
+ Module ir;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_BUILDER_H_
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
new file mode 100644
index 0000000..5f7553f
--- /dev/null
+++ b/src/tint/ir/builder_impl.cc
@@ -0,0 +1,372 @@
+// Copyright 2022 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/builder_impl.h"
+
+#include "src/tint/ast/alias.h"
+#include "src/tint/ast/block_statement.h"
+#include "src/tint/ast/break_if_statement.h"
+#include "src/tint/ast/break_statement.h"
+#include "src/tint/ast/continue_statement.h"
+#include "src/tint/ast/function.h"
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/statement.h"
+#include "src/tint/ast/static_assert.h"
+#include "src/tint/ir/function.h"
+#include "src/tint/ir/if.h"
+#include "src/tint/ir/loop.h"
+#include "src/tint/ir/module.h"
+#include "src/tint/ir/switch.h"
+#include "src/tint/ir/terminator.h"
+#include "src/tint/program.h"
+#include "src/tint/sem/module.h"
+
+namespace tint::ir {
+namespace {
+
+using ResultType = utils::Result<Module>;
+
+class FlowStackScope {
+ public:
+ FlowStackScope(BuilderImpl* impl, FlowNode* node) : impl_(impl) {
+ impl_->flow_stack.Push(node);
+ }
+
+ ~FlowStackScope() { impl_->flow_stack.Pop(); }
+
+ private:
+ BuilderImpl* impl_;
+};
+
+} // namespace
+
+BuilderImpl::BuilderImpl(const Program* program) : builder_(program) {}
+
+BuilderImpl::~BuilderImpl() = default;
+
+void BuilderImpl::BranchTo(const FlowNode* node) {
+ TINT_ASSERT(IR, current_flow_block_);
+ TINT_ASSERT(IR, !current_flow_block_->branch_target);
+
+ builder_.Branch(current_flow_block_, node);
+ current_flow_block_->branch_target = node;
+ current_flow_block_ = nullptr;
+}
+
+void BuilderImpl::BranchToIfNeeded(const FlowNode* node) {
+ if (!current_flow_block_ || current_flow_block_->branch_target) {
+ return;
+ }
+ BranchTo(node);
+}
+
+FlowNode* BuilderImpl::FindEnclosingControl(ControlFlags flags) {
+ for (auto it = flow_stack.rbegin(); it != flow_stack.rend(); ++it) {
+ if ((*it)->Is<Loop>()) {
+ return *it;
+ }
+ if (flags == ControlFlags::kExcludeSwitch) {
+ continue;
+ }
+ if ((*it)->Is<Switch>()) {
+ return *it;
+ }
+ }
+ return nullptr;
+}
+
+ResultType BuilderImpl::Build() {
+ auto* sem = builder_.ir.program->Sem().Module();
+
+ for (auto* decl : sem->DependencyOrderedDeclarations()) {
+ bool ok = tint::Switch(
+ decl, //
+ // [&](const ast::Struct* str) {
+ // return false;
+ // },
+ [&](const ast::Alias*) {
+ // Folded away and doesn't appear in the IR.
+ return true;
+ },
+ // [&](const ast::Const*) {
+ // return false;
+ // },
+ // [&](const ast::Override*) {
+ // return false;
+ // },
+ [&](const ast::Function* func) { return EmitFunction(func); },
+ // [&](const ast::Enable*) {
+ // return false;
+ // },
+ [&](const ast::StaticAssert*) {
+ // Evaluated by the resolver, drop from the IR.
+ return true;
+ },
+ [&](Default) {
+ TINT_ICE(IR, diagnostics_) << "unhandled type: " << decl->TypeInfo().name;
+ return false;
+ });
+ if (!ok) {
+ return utils::Failure;
+ }
+ }
+
+ return ResultType{std::move(builder_.ir)};
+}
+
+bool BuilderImpl::EmitFunction(const ast::Function* ast_func) {
+ // The flow stack should have been emptied when the previous function finshed building.
+ TINT_ASSERT(IR, flow_stack.IsEmpty());
+
+ auto* ir_func = builder_.CreateFunction(ast_func);
+ current_function_ = ir_func;
+ builder_.ir.functions.Push(ir_func);
+
+ ast_to_flow_[ast_func] = ir_func;
+
+ if (ast_func->IsEntryPoint()) {
+ builder_.ir.entry_points.Push(ir_func);
+ }
+
+ {
+ FlowStackScope scope(this, ir_func);
+
+ current_flow_block_ = ir_func->start_target;
+ if (!EmitStatements(ast_func->body->statements)) {
+ return false;
+ }
+
+ // If the branch target has already been set then a `return` was called. Only set in the
+ // case where `return` wasn't called.
+ BranchToIfNeeded(current_function_->end_target);
+ }
+
+ TINT_ASSERT(IR, flow_stack.IsEmpty());
+ current_flow_block_ = nullptr;
+ current_function_ = nullptr;
+
+ return true;
+}
+
+bool BuilderImpl::EmitStatements(utils::VectorRef<const ast::Statement*> stmts) {
+ for (auto* s : stmts) {
+ if (!EmitStatement(s)) {
+ return false;
+ }
+
+ // If the current flow block has a branch target then the rest of the statements in this
+ // block are dead code. Skip them.
+ if (!current_flow_block_ || current_flow_block_->branch_target) {
+ break;
+ }
+ }
+ return true;
+}
+
+bool BuilderImpl::EmitStatement(const ast::Statement* stmt) {
+ return tint::Switch(
+ stmt,
+ // [&](const ast::AssignmentStatement* a) { },
+ [&](const ast::BlockStatement* b) { return EmitBlock(b); },
+ [&](const ast::BreakStatement* b) { return EmitBreak(b); },
+ [&](const ast::BreakIfStatement* b) { return EmitBreakIf(b); },
+ // [&](const ast::CallStatement* c) { },
+ [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
+ // [&](const ast::DiscardStatement* d) { },
+ // [&](const ast::FallthroughStatement*) { },
+ [&](const ast::IfStatement* i) { return EmitIf(i); },
+ [&](const ast::LoopStatement* l) { return EmitLoop(l); },
+ // [&](const ast::ForLoopStatement* l) { },
+ // [&](const ast::WhileStatement* l) { },
+ [&](const ast::ReturnStatement* r) { return EmitReturn(r); },
+ // [&](const ast::SwitchStatement* s) { },
+ // [&](const ast::VariableDeclStatement* v) { },
+ [&](const ast::StaticAssert*) {
+ return true; // Not emitted
+ },
+ [&](Default) {
+ TINT_ICE(IR, diagnostics_)
+ << "unknown statement type: " << std::string(stmt->TypeInfo().name);
+ return false;
+ });
+}
+
+bool BuilderImpl::EmitBlock(const ast::BlockStatement* block) {
+ // Note, this doesn't need to emit a Block as the current block flow node should be
+ // sufficient as the blocks all get flattened. Each flow control node will inject the basic
+ // blocks it requires.
+ return EmitStatements(block->statements);
+}
+
+bool BuilderImpl::EmitIf(const ast::IfStatement* stmt) {
+ auto* if_node = builder_.CreateIf(stmt);
+
+ // TODO(dsinclair): Emit the condition expression into the current block
+
+ BranchTo(if_node);
+
+ ast_to_flow_[stmt] = if_node;
+
+ {
+ FlowStackScope scope(this, if_node);
+
+ // TODO(dsinclair): set if condition register into if flow node
+
+ current_flow_block_ = if_node->true_target;
+ if (!EmitStatement(stmt->body)) {
+ return false;
+ }
+
+ current_flow_block_ = if_node->false_target;
+ if (stmt->else_statement && !EmitStatement(stmt->else_statement)) {
+ return false;
+ }
+ }
+ current_flow_block_ = nullptr;
+
+ // If both branches went somewhere, then they both returned, continued or broke. So,
+ // there is no need for the if merge-block and there is nothing to branch to the merge
+ // block anyway.
+ if (if_node->true_target->branch_target && if_node->false_target->branch_target) {
+ return true;
+ }
+
+ if_node->merge_target = builder_.CreateBlock();
+ current_flow_block_ = if_node->merge_target;
+
+ // If the true branch did not execute control flow, then go to the merge target
+ if (!if_node->true_target->branch_target) {
+ if_node->true_target->branch_target = if_node->merge_target;
+ }
+ // If the false branch did not execute control flow, then go to the merge target
+ if (!if_node->false_target->branch_target) {
+ if_node->false_target->branch_target = if_node->merge_target;
+ }
+
+ return true;
+}
+
+bool BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
+ auto* loop_node = builder_.CreateLoop(stmt);
+
+ BranchTo(loop_node);
+
+ ast_to_flow_[stmt] = loop_node;
+
+ {
+ FlowStackScope scope(this, loop_node);
+
+ current_flow_block_ = loop_node->start_target;
+ if (!EmitStatement(stmt->body)) {
+ return false;
+ }
+
+ // The current block didn't `break`, `return` or `continue`, go to the continuing block.
+ BranchToIfNeeded(loop_node->continuing_target);
+
+ current_flow_block_ = loop_node->continuing_target;
+ if (stmt->continuing) {
+ if (!EmitStatement(stmt->continuing)) {
+ return false;
+ }
+ }
+
+ // Branch back to the start node if the continue target didn't branch out already
+ BranchToIfNeeded(loop_node->start_target);
+ }
+
+ current_flow_block_ = loop_node->merge_target;
+ return true;
+}
+
+bool BuilderImpl::EmitReturn(const ast::ReturnStatement*) {
+ // TODO(dsinclair): Emit the return value ....
+
+ BranchTo(current_function_->end_target);
+
+ // TODO(dsinclair): The BranchTo will reset the current block. What happens with dead code
+ // after the return?
+
+ return true;
+}
+
+bool BuilderImpl::EmitBreak(const ast::BreakStatement*) {
+ auto* current_control = FindEnclosingControl(ControlFlags::kNone);
+ TINT_ASSERT(IR, current_control);
+
+ if (auto* c = current_control->As<Loop>()) {
+ BranchTo(c->merge_target);
+ } else if (auto* s = current_control->As<Switch>()) {
+ BranchTo(s->merge_target);
+ } else {
+ TINT_UNREACHABLE(IR, diagnostics_);
+ return false;
+ }
+
+ // TODO(dsinclair): The BranchTo will reset the current block. What happens with dead code
+ // after the break?
+ return true;
+}
+
+bool BuilderImpl::EmitContinue(const ast::ContinueStatement*) {
+ auto* current_control = FindEnclosingControl(ControlFlags::kExcludeSwitch);
+ TINT_ASSERT(IR, current_control);
+
+ if (auto* c = current_control->As<Loop>()) {
+ BranchTo(c->continuing_target);
+ } else {
+ TINT_UNREACHABLE(IR, diagnostics_);
+ }
+
+ // TODO(dsinclair): The BranchTo will reset the current block. What happens with dead code
+ // after the continue?
+
+ return true;
+}
+
+bool BuilderImpl::EmitBreakIf(const ast::BreakIfStatement* stmt) {
+ auto* if_node = builder_.CreateIf(stmt, Builder::IfFlags::kCreateMerge);
+
+ // TODO(dsinclair): Emit the condition expression into the current block
+
+ BranchTo(if_node);
+
+ ast_to_flow_[stmt] = if_node;
+
+ auto* current_control = FindEnclosingControl(ControlFlags::kExcludeSwitch);
+ TINT_ASSERT(IR, current_control);
+ TINT_ASSERT(IR, current_control->Is<Loop>());
+
+ auto* loop = current_control->As<Loop>();
+
+ // TODO(dsinclair): set if condition register into if flow node
+
+ current_flow_block_ = if_node->true_target;
+ BranchTo(loop->merge_target);
+
+ current_flow_block_ = if_node->false_target;
+ BranchTo(if_node->merge_target);
+
+ current_flow_block_ = if_node->merge_target;
+
+ // The `break-if` has to be the last item in the continuing block. The false branch of the
+ // `break-if` will always take us back to the start of the loop.
+ // break then we go back to the start of the loop.
+ BranchTo(loop->start_target);
+
+ return true;
+}
+
+} // namespace tint::ir
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
new file mode 100644
index 0000000..8daeebc
--- /dev/null
+++ b/src/tint/ir/builder_impl.h
@@ -0,0 +1,155 @@
+// Copyright 2022 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_BUILDER_IMPL_H_
+#define SRC_TINT_IR_BUILDER_IMPL_H_
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/ir/builder.h"
+#include "src/tint/ir/flow_node.h"
+#include "src/tint/ir/module.h"
+#include "src/tint/utils/result.h"
+
+// Forward Declarations
+namespace tint {
+class Program;
+} // namespace tint
+namespace tint::ast {
+class BlockStatement;
+class BreakIfStatement;
+class BreakStatement;
+class ContinueStatement;
+class Function;
+class IfStatement;
+class LoopStatement;
+class ReturnStatement;
+class Statement;
+} // namespace tint::ast
+namespace tint::ir {
+class Block;
+class If;
+class Function;
+class Loop;
+class Switch;
+class Terminator;
+} // namespace tint::ir
+
+namespace tint::ir {
+
+/// Builds an ir::Module from a given ast::Program
+class BuilderImpl {
+ public:
+ /// Constructor
+ /// @param program the program to create from
+ explicit BuilderImpl(const Program* program);
+ /// Destructor
+ ~BuilderImpl();
+
+ /// Builds an ir::Module from the given Program
+ /// @returns true on success, false otherwise
+ utils::Result<Module> Build();
+
+ /// @returns the error
+ std::string error() const { return diagnostics_.str(); }
+
+ /// Emits a function to the IR.
+ /// @param func the function to emit
+ /// @returns true if successful, false otherwise
+ bool EmitFunction(const ast::Function* func);
+
+ /// Emits a set of statements to the IR.
+ /// @param stmts the statements to emit
+ /// @returns true if successful, false otherwise.
+ bool EmitStatements(utils::VectorRef<const ast::Statement*> stmts);
+
+ /// Emits a statement to the IR
+ /// @param stmt the statment to emit
+ /// @returns true on success, false otherwise.
+ bool EmitStatement(const ast::Statement* stmt);
+
+ /// Emits a block statement to the IR.
+ /// @param block the block to emit
+ /// @returns true if successful, false otherwise.
+ bool EmitBlock(const ast::BlockStatement* block);
+
+ /// Emits an if control node to the IR.
+ /// @param stmt the if statement
+ /// @returns true if successful, false otherwise.
+ bool EmitIf(const ast::IfStatement* stmt);
+
+ /// Emits a return node to the IR.
+ /// @param stmt the return AST statement
+ /// @returns true if successful, false otherwise.
+ bool EmitReturn(const ast::ReturnStatement* stmt);
+
+ /// Emits a loop control node to the IR.
+ /// @param stmt the loop statement
+ /// @returns true if successful, false otherwise.
+ bool EmitLoop(const ast::LoopStatement* stmt);
+
+ /// Emits a break statement
+ /// @param stmt the break statement
+ /// @returns true if successfull, false otherwise.
+ bool EmitBreak(const ast::BreakStatement* stmt);
+
+ /// Emits a continue statement
+ /// @param stmt the continue statement
+ /// @returns true if successfull, false otherwise.
+ bool EmitContinue(const ast::ContinueStatement* stmt);
+
+ /// Emits a break-if statement
+ /// @param stmt the break-if statement
+ /// @returns true if successfull, false otherwise.
+ bool EmitBreakIf(const ast::BreakIfStatement* stmt);
+
+ /// Retrieve the IR Flow node for a given AST node.
+ /// @param n the node to lookup
+ /// @returns the FlowNode for the given ast::Node or nullptr if it doesn't exist.
+ const ir::FlowNode* FlowNodeForAstNode(const ast::Node* n) const {
+ if (ast_to_flow_.count(n) == 0) {
+ return nullptr;
+ }
+ return ast_to_flow_.at(n);
+ }
+
+ /// The stack of flow control blocks.
+ utils::Vector<FlowNode*, 8> flow_stack;
+
+ private:
+ enum class ControlFlags { kNone, kExcludeSwitch };
+
+ void BranchTo(const ir::FlowNode* node);
+ void BranchToIfNeeded(const ir::FlowNode* node);
+
+ FlowNode* FindEnclosingControl(ControlFlags flags);
+
+ Builder builder_;
+
+ diag::List diagnostics_;
+
+ Block* current_flow_block_ = nullptr;
+ Function* current_function_ = nullptr;
+
+ /// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
+ /// Used for testing purposes.
+ std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_BUILDER_IMPL_H_
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
new file mode 100644
index 0000000..fa37ac5
--- /dev/null
+++ b/src/tint/ir/builder_impl_test.cc
@@ -0,0 +1,582 @@
+// Copyright 2022 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/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using IRBuilderImplTest = TestHelper;
+
+TEST_F(IRBuilderImplTest, Func) {
+ // func -> start -> end
+
+ Func("f", utils::Empty, ty.void_(), utils::Empty);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ ASSERT_EQ(0u, m.entry_points.Length());
+ ASSERT_EQ(1u, m.functions.Length());
+
+ auto* f = m.functions[0];
+ EXPECT_NE(f->start_target, nullptr);
+ EXPECT_NE(f->end_target, nullptr);
+
+ EXPECT_EQ(f->start_target->branch_target, f->end_target);
+}
+
+TEST_F(IRBuilderImplTest, EntryPoint) {
+ Func("f", utils::Empty, ty.void_(), utils::Empty,
+ utils::Vector{Stage(ast::PipelineStage::kFragment)});
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ ASSERT_EQ(1u, m.entry_points.Length());
+ EXPECT_EQ(m.functions[0], m.entry_points[0]);
+}
+
+TEST_F(IRBuilderImplTest, IfStatement) {
+ // func -> start -> if -> true block
+ // -> false block
+ //
+ // [true block] -> if merge
+ // [false block] -> if merge
+ // [if merge] -> func end
+ //
+ auto* ast_if = If(true, Block(), Else(Block()));
+ WrapInFunction(ast_if);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ EXPECT_TRUE(ir_if->Is<ir::If>());
+
+ // TODO(dsinclair): check condition
+
+ auto* flow = ir_if->As<ir::If>();
+ ASSERT_NE(flow->true_target, nullptr);
+ ASSERT_NE(flow->false_target, nullptr);
+ ASSERT_NE(flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, flow);
+ EXPECT_EQ(flow->true_target->branch_target, flow->merge_target);
+ EXPECT_EQ(flow->false_target->branch_target, flow->merge_target);
+ EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, IfStatement_TrueReturns) {
+ // func -> start -> if -> true block
+ // -> false block
+ //
+ // [true block] -> func end
+ // [false block] -> if merge
+ // [if merge] -> func end
+ //
+ auto* ast_if = If(true, Block(Return()));
+ WrapInFunction(ast_if);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ EXPECT_TRUE(ir_if->Is<ir::If>());
+
+ auto* flow = ir_if->As<ir::If>();
+ ASSERT_NE(flow->true_target, nullptr);
+ ASSERT_NE(flow->false_target, nullptr);
+ ASSERT_NE(flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, flow);
+ EXPECT_EQ(flow->true_target->branch_target, func->end_target);
+ EXPECT_EQ(flow->false_target->branch_target, flow->merge_target);
+ EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, IfStatement_FalseReturns) {
+ // func -> start -> if -> true block
+ // -> false block
+ //
+ // [true block] -> if merge
+ // [false block] -> func end
+ // [if merge] -> func end
+ //
+ auto* ast_if = If(true, Block(), Else(Block(Return())));
+ WrapInFunction(ast_if);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ EXPECT_TRUE(ir_if->Is<ir::If>());
+
+ auto* flow = ir_if->As<ir::If>();
+ ASSERT_NE(flow->true_target, nullptr);
+ ASSERT_NE(flow->false_target, nullptr);
+ ASSERT_NE(flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, flow);
+ EXPECT_EQ(flow->true_target->branch_target, flow->merge_target);
+ EXPECT_EQ(flow->false_target->branch_target, func->end_target);
+ EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, IfStatement_BothReturn) {
+ // func -> start -> if -> true block
+ // -> false block
+ //
+ // [true block] -> func end
+ // [false block] -> func end
+ // [if merge] -> nullptr
+ //
+ auto* ast_if = If(true, Block(Return()), Else(Block(Return())));
+ WrapInFunction(ast_if);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ EXPECT_TRUE(ir_if->Is<ir::If>());
+
+ auto* flow = ir_if->As<ir::If>();
+ ASSERT_NE(flow->true_target, nullptr);
+ ASSERT_NE(flow->false_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, flow);
+ EXPECT_EQ(flow->true_target->branch_target, func->end_target);
+ EXPECT_EQ(flow->false_target->branch_target, func->end_target);
+ EXPECT_EQ(flow->merge_target, nullptr);
+}
+
+TEST_F(IRBuilderImplTest, Loop_WithBreak) {
+ // func -> start -> loop -> loop start -> loop merge -> func end
+ //
+ // [continuing] -> loop start
+ //
+ auto* ast_loop = Loop(Block(Break()));
+ WrapInFunction(ast_loop);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop = b.FlowNodeForAstNode(ast_loop);
+ ASSERT_NE(ir_loop, nullptr);
+ EXPECT_TRUE(ir_loop->Is<ir::Loop>());
+
+ auto* flow = ir_loop->As<ir::Loop>();
+ ASSERT_NE(flow->start_target, nullptr);
+ ASSERT_NE(flow->continuing_target, nullptr);
+ ASSERT_NE(flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, flow);
+ EXPECT_EQ(flow->start_target->branch_target, flow->merge_target);
+ EXPECT_EQ(flow->continuing_target->branch_target, flow->start_target);
+ EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, Loop_WithContinue) {
+ // func -> start -> loop -> loop start -> if -> true block
+ // -> false block
+ //
+ // [if true] -> loop merge
+ // [if false] -> if merge
+ // [if merge] -> loop continuing
+ // [loop continuing] -> loop start
+ // [loop merge] -> func end
+ //
+ auto* ast_if = If(true, Block(Break()));
+ auto* ast_loop = Loop(Block(ast_if, Continue()));
+ WrapInFunction(ast_loop);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop = b.FlowNodeForAstNode(ast_loop);
+ ASSERT_NE(ir_loop, nullptr);
+ EXPECT_TRUE(ir_loop->Is<ir::Loop>());
+
+ auto* loop_flow = ir_loop->As<ir::Loop>();
+ ASSERT_NE(loop_flow->start_target, nullptr);
+ ASSERT_NE(loop_flow->continuing_target, nullptr);
+ ASSERT_NE(loop_flow->merge_target, nullptr);
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ ASSERT_TRUE(ir_if->Is<ir::If>());
+
+ auto* if_flow = ir_if->As<ir::If>();
+ ASSERT_NE(if_flow->true_target, nullptr);
+ ASSERT_NE(if_flow->false_target, nullptr);
+ ASSERT_NE(if_flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, loop_flow);
+ EXPECT_EQ(loop_flow->start_target->branch_target, if_flow);
+ EXPECT_EQ(if_flow->true_target->branch_target, loop_flow->merge_target);
+ EXPECT_EQ(if_flow->false_target->branch_target, if_flow->merge_target);
+ EXPECT_EQ(loop_flow->continuing_target->branch_target, loop_flow->start_target);
+ EXPECT_EQ(loop_flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, Loop_WithContinuing_BreakIf) {
+ // func -> start -> loop -> loop start -> continuing
+ //
+ // [loop continuing] -> if -> true branch
+ // -> false branch
+ // [if true] -> loop merge
+ // [if false] -> if merge
+ // [if merge] -> loop start
+ // [loop merge] -> func end
+ //
+ auto* ast_break_if = BreakIf(true);
+ auto* ast_loop = Loop(Block(), Block(ast_break_if));
+ WrapInFunction(ast_loop);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop = b.FlowNodeForAstNode(ast_loop);
+ ASSERT_NE(ir_loop, nullptr);
+ EXPECT_TRUE(ir_loop->Is<ir::Loop>());
+
+ auto* loop_flow = ir_loop->As<ir::Loop>();
+ ASSERT_NE(loop_flow->start_target, nullptr);
+ ASSERT_NE(loop_flow->continuing_target, nullptr);
+ ASSERT_NE(loop_flow->merge_target, nullptr);
+
+ auto* ir_break_if = b.FlowNodeForAstNode(ast_break_if);
+ ASSERT_NE(ir_break_if, nullptr);
+ ASSERT_TRUE(ir_break_if->Is<ir::If>());
+
+ auto* break_if_flow = ir_break_if->As<ir::If>();
+ ASSERT_NE(break_if_flow->true_target, nullptr);
+ ASSERT_NE(break_if_flow->false_target, nullptr);
+ ASSERT_NE(break_if_flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, loop_flow);
+ EXPECT_EQ(loop_flow->start_target->branch_target, loop_flow->continuing_target);
+ EXPECT_EQ(loop_flow->continuing_target->branch_target, break_if_flow);
+ EXPECT_EQ(break_if_flow->true_target->branch_target, loop_flow->merge_target);
+ EXPECT_EQ(break_if_flow->false_target->branch_target, break_if_flow->merge_target);
+ EXPECT_EQ(break_if_flow->merge_target->branch_target, loop_flow->start_target);
+ EXPECT_EQ(loop_flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, Loop_WithReturn) {
+ // func -> start -> loop -> loop start -> if -> true block
+ // -> false block
+ //
+ // [if true] -> func end
+ // [if false] -> if merge
+ // [if merge] -> loop continuing
+ // [loop continuing] -> loop start
+ // [loop merge] -> func end
+ //
+ auto* ast_if = If(true, Block(Return()));
+ auto* ast_loop = Loop(Block(ast_if, Continue()));
+ WrapInFunction(ast_loop);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop = b.FlowNodeForAstNode(ast_loop);
+ ASSERT_NE(ir_loop, nullptr);
+ EXPECT_TRUE(ir_loop->Is<ir::Loop>());
+
+ auto* loop_flow = ir_loop->As<ir::Loop>();
+ ASSERT_NE(loop_flow->start_target, nullptr);
+ ASSERT_NE(loop_flow->continuing_target, nullptr);
+ ASSERT_NE(loop_flow->merge_target, nullptr);
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ ASSERT_TRUE(ir_if->Is<ir::If>());
+
+ auto* if_flow = ir_if->As<ir::If>();
+ ASSERT_NE(if_flow->true_target, nullptr);
+ ASSERT_NE(if_flow->false_target, nullptr);
+ ASSERT_NE(if_flow->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(loop_flow->start_target->branch_target, if_flow);
+ EXPECT_EQ(if_flow->true_target->branch_target, func->end_target);
+ EXPECT_EQ(if_flow->false_target->branch_target, if_flow->merge_target);
+
+ EXPECT_EQ(loop_flow->continuing_target->branch_target, loop_flow->start_target);
+
+ EXPECT_EQ(func->start_target->branch_target, ir_loop);
+ EXPECT_EQ(loop_flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, Loop_WithIf_BothBranchesBreak) {
+ // func -> start -> loop -> loop start -> if -> true branch
+ // -> false branch
+ //
+ // [if true] -> loop merge
+ // [if false] -> loop merge
+ // [if merge] -> nullptr
+ // [loop continuing] -> loop start
+ // [loop merge] -> func end
+ //
+ auto* ast_if = If(true, Block(Break()), Else(Block(Break())));
+ auto* ast_loop = Loop(Block(ast_if, Continue()));
+ WrapInFunction(ast_loop);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop = b.FlowNodeForAstNode(ast_loop);
+ ASSERT_NE(ir_loop, nullptr);
+ EXPECT_TRUE(ir_loop->Is<ir::Loop>());
+
+ auto* loop_flow = ir_loop->As<ir::Loop>();
+ ASSERT_NE(loop_flow->start_target, nullptr);
+ ASSERT_NE(loop_flow->continuing_target, nullptr);
+ ASSERT_NE(loop_flow->merge_target, nullptr);
+
+ auto* ir_if = b.FlowNodeForAstNode(ast_if);
+ ASSERT_NE(ir_if, nullptr);
+ ASSERT_TRUE(ir_if->Is<ir::If>());
+
+ auto* if_flow = ir_if->As<ir::If>();
+ ASSERT_NE(if_flow->true_target, nullptr);
+ ASSERT_NE(if_flow->false_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ // Note, the `continue` is dead code because both if branches go out of loop, so it just gets
+ // dropped.
+
+ EXPECT_EQ(func->start_target->branch_target, loop_flow);
+ EXPECT_EQ(loop_flow->start_target->branch_target, if_flow);
+ EXPECT_EQ(if_flow->true_target->branch_target, loop_flow->merge_target);
+ EXPECT_EQ(if_flow->false_target->branch_target, loop_flow->merge_target);
+ EXPECT_EQ(if_flow->merge_target, nullptr);
+ EXPECT_EQ(loop_flow->continuing_target->branch_target, loop_flow->start_target);
+ EXPECT_EQ(loop_flow->merge_target->branch_target, func->end_target);
+}
+
+TEST_F(IRBuilderImplTest, Loop_Nested) {
+ // loop { // loop_a
+ // loop { // loop_b
+ // if (true) { break; } // if_a
+ // if (true) { continue; } // if_b
+ // continuing {
+ // loop { // loop_c
+ // break;
+ // }
+ //
+ // loop { // loop_d
+ // continuing {
+ // break if (true); // if_c
+ // }
+ // }
+ // }
+ // }
+ // if (true) { break; } // if_d
+ // }
+ //
+ // func -> start -> loop_a -> loop_a start
+ //
+ // [loop_a start] -> loop_b
+ // [loop_b start] -> if_a
+ // [if_a true] -> loop_b merge
+ // [if_a false] -> if_a merge
+ // [if_a merge] -> if_b
+ // [if_b true] -> loop_b continuing
+ // [if_b false] -> if_b merge
+ // [if_b merge] -> loop_b continug
+ // [loop_b continuing] -> loop_c
+ // [loop_c start] -> loop_c merge
+ // [loop_c continuing] -> loop_c start
+ // [loop_c merge] -> loop_d
+ // [loop_d start] -> loop_d continuing
+ // [loop_d continuing] -> if_c
+ // [if_c true] -> loop_d merge
+ // [if_c false] -> if_c merge
+ // [if c merge] -> loop_d start
+ // [loop_d merge] -> loop_b start
+ // [loop_b merge] -> if_d
+ // [if_d true] -> loop_a merge
+ // [if_d false] -> if_d merge
+ // [if_d merge] -> loop_a continuing
+ // [loop_a continuing] -> loop_a start
+ // [loop_a merge] -> func end
+ //
+
+ auto* ast_if_a = If(true, Block(Break()));
+ auto* ast_if_b = If(true, Block(Continue()));
+ auto* ast_if_c = BreakIf(true);
+ auto* ast_if_d = If(true, Block(Break()));
+
+ auto* ast_loop_d = Loop(Block(), Block(ast_if_c));
+ auto* ast_loop_c = Loop(Block(Break()));
+
+ auto* ast_loop_b = Loop(Block(ast_if_a, ast_if_b), Block(ast_loop_c, ast_loop_d));
+ auto* ast_loop_a = Loop(Block(ast_loop_b, ast_if_d));
+
+ WrapInFunction(ast_loop_a);
+ auto& b = Build();
+
+ auto r = b.Build();
+ ASSERT_TRUE(r) << b.error();
+ auto m = r.Move();
+
+ auto* ir_loop_a = b.FlowNodeForAstNode(ast_loop_a);
+ ASSERT_NE(ir_loop_a, nullptr);
+ EXPECT_TRUE(ir_loop_a->Is<ir::Loop>());
+ auto* loop_flow_a = ir_loop_a->As<ir::Loop>();
+ ASSERT_NE(loop_flow_a->start_target, nullptr);
+ ASSERT_NE(loop_flow_a->continuing_target, nullptr);
+ ASSERT_NE(loop_flow_a->merge_target, nullptr);
+
+ auto* ir_loop_b = b.FlowNodeForAstNode(ast_loop_b);
+ ASSERT_NE(ir_loop_b, nullptr);
+ EXPECT_TRUE(ir_loop_b->Is<ir::Loop>());
+ auto* loop_flow_b = ir_loop_b->As<ir::Loop>();
+ ASSERT_NE(loop_flow_b->start_target, nullptr);
+ ASSERT_NE(loop_flow_b->continuing_target, nullptr);
+ ASSERT_NE(loop_flow_b->merge_target, nullptr);
+
+ auto* ir_loop_c = b.FlowNodeForAstNode(ast_loop_c);
+ ASSERT_NE(ir_loop_c, nullptr);
+ EXPECT_TRUE(ir_loop_c->Is<ir::Loop>());
+ auto* loop_flow_c = ir_loop_c->As<ir::Loop>();
+ ASSERT_NE(loop_flow_c->start_target, nullptr);
+ ASSERT_NE(loop_flow_c->continuing_target, nullptr);
+ ASSERT_NE(loop_flow_c->merge_target, nullptr);
+
+ auto* ir_loop_d = b.FlowNodeForAstNode(ast_loop_d);
+ ASSERT_NE(ir_loop_d, nullptr);
+ EXPECT_TRUE(ir_loop_d->Is<ir::Loop>());
+ auto* loop_flow_d = ir_loop_d->As<ir::Loop>();
+ ASSERT_NE(loop_flow_d->start_target, nullptr);
+ ASSERT_NE(loop_flow_d->continuing_target, nullptr);
+ ASSERT_NE(loop_flow_d->merge_target, nullptr);
+
+ auto* ir_if_a = b.FlowNodeForAstNode(ast_if_a);
+ ASSERT_NE(ir_if_a, nullptr);
+ EXPECT_TRUE(ir_if_a->Is<ir::If>());
+ auto* if_flow_a = ir_if_a->As<ir::If>();
+ ASSERT_NE(if_flow_a->true_target, nullptr);
+ ASSERT_NE(if_flow_a->false_target, nullptr);
+ ASSERT_NE(if_flow_a->merge_target, nullptr);
+
+ auto* ir_if_b = b.FlowNodeForAstNode(ast_if_b);
+ ASSERT_NE(ir_if_b, nullptr);
+ EXPECT_TRUE(ir_if_b->Is<ir::If>());
+ auto* if_flow_b = ir_if_b->As<ir::If>();
+ ASSERT_NE(if_flow_b->true_target, nullptr);
+ ASSERT_NE(if_flow_b->false_target, nullptr);
+ ASSERT_NE(if_flow_b->merge_target, nullptr);
+
+ auto* ir_if_c = b.FlowNodeForAstNode(ast_if_c);
+ ASSERT_NE(ir_if_c, nullptr);
+ EXPECT_TRUE(ir_if_c->Is<ir::If>());
+ auto* if_flow_c = ir_if_c->As<ir::If>();
+ ASSERT_NE(if_flow_c->true_target, nullptr);
+ ASSERT_NE(if_flow_c->false_target, nullptr);
+ ASSERT_NE(if_flow_c->merge_target, nullptr);
+
+ auto* ir_if_d = b.FlowNodeForAstNode(ast_if_d);
+ ASSERT_NE(ir_if_d, nullptr);
+ EXPECT_TRUE(ir_if_d->Is<ir::If>());
+ auto* if_flow_d = ir_if_d->As<ir::If>();
+ ASSERT_NE(if_flow_d->true_target, nullptr);
+ ASSERT_NE(if_flow_d->false_target, nullptr);
+ ASSERT_NE(if_flow_d->merge_target, nullptr);
+
+ ASSERT_EQ(1u, m.functions.Length());
+ auto* func = m.functions[0];
+
+ EXPECT_EQ(func->start_target->branch_target, loop_flow_a);
+ EXPECT_EQ(loop_flow_a->start_target->branch_target, loop_flow_b);
+ EXPECT_EQ(loop_flow_b->start_target->branch_target, if_flow_a);
+ EXPECT_EQ(if_flow_a->true_target->branch_target, loop_flow_b->merge_target);
+ EXPECT_EQ(if_flow_a->false_target->branch_target, if_flow_a->merge_target);
+ EXPECT_EQ(if_flow_a->merge_target->branch_target, if_flow_b);
+ EXPECT_EQ(if_flow_b->true_target->branch_target, loop_flow_b->continuing_target);
+ EXPECT_EQ(if_flow_b->false_target->branch_target, if_flow_b->merge_target);
+ EXPECT_EQ(if_flow_b->merge_target->branch_target, loop_flow_b->continuing_target);
+ EXPECT_EQ(loop_flow_b->continuing_target->branch_target, loop_flow_c);
+ EXPECT_EQ(loop_flow_c->start_target->branch_target, loop_flow_c->merge_target);
+ EXPECT_EQ(loop_flow_c->continuing_target->branch_target, loop_flow_c->start_target);
+ EXPECT_EQ(loop_flow_c->merge_target->branch_target, loop_flow_d);
+ EXPECT_EQ(loop_flow_d->start_target->branch_target, loop_flow_d->continuing_target);
+ EXPECT_EQ(loop_flow_d->continuing_target->branch_target, if_flow_c);
+ EXPECT_EQ(if_flow_c->true_target->branch_target, loop_flow_d->merge_target);
+ EXPECT_EQ(if_flow_c->false_target->branch_target, if_flow_c->merge_target);
+ EXPECT_EQ(if_flow_c->merge_target->branch_target, loop_flow_d->start_target);
+ EXPECT_EQ(loop_flow_d->merge_target->branch_target, loop_flow_b->start_target);
+ EXPECT_EQ(loop_flow_b->merge_target->branch_target, if_flow_d);
+ EXPECT_EQ(if_flow_d->true_target->branch_target, loop_flow_a->merge_target);
+ EXPECT_EQ(if_flow_d->false_target->branch_target, if_flow_d->merge_target);
+ EXPECT_EQ(if_flow_d->merge_target->branch_target, loop_flow_a->continuing_target);
+ EXPECT_EQ(loop_flow_a->continuing_target->branch_target, loop_flow_a->start_target);
+ EXPECT_EQ(loop_flow_a->merge_target->branch_target, func->end_target);
+}
+
+} // namespace
+} // namespace tint::ir
diff --git a/src/tint/ir/flow_node.cc b/src/tint/ir/flow_node.cc
new file mode 100644
index 0000000..bbbd78b
--- /dev/null
+++ b/src/tint/ir/flow_node.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/flow_node.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::FlowNode);
+
+namespace tint::ir {
+
+FlowNode::FlowNode() = default;
+
+FlowNode::~FlowNode() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/flow_node.h b/src/tint/ir/flow_node.h
new file mode 100644
index 0000000..0158af4
--- /dev/null
+++ b/src/tint/ir/flow_node.h
@@ -0,0 +1,34 @@
+// Copyright 2022 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_FLOW_NODE_H_
+#define SRC_TINT_IR_FLOW_NODE_H_
+
+#include "src/tint/castable.h"
+
+namespace tint::ir {
+
+/// Base class for flow nodes
+class FlowNode : public Castable<FlowNode> {
+ public:
+ ~FlowNode() override;
+
+ protected:
+ /// Constructor
+ FlowNode();
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_FLOW_NODE_H_
diff --git a/src/tint/ir/function.cc b/src/tint/ir/function.cc
new file mode 100644
index 0000000..f74550e
--- /dev/null
+++ b/src/tint/ir/function.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/function.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Function);
+
+namespace tint::ir {
+
+Function::Function(const ast::Function* f) : Base(), source(f) {}
+
+Function::~Function() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/function.h b/src/tint/ir/function.h
new file mode 100644
index 0000000..c63aec0
--- /dev/null
+++ b/src/tint/ir/function.h
@@ -0,0 +1,49 @@
+// Copyright 2022 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_FUNCTION_H_
+#define SRC_TINT_IR_FUNCTION_H_
+
+#include "src/tint/ast/function.h"
+#include "src/tint/ir/flow_node.h"
+
+// Forward declarations
+namespace tint::ir {
+class Block;
+class Terminator;
+} // namespace tint::ir
+
+namespace tint::ir {
+
+/// An IR representation of a function
+class Function : public Castable<Function, FlowNode> {
+ public:
+ /// Constructor
+ /// @param func the ast::Function to create from
+ explicit Function(const ast::Function* func);
+ ~Function() override;
+
+ /// The ast function this ir function is created from.
+ const ast::Function* source;
+
+ /// The start target is the first block in a function.
+ Block* start_target = nullptr;
+ /// The end target is the end of the function. It is used as the branch target if a return is
+ /// encountered in the function.
+ Terminator* end_target = nullptr;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_FUNCTION_H_
diff --git a/src/tint/ir/if.cc b/src/tint/ir/if.cc
new file mode 100644
index 0000000..df017d2
--- /dev/null
+++ b/src/tint/ir/if.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/if.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::If);
+
+namespace tint::ir {
+
+If::If(const ast::Statement* stmt) : Base(), source(stmt) {}
+
+If::~If() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/if.h b/src/tint/ir/if.h
new file mode 100644
index 0000000..84967b0
--- /dev/null
+++ b/src/tint/ir/if.h
@@ -0,0 +1,50 @@
+// Copyright 2022 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_IF_H_
+#define SRC_TINT_IR_IF_H_
+
+#include "src/tint/ast/if_statement.h"
+#include "src/tint/ir/flow_node.h"
+
+// Forward declarations
+namespace tint::ir {
+class Block;
+} // namespace tint::ir
+
+namespace tint::ir {
+
+/// A flow node representing an if statement. The node always contains a true and a false block. It
+/// may contain a merge block where the true/false blocks will merge too unless they return.
+class If : public Castable<If, FlowNode> {
+ public:
+ /// Constructor
+ /// @param stmt the ast::IfStatement or ast::BreakIfStatement
+ explicit If(const ast::Statement* stmt);
+ ~If() override;
+
+ /// The ast::IfStatement or ast::BreakIfStatement source for this flow node.
+ const ast::Statement* source;
+
+ /// The true branch block
+ Block* true_target = nullptr;
+ /// The false branch block
+ Block* false_target = nullptr;
+ /// An optional block where the true/false blocks will branch too if needed.
+ Block* merge_target = nullptr;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_IF_H_
diff --git a/src/tint/ir/loop.cc b/src/tint/ir/loop.cc
new file mode 100644
index 0000000..4458de6
--- /dev/null
+++ b/src/tint/ir/loop.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/loop.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Loop);
+
+namespace tint::ir {
+
+Loop::Loop(const ast::LoopStatement* stmt) : Base(), source(stmt) {}
+
+Loop::~Loop() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/loop.h b/src/tint/ir/loop.h
new file mode 100644
index 0000000..03a0190
--- /dev/null
+++ b/src/tint/ir/loop.h
@@ -0,0 +1,47 @@
+// Copyright 2022 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_LOOP_H_
+#define SRC_TINT_IR_LOOP_H_
+
+#include "src/tint/ast/loop_statement.h"
+#include "src/tint/ir/block.h"
+#include "src/tint/ir/flow_node.h"
+
+namespace tint::ir {
+
+/// Flow node describing a loop.
+class Loop : public Castable<Loop, FlowNode> {
+ public:
+ /// Constructor
+ /// @param stmt the ast::LoopStatement
+ explicit Loop(const ast::LoopStatement* stmt);
+ ~Loop() override;
+
+ /// The ast loop statement this ir loop is created from.
+ const ast::LoopStatement* source;
+
+ /// The start block is the first block in a loop.
+ Block* start_target = nullptr;
+ /// The continue target of the block.
+ Block* continuing_target = nullptr;
+ /// The loop merge target. If the `loop` does a `return` then this block may not actually
+ /// end up in the control flow. We need it if the loop does a `break` we know where to break
+ /// too.
+ Block* merge_target = nullptr;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_LOOP_H_
diff --git a/src/tint/ir/module.cc b/src/tint/ir/module.cc
new file mode 100644
index 0000000..0f9be09
--- /dev/null
+++ b/src/tint/ir/module.cc
@@ -0,0 +1,49 @@
+// Copyright 2022 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/module.h"
+
+#include "src/tint/ir/builder_impl.h"
+#include "src/tint/program.h"
+
+namespace tint::ir {
+
+// static
+Module::Result Module::FromProgram(const Program* program) {
+ if (!program->IsValid()) {
+ return Result{std::string("input program is not valid")};
+ }
+
+ BuilderImpl b(program);
+ auto r = b.Build();
+ if (!r) {
+ return b.error();
+ }
+
+ return Result{r.Move()};
+}
+
+Module::Module(const Program* prog) : program(prog) {}
+
+Module::Module(Module&&) = default;
+
+Module::~Module() = default;
+
+Module& Module::operator=(Module&&) = default;
+
+const Program* Module::ToProgram() const {
+ return nullptr;
+}
+
+} // namespace tint::ir
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
new file mode 100644
index 0000000..4d38614
--- /dev/null
+++ b/src/tint/ir/module.h
@@ -0,0 +1,81 @@
+// Copyright 2022 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_MODULE_H_
+#define SRC_TINT_IR_MODULE_H_
+
+#include <string>
+
+#include "src/tint/ir/function.h"
+#include "src/tint/utils/block_allocator.h"
+#include "src/tint/utils/result.h"
+#include "src/tint/utils/vector.h"
+
+// Forward Declarations
+namespace tint {
+class Program;
+} // namespace tint
+
+namespace tint::ir {
+
+/// Main module class for the IR.
+class Module {
+ public:
+ /// The result type for the FromProgram method.
+ using Result = utils::Result<Module, std::string>;
+
+ /// Builds an ir::Module from the given Program
+ /// @param program the Program to use.
+ /// @returns the `utiils::Result` of generating the IR. The result will contain the `ir::Module`
+ /// on success, otherwise the `std::string` error.
+ ///
+ /// @note this assumes the program |IsValid|, and has had const-eval done so
+ /// any abstract values have been calculated and converted into the relevant
+ /// concrete types.
+ static Result FromProgram(const Program* program);
+
+ /// Constructor
+ /// @param program the program this module was constructed from
+ explicit Module(const Program* program);
+ /// Move constructor
+ /// @param o the module to move from
+ Module(Module&& o);
+ /// Destructor
+ ~Module();
+
+ /// Move assign
+ /// @param o the module to assign from
+ /// @returns a reference to this module
+ Module& operator=(Module&& o);
+
+ /// Converts the module back to a Program
+ /// @returns the resulting program, or nullptr on error
+ /// (Note, this will probably turn into a utils::Result, just stubbing for now)
+ const Program* ToProgram() const;
+
+ /// The flow node allocator
+ utils::BlockAllocator<FlowNode> flow_nodes;
+
+ /// List of functions in the program
+ utils::Vector<Function*, 8> functions;
+ /// List of indexes into the functions list for the entry points
+ utils::Vector<Function*, 8> entry_points;
+
+ /// The source ast::Program this module was constucted from
+ const Program* program;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_MODULE_H_
diff --git a/src/tint/ir/switch.cc b/src/tint/ir/switch.cc
new file mode 100644
index 0000000..9ad6d30
--- /dev/null
+++ b/src/tint/ir/switch.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/switch.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Switch);
+
+namespace tint::ir {
+
+Switch::Switch() : Base() {}
+
+Switch::~Switch() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/switch.h b/src/tint/ir/switch.h
new file mode 100644
index 0000000..e9de3ae
--- /dev/null
+++ b/src/tint/ir/switch.h
@@ -0,0 +1,36 @@
+// Copyright 2022 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_SWITCH_H_
+#define SRC_TINT_IR_SWITCH_H_
+
+#include "src/tint/ir/block.h"
+#include "src/tint/ir/flow_node.h"
+
+namespace tint::ir {
+
+/// Flow node representing a switch statement
+class Switch : public Castable<Switch, FlowNode> {
+ public:
+ /// Constructor
+ Switch();
+ ~Switch() override;
+
+ /// The switch merge target
+ Block* merge_target;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_SWITCH_H_
diff --git a/src/tint/ir/terminator.cc b/src/tint/ir/terminator.cc
new file mode 100644
index 0000000..6d7678d
--- /dev/null
+++ b/src/tint/ir/terminator.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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/terminator.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Terminator);
+
+namespace tint::ir {
+
+Terminator::Terminator() : Base() {}
+
+Terminator::~Terminator() = default;
+
+} // namespace tint::ir
diff --git a/src/tint/ir/terminator.h b/src/tint/ir/terminator.h
new file mode 100644
index 0000000..9f9aba0
--- /dev/null
+++ b/src/tint/ir/terminator.h
@@ -0,0 +1,33 @@
+// Copyright 2022 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_TERMINATOR_H_
+#define SRC_TINT_IR_TERMINATOR_H_
+
+#include "src/tint/ir/flow_node.h"
+
+namespace tint::ir {
+
+/// Flow node used as the end of a function. Must only be used as the `end_target` in a function
+/// flow node. There are no instructions and no branches from this node.
+class Terminator : public Castable<Terminator, FlowNode> {
+ public:
+ /// Constructor
+ Terminator();
+ ~Terminator() override;
+};
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_TERMINATOR_H_
diff --git a/src/tint/ir/test_helper.h b/src/tint/ir/test_helper.h
new file mode 100644
index 0000000..fdc4787
--- /dev/null
+++ b/src/tint/ir/test_helper.h
@@ -0,0 +1,63 @@
+// Copyright 2022 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_TEST_HELPER_H_
+#define SRC_TINT_IR_TEST_HELPER_H_
+
+#include <memory>
+#include <utility>
+
+#include "gtest/gtest.h"
+#include "src/tint/ir/builder_impl.h"
+#include "src/tint/program_builder.h"
+
+namespace tint::ir {
+
+/// Helper class for testing
+template <typename BASE>
+class TestHelperBase : public BASE, public ProgramBuilder {
+ public:
+ TestHelperBase() = default;
+
+ ~TestHelperBase() override = default;
+
+ /// Builds and returns a BuilderImpl from the program.
+ /// @note The builder is only created once. Multiple calls to Build() will
+ /// return the same builder without rebuilding.
+ /// @return the builder
+ BuilderImpl& Build() {
+ if (gen_) {
+ return *gen_;
+ }
+ program = std::make_unique<Program>(std::move(*this));
+ diag::Formatter formatter;
+ [&]() { ASSERT_TRUE(program->IsValid()) << formatter.format(program->Diagnostics()); }();
+ gen_ = std::make_unique<BuilderImpl>(program.get());
+ return *gen_;
+ }
+
+ /// The program built with a call to Build()
+ std::unique_ptr<Program> program;
+
+ private:
+ std::unique_ptr<BuilderImpl> gen_;
+};
+using TestHelper = TestHelperBase<testing::Test>;
+
+template <typename T>
+using TestParamHelper = TestHelperBase<testing::TestWithParam<T>>;
+
+} // namespace tint::ir
+
+#endif // SRC_TINT_IR_TEST_HELPER_H_
diff --git a/src/tint/utils/result.h b/src/tint/utils/result.h
index baca65f..41fda19 100644
--- a/src/tint/utils/result.h
+++ b/src/tint/utils/result.h
@@ -16,6 +16,7 @@
#define SRC_TINT_UTILS_RESULT_H_
#include <ostream>
+#include <utility>
#include <variant>
#include "src/tint/debug.h"
@@ -47,10 +48,20 @@
: value{success} {}
/// Constructor
+ /// @param success the success result
+ Result(SUCCESS_TYPE&& success) // NOLINT(runtime/explicit):
+ : value(std::move(SUCCESS_TYPE(std::move(success)))) {}
+
+ /// Constructor
/// @param failure the failure result
Result(const FAILURE_TYPE& failure) // NOLINT(runtime/explicit):
: value{failure} {}
+ /// Constructor
+ /// @param failure the failure result
+ Result(FAILURE_TYPE&& failure) // NOLINT(runtime/explicit):
+ : value{std::move(failure)} {}
+
/// Copy constructor with success / failure casting
/// @param other the Result to copy
template <typename S,
@@ -91,6 +102,13 @@
return std::get<SUCCESS_TYPE>(value);
}
+ /// @returns the success value
+ /// @warning attempting to call this when the Result holds an failure value will result in UB.
+ SUCCESS_TYPE&& Move() {
+ Validate();
+ return std::get<SUCCESS_TYPE>(std::move(value));
+ }
+
/// @returns the failure value
/// @warning attempting to call this when the Result holds a success value will result in UB.
const FAILURE_TYPE& Failure() const {