/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 | } |