| // Copyright 2023 The Dawn & Tint Authors |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // 3. Neither the name of the copyright holder nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "src/tint/lang/core/ir/transform/direct_variable_access.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "src/tint/lang/core/ir/builder.h" |
| #include "src/tint/lang/core/ir/clone_context.h" |
| #include "src/tint/lang/core/ir/module.h" |
| #include "src/tint/lang/core/ir/traverse.h" |
| #include "src/tint/lang/core/ir/user_call.h" |
| #include "src/tint/lang/core/ir/validator.h" |
| #include "src/tint/lang/core/ir/var.h" |
| #include "src/tint/utils/containers/reverse.h" |
| |
| using namespace tint::core::fluent_types; // NOLINT |
| using namespace tint::core::number_suffixes; // NOLINT |
| |
| namespace tint::core::ir::transform { |
| namespace { |
| |
| /// An access root, originating from a module-scope var. |
| /// These roots are not passed by parameter, but instead the callee references the module-scope var |
| /// directly. |
| struct RootModuleScopeVar { |
| /// The module-scope var |
| Var* var = nullptr; |
| |
| /// @return a hash value for this object |
| size_t HashCode() const { return Hash(var); } |
| |
| /// Inequality operator |
| bool operator!=(const RootModuleScopeVar& other) const { return var != other.var; } |
| }; |
| |
| /// An access root, originating from a pointer parameter or function-scope var. |
| /// These roots are passed by pointer parameter. |
| struct RootPtrParameter { |
| /// The parameter pointer type |
| const type::Pointer* type = nullptr; |
| |
| /// @return a hash value for this object |
| size_t HashCode() const { return Hash(type); } |
| |
| /// Inequality operator |
| bool operator!=(const RootPtrParameter& other) const { return type != other.type; } |
| }; |
| |
| /// An access root. Either a RootModuleScopeVar or RootPtrParameter. |
| using AccessRoot = std::variant<RootModuleScopeVar, RootPtrParameter>; |
| |
| /// MemberAccess is an access operator to a struct member. |
| struct MemberAccess { |
| /// The member being accessed |
| const type::StructMember* member; |
| |
| /// @return a hash member for this object |
| size_t HashCode() const { return Hash(member); } |
| |
| /// Inequality operator |
| bool operator!=(const MemberAccess& other) const { return member != other.member; } |
| }; |
| |
| /// IndexAccess is an access operator to an array element or matrix column. |
| /// The ordered list of indices is passed by parameter. |
| struct IndexAccess { |
| /// @return a hash value for this object |
| size_t HashCode() const { return 42; } |
| |
| /// Inequality operator |
| bool operator!=(const IndexAccess&) const { return false; } |
| }; |
| |
| /// An access operation. Either a MemberAccess or IndexAccess. |
| using AccessOp = std::variant<MemberAccess, IndexAccess>; |
| |
| /// A AccessShape describes the static "path" from a root variable to an element within the |
| /// variable. |
| /// |
| /// Functions that have pointer parameters which need transforming will be forked into one or more |
| /// 'variants'. Each variant has different AccessShapes for the pointer parameters - the transform |
| /// will only emit one variant when the shapes of the pointer parameter accesses match. |
| /// |
| /// Array accessors index expressions are held externally to the AccessShape, so |
| /// AccessShape will be considered equal even if the array or matrix index values differ. |
| /// |
| /// For example, consider the following: |
| /// |
| /// ``` |
| /// struct A { |
| /// x : array<i32, 8>, |
| /// y : u32, |
| /// }; |
| /// struct B { |
| /// x : i32, |
| /// y : array<A, 4> |
| /// }; |
| /// var<workgroup> C : B; |
| /// ``` |
| /// |
| /// The following AccessShape would describe the following: |
| /// |
| /// +====================================+===============+=================================+ |
| /// | AccessShape | Type | Expression | |
| /// +====================================+===============+=================================+ |
| /// | [ Var 'C', MemberAccess 'x' ] | i32 | C.x | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// | [ Var 'C', MemberAccess 'y' ] | array<A, 4> | C.y | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// | [ Var 'C', MemberAccess 'y', | A | C.y[indices[0]] | |
| /// | IndexAccess ] | | | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// | [ Var 'C', MemberAccess 'y', | array<i32, 8> | C.y[indices[0]].x | |
| /// | IndexAccess, MemberAccess 'x' ] | | | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// | [ Var 'C', MemberAccess 'y', | i32 | C.y[indices[0]].x[indices[1]] | |
| /// | IndexAccess, MemberAccess 'x', | | | |
| /// | IndexAccess ] | | | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// | [ Var 'C', MemberAccess 'y', | u32 | C.y[indices[0]].y | |
| /// | IndexAccess, MemberAccess 'y' ] | | | |
| /// +------------------------------------+---------------+---------------------------------+ |
| /// |
| /// Where: `indices` is the AccessChain::indices. |
| struct AccessShape { |
| /// The access root. |
| AccessRoot root; |
| /// The access operations. |
| Vector<AccessOp, 8> ops; |
| |
| /// @returns the number of IndexAccess operations in #ops. |
| uint32_t NumIndexAccesses() const { |
| uint32_t count = 0; |
| for (auto& op : ops) { |
| if (std::holds_alternative<IndexAccess>(op)) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| /// @return a hash value for this object |
| size_t HashCode() const { return Hash(root, ops); } |
| |
| /// Inequality operator |
| bool operator!=(const AccessShape& other) const { |
| return root != other.root || ops != other.ops; |
| } |
| }; |
| |
| /// AccessChain describes a chain of access expressions originating from a variable. |
| struct AccessChain { |
| /// The shape of the access chain |
| AccessShape shape; |
| /// The originating pointer |
| Value* root_ptr = nullptr; |
| /// The array of dynamic indices |
| Vector<Value*, 8> indices; |
| }; |
| |
| /// A variant signature describes the access shape of all the function's pointer parameters. |
| /// This is a map of pointer parameter index to access shape. |
| using VariantSignature = Hashmap<size_t, AccessShape, 4>; |
| |
| /// FnInfo describes a function that has pointer parameters which need replacing. |
| /// This function will be replaced by zero, one or many variants. Each variant will have a unique |
| /// access shape for the function's the pointer arguments. |
| struct FnInfo { |
| /// A map of variant signature to the variant's unique IR function. |
| Hashmap<VariantSignature, Function*, 4> variants_by_sig; |
| /// The order to emit the variants in the final module. |
| Vector<Function*, 4> ordered_variants; |
| }; |
| |
| /// FnVariant describes a unique variant of a function that has pointer parameters that need |
| /// replacing. |
| struct FnVariant { |
| /// The signature of the variant. |
| VariantSignature signature; |
| /// The IR function for this variant. |
| Function* fn = nullptr; |
| /// The function information of the original function that this variant is based off. |
| FnInfo* info = nullptr; |
| }; |
| |
| /// PIMPL state for the transform. |
| struct State { |
| /// The IR module. |
| Module& ir; |
| |
| /// The transform options |
| const DirectVariableAccessOptions& options; |
| |
| /// The IR builder. |
| Builder b{ir}; |
| |
| /// The type manager. |
| core::type::Manager& ty{ir.Types()}; |
| |
| /// The symbol table. |
| SymbolTable& sym{ir.symbols}; |
| |
| /// The functions that have pointer parameters that need transforming. |
| /// These functions will be replaced with zero, one or many forked variants. |
| Hashmap<ir::Function*, FnInfo*, 8> need_forking{}; |
| |
| /// Queue of variants that need building |
| Vector<FnVariant, 8> variants_to_build{}; |
| |
| /// Allocator for FnInfo |
| BlockAllocator<FnInfo> fn_info_allocator{}; |
| |
| /// Process the module. |
| void Process() { |
| // Make a copy of all the functions in the IR module. |
| // Use transform to convert from ConstPropagatingPtr<Function> to Function* |
| auto input_fns = Transform<8>(ir.functions.Slice(), [](auto& fn) { return fn.Get(); }); |
| |
| // Populate #need_forking |
| GatherFnsThatNeedForking(); |
| |
| // Transform the functions that make calls to #need_forking, which aren't in #need_forking |
| // themselves. |
| BuildRootFns(); |
| |
| // Build variants of the functions in #need_forking. |
| BuildFnVariants(); |
| |
| // Rebuild ir.functions. |
| EmitFunctions(input_fns); |
| } |
| |
| /// Populates #need_forking with all the functions that have pointer parameters which need |
| /// transforming. These functions will be replaced with variants based on the access shapes. |
| void GatherFnsThatNeedForking() { |
| for (auto& fn : ir.functions) { |
| for (auto* param : fn->Params()) { |
| if (ParamNeedsTransforming(param)) { |
| need_forking.Add(fn, fn_info_allocator.Create()); |
| break; |
| } |
| } |
| } |
| } |
| |
| /// Adjusts the calls of all the functions that make calls to #need_forking, which aren't in |
| /// #need_forking themselves. This populates #variants_to_build with the called functions. |
| void BuildRootFns() { |
| for (auto& fn : ir.functions) { |
| if (!need_forking.Contains(fn)) { |
| TransformCalls(fn); |
| } |
| } |
| } |
| |
| /// Applies the necessary transformations to all the pointer arguments of calls made to |
| /// functions in #need_forking. Also populates #variants_to_build with the variants of the |
| /// callee functions. |
| /// @param fn the function to transform |
| void TransformCalls(Function* fn) { |
| // For all the function calls in the function... |
| Traverse(fn->Block(), [&](UserCall* call) { |
| auto* target = call->Target(); |
| auto target_info = need_forking.Get(target); |
| if (!target_info) { |
| // Not a call to a function in #need_forking. Leave alone. |
| return; |
| } |
| |
| // Found a call to a function in #need_forking. |
| // This call needs transforming to call the generated variant. |
| |
| // New arguments to the call. This includes transformed and untransformed arguments. |
| Vector<Value*, 8> new_args; |
| |
| // Pointer arguments that are being replaced. |
| Vector<Value*, 8> replaced_args; |
| |
| // Signature of the callee variant |
| VariantSignature signature; |
| |
| // For each argument / parameter... |
| for (size_t i = 0, n = call->Args().Length(); i < n; i++) { |
| auto* arg = call->Args()[i]; |
| auto* param = target->Params()[i]; |
| if (ParamNeedsTransforming(param)) { |
| // This argument needs replacing with: |
| // * Nothing: root is a module-scope var and the access chain has no indicies. |
| // * A single pointer argument to the root variable: The root is a pointer |
| // parameter or a function-scoped variable, and the access chain has no |
| // indicies. |
| // * A single indices array argument: The root is a module-scope var and the |
| // access chain has indices. |
| // * Both a pointer argument and indices array argument: The root is a pointer |
| // parameter or a function-scoped variable and the access chain has indices. |
| b.InsertBefore(call, [&] { |
| // Get the access chain for the pointer argument. |
| auto chain = AccessChainFor(arg); |
| // If the root is not a module-scope variable, then pass this root pointer |
| // as an argument. |
| if (std::holds_alternative<RootPtrParameter>(chain.shape.root)) { |
| new_args.Push(chain.root_ptr); |
| } |
| // If the chain access contains indices, then pass these as an array of u32. |
| if (size_t array_len = chain.indices.Length(); array_len > 0) { |
| auto* array = ty.array(ty.u32(), static_cast<uint32_t>(array_len)); |
| auto* indices = b.Construct(array, std::move(chain.indices)); |
| new_args.Push(indices->Result(0)); |
| } |
| // Record the parameter shape for the variant's signature. |
| signature.Add(i, chain.shape); |
| }); |
| // Record that this pointer argument has been replaced. |
| replaced_args.Push(arg); |
| } else { |
| // Argument does not need transformation. |
| // Push the existing argument to new_args. |
| new_args.Push(arg); |
| } |
| } |
| |
| // Replace the call's arguments with new_args. |
| call->SetArgs(std::move(new_args)); |
| |
| // Clean up instructions that provided the now unused argument values. |
| for (auto* old_arg : replaced_args) { |
| DeleteDeadInstructions(old_arg); |
| } |
| |
| // Look to see if this callee signature already has a variant created. |
| auto* new_target = (*target_info)->variants_by_sig.GetOrCreate(signature, [&] { |
| // New signature. |
| |
| // Clone the original function to seed the new variant. |
| auto* variant_fn = CloneContext{ir}.Clone(target); |
| (*target_info)->ordered_variants.Push(variant_fn); |
| |
| // Build a unique name for the variant. |
| if (auto fn_name = ir.NameOf(variant_fn); fn_name.IsValid()) { |
| StringStream variant_name; |
| variant_name << fn_name.NameView(); |
| auto params = signature.Keys().Sort(); |
| for (auto param_idx : params) { |
| variant_name << "_" << AccessShapeName(*signature.Get(param_idx)); |
| } |
| ir.SetName(variant_fn, variant_name.str()); |
| } |
| |
| // Create an entry for the variant, and add it to the queue of variants that need to |
| // be built. We don't do this here to avoid unbounded stack usage. |
| variants_to_build.Push(FnVariant{/* signature */ signature, |
| /* fn */ variant_fn, |
| /* info */ *target_info}); |
| return variant_fn; |
| }); |
| |
| // Re-point the target of the call to the variant. |
| call->SetTarget(new_target); |
| }); |
| } |
| |
| /// Builds all the variants in #variants_to_build by: |
| /// * Replacing the pointer parameters with zero, one or two parameters (root pointer, indices). |
| /// * Transforming any calls made by that variant to other functions found in #need_forking. |
| /// Note: The transformation of calls can add more variants to #variants_to_build. |
| /// BuildFnVariants() will continue to build variants until #variants_to_build is empty. |
| void BuildFnVariants() { |
| while (!variants_to_build.IsEmpty()) { |
| auto variant = variants_to_build.Pop(); |
| BuildFnVariantParams(variant); |
| TransformCalls(variant.fn); |
| } |
| } |
| |
| /// Walks the instructions that built #value to obtain the root variable and the pointer |
| /// accesses. |
| /// @param value the pointer value to get the access chain for |
| /// @return an AccessChain |
| AccessChain AccessChainFor(Value* value) { |
| AccessChain chain; |
| while (value) { |
| TINT_ASSERT(value->Alive()); |
| value = tint::Switch( |
| value, // |
| [&](InstructionResult* res) { |
| // value was emitted by an instruction |
| auto* inst = res->Instruction(); |
| return tint::Switch( |
| inst, |
| [&](Access* access) { |
| // The AccessOp of this access instruction |
| Vector<AccessOp, 8> ops; |
| // The ordered, non-member accesses performed by this access instruction |
| Vector<Value*, 8> indices; |
| // The pointee-type that each access is being performed on |
| auto* obj_ty = access->Object()->Type()->UnwrapPtr(); |
| |
| // For each access operation... |
| for (auto idx : access->Indices()) { |
| if (auto* str = obj_ty->As<type::Struct>()) { |
| // Struct type accesses must be constant, representing the index |
| // of the member being accessed. |
| TINT_ASSERT(idx->Is<Constant>()); |
| auto i = idx->As<Constant>()->Value()->ValueAs<uint32_t>(); |
| auto* member = str->Members()[i]; |
| ops.Push(MemberAccess{member}); |
| obj_ty = member->Type(); |
| continue; |
| } |
| |
| // Array or matrix access. |
| // Convert index to u32 if it isn't already. |
| if (!idx->Type()->Is<type::U32>()) { |
| idx = b.Convert(ty.u32(), idx)->Result(0); |
| } |
| |
| ops.Push(IndexAccess{}); |
| indices.Push(idx); |
| obj_ty = obj_ty->Elements().type; |
| } |
| |
| // Push the ops and indices in reverse order to the chain. This is done |
| // so we can continue to walk the IR values and push accesses (without |
| // insertion) that bring us closer to the pointer root. These are |
| // reversed again once the root variable is found. |
| for (auto& op : Reverse(ops)) { |
| chain.shape.ops.Push(op); |
| } |
| for (auto& idx : Reverse(indices)) { |
| chain.indices.Push(idx); |
| } |
| |
| TINT_ASSERT(obj_ty == access->Result(0)->Type()->UnwrapPtr()); |
| return access->Object(); |
| }, |
| [&](Var* var) { |
| // A 'var' is a pointer root. |
| if (var->Block() == ir.root_block) { |
| // Root pointer is a module-scope 'var' |
| chain.shape.root = RootModuleScopeVar{var}; |
| } else { |
| // Root pointer is a function-scope 'var' |
| chain.shape.root = |
| RootPtrParameter{var->Result(0)->Type()->As<type::Pointer>()}; |
| } |
| chain.root_ptr = var->Result(0); |
| return nullptr; |
| }, |
| [&](Let* let) { return let->Value(); }, // |
| TINT_ICE_ON_NO_MATCH); |
| }, |
| [&](FunctionParam* param) { |
| // Root pointer is a parameter of the caller |
| chain.shape.root = RootPtrParameter{param->Type()->As<type::Pointer>()}; |
| chain.root_ptr = param; |
| return nullptr; |
| }, // |
| TINT_ICE_ON_NO_MATCH); |
| } |
| |
| // Reverse the chain's ops and indices. See above for why. |
| chain.shape.ops.Reverse(); |
| chain.indices.Reverse(); |
| |
| return chain; |
| } |
| |
| /// Replaces the pointer parameters that need transforming of the variant function @p variant. |
| /// Instructions are inserted at the top of the @p variant function block to reconstruct the |
| /// pointer parameters from the access chain using the root pointer and access ops. |
| /// @param variant the variant function to transform |
| void BuildFnVariantParams(const FnVariant& variant) { |
| // Insert new instructions at the top of the function block... |
| b.InsertBefore(variant.fn->Block()->Front(), [&] { |
| // The replacement parameters for the variant function |
| Vector<ir::FunctionParam*, 8> new_params; |
| const auto& old_params = variant.fn->Params(); |
| // For each parameter in the original function... |
| for (size_t param_idx = 0; param_idx < old_params.Length(); param_idx++) { |
| auto* old_param = old_params[param_idx]; |
| if (!ParamNeedsTransforming(old_param)) { |
| // Parameter does not need transforming. |
| new_params.Push(old_param); |
| continue; |
| } |
| |
| // Pointer parameter that needs transforming |
| // Grab the access shape of the pointer parameter from the signature |
| auto shape = variant.signature.Get(param_idx); |
| // The root pointer value |
| Value* root_ptr = nullptr; |
| |
| // Build the root pointer parameter, if required. |
| FunctionParam* root_ptr_param = nullptr; |
| if (auto* ptr_param = std::get_if<RootPtrParameter>(&shape->root)) { |
| // Root pointer is passed as a parameter |
| root_ptr_param = b.FunctionParam(ptr_param->type); |
| new_params.Push(root_ptr_param); |
| root_ptr = root_ptr_param; |
| } else if (auto* global = std::get_if<RootModuleScopeVar>(&shape->root)) { |
| // Root pointer is a module-scope var |
| root_ptr = global->var->Result(0); |
| } else { |
| TINT_ICE() << "unhandled AccessShape root variant"; |
| } |
| |
| // Build the access indices parameter, if required. |
| ir::FunctionParam* indices_param = nullptr; |
| if (uint32_t n = shape->NumIndexAccesses(); n > 0) { |
| // Indices are passed as an array of u32 |
| indices_param = b.FunctionParam(ty.array(ty.u32(), n)); |
| new_params.Push(indices_param); |
| } |
| |
| // Generate names for the new parameter(s) based on the replaced parameter name. |
| if (auto param_name = ir.NameOf(old_param); param_name.IsValid()) { |
| // Propagate old parameter name to the new parameters |
| if (root_ptr_param) { |
| ir.SetName(root_ptr_param, param_name.Name() + "_root"); |
| } |
| if (indices_param) { |
| ir.SetName(indices_param, param_name.Name() + "_indices"); |
| } |
| } |
| |
| // Rebuild the pointer from the root pointer and accesses. |
| uint32_t index_index = 0; |
| auto chain = Transform(shape->ops, [&](const AccessOp& op) -> Value* { |
| if (auto* m = std::get_if<MemberAccess>(&op)) { |
| return b.Constant(u32(m->member->Index())); |
| } |
| auto* access = b.Access(ty.u32(), indices_param, u32(index_index++)); |
| return access->Result(0); |
| }); |
| auto* access = b.Access(old_param->Type(), root_ptr, std::move(chain)); |
| |
| // Replace the now removed parameter value with the access instruction |
| old_param->ReplaceAllUsesWith(access->Result(0)); |
| old_param->Destroy(); |
| } |
| |
| // Replace the function's parameters |
| variant.fn->SetParams(std::move(new_params)); |
| }); |
| } |
| |
| /// Repopulates #ir.functions with the functions in #need_forking replaced with their generated |
| /// variants. |
| /// @param input_fns the content of #ir.functions before transformation began. |
| void EmitFunctions(VectorRef<Function*> input_fns) { |
| ir.functions.Clear(); |
| for (auto& fn : input_fns) { |
| if (auto info = need_forking.Get(fn)) { |
| fn->Destroy(); |
| for (auto variant : (*info)->ordered_variants) { |
| ir.functions.Push(variant); |
| } |
| } else { |
| ir.functions.Push(fn); |
| } |
| } |
| } |
| |
| /// @returns a string describing the given AccessShape, used to suffix the generated function |
| /// variants. |
| std::string AccessShapeName(const AccessShape& shape) { |
| StringStream ss; |
| |
| if (auto* global = std::get_if<RootModuleScopeVar>(&shape.root)) { |
| ss << ir.NameOf(global->var).NameView(); |
| } else { |
| ss << "P"; |
| } |
| |
| for (auto& op : shape.ops) { |
| ss << "_"; |
| |
| if (std::holds_alternative<IndexAccess>(op)) { |
| /// The op uses an index taken from an array parameter. |
| ss << "X"; |
| continue; |
| } |
| |
| if (auto* access = std::get_if<MemberAccess>(&op); TINT_LIKELY(access)) { |
| ss << access->member->Name().NameView(); |
| continue; |
| } |
| |
| TINT_ICE() << "unhandled variant for access chain"; |
| break; |
| } |
| return ss.str(); |
| } |
| |
| /// @return true if @p param is a pointer parameter that requires transforming, based on the |
| /// address space and transform options. |
| /// @param param the function parameter |
| bool ParamNeedsTransforming(FunctionParam* param) const { |
| if (auto* ptr = param->Type()->As<type::Pointer>()) { |
| switch (ptr->AddressSpace()) { |
| case core::AddressSpace::kStorage: |
| case core::AddressSpace::kUniform: |
| case core::AddressSpace::kWorkgroup: |
| return true; |
| case core::AddressSpace::kFunction: |
| return options.transform_function; |
| case core::AddressSpace::kPrivate: |
| return options.transform_private; |
| default: |
| break; |
| } |
| } |
| return false; |
| } |
| |
| /// Walks the instructions that built @p value, deleting those that are no longer used. |
| /// @param value the pointer value that was used as a now replaced pointer argument. |
| void DeleteDeadInstructions(ir::Value* value) { |
| // While value has no uses... |
| while (value && value->Usages().Count() == 0) { |
| auto* inst_res = value->As<InstructionResult>(); |
| if (!inst_res) { |
| return; // Only instructions can be removed. |
| } |
| value = tint::Switch( |
| inst_res->Instruction(), // |
| [&](Access* access) { |
| TINT_DEFER(access->Destroy()); |
| return access->Object(); |
| }, |
| [&](Let* let) { |
| TINT_DEFER(let->Destroy()); |
| return let->Value(); |
| }); |
| } |
| } |
| }; |
| |
| } // namespace |
| |
| Result<SuccessType> DirectVariableAccess(Module& ir, const DirectVariableAccessOptions& options) { |
| auto result = ValidateAndDumpIfNeeded(ir, "DirectVariableAccess transform"); |
| if (result != Success) { |
| return result; |
| } |
| |
| State{ir, options}.Process(); |
| |
| return Success; |
| } |
| |
| } // namespace tint::core::ir::transform |