HLSL: work around FXC failures when dynamically indexing arrays in structs
FXC fails to compile code that assigns to dynamically-indexed fixed-size
arrays in structs on internal shader variables with:
error X3500: array reference cannot be used as an l-value; not natively
addressable
This CL detects this case, and transforms such assignments into copying
out the array to a local variable, assigning to that local, and then
copying the array back.
Also manually regenerate SKIPs for HLSL/FXC after this change, which
fixes 30 tests. Also exposes some "compilation aborted unexpectedly" now
that "array reference cannot be used as an l-value" has been fixed. For
tests that fail for both DXC and FXC, updating SKIPs to the DXC one to
help distinguish actual FXC bugs from valid errors.
Bug: tint:998
Bug: tint:1206
Change-Id: I09204d8d81ab27d1c257538ad702414ccc386543
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/71620
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/BUILD.gn b/src/BUILD.gn
index f88173b..1003f27 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -451,6 +451,8 @@
"transform/fold_trivial_single_use_lets.h",
"transform/for_loop_to_loop.cc",
"transform/for_loop_to_loop.h",
+ "transform/localize_struct_array_assignment.cc",
+ "transform/localize_struct_array_assignment.h",
"transform/loop_to_for_loop.cc",
"transform/loop_to_for_loop.h",
"transform/manager.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3994fed..10aeea6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -312,6 +312,8 @@
transform/fold_constants.h
transform/fold_trivial_single_use_lets.cc
transform/fold_trivial_single_use_lets.h
+ transform/localize_struct_array_assignment.cc
+ transform/localize_struct_array_assignment.h
transform/for_loop_to_loop.cc
transform/for_loop_to_loop.h
transform/glsl.cc
@@ -971,6 +973,7 @@
transform/fold_constants_test.cc
transform/fold_trivial_single_use_lets_test.cc
transform/for_loop_to_loop_test.cc
+ transform/localize_struct_array_assignment_test.cc
transform/loop_to_for_loop_test.cc
transform/module_scope_var_to_entry_point_param_test.cc
transform/multiplanar_external_texture_test.cc
diff --git a/src/program_builder.h b/src/program_builder.h
index e440474..53929af 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -2468,6 +2468,15 @@
}
}
+ /// Unmarks that the given transform `T` has been applied to this program.
+ template <typename T>
+ void UnsetTransformApplied() {
+ auto it = transforms_applied_.find(&TypeInfo::Of<T>());
+ if (it != transforms_applied_.end()) {
+ transforms_applied_.erase(it);
+ }
+ }
+
/// @returns true if the transform of type `T` was applied.
template <typename T>
bool HasTransformApplied() {
diff --git a/src/transform/localize_struct_array_assignment.cc b/src/transform/localize_struct_array_assignment.cc
new file mode 100644
index 0000000..6d73ec9
--- /dev/null
+++ b/src/transform/localize_struct_array_assignment.cc
@@ -0,0 +1,230 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/transform/localize_struct_array_assignment.h"
+
+#include <unordered_map>
+#include <utility>
+
+#include "src/ast/assignment_statement.h"
+#include "src/ast/traverse_expressions.h"
+#include "src/program_builder.h"
+#include "src/sem/expression.h"
+#include "src/sem/member_accessor_expression.h"
+#include "src/sem/reference_type.h"
+#include "src/sem/statement.h"
+#include "src/sem/variable.h"
+#include "src/transform/simplify_pointers.h"
+#include "src/utils/scoped_assignment.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::LocalizeStructArrayAssignment);
+
+namespace tint {
+namespace transform {
+
+/// Private implementation of LocalizeStructArrayAssignment transform
+class LocalizeStructArrayAssignment::State {
+ private:
+ CloneContext& ctx;
+ ProgramBuilder& b;
+
+ /// Returns true if `expr` contains an index accessor expression to a
+ /// structure member of array type.
+ bool ContainsStructArrayIndex(const ast::Expression* expr) {
+ bool result = false;
+ ast::TraverseExpressions(
+ expr, b.Diagnostics(), [&](const ast::IndexAccessorExpression* ia) {
+ // Indexing using a runtime value?
+ auto* idx_sem = ctx.src->Sem().Get(ia->index);
+ if (!idx_sem->ConstantValue().IsValid()) {
+ // Indexing a member access expr?
+ if (auto* ma = ia->object->As<ast::MemberAccessorExpression>()) {
+ // That accesses an array?
+ if (ctx.src->TypeOf(ma)->UnwrapRef()->Is<sem::Array>()) {
+ result = true;
+ return ast::TraverseAction::Stop;
+ }
+ }
+ }
+ return ast::TraverseAction::Descend;
+ });
+
+ return result;
+ }
+
+ // Returns the type and storage class of the originating variable of the lhs
+ // of the assignment statement.
+ // See https://www.w3.org/TR/WGSL/#originating-variable-section
+ std::pair<const sem::Type*, ast::StorageClass>
+ GetOriginatingTypeAndStorageClass(
+ const ast::AssignmentStatement* assign_stmt) {
+ // Get first IdentifierExpr from lhs of assignment, which should resolve to
+ // the pointer or reference of the originating variable of the assignment.
+ // TraverseExpressions traverses left to right, and this code depends on the
+ // fact that for an assignment statement, the variable will be the left-most
+ // expression.
+ // TODO(crbug.com/tint/1341): do this in the Resolver, setting the
+ // originating variable on sem::Expression.
+ const ast::IdentifierExpression* ident = nullptr;
+ ast::TraverseExpressions(assign_stmt->lhs, b.Diagnostics(),
+ [&](const ast::IdentifierExpression* id) {
+ ident = id;
+ return ast::TraverseAction::Stop;
+ });
+ auto* sem_var_user = ctx.src->Sem().Get<sem::VariableUser>(ident);
+ if (!sem_var_user) {
+ TINT_ICE(Transform, b.Diagnostics())
+ << "Expected to find variable of lhs of assignment statement";
+ return {};
+ }
+
+ auto* var = sem_var_user->Variable();
+ if (auto* ptr = var->Type()->As<sem::Pointer>()) {
+ return {ptr->StoreType(), ptr->StorageClass()};
+ }
+
+ auto* ref = var->Type()->As<sem::Reference>();
+ if (!ref) {
+ TINT_ICE(Transform, b.Diagnostics())
+ << "Expecting to find variable of type pointer or reference on lhs "
+ "of assignment statement";
+ return {};
+ }
+
+ return {ref->StoreType(), ref->StorageClass()};
+ }
+
+ public:
+ /// Constructor
+ /// @param ctx_in the CloneContext primed with the input program and
+ /// ProgramBuilder
+ explicit State(CloneContext& ctx_in) : ctx(ctx_in), b(*ctx_in.dst) {}
+
+ /// Runs the transform
+ void Run() {
+ struct Shared {
+ bool process_nested_nodes = false;
+ ast::StatementList insert_before_stmts;
+ ast::StatementList insert_after_stmts;
+ } s;
+
+ ctx.ReplaceAll([&](const ast::AssignmentStatement* assign_stmt)
+ -> const ast::Statement* {
+ // Process if it's an assignment statement to a dynamically indexed array
+ // within a struct on a function or private storage variable. This
+ // specific use-case is what FXC fails to compile with:
+ // error X3500: array reference cannot be used as an l-value; not natively
+ // addressable
+ if (!ContainsStructArrayIndex(assign_stmt->lhs)) {
+ return nullptr;
+ }
+ auto og = GetOriginatingTypeAndStorageClass(assign_stmt);
+ if (!(og.first->Is<sem::Struct>() &&
+ (og.second == ast::StorageClass::kFunction ||
+ og.second == ast::StorageClass::kPrivate))) {
+ return nullptr;
+ }
+
+ // Reset shared state for this assignment statement
+ s = Shared{};
+
+ const ast::Expression* new_lhs = nullptr;
+ {
+ TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, true);
+ new_lhs = ctx.Clone(assign_stmt->lhs);
+ }
+
+ auto* new_assign_stmt = b.Assign(new_lhs, ctx.Clone(assign_stmt->rhs));
+
+ // Combine insert_before_stmts + new_assign_stmt + insert_after_stmts into
+ // a block and return it
+ ast::StatementList stmts = std::move(s.insert_before_stmts);
+ stmts.reserve(1 + s.insert_after_stmts.size());
+ stmts.emplace_back(new_assign_stmt);
+ stmts.insert(stmts.end(), s.insert_after_stmts.begin(),
+ s.insert_after_stmts.end());
+
+ return b.Block(std::move(stmts));
+ });
+
+ ctx.ReplaceAll([&](const ast::IndexAccessorExpression* index_access)
+ -> const ast::Expression* {
+ if (!s.process_nested_nodes) {
+ return nullptr;
+ }
+
+ // Indexing a member access expr?
+ auto* mem_access =
+ index_access->object->As<ast::MemberAccessorExpression>();
+ if (!mem_access) {
+ return nullptr;
+ }
+
+ // Process any nested IndexAccessorExpressions
+ mem_access = ctx.Clone(mem_access);
+
+ // Store the address of the member access into a let as we need to read
+ // the value twice e.g. let tint_symbol = &(s.a1);
+ auto mem_access_ptr = b.Sym();
+ s.insert_before_stmts.push_back(
+ b.Decl(b.Const(mem_access_ptr, nullptr, b.AddressOf(mem_access))));
+
+ // Disable further transforms when cloning
+ TINT_SCOPED_ASSIGNMENT(s.process_nested_nodes, false);
+
+ // Copy entire array out of struct into local temp var
+ // e.g. var tint_symbol_1 = *(tint_symbol);
+ auto tmp_var = b.Sym();
+ s.insert_before_stmts.push_back(
+ b.Decl(b.Var(tmp_var, nullptr, b.Deref(mem_access_ptr))));
+
+ // Replace input index_access with a clone of itself, but with its
+ // .object replaced by the new temp var. This is returned from this
+ // function to modify the original assignment statement. e.g.
+ // tint_symbol_1[uniforms.i]
+ auto* new_index_access =
+ b.IndexAccessor(tmp_var, ctx.Clone(index_access->index));
+
+ // Assign temp var back to array
+ // e.g. *(tint_symbol) = tint_symbol_1;
+ auto* assign_rhs_to_temp = b.Assign(b.Deref(mem_access_ptr), tmp_var);
+ s.insert_after_stmts.insert(s.insert_after_stmts.begin(),
+ assign_rhs_to_temp); // push_front
+
+ return new_index_access;
+ });
+
+ ctx.Clone();
+ }
+};
+
+LocalizeStructArrayAssignment::LocalizeStructArrayAssignment() = default;
+
+LocalizeStructArrayAssignment::~LocalizeStructArrayAssignment() = default;
+
+void LocalizeStructArrayAssignment::Run(CloneContext& ctx,
+ const DataMap&,
+ DataMap&) {
+ if (!Requires<SimplifyPointers>(ctx)) {
+ return;
+ }
+
+ State state(ctx);
+ state.Run();
+
+ // This transform may introduce pointers
+ ctx.dst->UnsetTransformApplied<transform::SimplifyPointers>();
+}
+} // namespace transform
+} // namespace tint
diff --git a/src/transform/localize_struct_array_assignment.h b/src/transform/localize_struct_array_assignment.h
new file mode 100644
index 0000000..ce56e14
--- /dev/null
+++ b/src/transform/localize_struct_array_assignment.h
@@ -0,0 +1,53 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
+#define SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
+
+#include "src/transform/transform.h"
+
+namespace tint {
+namespace transform {
+
+/// This transforms replaces assignment to dynamically-indexed fixed-size arrays
+/// in structs on shader-local variables with code that copies the arrays to a
+/// temporary local variable, assigns to the local variable, and copies the
+/// array back. This is to work around FXC's compilation failure for these cases
+/// (see crbug.com/tint/1206).
+class LocalizeStructArrayAssignment
+ : public Castable<LocalizeStructArrayAssignment, Transform> {
+ public:
+ /// Constructor
+ LocalizeStructArrayAssignment();
+
+ /// Destructor
+ ~LocalizeStructArrayAssignment() override;
+
+ protected:
+ /// Runs the transform using the CloneContext built for transforming a
+ /// program. Run() is responsible for calling Clone() on the CloneContext.
+ /// @param ctx the CloneContext primed with the input program and
+ /// ProgramBuilder
+ /// @param inputs optional extra transform-specific input data
+ /// @param outputs optional extra transform-specific output data
+ void Run(CloneContext& ctx, const DataMap& inputs, DataMap& outputs) override;
+
+ private:
+ class State;
+};
+
+} // namespace transform
+} // namespace tint
+
+#endif // SRC_TRANSFORM_LOCALIZE_STRUCT_ARRAY_ASSIGNMENT_H_
diff --git a/src/transform/localize_struct_array_assignment_test.cc b/src/transform/localize_struct_array_assignment_test.cc
new file mode 100644
index 0000000..197aa02
--- /dev/null
+++ b/src/transform/localize_struct_array_assignment_test.cc
@@ -0,0 +1,620 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/transform/localize_struct_array_assignment.h"
+#include "src/transform/simplify_pointers.h"
+#include "src/transform/unshadow.h"
+
+#include "src/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using LocalizeStructArrayAssignmentTest = TransformTest;
+
+TEST_F(LocalizeStructArrayAssignmentTest, MissingSimplifyPointers) {
+ auto* src = R"()";
+ auto* expect =
+ "error: tint::transform::LocalizeStructArrayAssignment depends on "
+ "tint::transform::SimplifyPointers but the dependency was not run";
+
+ auto got = Run<LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, EmptyModule) {
+ auto* src = R"()";
+ auto* expect = src;
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArray) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ s1.a1[uniforms.i] = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ {
+ let tint_symbol = &(s1.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructStructArray) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a : array<InnerS, 8>;
+};
+
+struct OuterS {
+ s2 : S1;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ s1.s2.a[uniforms.i] = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a : array<InnerS, 8>;
+};
+
+struct OuterS {
+ s2 : S1;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ {
+ let tint_symbol = &(s1.s2.a);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayArray) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<array<InnerS, 8>, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ s1.a1[uniforms.i][uniforms.j] = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<array<InnerS, 8>, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ {
+ let tint_symbol = &(s1.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i][uniforms.j] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStruct) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ s2 : InnerS;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ s1.a1[uniforms.i].s2 = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ s2 : InnerS;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ {
+ let tint_symbol = &(s1.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i].s2 = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, StructArrayStructArray) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s : OuterS;
+ s.a1[uniforms.i].a2[uniforms.j] = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s : OuterS;
+ {
+ let tint_symbol = &(s.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ let tint_symbol_2 = &(tint_symbol_1[uniforms.i].a2);
+ var tint_symbol_3 = *(tint_symbol_2);
+ tint_symbol_3[uniforms.j] = v;
+ *(tint_symbol_2) = tint_symbol_3;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, IndexingWithSideEffectFunc) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+var<private> nextIndex : u32;
+fn getNextIndex() -> u32 {
+ nextIndex = nextIndex + 1u;
+ return nextIndex;
+}
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s : OuterS;
+ s.a1[getNextIndex()].a2[uniforms.j] = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+ j : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct S1 {
+ a2 : array<InnerS, 8>;
+};
+
+struct OuterS {
+ a1 : array<S1, 8>;
+};
+
+var<private> nextIndex : u32;
+
+fn getNextIndex() -> u32 {
+ nextIndex = (nextIndex + 1u);
+ return nextIndex;
+}
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s : OuterS;
+ {
+ let tint_symbol = &(s.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ let tint_symbol_2 = &(tint_symbol_1[getNextIndex()].a2);
+ var tint_symbol_3 = *(tint_symbol_2);
+ tint_symbol_3[uniforms.j] = v;
+ *(tint_symbol_2) = tint_symbol_3;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerArg) {
+ auto* src = R"(
+[[block]] struct Uniforms {
+ i : u32;
+};
+struct InnerS {
+ v : i32;
+};
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+ var v : InnerS;
+ (*p).a1[uniforms.i] = v;
+}
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var s1 : OuterS;
+ f(&s1);
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, OuterS>) {
+ var v : InnerS;
+ {
+ let tint_symbol = &((*(p)).a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[uniforms.i] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var s1 : OuterS;
+ f(&(s1));
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, ViaPointerVar) {
+ auto* src = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, InnerS>, v : InnerS) {
+ *(p) = v;
+}
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ let p = &(s1.a1[uniforms.i]);
+ *(p) = v;
+}
+)";
+
+ auto* expect = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+struct InnerS {
+ v : i32;
+};
+
+struct OuterS {
+ a1 : array<InnerS, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+fn f(p : ptr<function, InnerS>, v : InnerS) {
+ *(p) = v;
+}
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var v : InnerS;
+ var s1 : OuterS;
+ let p_save = uniforms.i;
+ {
+ let tint_symbol = &(s1.a1);
+ var tint_symbol_1 = *(tint_symbol);
+ tint_symbol_1[p_save] = v;
+ *(tint_symbol) = tint_symbol_1;
+ }
+}
+)";
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(LocalizeStructArrayAssignmentTest, VectorAssignment) {
+ auto* src = R"(
+[[block]]
+struct Uniforms {
+ i : u32;
+};
+
+[[block]]
+struct OuterS {
+ a1 : array<u32, 8>;
+};
+
+[[group(1), binding(4)]] var<uniform> uniforms : Uniforms;
+
+fn f(i : u32) -> u32 {
+ return (i + 1u);
+}
+
+[[stage(compute), workgroup_size(1)]]
+fn main() {
+ var s1 : OuterS;
+ var v : vec3<f32>;
+ v[s1.a1[uniforms.i]] = 1.0;
+ v[f(s1.a1[uniforms.i])] = 1.0;
+}
+)";
+
+ // Transform does nothing here as we're not actually assigning to the array in
+ // the struct.
+ auto* expect = src;
+
+ auto got =
+ Run<Unshadow, SimplifyPointers, LocalizeStructArrayAssignment>(src);
+ EXPECT_EQ(expect, str(got));
+}
+
+} // namespace
+} // namespace transform
+} // namespace tint
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index c66278f..82c36cb 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -51,6 +51,7 @@
#include "src/transform/decompose_memory_access.h"
#include "src/transform/external_texture_transform.h"
#include "src/transform/fold_trivial_single_use_lets.h"
+#include "src/transform/localize_struct_array_assignment.h"
#include "src/transform/loop_to_for_loop.h"
#include "src/transform/manager.h"
#include "src/transform/num_workgroups_from_uniform.h"
@@ -145,6 +146,15 @@
manager.Add<transform::Unshadow>();
+ // LocalizeStructArrayAssignment must come after:
+ // * SimplifyPointers, because it assumes assignment to arrays in structs are
+ // done directly, not indirectly.
+ // TODO(crbug.com/tint/1340): See if we can get rid of the duplicate
+ // SimplifyPointers transform. Can't do it right now because
+ // LocalizeStructArrayAssignment introduces pointers.
+ manager.Add<transform::SimplifyPointers>();
+ manager.Add<transform::LocalizeStructArrayAssignment>();
+
// Attempt to convert `loop`s into for-loops. This is to try and massage the
// output into something that will not cause FXC to choke or misbehave.
manager.Add<transform::FoldTrivialSingleUseLets>();