[spirv-reader] Add switch-selection
- Avoid redundant switch-break.
WGSL does an implicit break at the end of a switch case, because
it has fallthrough.
TODO: Emit fallthrough
Bug: tint:3
Change-Id: Ida44b13181a01a2c1459c0447dac496ba5b97ffc
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22961
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 3c42772..723f351 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -14,6 +14,7 @@
#include "src/reader/spirv/function.h"
+#include <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -28,6 +29,7 @@
#include "src/ast/assignment_statement.h"
#include "src/ast/binary_expression.h"
#include "src/ast/break_statement.h"
+#include "src/ast/case_statement.h"
#include "src/ast/continue_statement.h"
#include "src/ast/else_statement.h"
#include "src/ast/fallthrough_statement.h"
@@ -38,6 +40,7 @@
#include "src/ast/member_accessor_expression.h"
#include "src/ast/return_statement.h"
#include "src/ast/scalar_constructor_expression.h"
+#include "src/ast/sint_literal.h"
#include "src/ast/storage_class.h"
#include "src/ast/switch_statement.h"
#include "src/ast/uint_literal.h"
@@ -387,7 +390,7 @@
uint32_t end_id,
CompletionAction completion_action,
ast::StatementList statements,
- ast::CaseStatementList cases)
+ std::unique_ptr<ast::CaseStatementList> cases)
: construct_(construct),
end_id_(end_id),
completion_action_(completion_action),
@@ -401,9 +404,8 @@
void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
uint32_t end_id,
CompletionAction action) {
- statements_stack_.emplace_back(StatementBlock(construct, end_id, action,
- ast::StatementList{},
- ast::CaseStatementList{}));
+ statements_stack_.emplace_back(
+ StatementBlock{construct, end_id, action, ast::StatementList{}, nullptr});
}
const ast::StatementList& FunctionEmitter::ast_body() {
@@ -981,7 +983,6 @@
// Process case targets.
for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
- const auto o = branch->GetInOperand(iarg);
const auto value = branch->GetInOperand(iarg).AsLiteralUint64();
const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
@@ -1715,8 +1716,14 @@
break;
case Construct::kSwitchSelection:
+ if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
+ return false;
+ }
+ if (!EmitSwitchStart(block_info)) {
+ return false;
+ }
has_normal_terminator = false;
- return Fail() << "unhandled: switch construct";
+ break;
}
}
@@ -1827,6 +1834,128 @@
return success();
}
+bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
+ // The block is the if-header block. So its construct is the if construct.
+ auto* construct = block_info.construct;
+ assert(construct->kind == Construct::kSwitchSelection);
+ assert(construct->begin_id == block_info.id);
+ const auto* branch = block_info.basic_block->terminator();
+
+ auto* const switch_stmt =
+ AddStatement(std::make_unique<ast::SwitchStatement>())->AsSwitch();
+ const auto selector_id = branch->GetSingleWordInOperand(0);
+ // Generate the code for the selector.
+ auto selector = MakeExpression(selector_id);
+ switch_stmt->set_condition(std::move(selector.expr));
+
+ // First, push the statement block for the entire switch. All the actual
+ // work is done by completion actions of the case/default clauses.
+ PushNewStatementBlock(
+ construct, construct->end_id, [switch_stmt](StatementBlock* s) {
+ switch_stmt->set_body(std::move(*std::move(s->cases_)));
+ });
+ statements_stack_.back().cases_ = std::make_unique<ast::CaseStatementList>();
+ // Grab a pointer to the case list. It will get buried in the statement block
+ // stack.
+ auto* cases = statements_stack_.back().cases_.get();
+
+ // We will push statement-blocks onto the stack to gather the statements in
+ // the default clause and cases clauses. Determine the list of blocks
+ // that start each clause.
+ std::vector<const BlockInfo*> clause_heads;
+
+ // Collect the case clauses, even if they are just the merge block.
+ // First the default clause.
+ const auto default_id = branch->GetSingleWordInOperand(1);
+ const auto* default_info = GetBlockInfo(default_id);
+ clause_heads.push_back(default_info);
+ // Now the case clauses.
+ for (uint32_t iarg = 2; iarg + 1 < branch->NumInOperands(); iarg += 2) {
+ const auto case_target_id = branch->GetSingleWordInOperand(iarg + 1);
+ clause_heads.push_back(GetBlockInfo(case_target_id));
+ }
+
+ std::stable_sort(clause_heads.begin(), clause_heads.end(),
+ [](const BlockInfo* lhs, const BlockInfo* rhs) {
+ return lhs->pos < rhs->pos;
+ });
+ // Remove duplicates
+ {
+ // Use read index r, and write index w.
+ // Invariant: w <= r;
+ size_t w = 0;
+ for (size_t r = 0; r < clause_heads.size(); ++r) {
+ if (clause_heads[r] != clause_heads[w]) {
+ ++w; // Advance the write cursor.
+ }
+ clause_heads[w] = clause_heads[r];
+ }
+ // We know it's not empty because it always has at least a default clause.
+ assert(!clause_heads.empty());
+ clause_heads.resize(w + 1);
+ }
+
+ // Push them on in reverse order.
+ const auto last_clause_index = clause_heads.size() - 1;
+ for (size_t i = last_clause_index;; --i) {
+ // Create the case clause. Temporarily put it in the wrong order
+ // on the case statement list.
+ cases->emplace_back(std::make_unique<ast::CaseStatement>());
+ auto* clause = cases->back().get();
+
+ // Create a list of integer literals for the selector values leading to
+ // this case clause.
+ ast::CaseSelectorList selectors;
+ const auto* values_ptr = clause_heads[i]->case_values.get();
+ const bool has_selectors = (values_ptr && !values_ptr->empty());
+ if (has_selectors) {
+ std::vector<uint64_t> values(values_ptr->begin(), values_ptr->end());
+ std::stable_sort(values.begin(), values.end());
+ for (auto value : values) {
+ // The rest of this module can handle up to 64 bit switch values.
+ // The Tint AST handles 32-bit values.
+ const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
+ if (selector.type->is_unsigned_scalar_or_vector()) {
+ selectors.emplace_back(
+ std::make_unique<ast::UintLiteral>(selector.type, value32));
+ } else {
+ selectors.emplace_back(
+ std::make_unique<ast::SintLiteral>(selector.type, value32));
+ }
+ }
+ clause->set_selectors(std::move(selectors));
+ }
+
+ // Where does this clause end?
+ const auto end_id = (i + 1 < clause_heads.size()) ? clause_heads[i + 1]->id
+ : construct->end_id;
+
+ PushNewStatementBlock(construct, end_id, [clause](StatementBlock* s) {
+ clause->set_body(std::move(s->statements_));
+ });
+
+ if ((default_info == clause_heads[i]) && has_selectors &&
+ construct->ContainsPos(default_info->pos)) {
+ // Generate a default clause with a just fallthrough.
+ ast::StatementList stmts;
+ stmts.emplace_back(std::make_unique<ast::FallthroughStatement>());
+ auto case_stmt = std::make_unique<ast::CaseStatement>();
+ case_stmt->set_body(std::move(stmts));
+ cases->emplace_back(std::move(case_stmt));
+ }
+
+ if (i == 0) {
+ break;
+ }
+ }
+
+ // We've listed cases in reverse order in the switch statement. Reorder them
+ // to match the presentation order in WGSL.
+ std::reverse(cases->begin(), cases->end());
+
+ return success();
+}
+
bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
auto* loop = AddStatement(std::make_unique<ast::LoopStatement>())->AsLoop();
PushNewStatementBlock(
@@ -1946,7 +2075,31 @@
case EdgeKind::kBack:
// Nothing to do. The loop backedge is implicit.
break;
- case EdgeKind::kSwitchBreak:
+ case EdgeKind::kSwitchBreak: {
+ // Don't bother with a break at the end of a case.
+ const auto header = dest_info.header_for_merge;
+ assert(header != 0);
+ const auto* exiting_construct = GetBlockInfo(header)->construct;
+ assert(exiting_construct->kind == Construct::kSwitchSelection);
+ const auto candidate_next_case_pos = src_info.pos + 1;
+ // Leaving the last block from the last case?
+ if (candidate_next_case_pos == dest_info.pos) {
+ // No break needed.
+ return nullptr;
+ }
+ // Leaving the last block from not-the-last-case?
+ if (exiting_construct->ContainsPos(candidate_next_case_pos)) {
+ const auto* candidate_next_case =
+ GetBlockInfo(block_order_[candidate_next_case_pos]);
+ if (candidate_next_case->case_head_for == exiting_construct ||
+ candidate_next_case->default_head_for == exiting_construct) {
+ // No break needed.
+ return nullptr;
+ }
+ }
+ // We need a break.
+ return std::make_unique<ast::BreakStatement>();
+ }
case EdgeKind::kLoopBreak:
return std::make_unique<ast::BreakStatement>();
case EdgeKind::kLoopContinue:
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index a96f7e8..a5a5347 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -289,6 +289,13 @@
/// @returns false if emission failed.
bool EmitIfStart(const BlockInfo& block_info);
+ /// Emits a SwitchStatement, including its condition expression, and sets
+ /// up the statement stack to accumulate subsequent basic blocks into
+ /// the default clause and case clauses.
+ /// @param block_info the switch-selection header block
+ /// @returns false if emission failed.
+ bool EmitSwitchStart(const BlockInfo& block_info);
+
/// Emits a LoopStatement, and pushes a new StatementBlock to accumulate
/// the remaining instructions in the current block and subsequent blocks
/// in the loop.
@@ -375,7 +382,7 @@
/// Gets the block info for a block ID, if any exists
/// @param id the SPIR-V ID of the OpLabel instruction starting the block
/// @returns the block info for the given ID, if it exists, or nullptr
- BlockInfo* GetBlockInfo(uint32_t id) {
+ BlockInfo* GetBlockInfo(uint32_t id) const {
auto where = block_info_.find(id);
if (where == block_info_.end())
return nullptr;
@@ -434,7 +441,7 @@
uint32_t end_id,
CompletionAction completion_action,
ast::StatementList statements,
- ast::CaseStatementList cases);
+ std::unique_ptr<ast::CaseStatementList> cases);
StatementBlock(StatementBlock&&);
~StatementBlock();
@@ -449,10 +456,13 @@
// Only one of |statements| or |cases| is active.
- // The list of statements being built.
+ // The list of statements being built, if this construct is not a switch.
ast::StatementList statements_;
- // The list of cases being built, for a switch.
- ast::CaseStatementList cases_;
+ // The list of switch cases being built, if this construct is a switch.
+ // The algorithm will cache a pointer to the vector. We want that pointer
+ // to be stable no matter how |statements_stack_| is resized. That's
+ // why we make this a unique_ptr rather than just a plain vector.
+ std::unique_ptr<ast::CaseStatementList> cases_;
};
/// Pushes an empty statement block onto the statements stack.
diff --git a/src/reader/spirv/function_cfg_test.cc b/src/reader/spirv/function_cfg_test.cc
index 4d64864..4cf029d 100644
--- a/src/reader/spirv/function_cfg_test.cc
+++ b/src/reader/spirv/function_cfg_test.cc
@@ -60,7 +60,9 @@
%cond3 = OpConstantFalse %bool
%uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
%selector = OpConstant %uint 42
+ %signed_selector = OpConstant %int 42
%uintfn = OpTypeFunction %uint
@@ -72,6 +74,11 @@
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%uint_7 = OpConstant %uint 7
+ %uint_8 = OpConstant %uint 8
+ %uint_20 = OpConstant %uint 20
+ %uint_30 = OpConstant %uint 30
+ %uint_40 = OpConstant %uint 40
+ %uint_50 = OpConstant %uint 50
%ptr_Private_uint = OpTypePointer Private %uint
%var = OpVariable %ptr_Private_uint Private
@@ -8757,6 +8764,486 @@
)")) << ToString(fe.ast_body());
}
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_NoCases) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+// First do no special control flow: no fallthroughs, breaks, continues.
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_OneCase) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_TwoCases) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20 30 %30
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 30{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsMerge_CasesWithDup) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20 30 %30 40 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 30{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Case 20, 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsCase_NoDupCases) {
+ // The default block is not the merge block. But not the same as a case
+ // either.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %30 20 %20 40 %40
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel ; the named default block
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ }
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_DefaultIsCase_WithDupCase) {
+ // The default block is not the merge block and is the same as a case.
+ // We emit the default case separately, but just before the labeled
+ // case, and with a fallthrough.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %30 20 %20 30 %30 40 %40
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel ; the named default block, also a case
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ }
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ Fallthrough{}
+ }
+ Case 30{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_Case_SintValue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ ; SPIR-V assembler doesn't support negative literals in switch
+ OpSwitch %signed_selector %99 20 %20 2000000000 %30 !4000000000 %40
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case -294967296{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ }
+ Case 2000000000{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Switch_Case_UintValue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20 2000000000 %30 50 %40
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 50{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ }
+ Case 2000000000{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest, EmitBody_Return_TopLevel) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -9227,8 +9714,128 @@
)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_SwitchBreak) {
- // TODO(dneto): support switch first.
+TEST_F(SpvParserTest, EmitBody_Branch_SwitchBreak_LastInCase) {
+ // When the break is last in its case, we omit it because it's implicit in
+ // WGSL.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranch %99 ; branch to merge. Last in case
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_Branch_SwitchBreak_NotLastInCase) {
+ // When the break is not last in its case, we must emit a 'break'
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpSelectionMerge %50 None
+ OpBranchConditional %cond %40 %50
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranch %99 ; branch to merge. Not last in case
+
+ %50 = OpLabel ; inner merge
+ OpStore %var %uint_50
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{50}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromBody) {
@@ -9459,6 +10066,91 @@
)")) << ToString(fe.ast_body());
}
+TEST_F(SpvParserTest, EmitBody_Branch_LoopContinue_FromSwitch) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_2
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_3
+ OpSelectionMerge %79 None
+ OpSwitch %selector %79 40 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_4
+ OpBranch %80 ; continue edge
+
+ %79 = OpLabel ; switch merge
+ OpStore %var %uint_5
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_6
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ Continue{}
+ }
+ Default{
+ }
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest, EmitBody_Branch_IfBreak_FromThen) {
// When unconditional, the if-break must be last in the then clause.
auto* p = parser(test::Assemble(CommonTypes() + R"(
@@ -9546,7 +10238,7 @@
}
TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_Fallthrough) {
- // TODO(dneto): support switch first.
+ // TODO(dneto): support fallthrough first.
}
TEST_F(SpvParserTest, EmitBody_Branch_Forward) {
@@ -9901,33 +10593,459 @@
}
TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
- // TODO(dneto): needs switch support
+ EmitBody_BranchConditional_SwitchBreak_SwitchBreak_LastInCase) {
+ // When the break is last in its case, we omit it because it's implicit in
+ // WGSL.
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranchConditional %cond2 %99 %99 ; branch to merge. Last in case
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
- // TODO(dneto): needs switch support
+ EmitBody_BranchConditional_SwitchBreak_SwitchBreak_NotLastInCase) {
+ // When the break is not last in its case, we must emit a 'break'
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpSelectionMerge %50 None
+ OpBranchConditional %cond %40 %50
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranchConditional %cond2 %99 %99 ; branch to merge. Not last in case
+
+ %50 = OpLabel ; inner merge
+ OpStore %var %uint_50
+ OpBranch %99
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{50}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
- // TODO(dneto): needs switch support
+TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Continue_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_2
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_3
+ OpSelectionMerge %79 None
+ OpSwitch %selector %79 40 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranchConditional %cond %80 %79 ; break; continue on true
+
+ %79 = OpLabel
+ OpStore %var %uint_6
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_7
+ OpBranch %20
+
+ %99 = OpLabel ; loop merge
+ OpStore %var %uint_8
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Continue{}
+ }
+ }
+ }
+ Default{
+ }
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{8}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
-TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
- // TODO(dneto): needs switch support
+TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Continue_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_2
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_3
+ OpSelectionMerge %79 None
+ OpSwitch %selector %79 40 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_40
+ OpBranchConditional %cond %79 %80 ; break; continue on false
+
+ %79 = OpLabel
+ OpStore %var %uint_6
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_7
+ OpBranch %20
+
+ %99 = OpLabel ; loop merge
+ OpStore %var %uint_8
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{40}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Continue{}
+ }
+ }
+ }
+ Default{
+ }
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{8}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Forward_OnTrue) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranchConditional %cond %30 %99 ; break; forward on true
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %99 = OpLabel ; switch merge
+ OpStore %var %uint_8
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ }
+ }
+ Else{
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{8}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_SwitchBreak_Forward_OnFalse) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpSelectionMerge %99 None
+ OpSwitch %selector %99 20 %20
+
+ %20 = OpLabel
+ OpStore %var %uint_20
+ OpBranchConditional %cond %99 %30 ; break; forward on false
+
+ %30 = OpLabel
+ OpStore %var %uint_30
+ OpBranch %99
+
+ %99 = OpLabel ; switch merge
+ OpStore %var %uint_8
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Switch{
+ ScalarConstructor{42}
+ {
+ Case 20{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{20}
+ }
+ If{
+ (
+ ScalarConstructor{false}
+ )
+ {
+ Break{}
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{30}
+ }
+ }
+ Default{
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{8}
+}
+Return{}
+)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest,
@@ -10232,11 +11350,11 @@
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnTrue) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnFalse) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
@@ -10668,6 +11786,91 @@
)")) << ToString(fe.ast_body());
}
+TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopContinue_FromSwitch) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpBranch %20
+
+ %20 = OpLabel
+ OpStore %var %uint_2
+ OpLoopMerge %99 %80 None
+ OpBranch %30
+
+ %30 = OpLabel
+ OpStore %var %uint_3
+ OpSelectionMerge %79 None
+ OpSwitch %selector %79 40 %40
+
+ %40 = OpLabel
+ OpStore %var %uint_4
+ OpBranchConditional %cond2 %80 %80; dup continue edge
+
+ %79 = OpLabel ; switch merge
+ OpStore %var %uint_5
+ OpBranch %80
+
+ %80 = OpLabel ; continue target
+ OpStore %var %uint_6
+ OpBranch %20
+
+ %99 = OpLabel
+ OpStore %var %uint_7
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
+}
+Loop{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{3}
+ }
+ Switch{
+ ScalarConstructor{42}
+ {
+ Case 40{
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{4}
+ }
+ Continue{}
+ }
+ Default{
+ }
+ }
+ }
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{5}
+ }
+ continuing {
+ Assignment{
+ Identifier{var}
+ ScalarConstructor{6}
+ }
+ }
+}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{7}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_IfBreak_OnTrue) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
@@ -10856,11 +12059,11 @@
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
@@ -11098,20 +12301,67 @@
TEST_F(SpvParserTest,
DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
// Can only be to the same target.
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
+
TEST_F(
SpvParserTest,
DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
- // TODO(dneto): needs switch support
+ // TODO(dneto): needs fallthrough support
}
-TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_Forward_Forward_Same) {
- // TODO(dneto): needs switch support
+
+TEST_F(SpvParserTest, EmitBody_BranchConditional_Forward_Forward_Same) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpStore %var %uint_1
+ OpBranchConditional %cond %99 %99; forward
+
+ %99 = OpLabel
+ OpStore %var %uint_2
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_TRUE(fe.EmitBody()) << p->error();
+ EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
+ Identifier{var}
+ ScalarConstructor{1}
}
+Assignment{
+ Identifier{var}
+ ScalarConstructor{2}
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest,
- DISABLED_EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
- // TODO(dneto): needs switch support
+ EmitBody_BranchConditional_Forward_Forward_Different_IsError) {
+ auto* p = parser(test::Assemble(CommonTypes() + R"(
+ %100 = OpFunction %void None %voidfn
+
+ %10 = OpLabel
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel
+ OpReturn
+
+ %99 = OpLabel
+ OpStore %var %uint_2
+ OpReturn
+
+ OpFunctionEnd
+ )"));
+ ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
+ FunctionEmitter fe(p, *spirv_function(100));
+ EXPECT_FALSE(fe.EmitBody());
+ EXPECT_THAT(p->error(),
+ Eq("Control flow diverges at block 10 (to 20, 99) but it is not "
+ "a structured header (it has no merge instruction)"));
}
TEST_F(SpvParserTest,