[IR] Add support for `fallthrough`.

This CL adds `fallthrough` support to the IR building. Fallthrough is
deprecated but needs to be supported until removed from Tint.

Bug: tint:1718
Change-Id: I52ce4cc9953384a450ad09422b2ba38943284a42
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/107802
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
dan sinclair 2022-11-01 17:03:05 +00:00 committed by Dawn LUCI CQ
parent c22b8b9dc8
commit 50c29f6d38
3 changed files with 93 additions and 7 deletions

View File

@ -19,6 +19,7 @@
#include "src/tint/ast/break_if_statement.h" #include "src/tint/ast/break_if_statement.h"
#include "src/tint/ast/break_statement.h" #include "src/tint/ast/break_statement.h"
#include "src/tint/ast/continue_statement.h" #include "src/tint/ast/continue_statement.h"
#include "src/tint/ast/fallthrough_statement.h"
#include "src/tint/ast/function.h" #include "src/tint/ast/function.h"
#include "src/tint/ast/if_statement.h" #include "src/tint/ast/if_statement.h"
#include "src/tint/ast/return_statement.h" #include "src/tint/ast/return_statement.h"
@ -204,7 +205,7 @@ bool BuilderImpl::EmitStatement(const ast::Statement* stmt) {
// [&](const ast::CallStatement* c) { }, // [&](const ast::CallStatement* c) { },
[&](const ast::ContinueStatement* c) { return EmitContinue(c); }, [&](const ast::ContinueStatement* c) { return EmitContinue(c); },
// [&](const ast::DiscardStatement* d) { }, // [&](const ast::DiscardStatement* d) { },
// [&](const ast::FallthroughStatement*) { }, [&](const ast::FallthroughStatement*) { return EmitFallthrough(); },
[&](const ast::IfStatement* i) { return EmitIf(i); }, [&](const ast::IfStatement* i) { return EmitIf(i); },
[&](const ast::LoopStatement* l) { return EmitLoop(l); }, [&](const ast::LoopStatement* l) { return EmitLoop(l); },
// [&](const ast::ForLoopStatement* l) { }, // [&](const ast::ForLoopStatement* l) { },
@ -324,12 +325,25 @@ bool BuilderImpl::EmitSwitch(const ast::SwitchStatement* stmt) {
{ {
FlowStackScope scope(this, switch_node); FlowStackScope scope(this, switch_node);
// TODO(crbug.com/tint/1644): This can be simplifed when fallthrough is removed, a single
// loop can be used to iterate each body statement and emit for the case. Two loops are
// needed in order to have the target for a fallthrough.
for (const auto* c : stmt->body) { for (const auto* c : stmt->body) {
current_flow_block_ = builder_.CreateCase(switch_node, c->selectors); builder_.CreateCase(switch_node, c->selectors);
if (!EmitStatement(c->body)) { }
for (size_t i = 0; i < stmt->body.Length(); ++i) {
current_flow_block_ = switch_node->cases[i].start_target;
if (i < (stmt->body.Length() - 1)) {
fallthrough_target_ = switch_node->cases[i + 1].start_target;
}
if (!EmitStatement(stmt->body[i]->body)) {
return false; return false;
} }
BranchToIfNeeded(switch_node->merge_target); BranchToIfNeeded(switch_node->merge_target);
fallthrough_target_ = nullptr;
} }
} }
current_flow_block_ = nullptr; current_flow_block_ = nullptr;
@ -419,4 +433,10 @@ bool BuilderImpl::EmitBreakIf(const ast::BreakIfStatement* stmt) {
return true; return true;
} }
bool BuilderImpl::EmitFallthrough() {
TINT_ASSERT(IR, fallthrough_target_ != nullptr);
BranchTo(fallthrough_target_);
return true;
}
} // namespace tint::ir } // namespace tint::ir

View File

@ -104,24 +104,28 @@ class BuilderImpl {
/// Emits a switch statement /// Emits a switch statement
/// @param stmt the switch statement /// @param stmt the switch statement
/// @returns true if successfull, false otherwise. /// @returns true if successful, false otherwise.
bool EmitSwitch(const ast::SwitchStatement* stmt); bool EmitSwitch(const ast::SwitchStatement* stmt);
/// Emits a break statement /// Emits a break statement
/// @param stmt the break statement /// @param stmt the break statement
/// @returns true if successfull, false otherwise. /// @returns true if successful, false otherwise.
bool EmitBreak(const ast::BreakStatement* stmt); bool EmitBreak(const ast::BreakStatement* stmt);
/// Emits a continue statement /// Emits a continue statement
/// @param stmt the continue statement /// @param stmt the continue statement
/// @returns true if successfull, false otherwise. /// @returns true if successful, false otherwise.
bool EmitContinue(const ast::ContinueStatement* stmt); bool EmitContinue(const ast::ContinueStatement* stmt);
/// Emits a break-if statement /// Emits a break-if statement
/// @param stmt the break-if statement /// @param stmt the break-if statement
/// @returns true if successfull, false otherwise. /// @returns true if successful, false otherwise.
bool EmitBreakIf(const ast::BreakIfStatement* stmt); bool EmitBreakIf(const ast::BreakIfStatement* stmt);
/// Emits a fallthrough statement
/// @returns true if successful, false otherwise
bool EmitFallthrough();
/// Retrieve the IR Flow node for a given AST node. /// Retrieve the IR Flow node for a given AST node.
/// @param n the node to lookup /// @param n the node to lookup
/// @returns the FlowNode for the given ast::Node or nullptr if it doesn't exist. /// @returns the FlowNode for the given ast::Node or nullptr if it doesn't exist.
@ -150,6 +154,9 @@ class BuilderImpl {
Block* current_flow_block_ = nullptr; Block* current_flow_block_ = nullptr;
Function* current_function_ = nullptr; Function* current_function_ = nullptr;
// TODO(crbug.com/tint/1644): Remove this when fallthrough is removed.
Block* fallthrough_target_ = nullptr;
/// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node. /// Map from ast nodes to flow nodes, used to retrieve the flow node for a given AST node.
/// Used for testing purposes. /// Used for testing purposes.
std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_; std::unordered_map<const ast::Node*, const FlowNode*> ast_to_flow_;

View File

@ -1039,5 +1039,64 @@ TEST_F(IRBuilderImplTest, Switch_AllReturn) {
EXPECT_EQ(flow->merge_target->branch_target, nullptr); EXPECT_EQ(flow->merge_target->branch_target, nullptr);
} }
// TODO(crbug.com/tint/1644): Remove when fallthrough is removed.
TEST_F(IRBuilderImplTest, Switch_Fallthrough) {
// func -> switch -> case 1
// -> case 2
// -> default
//
// [case 1] -> switch merge
// [case 2] -> default
// [default] -> switch merge
// [switch merge] -> func end
//
auto* ast_switch =
Switch(1_i, utils::Vector{Case(utils::Vector{CaseSelector(0_i)}, Block()),
Case(utils::Vector{CaseSelector(1_i)}, Block(Fallthrough())),
DefaultCase(Block())});
WrapInFunction(ast_switch);
auto& b = Build();
auto r = b.Build();
ASSERT_TRUE(r) << b.error();
auto m = r.Move();
auto* ir_switch = b.FlowNodeForAstNode(ast_switch);
ASSERT_NE(ir_switch, nullptr);
ASSERT_TRUE(ir_switch->Is<ir::Switch>());
auto* flow = ir_switch->As<ir::Switch>();
ASSERT_NE(flow->merge_target, nullptr);
ASSERT_EQ(3u, flow->cases.Length());
ASSERT_EQ(1u, m.functions.Length());
auto* func = m.functions[0];
ASSERT_EQ(1u, flow->cases[0].selectors.Length());
ASSERT_TRUE(flow->cases[0].selectors[0]->expr->Is<ast::IntLiteralExpression>());
EXPECT_EQ(0_i, flow->cases[0].selectors[0]->expr->As<ast::IntLiteralExpression>()->value);
ASSERT_EQ(1u, flow->cases[1].selectors.Length());
ASSERT_TRUE(flow->cases[1].selectors[0]->expr->Is<ast::IntLiteralExpression>());
EXPECT_EQ(1_i, flow->cases[1].selectors[0]->expr->As<ast::IntLiteralExpression>()->value);
ASSERT_EQ(1u, flow->cases[2].selectors.Length());
EXPECT_TRUE(flow->cases[2].selectors[0]->IsDefault());
EXPECT_EQ(1u, flow->inbound_branches.Length());
EXPECT_EQ(1u, flow->cases[0].start_target->inbound_branches.Length());
EXPECT_EQ(1u, flow->cases[1].start_target->inbound_branches.Length());
EXPECT_EQ(2u, flow->cases[2].start_target->inbound_branches.Length());
EXPECT_EQ(2u, flow->merge_target->inbound_branches.Length());
EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
EXPECT_EQ(func->start_target->branch_target, ir_switch);
EXPECT_EQ(flow->cases[0].start_target->branch_target, flow->merge_target);
EXPECT_EQ(flow->cases[1].start_target->branch_target, flow->cases[2].start_target);
EXPECT_EQ(flow->cases[2].start_target->branch_target, flow->merge_target);
EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
}
} // namespace } // namespace
} // namespace tint::ir } // namespace tint::ir