/src/solidity/libyul/backends/evm/ControlFlowGraphBuilder.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 | | * Transformation of a Yul AST into a control flow graph. |
20 | | */ |
21 | | |
22 | | #include <libyul/backends/evm/ControlFlowGraphBuilder.h> |
23 | | #include <libyul/AST.h> |
24 | | #include <libyul/Exceptions.h> |
25 | | #include <libyul/Utilities.h> |
26 | | #include <libyul/ControlFlowSideEffectsCollector.h> |
27 | | #include <libyul/backends/evm/EVMDialect.h> |
28 | | |
29 | | #include <libsolutil/Visitor.h> |
30 | | #include <libsolutil/Algorithms.h> |
31 | | |
32 | | #include <range/v3/action/push_back.hpp> |
33 | | #include <range/v3/action/erase.hpp> |
34 | | #include <range/v3/range/conversion.hpp> |
35 | | #include <range/v3/view/concat.hpp> |
36 | | #include <range/v3/view/drop_last.hpp> |
37 | | #include <range/v3/view/enumerate.hpp> |
38 | | #include <range/v3/view/filter.hpp> |
39 | | #include <range/v3/view/iota.hpp> |
40 | | #include <range/v3/view/map.hpp> |
41 | | #include <range/v3/view/reverse.hpp> |
42 | | #include <range/v3/view/single.hpp> |
43 | | #include <range/v3/view/take_last.hpp> |
44 | | #include <range/v3/view/transform.hpp> |
45 | | |
46 | | using namespace solidity; |
47 | | using namespace solidity::yul; |
48 | | |
49 | | namespace |
50 | | { |
51 | | /// Removes edges to blocks that are not reachable. |
52 | | void cleanUnreachable(CFG& _cfg) |
53 | 214k | { |
54 | | // If operation is a function call it adds the callee entry as child |
55 | 214k | auto const addFunctionsEntries = [&_cfg](CFG::BasicBlock* _node, auto&& _addChild) |
56 | 3.34M | { |
57 | 3.34M | for (auto const& operation: _node->operations) |
58 | 10.2M | { |
59 | 10.2M | if (auto const* functionCall = std::get_if<CFG::FunctionCall>(&operation.operation)) |
60 | 1.00M | { |
61 | 1.00M | auto const functionInfo = _cfg.functionInfo.at(&(functionCall->function.get())); |
62 | 1.00M | _addChild(functionInfo.entry); |
63 | 1.00M | } |
64 | 10.2M | } |
65 | 3.34M | }; |
66 | | |
67 | | // Determine which blocks are reachable from the entry. |
68 | 214k | util::BreadthFirstSearch<CFG::BasicBlock*> reachabilityCheck{{_cfg.entry}}; |
69 | 3.34M | reachabilityCheck.run([&](CFG::BasicBlock* _node, auto&& _addChild) { |
70 | 3.34M | addFunctionsEntries(_node, _addChild); |
71 | 3.34M | visit(util::GenericVisitor{ |
72 | 3.34M | [&](CFG::BasicBlock::Jump const& _jump) { |
73 | 1.58M | _addChild(_jump.target); |
74 | 1.58M | }, |
75 | 3.34M | [&](CFG::BasicBlock::ConditionalJump const& _jump) { |
76 | 864k | _addChild(_jump.zero); |
77 | 864k | _addChild(_jump.nonZero); |
78 | 864k | }, |
79 | 3.34M | [](CFG::BasicBlock::FunctionReturn const&) {}, |
80 | 3.34M | [](CFG::BasicBlock::Terminated const&) {}, |
81 | 3.34M | [](CFG::BasicBlock::MainExit const&) {} |
82 | 3.34M | }, _node->exit); |
83 | 3.34M | }); |
84 | | |
85 | | // Remove all entries from unreachable nodes from the graph. |
86 | 214k | for (CFG::BasicBlock* node: reachabilityCheck.visited) |
87 | 3.59M | std::erase_if(node->entries, [&](CFG::BasicBlock* entry) -> bool { |
88 | 3.59M | return !reachabilityCheck.visited.count(entry); |
89 | 3.59M | }); |
90 | | |
91 | | // Remove functions which are never referenced. |
92 | 542k | _cfg.functions.erase(std::remove_if(_cfg.functions.begin(), _cfg.functions.end(), [&](auto const& item) { |
93 | 542k | return !reachabilityCheck.visited.count(_cfg.functionInfo.at(item).entry); |
94 | 542k | }), _cfg.functions.end()); |
95 | | |
96 | | // Remove functionInfos which are never referenced. |
97 | 542k | std::erase_if(_cfg.functionInfo, [&](auto const& entry) -> bool { |
98 | 542k | return !reachabilityCheck.visited.count(entry.second.entry); |
99 | 542k | }); |
100 | 214k | } |
101 | | |
102 | | /// Sets the ``recursive`` member to ``true`` for all recursive function calls. |
103 | | void markRecursiveCalls(CFG& _cfg) |
104 | 214k | { |
105 | 214k | std::map<CFG::BasicBlock*, std::vector<CFG::FunctionCall*>> callsPerBlock; |
106 | 214k | auto const& findCalls = [&](CFG::BasicBlock* _block) |
107 | 2.44M | { |
108 | 2.44M | if (auto* calls = util::valueOrNullptr(callsPerBlock, _block)) |
109 | 1.93M | return *calls; |
110 | 504k | std::vector<CFG::FunctionCall*>& calls = callsPerBlock[_block]; |
111 | 1.85M | util::BreadthFirstSearch<CFG::BasicBlock*>{{_block}}.run([&](CFG::BasicBlock* _block, auto _addChild) { |
112 | 1.85M | for (auto& operation: _block->operations) |
113 | 4.80M | if (auto* functionCall = std::get_if<CFG::FunctionCall>(&operation.operation)) |
114 | 580k | calls.emplace_back(functionCall); |
115 | 1.85M | std::visit(util::GenericVisitor{ |
116 | 1.85M | [&](CFG::BasicBlock::MainExit const&) {}, |
117 | 1.85M | [&](CFG::BasicBlock::Jump const& _jump) |
118 | 1.85M | { |
119 | 823k | _addChild(_jump.target); |
120 | 823k | }, |
121 | 1.85M | [&](CFG::BasicBlock::ConditionalJump const& _conditionalJump) |
122 | 1.85M | { |
123 | 419k | _addChild(_conditionalJump.zero); |
124 | 419k | _addChild(_conditionalJump.nonZero); |
125 | 419k | }, |
126 | 1.85M | [&](CFG::BasicBlock::FunctionReturn const&) {}, |
127 | 1.85M | [&](CFG::BasicBlock::Terminated const&) {}, |
128 | 1.85M | }, _block->exit); |
129 | 1.85M | }); |
130 | 504k | return calls; |
131 | 2.44M | }; |
132 | 214k | for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) |
133 | 504k | for (CFG::FunctionCall* call: findCalls(functionInfo.entry)) |
134 | 580k | { |
135 | 580k | util::BreadthFirstSearch<CFG::FunctionCall*> breadthFirstSearch{{call}}; |
136 | 1.97M | breadthFirstSearch.run([&](CFG::FunctionCall* _call, auto _addChild) { |
137 | 1.97M | auto& calledFunctionInfo = _cfg.functionInfo.at(&_call->function.get()); |
138 | 1.97M | if (&calledFunctionInfo == &functionInfo) |
139 | 33.7k | { |
140 | 33.7k | call->recursive = true; |
141 | 33.7k | breadthFirstSearch.abort(); |
142 | 33.7k | return; |
143 | 33.7k | } |
144 | 1.93M | for (CFG::FunctionCall* nestedCall: findCalls(_cfg.functionInfo.at(&_call->function.get()).entry)) |
145 | 3.67M | _addChild(nestedCall); |
146 | 1.93M | }); |
147 | 580k | } |
148 | 214k | } |
149 | | |
150 | | /// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected sub-graph of the CFG. |
151 | | /// Entering such a block means that control flow will never return to a previously visited block. |
152 | | void markStartsOfSubGraphs(CFG& _cfg) |
153 | 214k | { |
154 | 214k | std::vector<CFG::BasicBlock*> entries; |
155 | 214k | entries.emplace_back(_cfg.entry); |
156 | 214k | for (auto&& functionInfo: _cfg.functionInfo | ranges::views::values) |
157 | 504k | entries.emplace_back(functionInfo.entry); |
158 | 214k | for (auto& entry: entries) |
159 | 718k | { |
160 | | /** |
161 | | * Detect bridges following Algorithm 1 in https://arxiv.org/pdf/2108.07346.pdf |
162 | | * and mark the bridge targets as starts of sub-graphs. |
163 | | */ |
164 | 718k | std::set<CFG::BasicBlock*> visited; |
165 | 718k | std::map<CFG::BasicBlock*, size_t> disc; |
166 | 718k | std::map<CFG::BasicBlock*, size_t> low; |
167 | 718k | std::map<CFG::BasicBlock*, CFG::BasicBlock*> parent; |
168 | 718k | size_t time = 0; |
169 | 3.34M | auto dfs = [&](CFG::BasicBlock* _u, auto _recurse) -> void { |
170 | 3.34M | visited.insert(_u); |
171 | 3.34M | disc[_u] = low[_u] = time; |
172 | 3.34M | time++; |
173 | | |
174 | 3.34M | std::vector<CFG::BasicBlock*> children = _u->entries; |
175 | 3.34M | visit(util::GenericVisitor{ |
176 | 3.34M | [&](CFG::BasicBlock::Jump const& _jump) { |
177 | 1.58M | children.emplace_back(_jump.target); |
178 | 1.58M | }, |
179 | 3.34M | [&](CFG::BasicBlock::ConditionalJump const& _jump) { |
180 | 864k | children.emplace_back(_jump.zero); |
181 | 864k | children.emplace_back(_jump.nonZero); |
182 | 864k | }, |
183 | 3.34M | [&](CFG::BasicBlock::FunctionReturn const&) {}, |
184 | 3.34M | [&](CFG::BasicBlock::Terminated const&) { _u->isStartOfSubGraph = true; }, |
185 | 3.34M | [&](CFG::BasicBlock::MainExit const&) { _u->isStartOfSubGraph = true; } |
186 | 3.34M | }, _u->exit); |
187 | 3.34M | yulAssert(!util::contains(children, _u)); |
188 | | |
189 | 3.34M | for (CFG::BasicBlock* v: children) |
190 | 6.63M | if (!visited.count(v)) |
191 | 2.62M | { |
192 | 2.62M | parent[v] = _u; |
193 | 2.62M | _recurse(v, _recurse); |
194 | 2.62M | low[_u] = std::min(low[_u], low[v]); |
195 | 2.62M | if (low[v] > disc[_u]) |
196 | 1.04M | { |
197 | | // _u <-> v is a cut edge in the undirected graph |
198 | 1.04M | bool edgeVtoU = util::contains(_u->entries, v); |
199 | 1.04M | bool edgeUtoV = util::contains(v->entries, _u); |
200 | 1.04M | if (edgeVtoU && !edgeUtoV) |
201 | | // Cut edge v -> _u |
202 | 0 | _u->isStartOfSubGraph = true; |
203 | 1.04M | else if (edgeUtoV && !edgeVtoU) |
204 | | // Cut edge _u -> v |
205 | 1.01M | v->isStartOfSubGraph = true; |
206 | 1.04M | } |
207 | 2.62M | } |
208 | 4.01M | else if (v != parent[_u]) |
209 | 1.35M | low[_u] = std::min(low[_u], disc[v]); |
210 | 3.34M | }; |
211 | 718k | dfs(entry, dfs); |
212 | 718k | } |
213 | 214k | } |
214 | | |
215 | | /// Marks each block that needs to maintain a clean stack. That is each block that has an outgoing |
216 | | /// path to a function return. |
217 | | void markNeedsCleanStack(CFG& _cfg) |
218 | 214k | { |
219 | 214k | for (auto& functionInfo: _cfg.functionInfo | ranges::views::values) |
220 | 504k | for (CFG::BasicBlock* exit: functionInfo.exits) |
221 | 1.82M | util::BreadthFirstSearch<CFG::BasicBlock*>{{exit}}.run([&](CFG::BasicBlock* _block, auto _addChild) { |
222 | 1.82M | _block->needsCleanStack = true; |
223 | 1.82M | for (CFG::BasicBlock* entry: _block->entries) |
224 | 1.54M | _addChild(entry); |
225 | 1.82M | }); |
226 | 214k | } |
227 | | } |
228 | | |
229 | | std::unique_ptr<CFG> ControlFlowGraphBuilder::build( |
230 | | AsmAnalysisInfo const& _analysisInfo, |
231 | | Dialect const& _dialect, |
232 | | Block const& _block |
233 | | ) |
234 | 214k | { |
235 | 214k | auto result = std::make_unique<CFG>(); |
236 | 214k | result->entry = &result->makeBlock(debugDataOf(_block)); |
237 | | |
238 | 214k | ControlFlowSideEffectsCollector sideEffects(_dialect, _block); |
239 | 214k | ControlFlowGraphBuilder builder(*result, _analysisInfo, sideEffects.functionSideEffects(), _dialect); |
240 | 214k | builder.m_currentBlock = result->entry; |
241 | 214k | builder(_block); |
242 | | |
243 | 214k | cleanUnreachable(*result); |
244 | 214k | markRecursiveCalls(*result); |
245 | 214k | markStartsOfSubGraphs(*result); |
246 | 214k | markNeedsCleanStack(*result); |
247 | | |
248 | | // TODO: It might be worthwhile to run some further simplifications on the graph itself here. |
249 | | // E.g. if there is a jump to a node that has the jumping node as its only entry, the nodes can be fused, etc. |
250 | | |
251 | 214k | return result; |
252 | 214k | } |
253 | | |
254 | | ControlFlowGraphBuilder::ControlFlowGraphBuilder( |
255 | | CFG& _graph, |
256 | | AsmAnalysisInfo const& _analysisInfo, |
257 | | util::unordered_flat_map<FunctionDefinition const*, ControlFlowSideEffects> const& _functionSideEffects, |
258 | | Dialect const& _dialect |
259 | | ): |
260 | 757k | m_graph(_graph), |
261 | 757k | m_info(_analysisInfo), |
262 | 757k | m_functionSideEffects(_functionSideEffects), |
263 | 757k | m_dialect(_dialect) |
264 | 757k | {} |
265 | | |
266 | | StackSlot ControlFlowGraphBuilder::operator()(Literal const& _literal) |
267 | 8.91M | { |
268 | 8.91M | return LiteralSlot{_literal.value.value(), _literal.debugData}; |
269 | 8.91M | } |
270 | | |
271 | | StackSlot ControlFlowGraphBuilder::operator()(Identifier const& _identifier) |
272 | 2.85M | { |
273 | 2.85M | return VariableSlot{lookupVariable(_identifier.name), _identifier.debugData}; |
274 | 2.85M | } |
275 | | |
276 | | StackSlot ControlFlowGraphBuilder::operator()(Expression const& _expression) |
277 | 0 | { |
278 | 0 | return std::visit(*this, _expression); |
279 | 0 | } |
280 | | |
281 | | StackSlot ControlFlowGraphBuilder::operator()(FunctionCall const& _call) |
282 | 3.94M | { |
283 | 3.94M | Stack const& output = visitFunctionCall(_call); |
284 | 3.94M | yulAssert(output.size() == 1, ""); |
285 | 3.94M | return output.front(); |
286 | 3.94M | } |
287 | | |
288 | | void ControlFlowGraphBuilder::operator()(VariableDeclaration const& _varDecl) |
289 | 1.42M | { |
290 | 1.42M | yulAssert(m_currentBlock, ""); |
291 | 1.60M | auto declaredVariables = _varDecl.variables | ranges::views::transform([&](NameWithDebugData const& _var) { |
292 | 1.60M | return VariableSlot{lookupVariable(_var.name), _var.debugData}; |
293 | 1.60M | }) | ranges::to<std::vector<VariableSlot>>; |
294 | 1.42M | Stack input; |
295 | 1.42M | if (_varDecl.value) |
296 | 1.41M | input = visitAssignmentRightHandSide(*_varDecl.value, declaredVariables.size()); |
297 | 15.2k | else |
298 | 15.2k | input = Stack(_varDecl.variables.size(), LiteralSlot{0, _varDecl.debugData}); |
299 | 1.42M | m_currentBlock->operations.emplace_back(CFG::Operation{ |
300 | 1.42M | std::move(input), |
301 | 1.42M | declaredVariables | ranges::to<Stack>, |
302 | 1.42M | CFG::Assignment{_varDecl.debugData, declaredVariables} |
303 | 1.42M | }); |
304 | 1.42M | } |
305 | | void ControlFlowGraphBuilder::operator()(Assignment const& _assignment) |
306 | 916k | { |
307 | 920k | auto assignedVariables = _assignment.variableNames | ranges::views::transform([&](Identifier const& _var) { |
308 | 920k | return VariableSlot{lookupVariable(_var.name), _var.debugData}; |
309 | 920k | }) | ranges::to<std::vector<VariableSlot>>; |
310 | | |
311 | 916k | Stack input = visitAssignmentRightHandSide(*_assignment.value, assignedVariables.size()); |
312 | 916k | yulAssert(m_currentBlock); |
313 | 916k | m_currentBlock->operations.emplace_back(CFG::Operation{ |
314 | 916k | std::move(input), |
315 | | // output |
316 | 916k | assignedVariables | ranges::to<Stack>, |
317 | | // operation |
318 | 916k | CFG::Assignment{_assignment.debugData, assignedVariables} |
319 | 916k | }); |
320 | 916k | } |
321 | | void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt) |
322 | 2.82M | { |
323 | 2.82M | std::visit(util::GenericVisitor{ |
324 | 2.82M | [&](FunctionCall const& _call) { |
325 | 2.82M | Stack const& output = visitFunctionCall(_call); |
326 | 2.82M | yulAssert(output.empty(), ""); |
327 | 2.82M | }, |
328 | 2.82M | [&](auto const&) { yulAssert(false, ""); }Unexecuted instantiation: ControlFlowGraphBuilder.cpp:auto solidity::yul::ControlFlowGraphBuilder::operator()(solidity::yul::ExpressionStatement const&)::$_1::operator()<solidity::yul::Identifier>(solidity::yul::Identifier const&) const Unexecuted instantiation: ControlFlowGraphBuilder.cpp:auto solidity::yul::ControlFlowGraphBuilder::operator()(solidity::yul::ExpressionStatement const&)::$_1::operator()<solidity::yul::Literal>(solidity::yul::Literal const&) const |
329 | 2.82M | }, _exprStmt.expression); |
330 | 2.82M | } |
331 | | |
332 | | void ControlFlowGraphBuilder::operator()(Block const& _block) |
333 | 2.97M | { |
334 | 2.97M | ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get()); |
335 | 2.97M | for (auto const& statement: _block.statements) |
336 | 6.86M | if (auto const* function = std::get_if<FunctionDefinition>(&statement)) |
337 | 542k | registerFunction(*function); |
338 | 2.97M | for (auto const& statement: _block.statements) |
339 | 6.86M | std::visit(*this, statement); |
340 | 2.97M | } |
341 | | |
342 | | void ControlFlowGraphBuilder::operator()(If const& _if) |
343 | 249k | { |
344 | 249k | auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body)); |
345 | 249k | auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock)); |
346 | 249k | StackSlot condition = std::visit(*this, *_if.condition); |
347 | 249k | makeConditionalJump(debugDataOf(_if), std::move(condition), ifBranch, afterIf); |
348 | 249k | m_currentBlock = &ifBranch; |
349 | 249k | (*this)(_if.body); |
350 | 249k | jump(debugDataOf(_if.body), afterIf); |
351 | 249k | } |
352 | | |
353 | | void ControlFlowGraphBuilder::operator()(Switch const& _switch) |
354 | 103k | { |
355 | 103k | yulAssert(m_currentBlock, ""); |
356 | 103k | langutil::DebugData::ConstPtr preSwitchDebugData = debugDataOf(_switch); |
357 | | |
358 | 103k | auto ghostVariableId = m_graph.ghostVariables.size(); |
359 | 103k | YulName ghostVariableName("GHOST[" + std::to_string(ghostVariableId) + "]"); |
360 | 103k | auto& ghostVar = m_graph.ghostVariables.emplace_back(Scope::Variable{ghostVariableName}); |
361 | | |
362 | | // Artificially generate: |
363 | | // let <ghostVariable> := <switchExpression> |
364 | 103k | VariableSlot ghostVarSlot{ghostVar, debugDataOf(*_switch.expression)}; |
365 | 103k | StackSlot expression = std::visit(*this, *_switch.expression); |
366 | 103k | m_currentBlock->operations.emplace_back(CFG::Operation{ |
367 | 103k | Stack{std::move(expression)}, |
368 | 103k | Stack{ghostVarSlot}, |
369 | 103k | CFG::Assignment{_switch.debugData, {ghostVarSlot}} |
370 | 103k | }); |
371 | | |
372 | 103k | std::optional<BuiltinHandle> const& equalityBuiltinHandle = m_dialect.equalityFunctionHandle(); |
373 | 103k | yulAssert(equalityBuiltinHandle); |
374 | | |
375 | | // Artificially generate: |
376 | | // eq(<literal>, <ghostVariable>) |
377 | 279k | auto makeValueCompare = [&](Case const& _case) { |
378 | 279k | yul::FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(yul::FunctionCall{ |
379 | 279k | debugDataOf(_case), |
380 | 279k | BuiltinName{{}, *equalityBuiltinHandle}, |
381 | 279k | {*_case.value, Identifier{{}, ghostVariableName}} |
382 | 279k | }); |
383 | 279k | BuiltinFunction const& equalityBuiltin = m_dialect.builtin(*equalityBuiltinHandle); |
384 | 279k | CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{ |
385 | 279k | Stack{ghostVarSlot, LiteralSlot{_case.value->value.value(), debugDataOf(*_case.value)}}, |
386 | 279k | Stack{TemporarySlot{ghostCall, 0}}, |
387 | 279k | CFG::BuiltinCall{debugDataOf(_case), equalityBuiltin, ghostCall, 2}, |
388 | 279k | }); |
389 | 279k | return operation.output.front(); |
390 | 279k | }; |
391 | 103k | CFG::BasicBlock& afterSwitch = m_graph.makeBlock(preSwitchDebugData); |
392 | 103k | yulAssert(!_switch.cases.empty(), ""); |
393 | 103k | for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1)) |
394 | 255k | { |
395 | 255k | yulAssert(switchCase.value, ""); |
396 | 255k | auto& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
397 | 255k | auto& elseBranch = m_graph.makeBlock(debugDataOf(_switch)); |
398 | 255k | makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch); |
399 | 255k | m_currentBlock = &caseBranch; |
400 | 255k | (*this)(switchCase.body); |
401 | 255k | jump(debugDataOf(switchCase.body), afterSwitch); |
402 | 255k | m_currentBlock = &elseBranch; |
403 | 255k | } |
404 | 103k | Case const& switchCase = _switch.cases.back(); |
405 | 103k | if (switchCase.value) |
406 | 23.2k | { |
407 | 23.2k | CFG::BasicBlock& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
408 | 23.2k | makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch); |
409 | 23.2k | m_currentBlock = &caseBranch; |
410 | 23.2k | } |
411 | 103k | (*this)(switchCase.body); |
412 | 103k | jump(debugDataOf(switchCase.body), afterSwitch); |
413 | 103k | } |
414 | | |
415 | | void ControlFlowGraphBuilder::operator()(ForLoop const& _loop) |
416 | 460k | { |
417 | 460k | langutil::DebugData::ConstPtr preLoopDebugData = debugDataOf(_loop); |
418 | 460k | ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get()); |
419 | 460k | (*this)(_loop.pre); |
420 | | |
421 | 460k | std::optional<bool> constantCondition; |
422 | 460k | if (auto const* literalCondition = std::get_if<yul::Literal>(_loop.condition.get())) |
423 | 103k | constantCondition = literalCondition->value.value() != 0; |
424 | | |
425 | 460k | CFG::BasicBlock& loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition)); |
426 | 460k | CFG::BasicBlock& loopBody = m_graph.makeBlock(debugDataOf(_loop.body)); |
427 | 460k | CFG::BasicBlock& post = m_graph.makeBlock(debugDataOf(_loop.post)); |
428 | 460k | CFG::BasicBlock& afterLoop = m_graph.makeBlock(preLoopDebugData); |
429 | | |
430 | 460k | ScopedSaveAndRestore scopedSaveAndRestore(m_forLoopInfo, ForLoopInfo{afterLoop, post}); |
431 | | |
432 | 460k | if (constantCondition.has_value()) |
433 | 103k | { |
434 | 103k | if (*constantCondition) |
435 | 102k | { |
436 | 102k | jump(debugDataOf(_loop.pre), loopBody); |
437 | 102k | (*this)(_loop.body); |
438 | 102k | jump(debugDataOf(_loop.body), post); |
439 | 102k | (*this)(_loop.post); |
440 | 102k | jump(debugDataOf(_loop.post), loopBody, true); |
441 | 102k | } |
442 | 624 | else |
443 | 624 | jump(debugDataOf(_loop.pre), afterLoop); |
444 | 103k | } |
445 | 357k | else |
446 | 357k | { |
447 | 357k | jump(debugDataOf(_loop.pre), loopCondition); |
448 | 357k | StackSlot condition = std::visit(*this, *_loop.condition); |
449 | 357k | makeConditionalJump(debugDataOf(*_loop.condition), std::move(condition), loopBody, afterLoop); |
450 | 357k | m_currentBlock = &loopBody; |
451 | 357k | (*this)(_loop.body); |
452 | 357k | jump(debugDataOf(_loop.body), post); |
453 | 357k | (*this)(_loop.post); |
454 | 357k | jump(debugDataOf(_loop.post), loopCondition, true); |
455 | 357k | } |
456 | | |
457 | 460k | m_currentBlock = &afterLoop; |
458 | 460k | } |
459 | | |
460 | | void ControlFlowGraphBuilder::operator()(Break const& _break) |
461 | 26.5k | { |
462 | 26.5k | yulAssert(m_forLoopInfo.has_value(), ""); |
463 | 26.5k | jump(debugDataOf(_break), m_forLoopInfo->afterLoop); |
464 | 26.5k | m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); |
465 | 26.5k | } |
466 | | |
467 | | void ControlFlowGraphBuilder::operator()(Continue const& _continue) |
468 | 29.8k | { |
469 | 29.8k | yulAssert(m_forLoopInfo.has_value(), ""); |
470 | 29.8k | jump(debugDataOf(_continue), m_forLoopInfo->post); |
471 | 29.8k | m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); |
472 | 29.8k | } |
473 | | |
474 | | // '_leave' and '__leave' are reserved in VisualStudio |
475 | | void ControlFlowGraphBuilder::operator()(Leave const& leave_) |
476 | 58.3k | { |
477 | 58.3k | yulAssert(m_currentFunction.has_value(), ""); |
478 | 58.3k | m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(leave_), *m_currentFunction}; |
479 | 58.3k | (*m_currentFunction)->exits.emplace_back(m_currentBlock); |
480 | 58.3k | m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); |
481 | 58.3k | } |
482 | | |
483 | | void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function) |
484 | 542k | { |
485 | 542k | yulAssert(m_scope, ""); |
486 | 542k | yulAssert(m_scope->identifiers.count(_function.name), ""); |
487 | 542k | Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name)); |
488 | 542k | m_graph.functions.emplace_back(&function); |
489 | | |
490 | 542k | CFG::FunctionInfo& functionInfo = m_graph.functionInfo.at(&function); |
491 | | |
492 | 542k | ControlFlowGraphBuilder builder{m_graph, m_info, m_functionSideEffects, m_dialect}; |
493 | 542k | builder.m_currentFunction = &functionInfo; |
494 | 542k | builder.m_currentBlock = functionInfo.entry; |
495 | 542k | builder(_function.body); |
496 | 542k | functionInfo.exits.emplace_back(builder.m_currentBlock); |
497 | 542k | builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo}; |
498 | 542k | } |
499 | | |
500 | | void ControlFlowGraphBuilder::registerFunction(FunctionDefinition const& _functionDefinition) |
501 | 542k | { |
502 | 542k | yulAssert(m_scope, ""); |
503 | 542k | yulAssert(m_scope->identifiers.count(_functionDefinition.name), ""); |
504 | 542k | Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name)); |
505 | | |
506 | 542k | yulAssert(m_info.scopes.at(&_functionDefinition.body), ""); |
507 | 542k | Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_functionDefinition).get()).get(); |
508 | 542k | yulAssert(virtualFunctionScope, ""); |
509 | | |
510 | 542k | bool inserted = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{ |
511 | 542k | _functionDefinition.debugData, |
512 | 542k | function, |
513 | 542k | _functionDefinition, |
514 | 542k | &m_graph.makeBlock(debugDataOf(_functionDefinition.body)), |
515 | 542k | _functionDefinition.parameters | ranges::views::transform([&](auto const& _param) { |
516 | 481k | return VariableSlot{ |
517 | 481k | std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)), |
518 | 481k | _param.debugData |
519 | 481k | }; |
520 | 481k | }) | ranges::to<std::vector>, |
521 | 542k | _functionDefinition.returnVariables | ranges::views::transform([&](auto const& _retVar) { |
522 | 425k | return VariableSlot{ |
523 | 425k | std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_retVar.name)), |
524 | 425k | _retVar.debugData |
525 | 425k | }; |
526 | 425k | }) | ranges::to<std::vector>, |
527 | 542k | {}, |
528 | 542k | m_functionSideEffects.at(&_functionDefinition).canContinue |
529 | 542k | })).second; |
530 | 542k | yulAssert(inserted); |
531 | 542k | } |
532 | | |
533 | | Stack const& ControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call) |
534 | 8.12M | { |
535 | 8.12M | yulAssert(m_scope, ""); |
536 | 8.12M | yulAssert(m_currentBlock, ""); |
537 | | |
538 | 8.12M | Stack const* output = nullptr; |
539 | 8.12M | bool canContinue = true; |
540 | 8.12M | if (BuiltinFunction const* builtin = resolveBuiltinFunction(_call.functionName, m_dialect)) |
541 | 7.04M | { |
542 | 7.04M | Stack inputs; |
543 | 7.04M | for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) |
544 | 13.2M | if (!builtin->literalArgument(idx).has_value()) |
545 | 13.0M | inputs.emplace_back(std::visit(*this, arg)); |
546 | 7.04M | CFG::BuiltinCall builtinCall{_call.debugData, *builtin, _call, inputs.size()}; |
547 | 7.04M | output = &m_currentBlock->operations.emplace_back(CFG::Operation{ |
548 | | // input |
549 | 7.04M | std::move(inputs), |
550 | | // output |
551 | 7.04M | ranges::views::iota(0u, builtin->numReturns) | ranges::views::transform([&](size_t _i) { |
552 | 4.77M | return TemporarySlot{_call, _i}; |
553 | 4.77M | }) | ranges::to<Stack>, |
554 | | // operation |
555 | 7.04M | std::move(builtinCall) |
556 | 7.04M | }).output; |
557 | 7.04M | canContinue = builtin->controlFlowSideEffects.canContinue; |
558 | 7.04M | } |
559 | 1.08M | else |
560 | 1.08M | { |
561 | 1.08M | yulAssert(std::holds_alternative<Identifier>(_call.functionName)); |
562 | 1.08M | Scope::Function const& function = lookupFunction(std::get<Identifier>(_call.functionName).name); |
563 | 1.08M | canContinue = m_graph.functionInfo.at(&function).canContinue; |
564 | 1.08M | Stack inputs; |
565 | 1.08M | if (canContinue) |
566 | 992k | inputs.emplace_back(FunctionCallReturnLabelSlot{_call}); |
567 | 1.08M | for (auto const& arg: _call.arguments | ranges::views::reverse) |
568 | 939k | inputs.emplace_back(std::visit(*this, arg)); |
569 | 1.08M | output = &m_currentBlock->operations.emplace_back(CFG::Operation{ |
570 | | // input |
571 | 1.08M | std::move(inputs), |
572 | | // output |
573 | 1.08M | ranges::views::iota(0u, function.numReturns) | ranges::views::transform([&](size_t _i) { |
574 | 683k | return TemporarySlot{_call, _i}; |
575 | 683k | }) | ranges::to<Stack>, |
576 | | // operation |
577 | 1.08M | CFG::FunctionCall{_call.debugData, function, _call, /* recursive */ false, canContinue} |
578 | 1.08M | }).output; |
579 | 1.08M | } |
580 | 8.12M | if (!canContinue) |
581 | 274k | { |
582 | 274k | m_currentBlock->exit = CFG::BasicBlock::Terminated{}; |
583 | 274k | m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock)); |
584 | 274k | } |
585 | 8.12M | return *output; |
586 | 8.12M | } |
587 | | |
588 | | Stack ControlFlowGraphBuilder::visitAssignmentRightHandSide(Expression const& _expression, size_t _expectedSlotCount) |
589 | 2.32M | { |
590 | 2.32M | return std::visit(util::GenericVisitor{ |
591 | 2.32M | [&](FunctionCall const& _call) -> Stack { |
592 | 1.36M | Stack const& output = visitFunctionCall(_call); |
593 | 1.36M | yulAssert(_expectedSlotCount == output.size(), ""); |
594 | 1.36M | return output; |
595 | 1.36M | }, |
596 | 2.32M | [&](auto const& _identifierOrLiteral) -> Stack { |
597 | 965k | yulAssert(_expectedSlotCount == 1, ""); |
598 | 965k | return {(*this)(_identifierOrLiteral)}; |
599 | 965k | } ControlFlowGraphBuilder.cpp:std::__1::vector<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot>, std::__1::allocator<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> > > solidity::yul::ControlFlowGraphBuilder::visitAssignmentRightHandSide(std::__1::variant<solidity::yul::FunctionCall, solidity::yul::Identifier, solidity::yul::Literal> const&, unsigned long)::$_1::operator()<solidity::yul::Identifier>(solidity::yul::Identifier const&) const Line | Count | Source | 596 | 216k | [&](auto const& _identifierOrLiteral) -> Stack { | 597 | | yulAssert(_expectedSlotCount == 1, ""); | 598 | 216k | return {(*this)(_identifierOrLiteral)}; | 599 | 216k | } |
ControlFlowGraphBuilder.cpp:std::__1::vector<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot>, std::__1::allocator<std::__1::variant<solidity::yul::FunctionCallReturnLabelSlot, solidity::yul::FunctionReturnLabelSlot, solidity::yul::VariableSlot, solidity::yul::LiteralSlot, solidity::yul::TemporarySlot, solidity::yul::JunkSlot> > > solidity::yul::ControlFlowGraphBuilder::visitAssignmentRightHandSide(std::__1::variant<solidity::yul::FunctionCall, solidity::yul::Identifier, solidity::yul::Literal> const&, unsigned long)::$_1::operator()<solidity::yul::Literal>(solidity::yul::Literal const&) const Line | Count | Source | 596 | 749k | [&](auto const& _identifierOrLiteral) -> Stack { | 597 | | yulAssert(_expectedSlotCount == 1, ""); | 598 | 749k | return {(*this)(_identifierOrLiteral)}; | 599 | 749k | } |
|
600 | 2.32M | }, _expression); |
601 | 2.32M | } |
602 | | |
603 | | Scope::Function const& ControlFlowGraphBuilder::lookupFunction(YulName _name) const |
604 | 1.08M | { |
605 | 1.08M | Scope::Function const* function = nullptr; |
606 | 1.08M | yulAssert(m_scope->lookup(_name, util::GenericVisitor{ |
607 | 1.08M | [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, |
608 | 1.08M | [&](Scope::Function& _function) { function = &_function; } |
609 | 1.08M | }), "Function name not found."); |
610 | 1.08M | yulAssert(function, ""); |
611 | 1.08M | return *function; |
612 | 1.08M | } |
613 | | |
614 | | Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulName _name) const |
615 | 5.37M | { |
616 | 5.37M | yulAssert(m_scope, ""); |
617 | 5.37M | Scope::Variable const* var = nullptr; |
618 | 5.37M | if (m_scope->lookup(_name, util::GenericVisitor{ |
619 | 5.37M | [&](Scope::Variable& _var) { var = &_var; }, |
620 | 5.37M | [](Scope::Function&) |
621 | 5.37M | { |
622 | 0 | yulAssert(false, "Function not removed during desugaring."); |
623 | 0 | } |
624 | 5.37M | })) |
625 | 5.37M | { |
626 | 5.37M | yulAssert(var, ""); |
627 | 5.37M | return *var; |
628 | 5.37M | }; |
629 | 0 | yulAssert(false, "External identifier access unimplemented."); |
630 | 0 | } |
631 | | |
632 | | void ControlFlowGraphBuilder::makeConditionalJump( |
633 | | langutil::DebugData::ConstPtr _debugData, |
634 | | StackSlot _condition, |
635 | | CFG::BasicBlock& _nonZero, |
636 | | CFG::BasicBlock& _zero |
637 | | ) |
638 | 886k | { |
639 | 886k | yulAssert(m_currentBlock, ""); |
640 | 886k | m_currentBlock->exit = CFG::BasicBlock::ConditionalJump{ |
641 | 886k | std::move(_debugData), |
642 | 886k | std::move(_condition), |
643 | 886k | &_nonZero, |
644 | 886k | &_zero |
645 | 886k | }; |
646 | 886k | _nonZero.entries.emplace_back(m_currentBlock); |
647 | 886k | _zero.entries.emplace_back(m_currentBlock); |
648 | 886k | m_currentBlock = nullptr; |
649 | 886k | } |
650 | | |
651 | | void ControlFlowGraphBuilder::jump( |
652 | | langutil::DebugData::ConstPtr _debugData, |
653 | | CFG::BasicBlock& _target, |
654 | | bool backwards |
655 | | ) |
656 | 2.04M | { |
657 | | yulAssert(m_currentBlock, ""); |
658 | 2.04M | m_currentBlock->exit = CFG::BasicBlock::Jump{std::move(_debugData), &_target, backwards}; |
659 | 2.04M | _target.entries.emplace_back(m_currentBlock); |
660 | 2.04M | m_currentBlock = &_target; |
661 | 2.04M | } |