/src/solidity/libyul/backends/evm/ssa/SSACFGBuilder.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/ssa/SSACFGBuilder.h> |
23 | | |
24 | | #include <libyul/backends/evm/ssa/ControlFlowGraphs.h> |
25 | | |
26 | | #include <libyul/AST.h> |
27 | | #include <libyul/ControlFlowSideEffectsCollector.h> |
28 | | #include <libyul/optimiser/Metrics.h> |
29 | | #include <libyul/Exceptions.h> |
30 | | #include <libyul/Utilities.h> |
31 | | |
32 | | #include <libsolutil/Algorithms.h> |
33 | | #include <libsolutil/StringUtils.h> |
34 | | #include <libsolutil/Visitor.h> |
35 | | |
36 | | #include <range/v3/algorithm/reverse.hpp> |
37 | | #include <range/v3/range/conversion.hpp> |
38 | | #include <range/v3/view/drop_last.hpp> |
39 | | #include <range/v3/view/enumerate.hpp> |
40 | | #include <range/v3/view/filter.hpp> |
41 | | #include <range/v3/view/reverse.hpp> |
42 | | #include <range/v3/view/transform.hpp> |
43 | | #include <range/v3/view/zip.hpp> |
44 | | |
45 | | using namespace solidity; |
46 | | using namespace solidity::yul; |
47 | | using namespace solidity::yul::ssa; |
48 | | |
49 | | SSACFGBuilder::SSACFGBuilder( |
50 | | ControlFlowGraphs& _controlFlow, |
51 | | SSACFG& _graph, |
52 | | AsmAnalysisInfo const& _analysisInfo, |
53 | | ControlFlowSideEffectsCollector const& _sideEffects, |
54 | | EVMDialect const& _dialect, |
55 | | bool _generateDebugInfo, |
56 | | FunctionRegistry& _functionRegistry |
57 | | ): |
58 | 0 | m_controlFlow(_controlFlow), |
59 | 0 | m_graph(_graph), |
60 | 0 | m_info(_analysisInfo), |
61 | 0 | m_sideEffects(_sideEffects), |
62 | 0 | m_dialect(_dialect), |
63 | 0 | m_generateDebugInfo(_generateDebugInfo), |
64 | 0 | m_functionRegistry(_functionRegistry), |
65 | 0 | m_memoryGuardHandle([&] { |
66 | 0 | auto const memoryGuardHandle = m_dialect.findBuiltin("memoryguard"); |
67 | 0 | yulAssert(memoryGuardHandle.has_value(), "We only support EVM dialects that contain memoryguard"); |
68 | 0 | return *memoryGuardHandle; |
69 | 0 | }()) |
70 | 0 | { |
71 | 0 | } |
72 | | |
73 | | std::unique_ptr<ControlFlowGraphs> SSACFGBuilder::build( |
74 | | AsmAnalysisInfo const& _analysisInfo, |
75 | | EVMDialect const& _dialect, |
76 | | Block const& _block, |
77 | | bool _generateDebugInfo |
78 | | ) |
79 | 0 | { |
80 | 0 | ControlFlowSideEffectsCollector sideEffects(_dialect, _block); |
81 | |
|
82 | 0 | auto controlFlowGraphs = std::make_unique<ControlFlowGraphs>(); |
83 | 0 | controlFlowGraphs->functionGraphs.emplace_back(std::make_unique<SSACFG>( |
84 | 0 | _dialect, |
85 | 0 | _generateDebugInfo ? std::make_unique<SSACFGDebugInfo>() : nullptr, |
86 | 0 | 2 * CodeSize::codeSize(_block) // empirically there are roughly 2x the instructions |
87 | 0 | )); |
88 | 0 | SSACFG& mainGraph = *controlFlowGraphs->functionGraphs.back(); |
89 | 0 | FunctionRegistry functionRegistry; |
90 | 0 | SSACFGBuilder builder(*controlFlowGraphs, mainGraph, _analysisInfo, sideEffects, _dialect, _generateDebugInfo, functionRegistry); |
91 | 0 | builder.m_currentBlock = mainGraph.makeBlock(debugDataOf(_block)); |
92 | 0 | builder.sealBlock(builder.m_currentBlock); |
93 | 0 | builder(_block); |
94 | 0 | if (!builder.blockInfo(builder.m_currentBlock).sealed) |
95 | 0 | builder.sealBlock(builder.m_currentBlock); |
96 | 0 | mainGraph.block(builder.m_currentBlock).exit = SSACFG::BasicBlock::MainExit{}; |
97 | 0 | return controlFlowGraphs; |
98 | 0 | } |
99 | | |
100 | | |
101 | | void SSACFGBuilder::buildFunctionGraph( |
102 | | Scope::Function const* _function, |
103 | | FunctionDefinition const* _functionDefinition |
104 | | ) |
105 | 0 | { |
106 | | // The graph slot and the FunctionGraphID have already been allocated in |
107 | | // `registerFunctionDefinition`; this call builds the body into that slot. |
108 | 0 | auto const regIt = m_functionRegistry.find(_function); |
109 | 0 | yulAssert(regIt != m_functionRegistry.end(), "Function graph must be registered before building."); |
110 | 0 | auto& cfg = *m_controlFlow.functionGraphs[regIt->second.id]; |
111 | |
|
112 | 0 | yulAssert(m_info.scopes.at(&_functionDefinition->body), ""); |
113 | 0 | Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(_functionDefinition).get()).get(); |
114 | 0 | yulAssert(virtualFunctionScope, ""); |
115 | |
|
116 | 0 | cfg.entry = cfg.makeBlock(debugDataOf(_functionDefinition->body)); |
117 | 0 | auto argumentBindings = _functionDefinition->parameters | ranges::views::transform([&](auto const& _param) { |
118 | 0 | auto const& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)); |
119 | | // Note: cannot use std::make_tuple since it unwraps reference wrappers. |
120 | 0 | return std::tuple{std::cref(var), cfg.newFunctionArgument()}; |
121 | 0 | }) | ranges::to<std::vector>; |
122 | 0 | auto returnVars = _functionDefinition->returnVariables | ranges::views::transform([&](auto const& _param) { |
123 | 0 | return std::cref(std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name))); |
124 | 0 | }) | ranges::to<std::vector<std::reference_wrapper<Scope::Variable const>>>(); |
125 | |
|
126 | 0 | if (cfg.debugInfo) |
127 | 0 | cfg.debugInfo->graphDebugData = _functionDefinition->debugData; |
128 | 0 | cfg.canContinue = m_sideEffects.functionSideEffects().at(_functionDefinition).canContinue; |
129 | 0 | cfg.arguments = argumentBindings |
130 | 0 | | ranges::views::transform([](auto const& _binding) { return std::get<1>(_binding); }) |
131 | 0 | | ranges::to<std::vector>; |
132 | |
|
133 | 0 | SSACFGBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect, m_generateDebugInfo, m_functionRegistry); |
134 | 0 | builder.m_currentBlock = cfg.entry; |
135 | 0 | builder.m_currentReturnVars = returnVars; |
136 | 0 | for (auto&& [var, varId]: argumentBindings) |
137 | 0 | builder.currentDef(var, cfg.entry) = varId; |
138 | 0 | for (auto const& var: returnVars) |
139 | 0 | builder.currentDef(var.get(), cfg.entry) = builder.zero(); |
140 | 0 | builder.sealBlock(cfg.entry); |
141 | 0 | builder(_functionDefinition->body); |
142 | 0 | cfg.exits.insert(builder.m_currentBlock); |
143 | | // Artificial explicit function exit (`leave`) at the end of the body. |
144 | 0 | builder(Leave{debugDataOf(*_functionDefinition)}); |
145 | 0 | } |
146 | | |
147 | | void SSACFGBuilder::operator()(ExpressionStatement const& _expressionStatement) |
148 | 0 | { |
149 | 0 | auto const* functionCall = std::get_if<FunctionCall>(&_expressionStatement.expression); |
150 | 0 | yulAssert(functionCall); |
151 | 0 | visitFunctionCall(*functionCall); |
152 | 0 | } |
153 | | |
154 | | void SSACFGBuilder::operator()(Assignment const& _assignment) |
155 | 0 | { |
156 | 0 | assign( |
157 | 0 | _assignment.variableNames | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>, |
158 | 0 | _assignment.value.get() |
159 | 0 | ); |
160 | 0 | } |
161 | | |
162 | | void SSACFGBuilder::operator()(VariableDeclaration const& _variableDeclaration) |
163 | 0 | { |
164 | 0 | assign( |
165 | 0 | _variableDeclaration.variables | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>, |
166 | 0 | _variableDeclaration.value.get() |
167 | 0 | ); |
168 | 0 | } |
169 | | |
170 | | void SSACFGBuilder::operator()(FunctionDefinition const& _functionDefinition) |
171 | 0 | { |
172 | 0 | Scope::Function const& function = lookupFunction(_functionDefinition.name); |
173 | 0 | buildFunctionGraph(&function, &_functionDefinition); |
174 | 0 | } |
175 | | |
176 | | void SSACFGBuilder::operator()(If const& _if) |
177 | 0 | { |
178 | 0 | auto const condition = std::visit(*this, *_if.condition); |
179 | 0 | auto const ifBranch = m_graph.makeBlock(debugDataOf(_if.body)); |
180 | 0 | auto const afterIf = m_graph.makeBlock(currentBlockDebugData()); |
181 | 0 | conditionalJump( |
182 | 0 | debugDataOf(_if), |
183 | 0 | condition, |
184 | 0 | ifBranch, |
185 | 0 | afterIf |
186 | 0 | ); |
187 | 0 | sealBlock(ifBranch); |
188 | 0 | m_currentBlock = ifBranch; |
189 | 0 | (*this)(_if.body); |
190 | 0 | jump(debugDataOf(_if.body), afterIf); |
191 | 0 | sealBlock(afterIf); |
192 | 0 | } |
193 | | |
194 | | void SSACFGBuilder::operator()(Switch const& _switch) |
195 | 0 | { |
196 | 0 | auto expression = std::visit(*this, *_switch.expression); |
197 | |
|
198 | 0 | std::optional<BuiltinHandle> equalityBuiltinHandle = m_dialect.equalityFunctionHandle(); |
199 | 0 | yulAssert(equalityBuiltinHandle); |
200 | |
|
201 | 0 | auto const& equalityBuiltin = m_dialect.builtin(*equalityBuiltinHandle); |
202 | 0 | auto makeValueCompare = [&](Case const& _case) { |
203 | 0 | return m_graph.makeBuiltinCallWithProjections( |
204 | 0 | m_currentBlock, |
205 | 0 | SSACFG::BuiltinCall{*equalityBuiltinHandle, {}}, |
206 | 0 | {expression, m_graph.newLiteral(debugDataOf(_case), _case.value->value.value())}, |
207 | 0 | static_cast<InstructionStore::NumReturnsSizeType>(equalityBuiltin.numReturns), |
208 | 0 | debugDataOf(_case) |
209 | 0 | ); |
210 | 0 | }; |
211 | |
|
212 | 0 | auto afterSwitch = m_graph.makeBlock(currentBlockDebugData()); |
213 | 0 | yulAssert(!_switch.cases.empty(), ""); |
214 | 0 | for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1)) |
215 | 0 | { |
216 | 0 | yulAssert(switchCase.value, ""); |
217 | 0 | auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
218 | 0 | auto elseBranch = m_graph.makeBlock(debugDataOf(_switch)); |
219 | |
|
220 | 0 | conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch); |
221 | 0 | sealBlock(caseBranch); |
222 | 0 | sealBlock(elseBranch); |
223 | 0 | m_currentBlock = caseBranch; |
224 | 0 | (*this)(switchCase.body); |
225 | 0 | jump(debugDataOf(switchCase.body), afterSwitch); |
226 | 0 | m_currentBlock = elseBranch; |
227 | 0 | } |
228 | 0 | Case const& switchCase = _switch.cases.back(); |
229 | 0 | if (switchCase.value) |
230 | 0 | { |
231 | 0 | auto caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body)); |
232 | 0 | conditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch); |
233 | 0 | sealBlock(caseBranch); |
234 | 0 | m_currentBlock = caseBranch; |
235 | 0 | } |
236 | 0 | (*this)(switchCase.body); |
237 | 0 | jump(debugDataOf(switchCase.body), afterSwitch); |
238 | 0 | sealBlock(afterSwitch); |
239 | 0 | } |
240 | | void SSACFGBuilder::operator()(ForLoop const& _loop) |
241 | 0 | { |
242 | 0 | ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get()); |
243 | 0 | (*this)(_loop.pre); |
244 | 0 | auto preLoopDebugData = currentBlockDebugData(); |
245 | |
|
246 | 0 | SSACFG::BlockId const loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition)); |
247 | 0 | SSACFG::BlockId const loopBody = m_graph.makeBlock(debugDataOf(_loop.body)); |
248 | 0 | SSACFG::BlockId const post = m_graph.makeBlock(debugDataOf(_loop.post)); |
249 | 0 | SSACFG::BlockId const afterLoop = m_graph.makeBlock(preLoopDebugData); |
250 | |
|
251 | 0 | class ForLoopInfoScope { |
252 | 0 | public: |
253 | 0 | ForLoopInfoScope(std::stack<ForLoopInfo>& _info, SSACFG::BlockId _breakBlock, SSACFG::BlockId _continueBlock): m_info(_info) |
254 | 0 | { |
255 | 0 | m_info.push(ForLoopInfo{_breakBlock, _continueBlock}); |
256 | 0 | } |
257 | 0 | ~ForLoopInfoScope() { |
258 | 0 | m_info.pop(); |
259 | 0 | } |
260 | 0 | private: |
261 | 0 | std::stack<ForLoopInfo>& m_info; |
262 | 0 | } forLoopInfoScope(m_forLoopInfo, afterLoop, post); |
263 | |
|
264 | 0 | jump(debugDataOf(_loop.pre), loopCondition); |
265 | 0 | auto condition = std::visit(*this, *_loop.condition); |
266 | 0 | conditionalJump(debugDataOf(*_loop.condition), condition, loopBody, afterLoop); |
267 | 0 | sealBlock(loopBody); |
268 | 0 | m_currentBlock = loopBody; |
269 | 0 | (*this)(_loop.body); |
270 | 0 | jump(debugDataOf(_loop.body), post); |
271 | 0 | sealBlock(post); |
272 | 0 | (*this)(_loop.post); |
273 | 0 | jump(debugDataOf(_loop.post), loopCondition); |
274 | 0 | sealBlock(loopCondition); |
275 | |
|
276 | 0 | sealBlock(afterLoop); |
277 | 0 | m_currentBlock = afterLoop; |
278 | 0 | } |
279 | | |
280 | | void SSACFGBuilder::operator()(Break const& _break) |
281 | 0 | { |
282 | 0 | yulAssert(!m_forLoopInfo.empty()); |
283 | 0 | auto savedBlockDebugData = currentBlockDebugData(); |
284 | 0 | jump(debugDataOf(_break), m_forLoopInfo.top().breakBlock); |
285 | 0 | m_currentBlock = m_graph.makeBlock(savedBlockDebugData); |
286 | 0 | sealBlock(m_currentBlock); |
287 | 0 | } |
288 | | |
289 | | void SSACFGBuilder::operator()(Continue const& _continue) |
290 | 0 | { |
291 | 0 | yulAssert(!m_forLoopInfo.empty()); |
292 | 0 | auto const savedBlockDebugData = currentBlockDebugData(); |
293 | 0 | jump(debugDataOf(_continue), m_forLoopInfo.top().continueBlock); |
294 | 0 | m_currentBlock = m_graph.makeBlock(savedBlockDebugData); |
295 | 0 | sealBlock(m_currentBlock); |
296 | 0 | } |
297 | | |
298 | | void SSACFGBuilder::operator()(Leave const& _leaveStatement) |
299 | 0 | { |
300 | 0 | auto const savedBlockDebugData = currentBlockDebugData(); |
301 | 0 | if (m_graph.debugInfo) |
302 | 0 | m_graph.debugInfo->setExitDebugData(m_currentBlock, debugDataOf(_leaveStatement)); |
303 | 0 | currentBlock().exit = SSACFG::BasicBlock::FunctionReturn{ |
304 | 0 | m_currentReturnVars | ranges::views::transform([&](auto _var) { |
305 | 0 | return readVariable(_var, m_currentBlock); |
306 | 0 | }) | ranges::to<std::vector> |
307 | 0 | }; |
308 | 0 | m_currentBlock = m_graph.makeBlock(savedBlockDebugData); |
309 | 0 | sealBlock(m_currentBlock); |
310 | 0 | } |
311 | | |
312 | | void SSACFGBuilder::registerFunctionDefinition(FunctionDefinition const& _functionDefinition) |
313 | 0 | { |
314 | 0 | yulAssert(m_scope, ""); |
315 | 0 | yulAssert(m_scope->identifiers.count(_functionDefinition.name), ""); |
316 | 0 | auto& function = std::get<Scope::Function>(m_scope->identifiers.at(_functionDefinition.name)); |
317 | | |
318 | | // Allocate the graph slot up front so sibling functions (and mutually-recursive pairs) can |
319 | | // reference this function by its FunctionGraphID when their bodies are built later. |
320 | 0 | m_controlFlow.functionGraphs.emplace_back(std::make_unique<SSACFG>( |
321 | 0 | m_dialect, |
322 | 0 | m_generateDebugInfo ? std::make_unique<SSACFGDebugInfo>() : nullptr, |
323 | 0 | 2 * CodeSize::codeSize(_functionDefinition.body) // empirically there are roughly 2x the instructions |
324 | 0 | )); |
325 | 0 | auto const graphID = static_cast<FunctionGraphID>(m_controlFlow.functionGraphs.size() - 1); |
326 | 0 | auto& cfg = *m_controlFlow.functionGraphs.back(); |
327 | 0 | cfg.name = function.name.str(); |
328 | 0 | cfg.numReturns = _functionDefinition.returnVariables.size(); |
329 | 0 | m_functionRegistry[&function] = {graphID, &_functionDefinition}; |
330 | 0 | } |
331 | | |
332 | | void SSACFGBuilder::operator()(Block const& _block) |
333 | 0 | { |
334 | 0 | ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get()); |
335 | | // gather all function definitions so that they are visible to each other's subgraphs |
336 | 0 | static constexpr auto functionDefinitionFilter = ranges::views::filter( |
337 | 0 | [](auto const& _statement) { return std::holds_alternative<FunctionDefinition>(_statement); } |
338 | 0 | ); |
339 | 0 | for (auto const& statement: _block.statements | functionDefinitionFilter) |
340 | 0 | registerFunctionDefinition(std::get<FunctionDefinition>(statement)); |
341 | | // now visit the rest |
342 | 0 | for (auto const& statement: _block.statements) |
343 | 0 | std::visit(*this, statement); |
344 | 0 | } |
345 | | |
346 | | InstId SSACFGBuilder::operator()(FunctionCall const& _call) |
347 | 0 | { |
348 | | // Single-output expression context: the call's InstId is itself the value handle. |
349 | 0 | return visitFunctionCall(_call); |
350 | 0 | } |
351 | | |
352 | | InstId SSACFGBuilder::operator()(Identifier const& _identifier) |
353 | 0 | { |
354 | 0 | auto const& var = lookupVariable(_identifier.name); |
355 | 0 | return readVariable(var, m_currentBlock); |
356 | 0 | } |
357 | | |
358 | | InstId SSACFGBuilder::operator()(Literal const& _literal) |
359 | 0 | { |
360 | 0 | return m_graph.newLiteral(currentBlockDebugData(), _literal.value.value()); |
361 | 0 | } |
362 | | |
363 | | void SSACFGBuilder::assign(std::vector<std::reference_wrapper<Scope::Variable const>> _variables, Expression const* _expression) |
364 | 0 | { |
365 | 0 | if (auto const* functionCall = std::get_if<FunctionCall>(_expression)) |
366 | 0 | { |
367 | 0 | InstId const callId = visitFunctionCall(*functionCall); |
368 | 0 | if (m_graph.isUnreachable(callId)) |
369 | 0 | { |
370 | | // The callee did not continue: post-call block is unreachable. Bind variables to unreachable. |
371 | 0 | for (auto const& var: _variables) |
372 | 0 | writeVariable(var, m_currentBlock, callId); |
373 | 0 | } |
374 | 0 | else |
375 | 0 | { |
376 | 0 | yulAssert(m_graph.numReturnsOf(callId) == _variables.size()); |
377 | 0 | auto const outputs = m_graph.outputsOf(callId); |
378 | 0 | yulAssert(outputs.size() == _variables.size()); |
379 | 0 | for (auto const& [var, output]: ranges::views::zip(_variables, outputs)) |
380 | 0 | writeVariable(var, m_currentBlock, output); |
381 | 0 | } |
382 | 0 | return; |
383 | 0 | } |
384 | 0 | auto const rhs = _expression ? |
385 | 0 | std::vector{std::visit(*this, *_expression)} : |
386 | 0 | std::vector(_variables.size(), zero()); |
387 | 0 | yulAssert(rhs.size() == _variables.size()); |
388 | 0 | for (auto const& [var, value]: ranges::zip_view(_variables, rhs)) |
389 | 0 | writeVariable(var, m_currentBlock, value); |
390 | 0 | } |
391 | | |
392 | | InstId SSACFGBuilder::visitFunctionCall(FunctionCall const& _call) |
393 | 0 | { |
394 | 0 | bool canContinue = true; |
395 | 0 | InstId const id = std::visit(solidity::util::GenericVisitor{ |
396 | 0 | [&](BuiltinName const& _builtinName) -> InstId |
397 | 0 | { |
398 | | // memoryguard(N) is represented as a dedicated MemoryGuard Inst; the boundary value lives in the |
399 | | // corresponding ControlFlowGraphs instance |
400 | 0 | if (_builtinName.handle == m_memoryGuardHandle) |
401 | 0 | { |
402 | 0 | yulAssert(_call.arguments.size() == 1); |
403 | 0 | Literal const* literal = std::get_if<Literal>(&_call.arguments.front()); |
404 | 0 | yulAssert(literal && literal->kind == LiteralKind::Number); |
405 | 0 | u256 const value = literal->value.value(); |
406 | 0 | if (m_controlFlow.memoryGuard) |
407 | 0 | yulAssert( |
408 | 0 | *m_controlFlow.memoryGuard == value, |
409 | 0 | "memoryguard: inconsistent literal across subobject" |
410 | 0 | ); |
411 | 0 | else |
412 | 0 | m_controlFlow.memoryGuard = value; |
413 | 0 | canContinue = m_dialect.builtin(m_memoryGuardHandle).controlFlowSideEffects.canContinue; |
414 | 0 | return m_graph.makeMemoryGuard(m_currentBlock, debugDataOf(_call)); |
415 | 0 | } |
416 | | |
417 | 0 | auto const& builtin = m_dialect.builtin(_builtinName.handle); |
418 | 0 | yulAssert(_call.arguments.size() == builtin.numParameters); |
419 | 0 | std::vector<Literal> literalArguments; |
420 | 0 | for (auto&& [kind, arg]: ranges::views::zip(builtin.literalArguments, _call.arguments)) |
421 | 0 | if (kind.has_value()) |
422 | 0 | { |
423 | 0 | yulAssert(std::holds_alternative<Literal>(arg)); |
424 | 0 | literalArguments.emplace_back(std::get<Literal>(arg)); |
425 | 0 | } |
426 | | // Arguments must be evaluated from right to left, according to Yul specification |
427 | 0 | std::vector<InstId> inputs; |
428 | 0 | for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) |
429 | 0 | if (!builtin.literalArgument(idx).has_value()) |
430 | 0 | inputs.emplace_back(std::visit(*this, arg)); |
431 | | // But we want to store them in the original order |
432 | 0 | ranges::reverse(inputs); |
433 | 0 | canContinue = builtin.controlFlowSideEffects.canContinue; |
434 | 0 | return m_graph.makeBuiltinCallWithProjections( |
435 | 0 | m_currentBlock, |
436 | 0 | SSACFG::BuiltinCall{_builtinName.handle, std::move(literalArguments)}, |
437 | 0 | std::move(inputs), |
438 | 0 | static_cast<InstructionStore::NumReturnsSizeType>(builtin.numReturns), |
439 | 0 | debugDataOf(_call) |
440 | 0 | ); |
441 | 0 | }, |
442 | 0 | [&](Identifier const& _identifier) -> InstId |
443 | 0 | { |
444 | 0 | YulName const& functionName = _identifier.name; |
445 | 0 | Scope::Function const& function = lookupFunction(functionName); |
446 | 0 | auto const calleeIt = m_functionRegistry.find(&function); |
447 | 0 | yulAssert(calleeIt != m_functionRegistry.end(), "Called function has no registered graph id."); |
448 | 0 | canContinue = m_sideEffects.functionSideEffects().at(calleeIt->second.definition).canContinue; |
449 | | // Arguments must be evaluated from right to left, according to Yul specification |
450 | 0 | std::vector<InstId> inputs; |
451 | 0 | for (auto const& arg: _call.arguments | ranges::views::reverse) |
452 | 0 | inputs.emplace_back(std::visit(*this, arg)); |
453 | | // But we want to store them in the original order |
454 | 0 | ranges::reverse(inputs); |
455 | 0 | return m_graph.makeCallWithProjections( |
456 | 0 | m_currentBlock, |
457 | 0 | SSACFG::Call{calleeIt->second.id, canContinue, function.numReturns}, |
458 | 0 | std::move(inputs), |
459 | 0 | static_cast<InstructionStore::NumReturnsSizeType>(function.numReturns), |
460 | 0 | debugDataOf(_call) |
461 | 0 | ); |
462 | 0 | } |
463 | 0 | }, _call.functionName); |
464 | 0 | if (canContinue) |
465 | 0 | return id; |
466 | | // `id` lives in the now-Terminated call block and won't dominate the successor, |
467 | | // so terminate, open a fresh successor and signal non-continuation via unreachableValue. |
468 | 0 | currentBlock().exit = SSACFG::BasicBlock::Terminated{}; |
469 | 0 | m_currentBlock = m_graph.makeBlock(currentBlockDebugData()); |
470 | 0 | sealBlock(m_currentBlock); |
471 | 0 | return m_graph.unreachableValue(); |
472 | 0 | } |
473 | | |
474 | | InstId SSACFGBuilder::zero() |
475 | 0 | { |
476 | 0 | return m_graph.newLiteral(currentBlockDebugData(), 0u); |
477 | 0 | } |
478 | | |
479 | | InstId SSACFGBuilder::readVariable(Scope::Variable const& _variable, SSACFG::BlockId _block) |
480 | 0 | { |
481 | 0 | auto const& def = currentDef(_variable, _block); |
482 | 0 | if (def.hasValue()) |
483 | 0 | return def; |
484 | 0 | return readVariableRecursive(_variable, _block); |
485 | 0 | } |
486 | | |
487 | | InstId SSACFGBuilder::readVariableRecursive(Scope::Variable const& _variable, SSACFG::BlockId _block) |
488 | 0 | { |
489 | 0 | auto& block = m_graph.block(_block); |
490 | 0 | auto& info = blockInfo(_block); |
491 | |
|
492 | 0 | InstId val; |
493 | 0 | if (!info.sealed) |
494 | 0 | { |
495 | | // incomplete block: create a phi and defer upsilon emission until the block is sealed |
496 | 0 | val = m_graph.newPhi(_block); |
497 | 0 | info.incompletePhis.emplace_back(val, _variable); |
498 | 0 | } |
499 | 0 | else if (block.entries.size() == 1) |
500 | | // one predecessor: no phi needed |
501 | 0 | val = readVariable(_variable, *block.entries.begin()); |
502 | 0 | else |
503 | 0 | { |
504 | | // Break potential cycles with an argument-less phi; emit upsilons for all predecessors. |
505 | 0 | val = m_graph.newPhi(_block); |
506 | 0 | writeVariable(_variable, _block, val); |
507 | 0 | addPhiOperands(_variable, val); |
508 | 0 | } |
509 | 0 | writeVariable(_variable, _block, val); |
510 | 0 | return val; |
511 | 0 | } |
512 | | |
513 | | void SSACFGBuilder::addPhiOperands(Scope::Variable const& _variable, InstId _phi) |
514 | 0 | { |
515 | 0 | SSACFG::BlockId const phiBlock = m_graph.inst(_phi).block; |
516 | 0 | for (auto const& pred: m_graph.block(phiBlock).entries) |
517 | 0 | { |
518 | 0 | auto const val = readVariable(_variable, pred); |
519 | 0 | emitUpsilon(pred, val, _phi); |
520 | 0 | } |
521 | 0 | } |
522 | | |
523 | | void SSACFGBuilder::emitUpsilon(SSACFG::BlockId _block, InstId _value, InstId _phi) |
524 | 0 | { |
525 | 0 | yulAssert(m_graph.isPhi(_phi)); |
526 | 0 | m_graph.emitUpsilon(_block, _value, _phi); |
527 | 0 | } |
528 | | |
529 | | void SSACFGBuilder::writeVariable(Scope::Variable const& _variable, SSACFG::BlockId _block, InstId _value) |
530 | 0 | { |
531 | 0 | currentDef(_variable, _block) = _value; |
532 | 0 | } |
533 | | |
534 | | Scope::Function const& SSACFGBuilder::lookupFunction(YulName _name) const |
535 | 0 | { |
536 | 0 | Scope::Function const* function = nullptr; |
537 | 0 | yulAssert(m_scope->lookup(_name, solidity::util::GenericVisitor{ |
538 | 0 | [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, |
539 | 0 | [&](Scope::Function& _function) { function = &_function; } |
540 | 0 | }), "Function name not found."); |
541 | 0 | yulAssert(function, ""); |
542 | 0 | return *function; |
543 | 0 | } |
544 | | |
545 | | Scope::Variable const& SSACFGBuilder::lookupVariable(YulName _name) const |
546 | 0 | { |
547 | 0 | yulAssert(m_scope, ""); |
548 | 0 | Scope::Variable const* var = nullptr; |
549 | 0 | if (m_scope->lookup(_name, solidity::util::GenericVisitor{ |
550 | 0 | [&](Scope::Variable const& _var) { var = &_var; }, |
551 | 0 | [](Scope::Function const&) |
552 | 0 | { |
553 | 0 | yulAssert(false, "Function not removed during desugaring."); |
554 | 0 | } |
555 | 0 | })) |
556 | 0 | { |
557 | 0 | yulAssert(var); |
558 | 0 | return *var; |
559 | 0 | }; |
560 | 0 | yulAssert(false, "External identifier access unimplemented."); |
561 | 0 | } |
562 | | |
563 | | void SSACFGBuilder::sealBlock(SSACFG::BlockId _block) |
564 | 0 | { |
565 | 0 | auto& info = blockInfo(_block); |
566 | 0 | yulAssert(!info.sealed, "Trying to seal already sealed block."); |
567 | | // emit upsilons for all incomplete phis before marking the block as sealed |
568 | 0 | for (auto&& [phi, variable] : info.incompletePhis) |
569 | 0 | addPhiOperands(variable, phi); |
570 | 0 | info.sealed = true; |
571 | 0 | } |
572 | | |
573 | | |
574 | | void SSACFGBuilder::conditionalJump( |
575 | | langutil::DebugData::ConstPtr _debugData, |
576 | | InstId _condition, |
577 | | SSACFG::BlockId _nonZero, |
578 | | SSACFG::BlockId _zero |
579 | | ) |
580 | 0 | { |
581 | 0 | if (m_graph.debugInfo) |
582 | 0 | m_graph.debugInfo->setExitDebugData(m_currentBlock, std::move(_debugData)); |
583 | 0 | currentBlock().exit = SSACFG::BasicBlock::ConditionalJump{ |
584 | 0 | _condition, |
585 | 0 | _nonZero, |
586 | 0 | _zero |
587 | 0 | }; |
588 | 0 | m_graph.block(_nonZero).entries.push_back(m_currentBlock); |
589 | 0 | m_graph.block(_zero).entries.push_back(m_currentBlock); |
590 | 0 | m_currentBlock = {}; |
591 | 0 | } |
592 | | |
593 | | void SSACFGBuilder::jump( |
594 | | langutil::DebugData::ConstPtr _debugData, |
595 | | SSACFG::BlockId _target |
596 | | ) |
597 | 0 | { |
598 | 0 | if (m_graph.debugInfo) |
599 | 0 | m_graph.debugInfo->setExitDebugData(m_currentBlock, std::move(_debugData)); |
600 | 0 | currentBlock().exit = SSACFG::BasicBlock::Jump{_target}; |
601 | | yulAssert(!blockInfo(_target).sealed); |
602 | 0 | m_graph.block(_target).entries.push_back(m_currentBlock); |
603 | 0 | m_currentBlock = _target; |
604 | 0 | } |
605 | | |