spirv-reader: fix overly aggressive dominance check
When finding the true-head, false-head, and potentially the premerge-head blocks of an if-selection, there was an overly aggressive check for the true-branch or false-branch landing on a merge block interior to the if-selection. The check was determining if the merge block actually corresponded to the selection header in question. If not, then it was throwing an error. The bug was that this check must be performed only if the target in question is actually inside the selection body. There are cases where the target could represent a structured exit, e.g. to an enclosing loop's merge or continue, or an enclosing switch construct's merge. There is still a latent bug: if either the true branch or false branch represent such a kLoopBreak, kLoopContinue, or kSwitchBreak edge, then those are not properly generated. That will be fixed in a followup CL. Bug: tint:243, tint:494 Change-Id: I141cce07fa0a1dfe5fad20dd2989315e4cd7b688 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47482 Auto-Submit: David Neto <dneto@google.com> Commit-Queue: Alan Baker <alanbaker@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Alan Baker <alanbaker@google.com>
This commit is contained in:
parent
095cd1c255
commit
0386472762
|
@ -1358,7 +1358,7 @@ bool FunctionEmitter::FindSwitchCaseHeaders() {
|
||||||
if (construct->begin_pos >= default_block->pos) {
|
if (construct->begin_pos >= default_block->pos) {
|
||||||
// An OpSwitch must dominate its cases. Also, it can't be a self-loop
|
// An OpSwitch must dominate its cases. Also, it can't be a self-loop
|
||||||
// as that would be a backedge, and backedges can only target a loop,
|
// as that would be a backedge, and backedges can only target a loop,
|
||||||
// and loops use an OpLoopMerge instruction, which can't preceded an
|
// and loops use an OpLoopMerge instruction, which can't precede an
|
||||||
// OpSwitch.
|
// OpSwitch.
|
||||||
return Fail() << "Switch branch from block " << construct->begin_id
|
return Fail() << "Switch branch from block " << construct->begin_id
|
||||||
<< " to default target block " << default_id
|
<< " to default target block " << default_id
|
||||||
|
@ -1553,7 +1553,7 @@ bool FunctionEmitter::ClassifyCFGEdges() {
|
||||||
uint32_t num_backedges = 0;
|
uint32_t num_backedges = 0;
|
||||||
|
|
||||||
// Track destinations for normal forward edges, either kForward
|
// Track destinations for normal forward edges, either kForward
|
||||||
// or kCaseFallThroughkIfBreak. These count toward the need
|
// or kCaseFallThrough. These count toward the need
|
||||||
// to have a merge instruction. We also track kIfBreak edges
|
// to have a merge instruction. We also track kIfBreak edges
|
||||||
// because when used with normal forward edges, we'll need
|
// because when used with normal forward edges, we'll need
|
||||||
// to generate a flow guard variable.
|
// to generate a flow guard variable.
|
||||||
|
@ -1797,6 +1797,15 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
|
||||||
const bool contains_true = construct->ContainsPos(true_head_pos);
|
const bool contains_true = construct->ContainsPos(true_head_pos);
|
||||||
const bool contains_false = construct->ContainsPos(false_head_pos);
|
const bool contains_false = construct->ContainsPos(false_head_pos);
|
||||||
|
|
||||||
|
// The cases for each edge are:
|
||||||
|
// - kBack: invalid because it's an invalid exit from the selection
|
||||||
|
// - kSwitchBreak
|
||||||
|
// - kLoopBreak
|
||||||
|
// - kLoopContinue
|
||||||
|
// - kIfBreak; normal case, may require a guard variable.
|
||||||
|
// - kFallThrough; invalid exit from the selection
|
||||||
|
// - kForward; normal case
|
||||||
|
|
||||||
if (contains_true) {
|
if (contains_true) {
|
||||||
if_header_info->true_head = true_head;
|
if_header_info->true_head = true_head;
|
||||||
}
|
}
|
||||||
|
@ -1804,11 +1813,12 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
|
||||||
if_header_info->false_head = false_head;
|
if_header_info->false_head = false_head;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((true_head_info->header_for_merge != 0) &&
|
if (contains_true && (true_head_info->header_for_merge != 0) &&
|
||||||
(true_head_info->header_for_merge != construct->begin_id)) {
|
(true_head_info->header_for_merge != construct->begin_id)) {
|
||||||
// The OpBranchConditional instruction for the true head block is an
|
// The OpBranchConditional instruction for the true head block is an
|
||||||
// alternate path to the merge block, and hence the merge block is not
|
// alternate path to the merge block of a construct nested inside the
|
||||||
// dominated by its own (different) header.
|
// selection, and hence the merge block is not dominated by its own
|
||||||
|
// (different) header.
|
||||||
return Fail() << "Block " << true_head
|
return Fail() << "Block " << true_head
|
||||||
<< " is the true branch for if-selection header "
|
<< " is the true branch for if-selection header "
|
||||||
<< construct->begin_id
|
<< construct->begin_id
|
||||||
|
@ -1816,11 +1826,12 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
|
||||||
<< true_head_info->header_for_merge
|
<< true_head_info->header_for_merge
|
||||||
<< " (violates dominance rule)";
|
<< " (violates dominance rule)";
|
||||||
}
|
}
|
||||||
if ((false_head_info->header_for_merge != 0) &&
|
if (contains_false && (false_head_info->header_for_merge != 0) &&
|
||||||
(false_head_info->header_for_merge != construct->begin_id)) {
|
(false_head_info->header_for_merge != construct->begin_id)) {
|
||||||
// The OpBranchConditional instruction for the false head block is an
|
// The OpBranchConditional instruction for the false head block is an
|
||||||
// alternate path to the merge block, and hence the merge block is not
|
// alternate path to the merge block of a construct nested inside the
|
||||||
// dominated by its own (different) header.
|
// selection, and hence the merge block is not dominated by its own
|
||||||
|
// (different) header.
|
||||||
return Fail() << "Block " << false_head
|
return Fail() << "Block " << false_head
|
||||||
<< " is the false branch for if-selection header "
|
<< " is the false branch for if-selection header "
|
||||||
<< construct->begin_id
|
<< construct->begin_id
|
||||||
|
|
|
@ -7220,8 +7220,9 @@ TEST_F(SpvParserTest, ClassifyCFGEdges_IfBreak_WithForwardToPremerge) {
|
||||||
EXPECT_THAT(p->error(), Eq(""));
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(
|
||||||
FindIfSelectionInternalHeaders_DomViolation_Merge_CantBeTrueHeader) {
|
SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeTrueHeader) {
|
||||||
auto assembly = CommonTypes() + R"(
|
auto assembly = CommonTypes() + R"(
|
||||||
%100 = OpFunction %void None %voidfn
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
@ -7252,8 +7253,9 @@ TEST_F(SpvParserTest,
|
||||||
"merge block for header block 20 (violates dominance rule)"));
|
"merge block for header block 20 (violates dominance rule)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(
|
||||||
FindIfSelectionInternalHeaders_DomViolation_Merge_CantBeFalseHeader) {
|
SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBeFalseHeader) {
|
||||||
auto assembly = CommonTypes() + R"(
|
auto assembly = CommonTypes() + R"(
|
||||||
%100 = OpFunction %void None %voidfn
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
@ -7284,8 +7286,9 @@ TEST_F(SpvParserTest,
|
||||||
"merge block for header block 20 (violates dominance rule)"));
|
"merge block for header block 20 (violates dominance rule)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest,
|
TEST_F(
|
||||||
FindIfSelectionInternalHeaders_DomViolation_Merge_CantBePremerge) {
|
SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_DomViolation_InteriorMerge_CantBePremerge) {
|
||||||
auto assembly = CommonTypes() + R"(
|
auto assembly = CommonTypes() + R"(
|
||||||
%100 = OpFunction %void None %voidfn
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
@ -7323,6 +7326,208 @@ TEST_F(SpvParserTest,
|
||||||
"(violates dominance rule)"));
|
"(violates dominance rule)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_TrueBranch_LoopBreak_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %10
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpLoopMerge %99 %90 None
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %99 %30 ; true branch breaking is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; selection merge
|
||||||
|
OpBranch %90
|
||||||
|
|
||||||
|
%90 = OpLabel ; continue target
|
||||||
|
OpBranch %10 ; backedge
|
||||||
|
|
||||||
|
%99 = OpLabel ; loop merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_TrueBranch_LoopContinue_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %10
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpLoopMerge %99 %90 None
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %90 %30 ; true branch continue is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; selection merge
|
||||||
|
OpBranch %90
|
||||||
|
|
||||||
|
%90 = OpLabel ; continue target
|
||||||
|
OpBranch %10 ; backedge
|
||||||
|
|
||||||
|
%99 = OpLabel ; loop merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_TrueBranch_SwitchBreak_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpSelectionMerge %99 None
|
||||||
|
OpSwitch %uint_20 %99 20 %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %99 %30 ; true branch switch break is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; if-selection merge
|
||||||
|
OpBranch %99
|
||||||
|
|
||||||
|
%99 = OpLabel ; switch merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest, FindIfSelectionInternalHeaders_FalseBranch_LoopBreak_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %10
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpLoopMerge %99 %90 None
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %30 %99 ; false branch breaking is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; selection merge
|
||||||
|
OpBranch %90
|
||||||
|
|
||||||
|
%90 = OpLabel ; continue target
|
||||||
|
OpBranch %10 ; backedge
|
||||||
|
|
||||||
|
%99 = OpLabel ; loop merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_FalseBranch_LoopContinue_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%5 = OpLabel
|
||||||
|
OpBranch %10
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpLoopMerge %99 %90 None
|
||||||
|
OpBranch %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %30 %90 ; false branch continue is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; selection merge
|
||||||
|
OpBranch %90
|
||||||
|
|
||||||
|
%90 = OpLabel ; continue target
|
||||||
|
OpBranch %10 ; backedge
|
||||||
|
|
||||||
|
%99 = OpLabel ; loop merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SpvParserTest,
|
||||||
|
FindIfSelectionInternalHeaders_FalseBranch_SwitchBreak_Ok) {
|
||||||
|
// crbug.com/tint/243
|
||||||
|
auto assembly = CommonTypes() + R"(
|
||||||
|
%100 = OpFunction %void None %voidfn
|
||||||
|
|
||||||
|
%10 = OpLabel
|
||||||
|
OpSelectionMerge %99 None
|
||||||
|
OpSwitch %uint_20 %99 20 %20
|
||||||
|
|
||||||
|
%20 = OpLabel
|
||||||
|
OpSelectionMerge %40 None
|
||||||
|
OpBranchConditional %cond %30 %99 ; false branch switch break is ok
|
||||||
|
|
||||||
|
%30 = OpLabel
|
||||||
|
OpBranch %40
|
||||||
|
|
||||||
|
%40 = OpLabel ; if-selection merge
|
||||||
|
OpBranch %99
|
||||||
|
|
||||||
|
%99 = OpLabel ; switch merge
|
||||||
|
OpReturn
|
||||||
|
)";
|
||||||
|
auto p = parser(test::Assemble(assembly));
|
||||||
|
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
|
||||||
|
FunctionEmitter fe(p.get(), *spirv_function(p.get(), 100));
|
||||||
|
EXPECT_TRUE(FlowFindIfSelectionInternalHeaders(&fe));
|
||||||
|
EXPECT_THAT(p->error(), Eq(""));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SpvParserTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
|
TEST_F(SpvParserTest, EmitBody_IfBreak_FromThen_ForwardWithinThen) {
|
||||||
// Exercises the hard case where we a single OpBranchConditional has both
|
// Exercises the hard case where we a single OpBranchConditional has both
|
||||||
// IfBreak and Forward edges, within the true-branch clause.
|
// IfBreak and Forward edges, within the true-branch clause.
|
||||||
|
|
Loading…
Reference in New Issue