/src/solidity/libsolidity/codegen/ir/IRGenerator.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 | | * @author Alex Beregszaszi |
20 | | * @date 2017 |
21 | | * Component that translates Solidity code into Yul. |
22 | | */ |
23 | | |
24 | | #include <libsolidity/codegen/ir/Common.h> |
25 | | #include <libsolidity/codegen/ir/IRGenerator.h> |
26 | | #include <libsolidity/codegen/ir/IRGeneratorForStatements.h> |
27 | | |
28 | | #include <libsolidity/ast/AST.h> |
29 | | #include <libsolidity/ast/ASTVisitor.h> |
30 | | #include <libsolidity/codegen/ABIFunctions.h> |
31 | | #include <libsolidity/codegen/CompilerUtils.h> |
32 | | |
33 | | #include <libyul/Object.h> |
34 | | #include <libyul/Utilities.h> |
35 | | |
36 | | #include <libsolutil/Algorithms.h> |
37 | | #include <libsolutil/CommonData.h> |
38 | | #include <libsolutil/StringUtils.h> |
39 | | #include <libsolutil/Whiskers.h> |
40 | | #include <libsolutil/JSON.h> |
41 | | |
42 | | #include <range/v3/algorithm/all_of.hpp> |
43 | | |
44 | | #include <sstream> |
45 | | #include <variant> |
46 | | |
47 | | using namespace solidity; |
48 | | using namespace solidity::frontend; |
49 | | using namespace solidity::langutil; |
50 | | using namespace solidity::util; |
51 | | using namespace std::string_literals; |
52 | | |
53 | | namespace |
54 | | { |
55 | | |
56 | | void verifyCallGraph( |
57 | | std::set<CallableDeclaration const*, ASTNode::CompareByID> const& _expectedCallables, |
58 | | std::set<FunctionDefinition const*> _generatedFunctions |
59 | | ) |
60 | 7.88k | { |
61 | 7.88k | for (auto const& expectedCallable: _expectedCallables) |
62 | 3.90k | if (auto const* expectedFunction = dynamic_cast<FunctionDefinition const*>(expectedCallable)) |
63 | 3.84k | { |
64 | 3.84k | solAssert( |
65 | 3.84k | _generatedFunctions.count(expectedFunction) == 1 || expectedFunction->isConstructor(), |
66 | 3.84k | "No code generated for function " + expectedFunction->name() + " even though it is not a constructor." |
67 | 3.84k | ); |
68 | 3.84k | _generatedFunctions.erase(expectedFunction); |
69 | 3.84k | } |
70 | | |
71 | 7.88k | solAssert( |
72 | 7.88k | _generatedFunctions.size() == 0, |
73 | 7.88k | "Of the generated functions " + toString(_generatedFunctions.size()) + " are not in the call graph." |
74 | 7.88k | ); |
75 | 7.88k | } |
76 | | |
77 | | std::set<CallableDeclaration const*, ASTNode::CompareByID> collectReachableCallables( |
78 | | CallGraph const& _graph |
79 | | ) |
80 | 7.88k | { |
81 | 7.88k | std::set<CallableDeclaration const*, ASTNode::CompareByID> reachableCallables; |
82 | 7.88k | for (CallGraph::Node const& reachableNode: _graph.edges | ranges::views::keys) |
83 | 7.44k | if (std::holds_alternative<CallableDeclaration const*>(reachableNode)) |
84 | 3.90k | reachableCallables.emplace(std::get<CallableDeclaration const*>(reachableNode)); |
85 | | |
86 | 7.88k | return reachableCallables; |
87 | 7.88k | } |
88 | | |
89 | | } |
90 | | |
91 | | std::string IRGenerator::run( |
92 | | ContractDefinition const& _contract, |
93 | | bytes const& _cborMetadata, |
94 | | std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources |
95 | | ) |
96 | 3.94k | { |
97 | 3.94k | return yul::reindent(generate(_contract, _cborMetadata, _otherYulSources)); |
98 | 3.94k | } |
99 | | |
100 | | std::string IRGenerator::generate( |
101 | | ContractDefinition const& _contract, |
102 | | bytes const& _cborMetadata, |
103 | | std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources |
104 | | ) |
105 | 3.94k | { |
106 | 3.94k | auto subObjectSources = [&_otherYulSources](UniqueVector<ContractDefinition const*> const& _subObjects) -> std::string |
107 | 7.88k | { |
108 | 7.88k | std::string subObjectsSources; |
109 | 7.88k | for (ContractDefinition const* subObject: _subObjects) |
110 | 106 | subObjectsSources += _otherYulSources.at(subObject); |
111 | 7.88k | return subObjectsSources; |
112 | 7.88k | }; |
113 | 3.94k | auto formatUseSrcMap = [](IRGenerationContext const& _context) -> std::string |
114 | 7.88k | { |
115 | 7.88k | return joinHumanReadable( |
116 | 7.89k | ranges::views::transform(_context.usedSourceNames(), [_context](std::string const& _sourceName) { |
117 | 7.89k | return std::to_string(_context.sourceIndices().at(_sourceName)) + ":" + escapeAndQuoteString(_sourceName); |
118 | 7.89k | }), |
119 | 7.88k | ", " |
120 | 7.88k | ); |
121 | 7.88k | }; |
122 | | |
123 | 3.94k | Whiskers t(R"(<?isEthdebugEnabled>/// ethdebug: enabled</isEthdebugEnabled> |
124 | 3.94k | /// @use-src <useSrcMapCreation> |
125 | 3.94k | object "<CreationObject>" { |
126 | 3.94k | code { |
127 | 3.94k | <sourceLocationCommentCreation> |
128 | 3.94k | <memoryInitCreation> |
129 | 3.94k | <callValueCheck> |
130 | 3.94k | <?library> |
131 | 3.94k | <!library> |
132 | 3.94k | <?constructorHasParams> let <constructorParams> := <copyConstructorArguments>() </constructorHasParams> |
133 | 3.94k | <constructor>(<constructorParams>) |
134 | 3.94k | </library> |
135 | 3.94k | <deploy> |
136 | 3.94k | <functions> |
137 | 3.94k | } |
138 | 3.94k | /// @use-src <useSrcMapDeployed> |
139 | 3.94k | object "<DeployedObject>" { |
140 | 3.94k | code { |
141 | 3.94k | <sourceLocationCommentDeployed> |
142 | 3.94k | <memoryInitDeployed> |
143 | 3.94k | <?library> |
144 | 3.94k | <?eof> |
145 | 3.94k | let called_via_delegatecall := iszero(eq(auxdataloadn(<library_address_immutable_offset>), address())) |
146 | 3.94k | <!eof> |
147 | 3.94k | let called_via_delegatecall := iszero(eq(loadimmutable("<library_address>"), address())) |
148 | 3.94k | </eof> |
149 | 3.94k | </library> |
150 | 3.94k | <dispatch> |
151 | 3.94k | <deployedFunctions> |
152 | 3.94k | } |
153 | 3.94k | <deployedSubObjects> |
154 | 3.94k | data "<metadataName>" hex"<cborMetadata>" |
155 | 3.94k | } |
156 | 3.94k | <subObjects> |
157 | 3.94k | } |
158 | 3.94k | )"); |
159 | | |
160 | 3.94k | resetContext(_contract, ExecutionContext::Creation); |
161 | 3.94k | auto const eof = m_context.eofVersion().has_value(); |
162 | 3.94k | if (eof && _contract.isLibrary()) |
163 | 0 | m_context.registerLibraryAddressImmutable(); |
164 | 3.94k | for (VariableDeclaration const* var: ContractType(_contract).immutableVariables()) |
165 | 90 | m_context.registerImmutableVariable(*var); |
166 | | |
167 | 3.94k | t("isEthdebugEnabled", m_context.debugInfoSelection().ethdebug); |
168 | 3.94k | t("CreationObject", IRNames::creationObject(_contract)); |
169 | 3.94k | t("sourceLocationCommentCreation", dispenseLocationComment(_contract)); |
170 | 3.94k | t("library", _contract.isLibrary()); |
171 | | |
172 | 3.94k | FunctionDefinition const* constructor = _contract.constructor(); |
173 | 3.94k | t("callValueCheck", !constructor || !constructor->isPayable() ? callValueCheck() : ""); |
174 | 3.94k | std::vector<std::string> constructorParams; |
175 | 3.94k | if (constructor && !constructor->parameters().empty()) |
176 | 155 | { |
177 | 324 | for (size_t i = 0; i < CompilerUtils::sizeOnStack(constructor->parameters()); ++i) |
178 | 169 | constructorParams.emplace_back(m_context.newYulVariable()); |
179 | 155 | t( |
180 | 155 | "copyConstructorArguments", |
181 | 155 | m_utils.copyConstructorArgumentsToMemoryFunction( |
182 | 155 | _contract, |
183 | 155 | IRNames::creationObject(_contract) |
184 | 155 | ) |
185 | 155 | ); |
186 | 155 | } |
187 | 3.94k | t("constructorParams", joinHumanReadable(constructorParams)); |
188 | 3.94k | t("constructorHasParams", !constructorParams.empty()); |
189 | 3.94k | t("constructor", IRNames::constructor(_contract)); |
190 | | |
191 | 3.94k | t("deploy", deployCode(_contract)); |
192 | 3.94k | generateConstructors(_contract); |
193 | 3.94k | std::set<FunctionDefinition const*> creationFunctionList = generateQueuedFunctions(); |
194 | 3.94k | InternalDispatchMap internalDispatchMap = generateInternalDispatchFunctions(_contract); |
195 | | |
196 | 3.94k | t("functions", m_context.functionCollector().requestedFunctions()); |
197 | 3.94k | t("subObjects", subObjectSources(m_context.subObjectsCreated())); |
198 | | |
199 | | // This has to be called only after all other code generation for the creation object is complete. |
200 | 3.94k | bool creationInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen(); |
201 | 3.94k | t("memoryInitCreation", memoryInit(!creationInvolvesMemoryUnsafeAssembly)); |
202 | 3.94k | t("useSrcMapCreation", formatUseSrcMap(m_context)); |
203 | | |
204 | 3.94k | auto const immutableVariables = m_context.immutableVariables(); |
205 | 3.94k | auto const libraryAddressImmutableOffset = (_contract.isLibrary() && eof) ? |
206 | 3.94k | m_context.libraryAddressImmutableOffset() : 0; |
207 | | |
208 | 3.94k | resetContext(_contract, ExecutionContext::Deployed); |
209 | | |
210 | | // When generating to EOF we have to initialize these two members, because they store offsets in EOF data section |
211 | | // which is used during deployed container generation |
212 | 3.94k | if (m_eofVersion.has_value()) |
213 | 0 | { |
214 | 0 | m_context.setImmutableVariables(std::move(immutableVariables)); |
215 | 0 | if (_contract.isLibrary()) |
216 | 0 | m_context.setLibraryAddressImmutableOffset(libraryAddressImmutableOffset); |
217 | 0 | } |
218 | | |
219 | | // NOTE: Function pointers can be passed from creation code via storage variables. We need to |
220 | | // get all the functions they could point to into the dispatch functions even if they're never |
221 | | // referenced by name in the deployed code. |
222 | 3.94k | m_context.initializeInternalDispatch(std::move(internalDispatchMap)); |
223 | | |
224 | | // Do not register immutables to avoid assignment. |
225 | 3.94k | t("DeployedObject", IRNames::deployedObject(_contract)); |
226 | 3.94k | t("sourceLocationCommentDeployed", dispenseLocationComment(_contract)); |
227 | 3.94k | t("eof", eof); |
228 | 3.94k | if (_contract.isLibrary()) |
229 | 33 | { |
230 | 33 | if (!eof) |
231 | 33 | t("library_address", IRNames::libraryAddressImmutable()); |
232 | 0 | else |
233 | 0 | t("library_address_immutable_offset", std::to_string(m_context.libraryAddressImmutableOffsetRelative())); |
234 | 33 | } |
235 | | |
236 | 3.94k | t("dispatch", dispatchRoutine(_contract)); |
237 | 3.94k | std::set<FunctionDefinition const*> deployedFunctionList = generateQueuedFunctions(); |
238 | 3.94k | generateInternalDispatchFunctions(_contract); |
239 | 3.94k | t("deployedFunctions", m_context.functionCollector().requestedFunctions()); |
240 | 3.94k | t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated())); |
241 | 3.94k | t("metadataName", yul::Object::metadataName()); |
242 | 3.94k | t("cborMetadata", util::toHex(_cborMetadata)); |
243 | | |
244 | 3.94k | t("useSrcMapDeployed", formatUseSrcMap(m_context)); |
245 | | |
246 | | // This has to be called only after all other code generation for the deployed object is complete. |
247 | 3.94k | bool deployedInvolvesMemoryUnsafeAssembly = m_context.memoryUnsafeInlineAssemblySeen(); |
248 | 3.94k | t("memoryInitDeployed", memoryInit(!deployedInvolvesMemoryUnsafeAssembly)); |
249 | | |
250 | 3.94k | solAssert(_contract.annotation().creationCallGraph->get() != nullptr, ""); |
251 | 3.94k | solAssert(_contract.annotation().deployedCallGraph->get() != nullptr, ""); |
252 | 3.94k | verifyCallGraph(collectReachableCallables(**_contract.annotation().creationCallGraph), std::move(creationFunctionList)); |
253 | 3.94k | verifyCallGraph(collectReachableCallables(**_contract.annotation().deployedCallGraph), std::move(deployedFunctionList)); |
254 | | |
255 | 3.94k | return t.render(); |
256 | 3.94k | } |
257 | | |
258 | | std::string IRGenerator::generate(Block const& _block) |
259 | 3.84k | { |
260 | 3.84k | IRGeneratorForStatements generator(m_context, m_utils, m_optimiserSettings); |
261 | 3.84k | generator.generate(_block); |
262 | 3.84k | return generator.code(); |
263 | 3.84k | } |
264 | | |
265 | | std::set<FunctionDefinition const*> IRGenerator::generateQueuedFunctions() |
266 | 7.88k | { |
267 | 7.88k | std::set<FunctionDefinition const*> functions; |
268 | | |
269 | 11.1k | while (!m_context.functionGenerationQueueEmpty()) |
270 | 3.30k | { |
271 | 3.30k | FunctionDefinition const& functionDefinition = *m_context.dequeueFunctionForCodeGeneration(); |
272 | | |
273 | 3.30k | functions.emplace(&functionDefinition); |
274 | | // NOTE: generateFunction() may modify function generation queue |
275 | 3.30k | generateFunction(functionDefinition); |
276 | 3.30k | } |
277 | | |
278 | 7.88k | return functions; |
279 | 7.88k | } |
280 | | |
281 | | InternalDispatchMap IRGenerator::generateInternalDispatchFunctions(ContractDefinition const& _contract) |
282 | 7.88k | { |
283 | 7.88k | solAssert( |
284 | 7.88k | m_context.functionGenerationQueueEmpty(), |
285 | 7.88k | "At this point all the enqueued functions should have been generated. " |
286 | 7.88k | "Otherwise the dispatch may be incomplete." |
287 | 7.88k | ); |
288 | | |
289 | 7.88k | InternalDispatchMap internalDispatchMap = m_context.consumeInternalDispatchMap(); |
290 | 7.88k | for (YulArity const& arity: internalDispatchMap | ranges::views::keys) |
291 | 28 | { |
292 | 28 | std::string funName = IRNames::internalDispatch(arity); |
293 | 28 | m_context.functionCollector().createFunction(funName, [&]() { |
294 | 28 | Whiskers templ(R"( |
295 | 28 | <sourceLocationComment> |
296 | 28 | function <functionName>(fun<?+in>, <in></+in>) <?+out>-> <out></+out> { |
297 | 28 | switch fun |
298 | 28 | <#cases> |
299 | 28 | case <funID> |
300 | 28 | { |
301 | 28 | <?+out> <out> :=</+out> <name>(<in>) |
302 | 28 | } |
303 | 28 | </cases> |
304 | 28 | default { <panic>() } |
305 | 28 | } |
306 | 28 | <sourceLocationComment> |
307 | 28 | )"); |
308 | 28 | templ("sourceLocationComment", dispenseLocationComment(_contract)); |
309 | 28 | templ("functionName", funName); |
310 | 28 | templ("panic", m_utils.panicFunction(PanicCode::InvalidInternalFunction)); |
311 | 28 | templ("in", suffixedVariableNameList("in_", 0, arity.in)); |
312 | 28 | templ("out", suffixedVariableNameList("out_", 0, arity.out)); |
313 | | |
314 | 28 | std::vector<std::map<std::string, std::string>> cases; |
315 | 28 | std::set<int64_t> caseValues; |
316 | 28 | for (FunctionDefinition const* function: internalDispatchMap.at(arity)) |
317 | 18 | { |
318 | 18 | solAssert(function, ""); |
319 | 18 | solAssert( |
320 | 18 | YulArity::fromType(*TypeProvider::function(*function, FunctionType::Kind::Internal)) == arity, |
321 | 18 | "A single dispatch function can only handle functions of one arity" |
322 | 18 | ); |
323 | 18 | solAssert(!function->isConstructor(), ""); |
324 | | // 0 is reserved for uninitialized function pointers |
325 | 18 | solAssert(function->id() != 0, "Unexpected function ID: 0"); |
326 | 18 | solAssert(caseValues.count(function->id()) == 0, "Duplicate function ID"); |
327 | 18 | solAssert(m_context.functionCollector().contains(IRNames::function(*function)), ""); |
328 | | |
329 | 18 | cases.emplace_back(std::map<std::string, std::string>{ |
330 | 18 | {"funID", std::to_string(m_context.mostDerivedContract().annotation().internalFunctionIDs.at(function))}, |
331 | 18 | {"name", IRNames::function(*function)} |
332 | 18 | }); |
333 | 18 | caseValues.insert(function->id()); |
334 | 18 | } |
335 | | |
336 | 28 | templ("cases", std::move(cases)); |
337 | 28 | return templ.render(); |
338 | 28 | }); |
339 | 28 | } |
340 | | |
341 | 7.88k | solAssert(m_context.internalDispatchClean(), ""); |
342 | 7.88k | solAssert( |
343 | 7.88k | m_context.functionGenerationQueueEmpty(), |
344 | 7.88k | "Internal dispatch generation must not add new functions to generation queue because they won't be proeessed." |
345 | 7.88k | ); |
346 | | |
347 | 7.88k | return internalDispatchMap; |
348 | 7.88k | } |
349 | | |
350 | | std::string IRGenerator::generateFunction(FunctionDefinition const& _function) |
351 | 3.30k | { |
352 | 3.30k | std::string functionName = IRNames::function(_function); |
353 | 3.30k | return m_context.functionCollector().createFunction(functionName, [&]() { |
354 | 3.25k | m_context.resetLocalVariables(); |
355 | 3.25k | Whiskers t(R"( |
356 | 3.25k | <astIDComment><sourceLocationComment> |
357 | 3.25k | function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> { |
358 | 3.25k | <retInit> |
359 | 3.25k | <body> |
360 | 3.25k | } |
361 | 3.25k | <contractSourceLocationComment> |
362 | 3.25k | )"); |
363 | | |
364 | 3.25k | if (m_context.debugInfoSelection().astID) |
365 | 3.25k | t("astIDComment", "/// @ast-id " + std::to_string(_function.id()) + "\n"); |
366 | 0 | else |
367 | 0 | t("astIDComment", ""); |
368 | 3.25k | t("sourceLocationComment", dispenseLocationComment(_function)); |
369 | 3.25k | t( |
370 | 3.25k | "contractSourceLocationComment", |
371 | 3.25k | dispenseLocationComment(m_context.mostDerivedContract()) |
372 | 3.25k | ); |
373 | | |
374 | 3.25k | t("functionName", functionName); |
375 | 3.25k | std::vector<std::string> params; |
376 | 3.25k | for (auto const& varDecl: _function.parameters()) |
377 | 1.90k | params += m_context.addLocalVariable(*varDecl).stackSlots(); |
378 | 3.25k | t("params", joinHumanReadable(params)); |
379 | 3.25k | std::vector<std::string> retParams; |
380 | 3.25k | std::string retInit; |
381 | 3.25k | for (auto const& varDecl: _function.returnParameters()) |
382 | 1.96k | { |
383 | 1.96k | retParams += m_context.addLocalVariable(*varDecl).stackSlots(); |
384 | 1.96k | retInit += generateInitialAssignment(*varDecl); |
385 | 1.96k | } |
386 | | |
387 | 3.25k | t("retParams", joinHumanReadable(retParams)); |
388 | 3.25k | t("retInit", retInit); |
389 | | |
390 | 3.25k | if (_function.modifiers().empty()) |
391 | 3.19k | t("body", generate(_function.body())); |
392 | 58 | else |
393 | 58 | { |
394 | 138 | for (size_t i = 0; i < _function.modifiers().size(); ++i) |
395 | 80 | { |
396 | 80 | ModifierInvocation const& modifier = *_function.modifiers().at(i); |
397 | 80 | std::string next = |
398 | 80 | i + 1 < _function.modifiers().size() ? |
399 | 22 | IRNames::modifierInvocation(*_function.modifiers().at(i + 1)) : |
400 | 80 | IRNames::functionWithModifierInner(_function); |
401 | 80 | generateModifier(modifier, _function, next); |
402 | 80 | } |
403 | 58 | t("body", |
404 | 58 | (retParams.empty() ? std::string{} : joinHumanReadable(retParams) + " := ") + |
405 | 58 | IRNames::modifierInvocation(*_function.modifiers().at(0)) + |
406 | 58 | "(" + |
407 | 58 | joinHumanReadable(retParams + params) + |
408 | 58 | ")" |
409 | 58 | ); |
410 | | // Now generate the actual inner function. |
411 | 58 | generateFunctionWithModifierInner(_function); |
412 | 58 | } |
413 | 3.25k | return t.render(); |
414 | 3.25k | }); |
415 | 3.30k | } |
416 | | |
417 | | std::string IRGenerator::generateModifier( |
418 | | ModifierInvocation const& _modifierInvocation, |
419 | | FunctionDefinition const& _function, |
420 | | std::string const& _nextFunction |
421 | | ) |
422 | 82 | { |
423 | 82 | std::string functionName = IRNames::modifierInvocation(_modifierInvocation); |
424 | 82 | return m_context.functionCollector().createFunction(functionName, [&]() { |
425 | 82 | m_context.resetLocalVariables(); |
426 | 82 | Whiskers t(R"( |
427 | 82 | <astIDComment><sourceLocationComment> |
428 | 82 | function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> { |
429 | 82 | <assignRetParams> |
430 | 82 | <evalArgs> |
431 | 82 | <body> |
432 | 82 | } |
433 | 82 | <contractSourceLocationComment> |
434 | 82 | )"); |
435 | | |
436 | 82 | t("functionName", functionName); |
437 | 82 | std::vector<std::string> retParamsIn; |
438 | 82 | for (auto const& varDecl: _function.returnParameters()) |
439 | 120 | retParamsIn += m_context.addLocalVariable(*varDecl).stackSlots(); |
440 | 82 | std::vector<std::string> params = retParamsIn; |
441 | 82 | for (auto const& varDecl: _function.parameters()) |
442 | 21 | params += m_context.addLocalVariable(*varDecl).stackSlots(); |
443 | 82 | t("params", joinHumanReadable(params)); |
444 | 82 | std::vector<std::string> retParams; |
445 | 82 | std::string assignRetParams; |
446 | 202 | for (size_t i = 0; i < retParamsIn.size(); ++i) |
447 | 120 | { |
448 | 120 | retParams.emplace_back(m_context.newYulVariable()); |
449 | 120 | assignRetParams += retParams.at(i) + " := " + retParamsIn.at(i) + "\n"; |
450 | 120 | } |
451 | 82 | t("retParams", joinHumanReadable(retParams)); |
452 | 82 | t("assignRetParams", assignRetParams); |
453 | | |
454 | 82 | ModifierDefinition const* modifier = dynamic_cast<ModifierDefinition const*>( |
455 | 82 | _modifierInvocation.name().annotation().referencedDeclaration |
456 | 82 | ); |
457 | 82 | solAssert(modifier, ""); |
458 | | |
459 | 82 | if (m_context.debugInfoSelection().astID) |
460 | 82 | t("astIDComment", "/// @ast-id " + std::to_string(modifier->id()) + "\n"); |
461 | 0 | else |
462 | 0 | t("astIDComment", ""); |
463 | 82 | t("sourceLocationComment", dispenseLocationComment(*modifier)); |
464 | 82 | t( |
465 | 82 | "contractSourceLocationComment", |
466 | 82 | dispenseLocationComment(m_context.mostDerivedContract()) |
467 | 82 | ); |
468 | | |
469 | 82 | switch (*_modifierInvocation.name().annotation().requiredLookup) |
470 | 82 | { |
471 | 82 | case VirtualLookup::Virtual: |
472 | 82 | modifier = &modifier->resolveVirtual(m_context.mostDerivedContract()); |
473 | 82 | solAssert(modifier, ""); |
474 | 82 | break; |
475 | 82 | case VirtualLookup::Static: |
476 | 0 | break; |
477 | 0 | case VirtualLookup::Super: |
478 | 0 | solAssert(false, ""); |
479 | 82 | } |
480 | | |
481 | 82 | solAssert( |
482 | 82 | modifier->parameters().empty() == |
483 | 82 | (!_modifierInvocation.arguments() || _modifierInvocation.arguments()->empty()), |
484 | 82 | "" |
485 | 82 | ); |
486 | 82 | IRGeneratorForStatements expressionEvaluator(m_context, m_utils, m_optimiserSettings); |
487 | 82 | if (_modifierInvocation.arguments()) |
488 | 60 | for (size_t i = 0; i < _modifierInvocation.arguments()->size(); i++) |
489 | 29 | { |
490 | 29 | IRVariable argument = expressionEvaluator.evaluateExpression( |
491 | 29 | *_modifierInvocation.arguments()->at(i), |
492 | 29 | *modifier->parameters()[i]->annotation().type |
493 | 29 | ); |
494 | 29 | expressionEvaluator.define( |
495 | 29 | m_context.addLocalVariable(*modifier->parameters()[i]), |
496 | 29 | argument |
497 | 29 | ); |
498 | 29 | } |
499 | | |
500 | 82 | t("evalArgs", expressionEvaluator.code()); |
501 | 93 | IRGeneratorForStatements generator(m_context, m_utils, m_optimiserSettings, [&]() { |
502 | 93 | std::string ret = joinHumanReadable(retParams); |
503 | 93 | return |
504 | 93 | (ret.empty() ? "" : ret + " := ") + |
505 | 93 | _nextFunction + "(" + joinHumanReadable(params) + ")\n"; |
506 | 93 | }); |
507 | 82 | generator.generate(modifier->body()); |
508 | 82 | t("body", generator.code()); |
509 | 82 | return t.render(); |
510 | 82 | }); |
511 | 82 | } |
512 | | |
513 | | std::string IRGenerator::generateFunctionWithModifierInner(FunctionDefinition const& _function) |
514 | 60 | { |
515 | 60 | std::string functionName = IRNames::functionWithModifierInner(_function); |
516 | 60 | return m_context.functionCollector().createFunction(functionName, [&]() { |
517 | 60 | m_context.resetLocalVariables(); |
518 | 60 | Whiskers t(R"( |
519 | 60 | <sourceLocationComment> |
520 | 60 | function <functionName>(<params>)<?+retParams> -> <retParams></+retParams> { |
521 | 60 | <assignRetParams> |
522 | 60 | <body> |
523 | 60 | } |
524 | 60 | <contractSourceLocationComment> |
525 | 60 | )"); |
526 | 60 | t("sourceLocationComment", dispenseLocationComment(_function)); |
527 | 60 | t( |
528 | 60 | "contractSourceLocationComment", |
529 | 60 | dispenseLocationComment(m_context.mostDerivedContract()) |
530 | 60 | ); |
531 | 60 | t("functionName", functionName); |
532 | 60 | std::vector<std::string> retParams; |
533 | 60 | std::vector<std::string> retParamsIn; |
534 | 60 | for (auto const& varDecl: _function.returnParameters()) |
535 | 90 | retParams += m_context.addLocalVariable(*varDecl).stackSlots(); |
536 | 60 | std::string assignRetParams; |
537 | 150 | for (size_t i = 0; i < retParams.size(); ++i) |
538 | 90 | { |
539 | 90 | retParamsIn.emplace_back(m_context.newYulVariable()); |
540 | 90 | assignRetParams += retParams.at(i) + " := " + retParamsIn.at(i) + "\n"; |
541 | 90 | } |
542 | 60 | std::vector<std::string> params = retParamsIn; |
543 | 60 | for (auto const& varDecl: _function.parameters()) |
544 | 19 | params += m_context.addLocalVariable(*varDecl).stackSlots(); |
545 | 60 | t("params", joinHumanReadable(params)); |
546 | 60 | t("retParams", joinHumanReadable(retParams)); |
547 | 60 | t("assignRetParams", assignRetParams); |
548 | 60 | t("body", generate(_function.body())); |
549 | 60 | return t.render(); |
550 | 60 | }); |
551 | 60 | } |
552 | | |
553 | | std::string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) |
554 | 934 | { |
555 | 934 | std::string functionName = IRNames::function(_varDecl); |
556 | 934 | return m_context.functionCollector().createFunction(functionName, [&]() { |
557 | 934 | Type const* type = _varDecl.annotation().type; |
558 | | |
559 | 934 | solAssert(_varDecl.isStateVariable(), ""); |
560 | | |
561 | 934 | FunctionType accessorType(_varDecl); |
562 | 934 | TypePointers paramTypes = accessorType.parameterTypes(); |
563 | 934 | if (_varDecl.immutable()) |
564 | 38 | { |
565 | 38 | solAssert(paramTypes.empty(), ""); |
566 | 38 | solUnimplementedAssert(type->sizeOnStack() == 1); |
567 | | |
568 | 38 | auto t = Whiskers(R"( |
569 | 38 | <astIDComment><sourceLocationComment> |
570 | 38 | function <functionName>() -> rval { |
571 | 38 | <?eof> |
572 | 38 | rval := auxdataloadn(<immutableOffset>) |
573 | 38 | <!eof> |
574 | 38 | rval := loadimmutable("<id>") |
575 | 38 | </eof> |
576 | 38 | } |
577 | 38 | <contractSourceLocationComment> |
578 | 38 | )"); |
579 | 38 | t( |
580 | 38 | "astIDComment", |
581 | 38 | m_context.debugInfoSelection().astID ? |
582 | 38 | "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : |
583 | 38 | "" |
584 | 38 | ); |
585 | 38 | t("sourceLocationComment", dispenseLocationComment(_varDecl)); |
586 | 38 | t( |
587 | 38 | "contractSourceLocationComment", |
588 | 38 | dispenseLocationComment(m_context.mostDerivedContract()) |
589 | 38 | ); |
590 | 38 | t("functionName", functionName); |
591 | | |
592 | 38 | auto const eof = m_context.eofVersion().has_value(); |
593 | 38 | t("eof", eof); |
594 | 38 | if (!eof) |
595 | 38 | t("id", std::to_string(_varDecl.id())); |
596 | 0 | else |
597 | 0 | t("immutableOffset", std::to_string(m_context.immutableMemoryOffsetRelative(_varDecl))); |
598 | | |
599 | 38 | return t.render(); |
600 | 38 | } |
601 | 896 | else if (_varDecl.isConstant()) |
602 | 0 | { |
603 | 0 | solAssert(paramTypes.empty(), ""); |
604 | 0 | return Whiskers(R"( |
605 | 0 | <astIDComment><sourceLocationComment> |
606 | 0 | function <functionName>() -> <ret> { |
607 | 0 | <ret> := <constantValueFunction>() |
608 | 0 | } |
609 | 0 | <contractSourceLocationComment> |
610 | 0 | )") |
611 | 0 | ( |
612 | 0 | "astIDComment", |
613 | 0 | m_context.debugInfoSelection().astID ? |
614 | 0 | "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : |
615 | 0 | "" |
616 | 0 | ) |
617 | 0 | ("sourceLocationComment", dispenseLocationComment(_varDecl)) |
618 | 0 | ( |
619 | 0 | "contractSourceLocationComment", |
620 | 0 | dispenseLocationComment(m_context.mostDerivedContract()) |
621 | 0 | ) |
622 | 0 | ("functionName", functionName) |
623 | 0 | ("constantValueFunction", IRGeneratorForStatements(m_context, m_utils, m_optimiserSettings).constantValueFunction(_varDecl)) |
624 | 0 | ("ret", suffixedVariableNameList("ret_", 0, _varDecl.type()->sizeOnStack())) |
625 | 0 | .render(); |
626 | 0 | } |
627 | | |
628 | 896 | std::string code; |
629 | | |
630 | 896 | auto const& location = m_context.storageLocationOfStateVariable(_varDecl); |
631 | 896 | code += Whiskers(R"( |
632 | 896 | let slot := <slot> |
633 | 896 | let offset := <offset> |
634 | 896 | )") |
635 | 896 | ("slot", location.first.str()) |
636 | 896 | ("offset", std::to_string(location.second)) |
637 | 896 | .render(); |
638 | | |
639 | 896 | if (!paramTypes.empty()) |
640 | 896 | solAssert( |
641 | 896 | location.second == 0, |
642 | 896 | "If there are parameters, we are dealing with structs or mappings and thus should have offset zero." |
643 | 896 | ); |
644 | | |
645 | | // The code of an accessor is of the form `x[a][b][c]` (it is slightly more complicated |
646 | | // if the final type is a struct). |
647 | | // In each iteration of the loop below, we consume one parameter, perform an |
648 | | // index access, reassign the yul variable `slot` and move @a currentType further "down". |
649 | | // The initial value of @a currentType is only used if we skip the loop completely. |
650 | 896 | Type const* currentType = _varDecl.annotation().type; |
651 | | |
652 | 896 | std::vector<std::string> parameters; |
653 | 896 | std::vector<std::string> returnVariables; |
654 | | |
655 | 1.31k | for (size_t i = 0; i < paramTypes.size(); ++i) |
656 | 417 | { |
657 | 417 | MappingType const* mappingType = dynamic_cast<MappingType const*>(currentType); |
658 | 417 | ArrayType const* arrayType = dynamic_cast<ArrayType const*>(currentType); |
659 | 417 | solAssert(mappingType || arrayType, ""); |
660 | | |
661 | 417 | std::vector<std::string> keys = IRVariable("key_" + std::to_string(i), |
662 | 417 | mappingType ? *mappingType->keyType() : *TypeProvider::uint256() |
663 | 417 | ).stackSlots(); |
664 | 417 | parameters += keys; |
665 | | |
666 | 417 | Whiskers templ(R"( |
667 | 417 | <?array> |
668 | 417 | if iszero(lt(<keys>, <length>(slot))) { revert(0, 0) } |
669 | 417 | </array> |
670 | 417 | slot<?array>, offset</array> := <indexAccess>(slot<?+keys>, <keys></+keys>) |
671 | 417 | )"); |
672 | 417 | templ( |
673 | 417 | "indexAccess", |
674 | 417 | mappingType ? |
675 | 88 | m_utils.mappingIndexAccessFunction(*mappingType, *mappingType->keyType()) : |
676 | 417 | m_utils.storageArrayIndexAccessFunction(*arrayType) |
677 | 417 | ) |
678 | 417 | ("array", arrayType != nullptr) |
679 | 417 | ("keys", joinHumanReadable(keys)); |
680 | 417 | if (arrayType) |
681 | 329 | templ("length", m_utils.arrayLengthFunction(*arrayType)); |
682 | | |
683 | 417 | code += templ.render(); |
684 | | |
685 | 417 | currentType = mappingType ? mappingType->valueType() : arrayType->baseType(); |
686 | 417 | } |
687 | | |
688 | 896 | auto returnTypes = accessorType.returnParameterTypes(); |
689 | 896 | solAssert(returnTypes.size() >= 1, ""); |
690 | 896 | if (StructType const* structType = dynamic_cast<StructType const*>(currentType)) |
691 | 6 | { |
692 | 6 | solAssert(location.second == 0, ""); |
693 | 6 | auto const& names = accessorType.returnParameterNames(); |
694 | 24 | for (size_t i = 0; i < names.size(); ++i) |
695 | 18 | { |
696 | 18 | if (returnTypes[i]->category() == Type::Category::Mapping) |
697 | 0 | continue; |
698 | 18 | if ( |
699 | 18 | auto const* arrayType = dynamic_cast<ArrayType const*>(returnTypes[i]); |
700 | 18 | arrayType && !arrayType->isByteArrayOrString() |
701 | 18 | ) |
702 | 0 | continue; |
703 | | |
704 | 18 | std::pair<u256, unsigned> const& offsets = structType->storageOffsetsOfMember(names[i]); |
705 | 18 | std::vector<std::string> retVars = IRVariable("ret_" + std::to_string(returnVariables.size()), *returnTypes[i]).stackSlots(); |
706 | 18 | returnVariables += retVars; |
707 | 18 | code += Whiskers(R"( |
708 | 18 | <ret> := <readStorage>(add(slot, <slotOffset>)) |
709 | 18 | )") |
710 | 18 | ("ret", joinHumanReadable(retVars)) |
711 | 18 | ("readStorage", m_utils.readFromStorage(*returnTypes[i], offsets.second, true, _varDecl.referenceLocation())) |
712 | 18 | ("slotOffset", offsets.first.str()) |
713 | 18 | .render(); |
714 | 18 | } |
715 | 6 | } |
716 | 890 | else |
717 | 890 | { |
718 | 890 | solAssert(returnTypes.size() == 1, ""); |
719 | 890 | auto const* arrayType = dynamic_cast<ArrayType const*>(returnTypes.front()); |
720 | 890 | if (arrayType) |
721 | 890 | solAssert(arrayType->isByteArrayOrString(), ""); |
722 | 890 | std::vector<std::string> retVars = IRVariable("ret", *returnTypes.front()).stackSlots(); |
723 | 890 | returnVariables += retVars; |
724 | 890 | code += Whiskers(R"( |
725 | 890 | <ret> := <readStorage>(slot, offset) |
726 | 890 | )") |
727 | 890 | ("ret", joinHumanReadable(retVars)) |
728 | 890 | ("readStorage", m_utils.readFromStorageDynamic(*returnTypes.front(), true, _varDecl.referenceLocation())) |
729 | 890 | .render(); |
730 | 890 | } |
731 | | |
732 | 896 | return Whiskers(R"( |
733 | 896 | <astIDComment><sourceLocationComment> |
734 | 896 | function <functionName>(<params>) -> <retVariables> { |
735 | 896 | <code> |
736 | 896 | } |
737 | 896 | <contractSourceLocationComment> |
738 | 896 | )") |
739 | 896 | ("functionName", functionName) |
740 | 896 | ("params", joinHumanReadable(parameters)) |
741 | 896 | ("retVariables", joinHumanReadable(returnVariables)) |
742 | 896 | ("code", std::move(code)) |
743 | 896 | ( |
744 | 896 | "astIDComment", |
745 | 896 | m_context.debugInfoSelection().astID ? |
746 | 896 | "/// @ast-id " + std::to_string(_varDecl.id()) + "\n" : |
747 | 896 | "" |
748 | 896 | ) |
749 | 896 | ("sourceLocationComment", dispenseLocationComment(_varDecl)) |
750 | 896 | ( |
751 | 896 | "contractSourceLocationComment", |
752 | 896 | dispenseLocationComment(m_context.mostDerivedContract()) |
753 | 896 | ) |
754 | 896 | .render(); |
755 | 896 | }); |
756 | 934 | } |
757 | | |
758 | | std::string IRGenerator::generateExternalFunction(ContractDefinition const& _contract, FunctionType const& _functionType) |
759 | 4.02k | { |
760 | 4.02k | std::string functionName = IRNames::externalFunctionABIWrapper(_functionType.declaration()); |
761 | 4.02k | return m_context.functionCollector().createFunction(functionName, [&](std::vector<std::string>&, std::vector<std::string>&) -> std::string { |
762 | 4.02k | Whiskers t(R"X( |
763 | 4.02k | <callValueCheck> |
764 | 4.02k | <?+params>let <params> := </+params> <abiDecode>(4, calldatasize()) |
765 | 4.02k | <?+retParams>let <retParams> := </+retParams> <function>(<params>) |
766 | 4.02k | let memPos := <allocateUnbounded>() |
767 | 4.02k | let memEnd := <abiEncode>(memPos <?+retParams>,</+retParams> <retParams>) |
768 | 4.02k | return(memPos, sub(memEnd, memPos)) |
769 | 4.02k | )X"); |
770 | 4.02k | t("callValueCheck", (_functionType.isPayable() || _contract.isLibrary()) ? "" : callValueCheck()); |
771 | | |
772 | 4.02k | unsigned paramVars = std::make_shared<TupleType>(_functionType.parameterTypes())->sizeOnStack(); |
773 | 4.02k | unsigned retVars = std::make_shared<TupleType>(_functionType.returnParameterTypes())->sizeOnStack(); |
774 | | |
775 | 4.02k | ABIFunctions abiFunctions(m_evmVersion, m_eofVersion, m_context.revertStrings(), m_context.functionCollector()); |
776 | 4.02k | t("abiDecode", abiFunctions.tupleDecoder(_functionType.parameterTypes())); |
777 | 4.02k | t("params", suffixedVariableNameList("param_", 0, paramVars)); |
778 | 4.02k | t("retParams", suffixedVariableNameList("ret_", 0, retVars)); |
779 | | |
780 | 4.02k | if (FunctionDefinition const* funDef = dynamic_cast<FunctionDefinition const*>(&_functionType.declaration())) |
781 | 3.09k | { |
782 | 3.09k | solAssert(!funDef->isConstructor()); |
783 | 3.09k | t("function", m_context.enqueueFunctionForCodeGeneration(*funDef)); |
784 | 3.09k | } |
785 | 936 | else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(&_functionType.declaration())) |
786 | 934 | t("function", generateGetter(*varDecl)); |
787 | 2 | else |
788 | 936 | solAssert(false, "Unexpected declaration for function!"); |
789 | | |
790 | 4.02k | t("allocateUnbounded", m_utils.allocateUnboundedFunction()); |
791 | 4.02k | t("abiEncode", abiFunctions.tupleEncoder(_functionType.returnParameterTypes(), _functionType.returnParameterTypes(), _contract.isLibrary())); |
792 | 4.02k | return t.render(); |
793 | 4.02k | }); |
794 | 4.02k | } |
795 | | |
796 | | std::string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl) |
797 | 1.96k | { |
798 | 1.96k | IRGeneratorForStatements generator(m_context, m_utils, m_optimiserSettings); |
799 | 1.96k | generator.initializeLocalVar(_varDecl); |
800 | 1.96k | return generator.code(); |
801 | 1.96k | } |
802 | | |
803 | | std::pair<std::string, std::map<ContractDefinition const*, std::vector<std::string>>> IRGenerator::evaluateConstructorArguments( |
804 | | ContractDefinition const& _contract |
805 | | ) |
806 | 4.07k | { |
807 | 4.07k | struct InheritanceOrder |
808 | 4.07k | { |
809 | 4.07k | bool operator()(ContractDefinition const* _c1, ContractDefinition const* _c2) const |
810 | 4.07k | { |
811 | 118 | solAssert(util::contains(linearizedBaseContracts, _c1) && util::contains(linearizedBaseContracts, _c2), ""); |
812 | 118 | auto it1 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c1); |
813 | 118 | auto it2 = find(linearizedBaseContracts.begin(), linearizedBaseContracts.end(), _c2); |
814 | 118 | return it1 < it2; |
815 | 118 | } |
816 | 4.07k | std::vector<ContractDefinition const*> const& linearizedBaseContracts; |
817 | 4.07k | } inheritanceOrder{_contract.annotation().linearizedBaseContracts}; |
818 | | |
819 | 4.07k | std::map<ContractDefinition const*, std::vector<std::string>> constructorParams; |
820 | | |
821 | 4.07k | std::map<ContractDefinition const*, std::vector<ASTPointer<Expression>>const *, InheritanceOrder> |
822 | 4.07k | baseConstructorArguments(inheritanceOrder); |
823 | | |
824 | 4.07k | for (ASTPointer<InheritanceSpecifier> const& base: _contract.baseContracts()) |
825 | 131 | if (FunctionDefinition const* baseConstructor = dynamic_cast<ContractDefinition const*>( |
826 | 131 | base->name().annotation().referencedDeclaration |
827 | 131 | )->constructor(); baseConstructor && base->arguments()) |
828 | 131 | solAssert(baseConstructorArguments.emplace( |
829 | 4.07k | dynamic_cast<ContractDefinition const*>(baseConstructor->scope()), |
830 | 4.07k | base->arguments() |
831 | 4.07k | ).second, ""); |
832 | | |
833 | 4.07k | if (FunctionDefinition const* constructor = _contract.constructor()) |
834 | 595 | for (ASTPointer<ModifierInvocation> const& modifier: constructor->modifiers()) |
835 | 94 | if (auto const* baseContract = dynamic_cast<ContractDefinition const*>( |
836 | 94 | modifier->name().annotation().referencedDeclaration |
837 | 94 | )) |
838 | 92 | if ( |
839 | 92 | FunctionDefinition const* baseConstructor = baseContract->constructor(); |
840 | 92 | baseConstructor && modifier->arguments() |
841 | 92 | ) |
842 | 92 | solAssert(baseConstructorArguments.emplace( |
843 | 4.07k | dynamic_cast<ContractDefinition const*>(baseConstructor->scope()), |
844 | 4.07k | modifier->arguments() |
845 | 4.07k | ).second, ""); |
846 | | |
847 | 4.07k | IRGeneratorForStatements generator{m_context, m_utils, m_optimiserSettings}; |
848 | 4.07k | for (auto&& [baseContract, arguments]: baseConstructorArguments) |
849 | 90 | { |
850 | 90 | solAssert(baseContract && arguments, ""); |
851 | 90 | if (baseContract->constructor() && !arguments->empty()) |
852 | 90 | { |
853 | 90 | std::vector<std::string> params; |
854 | 191 | for (size_t i = 0; i < arguments->size(); ++i) |
855 | 101 | params += generator.evaluateExpression( |
856 | 101 | *(arguments->at(i)), |
857 | 101 | *(baseContract->constructor()->parameters()[i]->type()) |
858 | 101 | ).stackSlots(); |
859 | 90 | constructorParams[baseContract] = std::move(params); |
860 | 90 | } |
861 | 90 | } |
862 | | |
863 | 4.07k | return {generator.code(), constructorParams}; |
864 | 4.07k | } |
865 | | |
866 | | std::string IRGenerator::initStateVariables(ContractDefinition const& _contract) |
867 | 4.07k | { |
868 | 4.07k | IRGeneratorForStatements generator{m_context, m_utils, m_optimiserSettings}; |
869 | 4.07k | for (VariableDeclaration const* variable: _contract.stateVariables()) |
870 | 1.92k | { |
871 | 1.92k | if (!variable->isConstant()) |
872 | 1.89k | generator.initializeStateVar(*variable); |
873 | 1.92k | } |
874 | | |
875 | 4.07k | return generator.code(); |
876 | 4.07k | } |
877 | | |
878 | | |
879 | | void IRGenerator::generateConstructors(ContractDefinition const& _contract) |
880 | 3.94k | { |
881 | 3.94k | auto listAllParams = |
882 | 3.94k | [&](std::map<ContractDefinition const*, std::vector<std::string>> const& baseParams) -> std::vector<std::string> |
883 | 4.20k | { |
884 | 4.20k | std::vector<std::string> params; |
885 | 4.20k | for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) |
886 | 4.91k | if (baseParams.count(contract)) |
887 | 250 | params += baseParams.at(contract); |
888 | 4.20k | return params; |
889 | 4.20k | }; |
890 | | |
891 | 3.94k | std::map<ContractDefinition const*, std::vector<std::string>> baseConstructorParams; |
892 | 8.01k | for (size_t i = 0; i < _contract.annotation().linearizedBaseContracts.size(); ++i) |
893 | 4.07k | { |
894 | 4.07k | ContractDefinition const* contract = _contract.annotation().linearizedBaseContracts[i]; |
895 | 4.07k | baseConstructorParams.erase(contract); |
896 | | |
897 | 4.07k | m_context.resetLocalVariables(); |
898 | 4.07k | m_context.functionCollector().createFunction(IRNames::constructor(*contract), [&]() { |
899 | 4.07k | Whiskers t(R"( |
900 | 4.07k | <astIDComment><sourceLocationComment> |
901 | 4.07k | function <functionName>(<params><comma><baseParams>) { |
902 | 4.07k | <evalBaseArguments> |
903 | 4.07k | <sourceLocationComment> |
904 | 4.07k | <?hasNextConstructor> <nextConstructor>(<nextParams>) </hasNextConstructor> |
905 | 4.07k | <initStateVariables> |
906 | 4.07k | <userDefinedConstructorBody> |
907 | 4.07k | } |
908 | 4.07k | <contractSourceLocationComment> |
909 | 4.07k | )"); |
910 | 4.07k | std::vector<std::string> params; |
911 | 4.07k | if (contract->constructor()) |
912 | 595 | for (ASTPointer<VariableDeclaration> const& varDecl: contract->constructor()->parameters()) |
913 | 270 | params += m_context.addLocalVariable(*varDecl).stackSlots(); |
914 | | |
915 | 4.07k | if (m_context.debugInfoSelection().astID && contract->constructor()) |
916 | 595 | t("astIDComment", "/// @ast-id " + std::to_string(contract->constructor()->id()) + "\n"); |
917 | 3.47k | else |
918 | 3.47k | t("astIDComment", ""); |
919 | 4.07k | t("sourceLocationComment", dispenseLocationComment( |
920 | 4.07k | contract->constructor() ? |
921 | 595 | dynamic_cast<ASTNode const&>(*contract->constructor()) : |
922 | 4.07k | dynamic_cast<ASTNode const&>(*contract) |
923 | 4.07k | )); |
924 | 4.07k | t( |
925 | 4.07k | "contractSourceLocationComment", |
926 | 4.07k | dispenseLocationComment(m_context.mostDerivedContract()) |
927 | 4.07k | ); |
928 | | |
929 | 4.07k | t("params", joinHumanReadable(params)); |
930 | 4.07k | std::vector<std::string> baseParams = listAllParams(baseConstructorParams); |
931 | 4.07k | t("baseParams", joinHumanReadable(baseParams)); |
932 | 4.07k | t("comma", !params.empty() && !baseParams.empty() ? ", " : ""); |
933 | 4.07k | t("functionName", IRNames::constructor(*contract)); |
934 | 4.07k | std::pair<std::string, std::map<ContractDefinition const*, std::vector<std::string>>> evaluatedArgs = evaluateConstructorArguments(*contract); |
935 | 4.07k | baseConstructorParams.insert(evaluatedArgs.second.begin(), evaluatedArgs.second.end()); |
936 | 4.07k | t("evalBaseArguments", evaluatedArgs.first); |
937 | 4.07k | if (i < _contract.annotation().linearizedBaseContracts.size() - 1) |
938 | 130 | { |
939 | 130 | t("hasNextConstructor", true); |
940 | 130 | ContractDefinition const* nextContract = _contract.annotation().linearizedBaseContracts[i + 1]; |
941 | 130 | t("nextConstructor", IRNames::constructor(*nextContract)); |
942 | 130 | t("nextParams", joinHumanReadable(listAllParams(baseConstructorParams))); |
943 | 130 | } |
944 | 3.94k | else |
945 | 3.94k | t("hasNextConstructor", false); |
946 | 4.07k | t("initStateVariables", initStateVariables(*contract)); |
947 | 4.07k | std::string body; |
948 | 4.07k | if (FunctionDefinition const* constructor = contract->constructor()) |
949 | 595 | { |
950 | 595 | std::vector<ModifierInvocation*> realModifiers; |
951 | 595 | for (auto const& modifierInvocation: constructor->modifiers()) |
952 | | // Filter out the base constructor calls |
953 | 94 | if (dynamic_cast<ModifierDefinition const*>(modifierInvocation->name().annotation().referencedDeclaration)) |
954 | 2 | realModifiers.emplace_back(modifierInvocation.get()); |
955 | 595 | if (realModifiers.empty()) |
956 | 593 | body = generate(constructor->body()); |
957 | 2 | else |
958 | 2 | { |
959 | 4 | for (size_t i = 0; i < realModifiers.size(); ++i) |
960 | 2 | { |
961 | 2 | ModifierInvocation const& modifier = *realModifiers.at(i); |
962 | 2 | std::string next = |
963 | 2 | i + 1 < realModifiers.size() ? |
964 | 0 | IRNames::modifierInvocation(*realModifiers.at(i + 1)) : |
965 | 2 | IRNames::functionWithModifierInner(*constructor); |
966 | 2 | generateModifier(modifier, *constructor, next); |
967 | 2 | } |
968 | 2 | body = |
969 | 2 | IRNames::modifierInvocation(*realModifiers.at(0)) + |
970 | 2 | "(" + |
971 | 2 | joinHumanReadable(params) + |
972 | 2 | ")"; |
973 | | // Now generate the actual inner function. |
974 | 2 | generateFunctionWithModifierInner(*constructor); |
975 | 2 | } |
976 | 595 | } |
977 | 4.07k | t("userDefinedConstructorBody", std::move(body)); |
978 | | |
979 | 4.07k | return t.render(); |
980 | 4.07k | }); |
981 | 4.07k | } |
982 | 3.94k | } |
983 | | |
984 | | std::string IRGenerator::deployCode(ContractDefinition const& _contract) |
985 | 3.94k | { |
986 | 3.94k | Whiskers t(R"X( |
987 | 3.94k | <?eof> |
988 | 3.94k | <?library> |
989 | 3.94k | mstore(<libraryAddressImmutableOffset>, address()) |
990 | 3.94k | </library> |
991 | 3.94k | returncontract("<object>", <auxDataStart>, <auxDataSize>) |
992 | 3.94k | <!eof> |
993 | 3.94k | let <codeOffset> := <allocateUnbounded>() |
994 | 3.94k | codecopy(<codeOffset>, dataoffset("<object>"), datasize("<object>")) |
995 | 3.94k | <#immutables> |
996 | 3.94k | setimmutable(<codeOffset>, "<immutableName>", <value>) |
997 | 3.94k | </immutables> |
998 | 3.94k | return(<codeOffset>, datasize("<object>")) |
999 | 3.94k | </eof> |
1000 | 3.94k | )X"); |
1001 | 3.94k | auto const eof = m_context.eofVersion().has_value(); |
1002 | 3.94k | t("eof", eof); |
1003 | 3.94k | t("allocateUnbounded", m_utils.allocateUnboundedFunction()); |
1004 | 3.94k | t("codeOffset", m_context.newYulVariable()); |
1005 | 3.94k | t("object", IRNames::deployedObject(_contract)); |
1006 | | |
1007 | 3.94k | std::vector<std::map<std::string, std::string>> immutables; |
1008 | 3.94k | if (_contract.isLibrary()) |
1009 | 33 | { |
1010 | 33 | solAssert(ContractType(_contract).immutableVariables().empty(), ""); |
1011 | 33 | if (!eof) |
1012 | 33 | immutables.emplace_back(std::map<std::string, std::string>{ |
1013 | 33 | {"immutableName"s, IRNames::libraryAddressImmutable()}, |
1014 | 33 | {"value"s, "address()"} |
1015 | 33 | }); |
1016 | 0 | else |
1017 | 0 | t("libraryAddressImmutableOffset", std::to_string(m_context.libraryAddressImmutableOffset())); |
1018 | 33 | } |
1019 | 3.91k | else |
1020 | 3.91k | { |
1021 | 3.91k | for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) |
1022 | 90 | { |
1023 | 90 | solUnimplementedAssert(immutable->type()->isValueType()); |
1024 | 90 | solUnimplementedAssert(immutable->type()->sizeOnStack() == 1); |
1025 | 90 | if (!eof) |
1026 | 90 | immutables.emplace_back(std::map<std::string, std::string>{ |
1027 | 90 | {"immutableName"s, std::to_string(immutable->id())}, |
1028 | 90 | {"value"s, "mload(" + std::to_string(m_context.immutableMemoryOffset(*immutable)) + ")"} |
1029 | 90 | }); |
1030 | 90 | } |
1031 | 3.91k | } |
1032 | | |
1033 | 3.94k | if (eof) |
1034 | 0 | { |
1035 | 0 | t("library", _contract.isLibrary()); |
1036 | 0 | t("auxDataStart", std::to_string(CompilerUtils::generalPurposeMemoryStart)); |
1037 | 0 | solAssert(m_context.reservedMemorySize() <= 0xFFFF, "Reserved memory size exceeded maximum allowed EOF data section size."); |
1038 | 0 | t("auxDataSize", std::to_string(m_context.reservedMemorySize())); |
1039 | 0 | } |
1040 | 3.94k | else |
1041 | 3.94k | t("immutables", std::move(immutables)); |
1042 | | |
1043 | 3.94k | return t.render(); |
1044 | 3.94k | } |
1045 | | |
1046 | | std::string IRGenerator::callValueCheck() |
1047 | 7.85k | { |
1048 | 7.85k | return "if callvalue() { " + m_utils.revertReasonIfDebugFunction("Ether sent to non-payable function") + "() }"; |
1049 | 7.85k | } |
1050 | | |
1051 | | std::string IRGenerator::dispatchRoutine(ContractDefinition const& _contract) |
1052 | 3.94k | { |
1053 | 3.94k | Whiskers t(R"X( |
1054 | 3.94k | <?+cases>if iszero(lt(calldatasize(), 4)) |
1055 | 3.94k | { |
1056 | 3.94k | let selector := <shr224>(calldataload(0)) |
1057 | 3.94k | switch selector |
1058 | 3.94k | <#cases> |
1059 | 3.94k | case <functionSelector> |
1060 | 3.94k | { |
1061 | 3.94k | // <functionName> |
1062 | 3.94k | <delegatecallCheck> |
1063 | 3.94k | <externalFunction>() |
1064 | 3.94k | } |
1065 | 3.94k | </cases> |
1066 | 3.94k | default {} |
1067 | 3.94k | }</+cases> |
1068 | 3.94k | <?+receiveEther>if iszero(calldatasize()) { <receiveEther> }</+receiveEther> |
1069 | 3.94k | <fallback> |
1070 | 3.94k | )X"); |
1071 | 3.94k | t("shr224", m_utils.shiftRightFunction(224)); |
1072 | 3.94k | std::vector<std::map<std::string, std::string>> functions; |
1073 | 3.94k | for (auto const& function: _contract.interfaceFunctions()) |
1074 | 4.02k | { |
1075 | 4.02k | functions.emplace_back(); |
1076 | 4.02k | std::map<std::string, std::string>& templ = functions.back(); |
1077 | 4.02k | templ["functionSelector"] = "0x" + function.first.hex(); |
1078 | 4.02k | FunctionTypePointer const& type = function.second; |
1079 | 4.02k | templ["functionName"] = type->externalSignature(); |
1080 | 4.02k | std::string delegatecallCheck; |
1081 | 4.02k | if (_contract.isLibrary()) |
1082 | 27 | { |
1083 | 27 | solAssert(!type->isPayable(), ""); |
1084 | 27 | if (type->stateMutability() > StateMutability::View) |
1085 | | // If the function is not a view function and is called without DELEGATECALL, |
1086 | | // we revert. |
1087 | 26 | delegatecallCheck = |
1088 | 26 | "if iszero(called_via_delegatecall) { " + |
1089 | 26 | m_utils.revertReasonIfDebugFunction("Non-view function of library called without DELEGATECALL") + |
1090 | 26 | "() }"; |
1091 | 27 | } |
1092 | 4.02k | templ["delegatecallCheck"] = delegatecallCheck; |
1093 | | |
1094 | 4.02k | templ["externalFunction"] = generateExternalFunction(_contract, *type); |
1095 | 4.02k | } |
1096 | 3.94k | t("cases", functions); |
1097 | 3.94k | FunctionDefinition const* etherReceiver = _contract.receiveFunction(); |
1098 | 3.94k | if (etherReceiver) |
1099 | 13 | { |
1100 | 13 | solAssert(!_contract.isLibrary(), ""); |
1101 | 13 | t("receiveEther", m_context.enqueueFunctionForCodeGeneration(*etherReceiver) + "() stop()"); |
1102 | 13 | } |
1103 | 3.93k | else |
1104 | 3.93k | t("receiveEther", ""); |
1105 | 3.94k | if (FunctionDefinition const* fallback = _contract.fallbackFunction()) |
1106 | 18 | { |
1107 | 18 | solAssert(!_contract.isLibrary(), ""); |
1108 | 18 | std::string fallbackCode; |
1109 | 18 | if (!fallback->isPayable()) |
1110 | 13 | fallbackCode += callValueCheck() + "\n"; |
1111 | 18 | if (fallback->parameters().empty()) |
1112 | 13 | fallbackCode += m_context.enqueueFunctionForCodeGeneration(*fallback) + "() stop()"; |
1113 | 5 | else |
1114 | 5 | { |
1115 | 5 | solAssert(fallback->parameters().size() == 1 && fallback->returnParameters().size() == 1, ""); |
1116 | 5 | fallbackCode += "let retval := " + m_context.enqueueFunctionForCodeGeneration(*fallback) + "(0, calldatasize())\n"; |
1117 | 5 | fallbackCode += "return(add(retval, 0x20), mload(retval))\n"; |
1118 | | |
1119 | 5 | } |
1120 | | |
1121 | 18 | t("fallback", fallbackCode); |
1122 | 18 | } |
1123 | 3.92k | else |
1124 | 3.92k | t("fallback", ( |
1125 | 3.92k | etherReceiver ? |
1126 | 11 | m_utils.revertReasonIfDebugFunction("Unknown signature and no fallback defined") : |
1127 | 3.92k | m_utils.revertReasonIfDebugFunction("Contract does not have fallback nor receive functions") |
1128 | 3.92k | ) + "()"); |
1129 | 3.94k | return t.render(); |
1130 | 3.94k | } |
1131 | | |
1132 | | std::string IRGenerator::memoryInit(bool _useMemoryGuard) |
1133 | 7.88k | { |
1134 | | // This function should be called at the beginning of the EVM call frame |
1135 | | // and thus can assume all memory to be zero, including the contents of |
1136 | | // the "zero memory area" (the position CompilerUtils::zeroPointer points to). |
1137 | 7.88k | return |
1138 | 7.88k | Whiskers{ |
1139 | 7.88k | _useMemoryGuard ? |
1140 | 7.82k | "mstore(<memPtr>, memoryguard(<freeMemoryStart>))" : |
1141 | 7.88k | "mstore(<memPtr>, <freeMemoryStart>)" |
1142 | 7.88k | } |
1143 | 7.88k | ("memPtr", std::to_string(CompilerUtils::freeMemoryPointer)) |
1144 | 7.88k | ( |
1145 | 7.88k | "freeMemoryStart", |
1146 | 7.88k | std::to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()) |
1147 | 7.88k | ).render(); |
1148 | 7.88k | } |
1149 | | |
1150 | | void IRGenerator::resetContext(ContractDefinition const& _contract, ExecutionContext _context) |
1151 | 7.88k | { |
1152 | 7.88k | solAssert( |
1153 | 7.88k | m_context.functionGenerationQueueEmpty(), |
1154 | 7.88k | "Reset function generation queue while it still had functions." |
1155 | 7.88k | ); |
1156 | 7.88k | solAssert( |
1157 | 7.88k | m_context.functionCollector().requestedFunctions().empty(), |
1158 | 7.88k | "Reset context while it still had functions." |
1159 | 7.88k | ); |
1160 | 7.88k | solAssert( |
1161 | 7.88k | m_context.internalDispatchClean(), |
1162 | 7.88k | "Reset internal dispatch map without consuming it." |
1163 | 7.88k | ); |
1164 | 7.88k | IRGenerationContext newContext( |
1165 | 7.88k | m_evmVersion, |
1166 | 7.88k | m_eofVersion, |
1167 | 7.88k | _context, |
1168 | 7.88k | m_context.revertStrings(), |
1169 | 7.88k | m_context.sourceIndices(), |
1170 | 7.88k | m_context.debugInfoSelection(), |
1171 | 7.88k | m_context.soliditySourceProvider() |
1172 | 7.88k | ); |
1173 | | |
1174 | 7.88k | m_context = std::move(newContext); |
1175 | | |
1176 | 7.88k | m_context.setMostDerivedContract(_contract); |
1177 | 7.88k | for (auto const location: {DataLocation::Storage, DataLocation::Transient}) |
1178 | 15.7k | for (auto const& var: ContractType(_contract).linearizedStateVariables(location)) |
1179 | 3.60k | m_context.addStateVariable(*std::get<0>(var), std::get<1>(var), std::get<2>(var)); |
1180 | 7.88k | } |
1181 | | |
1182 | | std::string IRGenerator::dispenseLocationComment(ASTNode const& _node) |
1183 | 24.7k | { |
1184 | 24.7k | return ::dispenseLocationComment(_node, m_context); |
1185 | 24.7k | } |