| // 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_FUNCTIONS_H_ |
| #define SRC_TINT_LANG_CORE_IR_REFERENCED_FUNCTIONS_H_ |
| |
| #include "src/tint/lang/core/ir/control_instruction.h" |
| #include "src/tint/lang/core/ir/module.h" |
| #include "src/tint/lang/core/ir/return.h" |
| #include "src/tint/lang/core/ir/user_call.h" |
| #include "src/tint/utils/containers/hashmap.h" |
| #include "src/tint/utils/rtti/switch.h" |
| |
| /// 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 { |
| |
| /// ReferencedFunctions is a helper to determine the set of functions 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 ReferencedFunctions { |
| // 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. |
| using BlockT = TranscribeConst<M, Block>; |
| using FunctionT = TranscribeConst<M, Function>; |
| |
| /// A set of a functions referenced by a function. |
| using FunctionSet = Hashset<FunctionT*, 16>; |
| |
| /// Constructor. |
| /// @param ir the module |
| explicit ReferencedFunctions(M& ir) { |
| // Loop over functions, recording the blocks that they are called from. |
| for (auto func : ir.functions) { |
| if (!func) { |
| continue; |
| } |
| func->ForEachUseUnsorted([&](const Usage& use) { |
| if (auto* call = use.instruction->As<UserCall>()) { |
| block_to_direct_calls_.GetOrAddZero(call->Block()).Add(func); |
| } else { |
| TINT_ASSERT(use.instruction->Is<Return>()); |
| } |
| }); |
| } |
| } |
| |
| /// Get the set of transitively referenced functions for a function. |
| /// @param func the function |
| /// @returns the set of transitively reference functions |
| FunctionSet& TransitiveReferences(FunctionT* func) { |
| return transitive_references_.GetOrAdd(func, [&] { |
| FunctionSet functions; |
| if (!func) { |
| return functions; |
| } |
| |
| // Walk blocks in the function to find function calls. |
| Vector<BlockT*, 64> block_queue{func->Block()}; |
| while (!block_queue.IsEmpty()) { |
| auto* next = block_queue.Pop(); |
| if (!next) { |
| continue; |
| } |
| |
| // Add directly referenced functions. |
| if (auto itr = block_to_direct_calls_.Get(next)) { |
| for (auto& callee : *itr) { |
| if (functions.Add(callee)) { |
| // Add functions transitively referenced by the callee. |
| const auto& callee_functions = TransitiveReferences(callee); |
| for (auto& transitive_func : callee_functions) { |
| functions.Add(transitive_func); |
| } |
| } |
| } |
| } |
| |
| // Loop over instructions in the block to find nested blocks. |
| for (auto* inst : *next) { |
| if (auto* ctrl = inst->template As<ControlInstruction>()) { |
| // Add nested blocks to the queue. |
| ctrl->ForeachBlock([&](BlockT* blk) { block_queue.Push(blk); }); |
| } |
| } |
| } |
| |
| return functions; |
| }); |
| } |
| |
| private: |
| /// A map from blocks to their directly called functions. |
| Hashmap<BlockT*, FunctionSet, 64> block_to_direct_calls_{}; |
| |
| /// A map from functions to their transitively referenced functions. |
| Hashmap<FunctionT*, FunctionSet, 8> transitive_references_; |
| }; |
| |
| } // namespace tint::core::ir |
| |
| #endif // SRC_TINT_LANG_CORE_IR_REFERENCED_FUNCTIONS_H_ |