Coverage Report

Created: 2025-09-04 07:34

/src/solidity/libevmasm/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
/** @file ConstantOptimiser.cpp
19
 * @author Christian <c@ethdev.com>
20
 * @date 2015
21
 */
22
23
#include <libevmasm/ConstantOptimiser.h>
24
#include <libevmasm/Assembly.h>
25
#include <libevmasm/GasMeter.h>
26
27
using namespace solidity;
28
using namespace solidity::evmasm;
29
30
unsigned ConstantOptimisationMethod::optimiseConstants(
31
  bool _isCreation,
32
  size_t _runs,
33
  langutil::EVMVersion _evmVersion,
34
  Assembly& _assembly
35
)
36
67.6k
{
37
  // TODO: design the optimiser in a way this is not needed
38
67.6k
  unsigned optimisations = 0;
39
67.6k
  for (auto& codeSection: _assembly.codeSections())
40
67.6k
  {
41
67.6k
    AssemblyItems& _items = codeSection.items;
42
43
67.6k
    std::map<AssemblyItem, size_t> pushes;
44
67.6k
    for (AssemblyItem const& item: _items)
45
3.74M
      if (item.type() == Push)
46
1.08M
        pushes[item]++;
47
67.6k
    std::map<u256, AssemblyItems> pendingReplacements;
48
67.6k
    for (auto it: pushes)
49
474k
    {
50
474k
      AssemblyItem const& item = it.first;
51
474k
      if (item.data() < 0x100)
52
152k
        continue;
53
322k
      Params params;
54
322k
      params.multiplicity = it.second;
55
322k
      params.isCreation = _isCreation;
56
322k
      params.runs = _runs;
57
322k
      params.evmVersion = _evmVersion;
58
322k
      LiteralMethod lit(params, item.data());
59
322k
      bigint literalGas = lit.gasNeeded();
60
322k
      CodeCopyMethod copy(params, item.data());
61
322k
      bigint copyGas = copy.gasNeeded();
62
322k
      ComputeMethod compute(params, item.data());
63
322k
      bigint computeGas = compute.gasNeeded();
64
322k
      AssemblyItems replacement;
65
322k
      if (copyGas < literalGas && copyGas < computeGas)
66
1.14k
      {
67
1.14k
        replacement = copy.execute(_assembly);
68
1.14k
        optimisations++;
69
1.14k
      }
70
320k
      else if (computeGas < literalGas && computeGas <= copyGas)
71
135k
      {
72
135k
        replacement = compute.execute(_assembly);
73
135k
        optimisations++;
74
135k
      }
75
322k
      if (!replacement.empty())
76
136k
        pendingReplacements[item.data()] = replacement;
77
322k
    }
78
67.6k
    if (!pendingReplacements.empty())
79
35.3k
      replaceConstants(_items, pendingReplacements);
80
67.6k
  }
81
67.6k
  return optimisations;
82
67.6k
}
83
84
bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items, langutil::EVMVersion _evmVersion)
85
29.9M
{
86
29.9M
  bigint gas = 0;
87
29.9M
  for (AssemblyItem const& item: _items)
88
104M
    if (item.type() == Push)
89
66.3M
      gas += GasMeter::pushGas(item.data(), _evmVersion);
90
37.7M
    else if (item.type() == Operation)
91
37.4M
    {
92
37.4M
      if (item.instruction() == Instruction::EXP)
93
127k
        gas += GasCosts::expGas;
94
37.3M
      else
95
37.3M
        gas += GasMeter::runGas(item.instruction(), _evmVersion);
96
37.4M
    }
97
29.9M
  return gas;
98
29.9M
}
99
100
bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
101
644k
{
102
644k
  assertThrow(_data.size() > 0, OptimizerException, "Empty bytecode generated.");
103
644k
  return bigint(GasMeter::dataGas(_data, m_params.isCreation, m_params.evmVersion));
104
644k
}
105
106
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items, langutil::EVMVersion _evmVersion)
107
29.6M
{
108
29.6M
  return evmasm::bytesRequired(_items, 3, _evmVersion, Precision::Approximate); // assume 3 byte addresses
109
29.6M
}
110
111
void ConstantOptimisationMethod::replaceConstants(
112
  AssemblyItems& _items,
113
  std::map<u256, AssemblyItems> const& _replacements
114
)
115
35.3k
{
116
35.3k
  AssemblyItems replaced;
117
35.3k
  for (AssemblyItem const& item: _items)
118
3.20M
  {
119
3.20M
    if (item.type() == Push)
120
885k
    {
121
885k
      auto it = _replacements.find(item.data());
122
885k
      if (it != _replacements.end())
123
185k
      {
124
185k
        replaced += it->second;
125
185k
        continue;
126
185k
      }
127
885k
    }
128
3.02M
    replaced.push_back(item);
129
3.02M
  }
130
35.3k
  _items = std::move(replaced);
131
35.3k
}
132
133
bigint LiteralMethod::gasNeeded() const
134
322k
{
135
322k
  return combineGas(
136
322k
    simpleRunGas({Instruction::PUSH1}, m_params.evmVersion),
137
    // PUSHX plus data
138
322k
    (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)),
139
322k
    0
140
322k
  );
141
322k
}
142
143
AssemblyItems LiteralMethod::execute(Assembly&) const
144
0
{
145
0
  return {};
146
0
}
147
148
bigint CodeCopyMethod::gasNeeded() const
149
322k
{
150
322k
  return combineGas(
151
    // Run gas: we ignore memory increase costs
152
322k
    simpleRunGas(copyRoutine(), m_params.evmVersion) + GasCosts::copyGas,
153
    // Data gas for copy routines: Some bytes are zero, but we ignore them.
154
322k
    bytesRequired(copyRoutine(), m_params.evmVersion) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
155
    // Data gas for data itself
156
322k
    dataGas(toBigEndian(m_value))
157
322k
  );
158
322k
}
159
160
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
161
1.14k
{
162
1.14k
  bytes data = toBigEndian(m_value);
163
1.14k
  assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
164
1.14k
  AssemblyItem newPushData = _assembly.newData(data);
165
1.14k
  return copyRoutine(&newPushData);
166
1.14k
}
167
168
AssemblyItems CodeCopyMethod::copyRoutine(AssemblyItem* _pushData) const
169
645k
{
170
645k
  if (_pushData)
171
645k
    assertThrow(_pushData->type() == PushData, OptimizerException, "Invalid Assembly Item.");
172
173
645k
  AssemblyItem dataUsed = _pushData ? *_pushData : AssemblyItem(PushData, u256(1) << 16);
174
175
  // PUSH0 is cheaper than PUSHn/DUP/SWAP.
176
645k
  if (m_params.evmVersion.hasPush0())
177
607k
  {
178
    // This costs ~29 gas.
179
607k
    AssemblyItems copyRoutine{
180
      // back up memory
181
      // mload(0)
182
607k
      u256(0),
183
607k
      Instruction::MLOAD,
184
185
      // codecopy(0, <offset>, 32)
186
607k
      u256(32),
187
607k
      dataUsed,
188
607k
      u256(0),
189
607k
      Instruction::CODECOPY,
190
191
      // mload(0)
192
607k
      u256(0),
193
607k
      Instruction::MLOAD,
194
195
      // restore original memory
196
      // mstore(0, x)
197
607k
      Instruction::SWAP1,
198
607k
      u256(0),
199
607k
      Instruction::MSTORE
200
607k
    };
201
607k
    return copyRoutine;
202
607k
  }
203
38.4k
  else
204
38.4k
  {
205
    // This costs ~33 gas.
206
38.4k
    AssemblyItems copyRoutine{
207
      // constant to be reused 3+ times
208
38.4k
      u256(0),
209
210
      // back up memory
211
      // mload(0)
212
38.4k
      Instruction::DUP1,
213
38.4k
      Instruction::MLOAD,
214
215
      // codecopy(0, <offset>, 32)
216
38.4k
      u256(32),
217
38.4k
      dataUsed,
218
38.4k
      Instruction::DUP4,
219
38.4k
      Instruction::CODECOPY,
220
221
      // mload(0)
222
38.4k
      Instruction::DUP2,
223
38.4k
      Instruction::MLOAD,
224
225
      // restore original memory
226
      // mstore(0, x)
227
38.4k
      Instruction::SWAP2,
228
38.4k
      Instruction::MSTORE
229
38.4k
    };
230
38.4k
    return copyRoutine;
231
38.4k
  }
232
645k
}
233
234
ComputeMethod::ComputeMethod(Params const& _params, u256 const& _value):
235
  ConstantOptimisationMethod(_params, _value)
236
322k
{
237
322k
  m_routine = findRepresentation(m_value);
238
322k
  assertThrow(
239
322k
    checkRepresentation(m_value, m_routine),
240
322k
    OptimizerException,
241
322k
    "Invalid constant expression created."
242
322k
  );
243
322k
}
244
322k
ComputeMethod::~ComputeMethod() = default;
245
246
AssemblyItems ComputeMethod::execute(Assembly&) const
247
135k
{
248
135k
  return m_routine;
249
135k
}
250
251
AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
252
34.0M
{
253
34.0M
  if (_value < 0x10000)
254
    // Very small value, not worth computing
255
22.1M
    return AssemblyItems{_value};
256
11.8M
  else if (numberEncodingSize(~_value) < numberEncodingSize(_value))
257
    // Negated is shorter to represent
258
38.3k
    return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
259
11.8M
  else
260
11.8M
  {
261
    // Decompose value into a * 2**k + b where abs(b) << 2**k
262
    // Is not always better, try literal and decomposition method.
263
11.8M
    AssemblyItems routine{u256(_value)};
264
11.8M
    bigint bestGas = gasNeeded(routine);
265
2.92G
    for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
266
2.91G
    {
267
2.91G
      unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff);
268
2.91G
      if (gapDetector != 0xff && gapDetector != 0x100)
269
2.89G
        continue;
270
271
17.2M
      u256 powerOfTwo = u256(1) << bits;
272
17.2M
      u256 upperPart = _value >> bits;
273
17.2M
      bigint lowerPart = _value & (powerOfTwo - 1);
274
17.2M
      if ((powerOfTwo - lowerPart) < lowerPart)
275
8.19M
      {
276
8.19M
        lowerPart = lowerPart - powerOfTwo; // make it negative
277
8.19M
        upperPart++;
278
8.19M
      }
279
17.2M
      if (upperPart == 0)
280
0
        continue;
281
17.2M
      if (abs(lowerPart) >= (powerOfTwo >> 8))
282
88.9k
        continue;
283
284
17.1M
      AssemblyItems newRoutine;
285
17.1M
      if (lowerPart != 0)
286
16.5M
        newRoutine += findRepresentation(u256(abs(lowerPart)));
287
17.1M
      if (m_params.evmVersion.hasBitwiseShifting())
288
17.0M
      {
289
17.0M
        newRoutine += findRepresentation(upperPart);
290
17.0M
        newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
291
17.0M
      }
292
126k
      else
293
126k
      {
294
126k
        newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
295
126k
        if (upperPart != 1)
296
93.0k
          newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
297
126k
      }
298
17.1M
      if (lowerPart > 0)
299
8.41M
        newRoutine += AssemblyItems{Instruction::ADD};
300
8.77M
      else if (lowerPart < 0)
301
8.10M
        newRoutine.push_back(Instruction::SUB);
302
303
17.1M
      if (m_maxSteps > 0)
304
17.1M
        m_maxSteps--;
305
17.1M
      bigint newGas = gasNeeded(newRoutine);
306
17.1M
      if (newGas < bestGas)
307
1.18M
      {
308
1.18M
        bestGas = std::move(newGas);
309
1.18M
        routine = std::move(newRoutine);
310
1.18M
      }
311
17.1M
    }
312
11.8M
    return routine;
313
11.8M
  }
314
34.0M
}
315
316
bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const
317
322k
{
318
  // This is a tiny EVM that can only evaluate some instructions.
319
322k
  std::vector<u256> stack;
320
322k
  for (AssemblyItem const& item: _routine)
321
997k
  {
322
997k
    switch (item.type())
323
997k
    {
324
356k
    case Operation:
325
356k
    {
326
356k
      if (stack.size() < item.arguments())
327
0
        return false;
328
356k
      u256* sp = &stack.back();
329
356k
      switch (item.instruction())
330
356k
      {
331
713
      case Instruction::MUL:
332
713
        sp[-1] = sp[0] * sp[-1];
333
713
        break;
334
776
      case Instruction::EXP:
335
776
        if (sp[-1] > 0xff)
336
0
          return false;
337
776
        sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
338
776
        break;
339
54.5k
      case Instruction::ADD:
340
54.5k
        sp[-1] = sp[0] + sp[-1];
341
54.5k
        break;
342
67.8k
      case Instruction::SUB:
343
67.8k
        sp[-1] = sp[0] - sp[-1];
344
67.8k
        break;
345
38.3k
      case Instruction::NOT:
346
38.3k
        sp[0] = ~sp[0];
347
38.3k
        break;
348
194k
      case Instruction::SHL:
349
194k
        assertThrow(
350
194k
          m_params.evmVersion.hasBitwiseShifting(),
351
194k
          OptimizerException,
352
194k
          "Shift generated for invalid EVM version."
353
194k
        );
354
194k
        assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
355
194k
        sp[-1] = u256(bigint(sp[-1]) << unsigned(sp[0]));
356
194k
        break;
357
0
      case Instruction::SHR:
358
0
        assertThrow(
359
0
          m_params.evmVersion.hasBitwiseShifting(),
360
0
          OptimizerException,
361
0
          "Shift generated for invalid EVM version."
362
0
        );
363
0
        assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
364
0
        sp[-1] = sp[-1] >> unsigned(sp[0]);
365
0
        break;
366
0
      default:
367
0
        return false;
368
356k
      }
369
356k
      stack.resize(stack.size() + item.deposit());
370
356k
      break;
371
356k
    }
372
640k
    case Push:
373
640k
      stack.push_back(item.data());
374
640k
      break;
375
0
    default:
376
0
      return false;
377
997k
    }
378
997k
  }
379
322k
  return stack.size() == 1 && stack.front() == _value;
380
322k
}
381
382
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const
383
29.3M
{
384
29.3M
  auto numExps = static_cast<size_t>(count(_routine.begin(), _routine.end(), Instruction::EXP));
385
29.3M
  return combineGas(
386
29.3M
    simpleRunGas(_routine, m_params.evmVersion) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)),
387
    // Data gas for routine: Some bytes are zero, but we ignore them.
388
29.3M
    bytesRequired(_routine, m_params.evmVersion) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
389
29.3M
    0
390
29.3M
  );
391
29.3M
}