[ir] Allow branching arguments.

When branching to a different flow node, there is a need to pass
arguments to the branch. These arguments could be the value of the
return.

This extracts a `Branch` out to an object with a target and arguments
and then updates the IR to use the new Branch structure.

Bug: tint:1718
Change-Id: Ic8de8046f58056327a04c8afe3b597810c80ccdb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/116546
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
This commit is contained in:
dan sinclair 2023-01-07 23:13:35 +00:00 committed by Dawn LUCI CQ
parent 55aa801705
commit a764437138
13 changed files with 576 additions and 476 deletions

View File

@ -15,6 +15,7 @@
#ifndef SRC_TINT_IR_BLOCK_H_ #ifndef SRC_TINT_IR_BLOCK_H_
#define SRC_TINT_IR_BLOCK_H_ #define SRC_TINT_IR_BLOCK_H_
#include "src/tint/ir/branch.h"
#include "src/tint/ir/flow_node.h" #include "src/tint/ir/flow_node.h"
#include "src/tint/ir/instruction.h" #include "src/tint/ir/instruction.h"
#include "src/tint/utils/vector.h" #include "src/tint/utils/vector.h"
@ -31,7 +32,7 @@ class Block : public Castable<Block, FlowNode> {
~Block() override; ~Block() override;
/// The node this block branches too. /// The node this block branches too.
const FlowNode* branch_target = nullptr; Branch branch = {};
/// The instructions in the block /// The instructions in the block
utils::Vector<const Instruction*, 16> instructions; utils::Vector<const Instruction*, 16> instructions;

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

@ -0,0 +1,36 @@
// Copyright 2023 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_TINT_IR_BRANCH_H_
#define SRC_TINT_IR_BRANCH_H_
#include "src/tint/ir/flow_node.h"
#include "src/tint/ir/value.h"
namespace tint::ir {
/// A information on a branch to another block
struct Branch {
/// The block being branched too.
FlowNode* target = nullptr;
/// The arguments provided for that branch. These arguments could be the
/// return value in the case of a branch to the terminator, or they could
/// be the basic block arguments passed into the block.
utils::Vector<Value*, 2> args;
};
} // namespace tint::ir
#endif // SRC_TINT_IR_BRANCH_H_

View File

@ -47,48 +47,49 @@ Function* Builder::CreateFunction() {
If* Builder::CreateIf() { If* Builder::CreateIf() {
auto* ir_if = ir.flow_nodes.Create<If>(); auto* ir_if = ir.flow_nodes.Create<If>();
ir_if->true_target = CreateBlock(); ir_if->true_.target = CreateBlock();
ir_if->false_target = CreateBlock(); ir_if->false_.target = CreateBlock();
ir_if->merge_target = CreateBlock(); ir_if->merge.target = CreateBlock();
// An if always branches to both the true and false block. // An if always branches to both the true and false block.
ir_if->true_target->inbound_branches.Push(ir_if); ir_if->true_.target->inbound_branches.Push(ir_if);
ir_if->false_target->inbound_branches.Push(ir_if); ir_if->false_.target->inbound_branches.Push(ir_if);
return ir_if; return ir_if;
} }
Loop* Builder::CreateLoop() { Loop* Builder::CreateLoop() {
auto* ir_loop = ir.flow_nodes.Create<Loop>(); auto* ir_loop = ir.flow_nodes.Create<Loop>();
ir_loop->start_target = CreateBlock(); ir_loop->start.target = CreateBlock();
ir_loop->continuing_target = CreateBlock(); ir_loop->continuing.target = CreateBlock();
ir_loop->merge_target = CreateBlock(); ir_loop->merge.target = CreateBlock();
// A loop always branches to the start block. // A loop always branches to the start block.
ir_loop->start_target->inbound_branches.Push(ir_loop); ir_loop->start.target->inbound_branches.Push(ir_loop);
return ir_loop; return ir_loop;
} }
Switch* Builder::CreateSwitch() { Switch* Builder::CreateSwitch() {
auto* ir_switch = ir.flow_nodes.Create<Switch>(); auto* ir_switch = ir.flow_nodes.Create<Switch>();
ir_switch->merge_target = CreateBlock(); ir_switch->merge.target = CreateBlock();
return ir_switch; return ir_switch;
} }
Block* Builder::CreateCase(Switch* s, utils::VectorRef<Switch::CaseSelector> selectors) { Block* Builder::CreateCase(Switch* s, utils::VectorRef<Switch::CaseSelector> selectors) {
s->cases.Push(Switch::Case{selectors, CreateBlock()}); s->cases.Push(Switch::Case{selectors, {CreateBlock(), utils::Empty}});
Block* b = s->cases.Back().start_target; Block* b = s->cases.Back().start.target->As<Block>();
// Switch branches into the case block // Switch branches into the case block
b->inbound_branches.Push(s); b->inbound_branches.Push(s);
return b; return b;
} }
void Builder::Branch(Block* from, FlowNode* to) { void Builder::Branch(Block* from, FlowNode* to, utils::VectorRef<Value*> args) {
TINT_ASSERT(IR, from); TINT_ASSERT(IR, from);
TINT_ASSERT(IR, to); TINT_ASSERT(IR, to);
from->branch_target = to; from->branch.target = to;
from->branch.args = args;
to->inbound_branches.Push(from); to->inbound_branches.Push(from);
} }

View File

@ -79,7 +79,8 @@ class Builder {
/// Branches the given block to the given flow node. /// Branches the given block to the given flow node.
/// @param from the block to branch from /// @param from the block to branch from
/// @param to the node to branch too /// @param to the node to branch too
void Branch(Block* from, FlowNode* to); /// @param args arguments to the branch
void Branch(Block* from, FlowNode* to, utils::VectorRef<Value*> args);
/// Creates a constant::Value /// Creates a constant::Value
/// @param args the arguments /// @param args the arguments

View File

@ -64,7 +64,7 @@ class FlowStackScope {
}; };
bool IsBranched(const Block* b) { bool IsBranched(const Block* b) {
return b->branch_target != nullptr; return b->branch.target != nullptr;
} }
bool IsConnected(const FlowNode* b) { bool IsConnected(const FlowNode* b) {
@ -92,11 +92,11 @@ BuilderImpl::BuilderImpl(const Program* program)
BuilderImpl::~BuilderImpl() = default; BuilderImpl::~BuilderImpl() = default;
void BuilderImpl::BranchTo(FlowNode* node) { void BuilderImpl::BranchTo(FlowNode* node, utils::VectorRef<Value*> args) {
TINT_ASSERT(IR, current_flow_block); TINT_ASSERT(IR, current_flow_block);
TINT_ASSERT(IR, !IsBranched(current_flow_block)); TINT_ASSERT(IR, !IsBranched(current_flow_block));
builder.Branch(current_flow_block, node); builder.Branch(current_flow_block, node, args);
current_flow_block = nullptr; current_flow_block = nullptr;
} }
@ -265,27 +265,27 @@ bool BuilderImpl::EmitIf(const ast::IfStatement* stmt) {
{ {
FlowStackScope scope(this, if_node); FlowStackScope scope(this, if_node);
current_flow_block = if_node->true_target; current_flow_block = if_node->true_.target->As<Block>();
if (!EmitStatement(stmt->body)) { if (!EmitStatement(stmt->body)) {
return false; return false;
} }
// If the true branch did not execute control flow, then go to the merge target // If the true branch did not execute control flow, then go to the merge target
BranchToIfNeeded(if_node->merge_target); BranchToIfNeeded(if_node->merge.target);
current_flow_block = if_node->false_target; current_flow_block = if_node->false_.target->As<Block>();
if (stmt->else_statement && !EmitStatement(stmt->else_statement)) { if (stmt->else_statement && !EmitStatement(stmt->else_statement)) {
return false; return false;
} }
// If the false branch did not execute control flow, then go to the merge target // If the false branch did not execute control flow, then go to the merge target
BranchToIfNeeded(if_node->merge_target); BranchToIfNeeded(if_node->merge.target);
} }
current_flow_block = nullptr; current_flow_block = nullptr;
// If both branches went somewhere, then they both returned, continued or broke. So, // 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 // there is no need for the if merge-block and there is nothing to branch to the merge
// block anyway. // block anyway.
if (IsConnected(if_node->merge_target)) { if (IsConnected(if_node->merge.target)) {
current_flow_block = if_node->merge_target; current_flow_block = if_node->merge.target->As<Block>();
} }
return true; return true;
@ -301,15 +301,15 @@ bool BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
{ {
FlowStackScope scope(this, loop_node); FlowStackScope scope(this, loop_node);
current_flow_block = loop_node->start_target; current_flow_block = loop_node->start.target->As<Block>();
if (!EmitStatement(stmt->body)) { if (!EmitStatement(stmt->body)) {
return false; return false;
} }
// The current block didn't `break`, `return` or `continue`, go to the continuing block. // The current block didn't `break`, `return` or `continue`, go to the continuing block.
BranchToIfNeeded(loop_node->continuing_target); BranchToIfNeeded(loop_node->continuing.target);
current_flow_block = loop_node->continuing_target; current_flow_block = loop_node->continuing.target->As<Block>();
if (stmt->continuing) { if (stmt->continuing) {
if (!EmitStatement(stmt->continuing)) { if (!EmitStatement(stmt->continuing)) {
return false; return false;
@ -317,13 +317,13 @@ bool BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
} }
// Branch back to the start node if the continue target didn't branch out already // Branch back to the start node if the continue target didn't branch out already
BranchToIfNeeded(loop_node->start_target); BranchToIfNeeded(loop_node->start.target);
} }
// The loop merge can get disconnected if the loop returns directly, or the continuing target // The loop merge can get disconnected if the loop returns directly, or the continuing target
// branches, eventually, to the merge, but nothing branched to the continuing target. // branches, eventually, to the merge, but nothing branched to the continuing target.
current_flow_block = loop_node->merge_target; current_flow_block = loop_node->merge.target->As<Block>();
if (!IsConnected(loop_node->merge_target)) { if (!IsConnected(loop_node->merge.target)) {
current_flow_block = nullptr; current_flow_block = nullptr;
} }
return true; return true;
@ -332,7 +332,9 @@ bool BuilderImpl::EmitLoop(const ast::LoopStatement* stmt) {
bool BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) { bool BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) {
auto* loop_node = builder.CreateLoop(); auto* loop_node = builder.CreateLoop();
// Continue is always empty, just go back to the start // Continue is always empty, just go back to the start
builder.Branch(loop_node->continuing_target, loop_node->start_target); TINT_ASSERT(IR, loop_node->continuing.target->Is<Block>());
builder.Branch(loop_node->continuing.target->As<Block>(), loop_node->start.target,
utils::Empty);
BranchTo(loop_node); BranchTo(loop_node);
@ -341,7 +343,7 @@ bool BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) {
{ {
FlowStackScope scope(this, loop_node); FlowStackScope scope(this, loop_node);
current_flow_block = loop_node->start_target; current_flow_block = loop_node->start.target->As<Block>();
// Emit the while condition into the start target of the loop // Emit the while condition into the start target of the loop
auto reg = EmitExpression(stmt->condition); auto reg = EmitExpression(stmt->condition);
@ -351,28 +353,33 @@ bool BuilderImpl::EmitWhile(const ast::WhileStatement* stmt) {
// Create an `if (cond) {} else {break;}` control flow // Create an `if (cond) {} else {break;}` control flow
auto* if_node = builder.CreateIf(); auto* if_node = builder.CreateIf();
builder.Branch(if_node->true_target, if_node->merge_target); TINT_ASSERT(IR, if_node->true_.target->Is<Block>());
builder.Branch(if_node->false_target, loop_node->merge_target); builder.Branch(if_node->true_.target->As<Block>(), if_node->merge.target, utils::Empty);
TINT_ASSERT(IR, if_node->false_.target->Is<Block>());
builder.Branch(if_node->false_.target->As<Block>(), loop_node->merge.target, utils::Empty);
if_node->condition = reg.Get(); if_node->condition = reg.Get();
BranchTo(if_node); BranchTo(if_node);
current_flow_block = if_node->merge_target; current_flow_block = if_node->merge.target->As<Block>();
if (!EmitStatement(stmt->body)) { if (!EmitStatement(stmt->body)) {
return false; return false;
} }
BranchToIfNeeded(loop_node->continuing_target); BranchToIfNeeded(loop_node->continuing.target);
} }
// The while loop always has a path to the merge target as the break statement comes before // The while loop always has a path to the merge target as the break statement comes before
// anything inside the loop. // anything inside the loop.
current_flow_block = loop_node->merge_target; current_flow_block = loop_node->merge.target->As<Block>();
return true; return true;
} }
bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) { bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
auto* loop_node = builder.CreateLoop(); auto* loop_node = builder.CreateLoop();
builder.Branch(loop_node->continuing_target, loop_node->start_target); TINT_ASSERT(IR, loop_node->continuing.target->Is<Block>());
builder.Branch(loop_node->continuing.target->As<Block>(), loop_node->start.target,
utils::Empty);
if (stmt->initializer) { if (stmt->initializer) {
// Emit the for initializer before branching to the loop // Emit the for initializer before branching to the loop
@ -388,7 +395,7 @@ bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
{ {
FlowStackScope scope(this, loop_node); FlowStackScope scope(this, loop_node);
current_flow_block = loop_node->start_target; current_flow_block = loop_node->start.target->As<Block>();
if (stmt->condition) { if (stmt->condition) {
// Emit the condition into the target target of the loop // Emit the condition into the target target of the loop
@ -399,22 +406,26 @@ bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
// Create an `if (cond) {} else {break;}` control flow // Create an `if (cond) {} else {break;}` control flow
auto* if_node = builder.CreateIf(); auto* if_node = builder.CreateIf();
builder.Branch(if_node->true_target, if_node->merge_target); TINT_ASSERT(IR, if_node->true_.target->Is<Block>());
builder.Branch(if_node->false_target, loop_node->merge_target); builder.Branch(if_node->true_.target->As<Block>(), if_node->merge.target, utils::Empty);
TINT_ASSERT(IR, if_node->false_.target->Is<Block>());
builder.Branch(if_node->false_.target->As<Block>(), loop_node->merge.target,
utils::Empty);
if_node->condition = reg.Get(); if_node->condition = reg.Get();
BranchTo(if_node); BranchTo(if_node);
current_flow_block = if_node->merge_target; current_flow_block = if_node->merge.target->As<Block>();
} }
if (!EmitStatement(stmt->body)) { if (!EmitStatement(stmt->body)) {
return false; return false;
} }
BranchToIfNeeded(loop_node->continuing_target); BranchToIfNeeded(loop_node->continuing.target);
if (stmt->continuing) { if (stmt->continuing) {
current_flow_block = loop_node->continuing_target; current_flow_block = loop_node->continuing.target->As<Block>();
if (!EmitStatement(stmt->continuing)) { if (!EmitStatement(stmt->continuing)) {
return false; return false;
} }
@ -422,7 +433,7 @@ bool BuilderImpl::EmitForLoop(const ast::ForLoopStatement* stmt) {
} }
// The while loop always has a path to the merge target as the break statement comes before // The while loop always has a path to the merge target as the break statement comes before
// anything inside the loop. // anything inside the loop.
current_flow_block = loop_node->merge_target; current_flow_block = loop_node->merge.target->As<Block>();
return true; return true;
} }
@ -458,24 +469,29 @@ bool BuilderImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
if (!EmitStatement(c->Body()->Declaration())) { if (!EmitStatement(c->Body()->Declaration())) {
return false; return false;
} }
BranchToIfNeeded(switch_node->merge_target); BranchToIfNeeded(switch_node->merge.target);
} }
} }
current_flow_block = nullptr; current_flow_block = nullptr;
if (IsConnected(switch_node->merge_target)) { if (IsConnected(switch_node->merge.target)) {
current_flow_block = switch_node->merge_target; current_flow_block = switch_node->merge.target->As<Block>();
} }
return true; return true;
} }
bool BuilderImpl::EmitReturn(const ast::ReturnStatement*) { bool BuilderImpl::EmitReturn(const ast::ReturnStatement* stmt) {
// TODO(dsinclair): Emit the return value. Need to determine how we want to utils::Vector<Value*, 1> ret_value;
// emit this. Emit a `return_value %yy` instruction? There is no `return` if (stmt->value) {
// instruction as we just branch. auto ret = EmitExpression(stmt->value);
if (!ret) {
return false;
}
ret_value.Push(ret.Get());
}
BranchTo(current_function_->end_target); BranchTo(current_function_->end_target, std::move(ret_value));
return true; return true;
} }
@ -484,9 +500,9 @@ bool BuilderImpl::EmitBreak(const ast::BreakStatement*) {
TINT_ASSERT(IR, current_control); TINT_ASSERT(IR, current_control);
if (auto* c = current_control->As<Loop>()) { if (auto* c = current_control->As<Loop>()) {
BranchTo(c->merge_target); BranchTo(c->merge.target);
} else if (auto* s = current_control->As<Switch>()) { } else if (auto* s = current_control->As<Switch>()) {
BranchTo(s->merge_target); BranchTo(s->merge.target);
} else { } else {
TINT_UNREACHABLE(IR, diagnostics_); TINT_UNREACHABLE(IR, diagnostics_);
return false; return false;
@ -500,7 +516,7 @@ bool BuilderImpl::EmitContinue(const ast::ContinueStatement*) {
TINT_ASSERT(IR, current_control); TINT_ASSERT(IR, current_control);
if (auto* c = current_control->As<Loop>()) { if (auto* c = current_control->As<Loop>()) {
BranchTo(c->continuing_target); BranchTo(c->continuing.target);
} else { } else {
TINT_UNREACHABLE(IR, diagnostics_); TINT_UNREACHABLE(IR, diagnostics_);
} }
@ -528,17 +544,17 @@ bool BuilderImpl::EmitBreakIf(const ast::BreakIfStatement* stmt) {
auto* loop = current_control->As<Loop>(); auto* loop = current_control->As<Loop>();
current_flow_block = if_node->true_target; current_flow_block = if_node->true_.target->As<Block>();
BranchTo(loop->merge_target); BranchTo(loop->merge.target);
current_flow_block = if_node->false_target; current_flow_block = if_node->false_.target->As<Block>();
BranchTo(if_node->merge_target); BranchTo(if_node->merge.target);
current_flow_block = if_node->merge_target; current_flow_block = if_node->merge.target->As<Block>();
// The `break-if` has to be the last item in the continuing block. The false branch of the // 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-if` will always take us back to the start of the loop.
BranchTo(loop->start_target); BranchTo(loop->start.target);
return true; return true;
} }

View File

@ -202,7 +202,7 @@ class BuilderImpl {
private: private:
enum class ControlFlags { kNone, kExcludeSwitch }; enum class ControlFlags { kNone, kExcludeSwitch };
void BranchTo(ir::FlowNode* node); void BranchTo(ir::FlowNode* node, utils::VectorRef<Value*> args = {});
void BranchToIfNeeded(ir::FlowNode* node); void BranchToIfNeeded(ir::FlowNode* node);
FlowNode* FindEnclosingControl(ControlFlags flags); FlowNode* FindEnclosingControl(ControlFlags flags);

File diff suppressed because it is too large Load Diff

View File

@ -59,24 +59,24 @@ std::string Debug::AsDotGraph(const Module* mod) {
if (node_to_name.count(b) == 0) { if (node_to_name.count(b) == 0) {
out << name_for(b) << R"( [label="block"])" << std::endl; out << name_for(b) << R"( [label="block"])" << std::endl;
} }
out << name_for(b) << " -> " << name_for(b->branch_target); out << name_for(b) << " -> " << name_for(b->branch.target);
// Dashed lines to merge blocks // Dashed lines to merge blocks
if (merge_nodes.count(b->branch_target) != 0) { if (merge_nodes.count(b->branch.target) != 0) {
out << " [style=dashed]"; out << " [style=dashed]";
} }
out << std::endl; out << std::endl;
Graph(b->branch_target); Graph(b->branch.target);
}, },
[&](const ir::Switch* s) { [&](const ir::Switch* s) {
out << name_for(s) << R"( [label="switch"])" << std::endl; out << name_for(s) << R"( [label="switch"])" << std::endl;
out << name_for(s->merge_target) << R"( [label="switch merge"])" << std::endl; out << name_for(s->merge.target) << R"( [label="switch merge"])" << std::endl;
merge_nodes.insert(s->merge_target); merge_nodes.insert(s->merge.target);
size_t i = 0; size_t i = 0;
for (const auto& c : s->cases) { for (const auto& c : s->cases) {
out << name_for(c.start_target) out << name_for(c.start.target)
<< R"( [label="case )" + std::to_string(i++) + R"("])" << std::endl; << R"( [label="case )" + std::to_string(i++) + R"("])" << std::endl;
} }
out << name_for(s) << " -> {"; out << name_for(s) << " -> {";
@ -84,56 +84,56 @@ std::string Debug::AsDotGraph(const Module* mod) {
if (&c != &(s->cases[0])) { if (&c != &(s->cases[0])) {
out << ", "; out << ", ";
} }
out << name_for(c.start_target); out << name_for(c.start.target);
} }
out << "}" << std::endl; out << "}" << std::endl;
for (const auto& c : s->cases) { for (const auto& c : s->cases) {
Graph(c.start_target); Graph(c.start.target);
} }
Graph(s->merge_target); Graph(s->merge.target);
}, },
[&](const ir::If* i) { [&](const ir::If* i) {
out << name_for(i) << R"( [label="if"])" << std::endl; out << name_for(i) << R"( [label="if"])" << std::endl;
out << name_for(i->true_target) << R"( [label="true"])" << std::endl; out << name_for(i->true_.target) << R"( [label="true"])" << std::endl;
out << name_for(i->false_target) << R"( [label="false"])" << std::endl; out << name_for(i->false_.target) << R"( [label="false"])" << std::endl;
out << name_for(i->merge_target) << R"( [label="if merge"])" << std::endl; out << name_for(i->merge.target) << R"( [label="if merge"])" << std::endl;
merge_nodes.insert(i->merge_target); merge_nodes.insert(i->merge.target);
out << name_for(i) << " -> {"; out << name_for(i) << " -> {";
out << name_for(i->true_target) << ", " << name_for(i->false_target); out << name_for(i->true_.target) << ", " << name_for(i->false_.target);
out << "}" << std::endl; out << "}" << std::endl;
// Subgraph if true/false branches so they draw on the same line // Subgraph if true/false branches so they draw on the same line
out << "subgraph sub_" << name_for(i) << " {" << std::endl; out << "subgraph sub_" << name_for(i) << " {" << std::endl;
out << R"(rank="same")" << std::endl; out << R"(rank="same")" << std::endl;
out << name_for(i->true_target) << std::endl; out << name_for(i->true_.target) << std::endl;
out << name_for(i->false_target) << std::endl; out << name_for(i->false_.target) << std::endl;
out << "}" << std::endl; out << "}" << std::endl;
Graph(i->true_target); Graph(i->true_.target);
Graph(i->false_target); Graph(i->false_.target);
Graph(i->merge_target); Graph(i->merge.target);
}, },
[&](const ir::Loop* l) { [&](const ir::Loop* l) {
out << name_for(l) << R"( [label="loop"])" << std::endl; out << name_for(l) << R"( [label="loop"])" << std::endl;
out << name_for(l->start_target) << R"( [label="start"])" << std::endl; out << name_for(l->start.target) << R"( [label="start"])" << std::endl;
out << name_for(l->continuing_target) << R"( [label="continuing"])" << std::endl; out << name_for(l->continuing.target) << R"( [label="continuing"])" << std::endl;
out << name_for(l->merge_target) << R"( [label="loop merge"])" << std::endl; out << name_for(l->merge.target) << R"( [label="loop merge"])" << std::endl;
merge_nodes.insert(l->merge_target); merge_nodes.insert(l->merge.target);
// Subgraph the continuing and merge so they get drawn on the same line // Subgraph the continuing and merge so they get drawn on the same line
out << "subgraph sub_" << name_for(l) << " {" << std::endl; out << "subgraph sub_" << name_for(l) << " {" << std::endl;
out << R"(rank="same")" << std::endl; out << R"(rank="same")" << std::endl;
out << name_for(l->continuing_target) << std::endl; out << name_for(l->continuing.target) << std::endl;
out << name_for(l->merge_target) << std::endl; out << name_for(l->merge.target) << std::endl;
out << "}" << std::endl; out << "}" << std::endl;
out << name_for(l) << " -> " << name_for(l->start_target) << std::endl; out << name_for(l) << " -> " << name_for(l->start.target) << std::endl;
Graph(l->start_target); Graph(l->start.target);
Graph(l->continuing_target); Graph(l->continuing.target);
Graph(l->merge_target); Graph(l->merge.target);
}, },
[&](const ir::Terminator*) { [&](const ir::Terminator*) {
// Already done // Already done

View File

@ -66,6 +66,16 @@ void Disassembler::EmitBlockInstructions(const Block* b) {
} }
} }
size_t Disassembler::GetIdForNode(const FlowNode* node) {
auto it = flow_node_to_id_.find(node);
if (it != flow_node_to_id_.end()) {
return it->second;
}
size_t id = next_node_id_++;
flow_node_to_id_[node] = id;
return id;
}
void Disassembler::Walk(const FlowNode* node) { void Disassembler::Walk(const FlowNode* node) {
if ((visited_.count(node) > 0) || (stop_nodes_.count(node) > 0)) { if ((visited_.count(node) > 0) || (stop_nodes_.count(node) > 0)) {
return; return;
@ -75,7 +85,7 @@ void Disassembler::Walk(const FlowNode* node) {
tint::Switch( tint::Switch(
node, node,
[&](const ir::Function* f) { [&](const ir::Function* f) {
Indent() << "Function" << std::endl; Indent() << "%" << GetIdForNode(f) << " = Function" << std::endl;
{ {
ScopedIndent func_indent(&indent_size_); ScopedIndent func_indent(&indent_size_);
@ -85,60 +95,74 @@ void Disassembler::Walk(const FlowNode* node) {
Walk(f->end_target); Walk(f->end_target);
}, },
[&](const ir::Block* b) { [&](const ir::Block* b) {
Indent() << "Block" << std::endl; Indent() << "%" << GetIdForNode(b) << " = Block" << std::endl;
EmitBlockInstructions(b); EmitBlockInstructions(b);
Walk(b->branch_target);
if (b->branch.target->Is<Terminator>()) {
Indent() << "Return ";
} else {
Indent() << "Branch ";
}
out_ << GetIdForNode(b->branch.target);
for (const auto* v : b->branch.args) {
out_ << " ";
v->ToString(out_, mod_.symbols);
}
out_ << std::endl;
Walk(b->branch.target);
}, },
[&](const ir::Switch* s) { [&](const ir::Switch* s) {
Indent() << "Switch (" << s->condition << ")" << std::endl; Indent() << "%" << GetIdForNode(s) << " = Switch (" << s->condition << ")" << std::endl;
{ {
ScopedIndent switch_indent(&indent_size_); ScopedIndent switch_indent(&indent_size_);
ScopedStopNode scope(&stop_nodes_, s->merge_target); ScopedStopNode scope(&stop_nodes_, s->merge.target);
for (const auto& c : s->cases) { for (const auto& c : s->cases) {
Indent() << "Case" << std::endl; Indent() << "Case" << std::endl;
ScopedIndent case_indent(&indent_size_); ScopedIndent case_indent(&indent_size_);
Walk(c.start_target); Walk(c.start.target);
} }
} }
Indent() << "Switch Merge" << std::endl; Indent() << "Switch Merge" << std::endl;
Walk(s->merge_target); Walk(s->merge.target);
}, },
[&](const ir::If* i) { [&](const ir::If* i) {
Indent() << "if (" << i->condition << ")" << std::endl; Indent() << "%" << GetIdForNode(i) << " = if (" << i->condition << ")" << std::endl;
{ {
ScopedIndent if_indent(&indent_size_); ScopedIndent if_indent(&indent_size_);
ScopedStopNode scope(&stop_nodes_, i->merge_target); ScopedStopNode scope(&stop_nodes_, i->merge.target);
Indent() << "true branch" << std::endl; Indent() << "true branch" << std::endl;
Walk(i->true_target); Walk(i->true_.target);
Indent() << "false branch" << std::endl; Indent() << "false branch" << std::endl;
Walk(i->false_target); Walk(i->false_.target);
} }
Indent() << "if merge" << std::endl; Indent() << "if merge" << std::endl;
Walk(i->merge_target); Walk(i->merge.target);
}, },
[&](const ir::Loop* l) { [&](const ir::Loop* l) {
Indent() << "loop" << std::endl; Indent() << "%" << GetIdForNode(l) << " = loop" << std::endl;
{ {
ScopedStopNode loop_scope(&stop_nodes_, l->merge_target); ScopedStopNode loop_scope(&stop_nodes_, l->merge.target);
ScopedIndent loop_indent(&indent_size_); ScopedIndent loop_indent(&indent_size_);
{ {
ScopedStopNode inner_scope(&stop_nodes_, l->continuing_target); ScopedStopNode inner_scope(&stop_nodes_, l->continuing.target);
Indent() << "loop start" << std::endl; Indent() << "loop start" << std::endl;
Walk(l->start_target); Walk(l->start.target);
} }
Indent() << "loop continuing" << std::endl; Indent() << "loop continuing" << std::endl;
ScopedIndent continuing_indent(&indent_size_); ScopedIndent continuing_indent(&indent_size_);
Walk(l->continuing_target); Walk(l->continuing.target);
} }
Indent() << "loop merge" << std::endl; Indent() << "loop merge" << std::endl;
Walk(l->merge_target); Walk(l->merge.target);
}, },
[&](const ir::Terminator*) { Indent() << "Function end" << std::endl; }); [&](const ir::Terminator*) { Indent() << "Function end" << std::endl; });
} }

View File

@ -17,6 +17,7 @@
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include "src/tint/ir/flow_node.h" #include "src/tint/ir/flow_node.h"
@ -46,11 +47,14 @@ class Disassembler {
private: private:
std::ostream& Indent(); std::ostream& Indent();
void Walk(const FlowNode* node); void Walk(const FlowNode* node);
size_t GetIdForNode(const FlowNode* node);
const Module& mod_; const Module& mod_;
std::stringstream out_; std::stringstream out_;
std::unordered_set<const FlowNode*> visited_; std::unordered_set<const FlowNode*> visited_;
std::unordered_set<const FlowNode*> stop_nodes_; std::unordered_set<const FlowNode*> stop_nodes_;
std::unordered_map<const FlowNode*, size_t> flow_node_to_id_;
size_t next_node_id_ = 0;
uint32_t indent_size_ = 0; uint32_t indent_size_ = 0;
}; };

View File

@ -15,6 +15,7 @@
#ifndef SRC_TINT_IR_IF_H_ #ifndef SRC_TINT_IR_IF_H_
#define SRC_TINT_IR_IF_H_ #define SRC_TINT_IR_IF_H_
#include "src/tint/ir/branch.h"
#include "src/tint/ir/flow_node.h" #include "src/tint/ir/flow_node.h"
#include "src/tint/ir/value.h" #include "src/tint/ir/value.h"
@ -33,12 +34,12 @@ class If : public Castable<If, FlowNode> {
~If() override; ~If() override;
/// The true branch block /// The true branch block
Block* true_target = nullptr; Branch true_ = {};
/// The false branch block /// The false branch block
Block* false_target = nullptr; Branch false_ = {};
/// An block to reconvert the true/false barnches. The block always exists, but there maybe no /// An block to reconvert the true/false barnches. The block always exists, but there maybe no
/// branches into it. (e.g. if both branches `return`) /// branches into it. (e.g. if both branches `return`)
Block* merge_target = nullptr; Branch merge = {};
/// Value holding the condition result /// Value holding the condition result
const Value* condition = nullptr; const Value* condition = nullptr;
}; };

View File

@ -16,6 +16,7 @@
#define SRC_TINT_IR_LOOP_H_ #define SRC_TINT_IR_LOOP_H_
#include "src/tint/ir/block.h" #include "src/tint/ir/block.h"
#include "src/tint/ir/branch.h"
#include "src/tint/ir/flow_node.h" #include "src/tint/ir/flow_node.h"
namespace tint::ir { namespace tint::ir {
@ -28,13 +29,13 @@ class Loop : public Castable<Loop, FlowNode> {
~Loop() override; ~Loop() override;
/// The start block is the first block in a loop. /// The start block is the first block in a loop.
Block* start_target = nullptr; Branch start = {};
/// The continue target of the block. /// The continue target of the block.
Block* continuing_target = nullptr; Branch continuing = {};
/// The loop merge target. If the `loop` does a `return` then this block may not actually /// 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 /// end up in the control flow. We need it if the loop does a `break` we know where to break
/// too. /// too.
Block* merge_target = nullptr; Branch merge = {};
}; };
} // namespace tint::ir } // namespace tint::ir

View File

@ -17,6 +17,7 @@
#include "src/tint/constant/value.h" #include "src/tint/constant/value.h"
#include "src/tint/ir/block.h" #include "src/tint/ir/block.h"
#include "src/tint/ir/branch.h"
#include "src/tint/ir/flow_node.h" #include "src/tint/ir/flow_node.h"
#include "src/tint/ir/value.h" #include "src/tint/ir/value.h"
@ -39,7 +40,7 @@ class Switch : public Castable<Switch, FlowNode> {
/// The case selector for this node /// The case selector for this node
utils::Vector<CaseSelector, 4> selectors; utils::Vector<CaseSelector, 4> selectors;
/// The start block for the case block. /// The start block for the case block.
Block* start_target; Branch start = {};
}; };
/// Constructor /// Constructor
@ -47,7 +48,7 @@ class Switch : public Castable<Switch, FlowNode> {
~Switch() override; ~Switch() override;
/// The switch merge target /// The switch merge target
Block* merge_target; Branch merge = {};
/// The switch case statements /// The switch case statements
utils::Vector<Case, 4> cases; utils::Vector<Case, 4> cases;