// 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 #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 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(); 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. // 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(); 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()) { storage_buffer_expr = accessor->structure; } auto* storage_buffer_sem = sem.Get(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(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(); 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, 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(cfg->ubo_binding.group), ctx.dst->create( cfg->ubo_binding.binding)}); } return buffer_size_ubo; }; std::unordered_set 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()) { // 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(); total_size = ctx.dst->Sub(total_storage_buffer_size, array_member_sem->Offset()); } else if (auto* arr = storage_buffer_type->As()) { 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(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 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