/src/solidity/libyul/backends/wasm/WasmCodeTransform.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 | | * Common code generator for translating Yul / inline assembly to Wasm. |
20 | | */ |
21 | | |
22 | | #include <libyul/backends/wasm/WasmCodeTransform.h> |
23 | | |
24 | | #include <libyul/backends/wasm/WasmDialect.h> |
25 | | |
26 | | #include <libyul/AST.h> |
27 | | #include <libyul/Dialect.h> |
28 | | #include <libyul/Utilities.h> |
29 | | #include <libyul/Exceptions.h> |
30 | | |
31 | | #include <liblangutil/Exceptions.h> |
32 | | |
33 | | #include <optional> |
34 | | #include <limits> |
35 | | |
36 | | using namespace std; |
37 | | using namespace solidity; |
38 | | using namespace solidity::yul; |
39 | | using namespace solidity::util; |
40 | | |
41 | | wasm::Module WasmCodeTransform::run(Dialect const& _dialect, yul::Block const& _ast) |
42 | 0 | { |
43 | 0 | wasm::Module module; |
44 | |
|
45 | 0 | TypeInfo typeInfo(_dialect, _ast); |
46 | 0 | WasmCodeTransform transform(_dialect, _ast, typeInfo); |
47 | |
|
48 | 0 | for (auto const& statement: _ast.statements) |
49 | 0 | { |
50 | 0 | yulAssert( |
51 | 0 | holds_alternative<yul::FunctionDefinition>(statement), |
52 | 0 | "Expected only function definitions at the highest level." |
53 | 0 | ); |
54 | 0 | if (holds_alternative<yul::FunctionDefinition>(statement)) |
55 | 0 | module.functions.emplace_back(transform.translateFunction(std::get<yul::FunctionDefinition>(statement))); |
56 | 0 | } |
57 | | |
58 | 0 | for (auto& imp: transform.m_functionsToImport) |
59 | 0 | module.imports.emplace_back(std::move(imp.second)); |
60 | 0 | module.globals = transform.m_globalVariables; |
61 | |
|
62 | 0 | return module; |
63 | 0 | } |
64 | | |
65 | | wasm::Expression WasmCodeTransform::generateMultiAssignment( |
66 | | vector<string> _variableNames, |
67 | | unique_ptr<wasm::Expression> _firstValue |
68 | | ) |
69 | 0 | { |
70 | 0 | yulAssert(!_variableNames.empty(), ""); |
71 | 0 | wasm::LocalAssignment assignment{move(_variableNames.front()), std::move(_firstValue)}; |
72 | |
|
73 | 0 | if (_variableNames.size() == 1) |
74 | 0 | return { std::move(assignment) }; |
75 | | |
76 | 0 | vector<wasm::Type> typesForGlobals; |
77 | 0 | for (size_t i = 1; i < _variableNames.size(); ++i) |
78 | 0 | typesForGlobals.push_back(translatedType(m_typeInfo.typeOfVariable(YulString(_variableNames[i])))); |
79 | 0 | vector<size_t> allocatedIndices = allocateGlobals(typesForGlobals); |
80 | 0 | yulAssert(allocatedIndices.size() == _variableNames.size() - 1, ""); |
81 | | |
82 | 0 | wasm::Block block; |
83 | 0 | block.statements.emplace_back(move(assignment)); |
84 | 0 | for (size_t i = 1; i < _variableNames.size(); ++i) |
85 | 0 | block.statements.emplace_back(wasm::LocalAssignment{ |
86 | 0 | move(_variableNames.at(i)), |
87 | 0 | make_unique<wasm::Expression>(wasm::GlobalVariable{m_globalVariables.at(allocatedIndices[i - 1]).variableName}) |
88 | 0 | }); |
89 | 0 | return { std::move(block) }; |
90 | 0 | } |
91 | | |
92 | | wasm::Expression WasmCodeTransform::operator()(yul::VariableDeclaration const& _varDecl) |
93 | 0 | { |
94 | 0 | vector<string> variableNames; |
95 | 0 | for (auto const& var: _varDecl.variables) |
96 | 0 | { |
97 | 0 | variableNames.emplace_back(var.name.str()); |
98 | 0 | m_localVariables.emplace_back(wasm::VariableDeclaration{variableNames.back(), translatedType(var.type)}); |
99 | 0 | } |
100 | |
|
101 | 0 | if (_varDecl.value) |
102 | 0 | return generateMultiAssignment(move(variableNames), visit(*_varDecl.value)); |
103 | 0 | else |
104 | 0 | return wasm::BuiltinCall{"nop", {}}; |
105 | 0 | } |
106 | | |
107 | | wasm::Expression WasmCodeTransform::operator()(yul::Assignment const& _assignment) |
108 | 0 | { |
109 | 0 | vector<string> variableNames; |
110 | 0 | for (auto const& var: _assignment.variableNames) |
111 | 0 | variableNames.emplace_back(var.name.str()); |
112 | 0 | return generateMultiAssignment(move(variableNames), visit(*_assignment.value)); |
113 | 0 | } |
114 | | |
115 | | wasm::Expression WasmCodeTransform::operator()(yul::ExpressionStatement const& _statement) |
116 | 0 | { |
117 | 0 | return visitReturnByValue(_statement.expression); |
118 | 0 | } |
119 | | |
120 | | void WasmCodeTransform::importBuiltinFunction(BuiltinFunction const* _builtin, string const& _module, string const& _externalName, string const& _internalName) |
121 | 0 | { |
122 | 0 | yulAssert(_builtin, ""); |
123 | 0 | yulAssert(_builtin->returns.size() <= 1, ""); |
124 | | // Imported function, use regular call, but mark for import. |
125 | 0 | YulString internalName(_internalName); |
126 | 0 | if (!m_functionsToImport.count(internalName)) |
127 | 0 | { |
128 | 0 | wasm::FunctionImport imp{ |
129 | 0 | _module, |
130 | 0 | _externalName, |
131 | 0 | _internalName, |
132 | 0 | {}, |
133 | 0 | _builtin->returns.empty() ? nullopt : make_optional<wasm::Type>(translatedType(_builtin->returns.front())) |
134 | 0 | }; |
135 | 0 | for (auto const& param: _builtin->parameters) |
136 | 0 | imp.paramTypes.emplace_back(translatedType(param)); |
137 | 0 | m_functionsToImport[internalName] = move(imp); |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | | wasm::Expression WasmCodeTransform::operator()(yul::FunctionCall const& _call) |
142 | 0 | { |
143 | 0 | if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) |
144 | 0 | { |
145 | 0 | if (_call.functionName.name.str().substr(0, 6) == "debug.") |
146 | 0 | importBuiltinFunction(builtin, "debug", builtin->name.str().substr(6), builtin->name.str()); |
147 | 0 | else if (_call.functionName.name.str().substr(0, 4) == "eth.") |
148 | 0 | importBuiltinFunction(builtin, "ethereum", builtin->name.str().substr(4), builtin->name.str()); |
149 | 0 | else |
150 | 0 | { |
151 | 0 | vector<wasm::Expression> arguments; |
152 | 0 | for (size_t i = 0; i < _call.arguments.size(); i++) |
153 | 0 | if (builtin->literalArgument(i)) |
154 | 0 | { |
155 | 0 | yulAssert(builtin->literalArgument(i) == LiteralKind::String, ""); |
156 | 0 | arguments.emplace_back(wasm::StringLiteral{std::get<Literal>(_call.arguments[i]).value.str()}); |
157 | 0 | } |
158 | 0 | else |
159 | 0 | arguments.emplace_back(visitReturnByValue(_call.arguments[i])); |
160 | | |
161 | 0 | return wasm::BuiltinCall{_call.functionName.name.str(), std::move(arguments)}; |
162 | 0 | } |
163 | 0 | } |
164 | | |
165 | | // If this function returns multiple values, then the first one will |
166 | | // be returned in the expression itself and the others in global variables. |
167 | | // The values have to be used right away in an assignment or variable declaration, |
168 | | // so it is handled there. |
169 | | |
170 | 0 | return wasm::FunctionCall{_call.functionName.name.str(), visit(_call.arguments)}; |
171 | 0 | } |
172 | | |
173 | | wasm::Expression WasmCodeTransform::operator()(yul::Identifier const& _identifier) |
174 | 0 | { |
175 | 0 | return wasm::LocalVariable{_identifier.name.str()}; |
176 | 0 | } |
177 | | |
178 | | wasm::Expression WasmCodeTransform::operator()(yul::Literal const& _literal) |
179 | 0 | { |
180 | 0 | return makeLiteral(translatedType(_literal.type), valueOfLiteral(_literal)); |
181 | 0 | } |
182 | | |
183 | | wasm::Expression WasmCodeTransform::operator()(yul::If const& _if) |
184 | 0 | { |
185 | 0 | yul::Type conditionType = m_typeInfo.typeOf(*_if.condition); |
186 | |
|
187 | 0 | wasm::Expression condition; |
188 | 0 | if (conditionType == "i32"_yulstring) |
189 | 0 | condition = visitReturnByValue(*_if.condition); |
190 | 0 | else if (conditionType == "i64"_yulstring) |
191 | 0 | { |
192 | 0 | vector<wasm::Expression> args; |
193 | 0 | args.emplace_back(visitReturnByValue(*_if.condition)); |
194 | 0 | args.emplace_back(makeLiteral(translatedType("i64"_yulstring), 0)); |
195 | | |
196 | | // NOTE: `if` in wasm requires an i32 argument |
197 | 0 | condition = wasm::BuiltinCall{"i64.ne", std::move(args)}; |
198 | 0 | } |
199 | 0 | else |
200 | 0 | yulAssert(false, "Invalid condition type"); |
201 | | |
202 | 0 | return wasm::If{make_unique<wasm::Expression>(move(condition)), visit(_if.body.statements), {}}; |
203 | 0 | } |
204 | | |
205 | | wasm::Expression WasmCodeTransform::operator()(yul::Switch const& _switch) |
206 | 0 | { |
207 | 0 | yul::Type expressionType = m_typeInfo.typeOf(*_switch.expression); |
208 | 0 | YulString eq_instruction = YulString(expressionType.str() + ".eq"); |
209 | 0 | yulAssert(WasmDialect::instance().builtin(eq_instruction), ""); |
210 | | |
211 | 0 | wasm::Block block; |
212 | 0 | string condition = m_nameDispenser.newName("condition"_yulstring).str(); |
213 | 0 | m_localVariables.emplace_back(wasm::VariableDeclaration{condition, translatedType(expressionType)}); |
214 | 0 | block.statements.emplace_back(wasm::LocalAssignment{condition, visit(*_switch.expression)}); |
215 | |
|
216 | 0 | vector<wasm::Expression>* currentBlock = &block.statements; |
217 | 0 | for (size_t i = 0; i < _switch.cases.size(); ++i) |
218 | 0 | { |
219 | 0 | Case const& c = _switch.cases.at(i); |
220 | 0 | if (c.value) |
221 | 0 | { |
222 | 0 | wasm::BuiltinCall comparison{eq_instruction.str(), make_vector<wasm::Expression>( |
223 | 0 | wasm::LocalVariable{condition}, |
224 | 0 | visitReturnByValue(*c.value) |
225 | 0 | )}; |
226 | 0 | wasm::If ifStmnt{ |
227 | 0 | make_unique<wasm::Expression>(move(comparison)), |
228 | 0 | visit(c.body.statements), |
229 | 0 | {} |
230 | 0 | }; |
231 | 0 | vector<wasm::Expression>* nextBlock = nullptr; |
232 | 0 | if (i != _switch.cases.size() - 1) |
233 | 0 | { |
234 | 0 | ifStmnt.elseStatements = make_unique<vector<wasm::Expression>>(); |
235 | 0 | nextBlock = ifStmnt.elseStatements.get(); |
236 | 0 | } |
237 | 0 | currentBlock->emplace_back(move(ifStmnt)); |
238 | 0 | currentBlock = nextBlock; |
239 | 0 | } |
240 | 0 | else |
241 | 0 | { |
242 | 0 | yulAssert(i == _switch.cases.size() - 1, "Default case must be last."); |
243 | 0 | *currentBlock += visit(c.body.statements); |
244 | 0 | } |
245 | 0 | } |
246 | 0 | return { std::move(block) }; |
247 | 0 | } |
248 | | |
249 | | wasm::Expression WasmCodeTransform::operator()(yul::FunctionDefinition const&) |
250 | 0 | { |
251 | 0 | yulAssert(false, "Should not have visited here."); |
252 | 0 | return {}; |
253 | 0 | } |
254 | | |
255 | | wasm::Expression WasmCodeTransform::operator()(yul::ForLoop const& _for) |
256 | 0 | { |
257 | 0 | string breakLabel = newLabel(); |
258 | 0 | string continueLabel = newLabel(); |
259 | 0 | m_breakContinueLabelNames.push({breakLabel, continueLabel}); |
260 | |
|
261 | 0 | yul::Type conditionType = m_typeInfo.typeOf(*_for.condition); |
262 | 0 | YulString eqz_instruction = YulString(conditionType.str() + ".eqz"); |
263 | 0 | yulAssert(WasmDialect::instance().builtin(eqz_instruction), ""); |
264 | | |
265 | 0 | std::vector<wasm::Expression> statements = visit(_for.pre.statements); |
266 | |
|
267 | 0 | wasm::Loop loop; |
268 | 0 | loop.labelName = newLabel(); |
269 | 0 | loop.statements.emplace_back(wasm::BranchIf{wasm::Label{breakLabel}, make_unique<wasm::Expression>( |
270 | 0 | wasm::BuiltinCall{eqz_instruction.str(), make_vector<wasm::Expression>( |
271 | 0 | visitReturnByValue(*_for.condition) |
272 | 0 | )} |
273 | 0 | )}); |
274 | 0 | loop.statements.emplace_back(wasm::Block{continueLabel, visit(_for.body.statements)}); |
275 | 0 | loop.statements += visit(_for.post.statements); |
276 | 0 | loop.statements.emplace_back(wasm::Branch{wasm::Label{loop.labelName}}); |
277 | |
|
278 | 0 | statements += make_vector<wasm::Expression>(move(loop)); |
279 | 0 | return wasm::Block{breakLabel, move(statements)}; |
280 | 0 | } |
281 | | |
282 | | wasm::Expression WasmCodeTransform::operator()(yul::Break const&) |
283 | 0 | { |
284 | 0 | yulAssert(m_breakContinueLabelNames.size() > 0, ""); |
285 | 0 | return wasm::Branch{wasm::Label{m_breakContinueLabelNames.top().first}}; |
286 | 0 | } |
287 | | |
288 | | wasm::Expression WasmCodeTransform::operator()(yul::Continue const&) |
289 | 0 | { |
290 | 0 | yulAssert(m_breakContinueLabelNames.size() > 0, ""); |
291 | 0 | return wasm::Branch{wasm::Label{m_breakContinueLabelNames.top().second}}; |
292 | 0 | } |
293 | | |
294 | | wasm::Expression WasmCodeTransform::operator()(yul::Leave const&) |
295 | 0 | { |
296 | 0 | yulAssert(!m_functionBodyLabel.empty(), ""); |
297 | 0 | return wasm::Branch{wasm::Label{m_functionBodyLabel}}; |
298 | 0 | } |
299 | | |
300 | | wasm::Expression WasmCodeTransform::operator()(yul::Block const& _block) |
301 | 0 | { |
302 | 0 | return wasm::Block{{}, visit(_block.statements)}; |
303 | 0 | } |
304 | | |
305 | | unique_ptr<wasm::Expression> WasmCodeTransform::visit(yul::Expression const& _expression) |
306 | 0 | { |
307 | 0 | return make_unique<wasm::Expression>(std::visit(*this, _expression)); |
308 | 0 | } |
309 | | |
310 | | wasm::Expression WasmCodeTransform::visitReturnByValue(yul::Expression const& _expression) |
311 | 0 | { |
312 | 0 | return std::visit(*this, _expression); |
313 | 0 | } |
314 | | |
315 | | vector<wasm::Expression> WasmCodeTransform::visit(vector<yul::Expression> const& _expressions) |
316 | 0 | { |
317 | 0 | vector<wasm::Expression> ret; |
318 | 0 | for (auto const& e: _expressions) |
319 | 0 | ret.emplace_back(visitReturnByValue(e)); |
320 | 0 | return ret; |
321 | 0 | } |
322 | | |
323 | | wasm::Expression WasmCodeTransform::visit(yul::Statement const& _statement) |
324 | 0 | { |
325 | 0 | return std::visit(*this, _statement); |
326 | 0 | } |
327 | | |
328 | | vector<wasm::Expression> WasmCodeTransform::visit(vector<yul::Statement> const& _statements) |
329 | 0 | { |
330 | 0 | vector<wasm::Expression> ret; |
331 | 0 | for (auto const& s: _statements) |
332 | 0 | ret.emplace_back(visit(s)); |
333 | 0 | return ret; |
334 | 0 | } |
335 | | |
336 | | wasm::FunctionDefinition WasmCodeTransform::translateFunction(yul::FunctionDefinition const& _fun) |
337 | 0 | { |
338 | 0 | wasm::FunctionDefinition fun; |
339 | 0 | fun.name = _fun.name.str(); |
340 | 0 | for (auto const& param: _fun.parameters) |
341 | 0 | fun.parameters.push_back({param.name.str(), translatedType(param.type)}); |
342 | 0 | for (auto const& retParam: _fun.returnVariables) |
343 | 0 | fun.locals.emplace_back(wasm::VariableDeclaration{retParam.name.str(), translatedType(retParam.type)}); |
344 | 0 | if (!_fun.returnVariables.empty()) |
345 | 0 | fun.returnType = translatedType(_fun.returnVariables[0].type); |
346 | |
|
347 | 0 | yulAssert(m_localVariables.empty(), ""); |
348 | 0 | yulAssert(m_functionBodyLabel.empty(), ""); |
349 | 0 | m_functionBodyLabel = newLabel(); |
350 | 0 | fun.body.emplace_back(wasm::Expression(wasm::Block{ |
351 | 0 | m_functionBodyLabel, |
352 | 0 | visit(_fun.body.statements) |
353 | 0 | })); |
354 | 0 | fun.locals += m_localVariables; |
355 | |
|
356 | 0 | m_localVariables.clear(); |
357 | 0 | m_functionBodyLabel = {}; |
358 | |
|
359 | 0 | if (!_fun.returnVariables.empty()) |
360 | 0 | { |
361 | | // First return variable is returned directly, the others are stored |
362 | | // in globals. |
363 | 0 | vector<wasm::Type> typesForGlobals; |
364 | 0 | for (size_t i = 1; i < _fun.returnVariables.size(); ++i) |
365 | 0 | typesForGlobals.push_back(translatedType(_fun.returnVariables[i].type)); |
366 | 0 | vector<size_t> allocatedIndices = allocateGlobals(typesForGlobals); |
367 | 0 | yulAssert(allocatedIndices.size() == _fun.returnVariables.size() - 1, ""); |
368 | | |
369 | 0 | for (size_t i = 1; i < _fun.returnVariables.size(); ++i) |
370 | 0 | fun.body.emplace_back(wasm::GlobalAssignment{ |
371 | 0 | m_globalVariables.at(allocatedIndices[i - 1]).variableName, |
372 | 0 | make_unique<wasm::Expression>(wasm::LocalVariable{_fun.returnVariables.at(i).name.str()}) |
373 | 0 | }); |
374 | 0 | fun.body.emplace_back(wasm::LocalVariable{_fun.returnVariables.front().name.str()}); |
375 | 0 | } |
376 | 0 | return fun; |
377 | 0 | } |
378 | | |
379 | | string WasmCodeTransform::newLabel() |
380 | 0 | { |
381 | 0 | return m_nameDispenser.newName("label_"_yulstring).str(); |
382 | 0 | } |
383 | | |
384 | | vector<size_t> WasmCodeTransform::allocateGlobals(vector<wasm::Type> const& _typesForGlobals) |
385 | 0 | { |
386 | 0 | map<wasm::Type, size_t> availableGlobals; |
387 | 0 | for (wasm::GlobalVariableDeclaration const& global: m_globalVariables) |
388 | 0 | ++availableGlobals[global.type]; |
389 | |
|
390 | 0 | map<wasm::Type, size_t> neededGlobals; |
391 | 0 | for (wasm::Type const& type: _typesForGlobals) |
392 | 0 | ++neededGlobals[type]; |
393 | |
|
394 | 0 | for (auto [type, neededGlobalCount]: neededGlobals) |
395 | 0 | while (availableGlobals[type] < neededGlobalCount) |
396 | 0 | { |
397 | 0 | m_globalVariables.emplace_back(wasm::GlobalVariableDeclaration{ |
398 | 0 | m_nameDispenser.newName("global_"_yulstring).str(), |
399 | 0 | type, |
400 | 0 | }); |
401 | |
|
402 | 0 | ++availableGlobals[type]; |
403 | 0 | } |
404 | |
|
405 | 0 | vector<size_t> allocatedIndices; |
406 | 0 | map<wasm::Type, size_t> nextGlobal; |
407 | 0 | for (wasm::Type const& type: _typesForGlobals) |
408 | 0 | { |
409 | 0 | while (m_globalVariables[nextGlobal[type]].type != type) |
410 | 0 | ++nextGlobal[type]; |
411 | |
|
412 | 0 | allocatedIndices.push_back(nextGlobal[type]++); |
413 | 0 | } |
414 | |
|
415 | 0 | yulAssert(all_of( |
416 | 0 | allocatedIndices.begin(), |
417 | 0 | allocatedIndices.end(), |
418 | 0 | [this](size_t index){ return index < m_globalVariables.size(); } |
419 | 0 | ), ""); |
420 | 0 | yulAssert(allocatedIndices.size() == set<size_t>(allocatedIndices.begin(), allocatedIndices.end()).size(), "Indices not unique"); |
421 | 0 | yulAssert(allocatedIndices.size() == _typesForGlobals.size(), ""); |
422 | 0 | return allocatedIndices; |
423 | 0 | } |
424 | | |
425 | | wasm::Type WasmCodeTransform::translatedType(yul::Type _yulType) |
426 | 0 | { |
427 | 0 | if (_yulType == "i32"_yulstring) |
428 | 0 | return wasm::Type::i32; |
429 | 0 | else if (_yulType == "i64"_yulstring) |
430 | 0 | return wasm::Type::i64; |
431 | 0 | else |
432 | 0 | yulAssert(false, "This Yul type does not have a corresponding type in Wasm."); |
433 | 0 | } |
434 | | |
435 | | wasm::Literal WasmCodeTransform::makeLiteral(wasm::Type _type, u256 _value) |
436 | 0 | { |
437 | 0 | if (_type == wasm::Type::i32) |
438 | 0 | { |
439 | 0 | yulAssert(_value <= numeric_limits<uint32_t>::max(), "Literal too large: " + _value.str()); |
440 | 0 | return wasm::Literal{static_cast<uint32_t>(_value)}; |
441 | 0 | } |
442 | 0 | else if (_type == wasm::Type::i64) |
443 | 0 | { |
444 | 0 | yulAssert(_value <= numeric_limits<uint64_t>::max(), "Literal too large: " + _value.str()); |
445 | 0 | return wasm::Literal{static_cast<uint64_t>(_value)}; |
446 | 0 | } |
447 | 0 | else |
448 | 0 | yulAssert(false, "Invalid Wasm literal type"); |
449 | 0 | } |