[ir] Add PreservePadding transform
Use it in the SPIR-V writer, followed by DirectVariableAccess.
Bug: tint:1906
Change-Id: Ie7aa96a527f5798028867a5c1565a35762a7009e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/153863
Commit-Queue: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index e4c3240..20133bc 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -35,6 +35,7 @@
"demote_to_helper.cc",
"direct_variable_access.cc",
"multiplanar_external_texture.cc",
+ "preserve_padding.cc",
"robustness.cc",
"shader_io.cc",
"std140.cc",
@@ -50,6 +51,7 @@
"demote_to_helper.h",
"direct_variable_access.h",
"multiplanar_external_texture.h",
+ "preserve_padding.h",
"robustness.h",
"shader_io.h",
"std140.h",
@@ -94,6 +96,7 @@
"direct_variable_access_test.cc",
"helper_test.h",
"multiplanar_external_texture_test.cc",
+ "preserve_padding_test.cc",
"robustness_test.cc",
"std140_test.cc",
"zero_init_workgroup_memory_test.cc",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index 1164934..ba85854 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -44,6 +44,8 @@
lang/core/ir/transform/direct_variable_access.h
lang/core/ir/transform/multiplanar_external_texture.cc
lang/core/ir/transform/multiplanar_external_texture.h
+ lang/core/ir/transform/preserve_padding.cc
+ lang/core/ir/transform/preserve_padding.h
lang/core/ir/transform/robustness.cc
lang/core/ir/transform/robustness.h
lang/core/ir/transform/shader_io.cc
@@ -92,6 +94,7 @@
lang/core/ir/transform/direct_variable_access_test.cc
lang/core/ir/transform/helper_test.h
lang/core/ir/transform/multiplanar_external_texture_test.cc
+ lang/core/ir/transform/preserve_padding_test.cc
lang/core/ir/transform/robustness_test.cc
lang/core/ir/transform/std140_test.cc
lang/core/ir/transform/zero_init_workgroup_memory_test.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 9ee4ae0..f6ce734 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -49,6 +49,8 @@
"direct_variable_access.h",
"multiplanar_external_texture.cc",
"multiplanar_external_texture.h",
+ "preserve_padding.cc",
+ "preserve_padding.h",
"robustness.cc",
"robustness.h",
"shader_io.cc",
@@ -95,6 +97,7 @@
"direct_variable_access_test.cc",
"helper_test.h",
"multiplanar_external_texture_test.cc",
+ "preserve_padding_test.cc",
"robustness_test.cc",
"std140_test.cc",
"zero_init_workgroup_memory_test.cc",
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.cc b/src/tint/lang/core/ir/transform/preserve_padding.cc
new file mode 100644
index 0000000..18f18f6
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/preserve_padding.cc
@@ -0,0 +1,173 @@
+// 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/preserve_padding.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;
+
+ /// The IR builder.
+ Builder b{ir};
+
+ /// The type manager.
+ core::type::Manager& ty{ir.Types()};
+
+ /// The symbol table.
+ SymbolTable& sym{ir.symbols};
+
+ /// Map from a type to a helper function that will store a decomposed value.
+ Hashmap<const core::type::Type*, Function*, 4> helpers{};
+
+ /// Process the module.
+ void Process() {
+ // Find host-visible stores of types that contain padding bytes.
+ Vector<Store*, 8> worklist;
+ for (auto inst : ir.instructions.Objects()) {
+ if (auto* store = inst->As<Store>(); store && store->Alive()) {
+ auto* ptr = store->To()->Type()->As<core::type::Pointer>();
+ if (ptr->AddressSpace() == core::AddressSpace::kStorage &&
+ ContainsPadding(ptr->StoreType())) {
+ worklist.Push(store);
+ }
+ }
+ }
+
+ // Replace the stores we found with calls to helper functions that decompose the accesses.
+ for (auto* store : worklist) {
+ auto* replacement = MakeStore(store->To(), store->From());
+ store->ReplaceWith(replacement);
+ store->Destroy();
+ }
+ }
+
+ /// Check if a type contains padding bytes.
+ /// @param type the type to check
+ /// @returns true if the type contains padding bytes
+ bool ContainsPadding(const type::Type* type) {
+ return tint::Switch(
+ type, //
+ [&](const type::Array* arr) {
+ auto* elem_ty = arr->ElemType();
+ if (arr->Stride() > elem_ty->Size()) {
+ return true;
+ }
+ return ContainsPadding(elem_ty);
+ },
+ [&](const type::Matrix* mat) {
+ return mat->ColumnStride() > mat->ColumnType()->Size();
+ },
+ [&](const type::Struct* str) {
+ uint32_t current_offset = 0;
+ for (auto* member : str->Members()) {
+ if (member->Offset() > current_offset) {
+ return true;
+ }
+ if (ContainsPadding(member->Type())) {
+ return true;
+ }
+ current_offset += member->Type()->Size();
+ }
+ return (current_offset < str->Size());
+ });
+ }
+
+ /// Create an instruction that stores a (possibly padded) type to memory, decomposing the access
+ /// into separate components to preserve padding if necessary.
+ /// @param to the pointer to store to
+ /// @param value the value to store
+ /// @returns the instruction that performs the store
+ Instruction* MakeStore(Value* to, Value* value) {
+ auto* store_type = value->Type();
+
+ // If there are no padding bytes in this type, just use a regular store instruction.
+ if (!ContainsPadding(store_type)) {
+ return b.Store(to, value);
+ }
+
+ // The type contains padding bytes, so call a helper function that decomposes the accesses.
+ auto* helper = helpers.GetOrCreate(store_type, [&] {
+ auto* func = b.Function("tint_store_and_preserve_padding", ty.void_());
+ auto* target = b.FunctionParam("target", ty.ptr(storage, store_type));
+ auto* value_param = b.FunctionParam("value_param", store_type);
+ func->SetParams({target, value_param});
+
+ b.Append(func->Block(), [&] {
+ tint::Switch(
+ store_type, //
+ [&](const type::Array* arr) {
+ b.LoopRange(
+ ty, 0_u, u32(arr->ConstantCount().value()), 1_u, [&](Value* idx) {
+ auto* el_ptr =
+ b.Access(ty.ptr(storage, arr->ElemType()), target, idx);
+ auto* el_value = b.Access(arr->ElemType(), value_param, idx);
+ MakeStore(el_ptr->Result(), el_value->Result());
+ });
+ },
+ [&](const type::Matrix* mat) {
+ for (uint32_t i = 0; i < mat->columns(); i++) {
+ auto* col_ptr =
+ b.Access(ty.ptr(storage, mat->ColumnType()), target, u32(i));
+ auto* col_value = b.Access(mat->ColumnType(), value_param, u32(i));
+ MakeStore(col_ptr->Result(), col_value->Result());
+ }
+ },
+ [&](const type::Struct* str) {
+ for (auto* member : str->Members()) {
+ auto* sub_ptr = b.Access(ty.ptr(storage, member->Type()), target,
+ u32(member->Index()));
+ auto* sub_value =
+ b.Access(member->Type(), value_param, u32(member->Index()));
+ MakeStore(sub_ptr->Result(), sub_value->Result());
+ }
+ });
+
+ b.Return(func);
+ });
+
+ return func;
+ });
+
+ return b.Call(helper, to, value);
+ }
+};
+
+} // namespace
+
+Result<SuccessType> PreservePadding(Module& ir) {
+ auto result = ValidateAndDumpIfNeeded(ir, "PreservePadding transform");
+ if (!result) {
+ return result;
+ }
+
+ State{ir}.Process();
+
+ return Success;
+}
+
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.h b/src/tint/lang/core/ir/transform/preserve_padding.h
new file mode 100644
index 0000000..fb0082d
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/preserve_padding.h
@@ -0,0 +1,37 @@
+// 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_PRESERVE_PADDING_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_PRESERVE_PADDING_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// PreservePadding is a transform that decomposes stores of whole structure and array types to
+/// preserve padding bytes.
+///
+/// @note assumes that DirectVariableAccess will be run afterwards for backends that need it.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> PreservePadding(Module& module);
+
+} // namespace tint::core::ir::transform
+
+#endif // SRC_TINT_LANG_CORE_IR_TRANSFORM_PRESERVE_PADDING_H_
diff --git a/src/tint/lang/core/ir/transform/preserve_padding_test.cc b/src/tint/lang/core/ir/transform/preserve_padding_test.cc
new file mode 100644
index 0000000..88fcebc
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/preserve_padding_test.cc
@@ -0,0 +1,1291 @@
+// 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/preserve_padding.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/type/array.h"
+#include "src/tint/lang/core/type/matrix.h"
+#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/core/type/struct.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+class IR_PreservePaddingTest : public TransformTest {
+ protected:
+ const type::Struct* MakeStructWithoutPadding() {
+ auto* structure =
+ ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.vec4<u32>()},
+ {mod.symbols.New("b"), ty.vec4<u32>()},
+ {mod.symbols.New("c"), ty.vec4<u32>()},
+ });
+ return structure;
+ }
+
+ const type::Struct* MakeStructWithTrailingPadding() {
+ auto* structure =
+ ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.vec4<u32>()},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+ return structure;
+ }
+
+ const type::Struct* MakeStructWithInternalPadding() {
+ auto* structure =
+ ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), ty.vec4<u32>()},
+ {mod.symbols.New("b"), ty.u32()},
+ {mod.symbols.New("c"), ty.vec4<u32>()},
+ });
+ return structure;
+ }
+};
+
+TEST_F(IR_PreservePaddingTest, NoModify_Workgroup) {
+ auto* structure = MakeStructWithTrailingPadding();
+ auto* buffer = b.Var("buffer", ty.ptr(workgroup, structure));
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<workgroup, MyStruct, read_write> = var
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_Private) {
+ auto* structure = MakeStructWithTrailingPadding();
+ auto* buffer = b.Var("buffer", ty.ptr(private_, structure));
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<private, MyStruct, read_write> = var
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_Function) {
+ auto* structure = MakeStructWithTrailingPadding();
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ auto* buffer = b.Var("buffer", ty.ptr(function, structure));
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%foo = func(%value:MyStruct):void -> %b1 {
+ %b1 = block {
+ %buffer:ptr<function, MyStruct, read_write> = var
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_StructWithoutPadding) {
+ auto* structure = MakeStructWithoutPadding();
+ auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:vec4<u32> @offset(16)
+ c:vec4<u32> @offset(32)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_MatrixWithoutPadding) {
+ auto* mat = ty.mat4x4<f32>();
+ auto* buffer = b.Var("buffer", ty.ptr(storage, mat));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", mat);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, mat4x4<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:mat4x4<f32>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_ArrayWithoutPadding) {
+ auto* arr = ty.array<vec4<f32>>();
+ auto* buffer = b.Var("buffer", ty.ptr(storage, arr));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", arr);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<vec4<f32>>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<vec4<f32>>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_Vec3) {
+ auto* buffer = b.Var("buffer", ty.ptr(storage, ty.vec3<f32>()));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", ty.vec3<f32>());
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, vec3<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:vec3<f32>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NoModify_LoadStructWithTrailingPadding) {
+ auto* structure = MakeStructWithTrailingPadding();
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* func = b.Function("foo", structure);
+ b.Append(func->Block(), [&] {
+ auto* load = b.Load(buffer);
+ b.Return(func, load);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func():MyStruct -> %b2 {
+ %b2 = block {
+ %3:MyStruct = load %buffer
+ ret %3
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = src;
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Struct_TrailingPadding) {
+ auto* structure = MakeStructWithTrailingPadding();
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, MyStruct, read_write>, %value_param:MyStruct):void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, vec4<u32>, read_write> = access %target, 0u
+ %9:vec4<u32> = access %value_param, 0u
+ store %8, %9
+ %10:ptr<storage, u32, read_write> = access %target, 1u
+ %11:u32 = access %value_param, 1u
+ store %10, %11
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Struct_InternalPadding) {
+ auto* structure = MakeStructWithInternalPadding();
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+ c:vec4<u32> @offset(32)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+ c:vec4<u32> @offset(32)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, MyStruct, read_write>, %value_param:MyStruct):void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, vec4<u32>, read_write> = access %target, 0u
+ %9:vec4<u32> = access %value_param, 0u
+ store %8, %9
+ %10:ptr<storage, u32, read_write> = access %target, 1u
+ %11:u32 = access %value_param, 1u
+ store %10, %11
+ %12:ptr<storage, vec4<u32>, read_write> = access %target, 2u
+ %13:vec4<u32> = access %value_param, 2u
+ store %12, %13
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, NestedStructWithPadding) {
+ auto* inner = MakeStructWithInternalPadding();
+ auto* outer = ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.New("inner"), inner},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, outer));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", outer);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+ c:vec4<u32> @offset(32)
+}
+
+Outer = struct @align(16) {
+ inner:MyStruct @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, Outer, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:Outer):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+ c:vec4<u32> @offset(32)
+}
+
+Outer = struct @align(16) {
+ inner:MyStruct @offset(0)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, Outer, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:Outer):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, Outer, read_write>, %value_param:Outer):void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, MyStruct, read_write> = access %target, 0u
+ %9:MyStruct = access %value_param, 0u
+ %10:void = call %tint_store_and_preserve_padding_1, %8, %9
+ ret
+ }
+}
+%tint_store_and_preserve_padding_1 = func(%target_1:ptr<storage, MyStruct, read_write>, %value_param_1:MyStruct):void -> %b4 { # %tint_store_and_preserve_padding_1: 'tint_store_and_preserve_padding', %target_1: 'target', %value_param_1: 'value_param'
+ %b4 = block {
+ %14:ptr<storage, vec4<u32>, read_write> = access %target_1, 0u
+ %15:vec4<u32> = access %value_param_1, 0u
+ store %14, %15
+ %16:ptr<storage, u32, read_write> = access %target_1, 1u
+ %17:u32 = access %value_param_1, 1u
+ store %16, %17
+ %18:ptr<storage, vec4<u32>, read_write> = access %target_1, 2u
+ %19:vec4<u32> = access %value_param_1, 2u
+ store %18, %19
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, StructWithPadding_InArray) {
+ auto* structure = MakeStructWithTrailingPadding();
+ auto* arr = ty.array(structure, 4);
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, arr));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", arr);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, array<MyStruct, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<MyStruct, 4>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:vec4<u32> @offset(0)
+ b:u32 @offset(16)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, array<MyStruct, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<MyStruct, 4>):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, array<MyStruct, 4>, read_write>, %value_param:array<MyStruct, 4>):void -> %b3 {
+ %b3 = block {
+ loop [i: %b4, b: %b5, c: %b6] { # loop_1
+ %b4 = block { # initializer
+ next_iteration %b5 0u
+ }
+ %b5 = block (%idx:u32) { # body
+ %9:bool = gte %idx:u32, 4u
+ if %9 [t: %b7] { # if_1
+ %b7 = block { # true
+ exit_loop # loop_1
+ }
+ }
+ %10:ptr<storage, MyStruct, read_write> = access %target, %idx:u32
+ %11:MyStruct = access %value_param, %idx:u32
+ %12:void = call %tint_store_and_preserve_padding_1, %10, %11
+ continue %b6
+ }
+ %b6 = block { # continuing
+ %14:u32 = add %idx:u32, 1u
+ next_iteration %b5 %14
+ }
+ }
+ ret
+ }
+}
+%tint_store_and_preserve_padding_1 = func(%target_1:ptr<storage, MyStruct, read_write>, %value_param_1:MyStruct):void -> %b8 { # %tint_store_and_preserve_padding_1: 'tint_store_and_preserve_padding', %target_1: 'target', %value_param_1: 'value_param'
+ %b8 = block {
+ %17:ptr<storage, vec4<u32>, read_write> = access %target_1, 0u
+ %18:vec4<u32> = access %value_param_1, 0u
+ store %17, %18
+ %19:ptr<storage, u32, read_write> = access %target_1, 1u
+ %20:u32 = access %value_param_1, 1u
+ store %19, %20
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Mat3x3) {
+ auto* mat = ty.mat3x3<f32>();
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, mat));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", mat);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, mat3x3<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:mat3x3<f32>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, mat3x3<f32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:mat3x3<f32>):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, mat3x3<f32>, read_write>, %value_param:mat3x3<f32>):void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, vec3<f32>, read_write> = access %target, 0u
+ %9:vec3<f32> = access %value_param, 0u
+ store %8, %9
+ %10:ptr<storage, vec3<f32>, read_write> = access %target, 1u
+ %11:vec3<f32> = access %value_param, 1u
+ store %10, %11
+ %12:ptr<storage, vec3<f32>, read_write> = access %target, 2u
+ %13:vec3<f32> = access %value_param, 2u
+ store %12, %13
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Mat3x3_InStruct) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* structure = ty.Struct(mod.symbols.New("MyStruct"), {
+ {mod.symbols.New("a"), mat},
+ {mod.symbols.New("b"), mat},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", structure);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+MyStruct = struct @align(16) {
+ a:mat3x3<f32> @offset(0)
+ b:mat3x3<f32> @offset(48)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+MyStruct = struct @align(16) {
+ a:mat3x3<f32> @offset(0)
+ b:mat3x3<f32> @offset(48)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, MyStruct, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:MyStruct):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, MyStruct, read_write>, %value_param:MyStruct):void -> %b3 {
+ %b3 = block {
+ %8:ptr<storage, mat3x3<f32>, read_write> = access %target, 0u
+ %9:mat3x3<f32> = access %value_param, 0u
+ %10:void = call %tint_store_and_preserve_padding_1, %8, %9
+ %12:ptr<storage, mat3x3<f32>, read_write> = access %target, 1u
+ %13:mat3x3<f32> = access %value_param, 1u
+ %14:void = call %tint_store_and_preserve_padding_1, %12, %13
+ ret
+ }
+}
+%tint_store_and_preserve_padding_1 = func(%target_1:ptr<storage, mat3x3<f32>, read_write>, %value_param_1:mat3x3<f32>):void -> %b4 { # %tint_store_and_preserve_padding_1: 'tint_store_and_preserve_padding', %target_1: 'target', %value_param_1: 'value_param'
+ %b4 = block {
+ %17:ptr<storage, vec3<f32>, read_write> = access %target_1, 0u
+ %18:vec3<f32> = access %value_param_1, 0u
+ store %17, %18
+ %19:ptr<storage, vec3<f32>, read_write> = access %target_1, 1u
+ %20:vec3<f32> = access %value_param_1, 1u
+ store %19, %20
+ %21:ptr<storage, vec3<f32>, read_write> = access %target_1, 2u
+ %22:vec3<f32> = access %value_param_1, 2u
+ store %21, %22
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Mat3x3_Array) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, arr));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", arr);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<mat3x3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<mat3x3<f32>, 4>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<mat3x3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<mat3x3<f32>, 4>):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, array<mat3x3<f32>, 4>, read_write>, %value_param:array<mat3x3<f32>, 4>):void -> %b3 {
+ %b3 = block {
+ loop [i: %b4, b: %b5, c: %b6] { # loop_1
+ %b4 = block { # initializer
+ next_iteration %b5 0u
+ }
+ %b5 = block (%idx:u32) { # body
+ %9:bool = gte %idx:u32, 4u
+ if %9 [t: %b7] { # if_1
+ %b7 = block { # true
+ exit_loop # loop_1
+ }
+ }
+ %10:ptr<storage, mat3x3<f32>, read_write> = access %target, %idx:u32
+ %11:mat3x3<f32> = access %value_param, %idx:u32
+ %12:void = call %tint_store_and_preserve_padding_1, %10, %11
+ continue %b6
+ }
+ %b6 = block { # continuing
+ %14:u32 = add %idx:u32, 1u
+ next_iteration %b5 %14
+ }
+ }
+ ret
+ }
+}
+%tint_store_and_preserve_padding_1 = func(%target_1:ptr<storage, mat3x3<f32>, read_write>, %value_param_1:mat3x3<f32>):void -> %b8 { # %tint_store_and_preserve_padding_1: 'tint_store_and_preserve_padding', %target_1: 'target', %value_param_1: 'value_param'
+ %b8 = block {
+ %17:ptr<storage, vec3<f32>, read_write> = access %target_1, 0u
+ %18:vec3<f32> = access %value_param_1, 0u
+ store %17, %18
+ %19:ptr<storage, vec3<f32>, read_write> = access %target_1, 1u
+ %20:vec3<f32> = access %value_param_1, 1u
+ store %19, %20
+ %21:ptr<storage, vec3<f32>, read_write> = access %target_1, 2u
+ %22:vec3<f32> = access %value_param_1, 2u
+ store %21, %22
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, Vec3_Array) {
+ auto* arr = ty.array(ty.vec3<f32>(), 4);
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, arr));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", arr);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<vec3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<vec3<f32>, 4>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<vec3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<vec3<f32>, 4>):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, array<vec3<f32>, 4>, read_write>, %value_param:array<vec3<f32>, 4>):void -> %b3 {
+ %b3 = block {
+ loop [i: %b4, b: %b5, c: %b6] { # loop_1
+ %b4 = block { # initializer
+ next_iteration %b5 0u
+ }
+ %b5 = block (%idx:u32) { # body
+ %9:bool = gte %idx:u32, 4u
+ if %9 [t: %b7] { # if_1
+ %b7 = block { # true
+ exit_loop # loop_1
+ }
+ }
+ %10:ptr<storage, vec3<f32>, read_write> = access %target, %idx:u32
+ %11:vec3<f32> = access %value_param, %idx:u32
+ store %10, %11
+ continue %b6
+ }
+ %b6 = block { # continuing
+ %12:u32 = add %idx:u32, 1u
+ next_iteration %b5 %12
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, ComplexNesting) {
+ auto* inner =
+ ty.Struct(mod.symbols.New("Inner"), {
+ {mod.symbols.New("a"), ty.u32()},
+ {mod.symbols.New("b"), ty.array<vec3<f32>, 4>()},
+ {mod.symbols.New("c"), ty.mat3x3<f32>()},
+ {mod.symbols.New("d"), ty.u32()},
+ });
+
+ auto* outer =
+ ty.Struct(mod.symbols.New("Outer"), {
+ {mod.symbols.New("a"), ty.u32()},
+ {mod.symbols.New("inner"), inner},
+ {mod.symbols.New("inner_arr"), ty.array(inner, 4)},
+ {mod.symbols.New("b"), ty.u32()},
+ });
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, ty.array(outer, 3)));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", ty.array(outer, 3));
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(buffer, value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+Inner = struct @align(16) {
+ a:u32 @offset(0)
+ b:array<vec3<f32>, 4> @offset(16)
+ c:mat3x3<f32> @offset(80)
+ d:u32 @offset(128)
+}
+
+Outer = struct @align(16) {
+ a_1:u32 @offset(0)
+ inner:Inner @offset(16)
+ inner_arr:array<Inner, 4> @offset(160)
+ b_1:u32 @offset(736)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, array<Outer, 3>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<Outer, 3>):void -> %b2 {
+ %b2 = block {
+ store %buffer, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+Inner = struct @align(16) {
+ a:u32 @offset(0)
+ b:array<vec3<f32>, 4> @offset(16)
+ c:mat3x3<f32> @offset(80)
+ d:u32 @offset(128)
+}
+
+Outer = struct @align(16) {
+ a_1:u32 @offset(0)
+ inner:Inner @offset(16)
+ inner_arr:array<Inner, 4> @offset(160)
+ b_1:u32 @offset(736)
+}
+
+%b1 = block { # root
+ %buffer:ptr<storage, array<Outer, 3>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:array<Outer, 3>):void -> %b2 {
+ %b2 = block {
+ %4:void = call %tint_store_and_preserve_padding, %buffer, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, array<Outer, 3>, read_write>, %value_param:array<Outer, 3>):void -> %b3 {
+ %b3 = block {
+ loop [i: %b4, b: %b5, c: %b6] { # loop_1
+ %b4 = block { # initializer
+ next_iteration %b5 0u
+ }
+ %b5 = block (%idx:u32) { # body
+ %9:bool = gte %idx:u32, 3u
+ if %9 [t: %b7] { # if_1
+ %b7 = block { # true
+ exit_loop # loop_1
+ }
+ }
+ %10:ptr<storage, Outer, read_write> = access %target, %idx:u32
+ %11:Outer = access %value_param, %idx:u32
+ %12:void = call %tint_store_and_preserve_padding_1, %10, %11
+ continue %b6
+ }
+ %b6 = block { # continuing
+ %14:u32 = add %idx:u32, 1u
+ next_iteration %b5 %14
+ }
+ }
+ ret
+ }
+}
+%tint_store_and_preserve_padding_1 = func(%target_1:ptr<storage, Outer, read_write>, %value_param_1:Outer):void -> %b8 { # %tint_store_and_preserve_padding_1: 'tint_store_and_preserve_padding', %target_1: 'target', %value_param_1: 'value_param'
+ %b8 = block {
+ %17:ptr<storage, u32, read_write> = access %target_1, 0u
+ %18:u32 = access %value_param_1, 0u
+ store %17, %18
+ %19:ptr<storage, Inner, read_write> = access %target_1, 1u
+ %20:Inner = access %value_param_1, 1u
+ %21:void = call %tint_store_and_preserve_padding_2, %19, %20
+ %23:ptr<storage, array<Inner, 4>, read_write> = access %target_1, 2u
+ %24:array<Inner, 4> = access %value_param_1, 2u
+ %25:void = call %tint_store_and_preserve_padding_3, %23, %24
+ %27:ptr<storage, u32, read_write> = access %target_1, 3u
+ %28:u32 = access %value_param_1, 3u
+ store %27, %28
+ ret
+ }
+}
+%tint_store_and_preserve_padding_2 = func(%target_2:ptr<storage, Inner, read_write>, %value_param_2:Inner):void -> %b9 { # %tint_store_and_preserve_padding_2: 'tint_store_and_preserve_padding', %target_2: 'target', %value_param_2: 'value_param'
+ %b9 = block {
+ %31:ptr<storage, u32, read_write> = access %target_2, 0u
+ %32:u32 = access %value_param_2, 0u
+ store %31, %32
+ %33:ptr<storage, array<vec3<f32>, 4>, read_write> = access %target_2, 1u
+ %34:array<vec3<f32>, 4> = access %value_param_2, 1u
+ %35:void = call %tint_store_and_preserve_padding_4, %33, %34
+ %37:ptr<storage, mat3x3<f32>, read_write> = access %target_2, 2u
+ %38:mat3x3<f32> = access %value_param_2, 2u
+ %39:void = call %tint_store_and_preserve_padding_5, %37, %38
+ %41:ptr<storage, u32, read_write> = access %target_2, 3u
+ %42:u32 = access %value_param_2, 3u
+ store %41, %42
+ ret
+ }
+}
+%tint_store_and_preserve_padding_4 = func(%target_3:ptr<storage, array<vec3<f32>, 4>, read_write>, %value_param_3:array<vec3<f32>, 4>):void -> %b10 { # %tint_store_and_preserve_padding_4: 'tint_store_and_preserve_padding', %target_3: 'target', %value_param_3: 'value_param'
+ %b10 = block {
+ loop [i: %b11, b: %b12, c: %b13] { # loop_2
+ %b11 = block { # initializer
+ next_iteration %b12 0u
+ }
+ %b12 = block (%idx_1:u32) { # body
+ %46:bool = gte %idx_1:u32, 4u
+ if %46 [t: %b14] { # if_2
+ %b14 = block { # true
+ exit_loop # loop_2
+ }
+ }
+ %47:ptr<storage, vec3<f32>, read_write> = access %target_3, %idx_1:u32
+ %48:vec3<f32> = access %value_param_3, %idx_1:u32
+ store %47, %48
+ continue %b13
+ }
+ %b13 = block { # continuing
+ %49:u32 = add %idx_1:u32, 1u
+ next_iteration %b12 %49
+ }
+ }
+ ret
+ }
+}
+%tint_store_and_preserve_padding_5 = func(%target_4:ptr<storage, mat3x3<f32>, read_write>, %value_param_4:mat3x3<f32>):void -> %b15 { # %tint_store_and_preserve_padding_5: 'tint_store_and_preserve_padding', %target_4: 'target', %value_param_4: 'value_param'
+ %b15 = block {
+ %52:ptr<storage, vec3<f32>, read_write> = access %target_4, 0u
+ %53:vec3<f32> = access %value_param_4, 0u
+ store %52, %53
+ %54:ptr<storage, vec3<f32>, read_write> = access %target_4, 1u
+ %55:vec3<f32> = access %value_param_4, 1u
+ store %54, %55
+ %56:ptr<storage, vec3<f32>, read_write> = access %target_4, 2u
+ %57:vec3<f32> = access %value_param_4, 2u
+ store %56, %57
+ ret
+ }
+}
+%tint_store_and_preserve_padding_3 = func(%target_5:ptr<storage, array<Inner, 4>, read_write>, %value_param_5:array<Inner, 4>):void -> %b16 { # %tint_store_and_preserve_padding_3: 'tint_store_and_preserve_padding', %target_5: 'target', %value_param_5: 'value_param'
+ %b16 = block {
+ loop [i: %b17, b: %b18, c: %b19] { # loop_3
+ %b17 = block { # initializer
+ next_iteration %b18 0u
+ }
+ %b18 = block (%idx_2:u32) { # body
+ %61:bool = gte %idx_2:u32, 4u
+ if %61 [t: %b20] { # if_3
+ %b20 = block { # true
+ exit_loop # loop_3
+ }
+ }
+ %62:ptr<storage, Inner, read_write> = access %target_5, %idx_2:u32
+ %63:Inner = access %value_param_5, %idx_2:u32
+ %64:void = call %tint_store_and_preserve_padding_2, %62, %63
+ continue %b19
+ }
+ %b19 = block { # continuing
+ %65:u32 = add %idx_2:u32, 1u
+ next_iteration %b18 %65
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_PreservePaddingTest, MultipleStoresSameType) {
+ auto* mat = ty.mat3x3<f32>();
+ auto* arr = ty.array(mat, 4);
+
+ auto* buffer = b.Var("buffer", ty.ptr(storage, arr));
+ buffer->SetBindingPoint(0, 0);
+ mod.root_block->Append(buffer);
+
+ auto* value = b.FunctionParam("value", mat);
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({value});
+ b.Append(func->Block(), [&] {
+ b.Store(b.Access(ty.ptr(storage, mat), buffer, 0_u), value);
+ b.Store(b.Access(ty.ptr(storage, mat), buffer, 1_u), value);
+ b.Store(b.Access(ty.ptr(storage, mat), buffer, 2_u), value);
+ b.Store(b.Access(ty.ptr(storage, mat), buffer, 3_u), value);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<mat3x3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:mat3x3<f32>):void -> %b2 {
+ %b2 = block {
+ %4:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 0u
+ store %4, %value
+ %5:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 1u
+ store %5, %value
+ %6:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 2u
+ store %6, %value
+ %7:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 3u
+ store %7, %value
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%b1 = block { # root
+ %buffer:ptr<storage, array<mat3x3<f32>, 4>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = func(%value:mat3x3<f32>):void -> %b2 {
+ %b2 = block {
+ %4:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 0u
+ %5:void = call %tint_store_and_preserve_padding, %4, %value
+ %7:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 1u
+ %8:void = call %tint_store_and_preserve_padding, %7, %value
+ %9:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 2u
+ %10:void = call %tint_store_and_preserve_padding, %9, %value
+ %11:ptr<storage, mat3x3<f32>, read_write> = access %buffer, 3u
+ %12:void = call %tint_store_and_preserve_padding, %11, %value
+ ret
+ }
+}
+%tint_store_and_preserve_padding = func(%target:ptr<storage, mat3x3<f32>, read_write>, %value_param:mat3x3<f32>):void -> %b3 {
+ %b3 = block {
+ %15:ptr<storage, vec3<f32>, read_write> = access %target, 0u
+ %16:vec3<f32> = access %value_param, 0u
+ store %15, %16
+ %17:ptr<storage, vec3<f32>, read_write> = access %target, 1u
+ %18:vec3<f32> = access %value_param, 1u
+ store %17, %18
+ %19:ptr<storage, vec3<f32>, read_write> = access %target, 2u
+ %20:vec3<f32> = access %value_param, 2u
+ store %19, %20
+ ret
+ }
+}
+)";
+
+ Run(PreservePadding);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::core::ir::transform
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index ec72695..6d0213f 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -23,7 +23,9 @@
#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/demote_to_helper.h"
+#include "src/tint/lang/core/ir/transform/direct_variable_access.h"
#include "src/tint/lang/core/ir/transform/multiplanar_external_texture.h"
+#include "src/tint/lang/core/ir/transform/preserve_padding.h"
#include "src/tint/lang/core/ir/transform/robustness.h"
#include "src/tint/lang/core/ir/transform/std140.h"
#include "src/tint/lang/core/ir/transform/zero_init_workgroup_memory.h"
@@ -83,6 +85,13 @@
RUN_TRANSFORM(core::ir::transform::ZeroInitWorkgroupMemory, module);
}
+ RUN_TRANSFORM(core::ir::transform::PreservePadding, module);
+
+ core::ir::transform::DirectVariableAccessOptions dva_options;
+ dva_options.transform_function = true;
+ dva_options.transform_private = true;
+ RUN_TRANSFORM(core::ir::transform::DirectVariableAccess, module, dva_options);
+
RUN_TRANSFORM(core::ir::transform::AddEmptyEntryPoint, module);
RUN_TRANSFORM(core::ir::transform::Bgra8UnormPolyfill, module);
RUN_TRANSFORM(core::ir::transform::BlockDecoratedStructs, module);