mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-07-08 14:15:58 +00:00
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 <noreply+kokoro@google.com> Commit-Queue: James Price <jrprice@google.com> Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
75db82c96b
commit
960aa2ee75
@ -548,6 +548,8 @@ libtint_source_set("libtint_core_all_src") {
|
|||||||
"symbol_table.cc",
|
"symbol_table.cc",
|
||||||
"symbol_table.h",
|
"symbol_table.h",
|
||||||
"traits.h",
|
"traits.h",
|
||||||
|
"transform/array_length_from_uniform.cc",
|
||||||
|
"transform/array_length_from_uniform.h",
|
||||||
"transform/binding_remapper.cc",
|
"transform/binding_remapper.cc",
|
||||||
"transform/binding_remapper.h",
|
"transform/binding_remapper.h",
|
||||||
"transform/bound_array_accessors.cc",
|
"transform/bound_array_accessors.cc",
|
||||||
|
@ -273,6 +273,8 @@ set(TINT_LIB_SRCS
|
|||||||
symbol.cc
|
symbol.cc
|
||||||
symbol.h
|
symbol.h
|
||||||
traits.h
|
traits.h
|
||||||
|
transform/array_length_from_uniform.cc
|
||||||
|
transform/array_length_from_uniform.h
|
||||||
transform/binding_remapper.cc
|
transform/binding_remapper.cc
|
||||||
transform/binding_remapper.h
|
transform/binding_remapper.h
|
||||||
transform/bound_array_accessors.cc
|
transform/bound_array_accessors.cc
|
||||||
@ -858,6 +860,7 @@ if(${TINT_BUILD_TESTS})
|
|||||||
|
|
||||||
if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
|
if(${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
|
||||||
list(APPEND TINT_TEST_SRCS
|
list(APPEND TINT_TEST_SRCS
|
||||||
|
transform/array_length_from_uniform.cc
|
||||||
transform/binding_remapper_test.cc
|
transform/binding_remapper_test.cc
|
||||||
transform/bound_array_accessors_test.cc
|
transform/bound_array_accessors_test.cc
|
||||||
transform/calculate_array_length_test.cc
|
transform/calculate_array_length_test.cc
|
||||||
|
165
src/transform/array_length_from_uniform.cc
Normal file
165
src/transform/array_length_from_uniform.cc
Normal file
@ -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 <memory>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#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<Config>();
|
||||||
|
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<ast::StructBlockDecoration>()});
|
||||||
|
buffer_size_ubo = ctx.dst->Global(
|
||||||
|
ctx.dst->Sym(), ctx.dst->ty.Of(buffer_size_struct),
|
||||||
|
ast::StorageClass::kUniform,
|
||||||
|
ast::DecorationList{
|
||||||
|
ctx.dst->create<ast::GroupDecoration>(cfg->ubo_binding.group),
|
||||||
|
ctx.dst->create<ast::BindingDecoration>(
|
||||||
|
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<ast::CallExpression>();
|
||||||
|
if (!call_expr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* call = sem.Get(call_expr);
|
||||||
|
auto* intrinsic = call->Target()->As<sem::Intrinsic>();
|
||||||
|
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<ast::UnaryOpExpression>();
|
||||||
|
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<ast::MemberAccessorExpression>();
|
||||||
|
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<sem::VariableUser>();
|
||||||
|
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<sem::Struct>();
|
||||||
|
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<Result>(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
|
104
src/transform/array_length_from_uniform.h
Normal file
104
src/transform/array_length_from_uniform.h
Normal file
@ -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 <unordered_map>
|
||||||
|
|
||||||
|
#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<u32, 8>;
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// [[group(0), binding(30)]]
|
||||||
|
/// var<uniform> 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<Data, transform::Data> {
|
||||||
|
/// 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<sem::BindingPoint, uint32_t> bindpoint_to_size_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Information produced about what the transform did.
|
||||||
|
struct Result : public Castable<Result, transform::Data> {
|
||||||
|
/// 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_
|
283
src/transform/array_length_from_uniform_test.cc
Normal file
283
src/transform/array_length_from_uniform_test.cc
Normal file
@ -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 <utility>
|
||||||
|
|
||||||
|
#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<ArrayLengthFromUniform>(src);
|
||||||
|
|
||||||
|
EXPECT_EQ(expect, str(got));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ArrayLengthFromUniformTest, Basic) {
|
||||||
|
auto* src = R"(
|
||||||
|
[[block]]
|
||||||
|
struct SB {
|
||||||
|
x : i32;
|
||||||
|
arr : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> sb : SB;
|
||||||
|
|
||||||
|
[[stage(compute)]]
|
||||||
|
fn main() {
|
||||||
|
var len : u32 = arrayLength(&sb.arr);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto* expect = R"(
|
||||||
|
[[block]]
|
||||||
|
struct tint_symbol {
|
||||||
|
buffer_size : array<u32, 1>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(30)]] var<uniform> tint_symbol_1 : tint_symbol;
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB {
|
||||||
|
x : i32;
|
||||||
|
arr : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> 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<ArrayLengthFromUniform::Config>(std::move(cfg));
|
||||||
|
|
||||||
|
auto got = Run<ArrayLengthFromUniform>(src, data);
|
||||||
|
|
||||||
|
EXPECT_EQ(expect, str(got));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
got.data.Get<ArrayLengthFromUniform::Result>()->needs_buffer_sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ArrayLengthFromUniformTest, WithStride) {
|
||||||
|
auto* src = R"(
|
||||||
|
[[block]]
|
||||||
|
struct SB {
|
||||||
|
x : i32;
|
||||||
|
y : f32;
|
||||||
|
arr : [[stride(64)]] array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> sb : SB;
|
||||||
|
|
||||||
|
[[stage(compute)]]
|
||||||
|
fn main() {
|
||||||
|
var len : u32 = arrayLength(&sb.arr);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
auto* expect = R"(
|
||||||
|
[[block]]
|
||||||
|
struct tint_symbol {
|
||||||
|
buffer_size : array<u32, 1>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(30)]] var<uniform> tint_symbol_1 : tint_symbol;
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB {
|
||||||
|
x : i32;
|
||||||
|
y : f32;
|
||||||
|
arr : [[stride(64)]] array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> 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<ArrayLengthFromUniform::Config>(std::move(cfg));
|
||||||
|
|
||||||
|
auto got = Run<ArrayLengthFromUniform>(src, data);
|
||||||
|
|
||||||
|
EXPECT_EQ(expect, str(got));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
got.data.Get<ArrayLengthFromUniform::Result>()->needs_buffer_sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) {
|
||||||
|
auto* src = R"(
|
||||||
|
[[block]]
|
||||||
|
struct SB1 {
|
||||||
|
x : i32;
|
||||||
|
arr1 : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB2 {
|
||||||
|
x : i32;
|
||||||
|
arr2 : array<vec4<f32>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(2)]] var<storage, read> sb1 : SB1;
|
||||||
|
|
||||||
|
[[group(1), binding(2)]] var<storage, read> 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<u32, 2>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(30)]] var<uniform> tint_symbol_1 : tint_symbol;
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB1 {
|
||||||
|
x : i32;
|
||||||
|
arr1 : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB2 {
|
||||||
|
x : i32;
|
||||||
|
arr2 : array<vec4<f32>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(2)]] var<storage, read> sb1 : SB1;
|
||||||
|
|
||||||
|
[[group(1), binding(2)]] var<storage, read> 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<ArrayLengthFromUniform::Config>(std::move(cfg));
|
||||||
|
|
||||||
|
auto got = Run<ArrayLengthFromUniform>(src, data);
|
||||||
|
|
||||||
|
EXPECT_EQ(expect, str(got));
|
||||||
|
EXPECT_TRUE(
|
||||||
|
got.data.Get<ArrayLengthFromUniform::Result>()->needs_buffer_sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ArrayLengthFromUniformTest, NoArrayLengthCalls) {
|
||||||
|
auto* src = R"(
|
||||||
|
[[block]]
|
||||||
|
struct SB {
|
||||||
|
x : i32;
|
||||||
|
arr : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(0)]] var<storage, read> 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<ArrayLengthFromUniform::Config>(std::move(cfg));
|
||||||
|
|
||||||
|
auto got = Run<ArrayLengthFromUniform>(src, data);
|
||||||
|
|
||||||
|
EXPECT_EQ(src, str(got));
|
||||||
|
EXPECT_FALSE(
|
||||||
|
got.data.Get<ArrayLengthFromUniform::Result>()->needs_buffer_sizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ArrayLengthFromUniformTest, MissingBindingPointToIndexMapping) {
|
||||||
|
auto* src = R"(
|
||||||
|
[[block]]
|
||||||
|
struct SB1 {
|
||||||
|
x : i32;
|
||||||
|
arr1 : array<i32>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[block]]
|
||||||
|
struct SB2 {
|
||||||
|
x : i32;
|
||||||
|
arr2 : array<vec4<f32>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[group(0), binding(2)]] var<storage, read> sb1 : SB1;
|
||||||
|
|
||||||
|
[[group(1), binding(2)]] var<storage, read> 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<ArrayLengthFromUniform::Config>(std::move(cfg));
|
||||||
|
|
||||||
|
auto got = Run<ArrayLengthFromUniform>(src, data);
|
||||||
|
|
||||||
|
EXPECT_EQ(expect, str(got));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace transform
|
||||||
|
} // namespace tint
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "src/transform/msl.h"
|
#include "src/transform/msl.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -24,6 +25,7 @@
|
|||||||
#include "src/sem/function.h"
|
#include "src/sem/function.h"
|
||||||
#include "src/sem/statement.h"
|
#include "src/sem/statement.h"
|
||||||
#include "src/sem/variable.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/canonicalize_entry_point_io.h"
|
||||||
#include "src/transform/external_texture_transform.h"
|
#include "src/transform/external_texture_transform.h"
|
||||||
#include "src/transform/inline_pointer_lets.h"
|
#include "src/transform/inline_pointer_lets.h"
|
||||||
@ -34,15 +36,38 @@
|
|||||||
#include "src/transform/wrap_arrays_in_structs.h"
|
#include "src/transform/wrap_arrays_in_structs.h"
|
||||||
#include "src/transform/zero_init_workgroup_memory.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 tint {
|
||||||
namespace transform {
|
namespace transform {
|
||||||
|
|
||||||
Msl::Msl() = default;
|
Msl::Msl() = default;
|
||||||
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;
|
Manager manager;
|
||||||
DataMap data;
|
DataMap internal_inputs;
|
||||||
|
|
||||||
|
auto* cfg = inputs.Get<Config>();
|
||||||
|
|
||||||
|
// 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 must come before CanonicalizeEntryPointIO as
|
||||||
// ZeroInitWorkgroupMemory may inject new builtin parameters.
|
// ZeroInitWorkgroupMemory may inject new builtin parameters.
|
||||||
manager.Add<ZeroInitWorkgroupMemory>();
|
manager.Add<ZeroInitWorkgroupMemory>();
|
||||||
@ -53,9 +78,14 @@ Output Msl::Run(const Program* in, const DataMap&) {
|
|||||||
manager.Add<PadArrayElements>();
|
manager.Add<PadArrayElements>();
|
||||||
manager.Add<InlinePointerLets>();
|
manager.Add<InlinePointerLets>();
|
||||||
manager.Add<Simplify>();
|
manager.Add<Simplify>();
|
||||||
data.Add<CanonicalizeEntryPointIO::Config>(
|
// ArrayLengthFromUniform must come after InlinePointerLets and Simplify, as
|
||||||
|
// it assumes that the form of the array length argument is &var.array.
|
||||||
|
manager.Add<ArrayLengthFromUniform>();
|
||||||
|
internal_inputs.Add<ArrayLengthFromUniform::Config>(
|
||||||
|
std::move(array_length_from_uniform_cfg));
|
||||||
|
internal_inputs.Add<CanonicalizeEntryPointIO::Config>(
|
||||||
CanonicalizeEntryPointIO::BuiltinStyle::kParameter);
|
CanonicalizeEntryPointIO::BuiltinStyle::kParameter);
|
||||||
auto out = manager.Run(in, data);
|
auto out = manager.Run(in, internal_inputs);
|
||||||
if (!out.program.IsValid()) {
|
if (!out.program.IsValid()) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@ -66,7 +96,10 @@ Output Msl::Run(const Program* in, const DataMap&) {
|
|||||||
// storage class(es) as transform options.
|
// storage class(es) as transform options.
|
||||||
HandleModuleScopeVariables(ctx);
|
HandleModuleScopeVariables(ctx);
|
||||||
ctx.Clone();
|
ctx.Clone();
|
||||||
return Output{Program(std::move(builder))};
|
|
||||||
|
auto result = std::make_unique<Result>(
|
||||||
|
out.data.Get<ArrayLengthFromUniform::Result>()->needs_buffer_sizes);
|
||||||
|
return Output{Program(std::move(builder)), std::move(result)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Msl::HandleModuleScopeVariables(CloneContext& ctx) const {
|
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 transform
|
||||||
} // namespace tint
|
} // namespace tint
|
||||||
|
@ -25,6 +25,41 @@ namespace transform {
|
|||||||
/// behavior.
|
/// behavior.
|
||||||
class Msl : public Transform {
|
class Msl : public Transform {
|
||||||
public:
|
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<Data, transform::Data> {
|
||||||
|
/// 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<Result, transform::Data> {
|
||||||
|
/// 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
|
/// Constructor
|
||||||
Msl();
|
Msl();
|
||||||
~Msl() override;
|
~Msl() override;
|
||||||
|
@ -275,6 +275,7 @@ tint_unittests_source_set("tint_unittests_core_src") {
|
|||||||
"../src/symbol_table_test.cc",
|
"../src/symbol_table_test.cc",
|
||||||
"../src/symbol_test.cc",
|
"../src/symbol_test.cc",
|
||||||
"../src/traits_test.cc",
|
"../src/traits_test.cc",
|
||||||
|
"../src/transform/array_length_from_uniform_test.cc",
|
||||||
"../src/transform/binding_remapper_test.cc",
|
"../src/transform/binding_remapper_test.cc",
|
||||||
"../src/transform/bound_array_accessors_test.cc",
|
"../src/transform/bound_array_accessors_test.cc",
|
||||||
"../src/transform/calculate_array_length_test.cc",
|
"../src/transform/calculate_array_length_test.cc",
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
SKIP: FAILED
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
[[block]]
|
struct tint_symbol_1 {
|
||||||
|
/* 0x0000 */ uint buffer_size[1];
|
||||||
|
};
|
||||||
struct S {
|
struct S {
|
||||||
a : array<i32>;
|
/* 0x0000 */ int a[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(0), binding(0)]] var<storage, read> G : S;
|
kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) {
|
||||||
|
uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
[[stage(compute)]]
|
return;
|
||||||
fn tint_symbol() {
|
|
||||||
let p = &(G);
|
|
||||||
let p2 = &((*(p)).a);
|
|
||||||
let l1 : u32 = arrayLength(p2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Failed to generate: error: Unknown import method: arrayLength
|
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
SKIP: FAILED
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
[[block]]
|
struct tint_symbol_1 {
|
||||||
|
/* 0x0000 */ uint buffer_size[1];
|
||||||
|
};
|
||||||
struct S {
|
struct S {
|
||||||
a : array<i32>;
|
/* 0x0000 */ int a[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(0), binding(0)]] var<storage, read> G : S;
|
kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) {
|
||||||
|
uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
[[stage(compute)]]
|
uint const l2 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
fn tint_symbol() {
|
return;
|
||||||
let l1 : u32 = arrayLength(&(G.a));
|
|
||||||
let p = &(G.a);
|
|
||||||
let l2 : u32 = arrayLength(p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Failed to generate: error: Unknown import method: arrayLength
|
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
SKIP: FAILED
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
[[block]]
|
struct tint_symbol_1 {
|
||||||
|
/* 0x0000 */ uint buffer_size[1];
|
||||||
|
};
|
||||||
struct S {
|
struct S {
|
||||||
a : array<i32>;
|
/* 0x0000 */ int a[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(0), binding(0)]] var<storage, read> G : S;
|
kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) {
|
||||||
|
uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
[[stage(compute)]]
|
return;
|
||||||
fn tint_symbol() {
|
|
||||||
let l1 : u32 = arrayLength(&(G.a));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Failed to generate: error: Unknown import method: arrayLength
|
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
SKIP: FAILED
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
[[block]]
|
struct tint_symbol_1 {
|
||||||
|
/* 0x0000 */ uint buffer_size[1];
|
||||||
|
};
|
||||||
struct S {
|
struct S {
|
||||||
a : array<i32>;
|
/* 0x0000 */ int a[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(0), binding(0)]] var<storage, read> G : S;
|
kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) {
|
||||||
|
uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
[[stage(compute)]]
|
return;
|
||||||
fn tint_symbol() {
|
|
||||||
let p = &(G.a);
|
|
||||||
let p2 = p;
|
|
||||||
let l1 : u32 = arrayLength(p2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Failed to generate: error: Unknown import method: arrayLength
|
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
SKIP: FAILED
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
[[block]]
|
struct tint_symbol_1 {
|
||||||
|
/* 0x0000 */ uint buffer_size[1];
|
||||||
|
};
|
||||||
struct S {
|
struct S {
|
||||||
a : array<i32>;
|
/* 0x0000 */ int a[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
[[group(0), binding(0)]] var<storage, read> G : S;
|
kernel void tint_symbol(constant tint_symbol_1& tint_symbol_2 [[buffer(30)]]) {
|
||||||
|
uint const l1 = ((tint_symbol_2.buffer_size[0u] - 0u) / 4u);
|
||||||
[[stage(compute)]]
|
return;
|
||||||
fn tint_symbol() {
|
|
||||||
let p = &(*(&(G)));
|
|
||||||
let p2 = &(*(p));
|
|
||||||
let p3 = &((*(p)).a);
|
|
||||||
let l1 : u32 = arrayLength(&(*(p3)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Failed to generate: error: Unknown import method: arrayLength
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user