/src/solidity/libyul/backends/evm/EVMCodeTransform.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 | | * Code generator for translating Yul / inline assembly to EVM. |
20 | | */ |
21 | | |
22 | | #include <libyul/backends/evm/EVMCodeTransform.h> |
23 | | |
24 | | #include <libyul/optimiser/NameCollector.h> |
25 | | #include <libyul/AsmAnalysisInfo.h> |
26 | | #include <libyul/Utilities.h> |
27 | | |
28 | | #include <libsolutil/Visitor.h> |
29 | | #include <libsolutil/StackTooDeepString.h> |
30 | | |
31 | | #include <liblangutil/Exceptions.h> |
32 | | |
33 | | #include <libevmasm/Instruction.h> |
34 | | |
35 | | #include <range/v3/view/reverse.hpp> |
36 | | |
37 | | #include <range/v3/algorithm/max.hpp> |
38 | | #include <range/v3/algorithm/none_of.hpp> |
39 | | #include <range/v3/view/enumerate.hpp> |
40 | | #include <range/v3/view/transform.hpp> |
41 | | |
42 | | #include <utility> |
43 | | #include <variant> |
44 | | |
45 | | using namespace solidity; |
46 | | using namespace solidity::yul; |
47 | | using namespace solidity::util; |
48 | | |
49 | | CodeTransform::CodeTransform( |
50 | | AbstractAssembly& _assembly, |
51 | | AsmAnalysisInfo& _analysisInfo, |
52 | | Block const& _block, |
53 | | bool _allowStackOpt, |
54 | | EVMDialect const& _dialect, |
55 | | BuiltinContext& _builtinContext, |
56 | | ExternalIdentifierAccess::CodeGenerator _identifierAccessCodeGen, |
57 | | UseNamedLabels _useNamedLabelsForFunctions, |
58 | | std::shared_ptr<Context> _context, |
59 | | std::vector<NameWithDebugData> _delayedReturnVariables, |
60 | | std::optional<AbstractAssembly::LabelID> _functionExitLabel |
61 | | ): |
62 | 523k | m_assembly(_assembly), |
63 | 523k | m_info(_analysisInfo), |
64 | 523k | m_dialect(_dialect), |
65 | 523k | m_builtinContext(_builtinContext), |
66 | 523k | m_allowStackOpt(_allowStackOpt), |
67 | 523k | m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions), |
68 | 523k | m_identifierAccessCodeGen(std::move(_identifierAccessCodeGen)), |
69 | 523k | m_context(std::move(_context)), |
70 | 523k | m_delayedReturnVariables(std::move(_delayedReturnVariables)), |
71 | 523k | m_functionExitLabel(_functionExitLabel) |
72 | 523k | { |
73 | 523k | if (!m_context) |
74 | 152k | { |
75 | | // initialize |
76 | 152k | m_context = std::make_shared<Context>(); |
77 | 152k | if (m_allowStackOpt) |
78 | 96.9k | m_context->variableReferences = VariableReferenceCounter::run(m_info, _block); |
79 | 152k | } |
80 | 523k | } |
81 | | |
82 | | void CodeTransform::decreaseReference(YulName, Scope::Variable const& _var) |
83 | 2.14M | { |
84 | 2.14M | if (!m_allowStackOpt) |
85 | 799k | return; |
86 | | |
87 | 1.34M | unsigned& ref = m_context->variableReferences.at(&_var); |
88 | 1.34M | yulAssert(ref >= 1, ""); |
89 | 1.34M | --ref; |
90 | 1.34M | if (ref == 0) |
91 | 626k | m_variablesScheduledForDeletion.insert(&_var); |
92 | 1.34M | } |
93 | | |
94 | | bool CodeTransform::unreferenced(Scope::Variable const& _var) const |
95 | 953k | { |
96 | 953k | return !m_context->variableReferences.count(&_var) || m_context->variableReferences[&_var] == 0; |
97 | 953k | } |
98 | | |
99 | | void CodeTransform::freeUnusedVariables(bool _popUnusedSlotsAtStackTop) |
100 | 5.66M | { |
101 | 5.66M | if (!m_allowStackOpt) |
102 | 1.74M | return; |
103 | | |
104 | 3.91M | for (auto const& identifier: m_scope->identifiers) |
105 | 17.3M | if (Scope::Variable const* var = std::get_if<Scope::Variable>(&identifier.second)) |
106 | 13.1M | if (m_variablesScheduledForDeletion.count(var)) |
107 | 486k | deleteVariable(*var); |
108 | | // Directly in a function body block, we can also delete the function arguments, |
109 | | // which live in the virtual function scope. |
110 | | // However, doing so after the return variables are already allocated, seems to have an adverse |
111 | | // effect, so we only do it before that. |
112 | 3.91M | if (!returnVariablesAndFunctionExitAreSetup() && !m_scope->functionScope && m_scope->superScope && m_scope->superScope->functionScope) |
113 | 343k | for (auto const& identifier: m_scope->superScope->identifiers) |
114 | 845k | if (Scope::Variable const* var = std::get_if<Scope::Variable>(&identifier.second)) |
115 | 845k | if (m_variablesScheduledForDeletion.count(var)) |
116 | 15.2k | deleteVariable(*var); |
117 | | |
118 | 3.91M | if (_popUnusedSlotsAtStackTop) |
119 | 4.12M | while (m_unusedStackSlots.count(m_assembly.stackHeight() - 1)) |
120 | 600k | { |
121 | 600k | yulAssert(m_unusedStackSlots.erase(m_assembly.stackHeight() - 1), ""); |
122 | 600k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
123 | 600k | } |
124 | 3.91M | } |
125 | | |
126 | | void CodeTransform::deleteVariable(Scope::Variable const& _var) |
127 | 654k | { |
128 | 654k | yulAssert(m_allowStackOpt, ""); |
129 | 654k | yulAssert(m_context->variableStackHeights.count(&_var) > 0, ""); |
130 | 654k | m_unusedStackSlots.insert(static_cast<int>(m_context->variableStackHeights[&_var])); |
131 | 654k | m_context->variableStackHeights.erase(&_var); |
132 | 654k | m_context->variableReferences.erase(&_var); |
133 | 654k | m_variablesScheduledForDeletion.erase(&_var); |
134 | 654k | } |
135 | | |
136 | | void CodeTransform::operator()(VariableDeclaration const& _varDecl) |
137 | 975k | { |
138 | 975k | yulAssert(m_scope, ""); |
139 | | |
140 | 975k | size_t const numVariables = _varDecl.variables.size(); |
141 | 975k | auto heightAtStart = static_cast<size_t>(m_assembly.stackHeight()); |
142 | 975k | if (_varDecl.value) |
143 | 539k | { |
144 | 539k | std::visit(*this, *_varDecl.value); |
145 | 539k | expectDeposit(static_cast<int>(numVariables), static_cast<int>(heightAtStart)); |
146 | 539k | freeUnusedVariables(false); |
147 | 539k | } |
148 | 435k | else |
149 | 435k | { |
150 | 435k | m_assembly.setSourceLocation(originLocationOf(_varDecl)); |
151 | 435k | size_t variablesLeft = numVariables; |
152 | 896k | while (variablesLeft--) |
153 | 460k | m_assembly.appendConstant(u256(0)); |
154 | 435k | } |
155 | | |
156 | 975k | m_assembly.setSourceLocation(originLocationOf(_varDecl)); |
157 | 975k | bool atTopOfStack = true; |
158 | 2.19M | for (size_t varIndex = 0; varIndex < numVariables; ++varIndex) |
159 | 1.22M | { |
160 | 1.22M | size_t varIndexReverse = numVariables - 1 - varIndex; |
161 | 1.22M | YulName varName = _varDecl.variables[varIndexReverse].name; |
162 | 1.22M | auto& var = std::get<Scope::Variable>(m_scope->identifiers.at(varName)); |
163 | 1.22M | m_context->variableStackHeights[&var] = heightAtStart + varIndexReverse; |
164 | 1.22M | if (!m_allowStackOpt) |
165 | 267k | continue; |
166 | | |
167 | 953k | if (unreferenced(var)) |
168 | 161k | { |
169 | 161k | if (atTopOfStack) |
170 | 152k | { |
171 | 152k | m_context->variableStackHeights.erase(&var); |
172 | 152k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
173 | 152k | } |
174 | 8.97k | else |
175 | 8.97k | m_variablesScheduledForDeletion.insert(&var); |
176 | 161k | } |
177 | 792k | else |
178 | 792k | { |
179 | 792k | bool foundUnusedSlot = false; |
180 | 792k | for (auto it = m_unusedStackSlots.begin(); it != m_unusedStackSlots.end(); ++it) |
181 | 43.7k | { |
182 | 43.7k | if (m_assembly.stackHeight() - *it > static_cast<int>(m_dialect.reachableStackDepth() + 1)) |
183 | 476 | continue; |
184 | 43.3k | foundUnusedSlot = true; |
185 | 43.3k | auto slot = static_cast<size_t>(*it); |
186 | 43.3k | m_unusedStackSlots.erase(it); |
187 | 43.3k | m_context->variableStackHeights[&var] = slot; |
188 | 43.3k | if (size_t heightDiff = variableHeightDiff(var, varName, true)) |
189 | 43.3k | m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1))); |
190 | 43.3k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
191 | 43.3k | break; |
192 | 43.7k | } |
193 | 792k | if (!foundUnusedSlot) |
194 | 748k | atTopOfStack = false; |
195 | 792k | } |
196 | 953k | } |
197 | 975k | } |
198 | | |
199 | | void CodeTransform::stackError(StackTooDeepError _error, int _targetStackHeight) |
200 | 195 | { |
201 | 195 | m_assembly.appendInstruction(evmasm::Instruction::INVALID); |
202 | | // Correct the stack. |
203 | 1.48k | while (m_assembly.stackHeight() > _targetStackHeight) |
204 | 1.28k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
205 | 195 | while (m_assembly.stackHeight() < _targetStackHeight) |
206 | 0 | m_assembly.appendConstant(u256(0)); |
207 | | // Store error. |
208 | 195 | m_stackErrors.emplace_back(std::move(_error)); |
209 | 195 | m_assembly.markAsInvalid(); |
210 | 195 | } |
211 | | |
212 | | void CodeTransform::operator()(Assignment const& _assignment) |
213 | 373k | { |
214 | 373k | int height = m_assembly.stackHeight(); |
215 | 373k | std::visit(*this, *_assignment.value); |
216 | 373k | expectDeposit(static_cast<int>(_assignment.variableNames.size()), height); |
217 | | |
218 | 373k | m_assembly.setSourceLocation(originLocationOf(_assignment)); |
219 | 373k | generateMultiAssignment(_assignment.variableNames); |
220 | 373k | } |
221 | | |
222 | | void CodeTransform::operator()(ExpressionStatement const& _statement) |
223 | 883k | { |
224 | 883k | m_assembly.setSourceLocation(originLocationOf(_statement)); |
225 | 883k | std::visit(*this, _statement.expression); |
226 | 883k | } |
227 | | |
228 | | void CodeTransform::operator()(FunctionCall const& _call) |
229 | 4.65M | { |
230 | 4.65M | yulAssert(m_scope, ""); |
231 | | |
232 | 4.65M | m_assembly.setSourceLocation(originLocationOf(_call)); |
233 | 4.65M | if (BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_call.functionName, m_dialect)) |
234 | 4.12M | { |
235 | 4.12M | for (auto&& [i, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) |
236 | 9.68M | if (!builtin->literalArgument(i)) |
237 | 9.46M | visitExpression(arg); |
238 | 4.12M | m_assembly.setSourceLocation(originLocationOf(_call)); |
239 | 4.12M | builtin->generateCode(_call, m_assembly, m_builtinContext); |
240 | 4.12M | } |
241 | 536k | else |
242 | 536k | { |
243 | 536k | yulAssert(std::holds_alternative<Identifier>(_call.functionName)); |
244 | 536k | AbstractAssembly::LabelID returnLabel = m_assembly.newLabelId(); |
245 | 536k | m_assembly.appendLabelReference(returnLabel); |
246 | | |
247 | 536k | Scope::Function* function = nullptr; |
248 | 536k | yulAssert(m_scope->lookup(std::get<Identifier>(_call.functionName).name, GenericVisitor{ |
249 | 536k | [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, |
250 | 536k | [&](Scope::Function& _function) { function = &_function; } |
251 | 536k | }), "Function name not found."); |
252 | 536k | yulAssert(function, ""); |
253 | 536k | yulAssert(function->numArguments == _call.arguments.size(), ""); |
254 | 536k | for (auto const& arg: _call.arguments | ranges::views::reverse) |
255 | 826k | visitExpression(arg); |
256 | 536k | m_assembly.setSourceLocation(originLocationOf(_call)); |
257 | 536k | m_assembly.appendJumpTo( |
258 | 536k | functionEntryID(*function), |
259 | 536k | static_cast<int>(function->numReturns) - static_cast<int>(function->numArguments) - 1, |
260 | 536k | AbstractAssembly::JumpType::IntoFunction |
261 | 536k | ); |
262 | 536k | m_assembly.appendLabel(returnLabel); |
263 | 536k | } |
264 | 4.65M | } |
265 | | |
266 | | void CodeTransform::operator()(Identifier const& _identifier) |
267 | 1.82M | { |
268 | 1.82M | m_assembly.setSourceLocation(originLocationOf(_identifier)); |
269 | | // First search internals, then externals. |
270 | 1.82M | yulAssert(m_scope, ""); |
271 | 1.82M | if (m_scope->lookup(_identifier.name, GenericVisitor{ |
272 | 1.82M | [&](Scope::Variable& _var) |
273 | 1.82M | { |
274 | | // TODO: opportunity for optimization: Do not DUP if this is the last reference |
275 | | // to the top most element of the stack |
276 | 1.77M | if (size_t heightDiff = variableHeightDiff(_var, _identifier.name, false)) |
277 | 1.77M | m_assembly.appendInstruction(evmasm::dupInstruction(static_cast<unsigned>(heightDiff))); |
278 | 0 | else |
279 | | // Store something to balance the stack |
280 | 0 | m_assembly.appendConstant(u256(0)); |
281 | 1.77M | decreaseReference(_identifier.name, _var); |
282 | 1.77M | }, |
283 | 1.82M | [](Scope::Function&) |
284 | 1.82M | { |
285 | 0 | yulAssert(false, "Function not removed during desugaring."); |
286 | 0 | } |
287 | 1.82M | })) |
288 | 1.77M | { |
289 | 1.77M | return; |
290 | 1.77M | } |
291 | 51.1k | yulAssert( |
292 | 51.1k | m_identifierAccessCodeGen, |
293 | 51.1k | "Identifier not found and no external access available." |
294 | 51.1k | ); |
295 | 51.1k | m_identifierAccessCodeGen(_identifier, IdentifierContext::RValue, m_assembly); |
296 | 51.1k | } |
297 | | |
298 | | void CodeTransform::operator()(Literal const& _literal) |
299 | 5.94M | { |
300 | 5.94M | m_assembly.setSourceLocation(originLocationOf(_literal)); |
301 | 5.94M | m_assembly.appendConstant(_literal.value.value()); |
302 | 5.94M | } |
303 | | |
304 | | void CodeTransform::operator()(If const& _if) |
305 | 168k | { |
306 | 168k | visitExpression(*_if.condition); |
307 | 168k | m_assembly.setSourceLocation(originLocationOf(_if)); |
308 | 168k | m_assembly.appendInstruction(evmasm::Instruction::ISZERO); |
309 | 168k | AbstractAssembly::LabelID end = m_assembly.newLabelId(); |
310 | 168k | m_assembly.appendJumpToIf(end); |
311 | 168k | (*this)(_if.body); |
312 | 168k | m_assembly.setSourceLocation(originLocationOf(_if)); |
313 | 168k | m_assembly.appendLabel(end); |
314 | 168k | } |
315 | | |
316 | | void CodeTransform::operator()(Switch const& _switch) |
317 | 43.1k | { |
318 | 43.1k | visitExpression(*_switch.expression); |
319 | 43.1k | int expressionHeight = m_assembly.stackHeight(); |
320 | 43.1k | std::map<Case const*, AbstractAssembly::LabelID> caseBodies; |
321 | 43.1k | AbstractAssembly::LabelID end = m_assembly.newLabelId(); |
322 | 43.1k | for (Case const& c: _switch.cases) |
323 | 76.1k | { |
324 | 76.1k | if (c.value) |
325 | 36.7k | { |
326 | 36.7k | (*this)(*c.value); |
327 | 36.7k | m_assembly.setSourceLocation(originLocationOf(c)); |
328 | 36.7k | AbstractAssembly::LabelID bodyLabel = m_assembly.newLabelId(); |
329 | 36.7k | caseBodies[&c] = bodyLabel; |
330 | 36.7k | yulAssert(m_assembly.stackHeight() == expressionHeight + 1, ""); |
331 | 36.7k | m_assembly.appendInstruction(evmasm::dupInstruction(2)); |
332 | 36.7k | m_assembly.appendInstruction(evmasm::Instruction::EQ); |
333 | 36.7k | m_assembly.appendJumpToIf(bodyLabel); |
334 | 36.7k | } |
335 | 39.3k | else |
336 | | // default case |
337 | 39.3k | (*this)(c.body); |
338 | 76.1k | } |
339 | 43.1k | m_assembly.setSourceLocation(originLocationOf(_switch)); |
340 | 43.1k | m_assembly.appendJumpTo(end); |
341 | | |
342 | 43.1k | size_t numCases = caseBodies.size(); |
343 | 43.1k | for (auto const& c: caseBodies) |
344 | 36.7k | { |
345 | 36.7k | m_assembly.setSourceLocation(originLocationOf(*c.first)); |
346 | 36.7k | m_assembly.appendLabel(c.second); |
347 | 36.7k | (*this)(c.first->body); |
348 | | // Avoid useless "jump to next" for the last case. |
349 | 36.7k | if (--numCases > 0) |
350 | 21.6k | { |
351 | 21.6k | m_assembly.setSourceLocation(originLocationOf(*c.first)); |
352 | 21.6k | m_assembly.appendJumpTo(end); |
353 | 21.6k | } |
354 | 36.7k | } |
355 | | |
356 | 43.1k | m_assembly.setSourceLocation(originLocationOf(_switch)); |
357 | 43.1k | m_assembly.appendLabel(end); |
358 | 43.1k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
359 | 43.1k | } |
360 | | |
361 | | void CodeTransform::operator()(FunctionDefinition const& _function) |
362 | 371k | { |
363 | 371k | yulAssert(m_scope, ""); |
364 | 371k | yulAssert(m_scope->identifiers.count(_function.name), ""); |
365 | 371k | Scope::Function& function = std::get<Scope::Function>(m_scope->identifiers.at(_function.name)); |
366 | | |
367 | 371k | size_t height = 1; |
368 | 371k | yulAssert(m_info.scopes.at(&_function.body), ""); |
369 | 371k | Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); |
370 | 371k | yulAssert(virtualFunctionScope, ""); |
371 | 371k | for (auto const& v: _function.parameters | ranges::views::reverse) |
372 | 525k | { |
373 | 525k | auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name)); |
374 | 525k | m_context->variableStackHeights[&var] = height++; |
375 | 525k | } |
376 | | |
377 | 371k | m_assembly.setSourceLocation(originLocationOf(_function)); |
378 | 371k | int const stackHeightBefore = m_assembly.stackHeight(); |
379 | | |
380 | 371k | m_assembly.appendLabel(functionEntryID(function)); |
381 | | |
382 | 371k | m_assembly.setStackHeight(static_cast<int>(height)); |
383 | | |
384 | 371k | CodeTransform subTransform( |
385 | 371k | m_assembly, |
386 | 371k | m_info, |
387 | 371k | _function.body, |
388 | 371k | m_allowStackOpt, |
389 | 371k | m_dialect, |
390 | 371k | m_builtinContext, |
391 | 371k | m_identifierAccessCodeGen, |
392 | 371k | m_useNamedLabelsForFunctions, |
393 | 371k | m_context, |
394 | 371k | _function.returnVariables, |
395 | 371k | m_assembly.newLabelId() |
396 | 371k | ); |
397 | 371k | subTransform.m_scope = virtualFunctionScope; |
398 | | |
399 | 371k | if (m_allowStackOpt) |
400 | | // Immediately delete entirely unused parameters. |
401 | 221k | for (auto const& v: _function.parameters | ranges::views::reverse) |
402 | 301k | { |
403 | 301k | auto& var = std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(v.name)); |
404 | 301k | if (util::valueOrDefault(m_context->variableReferences, &var, 0u) == 0) |
405 | 152k | subTransform.deleteVariable(var); |
406 | 301k | } |
407 | | |
408 | 371k | if (!m_allowStackOpt) |
409 | 149k | subTransform.setupReturnVariablesAndFunctionExit(); |
410 | | |
411 | 371k | subTransform.m_assignedNamedLabels = std::move(m_assignedNamedLabels); |
412 | | |
413 | 371k | subTransform(_function.body); |
414 | | |
415 | 371k | m_assignedNamedLabels = std::move(subTransform.m_assignedNamedLabels); |
416 | | |
417 | 371k | m_assembly.setSourceLocation(originLocationOf(_function)); |
418 | 371k | if (!subTransform.m_stackErrors.empty()) |
419 | 52.9k | { |
420 | 52.9k | m_assembly.markAsInvalid(); |
421 | 52.9k | for (StackTooDeepError& stackError: subTransform.m_stackErrors) |
422 | 96.2k | { |
423 | 96.2k | if (stackError.functionName.empty()) |
424 | 95.4k | stackError.functionName = _function.name; |
425 | 96.2k | m_stackErrors.emplace_back(std::move(stackError)); |
426 | 96.2k | } |
427 | 52.9k | } |
428 | | |
429 | 371k | if (!subTransform.returnVariablesAndFunctionExitAreSetup()) |
430 | 50.5k | subTransform.setupReturnVariablesAndFunctionExit(); |
431 | 371k | appendPopUntil(*subTransform.m_functionExitStackHeight); |
432 | | |
433 | 371k | yulAssert( |
434 | 371k | subTransform.m_functionExitStackHeight && |
435 | 371k | *subTransform.m_functionExitStackHeight == m_assembly.stackHeight(), |
436 | 371k | "" |
437 | 371k | ); |
438 | | |
439 | 371k | m_assembly.appendLabel(*subTransform.m_functionExitLabel); |
440 | | |
441 | 371k | { |
442 | | // The stack layout here is: |
443 | | // <return label>? <arguments...> <return values...> |
444 | | // But we would like it to be: |
445 | | // <return values...> <return label>? |
446 | | // So we have to append some SWAP and POP instructions. |
447 | | |
448 | | // This vector holds the desired target positions of all stack slots and is |
449 | | // modified parallel to the actual stack. |
450 | 371k | std::vector<int> stackLayout(static_cast<size_t>(m_assembly.stackHeight()), -1); |
451 | 371k | stackLayout[0] = static_cast<int>(_function.returnVariables.size()); // Move return label to the top |
452 | 371k | for (auto&& [n, returnVariable]: ranges::views::enumerate(_function.returnVariables)) |
453 | 424k | stackLayout.at(m_context->variableStackHeights.at( |
454 | 424k | &std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(returnVariable.name)) |
455 | 424k | )) = static_cast<int>(n); |
456 | | |
457 | 371k | size_t const reachableStackDepth = m_dialect.reachableStackDepth(); |
458 | 371k | if (stackLayout.size() > reachableStackDepth + 1) |
459 | 195 | { |
460 | 195 | size_t const unreachableSlots = stackLayout.size() - (reachableStackDepth + 1); |
461 | 195 | StackTooDeepError error( |
462 | 195 | _function.name, |
463 | 195 | YulName{}, |
464 | 195 | static_cast<int>(unreachableSlots), |
465 | 195 | "The function " + |
466 | 195 | _function.name.str() + |
467 | 195 | " has " + |
468 | 195 | std::to_string(unreachableSlots) + |
469 | 195 | " parameters or return variables too many to fit the stack size." |
470 | 195 | ); |
471 | 195 | stackError(std::move(error), m_assembly.stackHeight() - static_cast<int>(_function.parameters.size())); |
472 | 195 | } |
473 | 370k | else |
474 | 370k | { |
475 | 1.21M | while (!stackLayout.empty() && stackLayout.back() != static_cast<int>(stackLayout.size() - 1)) |
476 | 845k | if (stackLayout.back() < 0) |
477 | 273k | { |
478 | 273k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
479 | 273k | stackLayout.pop_back(); |
480 | 273k | } |
481 | 572k | else |
482 | 572k | { |
483 | 572k | m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(stackLayout.size()) - static_cast<unsigned>(stackLayout.back()) - 1u)); |
484 | 572k | std::swap(stackLayout[static_cast<size_t>(stackLayout.back())], stackLayout.back()); |
485 | 572k | } |
486 | 1.16M | for (size_t i = 0; i < stackLayout.size(); ++i) |
487 | 792k | yulAssert(i == static_cast<size_t>(stackLayout[i]), "Error reshuffling stack."); |
488 | 370k | } |
489 | 371k | } |
490 | 371k | m_assembly.appendJump( |
491 | 371k | stackHeightBefore - static_cast<int>(_function.returnVariables.size()), |
492 | 371k | AbstractAssembly::JumpType::OutOfFunction |
493 | 371k | ); |
494 | 371k | m_assembly.setStackHeight(stackHeightBefore); |
495 | 371k | } |
496 | | |
497 | | void CodeTransform::operator()(ForLoop const& _forLoop) |
498 | 80.8k | { |
499 | 80.8k | Scope* originalScope = m_scope; |
500 | | // We start with visiting the block, but not finalizing it. |
501 | 80.8k | m_scope = m_info.scopes.at(&_forLoop.pre).get(); |
502 | 80.8k | int stackStartHeight = m_assembly.stackHeight(); |
503 | | |
504 | 80.8k | visitStatements(_forLoop.pre.statements); |
505 | | |
506 | 80.8k | AbstractAssembly::LabelID loopStart = m_assembly.newLabelId(); |
507 | 80.8k | AbstractAssembly::LabelID postPart = m_assembly.newLabelId(); |
508 | 80.8k | AbstractAssembly::LabelID loopEnd = m_assembly.newLabelId(); |
509 | | |
510 | 80.8k | m_assembly.setSourceLocation(originLocationOf(_forLoop)); |
511 | 80.8k | m_assembly.appendLabel(loopStart); |
512 | | |
513 | 80.8k | visitExpression(*_forLoop.condition); |
514 | 80.8k | m_assembly.setSourceLocation(originLocationOf(_forLoop)); |
515 | 80.8k | m_assembly.appendInstruction(evmasm::Instruction::ISZERO); |
516 | 80.8k | m_assembly.appendJumpToIf(loopEnd); |
517 | | |
518 | 80.8k | int const stackHeightBody = m_assembly.stackHeight(); |
519 | 80.8k | m_context->forLoopStack.emplace(Context::ForLoopLabels{ {postPart, stackHeightBody}, {loopEnd, stackHeightBody} }); |
520 | 80.8k | (*this)(_forLoop.body); |
521 | | |
522 | 80.8k | m_assembly.setSourceLocation(originLocationOf(_forLoop)); |
523 | 80.8k | m_assembly.appendLabel(postPart); |
524 | | |
525 | 80.8k | (*this)(_forLoop.post); |
526 | | |
527 | 80.8k | m_assembly.setSourceLocation(originLocationOf(_forLoop)); |
528 | 80.8k | m_assembly.appendJumpTo(loopStart); |
529 | 80.8k | m_assembly.appendLabel(loopEnd); |
530 | | |
531 | 80.8k | finalizeBlock(_forLoop.pre, stackStartHeight); |
532 | 80.8k | m_context->forLoopStack.pop(); |
533 | 80.8k | m_scope = originalScope; |
534 | 80.8k | } |
535 | | |
536 | | int CodeTransform::appendPopUntil(int _targetDepth) |
537 | 456k | { |
538 | 456k | int const stackDiffAfter = m_assembly.stackHeight() - _targetDepth; |
539 | 611k | for (int i = 0; i < stackDiffAfter; ++i) |
540 | 155k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
541 | 456k | return stackDiffAfter; |
542 | 456k | } |
543 | | |
544 | | void CodeTransform::operator()(Break const& _break) |
545 | 41.3k | { |
546 | 41.3k | yulAssert(!m_context->forLoopStack.empty(), "Invalid break-statement. Requires surrounding for-loop in code generation."); |
547 | 41.3k | m_assembly.setSourceLocation(originLocationOf(_break)); |
548 | | |
549 | 41.3k | Context::JumpInfo const& jump = m_context->forLoopStack.top().done; |
550 | 41.3k | m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight)); |
551 | 41.3k | } |
552 | | |
553 | | void CodeTransform::operator()(Continue const& _continue) |
554 | 14.9k | { |
555 | 14.9k | yulAssert(!m_context->forLoopStack.empty(), "Invalid continue-statement. Requires surrounding for-loop in code generation."); |
556 | 14.9k | m_assembly.setSourceLocation(originLocationOf(_continue)); |
557 | | |
558 | 14.9k | Context::JumpInfo const& jump = m_context->forLoopStack.top().post; |
559 | 14.9k | m_assembly.appendJumpTo(jump.label, appendPopUntil(jump.targetStackHeight)); |
560 | 14.9k | } |
561 | | |
562 | | void CodeTransform::operator()(Leave const& _leaveStatement) |
563 | 28.7k | { |
564 | 28.7k | yulAssert(m_functionExitLabel, "Invalid leave-statement. Requires surrounding function in code generation."); |
565 | 28.7k | yulAssert(m_functionExitStackHeight, ""); |
566 | 28.7k | m_assembly.setSourceLocation(originLocationOf(_leaveStatement)); |
567 | 28.7k | m_assembly.appendJumpTo(*m_functionExitLabel, appendPopUntil(*m_functionExitStackHeight)); |
568 | 28.7k | } |
569 | | |
570 | | void CodeTransform::operator()(Block const& _block) |
571 | 1.11M | { |
572 | 1.11M | Scope* originalScope = m_scope; |
573 | 1.11M | m_scope = m_info.scopes.at(&_block).get(); |
574 | | |
575 | 1.11M | for (auto const& statement: _block.statements) |
576 | 2.72M | if (auto function = std::get_if<FunctionDefinition>(&statement)) |
577 | 371k | createFunctionEntryID(*function); |
578 | | |
579 | 1.11M | int blockStartStackHeight = m_assembly.stackHeight(); |
580 | 1.11M | visitStatements(_block.statements); |
581 | | |
582 | 1.11M | bool isOutermostFunctionBodyBlock = m_scope && m_scope->superScope && m_scope->superScope->functionScope; |
583 | 1.11M | bool performValidation = !m_allowStackOpt || !isOutermostFunctionBodyBlock; |
584 | 1.11M | finalizeBlock(_block, performValidation ? std::make_optional(blockStartStackHeight) : std::nullopt); |
585 | 1.11M | m_scope = originalScope; |
586 | 1.11M | } |
587 | | |
588 | | void CodeTransform::createFunctionEntryID(FunctionDefinition const& _function) |
589 | 371k | { |
590 | 371k | Scope::Function& scopeFunction = std::get<Scope::Function>(m_scope->identifiers.at(_function.name)); |
591 | 371k | yulAssert(!m_context->functionEntryIDs.count(&scopeFunction), ""); |
592 | | |
593 | 371k | std::optional<size_t> astID; |
594 | 371k | if (_function.debugData) |
595 | 371k | astID = _function.debugData->astID; |
596 | | |
597 | 371k | bool nameAlreadySeen = !m_assignedNamedLabels.insert(_function.name).second; |
598 | | |
599 | 371k | if (m_useNamedLabelsForFunctions == UseNamedLabels::YesAndForceUnique) |
600 | 371k | yulAssert(!nameAlreadySeen); |
601 | | |
602 | 371k | m_context->functionEntryIDs[&scopeFunction] = |
603 | 371k | ( |
604 | 371k | m_useNamedLabelsForFunctions != UseNamedLabels::Never && |
605 | 174k | !nameAlreadySeen |
606 | 371k | ) ? |
607 | 173k | m_assembly.namedLabel( |
608 | 173k | _function.name.str(), |
609 | 173k | _function.parameters.size(), |
610 | 173k | _function.returnVariables.size(), |
611 | 173k | astID |
612 | 173k | ) : |
613 | 371k | m_assembly.newLabelId(); |
614 | 371k | } |
615 | | |
616 | | AbstractAssembly::LabelID CodeTransform::functionEntryID(Scope::Function const& _scopeFunction) const |
617 | 907k | { |
618 | 907k | yulAssert(m_context->functionEntryIDs.count(&_scopeFunction), ""); |
619 | 907k | return m_context->functionEntryIDs.at(&_scopeFunction); |
620 | 907k | } |
621 | | |
622 | | void CodeTransform::visitExpression(Expression const& _expression) |
623 | 10.5M | { |
624 | 10.5M | int height = m_assembly.stackHeight(); |
625 | 10.5M | std::visit(*this, _expression); |
626 | 10.5M | expectDeposit(1, height); |
627 | 10.5M | } |
628 | | |
629 | | void CodeTransform::setupReturnVariablesAndFunctionExit() |
630 | 371k | { |
631 | 371k | yulAssert(isInsideFunction(), ""); |
632 | 371k | yulAssert(!returnVariablesAndFunctionExitAreSetup(), ""); |
633 | 371k | yulAssert(m_scope, ""); |
634 | | |
635 | 371k | ScopeGuard scopeGuard([oldScope = m_scope, this] { m_scope = oldScope; }); |
636 | 371k | if (!m_scope->functionScope) |
637 | 171k | { |
638 | 171k | yulAssert(m_scope->superScope && m_scope->superScope->functionScope, ""); |
639 | 171k | m_scope = m_scope->superScope; |
640 | 171k | } |
641 | | |
642 | | // We could reuse unused slots for return variables, but it turns out this is detrimental in practice. |
643 | 371k | m_unusedStackSlots.clear(); |
644 | | |
645 | 371k | if (m_delayedReturnVariables.empty()) |
646 | 123k | { |
647 | 123k | m_functionExitStackHeight = 1; |
648 | 123k | return; |
649 | 123k | } |
650 | | |
651 | | // Allocate slots for return variables as if they were declared as variables in the virtual function scope. |
652 | 247k | for (NameWithDebugData const& var: m_delayedReturnVariables) |
653 | 424k | (*this)(VariableDeclaration{var.debugData, {var}, {}}); |
654 | | |
655 | 424k | m_functionExitStackHeight = ranges::max(m_delayedReturnVariables | ranges::views::transform([&](NameWithDebugData const& _name) { |
656 | 424k | return variableStackHeight(_name.name); |
657 | 424k | })) + 1; |
658 | 247k | m_delayedReturnVariables.clear(); |
659 | 247k | } |
660 | | |
661 | | namespace |
662 | | { |
663 | | |
664 | | bool statementNeedsReturnVariableSetup(Statement const& _statement, std::vector<NameWithDebugData> const& _returnVariables) |
665 | 242k | { |
666 | 242k | if (std::holds_alternative<FunctionDefinition>(_statement)) |
667 | 28.5k | return true; |
668 | 213k | if ( |
669 | 213k | std::holds_alternative<ExpressionStatement>(_statement) || |
670 | 146k | std::holds_alternative<Assignment>(_statement) |
671 | 213k | ) |
672 | 134k | { |
673 | 134k | std::map<YulName, size_t> references = VariableReferencesCounter::countReferences(_statement); |
674 | 134k | auto isReferenced = [&references](NameWithDebugData const& _returnVariable) { |
675 | 124k | return references.count(_returnVariable.name); |
676 | 124k | }; |
677 | 134k | if (ranges::none_of(_returnVariables, isReferenced)) |
678 | 71.2k | return false; |
679 | 134k | } |
680 | 142k | return true; |
681 | 213k | } |
682 | | |
683 | | } |
684 | | |
685 | | void CodeTransform::visitStatements(std::vector<Statement> const& _statements) |
686 | 1.19M | { |
687 | 1.19M | std::optional<AbstractAssembly::LabelID> jumpTarget = std::nullopt; |
688 | | |
689 | 1.19M | for (auto const& statement: _statements) |
690 | 2.73M | { |
691 | 2.73M | freeUnusedVariables(); |
692 | 2.73M | if ( |
693 | 2.73M | isInsideFunction() && |
694 | 1.62M | !returnVariablesAndFunctionExitAreSetup() && |
695 | 242k | statementNeedsReturnVariableSetup(statement, m_delayedReturnVariables) |
696 | 2.73M | ) |
697 | 171k | setupReturnVariablesAndFunctionExit(); |
698 | | |
699 | 2.73M | auto const* functionDefinition = std::get_if<FunctionDefinition>(&statement); |
700 | 2.73M | if (functionDefinition && !jumpTarget) |
701 | 105k | { |
702 | 105k | m_assembly.setSourceLocation(originLocationOf(*functionDefinition)); |
703 | 105k | jumpTarget = m_assembly.newLabelId(); |
704 | 105k | m_assembly.appendJumpTo(*jumpTarget, 0); |
705 | 105k | } |
706 | 2.63M | else if (!functionDefinition && jumpTarget) |
707 | 66.3k | { |
708 | 66.3k | m_assembly.appendLabel(*jumpTarget); |
709 | 66.3k | jumpTarget = std::nullopt; |
710 | 66.3k | } |
711 | | |
712 | 2.73M | std::visit(*this, statement); |
713 | 2.73M | } |
714 | | // we may have a leftover jumpTarget |
715 | 1.19M | if (jumpTarget) |
716 | 38.9k | m_assembly.appendLabel(*jumpTarget); |
717 | | |
718 | 1.19M | freeUnusedVariables(); |
719 | 1.19M | } |
720 | | |
721 | | void CodeTransform::finalizeBlock(Block const& _block, std::optional<int> blockStartStackHeight) |
722 | 1.19M | { |
723 | 1.19M | m_assembly.setSourceLocation(originLocationOf(_block)); |
724 | | |
725 | 1.19M | freeUnusedVariables(); |
726 | | |
727 | | // pop variables |
728 | 1.19M | yulAssert(m_info.scopes.at(&_block).get() == m_scope, ""); |
729 | 1.19M | for (auto const& id: m_scope->identifiers) |
730 | 1.16M | if (std::holds_alternative<Scope::Variable>(id.second)) |
731 | 795k | { |
732 | 795k | Scope::Variable const& var = std::get<Scope::Variable>(id.second); |
733 | 795k | if (m_allowStackOpt) |
734 | 638k | { |
735 | 638k | yulAssert(!m_context->variableStackHeights.count(&var), ""); |
736 | 638k | yulAssert(!m_context->variableReferences.count(&var), ""); |
737 | 638k | } |
738 | 156k | else |
739 | 156k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
740 | 795k | } |
741 | | |
742 | 1.19M | if (blockStartStackHeight) |
743 | 970k | { |
744 | 970k | int deposit = m_assembly.stackHeight() - *blockStartStackHeight; |
745 | 970k | yulAssert(deposit == 0, "Invalid stack height at end of block: " + std::to_string(deposit)); |
746 | 970k | } |
747 | 1.19M | } |
748 | | |
749 | | void CodeTransform::generateMultiAssignment(std::vector<Identifier> const& _variableNames) |
750 | 373k | { |
751 | 373k | yulAssert(m_scope, ""); |
752 | 373k | for (auto const& variableName: _variableNames | ranges::views::reverse) |
753 | 374k | generateAssignment(variableName); |
754 | 373k | } |
755 | | |
756 | | void CodeTransform::generateAssignment(Identifier const& _variableName) |
757 | 374k | { |
758 | 374k | yulAssert(m_scope, ""); |
759 | 374k | if (auto var = m_scope->lookup(_variableName.name)) |
760 | 367k | { |
761 | 367k | Scope::Variable const& _var = std::get<Scope::Variable>(*var); |
762 | 367k | if (size_t heightDiff = variableHeightDiff(_var, _variableName.name, true)) |
763 | 367k | m_assembly.appendInstruction(evmasm::swapInstruction(static_cast<unsigned>(heightDiff - 1))); |
764 | 367k | m_assembly.appendInstruction(evmasm::Instruction::POP); |
765 | 367k | decreaseReference(_variableName.name, _var); |
766 | 367k | } |
767 | 7.53k | else |
768 | 7.53k | { |
769 | 7.53k | yulAssert( |
770 | 7.53k | m_identifierAccessCodeGen, |
771 | 7.53k | "Identifier not found and no external access available." |
772 | 7.53k | ); |
773 | 7.53k | m_identifierAccessCodeGen(_variableName, IdentifierContext::LValue, m_assembly); |
774 | 7.53k | } |
775 | 374k | } |
776 | | |
777 | | size_t CodeTransform::variableHeightDiff(Scope::Variable const& _var, YulName _varName, bool _forSwap) |
778 | 2.18M | { |
779 | 2.18M | yulAssert(m_context->variableStackHeights.count(&_var), ""); |
780 | 2.18M | size_t heightDiff = static_cast<size_t>(m_assembly.stackHeight()) - m_context->variableStackHeights[&_var]; |
781 | 2.18M | yulAssert(heightDiff > (_forSwap ? 1 : 0), "Negative stack difference for variable."); |
782 | 2.18M | size_t limit = m_dialect.reachableStackDepth() + (_forSwap ? 1 : 0); |
783 | 2.18M | if (heightDiff > limit) |
784 | 132k | { |
785 | 132k | m_stackErrors.emplace_back( |
786 | 132k | _varName, |
787 | 132k | heightDiff - limit, |
788 | 132k | "Variable " + |
789 | 132k | _varName.str() + |
790 | 132k | " is " + |
791 | 132k | std::to_string(heightDiff - limit) + |
792 | 132k | " slot(s) too deep inside the stack. " + |
793 | 132k | stackTooDeepString |
794 | 132k | ); |
795 | 132k | m_assembly.markAsInvalid(); |
796 | 132k | return _forSwap ? 2 : 1; |
797 | 132k | } |
798 | 2.05M | return heightDiff; |
799 | 2.18M | } |
800 | | |
801 | | int CodeTransform::variableStackHeight(YulName _name) const |
802 | 424k | { |
803 | 424k | Scope::Variable const* var = std::get_if<Scope::Variable>(m_scope->lookup(_name)); |
804 | 424k | yulAssert(var, ""); |
805 | 424k | return static_cast<int>(m_context->variableStackHeights.at(var)); |
806 | 424k | } |
807 | | |
808 | | void CodeTransform::expectDeposit(int _deposit, int _oldHeight) const |
809 | 11.5M | { |
810 | | yulAssert(m_assembly.stackHeight() == _oldHeight + _deposit, "Invalid stack deposit."); |
811 | 11.5M | } |