| // 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/calculate_array_length.h" |
| |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "src/ast/call_statement.h" |
| #include "src/ast/disable_validation_decoration.h" |
| #include "src/program_builder.h" |
| #include "src/sem/block_statement.h" |
| #include "src/sem/call.h" |
| #include "src/sem/statement.h" |
| #include "src/sem/struct.h" |
| #include "src/sem/variable.h" |
| #include "src/utils/get_or_create.h" |
| #include "src/utils/hash.h" |
| |
| TINT_INSTANTIATE_TYPEINFO(tint::transform::CalculateArrayLength); |
| TINT_INSTANTIATE_TYPEINFO( |
| tint::transform::CalculateArrayLength::BufferSizeIntrinsic); |
| |
| namespace tint { |
| namespace transform { |
| |
| namespace { |
| |
| /// ArrayUsage describes a runtime array usage. |
| /// It is used as a key by the array_length_by_usage map. |
| struct ArrayUsage { |
| ast::BlockStatement const* const block; |
| sem::Node const* const buffer; |
| bool operator==(const ArrayUsage& rhs) const { |
| return block == rhs.block && buffer == rhs.buffer; |
| } |
| struct Hasher { |
| inline std::size_t operator()(const ArrayUsage& u) const { |
| return utils::Hash(u.block, u.buffer); |
| } |
| }; |
| }; |
| |
| } // namespace |
| |
| CalculateArrayLength::BufferSizeIntrinsic::BufferSizeIntrinsic( |
| ProgramID program_id) |
| : Base(program_id) {} |
| CalculateArrayLength::BufferSizeIntrinsic::~BufferSizeIntrinsic() = default; |
| std::string CalculateArrayLength::BufferSizeIntrinsic::InternalName() const { |
| return "intrinsic_buffer_size"; |
| } |
| |
| CalculateArrayLength::BufferSizeIntrinsic* |
| CalculateArrayLength::BufferSizeIntrinsic::Clone(CloneContext* ctx) const { |
| return ctx->dst->ASTNodes().Create<CalculateArrayLength::BufferSizeIntrinsic>( |
| ctx->dst->ID()); |
| } |
| |
| CalculateArrayLength::CalculateArrayLength() = default; |
| CalculateArrayLength::~CalculateArrayLength() = default; |
| |
| void CalculateArrayLength::Run(CloneContext& ctx, const DataMap&, DataMap&) { |
| auto& sem = ctx.src->Sem(); |
| |
| // get_buffer_size_intrinsic() emits the function decorated with |
| // BufferSizeIntrinsic that is transformed by the HLSL writer into a call to |
| // [RW]ByteAddressBuffer.GetDimensions(). |
| std::unordered_map<const sem::Struct*, Symbol> buffer_size_intrinsics; |
| auto get_buffer_size_intrinsic = [&](const sem::Struct* buffer_type) { |
| return utils::GetOrCreate(buffer_size_intrinsics, buffer_type, [&] { |
| auto name = ctx.dst->Sym(); |
| auto* buffer_typename = |
| ctx.dst->ty.type_name(ctx.Clone(buffer_type->Declaration()->name())); |
| auto* disable_validation = |
| ctx.dst->ASTNodes().Create<ast::DisableValidationDecoration>( |
| ctx.dst->ID(), |
| ast::DisabledValidation::kIgnoreConstructibleFunctionParameter); |
| auto* func = ctx.dst->create<ast::Function>( |
| name, |
| ast::VariableList{ |
| // Note: The buffer parameter requires the kStorage StorageClass |
| // in order for HLSL to emit this as a ByteAddressBuffer. |
| ctx.dst->create<ast::Variable>( |
| ctx.dst->Sym("buffer"), ast::StorageClass::kStorage, |
| ast::Access::kUndefined, buffer_typename, true, nullptr, |
| ast::DecorationList{disable_validation}), |
| ctx.dst->Param("result", |
| ctx.dst->ty.pointer(ctx.dst->ty.u32(), |
| ast::StorageClass::kFunction)), |
| }, |
| ctx.dst->ty.void_(), nullptr, |
| ast::DecorationList{ |
| ctx.dst->ASTNodes().Create<BufferSizeIntrinsic>(ctx.dst->ID()), |
| }, |
| ast::DecorationList{}); |
| ctx.InsertAfter(ctx.src->AST().GlobalDeclarations(), |
| buffer_type->Declaration(), func); |
| return name; |
| }); |
| }; |
| |
| std::unordered_map<ArrayUsage, Symbol, ArrayUsage::Hasher> |
| array_length_by_usage; |
| |
| // Find all the arrayLength() calls... |
| for (auto* node : ctx.src->ASTNodes().Objects()) { |
| if (auto* call_expr = node->As<ast::CallExpression>()) { |
| auto* call = sem.Get(call_expr); |
| if (auto* intrinsic = call->Target()->As<sem::Intrinsic>()) { |
| if (intrinsic->Type() == sem::IntrinsicType::kArrayLength) { |
| // We're dealing with an arrayLength() call |
| |
| // https://gpuweb.github.io/gpuweb/wgsl/#array-types states: |
| // |
| // * The last member of the structure type defining the store type for |
| // a variable in the storage storage class may be a runtime-sized |
| // array. |
| // * A runtime-sized array must not be used as the store type or |
| // contained within a store type in any other cases. |
| // * An expression must not evaluate to a runtime-sized array type. |
| // |
| // We can assume that the arrayLength() call has a single argument of |
| // the form: arrayLength(&X.Y) where X is an expression that resolves |
| // to the storage buffer structure, and Y is the runtime sized array. |
| auto* arg = call_expr->args()[0]; |
| auto* address_of = arg->As<ast::UnaryOpExpression>(); |
| if (!address_of || address_of->op() != ast::UnaryOp::kAddressOf) { |
| TINT_ICE(Transform, ctx.dst->Diagnostics()) |
| << "arrayLength() expected pointer to member access, got " |
| << address_of->TypeInfo().name; |
| } |
| auto* array_expr = address_of->expr(); |
| |
| auto* accessor = array_expr->As<ast::MemberAccessorExpression>(); |
| if (!accessor) { |
| TINT_ICE(Transform, ctx.dst->Diagnostics()) |
| << "arrayLength() expected pointer to member access, got " |
| "pointer to " |
| << array_expr->TypeInfo().name; |
| break; |
| } |
| auto* storage_buffer_expr = accessor->structure(); |
| auto* storage_buffer_sem = sem.Get(storage_buffer_expr); |
| auto* storage_buffer_type = |
| storage_buffer_sem->Type()->UnwrapRef()->As<sem::Struct>(); |
| |
| // Generate BufferSizeIntrinsic for this storage type if we haven't |
| // already |
| auto buffer_size = get_buffer_size_intrinsic(storage_buffer_type); |
| |
| if (!storage_buffer_type) { |
| TINT_ICE(Transform, ctx.dst->Diagnostics()) |
| << "arrayLength(X.Y) expected X to be sem::Struct, got " |
| << storage_buffer_type->FriendlyName(ctx.src->Symbols()); |
| break; |
| } |
| |
| // Find the current statement block |
| auto* block = call->Stmt()->Block()->Declaration(); |
| |
| // If the storage_buffer_expr is resolves to a variable (typically |
| // true) then key the array_length from the variable. If not, key off |
| // the expression semantic node, which will be unique per call to |
| // arrayLength(). |
| const sem::Node* storage_buffer_usage = storage_buffer_sem; |
| if (auto* user = storage_buffer_sem->As<sem::VariableUser>()) { |
| storage_buffer_usage = user->Variable(); |
| } |
| |
| auto array_length = utils::GetOrCreate( |
| array_length_by_usage, {block, storage_buffer_usage}, [&] { |
| // First time this array length is used for this block. |
| // Let's calculate it. |
| |
| // Semantic info for the runtime array structure member |
| auto* array_member_sem = storage_buffer_type->Members().back(); |
| |
| // Construct the variable that'll hold the result of |
| // RWByteAddressBuffer.GetDimensions() |
| auto* buffer_size_result = ctx.dst->Decl( |
| ctx.dst->Var(ctx.dst->Sym(), ctx.dst->ty.u32(), |
| ast::StorageClass::kNone, ctx.dst->Expr(0u))); |
| |
| // Call storage_buffer.GetDimensions(&buffer_size_result) |
| auto* call_get_dims = |
| ctx.dst->create<ast::CallStatement>(ctx.dst->Call( |
| // BufferSizeIntrinsic(X, ARGS...) is |
| // translated to: |
| // X.GetDimensions(ARGS..) by the writer |
| buffer_size, ctx.Clone(storage_buffer_expr), |
| ctx.dst->AddressOf(ctx.dst->Expr( |
| buffer_size_result->variable()->symbol())))); |
| |
| // Calculate actual array length |
| // total_storage_buffer_size - array_offset |
| // array_length = ---------------------------------------- |
| // array_stride |
| auto name = ctx.dst->Sym(); |
| uint32_t array_offset = array_member_sem->Offset(); |
| uint32_t array_stride = array_member_sem->Size(); |
| auto* array_length_var = ctx.dst->Decl(ctx.dst->Const( |
| name, ctx.dst->ty.u32(), |
| ctx.dst->Div( |
| ctx.dst->Sub(buffer_size_result->variable()->symbol(), |
| array_offset), |
| array_stride))); |
| |
| // Insert the array length calculations at the top of the block |
| ctx.InsertBefore(block->statements(), *block->begin(), |
| buffer_size_result); |
| ctx.InsertBefore(block->statements(), *block->begin(), |
| call_get_dims); |
| ctx.InsertBefore(block->statements(), *block->begin(), |
| array_length_var); |
| return name; |
| }); |
| |
| // Replace the call to arrayLength() with the array length variable |
| ctx.Replace(call_expr, ctx.dst->Expr(array_length)); |
| } |
| } |
| } |
| } |
| |
| ctx.Clone(); |
| } |
| |
| } // namespace transform |
| } // namespace tint |