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>
This commit is contained in:
dan sinclair 2022-10-28 01:22:58 +00:00 committed by Dawn LUCI CQ
parent 14b5fb6f17
commit eee8d88210
26 changed files with 2374 additions and 0 deletions

411
docs/tint/ir.md Normal file
View File

@ -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.

View File

@ -248,6 +248,26 @@ set(TINT_LIB_SRCS
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 @@ if(TINT_BUILD_TESTS)
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

View File

@ -38,6 +38,7 @@ enum class System {
AST,
Clone,
Inspector,
IR,
Program,
ProgramBuilder,
Reader,

25
src/tint/ir/block.cc Normal file
View File

@ -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

37
src/tint/ir/block.h Normal file
View File

@ -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_

73
src/tint/ir/builder.cc Normal file
View File

@ -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

86
src/tint/ir/builder.h Normal file
View File

@ -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_

372
src/tint/ir/builder_impl.cc Normal file
View File

@ -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

155
src/tint/ir/builder_impl.h Normal file
View File

@ -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_

View File

@ -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

25
src/tint/ir/flow_node.cc Normal file
View File

@ -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

34
src/tint/ir/flow_node.h Normal file
View File

@ -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_

25
src/tint/ir/function.cc Normal file
View File

@ -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

49
src/tint/ir/function.h Normal file
View File

@ -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_

25
src/tint/ir/if.cc Normal file
View File

@ -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

50
src/tint/ir/if.h Normal file
View File

@ -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_

25
src/tint/ir/loop.cc Normal file
View File

@ -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

47
src/tint/ir/loop.h Normal file
View File

@ -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_

49
src/tint/ir/module.cc Normal file
View File

@ -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

81
src/tint/ir/module.h Normal file
View File

@ -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_

25
src/tint/ir/switch.cc Normal file
View File

@ -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

36
src/tint/ir/switch.h Normal file
View File

@ -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_

25
src/tint/ir/terminator.cc Normal file
View File

@ -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

33
src/tint/ir/terminator.h Normal file
View File

@ -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_

63
src/tint/ir/test_helper.h Normal file
View File

@ -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_

View File

@ -16,6 +16,7 @@
#define SRC_TINT_UTILS_RESULT_H_
#include <ostream>
#include <utility>
#include <variant>
#include "src/tint/debug.h"
@ -46,11 +47,21 @@ struct [[nodiscard]] Result {
Result(const SUCCESS_TYPE& success) // NOLINT(runtime/explicit):
: 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 @@ struct [[nodiscard]] Result {
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 {