Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libyul/ObjectOptimizer.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
#include <libyul/ObjectOptimizer.h>
20
21
#include <libyul/AsmAnalysisInfo.h>
22
#include <libyul/AsmAnalysis.h>
23
#include <libyul/AsmPrinter.h>
24
#include <libyul/AST.h>
25
#include <libyul/Exceptions.h>
26
#include <libyul/backends/evm/EVMDialect.h>
27
#include <libyul/backends/evm/EVMMetrics.h>
28
#include <libyul/optimiser/ASTCopier.h>
29
#include <libyul/optimiser/Suite.h>
30
31
#include <liblangutil/DebugInfoSelection.h>
32
33
#include <libsolutil/Keccak256.h>
34
35
#include <boost/algorithm/string.hpp>
36
37
#include <limits>
38
#include <numeric>
39
40
using namespace solidity;
41
using namespace solidity::langutil;
42
using namespace solidity::util;
43
using namespace solidity::yul;
44
45
46
Dialect const& yul::languageToDialect(Language _language, EVMVersion _version, std::optional<uint8_t> _eofVersion)
47
469k
{
48
469k
  switch (_language)
49
469k
  {
50
0
  case Language::Assembly:
51
469k
  case Language::StrictAssembly:
52
469k
    return EVMDialect::strictAssemblyForEVMObjects(_version, _eofVersion);
53
469k
  }
54
0
  util::unreachable();
55
0
}
56
57
void ObjectOptimizer::optimize(Object& _object, Settings const& _settings)
58
49.5k
{
59
49.5k
  yulAssert(_object.subId.empty(), "Not a top-level object.");
60
61
49.5k
  optimize(_object, _settings, true /* _isCreation */);
62
49.5k
}
63
64
void ObjectOptimizer::optimize(Object& _object, Settings const& _settings, bool _isCreation)
65
56.2k
{
66
56.2k
  yulAssert(_object.code());
67
56.2k
  yulAssert(_object.debugData);
68
69
56.2k
  for (auto& subNode: _object.subObjects)
70
13.1k
    if (auto subObject = dynamic_cast<Object*>(subNode.get()))
71
6.70k
    {
72
6.70k
      bool isCreation = !boost::ends_with(subObject->name, "_deployed");
73
6.70k
      optimize(
74
6.70k
        *subObject,
75
6.70k
        _settings,
76
6.70k
        isCreation
77
6.70k
      );
78
6.70k
    }
79
80
56.2k
  Dialect const& dialect = languageToDialect(_settings.language, _settings.evmVersion, _settings.eofVersion);
81
56.2k
  std::unique_ptr<GasMeter> meter;
82
56.2k
  if (EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&dialect))
83
56.2k
    meter = std::make_unique<GasMeter>(*evmDialect, _isCreation, _settings.expectedExecutionsPerDeployment);
84
85
56.2k
  std::optional<h256> cacheKey = calculateCacheKey(_object.code()->root(), *_object.debugData, _settings, _isCreation);
86
56.2k
  if (cacheKey.has_value() && m_cachedObjects.count(*cacheKey) != 0)
87
1.56k
  {
88
1.56k
    overwriteWithOptimizedObject(*cacheKey, _object);
89
1.56k
    return;
90
1.56k
  }
91
92
54.7k
  OptimiserSuite::run(
93
54.7k
    meter.get(),
94
54.7k
    _object,
95
54.7k
    _settings.optimizeStackAllocation,
96
54.7k
    _settings.yulOptimiserSteps,
97
54.7k
    _settings.yulOptimiserCleanupSteps,
98
54.7k
    _isCreation ? std::nullopt : std::make_optional(_settings.expectedExecutionsPerDeployment),
99
54.7k
    {}
100
54.7k
  );
101
102
54.7k
  if (cacheKey.has_value())
103
54.7k
    storeOptimizedObject(*cacheKey, _object, dialect);
104
54.7k
}
105
106
void ObjectOptimizer::storeOptimizedObject(util::h256 _cacheKey, Object const& _optimizedObject, Dialect const& _dialect)
107
54.7k
{
108
54.7k
  m_cachedObjects[_cacheKey] = CachedObject{
109
54.7k
    std::make_shared<Block>(ASTCopier{}.translate(_optimizedObject.code()->root())),
110
54.7k
    &_dialect,
111
54.7k
  };
112
54.7k
}
113
114
void ObjectOptimizer::overwriteWithOptimizedObject(util::h256 _cacheKey, Object& _object) const
115
1.56k
{
116
1.56k
  yulAssert(m_cachedObjects.count(_cacheKey) != 0);
117
1.56k
  CachedObject const& cachedObject = m_cachedObjects.at(_cacheKey);
118
119
1.56k
  yulAssert(cachedObject.optimizedAST);
120
1.56k
  yulAssert(cachedObject.dialect);
121
1.56k
  _object.setCode(std::make_shared<AST>(*cachedObject.dialect, ASTCopier{}.translate(*cachedObject.optimizedAST)));
122
1.56k
  yulAssert(_object.code());
123
1.56k
  yulAssert(_object.dialect());
124
125
  // There's no point in caching AnalysisInfo because it references AST nodes. It can't be shared
126
  // by multiple ASTs and it's easier to recalculate it than properly clone it.
127
1.56k
  _object.analysisInfo = std::make_shared<AsmAnalysisInfo>(
128
1.56k
    AsmAnalyzer::analyzeStrictAssertCorrect(
129
1.56k
      _object
130
1.56k
    )
131
1.56k
  );
132
133
  // NOTE: Source name index is included in the key so it must be identical. No need to store and restore it.
134
1.56k
}
135
136
std::optional<h256> ObjectOptimizer::calculateCacheKey(
137
  Block const& _ast,
138
  ObjectDebugData const& _debugData,
139
  Settings const& _settings,
140
  bool _isCreation
141
)
142
56.2k
{
143
56.2k
  AsmPrinter asmPrinter(
144
56.2k
    languageToDialect(_settings.language, _settings.evmVersion, _settings.eofVersion),
145
56.2k
    _debugData.sourceNames,
146
56.2k
    DebugInfoSelection::All()
147
56.2k
  );
148
149
56.2k
  bytes rawKey;
150
  // NOTE: AsmPrinter never prints nativeLocations included in debug data, so ASTs differing only
151
  // in that regard are considered equal here.  This is fine because the optimizer does not keep
152
  // them up to date across AST transformations anyway so in any use where they need to be reliable,
153
  // we just regenerate them by reparsing the object.
154
56.2k
  rawKey += keccak256(asmPrinter(_ast)).asBytes();
155
56.2k
  rawKey += keccak256(_debugData.formatUseSrcComment()).asBytes();
156
56.2k
  rawKey += h256(u256(_settings.language)).asBytes();
157
56.2k
  static_assert(static_cast<uint8_t>(static_cast<bool>(2)) == 1);
158
56.2k
  rawKey += FixedHash<1>(static_cast<uint8_t>(_settings.optimizeStackAllocation)).asBytes();
159
56.2k
  rawKey += h256(u256(_settings.expectedExecutionsPerDeployment)).asBytes();
160
56.2k
  rawKey += FixedHash<1>(static_cast<uint8_t>(_isCreation)).asBytes();
161
56.2k
  rawKey += keccak256(_settings.evmVersion.name()).asBytes();
162
56.2k
  yulAssert(!_settings.eofVersion.has_value() || *_settings.eofVersion > 0);
163
56.2k
  rawKey += FixedHash<1>(static_cast<uint8_t>(_settings.eofVersion ? *_settings.eofVersion : 0)).asBytes();
164
56.2k
  rawKey += keccak256(_settings.yulOptimiserSteps).asBytes();
165
56.2k
  rawKey += keccak256(_settings.yulOptimiserCleanupSteps).asBytes();
166
167
56.2k
  return h256(keccak256(rawKey));
168
56.2k
}