Coverage Report

Created: 2022-08-24 06:55

/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
}