[spirv-reader] Add fallthrough
Bug: tint:3 Change-Id: Ib2d337156d419ed13ef9c67aa94ac3ee90f79548 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/23041 Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
parent
416be308fc
commit
709b62528c
|
@ -2040,9 +2040,12 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
|
||||||
|
|
||||||
// The fallthrough case is special because WGSL requires the fallthrough
|
// The fallthrough case is special because WGSL requires the fallthrough
|
||||||
// statement to be last in the case clause.
|
// statement to be last in the case clause.
|
||||||
if (true_kind == EdgeKind::kCaseFallThrough ||
|
if (true_kind == EdgeKind::kCaseFallThrough) {
|
||||||
false_kind == EdgeKind::kCaseFallThrough) {
|
return EmitConditionalCaseFallThrough(block_info, std::move(cond),
|
||||||
return Fail() << "fallthrough is unhandled";
|
false_kind, *false_info, true);
|
||||||
|
} else if (false_kind == EdgeKind::kCaseFallThrough) {
|
||||||
|
return EmitConditionalCaseFallThrough(block_info, std::move(cond),
|
||||||
|
true_kind, *true_info, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, at most one edge is kForward or kIfBreak.
|
// At this point, at most one edge is kForward or kIfBreak.
|
||||||
|
@ -2067,16 +2070,21 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
|
||||||
return success();
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranch(
|
std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchInternal(
|
||||||
const BlockInfo& src_info,
|
const BlockInfo& src_info,
|
||||||
const BlockInfo& dest_info) const {
|
const BlockInfo& dest_info,
|
||||||
|
bool forced) const {
|
||||||
auto kind = src_info.succ_edge.find(dest_info.id)->second;
|
auto kind = src_info.succ_edge.find(dest_info.id)->second;
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case EdgeKind::kBack:
|
case EdgeKind::kBack:
|
||||||
// Nothing to do. The loop backedge is implicit.
|
// Nothing to do. The loop backedge is implicit.
|
||||||
break;
|
break;
|
||||||
case EdgeKind::kSwitchBreak: {
|
case EdgeKind::kSwitchBreak: {
|
||||||
// Don't bother with a break at the end of a case.
|
if (forced) {
|
||||||
|
return std::make_unique<ast::BreakStatement>();
|
||||||
|
}
|
||||||
|
// Unless forced, don't bother with a break at the end of a case/default
|
||||||
|
// clause.
|
||||||
const auto header = dest_info.header_for_merge;
|
const auto header = dest_info.header_for_merge;
|
||||||
assert(header != 0);
|
assert(header != 0);
|
||||||
const auto* exiting_construct = GetBlockInfo(header)->construct;
|
const auto* exiting_construct = GetBlockInfo(header)->construct;
|
||||||
|
@ -2148,6 +2156,52 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeSimpleIf(
|
||||||
return if_stmt;
|
return if_stmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FunctionEmitter::EmitConditionalCaseFallThrough(
|
||||||
|
const BlockInfo& src_info,
|
||||||
|
std::unique_ptr<ast::Expression> cond,
|
||||||
|
EdgeKind other_edge_kind,
|
||||||
|
const BlockInfo& other_dest,
|
||||||
|
bool fall_through_is_true_branch) {
|
||||||
|
// In WGSL, the fallthrough statement must come last in the case clause.
|
||||||
|
// So we'll emit an if statement for the other branch, and then emit
|
||||||
|
// the fallthrough.
|
||||||
|
|
||||||
|
// We have two distinct destinations. But we only get here if this
|
||||||
|
// is a normal terminator; in particular the source block is *not* the
|
||||||
|
// start of an if-selection. So at most one branch is a kForward or
|
||||||
|
// kCaseFallThrough.
|
||||||
|
if (other_edge_kind == EdgeKind::kForward) {
|
||||||
|
return Fail()
|
||||||
|
<< "internal error: normal terminator OpBranchConditional has "
|
||||||
|
"both forward and fallthrough edges";
|
||||||
|
}
|
||||||
|
if (other_edge_kind == EdgeKind::kIfBreak) {
|
||||||
|
return Fail()
|
||||||
|
<< "internal error: normal terminator OpBranchConditional has "
|
||||||
|
"both IfBreak and fallthrough edges. Violates nesting rule";
|
||||||
|
}
|
||||||
|
if (other_edge_kind == EdgeKind::kBack) {
|
||||||
|
return Fail()
|
||||||
|
<< "internal error: normal terminator OpBranchConditional has "
|
||||||
|
"both backedge and fallthrough edges. Violates nesting rule";
|
||||||
|
}
|
||||||
|
auto other_branch = MakeForcedBranch(src_info, other_dest);
|
||||||
|
if (other_branch == nullptr) {
|
||||||
|
return Fail() << "internal error: expected a branch for edge-kind "
|
||||||
|
<< int(other_edge_kind);
|
||||||
|
}
|
||||||
|
if (fall_through_is_true_branch) {
|
||||||
|
AddStatement(
|
||||||
|
MakeSimpleIf(std::move(cond), nullptr, std::move(other_branch)));
|
||||||
|
} else {
|
||||||
|
AddStatement(
|
||||||
|
MakeSimpleIf(std::move(cond), std::move(other_branch), nullptr));
|
||||||
|
}
|
||||||
|
AddStatement(std::make_unique<ast::FallthroughStatement>());
|
||||||
|
|
||||||
|
return success();
|
||||||
|
}
|
||||||
|
|
||||||
bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
|
bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
|
||||||
bool* already_emitted) {
|
bool* already_emitted) {
|
||||||
if (*already_emitted) {
|
if (*already_emitted) {
|
||||||
|
|
|
@ -330,12 +330,42 @@ class FunctionEmitter {
|
||||||
|
|
||||||
/// Returns a new statement to represent the given branch representing a
|
/// Returns a new statement to represent the given branch representing a
|
||||||
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
|
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
|
||||||
/// WGSL statement is required, the statement will be nullptr.
|
/// WGSL statement is required, the statement will be nullptr. This method
|
||||||
|
/// tries to avoid emitting a 'break' statement when that would be redundant
|
||||||
|
/// in WGSL due to implicit breaking out of a switch.
|
||||||
/// @param src_info the source block
|
/// @param src_info the source block
|
||||||
/// @param dest_info the destination block
|
/// @param dest_info the destination block
|
||||||
/// @returns the new statement, or a null statement
|
/// @returns the new statement, or a null statement
|
||||||
std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
|
std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
|
||||||
const BlockInfo& dest_info) const;
|
const BlockInfo& dest_info) const {
|
||||||
|
return MakeBranchInternal(src_info, dest_info, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new statement to represent the given branch representing a
|
||||||
|
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
|
||||||
|
/// WGSL statement is required, the statement will be nullptr.
|
||||||
|
/// @param src_info the source block
|
||||||
|
/// @param dest_info the destination block
|
||||||
|
/// @returns the new statement, or a null statement
|
||||||
|
std::unique_ptr<ast::Statement> MakeForcedBranch(
|
||||||
|
const BlockInfo& src_info,
|
||||||
|
const BlockInfo& dest_info) const {
|
||||||
|
return MakeBranchInternal(src_info, dest_info, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new statement to represent the given branch representing a
|
||||||
|
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
|
||||||
|
/// WGSL statement is required, the statement will be nullptr. When |forced|
|
||||||
|
/// is false, this method tries to avoid emitting a 'break' statement when
|
||||||
|
/// that would be redundant in WGSL due to implicit breaking out of a switch.
|
||||||
|
/// When |forced| is true, the method won't try to avoid emitting that break.
|
||||||
|
/// @param src_info the source block
|
||||||
|
/// @param dest_info the destination block
|
||||||
|
/// @param forced if true, always emit the branch (if it exists in WGSL)
|
||||||
|
/// @returns the new statement, or a null statement
|
||||||
|
std::unique_ptr<ast::Statement> MakeBranchInternal(const BlockInfo& src_info,
|
||||||
|
const BlockInfo& dest_info,
|
||||||
|
bool forced) const;
|
||||||
|
|
||||||
/// Returns a new if statement with the given statements as the then-clause
|
/// Returns a new if statement with the given statements as the then-clause
|
||||||
/// and the else-clause. Either or both clauses might be nullptr. If both
|
/// and the else-clause. Either or both clauses might be nullptr. If both
|
||||||
|
@ -349,6 +379,24 @@ class FunctionEmitter {
|
||||||
std::unique_ptr<ast::Statement> then_stmt,
|
std::unique_ptr<ast::Statement> then_stmt,
|
||||||
std::unique_ptr<ast::Statement> else_stmt) const;
|
std::unique_ptr<ast::Statement> else_stmt) const;
|
||||||
|
|
||||||
|
/// Emits the statements for an normal-terminator OpBranchConditional
|
||||||
|
/// where one branch is a case fall through (the true branch if and only
|
||||||
|
/// if |fall_through_is_true_branch| is true), and the other branch is
|
||||||
|
/// goes to a different destination, named by |other_dest|.
|
||||||
|
/// @param src_info the basic block from which we're branching
|
||||||
|
/// @param cond the branching condition
|
||||||
|
/// @param other_edge_kind the edge kind from the source block to the other
|
||||||
|
/// destination
|
||||||
|
/// @param other_dest the other branching destination
|
||||||
|
/// @param fall_through_is_true_branch true when the fall-through is the true
|
||||||
|
/// branch
|
||||||
|
/// @returns the false if emission fails
|
||||||
|
bool EmitConditionalCaseFallThrough(const BlockInfo& src_info,
|
||||||
|
std::unique_ptr<ast::Expression> cond,
|
||||||
|
EdgeKind other_edge_kind,
|
||||||
|
const BlockInfo& other_dest,
|
||||||
|
bool fall_through_is_true_branch);
|
||||||
|
|
||||||
/// Emits a normal instruction: not a terminator, label, or variable
|
/// Emits a normal instruction: not a terminator, label, or variable
|
||||||
/// declaration.
|
/// declaration.
|
||||||
/// @param inst the instruction
|
/// @param inst the instruction
|
||||||
|
|
|
@ -10237,8 +10237,63 @@ Return{}
|
||||||
)")) << ToString(fe.ast_body());
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, DISABLED_EmitBody_Branch_Fallthrough) {
|
TEST_F(SpvParserTest, EmitBody_Branch_Fallthrough) {
|
||||||
// TODO(dneto): support fallthrough first.
|
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 %30 ; uncondtional fallthrough
|
||||||
|
|
||||||
|
%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 20{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{20}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 30{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{30}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{7}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, EmitBody_Branch_Forward) {
|
TEST_F(SpvParserTest, EmitBody_Branch_Forward) {
|
||||||
|
@ -10299,8 +10354,8 @@ Return{}
|
||||||
// kLoopBreak: dup general case
|
// kLoopBreak: dup general case
|
||||||
// kLoopContinue: TESTED
|
// kLoopContinue: TESTED
|
||||||
// kIfBreak: invalid: switch and if must have distinct merge blocks
|
// kIfBreak: invalid: switch and if must have distinct merge blocks
|
||||||
// kCaseFallThrough: TODO(dneto)
|
// kCaseFallThrough: not possible, because switch break conflicts with loop
|
||||||
// kForward: TESTED
|
// break kForward: TESTED
|
||||||
//
|
//
|
||||||
// kLoopContinue with:
|
// kLoopContinue with:
|
||||||
// kBack : symmetry
|
// kBack : symmetry
|
||||||
|
@ -11039,13 +11094,143 @@ Return{}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest,
|
||||||
DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
|
EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnTrue) {
|
||||||
// TODO(dneto): needs fallthrough support
|
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
|
||||||
|
OpBranchConditional %cond %30 %99; fallthrough on true
|
||||||
|
|
||||||
|
%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 20{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{20}
|
||||||
|
}
|
||||||
|
If{
|
||||||
|
(
|
||||||
|
ScalarConstructor{false}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Else{
|
||||||
|
{
|
||||||
|
Break{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 30{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{30}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{7}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest,
|
||||||
DISABLED_EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
|
EmitBody_BranchConditional_SwitchBreak_Fallthrough_OnFalse) {
|
||||||
// TODO(dneto): needs fallthrough support
|
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
|
||||||
|
OpBranchConditional %cond %99 %30; fallthrough on false
|
||||||
|
|
||||||
|
%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 20{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{20}
|
||||||
|
}
|
||||||
|
If{
|
||||||
|
(
|
||||||
|
ScalarConstructor{false}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Break{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 30{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{30}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{7}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest,
|
||||||
|
@ -11349,12 +11534,53 @@ Return{}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest,
|
||||||
DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnTrue) {
|
EmitBody_BranchConditional_LoopBreak_Fallthrough_IsError) {
|
||||||
// TODO(dneto): needs fallthrough support
|
// It's an error because switch break conflicts with loop break.
|
||||||
}
|
auto* p = parser(test::Assemble(CommonTypes() + R"(
|
||||||
TEST_F(SpvParserTest,
|
%100 = OpFunction %void None %voidfn
|
||||||
DISABLED_EmitBody_BranchConditional_LoopBreak_Fallthrough_OnFalse) {
|
|
||||||
// TODO(dneto): needs fallthrough support
|
%10 = OpLabel
|
||||||
|
OpStore %var %uint_0
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpStore %var %uint_1
|
||||||
|
OpLoopMerge %99 %80 None
|
||||||
|
OpBranch %30
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpSelectionMerge %79 None
|
||||||
|
OpSwitch %selector %79 40 %40 50 %50
|
||||||
|
|
||||||
|
%40 = OpLabel
|
||||||
|
OpStore %var %uint_40
|
||||||
|
; error: branch to 99 bypasses switch's merge
|
||||||
|
OpBranchConditional %cond %99 %50 ; loop break; fall through
|
||||||
|
|
||||||
|
%50 = OpLabel
|
||||||
|
OpStore %var %uint_50
|
||||||
|
OpBranch %79
|
||||||
|
|
||||||
|
%79 = OpLabel ; switch merge
|
||||||
|
OpBranch %80
|
||||||
|
|
||||||
|
%80 = OpLabel ; continue target
|
||||||
|
OpStore %var %uint_4
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%99 = OpLabel
|
||||||
|
OpStore %var %uint_5
|
||||||
|
OpReturn
|
||||||
|
|
||||||
|
OpFunctionEnd
|
||||||
|
)"));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p, *spirv_function(100));
|
||||||
|
EXPECT_FALSE(fe.EmitBody()) << p->error();
|
||||||
|
EXPECT_THAT(
|
||||||
|
p->error(),
|
||||||
|
Eq("Branch from block 40 to block 99 is an invalid exit from construct "
|
||||||
|
"starting at block 30; branch bypasses merge block 79"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_LoopBreak_Forward_OnTrue) {
|
||||||
|
@ -12057,13 +12283,214 @@ Return{}
|
||||||
)")) << ToString(fe.ast_body());
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
|
||||||
DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnTrue) {
|
auto* p = parser(test::Assemble(CommonTypes() + R"(
|
||||||
// TODO(dneto): needs fallthrough support
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpStore %var %uint_0
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpStore %var %uint_1
|
||||||
|
OpLoopMerge %99 %80 None
|
||||||
|
OpBranch %30
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpStore %var %uint_2
|
||||||
|
OpSelectionMerge %79 None
|
||||||
|
OpSwitch %selector %79 40 %40 50 %50
|
||||||
|
|
||||||
|
%40 = OpLabel
|
||||||
|
OpStore %var %uint_40
|
||||||
|
OpBranchConditional %cond %50 %80 ; loop continue; fall through on true
|
||||||
|
|
||||||
|
%50 = OpLabel
|
||||||
|
OpStore %var %uint_50
|
||||||
|
OpBranch %79
|
||||||
|
|
||||||
|
%79 = OpLabel ; switch merge
|
||||||
|
OpStore %var %uint_3
|
||||||
|
OpBranch %80
|
||||||
|
|
||||||
|
%80 = OpLabel ; continue target
|
||||||
|
OpStore %var %uint_4
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%99 = OpLabel
|
||||||
|
OpStore %var %uint_5
|
||||||
|
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{0}
|
||||||
}
|
}
|
||||||
TEST_F(SpvParserTest,
|
Loop{
|
||||||
DISABLED_EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
|
Assignment{
|
||||||
// TODO(dneto): needs fallthrough support
|
Identifier{var}
|
||||||
|
ScalarConstructor{1}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{2}
|
||||||
|
}
|
||||||
|
Switch{
|
||||||
|
ScalarConstructor{42}
|
||||||
|
{
|
||||||
|
Case 40{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{40}
|
||||||
|
}
|
||||||
|
If{
|
||||||
|
(
|
||||||
|
ScalarConstructor{false}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Else{
|
||||||
|
{
|
||||||
|
Continue{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 50{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{50}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{3}
|
||||||
|
}
|
||||||
|
continuing {
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{4}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{5}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Fallthrough_OnFalse) {
|
||||||
|
auto* p = parser(test::Assemble(CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpStore %var %uint_0
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpStore %var %uint_1
|
||||||
|
OpLoopMerge %99 %80 None
|
||||||
|
OpBranch %30
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpStore %var %uint_2
|
||||||
|
OpSelectionMerge %79 None
|
||||||
|
OpSwitch %selector %79 40 %40 50 %50
|
||||||
|
|
||||||
|
%40 = OpLabel
|
||||||
|
OpStore %var %uint_40
|
||||||
|
OpBranchConditional %cond %80 %50 ; loop continue; fall through on false
|
||||||
|
|
||||||
|
%50 = OpLabel
|
||||||
|
OpStore %var %uint_50
|
||||||
|
OpBranch %79
|
||||||
|
|
||||||
|
%79 = OpLabel ; switch merge
|
||||||
|
OpStore %var %uint_3
|
||||||
|
OpBranch %80
|
||||||
|
|
||||||
|
%80 = OpLabel ; continue target
|
||||||
|
OpStore %var %uint_4
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%99 = OpLabel
|
||||||
|
OpStore %var %uint_5
|
||||||
|
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{0}
|
||||||
|
}
|
||||||
|
Loop{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{1}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{2}
|
||||||
|
}
|
||||||
|
Switch{
|
||||||
|
ScalarConstructor{42}
|
||||||
|
{
|
||||||
|
Case 40{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{40}
|
||||||
|
}
|
||||||
|
If{
|
||||||
|
(
|
||||||
|
ScalarConstructor{false}
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Continue{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 50{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{50}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{3}
|
||||||
|
}
|
||||||
|
continuing {
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{4}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{5}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Continue_Forward_OnTrue) {
|
||||||
|
@ -12298,16 +12725,103 @@ TEST_F(SpvParserTest,
|
||||||
"starting at block 20; branch bypasses merge block 89"));
|
"starting at block 20; branch bypasses merge block 89"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
|
||||||
DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Same) {
|
auto* p = parser(test::Assemble(CommonTypes() + R"(
|
||||||
// Can only be to the same target.
|
%100 = OpFunction %void None %voidfn
|
||||||
// TODO(dneto): needs fallthrough support
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpStore %var %uint_1
|
||||||
|
OpSelectionMerge %99 None
|
||||||
|
OpSwitch %selector %99 20 %20 30 %30
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpStore %var %uint_20
|
||||||
|
OpBranchConditional %cond %30 %30 ; fallthrough fallthrough
|
||||||
|
|
||||||
|
%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 20{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{20}
|
||||||
|
}
|
||||||
|
Fallthrough{}
|
||||||
|
}
|
||||||
|
Case 30{
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{30}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Default{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assignment{
|
||||||
|
Identifier{var}
|
||||||
|
ScalarConstructor{7}
|
||||||
|
}
|
||||||
|
Return{}
|
||||||
|
)")) << ToString(fe.ast_body());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(
|
TEST_F(SpvParserTest,
|
||||||
SpvParserTest,
|
EmitBody_BranchConditional_Fallthrough_NotLastInCase_IsError) {
|
||||||
DISABLED_EmitBody_BranchConditional_Fallthrough_Fallthrough_Different_IsError) {
|
// See also
|
||||||
// TODO(dneto): needs fallthrough support
|
// ClassifyCFGEdges_Fallthrough_BranchConditionalWith_Forward_IsError.
|
||||||
|
auto* p = parser(test::Assemble(CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpSelectionMerge %99 None
|
||||||
|
OpSwitch %selector %99 20 %20 40 %40
|
||||||
|
|
||||||
|
%20 = OpLabel ; case 30
|
||||||
|
OpSelectionMerge %39 None
|
||||||
|
OpBranchConditional %cond %40 %30 ; fallthrough and forward
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %39
|
||||||
|
|
||||||
|
%39 = OpLabel
|
||||||
|
OpBranch %99
|
||||||
|
|
||||||
|
%40 = OpLabel ; case 40
|
||||||
|
OpBranch %99
|
||||||
|
|
||||||
|
%99 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
|
||||||
|
OpFunctionEnd
|
||||||
|
)"));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p, *spirv_function(100));
|
||||||
|
EXPECT_FALSE(fe.EmitBody());
|
||||||
|
// The weird forward branch pulls in 40 as part of the selection rather than
|
||||||
|
// as a case.
|
||||||
|
EXPECT_THAT(fe.block_order(), ElementsAre(10, 20, 40, 30, 39, 99));
|
||||||
|
EXPECT_THAT(
|
||||||
|
p->error(),
|
||||||
|
Eq("Branch from 10 to 40 bypasses header 20 (dominance rule violated)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, EmitBody_BranchConditional_Forward_Forward_Same) {
|
TEST_F(SpvParserTest, EmitBody_BranchConditional_Forward_Forward_Same) {
|
||||||
|
|
Loading…
Reference in New Issue