Coverage Report

Created: 2026-07-13 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/ObjectOptimizer.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
#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
void ObjectOptimizer::optimize(Object& _object, Settings const& _settings)
47
44.8k
{
48
44.8k
  yulAssert(_object.subId.empty(), "Not a top-level object.");
49
50
44.8k
  optimize(_object, _settings, true /* _isCreation */);
51
44.8k
}
52
53
void ObjectOptimizer::optimize(Object& _object, Settings const& _settings, bool _isCreation)
54
51.7k
{
55
51.7k
  yulAssert(_object.code());
56
51.7k
  yulAssert(_object.debugData);
57
58
51.7k
  for (auto& subNode: _object.subObjects)
59
12.7k
    if (auto subObject = dynamic_cast<Object*>(subNode.get()))
60
6.88k
    {
61
6.88k
      bool isCreation = !boost::ends_with(subObject->name, "_deployed");
62
6.88k
      optimize(
63
6.88k
        *subObject,
64
6.88k
        _settings,
65
6.88k
        isCreation
66
6.88k
      );
67
6.88k
    }
68
69
51.7k
  EVMDialect const& dialect = EVMDialect::strictAssemblyForEVMObjects(_settings.evmVersion);
70
51.7k
  GasMeter const meter(dialect, _isCreation, _settings.expectedExecutionsPerDeployment);
71
72
51.7k
  std::optional<h256> cacheKey = calculateCacheKey(_object.code()->root(), *_object.debugData, _settings, _isCreation);
73
51.7k
  if (cacheKey.has_value() && m_cachedObjects.count(*cacheKey) != 0)
74
2.33k
  {
75
2.33k
    overwriteWithOptimizedObject(*cacheKey, _object);
76
2.33k
    return;
77
2.33k
  }
78
79
49.3k
  OptimiserSuite::run(
80
49.3k
    &meter,
81
49.3k
    _object,
82
49.3k
    _settings.optimizeStackAllocation,
83
49.3k
    _settings.yulOptimiserSteps,
84
49.3k
    _settings.yulOptimiserCleanupSteps,
85
49.3k
    _isCreation ? std::nullopt : std::make_optional(_settings.expectedExecutionsPerDeployment),
86
49.3k
    {}
87
49.3k
  );
88
89
49.3k
  if (cacheKey.has_value())
90
49.3k
    storeOptimizedObject(*cacheKey, _object, dialect);
91
49.3k
}
92
93
void ObjectOptimizer::storeOptimizedObject(util::h256 _cacheKey, Object const& _optimizedObject, Dialect const& _dialect)
94
49.3k
{
95
49.3k
  m_cachedObjects[_cacheKey] = CachedObject{
96
49.3k
    std::make_shared<Block>(ASTCopier{}.translate(_optimizedObject.code()->root())),
97
49.3k
    &_dialect,
98
49.3k
  };
99
49.3k
}
100
101
void ObjectOptimizer::overwriteWithOptimizedObject(util::h256 _cacheKey, Object& _object) const
102
2.33k
{
103
2.33k
  yulAssert(m_cachedObjects.count(_cacheKey) != 0);
104
2.33k
  CachedObject const& cachedObject = m_cachedObjects.at(_cacheKey);
105
106
2.33k
  yulAssert(cachedObject.optimizedAST);
107
2.33k
  yulAssert(cachedObject.dialect);
108
2.33k
  _object.setCode(std::make_shared<AST>(*cachedObject.dialect, ASTCopier{}.translate(*cachedObject.optimizedAST)));
109
2.33k
  yulAssert(_object.code());
110
2.33k
  yulAssert(_object.dialect());
111
112
  // There's no point in caching AnalysisInfo because it references AST nodes. It can't be shared
113
  // by multiple ASTs and it's easier to recalculate it than properly clone it.
114
2.33k
  _object.analysisInfo = std::make_shared<AsmAnalysisInfo>(
115
2.33k
    AsmAnalyzer::analyzeStrictAssertCorrect(
116
2.33k
      _object
117
2.33k
    )
118
2.33k
  );
119
120
  // NOTE: Source name index is included in the key so it must be identical. No need to store and restore it.
121
2.33k
}
122
123
std::optional<h256> ObjectOptimizer::calculateCacheKey(
124
  Block const& _ast,
125
  ObjectDebugData const& _debugData,
126
  Settings const& _settings,
127
  bool _isCreation
128
)
129
51.7k
{
130
51.7k
  AsmPrinter asmPrinter(
131
51.7k
    EVMDialect::strictAssemblyForEVMObjects(_settings.evmVersion),
132
51.7k
    _debugData.sourceNames,
133
51.7k
    DebugInfoSelection::All()
134
51.7k
  );
135
136
51.7k
  bytes rawKey;
137
  // NOTE: AsmPrinter never prints nativeLocations included in debug data, so ASTs differing only
138
  // in that regard are considered equal here.  This is fine because the optimizer does not keep
139
  // them up to date across AST transformations anyway so in any use where they need to be reliable,
140
  // we just regenerate them by reparsing the object.
141
51.7k
  rawKey += keccak256(asmPrinter(_ast)).asBytes();
142
51.7k
  rawKey += keccak256(_debugData.formatUseSrcComment()).asBytes();
143
51.7k
  static_assert(static_cast<uint8_t>(static_cast<bool>(2)) == 1);
144
51.7k
  rawKey += FixedHash<1>(static_cast<uint8_t>(_settings.optimizeStackAllocation)).asBytes();
145
51.7k
  rawKey += h256(u256(_settings.expectedExecutionsPerDeployment)).asBytes();
146
51.7k
  rawKey += FixedHash<1>(static_cast<uint8_t>(_isCreation)).asBytes();
147
51.7k
  rawKey += keccak256(_settings.evmVersion.name()).asBytes();
148
51.7k
  rawKey += keccak256(_settings.yulOptimiserSteps).asBytes();
149
51.7k
  rawKey += keccak256(_settings.yulOptimiserCleanupSteps).asBytes();
150
151
51.7k
  return h256(keccak256(rawKey));
152
51.7k
}