Coverage Report

Created: 2022-08-24 06:38

/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
12.9k
{
100
12.9k
  EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect);
101
12.9k
  bool usesOptimizedCodeGenerator =
102
12.9k
    _optimizeStackAllocation &&
103
12.9k
    evmDialect &&
104
12.9k
    evmDialect->evmVersion().canOverchargeGasForCall() &&
105
12.9k
    evmDialect->providesObjectAccess();
106
12.9k
  set<YulString> reservedIdentifiers = _externallyUsedIdentifiers;
107
12.9k
  reservedIdentifiers += _dialect.fixedFunctionNames();
108
109
12.9k
  *_object.code = std::get<Block>(Disambiguator(
110
12.9k
    _dialect,
111
12.9k
    *_object.analysisInfo,
112
12.9k
    reservedIdentifiers
113
12.9k
  )(*_object.code));
114
12.9k
  Block& ast = *_object.code;
115
116
12.9k
  NameDispenser dispenser{_dialect, ast, reservedIdentifiers};
117
12.9k
  OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment};
118
119
12.9k
  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
12.9k
  suite.runSequence("hgfo", ast);
124
125
12.9k
  NameSimplifier::run(suite.m_context, ast);
126
  // Now the user-supplied part
127
12.9k
  suite.runSequence(_optimisationSequence, ast);
128
129
  // This is a tuning parameter, but actually just prevents infinite loops.
130
12.9k
  size_t stackCompressorMaxIterations = 16;
131
12.9k
  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
12.9k
  if (!usesOptimizedCodeGenerator)
136
0
    StackCompressor::run(
137
0
      _dialect,
138
0
      _object,
139
0
      _optimizeStackAllocation,
140
0
      stackCompressorMaxIterations
141
0
    );
142
12.9k
  suite.runSequence("fDnTOc g", ast);
143
144
12.9k
  if (evmDialect)
145
12.9k
  {
146
12.9k
    yulAssert(_meter, "");
147
12.9k
    ConstantOptimiser{*evmDialect, *_meter}(ast);
148
12.9k
    if (usesOptimizedCodeGenerator)
149
12.9k
    {
150
12.9k
      StackCompressor::run(
151
12.9k
        _dialect,
152
12.9k
        _object,
153
12.9k
        _optimizeStackAllocation,
154
12.9k
        stackCompressorMaxIterations
155
12.9k
      );
156
12.9k
      if (evmDialect->providesObjectAccess())
157
12.9k
        StackLimitEvader::run(suite.m_context, _object);
158
12.9k
    }
159
0
    else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation)
160
0
      StackLimitEvader::run(suite.m_context, _object);
161
12.9k
  }
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
12.9k
  dispenser.reset(ast);
171
12.9k
  NameSimplifier::run(suite.m_context, ast);
172
12.9k
  VarNameCleaner::run(suite.m_context, ast);
173
174
12.9k
  *_object.analysisInfo = AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object);
175
12.9k
}
176
177
namespace
178
{
179
180
181
template <class... Step>
182
map<string, unique_ptr<OptimiserStep>> optimiserStepCollection()
183
1
{
184
1
  map<string, unique_ptr<OptimiserStep>> ret;
185
1
  for (unique_ptr<OptimiserStep>& s: util::make_vector<unique_ptr<OptimiserStep>>(
186
1
    (make_unique<OptimiserStepInstance<Step>>())...
187
1
  ))
188
33
  {
189
33
    yulAssert(!ret.count(s->name), "");
190
33
    ret[s->name] = std::move(s);
191
33
  }
192
1
  return ret;
193
1
}
194
195
}
196
197
map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
198
5.93M
{
199
5.93M
  static map<string, unique_ptr<OptimiserStep>> instance;
200
5.93M
  if (instance.empty())
201
1
    instance = optimiserStepCollection<
202
1
      BlockFlattener,
203
1
      CircularReferencesPruner,
204
1
      CommonSubexpressionEliminator,
205
1
      ConditionalSimplifier,
206
1
      ConditionalUnsimplifier,
207
1
      ControlFlowSimplifier,
208
1
      DeadCodeEliminator,
209
1
      EqualStoreEliminator,
210
1
      EquivalentFunctionCombiner,
211
1
      ExpressionInliner,
212
1
      ExpressionJoiner,
213
1
      ExpressionSimplifier,
214
1
      ExpressionSplitter,
215
1
      ForLoopConditionIntoBody,
216
1
      ForLoopConditionOutOfBody,
217
1
      ForLoopInitRewriter,
218
1
      FullInliner,
219
1
      FunctionGrouper,
220
1
      FunctionHoister,
221
1
      FunctionSpecializer,
222
1
      LiteralRematerialiser,
223
1
      LoadResolver,
224
1
      LoopInvariantCodeMotion,
225
1
      UnusedAssignEliminator,
226
1
      UnusedStoreEliminator,
227
1
      ReasoningBasedSimplifier,
228
1
      Rematerialiser,
229
1
      SSAReverser,
230
1
      SSATransform,
231
1
      StructuralSimplifier,
232
1
      UnusedFunctionParameterPruner,
233
1
      UnusedPruner,
234
1
      VarDeclInitializer
235
1
    >();
236
  // Does not include VarNameCleaner because it destroys the property of unique names.
237
  // Does not include NameSimplifier.
238
5.93M
  return instance;
239
5.93M
}
240
241
map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
242
1
{
243
1
  static map<string, char> lookupTable{
244
1
    {BlockFlattener::name,                'f'},
245
1
    {CircularReferencesPruner::name,      'l'},
246
1
    {CommonSubexpressionEliminator::name, 'c'},
247
1
    {ConditionalSimplifier::name,         'C'},
248
1
    {ConditionalUnsimplifier::name,       'U'},
249
1
    {ControlFlowSimplifier::name,         'n'},
250
1
    {DeadCodeEliminator::name,            'D'},
251
1
    {EqualStoreEliminator::name,          'E'},
252
1
    {EquivalentFunctionCombiner::name,    'v'},
253
1
    {ExpressionInliner::name,             'e'},
254
1
    {ExpressionJoiner::name,              'j'},
255
1
    {ExpressionSimplifier::name,          's'},
256
1
    {ExpressionSplitter::name,            'x'},
257
1
    {ForLoopConditionIntoBody::name,      'I'},
258
1
    {ForLoopConditionOutOfBody::name,     'O'},
259
1
    {ForLoopInitRewriter::name,           'o'},
260
1
    {FullInliner::name,                   'i'},
261
1
    {FunctionGrouper::name,               'g'},
262
1
    {FunctionHoister::name,               'h'},
263
1
    {FunctionSpecializer::name,           'F'},
264
1
    {LiteralRematerialiser::name,         'T'},
265
1
    {LoadResolver::name,                  'L'},
266
1
    {LoopInvariantCodeMotion::name,       'M'},
267
1
    {ReasoningBasedSimplifier::name,      'R'},
268
1
    {UnusedAssignEliminator::name,        'r'},
269
1
    {UnusedStoreEliminator::name,         'S'},
270
1
    {Rematerialiser::name,                'm'},
271
1
    {SSAReverser::name,                   'V'},
272
1
    {SSATransform::name,                  'a'},
273
1
    {StructuralSimplifier::name,          't'},
274
1
    {UnusedFunctionParameterPruner::name, 'p'},
275
1
    {UnusedPruner::name,                  'u'},
276
1
    {VarDeclInitializer::name,            'd'},
277
1
  };
278
1
  yulAssert(lookupTable.size() == allSteps().size(), "");
279
1
  yulAssert((
280
1
      util::convertContainer<set<char>>(string(NonStepAbbreviations)) -
281
1
      util::convertContainer<set<char>>(lookupTable | ranges::views::values)
282
1
    ).size() == string(NonStepAbbreviations).size(),
283
1
    "Step abbreviation conflicts with a character reserved for another syntactic element"
284
1
  );
285
286
1
  return lookupTable;
287
1
}
288
289
map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap()
290
11.5M
{
291
11.5M
  static map<char, string> lookupTable = util::invertMap(stepNameToAbbreviationMap());
292
293
11.5M
  return lookupTable;
294
11.5M
}
295
296
void OptimiserSuite::validateSequence(string_view _stepAbbreviations)
297
262k
{
298
262k
  int8_t nestingLevel = 0;
299
262k
  for (char abbreviation: _stepAbbreviations)
300
3.24M
    switch (abbreviation)
301
3.24M
    {
302
64.8k
    case ' ':
303
64.8k
    case '\n':
304
64.8k
      break;
305
181k
    case '[':
306
181k
      assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep");
307
181k
      nestingLevel++;
308
181k
      break;
309
181k
    case ']':
310
181k
      nestingLevel--;
311
181k
      assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets");
312
181k
      break;
313
2.81M
    default:
314
2.81M
    {
315
2.81M
      yulAssert(
316
2.81M
        string(NonStepAbbreviations).find(abbreviation) == string::npos,
317
2.81M
        "Unhandled syntactic element in the abbreviation sequence"
318
2.81M
      );
319
2.81M
      assertThrow(
320
2.81M
        stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(),
321
2.81M
        OptimizerException,
322
2.81M
        "'"s + abbreviation + "' is not a valid step abbreviation"
323
2.81M
      );
324
2.81M
      optional<string> invalid = allSteps().at(stepAbbreviationToNameMap().at(abbreviation))->invalidInCurrentEnvironment();
325
2.81M
      assertThrow(
326
2.81M
        !invalid.has_value(),
327
2.81M
        OptimizerException,
328
2.81M
        "'"s + abbreviation + "' is invalid in the current environment: " + *invalid
329
2.81M
      );
330
2.81M
    }
331
3.24M
    }
332
262k
  assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets");
333
262k
}
334
335
void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable)
336
262k
{
337
262k
  validateSequence(_stepAbbreviations);
338
339
  // This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'.
340
262k
  auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view>
341
366k
  {
342
2.12M
    for (size_t i = 0; i < _tail.size(); ++i)
343
1.86M
    {
344
1.86M
      yulAssert(_tail[i] != ']');
345
1.86M
      if (_tail[i] == '[')
346
103k
        return {_tail.substr(0, i), _tail.substr(i)};
347
1.86M
    }
348
262k
    return {_tail, {}};
349
366k
  };
350
351
  // This splits '[bbb]ccc...' into 'bbb' and 'ccc...'.
352
262k
  auto extractBracketContent = [](string_view _tail) -> tuple<string_view, string_view>
353
262k
  {
354
103k
    yulAssert(!_tail.empty() && _tail[0] == '[');
355
356
103k
    size_t contentLength = 0;
357
103k
    int8_t nestingLevel = 1;
358
103k
    for (char abbreviation: _tail.substr(1))
359
1.37M
    {
360
1.37M
      if (abbreviation == '[')
361
77.7k
      {
362
77.7k
        yulAssert(nestingLevel < numeric_limits<int8_t>::max());
363
77.7k
        ++nestingLevel;
364
77.7k
      }
365
1.29M
      else if (abbreviation == ']')
366
181k
      {
367
181k
        --nestingLevel;
368
181k
        if (nestingLevel == 0)
369
103k
          break;
370
181k
      }
371
1.27M
      ++contentLength;
372
1.27M
    }
373
103k
    yulAssert(nestingLevel == 0);
374
103k
    yulAssert(_tail[contentLength + 1] == ']');
375
376
103k
    return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)};
377
103k
  };
378
379
262k
  auto abbreviationsToSteps = [](string_view _sequence) -> vector<string>
380
659k
  {
381
659k
    vector<string> steps;
382
659k
    for (char abbreviation: _sequence)
383
3.19M
      if (abbreviation != ' ' && abbreviation != '\n')
384
3.12M
        steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation));
385
659k
    return steps;
386
659k
  };
387
388
262k
  vector<tuple<string_view, bool>> subsequences;
389
262k
  string_view tail = _stepAbbreviations;
390
366k
  while (!tail.empty())
391
366k
  {
392
366k
    string_view subsequence;
393
366k
    tie(subsequence, tail) = extractNonNestedPrefix(tail);
394
366k
    if (subsequence.size() > 0)
395
366k
      subsequences.push_back({subsequence, false});
396
397
366k
    if (tail.empty())
398
262k
      break;
399
400
103k
    tie(subsequence, tail) = extractBracketContent(tail);
401
103k
    if (subsequence.size() > 0)
402
103k
      subsequences.push_back({subsequence, true});
403
103k
  }
404
405
262k
  size_t codeSize = 0;
406
448k
  for (size_t round = 0; round < MaxRounds; ++round)
407
448k
  {
408
448k
    for (auto const& [subsequence, repeat]: subsequences)
409
869k
    {
410
869k
      if (repeat)
411
210k
        runSequence(subsequence, _ast, true);
412
659k
      else
413
659k
        runSequence(abbreviationsToSteps(subsequence), _ast);
414
869k
    }
415
416
448k
    if (!_repeatUntilStable)
417
51.8k
      break;
418
419
396k
    size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast);
420
396k
    if (newSize == codeSize)
421
210k
      break;
422
186k
    codeSize = newSize;
423
186k
  }
424
262k
}
425
426
void OptimiserSuite::runSequence(std::vector<string> const& _steps, Block& _ast)
427
659k
{
428
659k
  unique_ptr<Block> copy;
429
659k
  if (m_debug == Debug::PrintChanges)
430
0
    copy = make_unique<Block>(std::get<Block>(ASTCopier{}(_ast)));
431
659k
  for (string const& step: _steps)
432
3.12M
  {
433
3.12M
    if (m_debug == Debug::PrintStep)
434
0
      cout << "Running " << step << endl;
435
3.12M
    allSteps().at(step)->run(m_context, _ast);
436
3.12M
    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
3.12M
  }
449
659k
}