[spirv-reader] Emit break-if as needed.

This CL updates the SPIRV-Reader to emit `break-if` nodes instead of
`if-break` statements.

Bug: tint:1724
Change-Id: I8cd568f5e90a950acc5a42a470345273a5f1e6bc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/111103
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
This commit is contained in:
dan sinclair 2022-11-23 01:51:24 +00:00 committed by Dawn LUCI CQ
parent b119d3b15f
commit a8bc296259
4 changed files with 152 additions and 64 deletions

View File

@ -3210,8 +3210,8 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
return false; return false;
} }
AddStatement(create<ast::ReturnStatement>(Source{}, value.expr)); AddStatement(create<ast::ReturnStatement>(Source{}, value.expr));
}
return true; return true;
}
case spv::Op::OpKill: case spv::Op::OpKill:
// For now, assume SPIR-V OpKill has same semantics as WGSL discard. // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
// TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676 // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
@ -3266,21 +3266,58 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
return Fail() << "Fallthrough not supported in WGSL"; return Fail() << "Fallthrough not supported in WGSL";
} }
// In the case of a continuing block a `break-if` needs to be emitted for either an
// if-break or an if-else-break statement. This only happens inside the continue block.
// It's possible for a continue block to also be the loop block, so checks are needed
// that this is a continue construct and the header construct will cause a continuing
// construct to be emitted. (i.e. the header is not `continue is entire loop`.
bool needs_break_if = false;
if ((true_kind == EdgeKind::kLoopBreak || false_kind == EdgeKind::kLoopBreak) &&
block_info.construct && block_info.construct->kind == Construct::Kind::kContinue) {
auto* header = GetBlockInfo(block_info.construct->begin_id);
TINT_ASSERT(Reader, header->construct &&
header->construct->kind == Construct::Kind::kContinue);
if (!header->is_continue_entire_loop) {
needs_break_if = true;
}
}
// At this point, at most one edge is kForward or kIfBreak. // At this point, at most one edge is kForward or kIfBreak.
// Emit an 'if' statement to express the *other* branch as a conditional // If this is a continuing block and a `break` is to be emitted, then this needs to be
// break or continue. Either or both of these could be nullptr. // converted to a `break-if`. This may involve inverting the condition if this was a
// (A nullptr is generated for kIfBreak, kForward, or kBack.) // `break-unless`.
// Also if one of the branches is an if-break out of an if-selection if (needs_break_if) {
// requiring a flow guard, then get that flow guard name too. It will if (true_kind == EdgeKind::kLoopBreak && false_kind == EdgeKind::kLoopBreak) {
// come from at most one of these two branches. // Both branches break ... ?
std::string flow_guard; return Fail() << "Both branches of if inside continuing break.";
auto* true_branch = MakeBranchDetailed(block_info, *true_info, &flow_guard); }
auto* false_branch = MakeBranchDetailed(block_info, *false_info, &flow_guard);
AddStatement(MakeSimpleIf(cond, true_branch, false_branch)); if (true_kind == EdgeKind::kLoopBreak) {
if (!flow_guard.empty()) { AddStatement(create<ast::BreakIfStatement>(Source{}, cond));
PushGuard(flow_guard, statements_stack_.Back().GetEndId()); } else {
AddStatement(create<ast::BreakIfStatement>(
Source{},
create<ast::UnaryOpExpression>(Source{}, ast::UnaryOp::kNot, cond)));
}
return true;
} else {
// Emit an 'if' statement to express the *other* branch as a conditional
// break or continue. Either or both of these could be nullptr.
// (A nullptr is generated for kIfBreak, kForward, or kBack.)
// Also if one of the branches is an if-break out of an if-selection
// requiring a flow guard, then get that flow guard name too. It will
// come from at most one of these two branches.
std::string flow_guard;
auto* true_branch = MakeBranchDetailed(block_info, *true_info, &flow_guard);
auto* false_branch = MakeBranchDetailed(block_info, *false_info, &flow_guard);
AddStatement(MakeSimpleIf(cond, true_branch, false_branch));
if (!flow_guard.empty()) {
PushGuard(flow_guard, statements_stack_.Back().GetEndId());
}
} }
return true; return true;
} }

View File

@ -638,9 +638,8 @@ TEST_F(SpvParserCFGTest, RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsN
EXPECT_FALSE(bi99->is_continue_entire_loop); EXPECT_FALSE(bi99->is_continue_entire_loop);
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) {
RegisterMerges_GoodLoopMerge_MultiBlockLoop_ContinueIsNotHeader_BranchConditional) { // NOLINT
auto p = parser(test::Assemble(CommonTypes() + R"( auto p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -2605,7 +2604,7 @@ TEST_F(SpvParserCFGTest, VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDom
} }
TEST_F(SpvParserCFGTest, TEST_F(SpvParserCFGTest,
VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) { // NOLINT VerifyHeaderContinueMergeOrder_HeaderDoesNotStrictlyDominateContinueTarget) {
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -4451,9 +4450,8 @@ TEST_F(SpvParserCFGTest, ClassifyCFGEdges_BackEdge_MultiBlockLoop_SingleBlockCon
EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack); EXPECT_EQ(bi40->succ_edge[20], EdgeKind::kBack);
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) {
ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsNotHeader) { // NOLINT
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -4489,9 +4487,8 @@ TEST_F(
EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack); EXPECT_EQ(bi50->succ_edge[20], EdgeKind::kBack);
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) {
ClassifyCFGEdges_BackEdge_MultiBlockLoop_MultiBlockContinueConstruct_ContinueIsHeader) { // NOLINT
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -6051,7 +6048,7 @@ TEST_F(SpvParserCFGTest, ClassifyCFGEdges_DomViolation_AfterContinueToContinueIn
} }
TEST_F(SpvParserCFGTest, TEST_F(SpvParserCFGTest,
FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) { // NOLINT FindSwitchCaseHeaders_DomViolation_SwitchCase_CantBeMergeForOtherConstruct) {
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -6762,8 +6759,7 @@ TEST_F(SpvParserCFGTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
} }
TEST_F(SpvParserCFGTest, TEST_F(SpvParserCFGTest,
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) { // NOLINT - FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {
// line length
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -6795,10 +6791,8 @@ TEST_F(SpvParserCFGTest,
"merge block for header block 20 (violates dominance rule)")); "merge block for header block 20 (violates dominance rule)"));
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) { // NOLINT - line
// length
auto assembly = CommonTypes() + R"( auto assembly = CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -9474,9 +9468,7 @@ TEST_F(SpvParserCFGTest,
} }
TEST_F(SpvParserCFGTest, TEST_F(SpvParserCFGTest,
EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) { // NOLINT EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Unconditional) {
// - line
// length
auto p = parser(test::Assemble(CommonTypes() + R"( auto p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -9519,10 +9511,46 @@ return;
ASSERT_EQ(expect, got); ASSERT_EQ(expect, got);
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional_BreakIf) {
EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) { // NOLINT - auto p = parser(test::Assemble(CommonTypes() + R"(
// line length %100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %99 %80 None
OpBranch %80
%80 = OpLabel ; continue target
OpStore %var %uint_1
OpBranchConditional %cond %99 %20 ; exit, and backedge
%99 = OpLabel
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
auto ast_body = fe.ast_body();
auto got = test::ToString(p->program(), ast_body);
auto* expect = R"(loop {
continuing {
var_1 = 1u;
break if false;
}
}
return;
)";
ASSERT_EQ(expect, got);
}
TEST_F(SpvParserCFGTest,
EmitBody_Branch_LoopBreak_MultiBlockLoop_FromContinueConstructEnd_Conditional) {
auto p = parser(test::Assemble(CommonTypes() + R"( auto p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn
@ -9551,10 +9579,48 @@ TEST_F(
continuing { continuing {
var_1 = 1u; var_1 = 1u;
if (false) { break if !(false);
} else { }
break; }
} return;
)";
ASSERT_EQ(expect, got);
}
TEST_F(SpvParserCFGTest, EmitBody_Branch_LoopBreak_FromContinueConstructTail) {
auto p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpBranch %20
%20 = OpLabel
OpLoopMerge %99 %50 None
OpBranchConditional %cond %30 %99
%30 = OpLabel
OpBranch %50
%50 = OpLabel
OpBranch %60
%60 = OpLabel
OpBranchConditional %cond %20 %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
auto fe = p->function_emitter(100);
EXPECT_TRUE(fe.EmitBody()) << p->error();
auto ast_body = fe.ast_body();
auto got = test::ToString(p->program(), ast_body);
auto* expect = R"(loop {
if (false) {
} else {
break;
}
continuing {
break if !(false);
} }
} }
return; return;
@ -10009,9 +10075,7 @@ loop {
var_1 = 1u; var_1 = 1u;
continuing { continuing {
if (false) { break if false;
break;
}
} }
} }
var_1 = 5u; var_1 = 5u;
@ -10052,10 +10116,7 @@ loop {
var_1 = 1u; var_1 = 1u;
continuing { continuing {
if (false) { break if !(false);
} else {
break;
}
} }
} }
var_1 = 5u; var_1 = 5u;
@ -10878,9 +10939,8 @@ return;
ASSERT_EQ(expect, got); ASSERT_EQ(expect, got);
} }
TEST_F( TEST_F(SpvParserCFGTest,
SpvParserCFGTest, EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) {
EmitBody_BranchConditional_Continue_Continue_AfterHeader_Conditional_EmptyContinuing) { // NOLINT
// Like the previous tests, but with an empty continuing clause. // Like the previous tests, but with an empty continuing clause.
auto p = parser(test::Assemble(CommonTypes() + R"( auto p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn %100 = OpFunction %void None %voidfn

View File

@ -689,9 +689,7 @@ loop {
continuing { continuing {
x_1 = 4u; x_1 = 4u;
if (false) { break if false;
break;
}
} }
} }
x_1 = 5u; x_1 = 5u;
@ -1610,9 +1608,7 @@ OpFunctionEnd
x_999 = false; x_999 = false;
continuing { continuing {
if (true) { break if true;
break;
}
} }
} }
} }
@ -1694,9 +1690,7 @@ loop {
x_999 = false; x_999 = false;
continuing { continuing {
if (true) { break if true;
break;
}
} }
} }

View File

@ -103,10 +103,7 @@ fn insert_i1_i1_(treeIndex : ptr<function, i32>, data_1 : ptr<function, i32>) {
continuing { continuing {
let x_382 : f32 = x_27.injectionSwitch.x; let x_382 : f32 = x_27.injectionSwitch.x;
let x_384 : f32 = x_27.injectionSwitch.y; let x_384 : f32 = x_27.injectionSwitch.y;
if ((x_382 > x_384)) { break if !(x_382 > x_384);
} else {
break;
}
} }
} }
continue; continue;