| // Copyright 2024 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. |
| |
| #ifndef SRC_TINT_LANG_CORE_IR_REFERENCED_MODULE_DECLS_H_ |
| #define SRC_TINT_LANG_CORE_IR_REFERENCED_MODULE_DECLS_H_ |
| |
| #include "src/tint/lang/core/ir/constexpr_if.h" |
| #include "src/tint/lang/core/ir/control_instruction.h" |
| #include "src/tint/lang/core/ir/instruction.h" |
| #include "src/tint/lang/core/ir/let.h" |
| #include "src/tint/lang/core/ir/module.h" |
| #include "src/tint/lang/core/ir/override.h" |
| #include "src/tint/lang/core/ir/type/array_count.h" |
| #include "src/tint/lang/core/ir/user_call.h" |
| #include "src/tint/lang/core/ir/var.h" |
| #include "src/tint/lang/core/type/array.h" |
| #include "src/tint/lang/core/type/pointer.h" |
| #include "src/tint/utils/containers/hashmap.h" |
| #include "src/tint/utils/containers/unique_vector.h" |
| #include "src/tint/utils/rtti/switch.h" |
| |
| // Forward declarations. |
| namespace tint::core::ir { |
| class Block; |
| class Function; |
| } // namespace tint::core::ir |
| |
| /// Utility that helps guarantee the same const-ness is applied to both types. |
| template <class Src, class Dst> |
| using TranscribeConst = std::conditional_t<std::is_const<Src>{}, std::add_const_t<Dst>, Dst>; |
| |
| namespace tint::core::ir { |
| |
| /// ReferencedModuleDecls is a helper to determine the set of module-scope declarations that are |
| /// transitively referenced by functions in a module. |
| /// |
| /// References are determined lazily and cached for future requests. |
| /// |
| /// Note: |
| /// The template param M is used to ensure that inputs and outputs of this class have the same |
| /// const-ness. If 'Module' is supplied then the internal operations and output will not be |
| /// const, which is needed for transforms. Whereas if the param is 'const Module' the internals |
| /// and outputs will be const, which is needed for the IR validator. |
| /// Note: |
| /// Changes to the module can invalidate the cached data. This is intended to be created by |
| /// operations that need this information, and discarded when they complete. Tracking this |
| /// information inside the IR module would add overhead any time an instruction is added or |
| /// removed from the module. Since only a few operations need this information, it is expected |
| /// to be more efficient to generate it on demand. |
| template <typename M> |
| class ReferencedModuleDecls { |
| // Replace this with concepts when C++20 is available |
| static_assert(std::is_same<std::remove_cv_t<M>, Module>()); |
| |
| public: |
| /// Short form aliases for types that have the same constant-ness as M. |
| /// (The single use types are not aliased) |
| using BlockT = TranscribeConst<M, Block>; |
| using DeclT = TranscribeConst<M, Instruction>; |
| using FunctionT = TranscribeConst<M, Function>; |
| |
| /// A set of a declarations referenced by a function (in declaration order). |
| using DeclSet = UniqueVector<DeclT*, 16>; |
| |
| /// Constructor. |
| /// @param ir the module |
| explicit ReferencedModuleDecls(M& ir) { |
| // Loop over module-scope declarations, recording the blocks that they are referenced from. |
| BlockT* root_block = ir.root_block; |
| for (auto* inst : *root_block) { |
| if (!inst || !inst->Result(0)) { |
| continue; |
| } |
| |
| inst->Result()->ForEachUseUnsorted([&](const Usage& use) { |
| if (use.instruction->Block() != root_block) { |
| AddToBlock(block_to_direct_decls_.GetOrAddZero(use.instruction->Block()), inst); |
| } |
| }); |
| |
| // If the instruction is a `var<workgroup>` we have to check the type. If the type is an |
| // array with a `ValueArrayCount` then we need to check the count. If it's not |
| // `Constant` we need to add the instruction and any referenced instructions to the used |
| // set. |
| auto* var = inst->template As<core::ir::Var>(); |
| if (!var) { |
| continue; |
| } |
| auto* ptr = var->Result()->Type()->template As<core::type::Pointer>(); |
| TINT_ASSERT(ptr); |
| |
| if (ptr->AddressSpace() != core::AddressSpace::kWorkgroup) { |
| continue; |
| } |
| auto* ary = ptr->UnwrapPtr()->template As<core::type::Array>(); |
| if (!ary) { |
| continue; |
| } |
| auto* cnt = ary->Count()->template As<core::ir::type::ValueArrayCount>(); |
| if (!cnt || cnt->value->template Is<core::ir::Constant>()) { |
| continue; |
| } |
| |
| auto* cnt_inst = cnt->value->template As<core::ir::InstructionResult>(); |
| TINT_ASSERT(cnt_inst); |
| |
| // The usage of the var is as a `let` initializer. The array count needs to |
| // propagate to the `let` block. |
| var->Result()->ForEachUseUnsorted([&](const Usage& use) { |
| AddToBlock(block_to_direct_decls_.GetOrAddZero(use.instruction->Block()), |
| cnt_inst->Instruction()); |
| }); |
| } |
| } |
| |
| /// Get the set of transitively referenced module-scope declarations for a function. |
| /// @param func the function |
| /// @returns the set of transitively reference module-scope declarations |
| DeclSet& TransitiveReferences(FunctionT* func) { |
| return transitive_references_.GetOrAdd(func, [&] { |
| DeclSet decls; |
| GetTransitiveReferences(func ? func->Block() : nullptr, decls); |
| |
| // For a compute entry point, we need to check if any of the workgroup sizes are built |
| // on overrides. |
| if (func && func->Stage() == core::ir::Function::PipelineStage::kCompute) { |
| TINT_ASSERT(func->WorkgroupSize().has_value()); |
| |
| const auto workgroup_size = func->WorkgroupSize(); |
| for (auto wg_size : *workgroup_size) { |
| if (wg_size->template Is<core::ir::Constant>()) { |
| continue; |
| } |
| |
| // Workgroup size is based on instructions, walk up the chain adding those |
| // instructions to the `decls` list. |
| auto* inst = wg_size->template As<core::ir::InstructionResult>(); |
| TINT_ASSERT(inst); |
| |
| AddToBlock(decls, inst->Instruction()); |
| } |
| } |
| return decls; |
| }); |
| } |
| |
| void AddToBlock(DeclSet& decls, core::ir::Instruction* inst) { |
| Vector<DeclT*, 32> worklist; |
| worklist.Push(inst); |
| |
| while (!worklist.IsEmpty()) { |
| auto* wl_inst = worklist.Pop(); |
| if (decls.Add(wl_inst)) { |
| for (auto* operand : wl_inst->Operands()) { |
| if (!operand) { |
| continue; |
| } |
| auto* res = operand->template As<core::ir::InstructionResult>(); |
| if (!res) { |
| continue; |
| } |
| worklist.Push(res->Instruction()); |
| } |
| |
| // ConstExprIf instruction is special because it use to be a short circuiting binary |
| // operation that was converted to a specialized if instruction. |
| if (auto* const_expr_if = wl_inst->template As<ConstExprIf>()) { |
| auto add_block_instructions = [&worklist](Block* block) { |
| if (!block) { |
| return; |
| } |
| for (auto* block_inst : *block) { |
| worklist.Push(block_inst); |
| } |
| }; |
| |
| add_block_instructions(const_expr_if->True()); |
| add_block_instructions(const_expr_if->False()); |
| } |
| } |
| } |
| } |
| |
| private: |
| /// A map from blocks to their directly referenced declarations. |
| Hashmap<BlockT*, DeclSet, 64> block_to_direct_decls_{}; |
| |
| /// A map from functions to their transitively referenced declarations. |
| Hashmap<FunctionT*, DeclSet, 8> transitive_references_; |
| |
| /// Get the set of transitively referenced module-scope declarations for a block. |
| /// @param block the block |
| /// @param decls the set of transitively reference module-scope declarations to populate |
| void GetTransitiveReferences(BlockT* block, DeclSet& decls) { |
| if (!block) { |
| return; |
| } |
| |
| // Add directly referenced declarations. |
| if (auto itr = block_to_direct_decls_.Get(block)) { |
| for (auto& decl : *itr) { |
| decls.Add(decl); |
| } |
| } |
| |
| // Loop over instructions in the block to find indirectly referenced vars. |
| for (auto* inst : *block) { |
| tint::Switch( |
| inst, |
| [&](TranscribeConst<M, UserCall>* call) { |
| // Get declarations referenced by a function called from this block. |
| const auto& callee_decls = TransitiveReferences(call->Target()); |
| for (auto* decl : callee_decls) { |
| decls.Add(decl); |
| } |
| }, |
| [&](TranscribeConst<M, ControlInstruction>* ctrl) { |
| // Recurse into control instructions and gather their referenced declarations. |
| ctrl->ForeachBlock([&](BlockT* blk) { GetTransitiveReferences(blk, decls); }); |
| }); |
| } |
| } |
| }; |
| |
| } // namespace tint::core::ir |
| |
| #endif // SRC_TINT_LANG_CORE_IR_REFERENCED_MODULE_DECLS_H_ |