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/resource_binding.h
|
||||||
inspector/scalar.cc
|
inspector/scalar.cc
|
||||||
inspector/scalar.h
|
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.cc
|
||||||
number.h
|
number.h
|
||||||
program_builder.cc
|
program_builder.cc
|
||||||
|
@ -789,6 +809,8 @@ if(TINT_BUILD_TESTS)
|
||||||
diagnostic/diagnostic_test.cc
|
diagnostic/diagnostic_test.cc
|
||||||
diagnostic/formatter_test.cc
|
diagnostic/formatter_test.cc
|
||||||
diagnostic/printer_test.cc
|
diagnostic/printer_test.cc
|
||||||
|
ir/builder_impl_test.cc
|
||||||
|
ir/test_helper.h
|
||||||
number_test.cc
|
number_test.cc
|
||||||
program_builder_test.cc
|
program_builder_test.cc
|
||||||
program_test.cc
|
program_test.cc
|
||||||
|
|
|
@ -38,6 +38,7 @@ enum class System {
|
||||||
AST,
|
AST,
|
||||||
Clone,
|
Clone,
|
||||||
Inspector,
|
Inspector,
|
||||||
|
IR,
|
||||||
Program,
|
Program,
|
||||||
ProgramBuilder,
|
ProgramBuilder,
|
||||||
Reader,
|
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_
|
#define SRC_TINT_UTILS_RESULT_H_
|
||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "src/tint/debug.h"
|
#include "src/tint/debug.h"
|
||||||
|
@ -46,11 +47,21 @@ struct [[nodiscard]] Result {
|
||||||
Result(const SUCCESS_TYPE& success) // NOLINT(runtime/explicit):
|
Result(const SUCCESS_TYPE& success) // NOLINT(runtime/explicit):
|
||||||
: value{success} {}
|
: value{success} {}
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
/// @param success the success result
|
||||||
|
Result(SUCCESS_TYPE&& success) // NOLINT(runtime/explicit):
|
||||||
|
: value(std::move(SUCCESS_TYPE(std::move(success)))) {}
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// @param failure the failure result
|
/// @param failure the failure result
|
||||||
Result(const FAILURE_TYPE& failure) // NOLINT(runtime/explicit):
|
Result(const FAILURE_TYPE& failure) // NOLINT(runtime/explicit):
|
||||||
: value{failure} {}
|
: 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
|
/// Copy constructor with success / failure casting
|
||||||
/// @param other the Result to copy
|
/// @param other the Result to copy
|
||||||
template <typename S,
|
template <typename S,
|
||||||
|
@ -91,6 +102,13 @@ struct [[nodiscard]] Result {
|
||||||
return std::get<SUCCESS_TYPE>(value);
|
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
|
/// @returns the failure value
|
||||||
/// @warning attempting to call this when the Result holds a success value will result in UB.
|
/// @warning attempting to call this when the Result holds a success value will result in UB.
|
||||||
const FAILURE_TYPE& Failure() const {
|
const FAILURE_TYPE& Failure() const {
|
||||||
|
|
Loading…
Reference in New Issue