[spirv-reader] Don't hoist pointers that are already in scope.
Bug: tint:3, tint:213
Change-Id: I79410c670b8690c1f1ea86b7b9427f272a4ebbbb
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/27720
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index 5fb6b2c..724ff47 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -3247,12 +3247,21 @@
// Update the usage span for IDs used by this instruction.
// But skip uses in OpPhi because they are handled differently.
if (inst.opcode() != SpvOpPhi) {
- inst.ForEachInId([this, block_pos](const uint32_t* id_ptr) {
+ inst.ForEachInId([this, block_pos, block_info](const uint32_t* id_ptr) {
auto* def_info = GetDefInfo(*id_ptr);
if (def_info) {
def_info->num_uses++;
def_info->last_use_pos =
std::max(def_info->last_use_pos, block_pos);
+
+ // Determine whether this ID is defined in a different construct
+ // from this use.
+ const auto defining_block = block_order_[def_info->block_pos];
+ const auto* def_in_construct =
+ GetBlockInfo(defining_block)->construct;
+ if (def_in_construct != block_info->construct) {
+ def_info->used_in_another_construct = true;
+ }
}
});
}
@@ -3317,8 +3326,6 @@
const auto first_pos = def_info->block_pos;
const auto last_use_pos = def_info->last_use_pos;
- 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
@@ -3331,7 +3338,22 @@
}
}
- if (def_in_construct != construct_with_last_use) {
+ bool should_hoist = false;
+ if (!def_in_construct->ContainsPos(last_use_pos)) {
+ // To satisfy scoping, we have to hoist the definition out to an enclosing
+ // construct.
+ should_hoist = true;
+ } else {
+ // Avoid moving combinatorial values across constructs. This is a
+ // simple heuristic to avoid changing the cost of an operation
+ // by moving it into or out of a loop, for example.
+ if ((def_info->storage_class == ast::StorageClass::kNone) &&
+ def_info->used_in_another_construct) {
+ should_hoist = true;
+ }
+ }
+
+ if (should_hoist) {
const auto* enclosing_construct =
GetEnclosingScope(first_pos, last_use_pos);
if (enclosing_construct == def_in_construct) {
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 6d3ebb3..007c756 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -223,6 +223,10 @@
/// at all. The "last" ordering is determined by the function block order.
uint32_t last_use_pos = 0;
+ /// Is this value used in a construct other than the one in which it was
+ /// defined?
+ bool used_in_another_construct = false;
+
/// True if this ID requires a WGSL 'const' definition, due to context. It
/// might get one anyway (so this is *not* an if-and-only-if condition).
bool requires_named_const_def = false;
@@ -248,7 +252,7 @@
std::string phi_var;
/// The storage class to use for this value, if it is of pointer type.
- /// This is required to carry a stroage class override from a storage
+ /// This is required to carry a storage class override from a storage
/// buffer expressed in the old style (with Uniform storage class)
/// that needs to be remapped to StorageBuffer storage class.
/// This is kNone for non-pointers.
diff --git a/src/reader/spirv/function_var_test.cc b/src/reader/spirv/function_var_test.cc
index 2867a74..ebbac3d 100644
--- a/src/reader/spirv/function_var_test.cc
+++ b/src/reader/spirv/function_var_test.cc
@@ -728,7 +728,7 @@
EmitStatement_CombinatorialValue_Immediate_UsedOnceDifferentConstruct) {
// Translation should not sink expensive operations into or out of control
// flow. As a simple heuristic, don't move *any* combinatorial operation
- // across any constrol flow.
+ // across any control flow.
auto assembly = Preamble() + R"(
%100 = OpFunction %void None %voidfn
@@ -1182,6 +1182,74 @@
)")) << ToString(fe.ast_body());
}
+TEST_F(SpvParserTest,
+ EmitStatement_CombinatorialNonPointer_Hoisting_DefAndUseFirstBlockIf) {
+ // In this test, both the defintion and the use are in the first block
+ // of an IfSelection. No hoisting occurs because hoisting is triggered
+ // on whether the defining construct contains the last use, rather than
+ // whether the two constructs are the same.
+ //
+ // This example has two SSA IDs which are tempting to hoist but should not:
+ // %1 is defined and used in the first block of an IfSelection.
+ // Do not hoist it.
+ auto assembly = Preamble() + R"(
+ %cond = OpConstantTrue %bool
+
+ %100 = OpFunction %void None %voidfn
+
+ ; in IfSelection construct, nested in Function construct
+ %10 = OpLabel
+ %1 = OpCopyObject %uint %uint_1
+ %2 = OpCopyObject %uint %1
+ OpSelectionMerge %99 None
+ OpBranchConditional %cond %20 %99
+
+ %20 = OpLabel ; in IfSelection construct
+ 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();
+
+ // 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}
+ }
+ }
+}
+VariableDeclStatement{
+ Variable{
+ x_2
+ none
+ __u32
+ {
+ Identifier{x_1}
+ }
+ }
+}
+If{
+ (
+ ScalarConstructor{true}
+ )
+ {
+ }
+}
+Return{}
+)")) << ToString(fe.ast_body());
+}
+
TEST_F(SpvParserTest, EmitStatement_Phi_SingleBlockLoopIndex) {
auto assembly = Preamble() + R"(
%pty = OpTypePointer Private %uint