Coverage Report

Created: 2022-08-24 06:31

/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 std;
28
using namespace solidity;
29
using namespace solidity::evmasm;
30
31
unsigned ConstantOptimisationMethod::optimiseConstants(
32
  bool _isCreation,
33
  size_t _runs,
34
  langutil::EVMVersion _evmVersion,
35
  Assembly& _assembly
36
)
37
1.56k
{
38
  // TODO: design the optimiser in a way this is not needed
39
1.56k
  AssemblyItems& _items = _assembly.items();
40
41
1.56k
  unsigned optimisations = 0;
42
1.56k
  map<AssemblyItem, size_t> pushes;
43
1.56k
  for (AssemblyItem const& item: _items)
44
158k
    if (item.type() == Push)
45
38.2k
      pushes[item]++;
46
1.56k
  map<u256, AssemblyItems> pendingReplacements;
47
1.56k
  for (auto it: pushes)
48
16.9k
  {
49
16.9k
    AssemblyItem const& item = it.first;
50
16.9k
    if (item.data() < 0x100)
51
11.8k
      continue;
52
5.15k
    Params params;
53
5.15k
    params.multiplicity = it.second;
54
5.15k
    params.isCreation = _isCreation;
55
5.15k
    params.runs = _runs;
56
5.15k
    params.evmVersion = _evmVersion;
57
5.15k
    LiteralMethod lit(params, item.data());
58
5.15k
    bigint literalGas = lit.gasNeeded();
59
5.15k
    CodeCopyMethod copy(params, item.data());
60
5.15k
    bigint copyGas = copy.gasNeeded();
61
5.15k
    ComputeMethod compute(params, item.data());
62
5.15k
    bigint computeGas = compute.gasNeeded();
63
5.15k
    AssemblyItems replacement;
64
5.15k
    if (copyGas < literalGas && copyGas < computeGas)
65
0
    {
66
0
      replacement = copy.execute(_assembly);
67
0
      optimisations++;
68
0
    }
69
5.15k
    else if (computeGas < literalGas && computeGas <= copyGas)
70
614
    {
71
614
      replacement = compute.execute(_assembly);
72
614
      optimisations++;
73
614
    }
74
5.15k
    if (!replacement.empty())
75
614
      pendingReplacements[item.data()] = replacement;
76
5.15k
  }
77
1.56k
  if (!pendingReplacements.empty())
78
285
    replaceConstants(_items, pendingReplacements);
79
1.56k
  return optimisations;
80
1.56k
}
81
82
bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items)
83
30.5k
{
84
30.5k
  bigint gas = 0;
85
30.5k
  for (AssemblyItem const& item: _items)
86
104k
    if (item.type() == Push)
87
41.8k
      gas += GasMeter::runGas(Instruction::PUSH1);
88
63.0k
    else if (item.type() == Operation)
89
57.9k
    {
90
57.9k
      if (item.instruction() == Instruction::EXP)
91
2.80k
        gas += GasCosts::expGas;
92
55.0k
      else
93
55.0k
        gas += GasMeter::runGas(item.instruction());
94
57.9k
    }
95
30.5k
  return gas;
96
30.5k
}
97
98
bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
99
10.3k
{
100
10.3k
  assertThrow(_data.size() > 0, OptimizerException, "Empty bytecode generated.");
101
10.3k
  return bigint(GasMeter::dataGas(_data, m_params.isCreation, m_params.evmVersion));
102
10.3k
}
103
104
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
105
25.4k
{
106
25.4k
  return evmasm::bytesRequired(_items, 3, Precision::Approximate); // assume 3 byte addresses
107
25.4k
}
108
109
void ConstantOptimisationMethod::replaceConstants(
110
  AssemblyItems& _items,
111
  map<u256, AssemblyItems> const& _replacements
112
)
113
285
{
114
285
  AssemblyItems replaced;
115
285
  for (AssemblyItem const& item: _items)
116
83.2k
  {
117
83.2k
    if (item.type() == Push)
118
16.6k
    {
119
16.6k
      auto it = _replacements.find(item.data());
120
16.6k
      if (it != _replacements.end())
121
1.23k
      {
122
1.23k
        replaced += it->second;
123
1.23k
        continue;
124
1.23k
      }
125
16.6k
    }
126
82.0k
    replaced.push_back(item);
127
82.0k
  }
128
285
  _items = std::move(replaced);
129
285
}
130
131
bigint LiteralMethod::gasNeeded() const
132
5.15k
{
133
5.15k
  return combineGas(
134
5.15k
    simpleRunGas({Instruction::PUSH1}),
135
    // PUSHX plus data
136
5.15k
    (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)),
137
5.15k
    0
138
5.15k
  );
139
5.15k
}
140
141
bigint CodeCopyMethod::gasNeeded() const
142
5.15k
{
143
5.15k
  return combineGas(
144
    // Run gas: we ignore memory increase costs
145
5.15k
    simpleRunGas(copyRoutine()) + GasCosts::copyGas,
146
    // Data gas for copy routines: Some bytes are zero, but we ignore them.
147
5.15k
    bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
148
    // Data gas for data itself
149
5.15k
    dataGas(toBigEndian(m_value))
150
5.15k
  );
151
5.15k
}
152
153
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
154
0
{
155
0
  bytes data = toBigEndian(m_value);
156
0
  assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding.");
157
0
  AssemblyItems actualCopyRoutine = copyRoutine();
158
0
  actualCopyRoutine[4] = _assembly.newData(data);
159
0
  return actualCopyRoutine;
160
0
}
161
162
AssemblyItems const& CodeCopyMethod::copyRoutine()
163
10.3k
{
164
10.3k
  AssemblyItems static copyRoutine{
165
    // constant to be reused 3+ times
166
10.3k
    u256(0),
167
168
    // back up memory
169
    // mload(0)
170
10.3k
    Instruction::DUP1,
171
10.3k
    Instruction::MLOAD,
172
173
    // codecopy(0, <offset>, 32)
174
10.3k
    u256(32),
175
10.3k
    AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4]
176
10.3k
    Instruction::DUP4,
177
10.3k
    Instruction::CODECOPY,
178
179
    // mload(0)
180
10.3k
    Instruction::DUP2,
181
10.3k
    Instruction::MLOAD,
182
183
    // restore original memory
184
10.3k
    Instruction::SWAP2,
185
10.3k
    Instruction::MSTORE
186
10.3k
  };
187
10.3k
  return copyRoutine;
188
10.3k
}
189
190
AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
191
13.3k
{
192
13.3k
  if (_value < 0x10000)
193
    // Very small value, not worth computing
194
3.08k
    return AssemblyItems{_value};
195
10.2k
  else if (numberEncodingSize(~_value) < numberEncodingSize(_value))
196
    // Negated is shorter to represent
197
259
    return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
198
9.98k
  else
199
9.98k
  {
200
    // Decompose value into a * 2**k + b where abs(b) << 2**k
201
    // Is not always better, try literal and decomposition method.
202
9.98k
    AssemblyItems routine{u256(_value)};
203
9.98k
    bigint bestGas = gasNeeded(routine);
204
2.47M
    for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
205
2.46M
    {
206
2.46M
      unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff);
207
2.46M
      if (gapDetector != 0xff && gapDetector != 0x100)
208
2.46M
        continue;
209
210
5.15k
      u256 powerOfTwo = u256(1) << bits;
211
5.15k
      u256 upperPart = _value >> bits;
212
5.15k
      bigint lowerPart = _value & (powerOfTwo - 1);
213
5.15k
      if ((powerOfTwo - lowerPart) < lowerPart)
214
1.57k
      {
215
1.57k
        lowerPart = lowerPart - powerOfTwo; // make it negative
216
1.57k
        upperPart++;
217
1.57k
      }
218
5.15k
      if (upperPart == 0)
219
0
        continue;
220
5.15k
      if (abs(lowerPart) >= (powerOfTwo >> 8))
221
26
        continue;
222
223
5.12k
      AssemblyItems newRoutine;
224
5.12k
      if (lowerPart != 0)
225
3.67k
        newRoutine += findRepresentation(u256(abs(lowerPart)));
226
5.12k
      if (m_params.evmVersion.hasBitwiseShifting())
227
2.60k
      {
228
2.60k
        newRoutine += findRepresentation(upperPart);
229
2.60k
        newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
230
2.60k
      }
231
2.51k
      else
232
2.51k
      {
233
2.51k
        newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
234
2.51k
        if (upperPart != 1)
235
1.62k
          newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
236
2.51k
      }
237
5.12k
      if (lowerPart > 0)
238
2.12k
        newRoutine += AssemblyItems{Instruction::ADD};
239
3.00k
      else if (lowerPart < 0)
240
1.55k
        newRoutine.push_back(Instruction::SUB);
241
242
5.12k
      if (m_maxSteps > 0)
243
5.12k
        m_maxSteps--;
244
5.12k
      bigint newGas = gasNeeded(newRoutine);
245
5.12k
      if (newGas < bestGas)
246
517
      {
247
517
        bestGas = move(newGas);
248
517
        routine = move(newRoutine);
249
517
      }
250
5.12k
    }
251
9.98k
    return routine;
252
9.98k
  }
253
13.3k
}
254
255
bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const
256
5.15k
{
257
  // This is a tiny EVM that can only evaluate some instructions.
258
5.15k
  vector<u256> stack;
259
5.15k
  for (AssemblyItem const& item: _routine)
260
7.07k
  {
261
7.07k
    switch (item.type())
262
7.07k
    {
263
1.08k
    case Operation:
264
1.08k
    {
265
1.08k
      if (stack.size() < item.arguments())
266
0
        return false;
267
1.08k
      u256* sp = &stack.back();
268
1.08k
      switch (item.instruction())
269
1.08k
      {
270
263
      case Instruction::MUL:
271
263
        sp[-1] = sp[0] * sp[-1];
272
263
        break;
273
286
      case Instruction::EXP:
274
286
        if (sp[-1] > 0xff)
275
0
          return false;
276
286
        sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
277
286
        break;
278
1
      case Instruction::ADD:
279
1
        sp[-1] = sp[0] + sp[-1];
280
1
        break;
281
54
      case Instruction::SUB:
282
54
        sp[-1] = sp[0] - sp[-1];
283
54
        break;
284
259
      case Instruction::NOT:
285
259
        sp[0] = ~sp[0];
286
259
        break;
287
226
      case Instruction::SHL:
288
226
        assertThrow(
289
226
          m_params.evmVersion.hasBitwiseShifting(),
290
226
          OptimizerException,
291
226
          "Shift generated for invalid EVM version."
292
226
        );
293
226
        assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
294
226
        sp[-1] = u256(bigint(sp[-1]) << unsigned(sp[0]));
295
226
        break;
296
0
      case Instruction::SHR:
297
0
        assertThrow(
298
0
          m_params.evmVersion.hasBitwiseShifting(),
299
0
          OptimizerException,
300
0
          "Shift generated for invalid EVM version."
301
0
        );
302
0
        assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
303
0
        sp[-1] = sp[-1] >> unsigned(sp[0]);
304
0
        break;
305
0
      default:
306
0
        return false;
307
1.08k
      }
308
1.08k
      stack.resize(stack.size() + item.deposit());
309
1.08k
      break;
310
1.08k
    }
311
5.98k
    case Push:
312
5.98k
      stack.push_back(item.data());
313
5.98k
      break;
314
0
    default:
315
0
      return false;
316
7.07k
    }
317
7.07k
  }
318
5.15k
  return stack.size() == 1 && stack.front() == _value;
319
5.15k
}
320
321
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const
322
20.2k
{
323
20.2k
  auto numExps = static_cast<size_t>(count(_routine.begin(), _routine.end(), Instruction::EXP));
324
20.2k
  return combineGas(
325
20.2k
    simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)),
326
    // Data gas for routine: Some bytes are zero, but we ignore them.
327
20.2k
    bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas),
328
20.2k
    0
329
20.2k
  );
330
20.2k
}