/src/solidity/libyul/backends/evm/SSAControlFlowGraphBuilder.cpp
Line | Count | Source (jump to first uncovered line) |
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/SSAControlFlowGraphBuilder.h> |
23 | | |
24 | | #include <libyul/backends/evm/ControlFlow.h> |
25 | | #include <libyul/AST.h> |
26 | | #include <libyul/ControlFlowSideEffectsCollector.h> |
27 | | #include <libyul/Exceptions.h> |
28 | | #include <libyul/Utilities.h> |
29 | | |
30 | | #include <libsolutil/Algorithms.h> |
31 | | #include <libsolutil/StringUtils.h> |
32 | | #include <libsolutil/Visitor.h> |
33 | | |
34 | | #include <range/v3/algorithm/replace.hpp> |
35 | | #include <range/v3/range/conversion.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/reverse.hpp> |
40 | | #include <range/v3/view/transform.hpp> |
41 | | #include <range/v3/view/zip.hpp> |
42 | | |
43 | | using namespace solidity; |
44 | | using namespace solidity::yul; |
45 | | |
46 | | namespace solidity::yul |
47 | | { |
48 | | |
49 | | SSAControlFlowGraphBuilder::SSAControlFlowGraphBuilder( |
50 | | ControlFlow& _controlFlow, |
51 | | SSACFG& _graph, |
52 | | AsmAnalysisInfo const& _analysisInfo, |
53 | | ControlFlowSideEffectsCollector const& _sideEffects, |
54 | | Dialect const& _dialect, |
55 | | bool _keepLiteralAssignments |
56 | | ): |
57 | | m_controlFlow(_controlFlow), |
58 | | m_graph(_graph), |
59 | | m_info(_analysisInfo), |
60 | | m_sideEffects(_sideEffects), |
61 | | m_dialect(_dialect), |
62 | | m_keepLiteralAssignments(_keepLiteralAssignments) |
63 | 0 | { |
64 | 0 | } |
65 | | |
66 | | std::unique_ptr<ControlFlow> SSAControlFlowGraphBuilder::build( |
67 | | AsmAnalysisInfo const& _analysisInfo, |
68 | | Dialect const& _dialect, |
69 | | Block const& _block, |
70 | | bool _keepLiteralAssignments |
71 | | ) |
72 | 0 | { |
73 | 0 | ControlFlowSideEffectsCollector sideEffects(_dialect, _block); |
74 | |
|
75 | 0 | auto controlFlow = std::make_unique<ControlFlow>(); |
76 | 0 | SSAControlFlowGraphBuilder builder(*controlFlow, *controlFlow->mainGraph, _analysisInfo, sideEffects, _dialect, _keepLiteralAssignments); |
77 | 0 | builder.m_currentBlock = controlFlow->mainGraph->makeBlock(debugDataOf(_block)); |
78 | 0 | builder.sealBlock(builder.m_currentBlock); |
79 | 0 | builder(_block); |
80 | 0 | if (!builder.blockInfo(builder.m_currentBlock).sealed) |
81 | 0 | builder.sealBlock(builder.m_currentBlock); |
82 | 0 | controlFlow->mainGraph->block(builder.m_currentBlock).exit = SSACFG::BasicBlock::MainExit{}; |
83 | 0 | builder.cleanUnreachable(); |
84 | 0 | return controlFlow; |
85 | 0 | } |
86 | | |
87 | | SSACFG::ValueId SSAControlFlowGraphBuilder::tryRemoveTrivialPhi(SSACFG::ValueId _phi) |
88 | 0 | { |
89 | | // TODO: double-check if this is sane |
90 | 0 | auto const* phiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(_phi)); |
91 | 0 | yulAssert(phiInfo); |
92 | 0 | yulAssert(blockInfo(phiInfo->block).sealed); |
93 | | |
94 | 0 | SSACFG::ValueId same; |
95 | 0 | for (SSACFG::ValueId arg: phiInfo->arguments) |
96 | 0 | { |
97 | 0 | if (arg == same || arg == _phi) |
98 | 0 | continue; // unique value or self-reference |
99 | 0 | if (same.hasValue()) |
100 | 0 | return _phi; // phi merges at least two distinct values -> not trivial |
101 | 0 | same = arg; |
102 | 0 | } |
103 | 0 | if (!same.hasValue()) |
104 | 0 | { |
105 | | // This will happen for unreachable paths. |
106 | | // TODO: check how best to deal with this |
107 | 0 | same = m_graph.unreachableValue(); |
108 | 0 | } |
109 | |
|
110 | 0 | m_graph.block(phiInfo->block).phis.erase(_phi); |
111 | |
|
112 | 0 | std::vector<SSACFG::ValueId> phiUses; |
113 | 0 | for (size_t blockIdValue = 0; blockIdValue < m_graph.numBlocks(); ++blockIdValue) |
114 | 0 | { |
115 | 0 | auto& block = m_graph.block(SSACFG::BlockId{blockIdValue}); |
116 | 0 | for (auto blockPhi: block.phis) |
117 | 0 | { |
118 | 0 | yulAssert(blockPhi != _phi, "Phis should be defined in exactly one block, _phi was erased."); |
119 | 0 | auto* blockPhiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(blockPhi)); |
120 | 0 | yulAssert(blockPhiInfo); |
121 | 0 | bool usedInPhi = false; |
122 | 0 | for (auto& arg: blockPhiInfo->arguments) |
123 | 0 | if (arg == _phi) |
124 | 0 | { |
125 | 0 | arg = same; |
126 | 0 | usedInPhi = true; |
127 | 0 | } |
128 | 0 | if (usedInPhi) |
129 | 0 | phiUses.push_back(blockPhi); |
130 | 0 | } |
131 | 0 | for (auto& op: block.operations) |
132 | 0 | ranges::replace(op.inputs, _phi, same); |
133 | 0 | std::visit(util::GenericVisitor{ |
134 | 0 | [_phi, same](SSACFG::BasicBlock::FunctionReturn& _functionReturn) { |
135 | 0 | ranges::replace(_functionReturn.returnValues,_phi, same); |
136 | 0 | }, |
137 | 0 | [_phi, same](SSACFG::BasicBlock::ConditionalJump& _condJump) { |
138 | 0 | if (_condJump.condition == _phi) |
139 | 0 | _condJump.condition = same; |
140 | 0 | }, |
141 | 0 | [_phi, same](SSACFG::BasicBlock::JumpTable& _jumpTable) { |
142 | 0 | if (_jumpTable.value == _phi) |
143 | 0 | _jumpTable.value = same; |
144 | 0 | }, |
145 | 0 | [](SSACFG::BasicBlock::Jump&) {}, |
146 | 0 | [](SSACFG::BasicBlock::MainExit&) {}, |
147 | 0 | [](SSACFG::BasicBlock::Terminated&) {} |
148 | 0 | }, block.exit); |
149 | 0 | } |
150 | 0 | for (auto& currentVariableDefs: m_currentDef | ranges::views::values) |
151 | 0 | ranges::replace(currentVariableDefs, _phi, same); |
152 | |
|
153 | 0 | for (auto phiUse: phiUses) |
154 | 0 | tryRemoveTrivialPhi(phiUse); |
155 | |
|
156 | 0 | return same; |
157 | 0 | } |
158 | | |
159 | | /// Removes edges to blocks that are not reachable. |
160 | | void SSAControlFlowGraphBuilder::cleanUnreachable() |
161 | 0 | { |
162 | | // Determine which blocks are reachable from the entry. |
163 | 0 | util::BreadthFirstSearch<SSACFG::BlockId> reachabilityCheck{{m_graph.entry}}; |
164 | 0 | reachabilityCheck.run([&](SSACFG::BlockId _blockId, auto&& _addChild) { |
165 | 0 | auto const& block = m_graph.block(_blockId); |
166 | 0 | visit(util::GenericVisitor{ |
167 | 0 | [&](SSACFG::BasicBlock::Jump const& _jump) { |
168 | 0 | _addChild(_jump.target); |
169 | 0 | }, |
170 | 0 | [&](SSACFG::BasicBlock::ConditionalJump const& _jump) { |
171 | 0 | _addChild(_jump.zero); |
172 | 0 | _addChild(_jump.nonZero); |
173 | 0 | }, |
174 | 0 | [](SSACFG::BasicBlock::JumpTable const&) { yulAssert(false); }, |
175 | 0 | [](SSACFG::BasicBlock::FunctionReturn const&) {}, |
176 | 0 | [](SSACFG::BasicBlock::Terminated const&) {}, |
177 | 0 | [](SSACFG::BasicBlock::MainExit const&) {} |
178 | 0 | }, block.exit); |
179 | 0 | }); |
180 | | |
181 | | // Remove all entries from unreachable nodes from the graph. |
182 | 0 | for (SSACFG::BlockId blockId: reachabilityCheck.visited) |
183 | 0 | { |
184 | 0 | auto& block = m_graph.block(blockId); |
185 | |
|
186 | 0 | std::vector<SSACFG::ValueId> maybeTrivialPhi; |
187 | 0 | std::erase_if(block.entries, [&](auto const& entry) { return !reachabilityCheck.visited.contains(entry); }); |
188 | 0 | for (auto phi: block.phis) |
189 | 0 | if (auto* phiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(phi))) |
190 | 0 | { |
191 | 0 | auto erasedCount = std::erase_if(phiInfo->arguments, [&](SSACFG::ValueId _arg) { |
192 | 0 | return std::holds_alternative<SSACFG::UnreachableValue>(m_graph.valueInfo(_arg)); |
193 | 0 | }); |
194 | 0 | if (erasedCount > 0) |
195 | 0 | maybeTrivialPhi.push_back(phi); |
196 | 0 | } |
197 | | |
198 | | // After removing a phi argument, we might end up with a trivial phi that can be removed. |
199 | 0 | for (auto phi: maybeTrivialPhi) |
200 | 0 | tryRemoveTrivialPhi(phi); |
201 | 0 | } |
202 | 0 | } |
203 | | |
204 | | void SSAControlFlowGraphBuilder::buildFunctionGraph( |
205 | | Scope::Function const* _function, |
206 | | FunctionDefinition const* _functionDefinition |
207 | | ) |
208 | 0 | { |
209 | 0 | m_controlFlow.functionGraphs.emplace_back(std::make_unique<SSACFG>()); |
210 | 0 | auto& cfg = *m_controlFlow.functionGraphs.back(); |
211 | 0 | m_controlFlow.functionGraphMapping.emplace_back(_function, &cfg); |
212 | |
|
213 | 0 | yulAssert(m_info.scopes.at(&_functionDefinition->body), ""); |
214 | 0 | Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(_functionDefinition).get()).get(); |
215 | 0 | yulAssert(virtualFunctionScope, ""); |
216 | | |
217 | 0 | cfg.entry = cfg.makeBlock(debugDataOf(_functionDefinition->body)); |
218 | 0 | auto arguments = _functionDefinition->parameters | ranges::views::transform([&](auto const& _param) { |
219 | 0 | auto const& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)); |
220 | | // Note: cannot use std::make_tuple since it unwraps reference wrappers. |
221 | 0 | return std::tuple{std::cref(var), cfg.newVariable(cfg.entry)}; |
222 | 0 | }) | ranges::to<std::vector>; |
223 | 0 | auto returns = _functionDefinition->returnVariables | ranges::views::transform([&](auto const& _param) { |
224 | 0 | return std::cref(std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name))); |
225 | 0 | }) | ranges::to<std::vector>; |
226 | |
|
227 | 0 | cfg.debugData = _functionDefinition->debugData; |
228 | 0 | cfg.function = _function; |
229 | 0 | cfg.canContinue = m_sideEffects.functionSideEffects().at(_functionDefinition).canContinue; |
230 | 0 | cfg.arguments = arguments; |
231 | 0 | cfg.returns = returns; |
232 | |
|
233 | 0 | SSAControlFlowGraphBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect, m_keepLiteralAssignments); |
234 | 0 | builder.m_currentBlock = cfg.entry; |
235 | 0 | builder.m_functionDefinitions = m_functionDefinitions; |
236 | 0 | for (auto&& [var, varId]: cfg.arguments) |
237 | 0 | builder.currentDef(var, cfg.entry) = varId; |
238 | 0 | for (auto const& var: cfg.returns) |
239 | 0 | builder.currentDef(var.get(), cfg.entry) = builder.zero(); |
240 | 0 | builder.sealBlock(cfg.entry); |
241 | 0 | builder(_functionDefinition->body); |
242 | 0 | cfg.exits.insert(builder.m_currentBlock); |
243 | | // Artificial explicit function exit (`leave`) at the end of the body. |
244 | 0 | builder(Leave{debugDataOf(*_functionDefinition)}); |
245 | 0 | builder.cleanUnreachable(); |
246 | 0 | } |
247 | | |
248 | | void SSAControlFlowGraphBuilder::operator()(ExpressionStatement const& _expressionStatement) |
249 | 0 | { |
250 | 0 | auto const* functionCall = std::get_if<FunctionCall>(&_expressionStatement.expression); |
251 | 0 | yulAssert(functionCall); |
252 | 0 | auto results = visitFunctionCall(*functionCall); |
253 | 0 | yulAssert(results.empty()); |
254 | 0 | } |
255 | | |
256 | | void SSAControlFlowGraphBuilder::operator()(Assignment const& _assignment) |
257 | 0 | { |
258 | 0 | assign( |
259 | 0 | _assignment.variableNames | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>, |
260 | 0 | _assignment.value.get() |
261 | 0 | ); |
262 | 0 | } |
263 | | |
264 | | void SSAControlFlowGraphBuilder::operator()(VariableDeclaration const& _variableDeclaration) |
265 | 0 | { |
266 | 0 | assign( |
267 | 0 | _variableDeclaration.variables | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>, |
268 | 0 | _variableDeclaration.value.get() |
269 | 0 | ); |
270 | 0 | } |
271 | | |
272 | | void SSAControlFlowGraphBuilder::operator()(FunctionDefinition const& _functionDefinition) |
273 | 0 | { |
274 | 0 | Scope::Function const& function = lookupFunction(_functionDefinition.name); |
275 | 0 | buildFunctionGraph(&function, &_functionDefinition); |
276 | 0 | } |
277 | | |
278 | | void SSAControlFlowGraphBuilder::operator()(If const& _if) |
279 | 0 | { |
280 | 0 | std::optional<bool> constantCondition; |
281 | 0 | if (auto const* literalCondition = std::get_if<Literal>(_if.condition.get())) |
282 | 0 | constantCondition = literalCondition->value.value() != 0; |
283 | | // deal with literal (constant) conditions explicitly |
284 | 0 | if (constantCondition) |
285 | 0 | { |
286 | 0 | if (*constantCondition) |
287 | | // Always true - skip conditional, just execute if branch |
288 | 0 | (*this)(_if.body); |
289 | 0 | } |
290 | 0 | else |
291 | 0 | { |
292 | 0 | auto condition = std::visit(*this, *_if.condition); |
293 | 0 | auto ifBranch = m_graph.makeBlock(debugDataOf(_if.body)); |
294 | 0 | auto afterIf = m_graph.makeBlock(debugDataOf(currentBlock())); |
295 | 0 | conditionalJump( |
296 | 0 | debugDataOf(_if), |
297 | 0 | condition, |
298 | 0 | ifBranch, |
299 | 0 | afterIf |
300 | 0 | ); |
301 | 0 | sealBlock(ifBranch); |
302 | 0 | m_currentBlock = ifBranch; |
303 | 0 | (*this)(_if.body); |
304 | 0 | jump(debugDataOf(_if.body), afterIf); |
305 | 0 | sealBlock(afterIf); |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | | void SSAControlFlowGraphBuilder::operator()(Switch const& _switch) |
310 | 0 | { |
311 | 0 | auto expression = std::visit(*this, *_switch.expression); |
312 | |
|
313 | 0 | auto useJumpTableForSwitch = [](Switch const&) { |
314 | | // TODO: check for EOF support & tight switch values. |
315 | 0 | return false; |
316 | 0 | }; |
317 | 0 | if (useJumpTableForSwitch(_switch)) |
318 | 0 | { |
319 | | // TODO: also generate a subtraction to shift tight, but non-zero switch cases - or, alternative, |
320 | | // transform to zero-based tight switches on Yul if possible. |
321 | 0 | std::map<u256, SSACFG::BlockId> cases; |
322 | 0 | std::optional<SSACFG::BlockId> defaultCase; |
323 | 0 | std::vector<std::tuple<SSACFG::BlockId, std::reference_wrapper<Block const>>> children; |
324 | 0 | for (auto const& _case: _switch.cases) |
325 | 0 | { |
326 | 0 | auto blockId = m_graph.makeBlock(debugDataOf(_case.body)); |
327 | 0 | if (_case.value) |
328 | 0 | cases[_case.value->value.value()] = blockId; |
329 | 0 | else |
330 | 0 | defaultCase = blockId; |
331 | 0 | children.emplace_back(blockId, std::ref(_case.body)); |
332 | 0 | } |
333 | 0 | auto afterSwitch = m_graph.makeBlock(debugDataOf(currentBlock())); |
334 | |
|
335 | 0 | tableJump(debugDataOf(_switch), expression, cases, defaultCase ? *defaultCase : afterSwitch); |
336 | 0 | for (auto [blockId, block]: children) |
337 | 0 | { |
338 | 0 | sealBlock(blockId); |
339 | 0 | m_currentBlock = blockId; |
340 | 0 | (*this)(block); |
341 | 0 | jump(debugDataOf(currentBlock()), afterSwitch); |
342 | 0 | } |
343 | 0 | sealBlock(afterSwitch); |
344 | 0 | m_currentBlock = afterSwitch; |
345 | 0 | } |
346 | 0 | else |
347 | 0 | { |
348 | 0 | if (auto const* constantExpression = std::get_if<Literal>(_switch.expression.get())) |
349 | 0 | { |
350 | 0 | Case const* matchedCase = nullptr; |
351 | | // select case that matches (or default if available) |
352 | 0 | for (auto const& switchCase: _switch.cases) |
353 | 0 | { |
354 | 0 | if (!switchCase.value) |
355 | 0 | matchedCase = &switchCase; |
356 | 0 | if (switchCase.value && switchCase.value->value.value() == constantExpression->value.value()) |
357 | 0 | { |
358 | 0 | matchedCase = &switchCase; |
359 | 0 | break; |
360 | 0 | } |
361 | 0 | } |
362 | 0 | if (matchedCase) |
363 | 0 | { |
364 | | // inject directly into the current block |
365 | 0 | (*this)(matchedCase->body); |
366 | 0 | } |
367 | 0 | return; |
368 | 0 | } |
369 | | |
370 | 0 | std::optional<BuiltinHandle> equalityBuiltinHandle = m_dialect.equalityFunctionHandle(); |
371 | 0 | yulAssert(equalityBuiltinHandle); |
372 | | |
373 | 0 | auto makeValueCompare = [&](Case const& _case) { |
374 | 0 | FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(FunctionCall{ |
375 | 0 | debugDataOf(_case), |
376 | 0 | BuiltinName{{}, *equalityBuiltinHandle}, |
377 | 0 | {*_case.value /* skip second argument */ } |
378 | 0 | }); |
379 | 0 | auto outputValue = m_graph.newVariable(m_currentBlock); |
380 | 0 | currentBlock().operations.emplace_back(SSACFG::Operation{ |
381 | 0 | {outputValue}, |
382 | 0 | SSACFG::BuiltinCall{ |
383 | 0 | debugDataOf(_case), |
384 | 0 | m_dialect.builtin(*equalityBuiltinHandle), |
385 | 0 | ghostCall |
386 | 0 | }, |
387 | 0 | {m_graph.newLiteral(debugDataOf(_case), _case.value->value.value()), expression} |
388 | 0 | }); |
389 | 0 | return outputValue; |
390 | 0 | }; |
391 | |
|
392 | 0 | auto afterSwitch = m_graph.makeBlock(debugDataOf(currentBlock())); |
393 | 0 | yulAssert(!_switch.cases.empty(), ""); |
394 | 0 | for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1)) |
395 | 0 | { |
396 | 0 | yulAssert(switchCase.value, ""); |
397 | 0 | auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
398 | 0 | auto elseBranch = m_graph.makeBlock(debugDataOf(_switch)); |
399 | |
|
400 | 0 | conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch); |
401 | 0 | sealBlock(caseBranch); |
402 | 0 | sealBlock(elseBranch); |
403 | 0 | m_currentBlock = caseBranch; |
404 | 0 | (*this)(switchCase.body); |
405 | 0 | jump(debugDataOf(switchCase.body), afterSwitch); |
406 | 0 | m_currentBlock = elseBranch; |
407 | 0 | } |
408 | 0 | Case const& switchCase = _switch.cases.back(); |
409 | 0 | if (switchCase.value) |
410 | 0 | { |
411 | 0 | auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
412 | 0 | conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch); |
413 | 0 | sealBlock(caseBranch); |
414 | 0 | m_currentBlock = caseBranch; |
415 | 0 | } |
416 | 0 | (*this)(switchCase.body); |
417 | 0 | jump(debugDataOf(switchCase.body), afterSwitch); |
418 | 0 | sealBlock(afterSwitch); |
419 | 0 | } |
420 | 0 | } |
421 | | void SSAControlFlowGraphBuilder::operator()(ForLoop const& _loop) |
422 | 0 | { |
423 | 0 | ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get()); |
424 | 0 | (*this)(_loop.pre); |
425 | 0 | auto preLoopDebugData = debugDataOf(currentBlock()); |
426 | |
|
427 | 0 | std::optional<bool> constantCondition; |
428 | 0 | if (auto const* literalCondition = std::get_if<Literal>(_loop.condition.get())) |
429 | 0 | constantCondition = literalCondition->value.value() != 0; |
430 | |
|
431 | 0 | SSACFG::BlockId loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition)); |
432 | 0 | SSACFG::BlockId loopBody = m_graph.makeBlock(debugDataOf(_loop.body)); |
433 | 0 | SSACFG::BlockId post = m_graph.makeBlock(debugDataOf(_loop.post)); |
434 | 0 | SSACFG::BlockId afterLoop = m_graph.makeBlock(preLoopDebugData); |
435 | |
|
436 | 0 | class ForLoopInfoScope { |
437 | 0 | public: |
438 | 0 | ForLoopInfoScope(std::stack<ForLoopInfo>& _info, SSACFG::BlockId _breakBlock, SSACFG::BlockId _continueBlock): m_info(_info) |
439 | 0 | { |
440 | 0 | m_info.push(ForLoopInfo{_breakBlock, _continueBlock}); |
441 | 0 | } |
442 | 0 | ~ForLoopInfoScope() { |
443 | 0 | m_info.pop(); |
444 | 0 | } |
445 | 0 | private: |
446 | 0 | std::stack<ForLoopInfo>& m_info; |
447 | 0 | } forLoopInfoScope(m_forLoopInfo, afterLoop, post); |
448 | |
|
449 | 0 | if (constantCondition.has_value()) |
450 | 0 | { |
451 | 0 | std::visit(*this, *_loop.condition); |
452 | 0 | if (*constantCondition) |
453 | 0 | { |
454 | 0 | jump(debugDataOf(*_loop.condition), loopBody); |
455 | 0 | (*this)(_loop.body); |
456 | 0 | jump(debugDataOf(_loop.body), post); |
457 | 0 | sealBlock(post); |
458 | 0 | (*this)(_loop.post); |
459 | 0 | jump(debugDataOf(_loop.post), loopBody); |
460 | 0 | sealBlock(loopBody); |
461 | 0 | } |
462 | 0 | else |
463 | 0 | jump(debugDataOf(*_loop.condition), afterLoop); |
464 | 0 | } |
465 | 0 | else |
466 | 0 | { |
467 | 0 | jump(debugDataOf(_loop.pre), loopCondition); |
468 | 0 | auto condition = std::visit(*this, *_loop.condition); |
469 | 0 | conditionalJump(debugDataOf(*_loop.condition), condition, loopBody, afterLoop); |
470 | 0 | sealBlock(loopBody); |
471 | 0 | m_currentBlock = loopBody; |
472 | 0 | (*this)(_loop.body); |
473 | 0 | jump(debugDataOf(_loop.body), post); |
474 | 0 | sealBlock(post); |
475 | 0 | (*this)(_loop.post); |
476 | 0 | jump(debugDataOf(_loop.post), loopCondition); |
477 | 0 | sealBlock(loopCondition); |
478 | 0 | } |
479 | |
|
480 | 0 | sealBlock(afterLoop); |
481 | 0 | m_currentBlock = afterLoop; |
482 | 0 | } |
483 | | |
484 | | void SSAControlFlowGraphBuilder::operator()(Break const& _break) |
485 | 0 | { |
486 | 0 | yulAssert(!m_forLoopInfo.empty()); |
487 | 0 | auto currentBlockDebugData = debugDataOf(currentBlock()); |
488 | 0 | jump(debugDataOf(_break), m_forLoopInfo.top().breakBlock); |
489 | 0 | m_currentBlock = m_graph.makeBlock(currentBlockDebugData); |
490 | 0 | sealBlock(m_currentBlock); |
491 | 0 | } |
492 | | |
493 | | void SSAControlFlowGraphBuilder::operator()(Continue const& _continue) |
494 | 0 | { |
495 | 0 | yulAssert(!m_forLoopInfo.empty()); |
496 | 0 | auto currentBlockDebugData = debugDataOf(currentBlock()); |
497 | 0 | jump(debugDataOf(_continue), m_forLoopInfo.top().continueBlock); |
498 | 0 | m_currentBlock = m_graph.makeBlock(currentBlockDebugData); |
499 | 0 | sealBlock(m_currentBlock); |
500 | 0 | } |
501 | | |
502 | | void SSAControlFlowGraphBuilder::operator()(Leave const& _leaveStatement) |
503 | 0 | { |
504 | 0 | auto currentBlockDebugData = debugDataOf(currentBlock()); |
505 | 0 | currentBlock().exit = SSACFG::BasicBlock::FunctionReturn{ |
506 | 0 | debugDataOf(_leaveStatement), |
507 | 0 | m_graph.returns | ranges::views::transform([&](auto _var) { |
508 | 0 | return readVariable(_var, m_currentBlock); |
509 | 0 | }) | ranges::to<std::vector> |
510 | 0 | }; |
511 | 0 | m_currentBlock = m_graph.makeBlock(currentBlockDebugData); |
512 | 0 | sealBlock(m_currentBlock); |
513 | 0 | } |
514 | | |
515 | | void SSAControlFlowGraphBuilder::registerFunctionDefinition(FunctionDefinition const& _functionDefinition) |
516 | 0 | { |
517 | 0 | yulAssert(m_scope, ""); |
518 | 0 | yulAssert(m_scope->identifiers.count(_functionDefinition.name), ""); |
519 | 0 | auto& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name)); |
520 | 0 | m_graph.functions.emplace_back(function); |
521 | 0 | m_functionDefinitions.emplace_back(&function, &_functionDefinition); |
522 | 0 | } |
523 | | |
524 | | void SSAControlFlowGraphBuilder::operator()(Block const& _block) |
525 | 0 | { |
526 | 0 | ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get()); |
527 | | // gather all function definitions so that they are visible to each other's subgraphs |
528 | 0 | static constexpr auto functionDefinitionFilter = ranges::views::filter( |
529 | 0 | [](auto const& _statement) { return std::holds_alternative<FunctionDefinition>(_statement); } |
530 | 0 | ); |
531 | 0 | for (auto const& statement: _block.statements | functionDefinitionFilter) |
532 | 0 | registerFunctionDefinition(std::get<FunctionDefinition>(statement)); |
533 | | // now visit the rest |
534 | 0 | for (auto const& statement: _block.statements) |
535 | 0 | std::visit(*this, statement); |
536 | 0 | } |
537 | | |
538 | | SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(FunctionCall const& _call) |
539 | 0 | { |
540 | 0 | auto results = visitFunctionCall(_call); |
541 | 0 | yulAssert(results.size() == 1); |
542 | 0 | return results.front(); |
543 | 0 | } |
544 | | |
545 | | SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(Identifier const& _identifier) |
546 | 0 | { |
547 | 0 | auto const& var = lookupVariable(_identifier.name); |
548 | 0 | return readVariable(var, m_currentBlock); |
549 | 0 | } |
550 | | |
551 | | SSACFG::ValueId SSAControlFlowGraphBuilder::operator()(Literal const& _literal) |
552 | 0 | { |
553 | 0 | return m_graph.newLiteral(debugDataOf(currentBlock()), _literal.value.value()); |
554 | 0 | } |
555 | | |
556 | | void SSAControlFlowGraphBuilder::assign(std::vector<std::reference_wrapper<Scope::Variable const>> _variables, Expression const* _expression) |
557 | 0 | { |
558 | 0 | auto rhs = [&]() -> std::vector<SSACFG::ValueId> { |
559 | 0 | if (auto const* functionCall = std::get_if<FunctionCall>(_expression)) |
560 | 0 | return visitFunctionCall(*functionCall); |
561 | 0 | if (_expression) |
562 | 0 | return {std::visit(*this, *_expression)}; |
563 | 0 | return {_variables.size(), zero()}; |
564 | 0 | }(); |
565 | 0 | yulAssert(rhs.size() == _variables.size()); |
566 | | |
567 | 0 | for (auto const& [var, value]: ranges::zip_view(_variables, rhs)) |
568 | 0 | { |
569 | 0 | if (m_keepLiteralAssignments && m_graph.isLiteralValue(value)) |
570 | 0 | { |
571 | 0 | SSACFG::Operation assignment{ |
572 | 0 | .outputs = {m_graph.newVariable(m_currentBlock)}, |
573 | 0 | .kind = SSACFG::LiteralAssignment{}, |
574 | 0 | .inputs = {value} |
575 | 0 | }; |
576 | 0 | currentBlock().operations.emplace_back(assignment); |
577 | 0 | writeVariable(var, m_currentBlock, assignment.outputs.back()); |
578 | 0 | } |
579 | 0 | else |
580 | 0 | writeVariable(var, m_currentBlock, value); |
581 | 0 | } |
582 | |
|
583 | 0 | } |
584 | | |
585 | | std::vector<SSACFG::ValueId> SSAControlFlowGraphBuilder::visitFunctionCall(FunctionCall const& _call) |
586 | 0 | { |
587 | 0 | bool canContinue = true; |
588 | 0 | SSACFG::Operation operation = std::visit(util::GenericVisitor{ |
589 | 0 | [&](BuiltinName const& _builtinName) |
590 | 0 | { |
591 | 0 | auto const& builtin = m_dialect.builtin(_builtinName.handle); |
592 | 0 | SSACFG::Operation result{{}, SSACFG::BuiltinCall{_call.debugData, builtin, _call}, {}}; |
593 | 0 | for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) |
594 | 0 | if (!builtin.literalArgument(idx).has_value()) |
595 | 0 | result.inputs.emplace_back(std::visit(*this, arg)); |
596 | 0 | for (size_t i = 0; i < builtin.numReturns; ++i) |
597 | 0 | result.outputs.emplace_back(m_graph.newVariable(m_currentBlock)); |
598 | 0 | canContinue = builtin.controlFlowSideEffects.canContinue; |
599 | 0 | return result; |
600 | 0 | }, |
601 | 0 | [&](Identifier const& _identifier) |
602 | 0 | { |
603 | 0 | YulName const& functionName = _identifier.name; |
604 | 0 | Scope::Function const& function = lookupFunction(functionName); |
605 | 0 | auto const* definition = findFunctionDefinition(&function); |
606 | 0 | yulAssert(definition); |
607 | 0 | canContinue = m_sideEffects.functionSideEffects().at(definition).canContinue; |
608 | 0 | SSACFG::Operation result{{}, SSACFG::Call{debugDataOf(_call), function, _call, canContinue}, {}}; |
609 | 0 | for (auto const& arg: _call.arguments | ranges::views::reverse) |
610 | 0 | result.inputs.emplace_back(std::visit(*this, arg)); |
611 | 0 | for (size_t i = 0; i < function.numReturns; ++i) |
612 | 0 | result.outputs.emplace_back(m_graph.newVariable(m_currentBlock)); |
613 | 0 | return result; |
614 | 0 | } |
615 | 0 | }, _call.functionName); |
616 | 0 | auto results = operation.outputs; |
617 | 0 | currentBlock().operations.emplace_back(std::move(operation)); |
618 | 0 | if (!canContinue) |
619 | 0 | { |
620 | 0 | currentBlock().exit = SSACFG::BasicBlock::Terminated{}; |
621 | 0 | m_currentBlock = m_graph.makeBlock(debugDataOf(currentBlock())); |
622 | 0 | sealBlock(m_currentBlock); |
623 | 0 | } |
624 | 0 | return results; |
625 | 0 | } |
626 | | |
627 | | SSACFG::ValueId SSAControlFlowGraphBuilder::zero() |
628 | 0 | { |
629 | 0 | return m_graph.newLiteral(debugDataOf(currentBlock()), 0u); |
630 | 0 | } |
631 | | |
632 | | SSACFG::ValueId SSAControlFlowGraphBuilder::readVariable(Scope::Variable const& _variable, SSACFG::BlockId _block) |
633 | 0 | { |
634 | 0 | if (auto const& def = currentDef(_variable, _block)) |
635 | 0 | return *def; |
636 | 0 | return readVariableRecursive(_variable, _block); |
637 | 0 | } |
638 | | |
639 | | SSACFG::ValueId SSAControlFlowGraphBuilder::readVariableRecursive(Scope::Variable const& _variable, SSACFG::BlockId _block) |
640 | 0 | { |
641 | 0 | auto& block = m_graph.block(_block); |
642 | 0 | auto& info = blockInfo(_block); |
643 | |
|
644 | 0 | SSACFG::ValueId val; |
645 | 0 | if (!info.sealed) |
646 | 0 | { |
647 | | // incomplete block |
648 | 0 | val = m_graph.newPhi(_block); |
649 | 0 | block.phis.insert(val); |
650 | 0 | info.incompletePhis.emplace_back(val, _variable); |
651 | 0 | } |
652 | 0 | else if (block.entries.size() == 1) |
653 | | // one predecessor: no phi needed |
654 | 0 | val = readVariable(_variable, *block.entries.begin()); |
655 | 0 | else |
656 | 0 | { |
657 | | // Break potential cycles with operandless phi |
658 | 0 | val = m_graph.newPhi(_block); |
659 | 0 | block.phis.insert(val); |
660 | 0 | writeVariable(_variable, _block, val); |
661 | | // we call tryRemoveTrivialPhi explicitly opposed to what is presented in Algorithm 2, as our implementation |
662 | | // does not call it in addPhiOperands to avoid removing phis in unsealed blocks |
663 | 0 | val = tryRemoveTrivialPhi(addPhiOperands(_variable, val)); |
664 | 0 | } |
665 | 0 | writeVariable(_variable, _block, val); |
666 | 0 | return val; |
667 | 0 | } |
668 | | |
669 | | SSACFG::ValueId SSAControlFlowGraphBuilder::addPhiOperands(Scope::Variable const& _variable, SSACFG::ValueId _phi) |
670 | 0 | { |
671 | 0 | yulAssert(std::holds_alternative<SSACFG::PhiValue>(m_graph.valueInfo(_phi))); |
672 | 0 | auto& phi = std::get<SSACFG::PhiValue>(m_graph.valueInfo(_phi)); |
673 | 0 | for (auto const& pred: m_graph.block(phi.block).entries) |
674 | 0 | phi.arguments.emplace_back(readVariable(_variable, pred)); |
675 | | // we call tryRemoveTrivialPhi explicitly to avoid removing trivial phis in unsealed blocks |
676 | 0 | return _phi; |
677 | 0 | } |
678 | | |
679 | | void SSAControlFlowGraphBuilder::writeVariable(Scope::Variable const& _variable, SSACFG::BlockId _block, SSACFG::ValueId _value) |
680 | 0 | { |
681 | 0 | currentDef(_variable, _block) = _value; |
682 | 0 | } |
683 | | |
684 | | Scope::Function const& SSAControlFlowGraphBuilder::lookupFunction(YulName _name) const |
685 | 0 | { |
686 | 0 | Scope::Function const* function = nullptr; |
687 | 0 | yulAssert(m_scope->lookup(_name, util::GenericVisitor{ |
688 | 0 | [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, |
689 | 0 | [&](Scope::Function& _function) { function = &_function; } |
690 | 0 | }), "Function name not found."); |
691 | 0 | yulAssert(function, ""); |
692 | 0 | return *function; |
693 | 0 | } |
694 | | |
695 | | Scope::Variable const& SSAControlFlowGraphBuilder::lookupVariable(YulName _name) const |
696 | 0 | { |
697 | 0 | yulAssert(m_scope, ""); |
698 | 0 | Scope::Variable const* var = nullptr; |
699 | 0 | if (m_scope->lookup(_name, util::GenericVisitor{ |
700 | 0 | [&](Scope::Variable const& _var) { var = &_var; }, |
701 | 0 | [](Scope::Function const&) |
702 | 0 | { |
703 | 0 | yulAssert(false, "Function not removed during desugaring."); |
704 | 0 | } |
705 | 0 | })) |
706 | 0 | { |
707 | 0 | yulAssert(var); |
708 | 0 | return *var; |
709 | 0 | }; |
710 | 0 | yulAssert(false, "External identifier access unimplemented."); |
711 | 0 | } |
712 | | |
713 | | void SSAControlFlowGraphBuilder::sealBlock(SSACFG::BlockId _block) |
714 | 0 | { |
715 | | // this method deviates from Algorithm 4 in the reference paper, |
716 | | // as it would lead to tryRemoveTrivialPhi being called on unsealed blocks |
717 | 0 | auto& info = blockInfo(_block); |
718 | 0 | yulAssert(!info.sealed, "Trying to seal already sealed block."); |
719 | 0 | for (auto&& [phi, variable] : info.incompletePhis) |
720 | 0 | addPhiOperands(variable, phi); |
721 | 0 | info.sealed = true; |
722 | 0 | for (auto& [phi, _]: info.incompletePhis) |
723 | 0 | phi = tryRemoveTrivialPhi(phi); |
724 | 0 | } |
725 | | |
726 | | |
727 | | void SSAControlFlowGraphBuilder::conditionalJump( |
728 | | langutil::DebugData::ConstPtr _debugData, |
729 | | SSACFG::ValueId _condition, |
730 | | SSACFG::BlockId _nonZero, |
731 | | SSACFG::BlockId _zero |
732 | | ) |
733 | 0 | { |
734 | 0 | currentBlock().exit = SSACFG::BasicBlock::ConditionalJump{ |
735 | 0 | std::move(_debugData), |
736 | 0 | _condition, |
737 | 0 | _nonZero, |
738 | 0 | _zero |
739 | 0 | }; |
740 | 0 | m_graph.block(_nonZero).entries.insert(m_currentBlock); |
741 | 0 | m_graph.block(_zero).entries.insert(m_currentBlock); |
742 | 0 | m_currentBlock = {}; |
743 | 0 | } |
744 | | |
745 | | void SSAControlFlowGraphBuilder::jump( |
746 | | langutil::DebugData::ConstPtr _debugData, |
747 | | SSACFG::BlockId _target |
748 | | ) |
749 | 0 | { |
750 | 0 | currentBlock().exit = SSACFG::BasicBlock::Jump{std::move(_debugData), _target}; |
751 | 0 | yulAssert(!blockInfo(_target).sealed); |
752 | 0 | m_graph.block(_target).entries.insert(m_currentBlock); |
753 | 0 | m_currentBlock = _target; |
754 | 0 | } |
755 | | |
756 | | void SSAControlFlowGraphBuilder::tableJump( |
757 | | langutil::DebugData::ConstPtr _debugData, |
758 | | SSACFG::ValueId _value, |
759 | | std::map<u256, SSACFG::BlockId> _cases, |
760 | | SSACFG::BlockId _defaultCase) |
761 | 0 | { |
762 | 0 | for (auto caseBlock: _cases | ranges::views::values) |
763 | 0 | { |
764 | 0 | yulAssert(!blockInfo(caseBlock).sealed); |
765 | 0 | m_graph.block(caseBlock).entries.insert(m_currentBlock); |
766 | 0 | } |
767 | 0 | yulAssert(!blockInfo(_defaultCase).sealed); |
768 | 0 | m_graph.block(_defaultCase).entries.insert(m_currentBlock); |
769 | 0 | currentBlock().exit = SSACFG::BasicBlock::JumpTable{std::move(_debugData), _value, std::move(_cases), _defaultCase}; |
770 | 0 | m_currentBlock = {}; |
771 | 0 | } |
772 | | |
773 | | FunctionDefinition const* SSAControlFlowGraphBuilder::findFunctionDefinition(Scope::Function const* _function) const |
774 | 0 | { |
775 | 0 | auto it = std::find_if( |
776 | 0 | m_functionDefinitions.begin(), |
777 | 0 | m_functionDefinitions.end(), |
778 | 0 | [&_function](auto const& _entry) { return std::get<0>(_entry) == _function; } |
779 | 0 | ); |
780 | 0 | if (it != m_functionDefinitions.end()) |
781 | 0 | return std::get<1>(*it); |
782 | 0 | return nullptr; |
783 | 0 | } |
784 | | |
785 | | } |