Coverage Report

Created: 2025-09-08 08:10

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