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:
parent
14b5fb6f17
commit
eee8d88210
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -38,6 +38,7 @@ enum class System {
|
|||
AST,
|
||||
Clone,
|
||||
Inspector,
|
||||
IR,
|
||||
Program,
|
||||
ProgramBuilder,
|
||||
Reader,
|
||||
|
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue