Coverage Report

Created: 2026-06-30 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
530k
  explicit MiniEVMInterpreter(EVMDialect const& _dialect): m_dialect(_dialect) {}
44
45
  u256 eval(Expression const& _expr)
46
976k
  {
47
976k
    return std::visit(*this, _expr);
48
976k
  }
49
50
  u256 eval(evmasm::Instruction _instr, std::vector<Expression> const& _arguments)
51
233k
  {
52
233k
    std::vector<u256> args;
53
233k
    for (auto const& arg: _arguments)
54
445k
      args.emplace_back(eval(arg));
55
233k
    switch (_instr)
56
233k
    {
57
20.3k
    case evmasm::Instruction::ADD:
58
20.3k
      return args.at(0) + args.at(1);
59
63.8k
    case evmasm::Instruction::SUB:
60
63.8k
      return args.at(0) - args.at(1);
61
4
    case evmasm::Instruction::MUL:
62
4
      return args.at(0) * args.at(1);
63
2.84k
    case evmasm::Instruction::EXP:
64
2.84k
      return exp256(args.at(0), args.at(1));
65
125k
    case evmasm::Instruction::SHL:
66
125k
      return args.at(0) > 255 ? 0 : (args.at(1) << unsigned(args.at(0)));
67
20.9k
    case evmasm::Instruction::NOT:
68
20.9k
      return ~args.at(0);
69
0
    default:
70
0
      yulAssert(false, "Invalid operation generated in constant optimizer.");
71
233k
    }
72
0
    return 0;
73
233k
  }
74
75
  u256 operator()(FunctionCall const& _funCall)
76
233k
  {
77
233k
    BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, m_dialect);
78
233k
    yulAssert(builtin, "Expected builtin function.");
79
233k
    yulAssert(builtin->instruction, "Expected EVM instruction.");
80
233k
    return eval(*builtin->instruction, _funCall.arguments);
81
233k
  }
82
  u256 operator()(Literal const& _literal)
83
742k
  {
84
742k
    return _literal.value.value();
85
742k
  }
86
0
  u256 operator()(Identifier const&) { yulAssert(false, ""); }
87
88
  EVMDialect const& m_dialect;
89
};
90
}
91
92
void ConstantOptimiser::visit(Expression& _e)
93
6.75M
{
94
6.75M
  if (std::holds_alternative<Literal>(_e))
95
3.10M
  {
96
3.10M
    Literal const& literal = std::get<Literal>(_e);
97
3.10M
    if (literal.kind != LiteralKind::Number)
98
53.5k
      return;
99
100
3.04M
    if (
101
3.04M
      Expression const* repr =
102
3.04M
        RepresentationFinder(m_dialect, m_meter, debugDataOf(_e), m_cache)
103
3.04M
        .tryFindRepresentation(literal.value.value())
104
3.04M
    )
105
271k
      _e = ASTCopier{}.translate(*repr);
106
3.04M
  }
107
3.64M
  else
108
3.64M
    ASTModifier::visit(_e);
109
6.75M
}
110
111
Expression const* RepresentationFinder::tryFindRepresentation(u256 const& _value)
112
3.04M
{
113
3.04M
  if (_value < 0x10000)
114
2.39M
    return nullptr;
115
116
657k
  Representation const& repr = findRepresentation(_value);
117
657k
  if (std::holds_alternative<Literal>(*repr.expression))
118
386k
    return nullptr;
119
271k
  else
120
271k
    return repr.expression.get();
121
657k
}
122
123
Representation const& RepresentationFinder::findRepresentation(u256 const& _value)
124
1.93M
{
125
1.93M
  if (m_cache.count(_value))
126
1.40M
    return m_cache.at(_value);
127
128
530k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().not_);
129
530k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().exp);
130
530k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().mul);
131
530k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().add);
132
530k
  yulAssert(m_dialect.auxiliaryBuiltinHandles().sub);
133
134
530k
  auto const& auxHandles = m_dialect.auxiliaryBuiltinHandles();
135
136
530k
  Representation routine = represent(_value);
137
138
530k
  if (numberEncodingSize(~_value) < numberEncodingSize(_value))
139
    // Negated is shorter to represent
140
23.4k
    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
131M
  for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
144
131M
  {
145
131M
    unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff);
146
131M
    if (gapDetector != 0xff && gapDetector != 0x100)
147
130M
      continue;
148
149
874k
    u256 powerOfTwo = u256(1) << bits;
150
874k
    u256 upperPart = _value >> bits;
151
874k
    bigint lowerPart = _value & (powerOfTwo - 1);
152
874k
    if ((powerOfTwo - lowerPart) < lowerPart)
153
377k
    {
154
377k
      lowerPart = lowerPart - powerOfTwo; // make it negative
155
377k
      upperPart++;
156
377k
    }
157
874k
    if (upperPart == 0)
158
0
      continue;
159
874k
    if (abs(lowerPart) >= (powerOfTwo >> 8))
160
975
      continue;
161
873k
    Representation newRoutine;
162
873k
    if (m_dialect.evmVersion().hasBitwiseShifting())
163
790k
      newRoutine = represent(*auxHandles.shl, represent(bits), findRepresentation(upperPart));
164
82.5k
    else
165
82.5k
    {
166
82.5k
      newRoutine = represent(*auxHandles.exp, represent(2), represent(bits));
167
82.5k
      if (upperPart != 1)
168
31.2k
        newRoutine = represent(*auxHandles.mul, findRepresentation(upperPart), newRoutine);
169
82.5k
    }
170
171
873k
    if (newRoutine.cost >= routine.cost)
172
396k
      continue;
173
174
476k
    if (lowerPart > 0)
175
201k
      newRoutine = represent(*auxHandles.add, newRoutine, findRepresentation(u256(abs(lowerPart))));
176
274k
    else if (lowerPart < 0)
177
232k
      newRoutine = represent(*auxHandles.sub, newRoutine, findRepresentation(u256(abs(lowerPart))));
178
179
476k
    if (m_maxSteps > 0)
180
476k
      m_maxSteps--;
181
476k
    routine = min(std::move(routine), std::move(newRoutine));
182
476k
  }
183
530k
  yulAssert(MiniEVMInterpreter{m_dialect}.eval(*routine.expression) == _value, "Invalid expression generated.");
184
530k
  return m_cache[_value] = std::move(routine);
185
1.93M
}
186
187
Representation RepresentationFinder::represent(u256 const& _value) const
188
1.48M
{
189
1.48M
  Representation repr;
190
1.48M
  repr.expression = std::make_unique<Expression>(Literal{m_debugData, LiteralKind::Number, LiteralValue{_value, formatNumber(_value)}});
191
1.48M
  repr.cost = m_meter.costs(*repr.expression);
192
1.48M
  return repr;
193
1.48M
}
194
195
Representation RepresentationFinder::represent(
196
  BuiltinHandle const& _instruction,
197
  Representation const& _argument
198
) const
199
23.4k
{
200
23.4k
  Representation repr;
201
23.4k
  repr.expression = std::make_unique<Expression>(FunctionCall{
202
23.4k
    m_debugData,
203
23.4k
    BuiltinName{m_debugData, _instruction},
204
23.4k
    {ASTCopier{}.translate(*_argument.expression)}
205
23.4k
  });
206
23.4k
  repr.cost = _argument.cost + m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction);
207
23.4k
  return repr;
208
23.4k
}
209
210
Representation RepresentationFinder::represent(
211
  BuiltinHandle const& _instruction,
212
  Representation const& _arg1,
213
  Representation const& _arg2
214
) const
215
1.33M
{
216
1.33M
  Representation repr;
217
1.33M
  repr.expression = std::make_unique<Expression>(FunctionCall{
218
1.33M
    m_debugData,
219
1.33M
    BuiltinName{m_debugData, _instruction},
220
1.33M
    {ASTCopier{}.translate(*_arg1.expression), ASTCopier{}.translate(*_arg2.expression)}
221
1.33M
  });
222
1.33M
  repr.cost = m_meter.instructionCosts(*m_dialect.builtin(_instruction).instruction) + _arg1.cost + _arg2.cost;
223
1.33M
  return repr;
224
1.33M
}
225
226
Representation RepresentationFinder::min(Representation _a, Representation _b)
227
499k
{
228
499k
  if (_a.cost <= _b.cost)
229
350k
    return _a;
230
149k
  else
231
149k
    return _b;
232
499k
}