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