[spirv-reader] Avoid certain hoisting cases

If a definition occurs in the first block of an IfSelection or
SwitchSelection, then it should count as belonging to the scope of the
parent of that if or switch selection.  This prevents some bad hoisting
decisions.

Bug: tint:3, tint:213
Change-Id: I89df5e8cbc163577e19e78c2bf393eb1eec4a0aa
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27660
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: David Neto <dneto@google.com>
This commit is contained in:
David Neto 2020-08-28 19:58:12 +00:00 committed by Commit Bot service account
parent fed63103f5
commit 25b856aa64
2 changed files with 264 additions and 2 deletions

View File

@ -3317,10 +3317,19 @@ void FunctionEmitter::FindValuesNeedingNamedOrHoistedDefinition() {
const auto first_pos = def_info->block_pos;
const auto last_use_pos = def_info->last_use_pos;
const auto* const def_in_construct =
GetBlockInfo(block_order_[first_pos])->construct;
const auto* const construct_with_last_use =
GetBlockInfo(block_order_[last_use_pos])->construct;
const auto* def_in_construct =
GetBlockInfo(block_order_[first_pos])->construct;
// A definition in the first block of an kIfSelection or kSwitchSelection
// occurs before the branch, and so that definition should count as
// having been defined at the scope of the parent construct.
if (first_pos == def_in_construct->begin_pos) {
if ((def_in_construct->kind == Construct::kIfSelection) ||
(def_in_construct->kind == Construct::kSwitchSelection)) {
def_in_construct = def_in_construct->parent;
}
}
if (def_in_construct != construct_with_last_use) {
const auto* enclosing_construct =

View File

@ -929,6 +929,259 @@ Return{}
)")) << ToString(fe.ast_body());
}
TEST_F(
SpvParserTest,
EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InFunction) {
// This is a hoisting case, where the definition is in the first block
// of an if selection construct. In this case the definition should count
// as being in the parent (enclosing) construct.
//
// The definition of %1 is in an IfSelection construct and also the enclosing
// Function construct, both of which start at block %10. For the purpose of
// determining the construct containing %10, go to the parent construct of
// the IfSelection.
auto assembly = Preamble() + R"(
%pty = OpTypePointer Private %uint
%200 = OpVariable %pty Private
%cond = OpConstantTrue %bool
%100 = OpFunction %void None %voidfn
; in IfSelection construct, nested in Function construct
%10 = OpLabel
%1 = OpCopyObject %uint %uint_1
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %99
%20 = OpLabel ; in IfSelection construct
OpBranch %99
%99 = OpLabel
%3 = OpCopyObject %uint %1; in Function construct
OpStore %200 %3
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
// We don't hoist x_1 into its own mutable variable. It is emitted as
// a const definition.
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(VariableDeclStatement{
Variable{
x_1
none
__u32
{
ScalarConstructor{1}
}
}
}
If{
(
ScalarConstructor{true}
)
{
}
}
VariableDeclStatement{
Variable{
x_3
none
__u32
{
Identifier{x_1}
}
}
}
Assignment{
Identifier{x_200}
Identifier{x_3}
}
Return{}
)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest,
EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockIf_InIf) {
// This is like the previous case, but the IfSelection is nested inside
// another IfSelection.
// This tests that the hoisting algorithm goes to only one parent of
// the definining if-selection block, and doesn't jump all the way out
// to the Function construct that encloses everything.
//
// We should not hoist %1 because its definition should count as being
// in the outer IfSelection, not the inner IfSelection.
auto assembly = Preamble() + R"(
%pty = OpTypePointer Private %uint
%200 = OpVariable %pty Private
%cond = OpConstantTrue %bool
%100 = OpFunction %void None %voidfn
; outer IfSelection
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %99
; inner IfSelection
%20 = OpLabel
%1 = OpCopyObject %uint %uint_1
OpSelectionMerge %89 None
OpBranchConditional %cond %30 %89
%30 = OpLabel ; last block of inner IfSelection
OpBranch %89
; in outer IfSelection
%89 = OpLabel
%3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
OpStore %200 %3
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(If{
(
ScalarConstructor{true}
)
{
VariableDeclStatement{
Variable{
x_1
none
__u32
{
ScalarConstructor{1}
}
}
}
If{
(
ScalarConstructor{true}
)
{
}
}
VariableDeclStatement{
Variable{
x_3
none
__u32
{
Identifier{x_1}
}
}
}
Assignment{
Identifier{x_200}
Identifier{x_3}
}
}
}
Return{}
)")) << ToString(fe.ast_body());
}
TEST_F(
SpvParserTest,
EmitStatement_CombinatorialNonPointer_Hoisting_DefFirstBlockSwitch_InIf) {
// This is like the previous case, but the definition is in a SwitchSelection
// inside another IfSelection.
// Tests that definitions in the first block of a switch count as being
// in the parent of the switch construct.
auto assembly = Preamble() + R"(
%pty = OpTypePointer Private %uint
%200 = OpVariable %pty Private
%cond = OpConstantTrue %bool
%100 = OpFunction %void None %voidfn
; outer IfSelection
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %20 %99
; inner SwitchSelection
%20 = OpLabel
%1 = OpCopyObject %uint %uint_1
OpSelectionMerge %89 None
OpSwitch %uint_1 %89 0 %30
%30 = OpLabel ; last block of inner SwitchSelection
OpBranch %89
; in outer IfSelection
%89 = OpLabel
%3 = OpCopyObject %uint %1; Last use of %1, in outer IfSelection
OpStore %200 %3
OpBranch %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)";
auto* p = parser(test::Assemble(assembly));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << assembly;
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(If{
(
ScalarConstructor{true}
)
{
VariableDeclStatement{
Variable{
x_1
none
__u32
{
ScalarConstructor{1}
}
}
}
Switch{
ScalarConstructor{1}
{
Case 0{
}
Default{
}
}
}
VariableDeclStatement{
Variable{
x_3
none
__u32
{
Identifier{x_1}
}
}
}
Assignment{
Identifier{x_200}
Identifier{x_3}
}
}
}
Return{}
)")) << ToString(fe.ast_body());
}
TEST_F(SpvParserTest, EmitStatement_Phi_SingleBlockLoopIndex) {
auto assembly = Preamble() + R"(
%pty = OpTypePointer Private %uint