[spirv-writer] Combine access instruction chains
Some Qualcomm drivers lose matrix stride information for OpAccessChain
instructions that produce pointers to matrices. Use a transform to
combine chains of access instructions so that generated OpAccessChain
instructions always start from the root.
Bug: tint:1906
Change-Id: I371b11aa993da713144a6616dfa25943a986f9e5
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/154101
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 8dabe5c..62c2113 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -32,6 +32,7 @@
"binding_remapper.cc",
"block_decorated_structs.cc",
"builtin_polyfill.cc",
+ "combine_access_instructions.cc",
"conversion_polyfill.cc",
"demote_to_helper.cc",
"direct_variable_access.cc",
@@ -49,6 +50,7 @@
"binding_remapper.h",
"block_decorated_structs.h",
"builtin_polyfill.h",
+ "combine_access_instructions.h",
"conversion_polyfill.h",
"demote_to_helper.h",
"direct_variable_access.h",
@@ -94,6 +96,7 @@
"binding_remapper_test.cc",
"block_decorated_structs_test.cc",
"builtin_polyfill_test.cc",
+ "combine_access_instructions_test.cc",
"conversion_polyfill_test.cc",
"demote_to_helper_test.cc",
"direct_variable_access_test.cc",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index b2a8967..c0b7f14 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -38,6 +38,8 @@
lang/core/ir/transform/block_decorated_structs.h
lang/core/ir/transform/builtin_polyfill.cc
lang/core/ir/transform/builtin_polyfill.h
+ lang/core/ir/transform/combine_access_instructions.cc
+ lang/core/ir/transform/combine_access_instructions.h
lang/core/ir/transform/conversion_polyfill.cc
lang/core/ir/transform/conversion_polyfill.h
lang/core/ir/transform/demote_to_helper.cc
@@ -92,6 +94,7 @@
lang/core/ir/transform/binding_remapper_test.cc
lang/core/ir/transform/block_decorated_structs_test.cc
lang/core/ir/transform/builtin_polyfill_test.cc
+ lang/core/ir/transform/combine_access_instructions_test.cc
lang/core/ir/transform/conversion_polyfill_test.cc
lang/core/ir/transform/demote_to_helper_test.cc
lang/core/ir/transform/direct_variable_access_test.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index d38f791..6d2212e 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -43,6 +43,8 @@
"block_decorated_structs.h",
"builtin_polyfill.cc",
"builtin_polyfill.h",
+ "combine_access_instructions.cc",
+ "combine_access_instructions.h",
"conversion_polyfill.cc",
"conversion_polyfill.h",
"demote_to_helper.cc",
@@ -94,6 +96,7 @@
"binding_remapper_test.cc",
"block_decorated_structs_test.cc",
"builtin_polyfill_test.cc",
+ "combine_access_instructions_test.cc",
"conversion_polyfill_test.cc",
"demote_to_helper_test.cc",
"direct_variable_access_test.cc",
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions.cc b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
new file mode 100644
index 0000000..71b42c9
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
@@ -0,0 +1,80 @@
+// Copyright 2023 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/tint/lang/core/ir/transform/combine_access_instructions.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// PIMPL state for the transform.
+struct State {
+ /// The IR module.
+ Module& ir;
+
+ /// Process the module.
+ void Process() {
+ // Loop over every instruction looking for access instructions.
+ for (auto* inst : ir.instructions.Objects()) {
+ if (auto* access = inst->As<ir::Access>(); access && access->Alive()) {
+ // Look for places where the result of this access instruction is used as a base
+ // pointer for another access instruction.
+ access->Result()->ForEachUse([&](Usage use) {
+ auto* child = use.instruction->As<ir::Access>();
+ if (child && use.operand_index == ir::Access::kObjectOperandOffset) {
+ // Push the indices of the parent access instruction into the child.
+ Vector<ir::Value*, 4> operands;
+ operands.Push(access->Object());
+ for (auto* idx : access->Indices()) {
+ operands.Push(idx);
+ }
+ for (auto* idx : child->Indices()) {
+ operands.Push(idx);
+ }
+ child->SetOperands(std::move(operands));
+ }
+ });
+
+ // If there are no other uses of the access instruction, remove it.
+ if (access->Result()->Usages().IsEmpty()) {
+ access->Destroy();
+ }
+ }
+ }
+ }
+};
+
+} // namespace
+
+Result<SuccessType> CombineAccessInstructions(Module& ir) {
+ auto result = ValidateAndDumpIfNeeded(ir, "CombineAccessInstructions transform");
+ if (!result) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions.h b/src/tint/lang/core/ir/transform/combine_access_instructions.h
new file mode 100644
index 0000000..55005e2
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions.h
@@ -0,0 +1,34 @@
+// Copyright 2023 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_TINT_LANG_CORE_IR_TRANSFORM_COMBINE_ACCESS_INSTRUCTIONS_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_COMBINE_ACCESS_INSTRUCTIONS_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// CombineAccessInstructions is a transform that combines chains of access instructions.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> CombineAccessInstructions(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_COMBINE_ACCESS_INSTRUCTIONS_H_
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc b/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc
new file mode 100644
index 0000000..7c79865
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc
@@ -0,0 +1,874 @@
+// Copyright 2023 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/tint/lang/core/ir/transform/combine_access_instructions.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using IR_CombineAccessInstructionsTest = TransformTest;
+
+TEST_F(IR_CombineAccessInstructionsTest, NoModify_NoChaining) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_root = b.Access(ty.ptr(uniform, structure), buffer);
+ b.Load(access_root);
+
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ b.Load(access_arr);
+
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), buffer, 0_u, 1_u);
+ b.Load(access_mat);
+
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), buffer, 0_u, 1_u, 2_u);
+ b.Load(access_vec);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, MyStruct, read_write> = access %buffer
+ %4:MyStruct = load %3
+ %5:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %6:array<mat3x3<f32>, 4> = load %5
+ %7:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 1u
+ %8:mat3x3<f32> = load %7
+ %9:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %10:vec3<f32> = load %9
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, SimpleChain) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 2_u);
+ b.Load(access_vec);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:ptr<uniform, vec3<f32>, read_write> = access %4, 2u
+ %6:vec3<f32> = load %5
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %4:vec3<f32> = load %3
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, NoIndices_Root) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_root = b.Access(ty.ptr(uniform, structure), buffer);
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), access_root, 0_u);
+ b.Load(access_arr);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, MyStruct, read_write> = access %buffer
+ %4:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %3, 0u
+ %5:array<mat3x3<f32>, 4> = load %4
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:array<mat3x3<f32>, 4> = load %3
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, NoIndices_Middle) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* access_copy = b.Access(ty.ptr(uniform, arr), access_arr);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_copy, 1_u);
+ b.Load(access_mat);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %3
+ %5:ptr<uniform, mat3x3<f32>, read_write> = access %4, 1u
+ %6:mat3x3<f32> = load %5
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 1u
+ %4:mat3x3<f32> = load %3
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, NoIndices_End) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ auto* access_copy = b.Access(ty.ptr(uniform, mat), access_mat);
+ b.Load(access_copy);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:ptr<uniform, mat3x3<f32>, read_write> = access %4
+ %6:mat3x3<f32> = load %5
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 1u
+ %4:mat3x3<f32> = load %3
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, MutipleChains_FromRoot) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+
+ {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 2_u);
+ b.Load(access_vec);
+ }
+ {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 2_u);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 0_u);
+ b.Load(access_vec);
+ }
+ {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 3_u);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 1_u);
+ b.Load(access_vec);
+ }
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:ptr<uniform, vec3<f32>, read_write> = access %4, 2u
+ %6:vec3<f32> = load %5
+ %7:ptr<uniform, mat3x3<f32>, read_write> = access %3, 2u
+ %8:ptr<uniform, vec3<f32>, read_write> = access %7, 0u
+ %9:vec3<f32> = load %8
+ %10:ptr<uniform, mat3x3<f32>, read_write> = access %3, 3u
+ %11:ptr<uniform, vec3<f32>, read_write> = access %10, 1u
+ %12:vec3<f32> = load %11
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %4:vec3<f32> = load %3
+ %5:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 2u, 0u
+ %6:vec3<f32> = load %5
+ %7:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 3u, 1u
+ %8:vec3<f32> = load %7
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, MutipleChains_FromMiddle) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+
+ {
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 2_u);
+ b.Load(access_vec);
+ }
+ {
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 0_u);
+ b.Load(access_vec);
+ }
+ {
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 1_u);
+ b.Load(access_vec);
+ }
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:ptr<uniform, vec3<f32>, read_write> = access %4, 2u
+ %6:vec3<f32> = load %5
+ %7:ptr<uniform, vec3<f32>, read_write> = access %4, 0u
+ %8:vec3<f32> = load %7
+ %9:ptr<uniform, vec3<f32>, read_write> = access %4, 1u
+ %10:vec3<f32> = load %9
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %4:vec3<f32> = load %3
+ %5:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 0u
+ %6:vec3<f32> = load %5
+ %7:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 1u
+ %8:vec3<f32> = load %7
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, OtherUses_Root) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ b.Load(access_arr);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 2_u);
+ b.Load(access_vec);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:array<mat3x3<f32>, 4> = load %3
+ %5:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %6:ptr<uniform, vec3<f32>, read_write> = access %5, 2u
+ %7:vec3<f32> = load %6
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:array<mat3x3<f32>, 4> = load %3
+ %5:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %6:vec3<f32> = load %5
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, OtherUses_Middle) {
+ auto* vec = ty.vec3<f32>();
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ b.Load(access_mat);
+ auto* access_vec = b.Access(ty.ptr(uniform, vec), access_mat, 2_u);
+ b.Load(access_vec);
+
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:mat3x3<f32> = load %4
+ %6:ptr<uniform, vec3<f32>, read_write> = access %4, 2u
+ %7:vec3<f32> = load %6
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 1u
+ %4:mat3x3<f32> = load %3
+ %5:ptr<uniform, vec3<f32>, read_write> = access %buffer, 0u, 1u, 2u
+ %6:vec3<f32> = load %5
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, AccessResultUsesAsAccessIndex) {
+ auto* func = b.Function("foo", ty.f32());
+ auto* indices = b.FunctionParam("indices", ty.array<u32, 4>());
+ auto* values = b.FunctionParam("values", ty.array<f32, 4>());
+ b.Append(func->Block(), [&] {
+ auto* access_index = b.Access(ty.u32(), indices, 1_u);
+ auto* access_value = b.Access(ty.f32(), values, access_index);
+ b.Return(func, access_value);
+ });
+
+ auto* src = R"(
+%foo = func():f32 -> %b1 {
+ %b1 = block {
+ %2:u32 = access %indices, 1u
+ %4:f32 = access %values, %2
+ ret %4
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, UseInDifferentBlock_If) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* ifelse = b.If(true);
+ b.Append(ifelse->True(), [&] {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 1_u);
+ b.Load(access_mat);
+ b.ExitIf(ifelse);
+ });
+ b.Append(ifelse->False(), [&] {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 2_u);
+ b.Load(access_mat);
+ b.ExitIf(ifelse);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ if true [t: %b3, f: %b4] { # if_1
+ %b3 = block { # true
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 1u
+ %5:mat3x3<f32> = load %4
+ exit_if # if_1
+ }
+ %b4 = block { # false
+ %6:ptr<uniform, mat3x3<f32>, read_write> = access %3, 2u
+ %7:mat3x3<f32> = load %6
+ exit_if # if_1
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ if true [t: %b3, f: %b4] { # if_1
+ %b3 = block { # true
+ %3:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 1u
+ %4:mat3x3<f32> = load %3
+ exit_if # if_1
+ }
+ %b4 = block { # false
+ %5:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 2u
+ %6:mat3x3<f32> = load %5
+ exit_if # if_1
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_CombineAccessInstructionsTest, UseInDifferentBlock_Loop) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), arr},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(uniform, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* access_arr = b.Access(ty.ptr(uniform, arr), buffer, 0_u);
+ auto* loop = b.Loop();
+ b.Append(loop->Body(), [&] {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 2_u);
+ b.Load(access_mat);
+ b.Continue(loop);
+ });
+ b.Append(loop->Continuing(), [&] {
+ auto* access_mat = b.Access(ty.ptr(uniform, mat), access_arr, 3_u);
+ b.Load(access_mat);
+ b.BreakIf(loop, true);
+ });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ %3:ptr<uniform, array<mat3x3<f32>, 4>, read_write> = access %buffer, 0u
+ loop [b: %b3, c: %b4] { # loop_1
+ %b3 = block { # body
+ %4:ptr<uniform, mat3x3<f32>, read_write> = access %3, 2u
+ %5:mat3x3<f32> = load %4
+ continue %b4
+ }
+ %b4 = block { # continuing
+ %6:ptr<uniform, mat3x3<f32>, read_write> = access %3, 3u
+ %7:mat3x3<f32> = load %6
+ break_if true %b3
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:array<mat3x3<f32>, 4> @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<uniform, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():void -> %b2 {
+ %b2 = block {
+ loop [b: %b3, c: %b4] { # loop_1
+ %b3 = block { # body
+ %3:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 2u
+ %4:mat3x3<f32> = load %3
+ continue %b4
+ }
+ %b4 = block { # continuing
+ %5:ptr<uniform, mat3x3<f32>, read_write> = access %buffer, 0u, 3u
+ %6:mat3x3<f32> = load %5
+ break_if true %b3
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(CombineAccessInstructions);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/spirv/writer/access_test.cc b/src/tint/lang/spirv/writer/access_test.cc
index ffdd185..967cdf5 100644
--- a/src/tint/lang/spirv/writer/access_test.cc
+++ b/src/tint/lang/spirv/writer/access_test.cc
@@ -22,11 +22,11 @@
TEST_F(SpirvWriterTest, Access_Array_Value_ConstantIndex) {
auto* arr_val = b.FunctionParam("arr", ty.array(ty.i32(), 4));
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({arr_val});
b.Append(func->Block(), [&] {
auto* result = b.Access(ty.i32(), arr_val, 1_u);
- b.Return(func);
+ b.Return(func, result);
mod.SetName(result, "result");
});
@@ -35,11 +35,11 @@
}
TEST_F(SpirvWriterTest, Access_Array_Pointer_ConstantIndex) {
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
b.Append(func->Block(), [&] {
auto* arr_var = b.Var("arr", ty.ptr<function, array<i32, 4>>());
auto* result = b.Access(ty.ptr<function, i32>(), arr_var, 1_u);
- b.Return(func);
+ b.Return(func, b.Load(result));
mod.SetName(result, "result");
});
@@ -49,31 +49,31 @@
TEST_F(SpirvWriterTest, Access_Array_Pointer_DynamicIndex) {
auto* idx = b.FunctionParam("idx", ty.i32());
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* arr_var = b.Var("arr", ty.ptr<function, array<i32, 4>>());
auto* result = b.Access(ty.ptr<function, i32>(), arr_var, idx);
- b.Return(func);
+ b.Return(func, b.Load(result));
mod.SetName(result, "result");
});
ASSERT_TRUE(Generate()) << Error() << output_;
EXPECT_INST(R"(
- %13 = OpBitcast %uint %idx
- %14 = OpExtInst %uint %15 UMin %13 %uint_3
- %result = OpAccessChain %_ptr_Function_int %arr %14
+ %12 = OpBitcast %uint %idx
+ %13 = OpExtInst %uint %14 UMin %12 %uint_3
+ %result = OpAccessChain %_ptr_Function_int %arr %13
)");
}
TEST_F(SpirvWriterTest, Access_Matrix_Value_ConstantIndex) {
auto* mat_val = b.FunctionParam("mat", ty.mat2x2(ty.f32()));
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.vec2<f32>());
func->SetParams({mat_val});
b.Append(func->Block(), [&] {
auto* result_vector = b.Access(ty.vec2(ty.f32()), mat_val, 1_u);
auto* result_scalar = b.Access(ty.f32(), mat_val, 1_u, 0_u);
- b.Return(func);
+ b.Return(func, b.Multiply(ty.vec2<f32>(), result_vector, result_scalar));
mod.SetName(result_vector, "result_vector");
mod.SetName(result_scalar, "result_scalar");
});
@@ -127,11 +127,11 @@
TEST_F(SpirvWriterTest, Access_Vector_Value_ConstantIndex) {
auto* vec_val = b.FunctionParam("vec", ty.vec4(ty.i32()));
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({vec_val});
b.Append(func->Block(), [&] {
auto* result = b.Access(ty.i32(), vec_val, 1_u);
- b.Return(func);
+ b.Return(func, result);
mod.SetName(result, "result");
});
@@ -142,82 +142,84 @@
TEST_F(SpirvWriterTest, Access_Vector_Value_DynamicIndex) {
auto* vec_val = b.FunctionParam("vec", ty.vec4(ty.i32()));
auto* idx = b.FunctionParam("idx", ty.i32());
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({vec_val, idx});
b.Append(func->Block(), [&] {
auto* result = b.Access(ty.i32(), vec_val, idx);
- b.Return(func);
+ b.Return(func, result);
mod.SetName(result, "result");
});
ASSERT_TRUE(Generate()) << Error() << output_;
EXPECT_INST(R"(
- %10 = OpBitcast %uint %idx
- %11 = OpExtInst %uint %12 UMin %10 %uint_3
- %result = OpVectorExtractDynamic %int %vec %11
+ %9 = OpBitcast %uint %idx
+ %10 = OpExtInst %uint %11 UMin %9 %uint_3
+ %result = OpVectorExtractDynamic %int %vec %10
)");
}
TEST_F(SpirvWriterTest, Access_NestedVector_Value_DynamicIndex) {
auto* val = b.FunctionParam("arr", ty.array(ty.array(ty.vec4(ty.i32()), 4), 4));
auto* idx = b.FunctionParam("idx", ty.i32());
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({val, idx});
b.Append(func->Block(), [&] {
auto* result = b.Access(ty.i32(), val, 1_u, 2_u, idx);
- b.Return(func);
+ b.Return(func, result);
mod.SetName(result, "result");
});
ASSERT_TRUE(Generate()) << Error() << output_;
EXPECT_INST(R"(
- %13 = OpBitcast %uint %idx
- %14 = OpExtInst %uint %15 UMin %13 %uint_3
- %18 = OpCompositeExtract %v4int %arr 1 2
- %result = OpVectorExtractDynamic %int %18 %14
+ %12 = OpBitcast %uint %idx
+ %13 = OpExtInst %uint %14 UMin %12 %uint_3
+ %17 = OpCompositeExtract %v4int %arr 1 2
+ %result = OpVectorExtractDynamic %int %17 %13
)");
}
TEST_F(SpirvWriterTest, Access_Struct_Value_ConstantIndex) {
auto* str =
ty.Struct(mod.symbols.New("MyStruct"), {
- {mod.symbols.Register("a"), ty.f32()},
+ {mod.symbols.Register("a"), ty.i32()},
{mod.symbols.Register("b"), ty.vec4<i32>()},
});
auto* str_val = b.FunctionParam("str", str);
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.i32());
func->SetParams({str_val});
b.Append(func->Block(), [&] {
- auto* result_a = b.Access(ty.f32(), str_val, 0_u);
+ auto* result_a = b.Access(ty.i32(), str_val, 0_u);
auto* result_b = b.Access(ty.i32(), str_val, 1_u, 2_u);
- b.Return(func);
+ b.Return(func, b.Add(ty.i32(), result_a, result_b));
mod.SetName(result_a, "result_a");
mod.SetName(result_b, "result_b");
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result_a = OpCompositeExtract %float %str 0");
+ EXPECT_INST("%result_a = OpCompositeExtract %int %str 0");
EXPECT_INST("%result_b = OpCompositeExtract %int %str 1 2");
}
TEST_F(SpirvWriterTest, Access_Struct_Pointer_ConstantIndex) {
auto* str =
ty.Struct(mod.symbols.New("MyStruct"), {
- {mod.symbols.Register("a"), ty.f32()},
+ {mod.symbols.Register("a"), ty.i32()},
{mod.symbols.Register("b"), ty.vec4<i32>()},
});
- auto* func = b.Function("foo", ty.void_());
+ auto* func = b.Function("foo", ty.vec4<i32>());
b.Append(func->Block(), [&] {
auto* str_var = b.Var("str", ty.ptr(function, str, read_write));
- auto* result_a = b.Access(ty.ptr<function, f32>(), str_var, 0_u);
+ auto* result_a = b.Access(ty.ptr<function, i32>(), str_var, 0_u);
auto* result_b = b.Access(ty.ptr<function, vec4<i32>>(), str_var, 1_u);
- b.Return(func);
+ auto* val_a = b.Load(result_a);
+ auto* val_b = b.Load(result_b);
+ b.Return(func, b.Add(ty.vec4<i32>(), val_a, val_b));
mod.SetName(result_a, "result_a");
mod.SetName(result_b, "result_b");
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result_a = OpAccessChain %_ptr_Function_float %str %uint_0");
+ EXPECT_INST("%result_a = OpAccessChain %_ptr_Function_int %str %uint_0");
EXPECT_INST("%result_b = OpAccessChain %_ptr_Function_v4int %str %uint_1");
}
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 724997f..c2fc514 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -22,6 +22,7 @@
#include "src/tint/lang/core/ir/transform/binding_remapper.h"
#include "src/tint/lang/core/ir/transform/block_decorated_structs.h"
#include "src/tint/lang/core/ir/transform/builtin_polyfill.h"
+#include "src/tint/lang/core/ir/transform/combine_access_instructions.h"
#include "src/tint/lang/core/ir/transform/conversion_polyfill.h"
#include "src/tint/lang/core/ir/transform/demote_to_helper.h"
#include "src/tint/lang/core/ir/transform/direct_variable_access.h"
@@ -90,6 +91,7 @@
RUN_TRANSFORM(core::ir::transform::ZeroInitWorkgroupMemory, module);
}
+ // PreservePadding must come before DirectVariableAccess.
RUN_TRANSFORM(core::ir::transform::PreservePadding, module);
core::ir::transform::DirectVariableAccessOptions dva_options;
@@ -100,6 +102,12 @@
RUN_TRANSFORM(core::ir::transform::AddEmptyEntryPoint, module);
RUN_TRANSFORM(core::ir::transform::Bgra8UnormPolyfill, module);
RUN_TRANSFORM(core::ir::transform::BlockDecoratedStructs, module);
+
+ // CombineAccessInstructions must come after DirectVariableAccess and BlockDecoratedStructs.
+ // We run this transform as some Qualcomm drivers struggle with partial access chains that
+ // produce pointers to matrices.
+ RUN_TRANSFORM(core::ir::transform::CombineAccessInstructions, module);
+
RUN_TRANSFORM(BuiltinPolyfill, module);
RUN_TRANSFORM(core::ir::transform::DemoteToHelper, module);
RUN_TRANSFORM(ExpandImplicitSplats, module);
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
index 88d564f..59cd9c4 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -117,7 +117,7 @@
// Find the access instructions that need replacing.
Vector<AccessToReplace, 4> worklist;
for (auto* inst : ir.instructions.Objects()) {
- if (auto* access = inst->As<core::ir::Access>()) {
+ if (auto* access = inst->As<core::ir::Access>(); access && access->Alive()) {
if (auto to_replace = ShouldReplace(access)) {
worklist.Push(to_replace.value());
}