Coverage Report

Created: 2026-06-30 07:08

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