/src/solidity/libyul/optimiser/CallGraphGenerator.cpp
Line | Count | Source |
1 | | /* |
2 | | This file is part of solidity. |
3 | | |
4 | | solidity is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation, either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | solidity is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with solidity. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | // SPDX-License-Identifier: GPL-3.0 |
18 | | /** |
19 | | * Specific AST walker that generates the call graph. |
20 | | */ |
21 | | |
22 | | #include <libyul/optimiser/CallGraphGenerator.h> |
23 | | |
24 | | #include <libyul/AST.h> |
25 | | #include <libyul/Exceptions.h> |
26 | | |
27 | | #include <libsolutil/CommonData.h> |
28 | | #include <libsolutil/TarjanSCC.h> |
29 | | #include <libsolutil/Visitor.h> |
30 | | |
31 | | #include <range/v3/algorithm/binary_search.hpp> |
32 | | #include <range/v3/algorithm/sort.hpp> |
33 | | #include <range/v3/algorithm/unique.hpp> |
34 | | |
35 | | #include <cstddef> |
36 | | |
37 | | using namespace solidity; |
38 | | using namespace solidity::yul; |
39 | | using namespace solidity::util; |
40 | | |
41 | | namespace |
42 | | { |
43 | | |
44 | | class FunctionToIndexBiMapping |
45 | | { |
46 | | public: |
47 | | explicit FunctionToIndexBiMapping(std::map<FunctionHandle, std::vector<FunctionHandle>> const& _functionCalls) |
48 | 3.68M | { |
49 | 3.68M | for (auto const& [function, callees]: _functionCalls) |
50 | 11.5M | { |
51 | 11.5M | if (m_functionToIndex.try_emplace(function, m_indexToFunction.size()).second) |
52 | 9.73M | m_indexToFunction.emplace_back(function); |
53 | 11.5M | for (auto const& callee: callees) |
54 | 47.4M | if (m_functionToIndex.try_emplace(callee, m_indexToFunction.size()).second) |
55 | 19.1M | m_indexToFunction.emplace_back(callee); |
56 | 11.5M | } |
57 | 3.68M | } |
58 | | |
59 | | FunctionHandle const& indexToFunction(std::size_t const _index) const |
60 | 28.9M | { |
61 | 28.9M | return m_indexToFunction.at(_index); |
62 | 28.9M | } |
63 | | |
64 | | std::size_t numFunctions() const |
65 | 3.68M | { |
66 | 3.68M | return m_indexToFunction.size(); |
67 | 3.68M | } |
68 | | |
69 | | std::size_t functionToIndex(FunctionHandle const& _functionHandle) const |
70 | 94.8M | { |
71 | 94.8M | return m_functionToIndex.at(_functionHandle); |
72 | 94.8M | } |
73 | | |
74 | | private: |
75 | | std::vector<FunctionHandle> m_indexToFunction; |
76 | | std::map<FunctionHandle, std::size_t> m_functionToIndex; |
77 | | }; |
78 | | |
79 | | } |
80 | | |
81 | | CallGraphCycles CallGraph::analyzeCallCycles() const |
82 | 3.68M | { |
83 | | // A function is recursive iff it is part of a non-trivial strongly-connected component of the call graph (a |
84 | | // mutual-recursion cycle of any length) or it directly calls itself. The SCCs are computed with Tarjan's algorithm. |
85 | | // Tarjan's implementation requires dense node indices in [0, N), so assign each function handle |
86 | | // (both callers and callees) a consecutive index. |
87 | 3.68M | FunctionToIndexBiMapping const functionIndexBimap(functionCalls); |
88 | | |
89 | | // Build list of edges in the call graph |
90 | 3.68M | std::vector<std::vector<std::size_t>> indexBasedAdjacencyList(functionIndexBimap.numFunctions()); |
91 | 3.68M | for (auto const& [function, callees]: functionCalls) |
92 | 11.5M | for (auto const& callee: callees) |
93 | 47.4M | indexBasedAdjacencyList[functionIndexBimap.functionToIndex(function)].emplace_back(functionIndexBimap.functionToIndex(callee)); |
94 | | |
95 | | // Sort and deduplicate each adjacency list so the self-loop check below can use a binary search |
96 | 3.68M | for (auto& callees: indexBasedAdjacencyList) |
97 | 28.9M | { |
98 | 28.9M | ranges::sort(callees); |
99 | 28.9M | callees.erase(ranges::unique(callees), callees.end()); |
100 | 28.9M | } |
101 | | |
102 | 3.68M | std::vector<std::vector<FunctionHandle>> components; |
103 | 3.68M | std::set<FunctionHandle> recursiveFunctionHandleSet; |
104 | 3.68M | for (std::vector<std::size_t> const& scc: util::computeStronglyConnectedComponents<std::size_t>(indexBasedAdjacencyList)) |
105 | 28.7M | { |
106 | 28.7M | yulAssert(!scc.empty()); |
107 | 28.7M | std::vector<FunctionHandle>& component = components.emplace_back(); |
108 | 28.7M | for (std::size_t const node: scc) |
109 | 28.9M | component.emplace_back(functionIndexBimap.indexToFunction(node)); |
110 | 28.7M | if (component.size() > 1) |
111 | | // more than one element in the SCC: everything in it is mutually recursive |
112 | 35.8k | recursiveFunctionHandleSet.insert(component.begin(), component.end()); |
113 | 28.7M | else if (ranges::binary_search(indexBasedAdjacencyList[scc.front()], scc.front())) |
114 | | // self-recursion f -> f |
115 | 145k | recursiveFunctionHandleSet.insert(component.front()); |
116 | 28.7M | } |
117 | | |
118 | 3.68M | yulAssert(!recursiveFunctionHandleSet.contains(YulName{}), "the top-level block cannot be recursive"); |
119 | 3.68M | for (FunctionHandle const& recursiveFunction: recursiveFunctionHandleSet) |
120 | 3.68M | yulAssert(std::holds_alternative<YulName>(recursiveFunction), "a builtin cannot be recursive"); |
121 | | |
122 | 3.68M | return { |
123 | 3.68M | .stronglyConnectedComponents = std::move(components), |
124 | 3.68M | .recursiveFunctions = std::move(recursiveFunctionHandleSet) |
125 | 3.68M | }; |
126 | 3.68M | } |
127 | | |
128 | | CallGraph CallGraphGenerator::callGraph(Block const& _ast) |
129 | 5.24M | { |
130 | 5.24M | CallGraphGenerator gen; |
131 | 5.24M | gen(_ast); |
132 | 5.24M | return std::move(gen.m_callGraph); |
133 | 5.24M | } |
134 | | |
135 | | void CallGraphGenerator::operator()(FunctionCall const& _functionCall) |
136 | 159M | { |
137 | 159M | auto& functionCalls = m_callGraph.functionCalls[m_currentFunction]; |
138 | 159M | FunctionHandle identifier = std::visit(GenericVisitor{ |
139 | 159M | [](BuiltinName const& _builtin) -> FunctionHandle { return _builtin.handle; }, |
140 | 159M | [](Identifier const& _identifier) -> FunctionHandle { return _identifier.name; }, |
141 | 159M | }, _functionCall.functionName); |
142 | 159M | if (!util::contains(functionCalls, identifier)) |
143 | 64.1M | functionCalls.emplace_back(identifier); |
144 | 159M | ASTWalker::operator()(_functionCall); |
145 | 159M | } |
146 | | |
147 | | void CallGraphGenerator::operator()(ForLoop const& _forLoop) |
148 | 9.09M | { |
149 | 9.09M | m_callGraph.functionsWithLoops.insert(m_currentFunction); |
150 | 9.09M | ASTWalker::operator()(_forLoop); |
151 | 9.09M | } |
152 | | |
153 | | void CallGraphGenerator::operator()(FunctionDefinition const& _functionDefinition) |
154 | 10.5M | { |
155 | 10.5M | solRequire( |
156 | 10.5M | !m_callGraph.functionCalls.contains(_functionDefinition.name), |
157 | 10.5M | InputNotDisambiguatedException, |
158 | 10.5M | "CallGraphGenerator requires a disambiguated AST: duplicate function name " + _functionDefinition.name.str() + "." |
159 | 10.5M | ); |
160 | 10.5M | YulName previousFunction = m_currentFunction; |
161 | 10.5M | m_currentFunction = _functionDefinition.name; |
162 | 10.5M | m_callGraph.functionCalls[m_currentFunction] = {}; |
163 | 10.5M | ASTWalker::operator()(_functionDefinition); |
164 | 10.5M | m_currentFunction = previousFunction; |
165 | 10.5M | } |
166 | | |
167 | | CallGraphGenerator::CallGraphGenerator() |
168 | 5.24M | { |
169 | 5.24M | m_callGraph.functionCalls[YulName{}] = {}; |
170 | 5.24M | } |