From 960aa2ee757801a2d5ec8a9b2170dc3ea89b1f20 Mon Sep 17 00:00:00 2001 From: James Price Date: Sat, 19 Jun 2021 00:33:35 +0000 Subject: [PATCH] msl: Add ArrayLengthFromUniform transform Generate a uniform buffer that will receive the lengths of all storage buffers, and use this to implement calls to arrayLength(). The transform is provided with a set of mappings from storage buffer binding points to the corresponding index into the array of buffer lengths. The transform reports whether it generated the uniform buffers or not. Use this transform from the MSL sanitizer, using the binding number as the index into the array. This matches the behavior of spirv-cross, and so works with how Dawn already produces this uniform buffer. Bug: tint:256 Change-Id: I2682d2d024e8daa30f78270b8cfb6bbb32632133 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/54480 Kokoro: Kokoro Commit-Queue: James Price Reviewed-by: Ben Clayton --- src/BUILD.gn | 2 + src/CMakeLists.txt | 3 + src/transform/array_length_from_uniform.cc | 165 ++++++++++ src/transform/array_length_from_uniform.h | 104 +++++++ .../array_length_from_uniform_test.cc | 283 ++++++++++++++++++ src/transform/msl.cc | 53 +++- src/transform/msl.h | 35 +++ test/BUILD.gn | 1 + .../complex_via_let.wgsl.expected.msl | 21 +- .../arrayLength/deprecated.wgsl.expected.msl | 22 +- .../arrayLength/simple.wgsl.expected.msl | 19 +- .../arrayLength/via_let.wgsl.expected.msl | 21 +- .../via_let_complex.wgsl.expected.msl | 22 +- 13 files changed, 687 insertions(+), 64 deletions(-) create mode 100644 src/transform/array_length_from_uniform.cc create mode 100644 src/transform/array_length_from_uniform.h create mode 100644 src/transform/array_length_from_uniform_test.cc diff --git a/src/BUILD.gn b/src/BUILD.gn index 0ff76ce00d..eba4825f9e 100644 --- a/src/BUILD.gn +++ b/src/BUILD.gn @@ -548,6 +548,8 @@ libtint_source_set("libtint_core_all_src") { "symbol_table.cc", "symbol_table.h", "traits.h", + "transform/array_length_from_uniform.cc", + "transform/array_length_from_uniform.h", "transform/binding_remapper.cc", "transform/binding_remapper.h", "transform/bound_array_accessors.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fc5d550a70..6bc55b05e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -273,6 +273,8 @@ set(TINT_LIB_SRCS symbol.cc symbol.h traits.h + transform/array_length_from_uniform.cc + transform/array_length_from_uniform.h transform/binding_remapper.cc transform/binding_remapper.h transform/bound_array_accessors.cc @@ -858,6 +860,7 @@ if(${TINT_BUILD_TESTS}) if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER}) list(APPEND TINT_TEST_SRCS + transform/array_length_from_uniform.cc transform/binding_remapper_test.cc transform/bound_array_accessors_test.cc transform/calculate_array_length_test.cc diff --git a/src/transform/array_length_from_uniform.cc b/src/transform/array_length_from_uniform.cc new file mode 100644 index 0000000000..e965cae76c --- /dev/null +++ b/src/transform/array_length_from_uniform.cc @@ -0,0 +1,165 @@ +// 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/array_length_from_uniform.h" + +#include +#include + +#include "src/ast/struct_block_decoration.h" +#include "src/program_builder.h" +#include "src/sem/call.h" +#include "src/sem/variable.h" + +TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Config); +TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Result); + +namespace tint { +namespace transform { + +ArrayLengthFromUniform::ArrayLengthFromUniform() = default; +ArrayLengthFromUniform::~ArrayLengthFromUniform() = default; + +Output ArrayLengthFromUniform::Run(const Program* in, const DataMap& data) { + ProgramBuilder out; + CloneContext ctx(&out, in); + + auto* cfg = data.Get(); + if (cfg == nullptr) { + out.Diagnostics().add_error( + "missing transform data for ArrayLengthFromUniform"); + return Output(Program(std::move(out))); + } + + auto& sem = ctx.src->Sem(); + + const char* kBufferSizeMemberName = "buffer_size"; + + // Determine the size of the buffer size array. + uint32_t max_buffer_size_index = 0; + for (auto& idx : cfg->bindpoint_to_size_index) { + if (idx.second > max_buffer_size_index) { + max_buffer_size_index = idx.second; + } + } + + // Get (or create, on first call) the uniform buffer that will receive the + // size of each storage buffer in the module. + ast::Variable* buffer_size_ubo = nullptr; + auto get_ubo = [&]() { + if (!buffer_size_ubo) { + auto* buffer_size_struct = ctx.dst->Structure( + ctx.dst->Sym(), + {ctx.dst->Member( + kBufferSizeMemberName, + ctx.dst->ty.array(ctx.dst->ty.u32(), max_buffer_size_index + 1))}, + ast::DecorationList{ctx.dst->create()}); + buffer_size_ubo = ctx.dst->Global( + ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct), + ast::StorageClass::kUniform, + ast::DecorationList{ + ctx.dst->create(cfg->ubo_binding.group), + ctx.dst->create( + cfg->ubo_binding.binding)}); + } + return buffer_size_ubo; + }; + + // Find all calls to the arrayLength() intrinsic. + for (auto* node : ctx.src->ASTNodes().Objects()) { + auto* call_expr = node->As(); + if (!call_expr) { + continue; + } + + auto* call = sem.Get(call_expr); + auto* intrinsic = call->Target()->As(); + if (!intrinsic || intrinsic->Type() != sem::IntrinsicType::kArrayLength) { + continue; + } + + // Get the storage buffer that contains the runtime array. + // We assume that the argument to `arrayLength` has the form + // `&resource.array`, which requires that `InlinePointerLets` and `Simplify` + // have been run before this transform. + auto* param = call_expr->params()[0]->As(); + if (!param || param->op() != ast::UnaryOp::kAddressOf) { + TINT_ICE(ctx.dst->Diagnostics()) + << "expected form of arrayLength argument to be &resource.array"; + break; + } + auto* accessor = param->expr()->As(); + if (!accessor) { + TINT_ICE(ctx.dst->Diagnostics()) + << "expected form of arrayLength argument to be &resource.array"; + break; + } + auto* storage_buffer_expr = accessor->structure(); + auto* storage_buffer_sem = + sem.Get(storage_buffer_expr)->As(); + if (!storage_buffer_sem) { + TINT_ICE(ctx.dst->Diagnostics()) + << "expected form of arrayLength argument to be &resource.array"; + break; + } + + // Get the index to use for the buffer size array. + auto binding = storage_buffer_sem->Variable()->BindingPoint(); + auto idx_itr = cfg->bindpoint_to_size_index.find(binding); + if (idx_itr == cfg->bindpoint_to_size_index.end()) { + ctx.dst->Diagnostics().add_error( + "missing size index mapping for binding point (" + + std::to_string(binding.group) + "," + + std::to_string(binding.binding) + ")"); + continue; + } + + // Load the total storage buffer size from the UBO. + auto* total_storage_buffer_size = ctx.dst->IndexAccessor( + ctx.dst->MemberAccessor(get_ubo()->symbol(), kBufferSizeMemberName), + idx_itr->second); + + // Calculate actual array length + // total_storage_buffer_size - array_offset + // array_length = ---------------------------------------- + // array_stride + auto* storage_buffer_type = + storage_buffer_sem->Type()->UnwrapRef()->As(); + auto* array_member_sem = storage_buffer_type->Members().back(); + uint32_t array_offset = array_member_sem->Offset(); + uint32_t array_stride = array_member_sem->Size(); + auto* array_length = ctx.dst->Div( + ctx.dst->Sub(total_storage_buffer_size, array_offset), array_stride); + + ctx.Replace(call_expr, array_length); + } + + ctx.Clone(); + + return Output{Program(std::move(out)), + std::make_unique(buffer_size_ubo ? true : false)}; +} + +ArrayLengthFromUniform::Config::Config(sem::BindingPoint ubo_bp) + : ubo_binding(ubo_bp) {} +ArrayLengthFromUniform::Config::Config(const Config&) = default; +ArrayLengthFromUniform::Config::~Config() = default; + +ArrayLengthFromUniform::Result::Result(bool needs_sizes) + : needs_buffer_sizes(needs_sizes) {} +ArrayLengthFromUniform::Result::Result(const Result&) = default; +ArrayLengthFromUniform::Result::~Result() = default; + +} // namespace transform +} // namespace tint diff --git a/src/transform/array_length_from_uniform.h b/src/transform/array_length_from_uniform.h new file mode 100644 index 0000000000..7ccb588c86 --- /dev/null +++ b/src/transform/array_length_from_uniform.h @@ -0,0 +1,104 @@ +// 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_ARRAY_LENGTH_FROM_UNIFORM_H_ +#define SRC_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_ + +#include + +#include "src/sem/binding_point.h" +#include "src/transform/transform.h" + +namespace tint { + +// Forward declarations +class CloneContext; + +namespace transform { + +/// ArrayLengthFromUniform is a transform that implements calls to arrayLength() +/// by calculating the length from the total size of the storage buffer, which +/// is received via a uniform buffer. +/// +/// The generated uniform buffer will have the form: +/// ``` +/// [[block]] +/// struct buffer_size_struct { +/// buffer_size : array; +/// }; +/// +/// [[group(0), binding(30)]] +/// var buffer_size_ubo : buffer_size_struct; +/// ``` +/// The binding group and number used for this uniform buffer is provided via +/// the `Config` transform input. The `Config` struct also defines the mapping +/// from a storage buffer's `BindingPoint` to the array index that will be used +/// to get the size of that buffer. +/// +/// This transform assumes that the `InlinePointerLets` and `Simplify` +/// transforms have been run before it so that arguments to the arrayLength +/// builtin always have the form `&resource.array`. +class ArrayLengthFromUniform : public Transform { + public: + /// Constructor + ArrayLengthFromUniform(); + /// Destructor + ~ArrayLengthFromUniform() override; + + /// Configuration options for the ArrayLengthFromUniform transform. + struct Config : public Castable { + /// Constructor + /// @param ubo_bp the binding point to use for the generated uniform buffer. + explicit Config(sem::BindingPoint ubo_bp); + + /// Copy constructor + Config(const Config&); + + /// Destructor + ~Config() override; + + /// The binding point to use for the generated uniform buffer. + sem::BindingPoint ubo_binding; + + /// The mapping from binding point to the index for the buffer size lookup. + std::unordered_map bindpoint_to_size_index; + }; + + /// Information produced about what the transform did. + struct Result : public Castable { + /// Constructor + /// @param needs_sizes True if the transform generated the buffer sizes UBO. + explicit Result(bool needs_sizes); + + /// Copy constructor + Result(const Result&); + + /// Destructor + ~Result() override; + + /// True if the transform generated the buffer sizes UBO. + bool const needs_buffer_sizes; + }; + + /// Runs the transform on `program`, returning the transformation result. + /// @param program the source program to transform + /// @param data optional extra transform-specific data + /// @returns the transformation result + Output Run(const Program* program, const DataMap& data = {}) override; +}; + +} // namespace transform +} // namespace tint + +#endif // SRC_TRANSFORM_ARRAY_LENGTH_FROM_UNIFORM_H_ diff --git a/src/transform/array_length_from_uniform_test.cc b/src/transform/array_length_from_uniform_test.cc new file mode 100644 index 0000000000..006e094fc9 --- /dev/null +++ b/src/transform/array_length_from_uniform_test.cc @@ -0,0 +1,283 @@ +// 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/array_length_from_uniform.h" + +#include + +#include "src/transform/test_helper.h" + +namespace tint { +namespace transform { +namespace { + +using ArrayLengthFromUniformTest = TransformTest; + +TEST_F(ArrayLengthFromUniformTest, Error_MissingTransformData) { + auto* src = ""; + + auto* expect = "error: missing transform data for ArrayLengthFromUniform"; + + auto got = Run(src); + + EXPECT_EQ(expect, str(got)); +} + +TEST_F(ArrayLengthFromUniformTest, Basic) { + auto* src = R"( +[[block]] +struct SB { + x : i32; + arr : array; +}; + +[[group(0), binding(0)]] var sb : SB; + +[[stage(compute)]] +fn main() { + var len : u32 = arrayLength(&sb.arr); +} +)"; + + auto* expect = R"( +[[block]] +struct tint_symbol { + buffer_size : array; +}; + +[[group(0), binding(30)]] var tint_symbol_1 : tint_symbol; + +[[block]] +struct SB { + x : i32; + arr : array; +}; + +[[group(0), binding(0)]] var sb : SB; + +[[stage(compute)]] +fn main() { + var len : u32 = ((tint_symbol_1.buffer_size[0u] - 4u) / 4u); +} +)"; + + ArrayLengthFromUniform::Config cfg({0, 30u}); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); + + DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); + EXPECT_TRUE( + got.data.Get()->needs_buffer_sizes); +} + +TEST_F(ArrayLengthFromUniformTest, WithStride) { + auto* src = R"( +[[block]] +struct SB { + x : i32; + y : f32; + arr : [[stride(64)]] array; +}; + +[[group(0), binding(0)]] var sb : SB; + +[[stage(compute)]] +fn main() { + var len : u32 = arrayLength(&sb.arr); +} +)"; + + auto* expect = R"( +[[block]] +struct tint_symbol { + buffer_size : array; +}; + +[[group(0), binding(30)]] var tint_symbol_1 : tint_symbol; + +[[block]] +struct SB { + x : i32; + y : f32; + arr : [[stride(64)]] array; +}; + +[[group(0), binding(0)]] var sb : SB; + +[[stage(compute)]] +fn main() { + var len : u32 = ((tint_symbol_1.buffer_size[0u] - 8u) / 64u); +} +)"; + + ArrayLengthFromUniform::Config cfg({0, 30u}); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); + + DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); + EXPECT_TRUE( + got.data.Get()->needs_buffer_sizes); +} + +TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) { + auto* src = R"( +[[block]] +struct SB1 { + x : i32; + arr1 : array; +}; + +[[block]] +struct SB2 { + x : i32; + arr2 : array>; +}; + +[[group(0), binding(2)]] var sb1 : SB1; + +[[group(1), binding(2)]] var sb2 : SB2; + +[[stage(compute)]] +fn main() { + var len1 : u32 = arrayLength(&(sb1.arr1)); + var len2 : u32 = arrayLength(&(sb2.arr2)); + var x : u32 = (len1 + len2); +} +)"; + + auto* expect = R"( +[[block]] +struct tint_symbol { + buffer_size : array; +}; + +[[group(0), binding(30)]] var tint_symbol_1 : tint_symbol; + +[[block]] +struct SB1 { + x : i32; + arr1 : array; +}; + +[[block]] +struct SB2 { + x : i32; + arr2 : array>; +}; + +[[group(0), binding(2)]] var sb1 : SB1; + +[[group(1), binding(2)]] var sb2 : SB2; + +[[stage(compute)]] +fn main() { + var len1 : u32 = ((tint_symbol_1.buffer_size[0u] - 4u) / 4u); + var len2 : u32 = ((tint_symbol_1.buffer_size[1u] - 16u) / 16u); + var x : u32 = (len1 + len2); +} +)"; + + ArrayLengthFromUniform::Config cfg({0, 30u}); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1); + + DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); + EXPECT_TRUE( + got.data.Get()->needs_buffer_sizes); +} + +TEST_F(ArrayLengthFromUniformTest, NoArrayLengthCalls) { + auto* src = R"( +[[block]] +struct SB { + x : i32; + arr : array; +}; + +[[group(0), binding(0)]] var sb : SB; + +[[stage(compute)]] +fn main() { + ignore(&(sb.arr)); +} +)"; + + ArrayLengthFromUniform::Config cfg({0, 30u}); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); + + DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(src, str(got)); + EXPECT_FALSE( + got.data.Get()->needs_buffer_sizes); +} + +TEST_F(ArrayLengthFromUniformTest, MissingBindingPointToIndexMapping) { + auto* src = R"( +[[block]] +struct SB1 { + x : i32; + arr1 : array; +}; + +[[block]] +struct SB2 { + x : i32; + arr2 : array>; +}; + +[[group(0), binding(2)]] var sb1 : SB1; + +[[group(1), binding(2)]] var sb2 : SB2; + +[[stage(compute)]] +fn main() { + var len1 : u32 = arrayLength(&(sb1.arr1)); + var len2 : u32 = arrayLength(&(sb2.arr2)); + var x : u32 = (len1 + len2); +} +)"; + + auto* expect = "error: missing size index mapping for binding point (1,2)"; + + ArrayLengthFromUniform::Config cfg({0, 30u}); + cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2}, 0); + + DataMap data; + data.Add(std::move(cfg)); + + auto got = Run(src, data); + + EXPECT_EQ(expect, str(got)); +} + +} // namespace +} // namespace transform +} // namespace tint diff --git a/src/transform/msl.cc b/src/transform/msl.cc index f32c01aabb..6d45462fa1 100644 --- a/src/transform/msl.cc +++ b/src/transform/msl.cc @@ -14,6 +14,7 @@ #include "src/transform/msl.h" +#include #include #include #include @@ -24,6 +25,7 @@ #include "src/sem/function.h" #include "src/sem/statement.h" #include "src/sem/variable.h" +#include "src/transform/array_length_from_uniform.h" #include "src/transform/canonicalize_entry_point_io.h" #include "src/transform/external_texture_transform.h" #include "src/transform/inline_pointer_lets.h" @@ -34,15 +36,38 @@ #include "src/transform/wrap_arrays_in_structs.h" #include "src/transform/zero_init_workgroup_memory.h" +TINT_INSTANTIATE_TYPEINFO(tint::transform::Msl::Config); +TINT_INSTANTIATE_TYPEINFO(tint::transform::Msl::Result); + namespace tint { namespace transform { Msl::Msl() = default; Msl::~Msl() = default; -Output Msl::Run(const Program* in, const DataMap&) { +Output Msl::Run(const Program* in, const DataMap& inputs) { Manager manager; - DataMap data; + DataMap internal_inputs; + + auto* cfg = inputs.Get(); + + // Build the config for the array length transform. + uint32_t buffer_size_ubo_index = kDefaultBufferSizeUniformIndex; + if (cfg) { + buffer_size_ubo_index = cfg->buffer_size_ubo_index; + } + auto array_length_from_uniform_cfg = ArrayLengthFromUniform::Config( + sem::BindingPoint{0, buffer_size_ubo_index}); + + // Use the SSBO binding numbers as the indices for the buffer size lookups. + for (auto* var : in->AST().GlobalVariables()) { + auto* sem_var = in->Sem().Get(var); + if (sem_var->StorageClass() == ast::StorageClass::kStorage) { + array_length_from_uniform_cfg.bindpoint_to_size_index.emplace( + sem_var->BindingPoint(), sem_var->BindingPoint().binding); + } + } + // ZeroInitWorkgroupMemory must come before CanonicalizeEntryPointIO as // ZeroInitWorkgroupMemory may inject new builtin parameters. manager.Add(); @@ -53,9 +78,14 @@ Output Msl::Run(const Program* in, const DataMap&) { manager.Add(); manager.Add(); manager.Add(); - data.Add( + // ArrayLengthFromUniform must come after InlinePointerLets and Simplify, as + // it assumes that the form of the array length argument is &var.array. + manager.Add(); + internal_inputs.Add( + std::move(array_length_from_uniform_cfg)); + internal_inputs.Add( CanonicalizeEntryPointIO::BuiltinStyle::kParameter); - auto out = manager.Run(in, data); + auto out = manager.Run(in, internal_inputs); if (!out.program.IsValid()) { return out; } @@ -66,7 +96,10 @@ Output Msl::Run(const Program* in, const DataMap&) { // storage class(es) as transform options. HandleModuleScopeVariables(ctx); ctx.Clone(); - return Output{Program(std::move(builder))}; + + auto result = std::make_unique( + out.data.Get()->needs_buffer_sizes); + return Output{Program(std::move(builder)), std::move(result)}; } void Msl::HandleModuleScopeVariables(CloneContext& ctx) const { @@ -241,5 +274,15 @@ void Msl::HandleModuleScopeVariables(CloneContext& ctx) const { } } +Msl::Config::Config(uint32_t buffer_size_ubo_idx) + : buffer_size_ubo_index(buffer_size_ubo_idx) {} +Msl::Config::Config(const Config&) = default; +Msl::Config::~Config() = default; + +Msl::Result::Result(bool needs_buffer_sizes) + : needs_storage_buffer_sizes(needs_buffer_sizes) {} +Msl::Result::Result(const Result&) = default; +Msl::Result::~Result() = default; + } // namespace transform } // namespace tint diff --git a/src/transform/msl.h b/src/transform/msl.h index 01ccb1a64d..2870f9306c 100644 --- a/src/transform/msl.h +++ b/src/transform/msl.h @@ -25,6 +25,41 @@ namespace transform { /// behavior. class Msl : public Transform { public: + /// The default buffer slot to use for the storage buffer size buffer. + const uint32_t kDefaultBufferSizeUniformIndex = 30; + + /// Configuration options for the Msl sanitizer transform. + struct Config : public Castable { + /// Constructor + /// @param buffer_size_ubo_idx the index to use for the buffer size UBO + explicit Config(uint32_t buffer_size_ubo_idx); + + /// Copy constructor + Config(const Config&); + + /// Destructor + ~Config() override; + + /// The index to use when generating a UBO to receive storage buffer sizes. + uint32_t buffer_size_ubo_index; + }; + + /// Information produced by the sanitizer that users may need to act on. + struct Result : public Castable { + /// Constructor + /// @param needs_buffer_sizes True if the shader needs a UBO of buffer sizes + explicit Result(bool needs_buffer_sizes); + + /// Copy constructor + Result(const Result&); + + /// Destructor + ~Result() override; + + /// True if the shader needs a UBO of buffer sizes. + bool const needs_storage_buffer_sizes; + }; + /// Constructor Msl(); ~Msl() override; diff --git a/test/BUILD.gn b/test/BUILD.gn index 04d781da93..2871a80ab8 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -275,6 +275,7 @@ tint_unittests_source_set("tint_unittests_core_src") { "../src/symbol_table_test.cc", "../src/symbol_test.cc", "../src/traits_test.cc", + "../src/transform/array_length_from_uniform_test.cc", "../src/transform/binding_remapper_test.cc", "../src/transform/bound_array_accessors_test.cc", "../src/transform/calculate_array_length_test.cc", diff --git a/test/intrinsics/arrayLength/complex_via_let.wgsl.expected.msl b/test/intrinsics/arrayLength/complex_via_let.wgsl.expected.msl index b142311dbe..d690d2497e 100644 --- a/test/intrinsics/arrayLength/complex_via_let.wgsl.expected.msl +++ b/test/intrinsics/arrayLength/complex_via_let.wgsl.expected.msl @@ -1,18 +1,15 @@ -SKIP: FAILED +#include - -[[block]] +using namespace metal; +struct tint_symbol_1 { + /* 0x0000 */ uint buffer_size[1]; +}; struct S { - a : array; + /* 0x0000 */ int a[1]; }; -[[group(0), binding(0)]] var G : S; - -[[stage(compute)]] -fn tint_symbol() { - let p = &(G); - let p2 = &((*(p)).a); - let l1 : u32 = arrayLength(p2); +kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) { + uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + return; } -Failed to generate: error: Unknown import method: arrayLength diff --git a/test/intrinsics/arrayLength/deprecated.wgsl.expected.msl b/test/intrinsics/arrayLength/deprecated.wgsl.expected.msl index 522865a044..c86fc59bd4 100644 --- a/test/intrinsics/arrayLength/deprecated.wgsl.expected.msl +++ b/test/intrinsics/arrayLength/deprecated.wgsl.expected.msl @@ -1,18 +1,16 @@ -SKIP: FAILED +#include - -[[block]] +using namespace metal; +struct tint_symbol_1 { + /* 0x0000 */ uint buffer_size[1]; +}; struct S { - a : array; + /* 0x0000 */ int a[1]; }; -[[group(0), binding(0)]] var G : S; - -[[stage(compute)]] -fn tint_symbol() { - let l1 : u32 = arrayLength(&(G.a)); - let p = &(G.a); - let l2 : u32 = arrayLength(p); +kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) { + uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + uint const l2 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + return; } -Failed to generate: error: Unknown import method: arrayLength diff --git a/test/intrinsics/arrayLength/simple.wgsl.expected.msl b/test/intrinsics/arrayLength/simple.wgsl.expected.msl index 61e65daf72..d690d2497e 100644 --- a/test/intrinsics/arrayLength/simple.wgsl.expected.msl +++ b/test/intrinsics/arrayLength/simple.wgsl.expected.msl @@ -1,16 +1,15 @@ -SKIP: FAILED +#include - -[[block]] +using namespace metal; +struct tint_symbol_1 { + /* 0x0000 */ uint buffer_size[1]; +}; struct S { - a : array; + /* 0x0000 */ int a[1]; }; -[[group(0), binding(0)]] var G : S; - -[[stage(compute)]] -fn tint_symbol() { - let l1 : u32 = arrayLength(&(G.a)); +kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) { + uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + return; } -Failed to generate: error: Unknown import method: arrayLength diff --git a/test/intrinsics/arrayLength/via_let.wgsl.expected.msl b/test/intrinsics/arrayLength/via_let.wgsl.expected.msl index e87fc055cd..d690d2497e 100644 --- a/test/intrinsics/arrayLength/via_let.wgsl.expected.msl +++ b/test/intrinsics/arrayLength/via_let.wgsl.expected.msl @@ -1,18 +1,15 @@ -SKIP: FAILED +#include - -[[block]] +using namespace metal; +struct tint_symbol_1 { + /* 0x0000 */ uint buffer_size[1]; +}; struct S { - a : array; + /* 0x0000 */ int a[1]; }; -[[group(0), binding(0)]] var G : S; - -[[stage(compute)]] -fn tint_symbol() { - let p = &(G.a); - let p2 = p; - let l1 : u32 = arrayLength(p2); +kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) { + uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + return; } -Failed to generate: error: Unknown import method: arrayLength diff --git a/test/intrinsics/arrayLength/via_let_complex.wgsl.expected.msl b/test/intrinsics/arrayLength/via_let_complex.wgsl.expected.msl index 9fba102d83..d690d2497e 100644 --- a/test/intrinsics/arrayLength/via_let_complex.wgsl.expected.msl +++ b/test/intrinsics/arrayLength/via_let_complex.wgsl.expected.msl @@ -1,19 +1,15 @@ -SKIP: FAILED +#include - -[[block]] +using namespace metal; +struct tint_symbol_1 { + /* 0x0000 */ uint buffer_size[1]; +}; struct S { - a : array; + /* 0x0000 */ int a[1]; }; -[[group(0), binding(0)]] var G : S; - -[[stage(compute)]] -fn tint_symbol() { - let p = &(*(&(G))); - let p2 = &(*(p)); - let p3 = &((*(p)).a); - let l1 : u32 = arrayLength(&(*(p3))); +kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) { + uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u); + return; } -Failed to generate: error: Unknown import method: arrayLength