mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-06-11 09:03:28 +00:00
This change adds an override for Transform::ShouldRun() for many of the transforms that can trivially detect whether running would be a no-op or not. Most programs do not require all the transforms to be run, and by skipping those that are not needed, significant performance wins can be had. This change also removes Transform::Requires() and Program::HasTransformApplied(). This makes little sense now that transforms can be skipped, and the usefulness of this information has been severely reduced since the introduction of transforms that need to be run more than once. Instread, just document on the transform class what the expectations are. Issue: tint:1383 Change-Id: I1a6f27cc4ba61ca1475a4ba912c465db619f76c7 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/77121 Reviewed-by: Antonio Maiorano <amaiorano@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@google.com>
236 lines
8.8 KiB
C++
236 lines
8.8 KiB
C++
// 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 <string>
|
|
#include <utility>
|
|
|
|
#include "src/program_builder.h"
|
|
#include "src/sem/call.h"
|
|
#include "src/sem/function.h"
|
|
#include "src/sem/variable.h"
|
|
#include "src/transform/simplify_pointers.h"
|
|
|
|
TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform);
|
|
TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Config);
|
|
TINT_INSTANTIATE_TYPEINFO(tint::transform::ArrayLengthFromUniform::Result);
|
|
|
|
namespace tint {
|
|
namespace transform {
|
|
|
|
ArrayLengthFromUniform::ArrayLengthFromUniform() = default;
|
|
ArrayLengthFromUniform::~ArrayLengthFromUniform() = default;
|
|
|
|
/// Iterate over all arrayLength() intrinsics that operate on
|
|
/// storage buffer variables.
|
|
/// @param ctx the CloneContext.
|
|
/// @param functor of type void(const ast::CallExpression*, const
|
|
/// sem::VariableUser, const sem::GlobalVariable*). It takes in an
|
|
/// ast::CallExpression of the arrayLength call expression node, a
|
|
/// sem::VariableUser of the used storage buffer variable, and the
|
|
/// sem::GlobalVariable for the storage buffer.
|
|
template <typename F>
|
|
static void IterateArrayLengthOnStorageVar(CloneContext& ctx, F&& functor) {
|
|
auto& sem = ctx.src->Sem();
|
|
|
|
// 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.
|
|
// Since we require SimplifyPointers, we can assume that the arrayLength()
|
|
// call has one of two forms:
|
|
// arrayLength(&struct_var.array_member)
|
|
// arrayLength(&array_var)
|
|
auto* param = call_expr->args[0]->As<ast::UnaryOpExpression>();
|
|
if (!param || param->op != ast::UnaryOp::kAddressOf) {
|
|
TINT_ICE(Transform, ctx.dst->Diagnostics())
|
|
<< "expected form of arrayLength argument to be &array_var or "
|
|
"&struct_var.array_member";
|
|
break;
|
|
}
|
|
auto* storage_buffer_expr = param->expr;
|
|
if (auto* accessor = param->expr->As<ast::MemberAccessorExpression>()) {
|
|
storage_buffer_expr = accessor->structure;
|
|
}
|
|
auto* storage_buffer_sem = sem.Get<sem::VariableUser>(storage_buffer_expr);
|
|
if (!storage_buffer_sem) {
|
|
TINT_ICE(Transform, ctx.dst->Diagnostics())
|
|
<< "expected form of arrayLength argument to be &array_var or "
|
|
"&struct_var.array_member";
|
|
break;
|
|
}
|
|
|
|
// Get the index to use for the buffer size array.
|
|
auto* var = tint::As<sem::GlobalVariable>(storage_buffer_sem->Variable());
|
|
if (!var) {
|
|
TINT_ICE(Transform, ctx.dst->Diagnostics())
|
|
<< "storage buffer is not a global variable";
|
|
break;
|
|
}
|
|
functor(call_expr, storage_buffer_sem, var);
|
|
}
|
|
}
|
|
|
|
bool ArrayLengthFromUniform::ShouldRun(const Program* program,
|
|
const DataMap&) const {
|
|
for (auto* fn : program->AST().Functions()) {
|
|
if (auto* sem_fn = program->Sem().Get(fn)) {
|
|
for (auto* intrinsic : sem_fn->DirectlyCalledIntrinsics()) {
|
|
if (intrinsic->Type() == sem::IntrinsicType::kArrayLength) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ArrayLengthFromUniform::Run(CloneContext& ctx,
|
|
const DataMap& inputs,
|
|
DataMap& outputs) const {
|
|
auto* cfg = inputs.Get<Config>();
|
|
if (cfg == nullptr) {
|
|
ctx.dst->Diagnostics().add_error(
|
|
diag::System::Transform,
|
|
"missing transform data for " + std::string(TypeInfo().name));
|
|
return;
|
|
}
|
|
|
|
const char* kBufferSizeMemberName = "buffer_size";
|
|
|
|
// Determine the size of the buffer size array.
|
|
uint32_t max_buffer_size_index = 0;
|
|
|
|
IterateArrayLengthOnStorageVar(
|
|
ctx, [&](const ast::CallExpression*, const sem::VariableUser*,
|
|
const sem::GlobalVariable* var) {
|
|
auto binding = var->BindingPoint();
|
|
auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
|
|
if (idx_itr == cfg->bindpoint_to_size_index.end()) {
|
|
return;
|
|
}
|
|
if (idx_itr->second > max_buffer_size_index) {
|
|
max_buffer_size_index = idx_itr->second;
|
|
}
|
|
});
|
|
|
|
// Get (or create, on first call) the uniform buffer that will receive the
|
|
// size of each storage buffer in the module.
|
|
const ast::Variable* buffer_size_ubo = nullptr;
|
|
auto get_ubo = [&]() {
|
|
if (!buffer_size_ubo) {
|
|
// Emit an array<vec4<u32>, N>, where N is 1/4 number of elements.
|
|
// We do this because UBOs require an element stride that is 16-byte
|
|
// aligned.
|
|
auto* buffer_size_struct = ctx.dst->Structure(
|
|
ctx.dst->Sym(),
|
|
{ctx.dst->Member(
|
|
kBufferSizeMemberName,
|
|
ctx.dst->ty.array(ctx.dst->ty.vec4(ctx.dst->ty.u32()),
|
|
(max_buffer_size_index / 4) + 1))});
|
|
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;
|
|
};
|
|
|
|
std::unordered_set<uint32_t> used_size_indices;
|
|
|
|
IterateArrayLengthOnStorageVar(
|
|
ctx, [&](const ast::CallExpression* call_expr,
|
|
const sem::VariableUser* storage_buffer_sem,
|
|
const sem::GlobalVariable* var) {
|
|
auto binding = var->BindingPoint();
|
|
auto idx_itr = cfg->bindpoint_to_size_index.find(binding);
|
|
if (idx_itr == cfg->bindpoint_to_size_index.end()) {
|
|
return;
|
|
}
|
|
|
|
uint32_t size_index = idx_itr->second;
|
|
used_size_indices.insert(size_index);
|
|
|
|
// Load the total storage buffer size from the UBO.
|
|
uint32_t array_index = size_index / 4;
|
|
auto* vec_expr = ctx.dst->IndexAccessor(
|
|
ctx.dst->MemberAccessor(get_ubo()->symbol, kBufferSizeMemberName),
|
|
array_index);
|
|
uint32_t vec_index = size_index % 4;
|
|
auto* total_storage_buffer_size =
|
|
ctx.dst->IndexAccessor(vec_expr, vec_index);
|
|
|
|
// Calculate actual array length
|
|
// total_storage_buffer_size - array_offset
|
|
// array_length = ----------------------------------------
|
|
// array_stride
|
|
const ast::Expression* total_size = total_storage_buffer_size;
|
|
auto* storage_buffer_type = storage_buffer_sem->Type()->UnwrapRef();
|
|
const sem::Array* array_type = nullptr;
|
|
if (auto* str = storage_buffer_type->As<sem::Struct>()) {
|
|
// The variable is a struct, so subtract the byte offset of the array
|
|
// member.
|
|
auto* array_member_sem = str->Members().back();
|
|
array_type = array_member_sem->Type()->As<sem::Array>();
|
|
total_size = ctx.dst->Sub(total_storage_buffer_size,
|
|
array_member_sem->Offset());
|
|
} else if (auto* arr = storage_buffer_type->As<sem::Array>()) {
|
|
array_type = arr;
|
|
} else {
|
|
TINT_ICE(Transform, ctx.dst->Diagnostics())
|
|
<< "expected form of arrayLength argument to be &array_var or "
|
|
"&struct_var.array_member";
|
|
return;
|
|
}
|
|
auto* array_length = ctx.dst->Div(total_size, array_type->Stride());
|
|
|
|
ctx.Replace(call_expr, array_length);
|
|
});
|
|
|
|
ctx.Clone();
|
|
|
|
outputs.Add<Result>(used_size_indices);
|
|
}
|
|
|
|
ArrayLengthFromUniform::Config::Config(sem::BindingPoint ubo_bp)
|
|
: ubo_binding(ubo_bp) {}
|
|
ArrayLengthFromUniform::Config::Config(const Config&) = default;
|
|
ArrayLengthFromUniform::Config& ArrayLengthFromUniform::Config::operator=(
|
|
const Config&) = default;
|
|
ArrayLengthFromUniform::Config::~Config() = default;
|
|
|
|
ArrayLengthFromUniform::Result::Result(
|
|
std::unordered_set<uint32_t> used_size_indices_in)
|
|
: used_size_indices(std::move(used_size_indices_in)) {}
|
|
ArrayLengthFromUniform::Result::Result(const Result&) = default;
|
|
ArrayLengthFromUniform::Result::~Result() = default;
|
|
|
|
} // namespace transform
|
|
} // namespace tint
|