Coverage Report

Created: 2022-08-24 06:40

/src/solidity/libyul/YulStack.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
 * Full assembly stack that can support EVM-assembly and Yul as input and EVM, EVM1.5 and
20
 * Ewasm as output.
21
 */
22
23
24
#include <libyul/YulStack.h>
25
26
#include <libyul/AsmAnalysis.h>
27
#include <libyul/AsmAnalysisInfo.h>
28
#include <libyul/backends/evm/EthAssemblyAdapter.h>
29
#include <libyul/backends/evm/EVMCodeTransform.h>
30
#include <libyul/backends/evm/EVMDialect.h>
31
#include <libyul/backends/evm/EVMObjectCompiler.h>
32
#include <libyul/backends/evm/EVMMetrics.h>
33
#include <libyul/backends/wasm/WasmDialect.h>
34
#include <libyul/backends/wasm/WasmObjectCompiler.h>
35
#include <libyul/backends/wasm/EVMToEwasmTranslator.h>
36
#include <libyul/ObjectParser.h>
37
#include <libyul/optimiser/Suite.h>
38
39
#include <libevmasm/Assembly.h>
40
#include <liblangutil/Scanner.h>
41
#include <boost/algorithm/string.hpp>
42
#include <optional>
43
44
using namespace std;
45
using namespace solidity;
46
using namespace solidity::yul;
47
using namespace solidity::langutil;
48
49
namespace
50
{
51
Dialect const& languageToDialect(YulStack::Language _language, EVMVersion _version)
52
34.3k
{
53
34.3k
  switch (_language)
54
34.3k
  {
55
0
  case YulStack::Language::Assembly:
56
34.3k
  case YulStack::Language::StrictAssembly:
57
34.3k
    return EVMDialect::strictAssemblyForEVMObjects(_version);
58
0
  case YulStack::Language::Yul:
59
0
    return EVMDialectTyped::instance(_version);
60
0
  case YulStack::Language::Ewasm:
61
0
    return WasmDialect::instance();
62
34.3k
  }
63
0
  yulAssert(false, "");
64
0
  return Dialect::yulDeprecated();
65
0
}
66
67
// Duplicated from libsolidity/codegen/CompilerContext.cpp
68
// TODO: refactor and remove duplication
69
evmasm::Assembly::OptimiserSettings translateOptimiserSettings(
70
  frontend::OptimiserSettings const& _settings,
71
  langutil::EVMVersion _evmVersion
72
)
73
0
{
74
  // Constructing it this way so that we notice changes in the fields.
75
0
  evmasm::Assembly::OptimiserSettings asmSettings{false,  false, false, false, false, false, _evmVersion, 0};
76
0
  asmSettings.runInliner = _settings.runInliner;
77
0
  asmSettings.runJumpdestRemover = _settings.runJumpdestRemover;
78
0
  asmSettings.runPeephole = _settings.runPeephole;
79
0
  asmSettings.runDeduplicate = _settings.runDeduplicate;
80
0
  asmSettings.runCSE = _settings.runCSE;
81
0
  asmSettings.runConstantOptimiser = _settings.runConstantOptimiser;
82
0
  asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment;
83
0
  asmSettings.evmVersion = _evmVersion;
84
85
0
  return asmSettings;
86
0
}
87
88
}
89
90
91
CharStream const& YulStack::charStream(string const& _sourceName) const
92
0
{
93
0
  yulAssert(m_charStream, "");
94
0
  yulAssert(m_charStream->name() == _sourceName, "");
95
0
  return *m_charStream;
96
0
}
97
98
bool YulStack::parseAndAnalyze(std::string const& _sourceName, std::string const& _source)
99
14.7k
{
100
14.7k
  m_errors.clear();
101
14.7k
  m_analysisSuccessful = false;
102
14.7k
  m_charStream = make_unique<CharStream>(_source, _sourceName);
103
14.7k
  shared_ptr<Scanner> scanner = make_shared<Scanner>(*m_charStream);
104
14.7k
  m_parserResult = ObjectParser(m_errorReporter, languageToDialect(m_language, m_evmVersion)).parse(scanner, false);
105
14.7k
  if (!m_errorReporter.errors().empty())
106
0
    return false;
107
14.7k
  yulAssert(m_parserResult, "");
108
14.7k
  yulAssert(m_parserResult->code, "");
109
110
14.7k
  return analyzeParsed();
111
14.7k
}
112
113
void YulStack::optimize()
114
0
{
115
0
  if (!m_optimiserSettings.runYulOptimiser)
116
0
    return;
117
118
0
  yulAssert(m_analysisSuccessful, "Analysis was not successful.");
119
120
0
  m_analysisSuccessful = false;
121
0
  yulAssert(m_parserResult, "");
122
0
  optimize(*m_parserResult, true);
123
0
  yulAssert(analyzeParsed(), "Invalid source code after optimization.");
124
0
}
125
126
void YulStack::translate(YulStack::Language _targetLanguage)
127
0
{
128
0
  if (m_language == _targetLanguage)
129
0
    return;
130
131
0
  yulAssert(
132
0
    m_language == Language::StrictAssembly && _targetLanguage == Language::Ewasm,
133
0
    "Invalid language combination"
134
0
  );
135
136
0
  *m_parserResult = EVMToEwasmTranslator(
137
0
    languageToDialect(m_language, m_evmVersion),
138
0
    *this
139
0
  ).run(*parserResult());
140
141
0
  m_language = _targetLanguage;
142
0
}
143
144
bool YulStack::analyzeParsed()
145
14.7k
{
146
14.7k
  yulAssert(m_parserResult, "");
147
14.7k
  m_analysisSuccessful = analyzeParsed(*m_parserResult);
148
14.7k
  return m_analysisSuccessful;
149
14.7k
}
150
151
bool YulStack::analyzeParsed(Object& _object)
152
19.5k
{
153
19.5k
  yulAssert(_object.code, "");
154
19.5k
  _object.analysisInfo = make_shared<AsmAnalysisInfo>();
155
156
19.5k
  AsmAnalyzer analyzer(
157
19.5k
    *_object.analysisInfo,
158
19.5k
    m_errorReporter,
159
19.5k
    languageToDialect(m_language, m_evmVersion),
160
19.5k
    {},
161
19.5k
    _object.qualifiedDataNames()
162
19.5k
  );
163
19.5k
  bool success = analyzer.analyze(*_object.code);
164
19.5k
  for (auto& subNode: _object.subObjects)
165
6.45k
    if (auto subObject = dynamic_cast<Object*>(subNode.get()))
166
4.82k
      if (!analyzeParsed(*subObject))
167
0
        success = false;
168
19.5k
  return success;
169
19.5k
}
170
171
void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const
172
0
{
173
0
  EVMDialect const* dialect = nullptr;
174
0
  switch (m_language)
175
0
  {
176
0
    case Language::Assembly:
177
0
    case Language::StrictAssembly:
178
0
      dialect = &EVMDialect::strictAssemblyForEVMObjects(m_evmVersion);
179
0
      break;
180
0
    case Language::Yul:
181
0
      dialect = &EVMDialectTyped::instance(m_evmVersion);
182
0
      break;
183
0
    default:
184
0
      yulAssert(false, "Invalid language.");
185
0
      break;
186
0
  }
187
188
0
  EVMObjectCompiler::compile(*m_parserResult, _assembly, *dialect, _optimize);
189
0
}
190
191
void YulStack::optimize(Object& _object, bool _isCreation)
192
0
{
193
0
  yulAssert(_object.code, "");
194
0
  yulAssert(_object.analysisInfo, "");
195
0
  for (auto& subNode: _object.subObjects)
196
0
    if (auto subObject = dynamic_cast<Object*>(subNode.get()))
197
0
    {
198
0
      bool isCreation = !boost::ends_with(subObject->name.str(), "_deployed");
199
0
      optimize(*subObject, isCreation);
200
0
    }
201
202
0
  Dialect const& dialect = languageToDialect(m_language, m_evmVersion);
203
0
  unique_ptr<GasMeter> meter;
204
0
  if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&dialect))
205
0
    meter = make_unique<GasMeter>(*evmDialect, _isCreation, m_optimiserSettings.expectedExecutionsPerDeployment);
206
0
  OptimiserSuite::run(
207
0
    dialect,
208
0
    meter.get(),
209
0
    _object,
210
0
    m_optimiserSettings.optimizeStackAllocation,
211
0
    m_optimiserSettings.yulOptimiserSteps,
212
0
    _isCreation ? nullopt : make_optional(m_optimiserSettings.expectedExecutionsPerDeployment),
213
0
    {}
214
0
  );
215
0
}
216
217
MachineAssemblyObject YulStack::assemble(Machine _machine) const
218
0
{
219
0
  yulAssert(m_analysisSuccessful, "");
220
0
  yulAssert(m_parserResult, "");
221
0
  yulAssert(m_parserResult->code, "");
222
0
  yulAssert(m_parserResult->analysisInfo, "");
223
224
0
  switch (_machine)
225
0
  {
226
0
  case Machine::EVM:
227
0
    return assembleWithDeployed().first;
228
0
  case Machine::Ewasm:
229
0
  {
230
0
    yulAssert(m_language == Language::Ewasm, "");
231
0
    Dialect const& dialect = languageToDialect(m_language, EVMVersion{});
232
233
0
    MachineAssemblyObject object;
234
0
    auto result = WasmObjectCompiler::compile(*m_parserResult, dialect);
235
0
    object.assembly = std::move(result.first);
236
0
    object.bytecode = make_shared<evmasm::LinkerObject>();
237
0
    object.bytecode->bytecode = std::move(result.second);
238
0
    return object;
239
0
  }
240
0
  }
241
  // unreachable
242
0
  return MachineAssemblyObject();
243
0
}
244
245
std::pair<MachineAssemblyObject, MachineAssemblyObject>
246
YulStack::assembleWithDeployed(optional<string_view> _deployName) const
247
0
{
248
0
  auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName);
249
0
  yulAssert(creationAssembly, "");
250
0
  yulAssert(m_charStream, "");
251
252
0
  MachineAssemblyObject creationObject;
253
0
  creationObject.bytecode = make_shared<evmasm::LinkerObject>(creationAssembly->assemble());
254
0
  yulAssert(creationObject.bytecode->immutableReferences.empty(), "Leftover immutables.");
255
0
  creationObject.assembly = creationAssembly->assemblyString(m_debugInfoSelection);
256
0
  creationObject.sourceMappings = make_unique<string>(
257
0
    evmasm::AssemblyItem::computeSourceMapping(
258
0
      creationAssembly->items(),
259
0
      {{m_charStream->name(), 0}}
260
0
    )
261
0
  );
262
263
0
  MachineAssemblyObject deployedObject;
264
0
  if (deployedAssembly)
265
0
  {
266
0
    deployedObject.bytecode = make_shared<evmasm::LinkerObject>(deployedAssembly->assemble());
267
0
    deployedObject.assembly = deployedAssembly->assemblyString(m_debugInfoSelection);
268
0
    deployedObject.sourceMappings = make_unique<string>(
269
0
      evmasm::AssemblyItem::computeSourceMapping(
270
0
        deployedAssembly->items(),
271
0
        {{m_charStream->name(), 0}}
272
0
      )
273
0
    );
274
0
  }
275
276
0
  return {std::move(creationObject), std::move(deployedObject)};
277
0
}
278
279
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
280
YulStack::assembleEVMWithDeployed(optional<string_view> _deployName) const
281
0
{
282
0
  yulAssert(m_analysisSuccessful, "");
283
0
  yulAssert(m_parserResult, "");
284
0
  yulAssert(m_parserResult->code, "");
285
0
  yulAssert(m_parserResult->analysisInfo, "");
286
287
0
  evmasm::Assembly assembly(true, {});
288
0
  EthAssemblyAdapter adapter(assembly);
289
0
  compileEVM(adapter, m_optimiserSettings.optimizeStackAllocation);
290
291
0
  assembly.optimise(translateOptimiserSettings(m_optimiserSettings, m_evmVersion));
292
293
0
  optional<size_t> subIndex;
294
295
  // Pick matching assembly if name was given
296
0
  if (_deployName.has_value())
297
0
  {
298
0
    for (size_t i = 0; i < assembly.numSubs(); i++)
299
0
      if (assembly.sub(i).name() == _deployName)
300
0
      {
301
0
        subIndex = i;
302
0
        break;
303
0
      }
304
305
0
    solAssert(subIndex.has_value(), "Failed to find object to be deployed.");
306
0
  }
307
  // Otherwise use heuristic: If there is a single sub-assembly, this is likely the object to be deployed.
308
0
  else if (assembly.numSubs() == 1)
309
0
    subIndex = 0;
310
311
0
  if (subIndex.has_value())
312
0
  {
313
0
    evmasm::Assembly& runtimeAssembly = assembly.sub(*subIndex);
314
0
    return {make_shared<evmasm::Assembly>(assembly), make_shared<evmasm::Assembly>(runtimeAssembly)};
315
0
  }
316
317
0
  return {make_shared<evmasm::Assembly>(assembly), {}};
318
0
}
319
320
string YulStack::print(
321
  CharStreamProvider const* _soliditySourceProvider
322
) const
323
0
{
324
0
  yulAssert(m_parserResult, "");
325
0
  yulAssert(m_parserResult->code, "");
326
0
  return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion), m_debugInfoSelection, _soliditySourceProvider) + "\n";
327
0
}
328
329
shared_ptr<Object> YulStack::parserResult() const
330
58.0k
{
331
58.0k
  yulAssert(m_analysisSuccessful, "Analysis was not successful.");
332
58.0k
  yulAssert(m_parserResult, "");
333
58.0k
  yulAssert(m_parserResult->code, "");
334
58.0k
  return m_parserResult;
335
58.0k
}