Coverage Report

Created: 2022-08-24 06:52

/src/solidity/libyul/optimiser/Suite.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
 * Optimiser suite that combines all steps and also provides the settings for the heuristics.
20
 */
21
22
#include <libyul/optimiser/Suite.h>
23
24
#include <libyul/optimiser/Disambiguator.h>
25
#include <libyul/optimiser/VarDeclInitializer.h>
26
#include <libyul/optimiser/BlockFlattener.h>
27
#include <libyul/optimiser/CallGraphGenerator.h>
28
#include <libyul/optimiser/CircularReferencesPruner.h>
29
#include <libyul/optimiser/ControlFlowSimplifier.h>
30
#include <libyul/optimiser/ConditionalSimplifier.h>
31
#include <libyul/optimiser/ConditionalUnsimplifier.h>
32
#include <libyul/optimiser/DeadCodeEliminator.h>
33
#include <libyul/optimiser/FunctionGrouper.h>
34
#include <libyul/optimiser/FunctionHoister.h>
35
#include <libyul/optimiser/EqualStoreEliminator.h>
36
#include <libyul/optimiser/EquivalentFunctionCombiner.h>
37
#include <libyul/optimiser/ExpressionSplitter.h>
38
#include <libyul/optimiser/ExpressionJoiner.h>
39
#include <libyul/optimiser/ExpressionInliner.h>
40
#include <libyul/optimiser/FullInliner.h>
41
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
42
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
43
#include <libyul/optimiser/ForLoopInitRewriter.h>
44
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
45
#include <libyul/optimiser/FunctionSpecializer.h>
46
#include <libyul/optimiser/ReasoningBasedSimplifier.h>
47
#include <libyul/optimiser/Rematerialiser.h>
48
#include <libyul/optimiser/UnusedFunctionParameterPruner.h>
49
#include <libyul/optimiser/UnusedPruner.h>
50
#include <libyul/optimiser/ExpressionSimplifier.h>
51
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
52
#include <libyul/optimiser/Semantics.h>
53
#include <libyul/optimiser/SSAReverser.h>
54
#include <libyul/optimiser/SSATransform.h>
55
#include <libyul/optimiser/StackCompressor.h>
56
#include <libyul/optimiser/StackLimitEvader.h>
57
#include <libyul/optimiser/StructuralSimplifier.h>
58
#include <libyul/optimiser/SyntacticalEquality.h>
59
#include <libyul/optimiser/UnusedAssignEliminator.h>
60
#include <libyul/optimiser/UnusedStoreEliminator.h>
61
#include <libyul/optimiser/VarNameCleaner.h>
62
#include <libyul/optimiser/LoadResolver.h>
63
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
64
#include <libyul/optimiser/Metrics.h>
65
#include <libyul/optimiser/NameSimplifier.h>
66
#include <libyul/backends/evm/ConstantOptimiser.h>
67
#include <libyul/AsmAnalysis.h>
68
#include <libyul/AsmAnalysisInfo.h>
69
#include <libyul/AsmPrinter.h>
70
#include <libyul/AST.h>
71
#include <libyul/Object.h>
72
73
#include <libyul/backends/wasm/WasmDialect.h>
74
#include <libyul/backends/evm/NoOutputAssembly.h>
75
76
#include <libsolutil/CommonData.h>
77
78
#include <libyul/CompilabilityChecker.h>
79
80
#include <range/v3/view/map.hpp>
81
#include <range/v3/action/remove.hpp>
82
83
#include <limits>
84
#include <tuple>
85
86
using namespace std;
87
using namespace solidity;
88
using namespace solidity::yul;
89
90
void OptimiserSuite::run(
91
  Dialect const& _dialect,
92
  GasMeter const* _meter,
93
  Object& _object,
94
  bool _optimizeStackAllocation,
95
  string_view _optimisationSequence,
96
  optional<size_t> _expectedExecutionsPerDeployment,
97
  set<YulString> const& _externallyUsedIdentifiers
98
)
99
0
{
100
0
  EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect);
101
0
  bool usesOptimizedCodeGenerator =
102
0
    _optimizeStackAllocation &&
103
0
    evmDialect &&
104
0
    evmDialect->evmVersion().canOverchargeGasForCall() &&
105
0
    evmDialect->providesObjectAccess();
106
0
  set<YulString> reservedIdentifiers = _externallyUsedIdentifiers;
107
0
  reservedIdentifiers += _dialect.fixedFunctionNames();
108
109
0
  *_object.code = std::get<Block>(Disambiguator(
110
0
    _dialect,
111
0
    *_object.analysisInfo,
112
0
    reservedIdentifiers
113
0
  )(*_object.code));
114
0
  Block& ast = *_object.code;
115
116
0
  NameDispenser dispenser{_dialect, ast, reservedIdentifiers};
117
0
  OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment};
118
119
0
  OptimiserSuite suite(context, Debug::None);
120
121
  // Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and
122
  // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely.
123
0
  suite.runSequence("hgfo", ast);
124
125
0
  NameSimplifier::run(suite.m_context, ast);
126
  // Now the user-supplied part
127
0
  suite.runSequence(_optimisationSequence, ast);
128
129
  // This is a tuning parameter, but actually just prevents infinite loops.
130
0
  size_t stackCompressorMaxIterations = 16;
131
0
  suite.runSequence("g", ast);
132
133
  // We ignore the return value because we will get a much better error
134
  // message once we perform code generation.
135
0
  if (!usesOptimizedCodeGenerator)
136
0
    StackCompressor::run(
137
0
      _dialect,
138
0
      _object,
139
0
      _optimizeStackAllocation,
140
0
      stackCompressorMaxIterations
141
0
    );
142
0
  suite.runSequence("fDnTOc g", ast);
143
144
0
  if (evmDialect)
145
0
  {
146
0
    yulAssert(_meter, "");
147
0
    ConstantOptimiser{*evmDialect, *_meter}(ast);
148
0
    if (usesOptimizedCodeGenerator)
149
0
    {
150
0
      StackCompressor::run(
151
0
        _dialect,
152
0
        _object,
153
0
        _optimizeStackAllocation,
154
0
        stackCompressorMaxIterations
155
0
      );
156
0
      if (evmDialect->providesObjectAccess())
157
0
        StackLimitEvader::run(suite.m_context, _object);
158
0
    }
159
0
    else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation)
160
0
      StackLimitEvader::run(suite.m_context, _object);
161
0
  }
162
0
  else if (dynamic_cast<WasmDialect const*>(&_dialect))
163
0
  {
164
    // If the first statement is an empty block, remove it.
165
    // We should only have function definitions after that.
166
0
    if (ast.statements.size() > 1 && std::get<Block>(ast.statements.front()).statements.empty())
167
0
      ast.statements.erase(ast.statements.begin());
168
0
  }
169
170
0
  dispenser.reset(ast);
171
0
  NameSimplifier::run(suite.m_context, ast);
172
0
  VarNameCleaner::run(suite.m_context, ast);
173
174
0
  *_object.analysisInfo = AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
175
0
}
176
177
namespace
178
{
179
180
181
template <class... Step>
182
map<string, unique_ptr<OptimiserStep>> optimiserStepCollection()
183
0
{
184
0
  map<string, unique_ptr<OptimiserStep>> ret;
185
0
  for (unique_ptr<OptimiserStep>& s: util::make_vector<unique_ptr<OptimiserStep>>(
186
0
    (make_unique<OptimiserStepInstance<Step>>())...
187
0
  ))
188
0
  {
189
0
    yulAssert(!ret.count(s->name), "");
190
0
    ret[s->name] = std::move(s);
191
0
  }
192
0
  return ret;
193
0
}
194
195
}
196
197
map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
198
0
{
199
0
  static map<string, unique_ptr<OptimiserStep>> instance;
200
0
  if (instance.empty())
201
0
    instance = optimiserStepCollection<
202
0
      BlockFlattener,
203
0
      CircularReferencesPruner,
204
0
      CommonSubexpressionEliminator,
205
0
      ConditionalSimplifier,
206
0
      ConditionalUnsimplifier,
207
0
      ControlFlowSimplifier,
208
0
      DeadCodeEliminator,
209
0
      EqualStoreEliminator,
210
0
      EquivalentFunctionCombiner,
211
0
      ExpressionInliner,
212
0
      ExpressionJoiner,
213
0
      ExpressionSimplifier,
214
0
      ExpressionSplitter,
215
0
      ForLoopConditionIntoBody,
216
0
      ForLoopConditionOutOfBody,
217
0
      ForLoopInitRewriter,
218
0
      FullInliner,
219
0
      FunctionGrouper,
220
0
      FunctionHoister,
221
0
      FunctionSpecializer,
222
0
      LiteralRematerialiser,
223
0
      LoadResolver,
224
0
      LoopInvariantCodeMotion,
225
0
      UnusedAssignEliminator,
226
0
      UnusedStoreEliminator,
227
0
      ReasoningBasedSimplifier,
228
0
      Rematerialiser,
229
0
      SSAReverser,
230
0
      SSATransform,
231
0
      StructuralSimplifier,
232
0
      UnusedFunctionParameterPruner,
233
0
      UnusedPruner,
234
0
      VarDeclInitializer
235
0
    >();
236
  // Does not include VarNameCleaner because it destroys the property of unique names.
237
  // Does not include NameSimplifier.
238
0
  return instance;
239
0
}
240
241
map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
242
0
{
243
0
  static map<string, char> lookupTable{
244
0
    {BlockFlattener::name,                'f'},
245
0
    {CircularReferencesPruner::name,      'l'},
246
0
    {CommonSubexpressionEliminator::name, 'c'},
247
0
    {ConditionalSimplifier::name,         'C'},
248
0
    {ConditionalUnsimplifier::name,       'U'},
249
0
    {ControlFlowSimplifier::name,         'n'},
250
0
    {DeadCodeEliminator::name,            'D'},
251
0
    {EqualStoreEliminator::name,          'E'},
252
0
    {EquivalentFunctionCombiner::name,    'v'},
253
0
    {ExpressionInliner::name,             'e'},
254
0
    {ExpressionJoiner::name,              'j'},
255
0
    {ExpressionSimplifier::name,          's'},
256
0
    {ExpressionSplitter::name,            'x'},
257
0
    {ForLoopConditionIntoBody::name,      'I'},
258
0
    {ForLoopConditionOutOfBody::name,     'O'},
259
0
    {ForLoopInitRewriter::name,           'o'},
260
0
    {FullInliner::name,                   'i'},
261
0
    {FunctionGrouper::name,               'g'},
262
0
    {FunctionHoister::name,               'h'},
263
0
    {FunctionSpecializer::name,           'F'},
264
0
    {LiteralRematerialiser::name,         'T'},
265
0
    {LoadResolver::name,                  'L'},
266
0
    {LoopInvariantCodeMotion::name,       'M'},
267
0
    {ReasoningBasedSimplifier::name,      'R'},
268
0
    {UnusedAssignEliminator::name,        'r'},
269
0
    {UnusedStoreEliminator::name,         'S'},
270
0
    {Rematerialiser::name,                'm'},
271
0
    {SSAReverser::name,                   'V'},
272
0
    {SSATransform::name,                  'a'},
273
0
    {StructuralSimplifier::name,          't'},
274
0
    {UnusedFunctionParameterPruner::name, 'p'},
275
0
    {UnusedPruner::name,                  'u'},
276
0
    {VarDeclInitializer::name,            'd'},
277
0
  };
278
0
  yulAssert(lookupTable.size() == allSteps().size(), "");
279
0
  yulAssert((
280
0
      util::convertContainer<set<char>>(string(NonStepAbbreviations)) -
281
0
      util::convertContainer<set<char>>(lookupTable | ranges::views::values)
282
0
    ).size() == string(NonStepAbbreviations).size(),
283
0
    "Step abbreviation conflicts with a character reserved for another syntactic element"
284
0
  );
285
286
0
  return lookupTable;
287
0
}
288
289
map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
290
0
{
291
0
  static map<char, string> lookupTable = util::invertMap(stepNameToAbbreviationMap());
292
293
0
  return lookupTable;
294
0
}
295
296
void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
297
0
{
298
0
  int8_t nestingLevel = 0;
299
0
  for (char abbreviation: _stepAbbreviations)
300
0
    switch (abbreviation)
301
0
    {
302
0
    case ' ':
303
0
    case '\n':
304
0
      break;
305
0
    case '[':
306
0
      assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep");
307
0
      nestingLevel++;
308
0
      break;
309
0
    case ']':
310
0
      nestingLevel--;
311
0
      assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
312
0
      break;
313
0
    default:
314
0
    {
315
0
      yulAssert(
316
0
        string(NonStepAbbreviations).find(abbreviation) == string::npos,
317
0
        "Unhandled syntactic element in the abbreviation sequence"
318
0
      );
319
0
      assertThrow(
320
0
        stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(),
321
0
        OptimizerException,
322
0
        "'"s + abbreviation + "' is not a valid step abbreviation"
323
0
      );
324
0
      optional<string> invalid = allSteps().at(stepAbbreviationToNameMap().at(abbreviation))->invalidInCurrentEnvironment();
325
0
      assertThrow(
326
0
        !invalid.has_value(),
327
0
        OptimizerException,
328
0
        "'"s + abbreviation + "' is invalid in the current environment: " + *invalid
329
0
      );
330
0
    }
331
0
    }
332
0
  assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets");
333
0
}
334
335
void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable)
336
0
{
337
0
  validateSequence(_stepAbbreviations);
338
339
  // This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'.
340
0
  auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view>
341
0
  {
342
0
    for (size_t i = 0; i < _tail.size(); ++i)
343
0
    {
344
0
      yulAssert(_tail[i] != ']');
345
0
      if (_tail[i] == '[')
346
0
        return {_tail.substr(0, i), _tail.substr(i)};
347
0
    }
348
0
    return {_tail, {}};
349
0
  };
350
351
  // This splits '[bbb]ccc...' into 'bbb' and 'ccc...'.
352
0
  auto extractBracketContent = [](string_view _tail) -> tuple<string_view, string_view>
353
0
  {
354
0
    yulAssert(!_tail.empty() && _tail[0] == '[');
355
356
0
    size_t contentLength = 0;
357
0
    int8_t nestingLevel = 1;
358
0
    for (char abbreviation: _tail.substr(1))
359
0
    {
360
0
      if (abbreviation == '[')
361
0
      {
362
0
        yulAssert(nestingLevel < numeric_limits<int8_t>::max());
363
0
        ++nestingLevel;
364
0
      }
365
0
      else if (abbreviation == ']')
366
0
      {
367
0
        --nestingLevel;
368
0
        if (nestingLevel == 0)
369
0
          break;
370
0
      }
371
0
      ++contentLength;
372
0
    }
373
0
    yulAssert(nestingLevel == 0);
374
0
    yulAssert(_tail[contentLength + 1] == ']');
375
376
0
    return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)};
377
0
  };
378
379
0
  auto abbreviationsToSteps = [](string_view _sequence) -> vector<string>
380
0
  {
381
0
    vector<string> steps;
382
0
    for (char abbreviation: _sequence)
383
0
      if (abbreviation != ' ' && abbreviation != '\n')
384
0
        steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
385
0
    return steps;
386
0
  };
387
388
0
  vector<tuple<string_view, bool>> subsequences;
389
0
  string_view tail = _stepAbbreviations;
390
0
  while (!tail.empty())
391
0
  {
392
0
    string_view subsequence;
393
0
    tie(subsequence, tail) = extractNonNestedPrefix(tail);
394
0
    if (subsequence.size() > 0)
395
0
      subsequences.push_back({subsequence, false});
396
397
0
    if (tail.empty())
398
0
      break;
399
400
0
    tie(subsequence, tail) = extractBracketContent(tail);
401
0
    if (subsequence.size() > 0)
402
0
      subsequences.push_back({subsequence, true});
403
0
  }
404
405
0
  size_t codeSize = 0;
406
0
  for (size_t round = 0; round < MaxRounds; ++round)
407
0
  {
408
0
    for (auto const& [subsequence, repeat]: subsequences)
409
0
    {
410
0
      if (repeat)
411
0
        runSequence(subsequence, _ast, true);
412
0
      else
413
0
        runSequence(abbreviationsToSteps(subsequence), _ast);
414
0
    }
415
416
0
    if (!_repeatUntilStable)
417
0
      break;
418
419
0
    size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast);
420
0
    if (newSize == codeSize)
421
0
      break;
422
0
    codeSize = newSize;
423
0
  }
424
0
}
425
426
void OptimiserSuite::runSequence(std::vector<string> const& _steps, Block& _ast)
427
0
{
428
0
  unique_ptr<Block> copy;
429
0
  if (m_debug == Debug::PrintChanges)
430
0
    copy = make_unique<Block>(std::get<Block>(ASTCopier{}(_ast)));
431
0
  for (string const& step: _steps)
432
0
  {
433
0
    if (m_debug == Debug::PrintStep)
434
0
      cout << "Running " << step << endl;
435
0
    allSteps().at(step)->run(m_context, _ast);
436
0
    if (m_debug == Debug::PrintChanges)
437
0
    {
438
      // TODO should add switch to also compare variable names!
439
0
      if (SyntacticallyEqual{}.statementEqual(_ast, *copy))
440
0
        cout << "== Running " << step << " did not cause changes." << endl;
441
0
      else
442
0
      {
443
0
        cout << "== Running " << step << " changed the AST." << endl;
444
0
        cout << AsmPrinter{}(_ast) << endl;
445
0
        copy = make_unique<Block>(std::get<Block>(ASTCopier{}(_ast)));
446
0
      }
447
0
    }
448
0
  }
449
0
}