Coverage Report

Created: 2026-07-13 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libyul/backends/evm/ConstantOptimiser.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
 * Optimisation stage that replaces constants by expressions that compute them.
20
 */
21
22
#include <libyul/backends/evm/ConstantOptimiser.h>
23
24
#include <libyul/optimiser/ASTCopier.h>
25
#include <libyul/backends/evm/EVMMetrics.h>
26
#include <libyul/AST.h>
27
#include <libyul/Utilities.h>
28
29
#include <libsolutil/CommonData.h>
30
31
#include <variant>
32
33
using namespace solidity;
34
using namespace solidity::yul;
35
using namespace solidity::util;
36
37
using Representation = ConstantOptimiser::Representation;
38
39
namespace
40
{
41
struct MiniEVMInterpreter
42
{
43
625k
  explicit MiniEVMInterpreter(EVMDialect const& _dialect): m_dialect(_dialect) {}
44
45
  u256 eval(Expression const& _expr)
46
1.14M
  {
47
1.14M
    return std::visit(*this, _expr);
48
1.14M
  }
49
50
  u256 eval(evmasm::Instruction _instr, std::vector<Expression> const& _arguments)
51
274k
  {
52
274k
    std::vector<u256> args;
53
274k
    for (auto const& arg: _arguments)
54
523k
      args.emplace_back(eval(arg));
55
274k
    switch (_instr)
56
274k
    {
57
23.6k
    case evmasm::Instruction::ADD:
58
23.6k
      return args.at(0) + args.at(1);
59
75.2k
    case evmasm::Instruction::SUB:
60
75.2k
      return args.at(0) - args.at(1);
61
4
    case evmasm::Instruction::MUL:
62
4
      return args.at(0) * args.at(1);
63
3.32k
    case evmasm::Instruction::EXP:
64
3.32k
      return exp256(args.at(0), args.at(1));
65
146k
    case evmasm::Instruction::SHL:
66
146k
      return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0)));
67
25.0k
    case evmasm::Instruction::NOT:
68
25.0k
      return ~args.at(0);
69
0
    default:
70
0
      yulAssert(false, "Invalid operation generated in constant optimizer.");
71
274k
    }
72
0
    return 0;
73
274k
  }
74
75
  u256 operator()(FunctionCall const& _funCall)
76
274k
  {
77
274k
    BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, m_dialect);
78
274k
    yulAssert(builtin, "Expected builtin function.");
79
274k
    yulAssert(builtin->instruction, "Expected EVM instruction.");
80
274k
    return eval(*builtin->instruction, _funCall.arguments);
81
274k
  }
82
  u256 operator()(Literal const& _literal)
83
874k
  {
84
874k
    return _literal.value.value();
85
874k
  }
86
0
  u256 operator()(Identifier const&) { yulAssert(false, ""); }
87
88
  EVMDialect const& m_dialect;
89
};
90
}
91
92
void ConstantOptimiser::visit(Expression& _e)
93
7.91M
{
94
7.91M
  if (std::holds_alternative<Literal>(_e))
95
3.62M
  {
96
3.62M
    Literal const& literal = std::get<Literal>(_e);
97
3.62M
    if (literal.kind != LiteralKind::Number)
98
61.4k
      return;
99
100
3.55M
    if (
101
3.55M
      Expression const* repr =
102
3.55M
        RepresentationFinder(m_dialect, m_meter, debugDataOf(_e), m_cache)
103
3.55M
        .tryFindRepresentation(literal.value.value())
104
3.55M
    )
105
321k
      _e = ASTCopier{}.translate(*repr);
106
3.55M
  }
107
4.29M
  else
108
4.29M
    ASTModifier::visit(_e);
109
7.91M
}
110
111
Expression const* RepresentationFinder::tryFindRepresentation(u256 const& _value)
112
3.55M
{
113
3.55M
  if (_value < 0x10000)
114
2.78M
    return nullptr;
115
116
773k
  Representation const& repr = findRepresentation(_value);
117
773k
  if (std::holds_alternative<Literal>(*repr.expression))
118
451k
    return nullptr;
119
321k
  else
120
321k
    return repr.expression.get();
121
773k
}
122
123
Representation const& RepresentationFinder::findRepresentation(u256 const& _value)
124
2.25M
{
125
2.25M
  if (m_cache.count(_value))
126
1.62M
    return m_cache.at(_value);
127
128
625k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().not_);
129
625k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().exp);
130
625k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().mul);
131
625k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().add);
132
625k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().sub);
133
134
625k
  auto const& auxHandles = m_dialect.auxiliaryBuiltinHandles();
135
136
625k
  Representation routine = represent(_value);
137
138
625k
  if (numberEncodingSize(~_value) < numberEncodingSize(_value))
139
    // Negated is shorter to represent
140
28.2k
    routine = min(std::move(routine), represent(*auxHandles.not_, findRepresentation(~_value)));
141
142
  // Decompose value into a * 2**k + b where abs(b) << 2**k
143
155M
  for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
144
154M
  {
145
154M
    unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff);
146
154M
    if (gapDetector != 0xff && gapDetector != 0x100)
147
153M
      continue;
148
149
1.01M
    u256 powerOfTwo = u256(1) << bits;
150
1.01M
    u256 upperPart = _value >> bits;
151
1.01M
    bigint lowerPart = _value & (powerOfTwo - 1);
152
1.01M
    if ((powerOfTwo - lowerPart) < lowerPart)
153
441k
    {
154
441k
      lowerPart = lowerPart - powerOfTwo; // make it negative
155
441k
      upperPart++;
156
441k
    }
157
1.01M
    if (upperPart == 0)
158
0
      continue;
159
1.01M
    if (abs(lowerPart) >= (powerOfTwo >> 8))
160
1.14k
      continue;
161
1.01M
    Representation newRoutine;
162
1.01M
    if (m_dialect.evmVersion().hasBitwiseShifting())
163
909k
      newRoutine = represent(*auxHandles.shl, represent(bits), findRepresentation(upperPart));
164
104k
    else
165
104k
    {
166
104k
      newRoutine = represent(*auxHandles.exp, represent(2), represent(bits));
167
104k
      if (upperPart != 1)
168
40.7k
        newRoutine = represent(*auxHandles.mul, findRepresentation(upperPart), newRoutine);
169
104k
    }
170
171
1.01M
    if (newRoutine.cost >= routine.cost)
172
462k
      continue;
173
174
550k
    if (lowerPart > 0)
175
231k
      newRoutine = represent(*auxHandles.add, newRoutine, findRepresentation(u256(abs(lowerPart))));
176
319k
    else if (lowerPart < 0)
177
270k
      newRoutine = represent(*auxHandles.sub, newRoutine, findRepresentation(u256(abs(lowerPart))));
178
179
550k
    if (m_maxSteps > 0)
180
550k
      m_maxSteps--;
181
550k
    routine = min(std::move(routine), std::move(newRoutine));
182
550k
  }
183
625k
  yulAssert(MiniEVMInterpreter{m_dialect}.eval(*routine.expression) == _value, "Invalid expression generated.");
184
625k
  return m_cache[_value] = std::move(routine);
185
2.25M
}
186
187
Representation RepresentationFinder::represent(u256 const& _value) const
188
1.74M
{
189
1.74M
  Representation repr;
190
1.74M
  repr.expression = std::make_unique<Expression>(Literal{m_debugData, LiteralKind::Number, LiteralValue{_value, formatNumber(_value)}});
191
1.74M
  repr.cost = m_meter.costs(*repr.expression);
192
1.74M
  return repr;
193
1.74M
}
194
195
Representation RepresentationFinder::represent(
196
  BuiltinHandle const& _instruction,
197
  Representation const& _argument
198
) const
199
28.2k
{
200
28.2k
  Representation repr;
201
28.2k
  repr.expression = std::make_unique<Expression>(FunctionCall{
202
28.2k
    m_debugData,
203
28.2k
    BuiltinName{m_debugData, _instruction},
204
28.2k
    {ASTCopier{}.translate(*_argument.expression)}
205
28.2k
  });
206
28.2k
  repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction);
207
28.2k
  return repr;
208
28.2k
}
209
210
Representation RepresentationFinder::represent(
211
  BuiltinHandle const& _instruction,
212
  Representation const& _arg1,
213
  Representation const& _arg2
214
) const
215
1.55M
{
216
1.55M
  Representation repr;
217
1.55M
  repr.expression = std::make_unique<Expression>(FunctionCall{
218
1.55M
    m_debugData,
219
1.55M
    BuiltinName{m_debugData, _instruction},
220
1.55M
    {ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)}
221
1.55M
  });
222
1.55M
  repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction) + _arg1.cost + _arg2.cost;
223
1.55M
  return repr;
224
1.55M
}
225
226
Representation RepresentationFinder::min(Representation _a, Representation _b)
227
579k
{
228
579k
  if (_a.cost <= _b.cost)
229
403k
    return _a;
230
175k
  else
231
175k
    return _b;
232
579k
}