/src/solidity/libyul/optimiser/UnusedStoreEliminator.cpp
Line | Count | Source |
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 | | * Optimiser component that removes stores to memory and storage slots that are not used |
20 | | * or overwritten later on. |
21 | | */ |
22 | | |
23 | | #include <libyul/optimiser/UnusedStoreEliminator.h> |
24 | | |
25 | | #include <libyul/optimiser/Semantics.h> |
26 | | #include <libyul/optimiser/OptimizerUtilities.h> |
27 | | #include <libyul/optimiser/Semantics.h> |
28 | | #include <libyul/optimiser/SSAValueTracker.h> |
29 | | #include <libyul/optimiser/DataFlowAnalyzer.h> |
30 | | #include <libyul/optimiser/KnowledgeBase.h> |
31 | | #include <libyul/ControlFlowSideEffectsCollector.h> |
32 | | #include <libyul/AST.h> |
33 | | #include <libyul/Utilities.h> |
34 | | |
35 | | #include <libyul/backends/evm/EVMDialect.h> |
36 | | |
37 | | #include <libsolutil/CommonData.h> |
38 | | |
39 | | #include <libevmasm/Instruction.h> |
40 | | #include <libevmasm/SemanticInformation.h> |
41 | | |
42 | | #include <range/v3/algorithm/all_of.hpp> |
43 | | |
44 | | using namespace solidity; |
45 | | using namespace solidity::yul; |
46 | | |
47 | | void UnusedStoreEliminator::run(OptimiserStepContext& _context, Block& _ast) |
48 | 111k | { |
49 | 111k | std::map<FunctionHandle, SideEffects> functionSideEffects = SideEffectsPropagator::sideEffects( |
50 | 111k | _context.dialect, |
51 | 111k | CallGraphGenerator::callGraph(_ast) |
52 | 111k | ); |
53 | | |
54 | 111k | SSAValueTracker ssaValues; |
55 | 111k | ssaValues(_ast); |
56 | 111k | std::map<YulName, AssignedValue> values; |
57 | 111k | for (auto const& [name, expression]: ssaValues.values()) |
58 | 18.4M | values[name] = AssignedValue{expression, {}}; |
59 | | |
60 | 111k | bool const ignoreMemory = MSizeFinder::containsMSize(_context.dialect, _ast); |
61 | 111k | UnusedStoreEliminator rse{ |
62 | 111k | _context.dialect, |
63 | 111k | functionSideEffects, |
64 | 111k | ControlFlowSideEffectsCollector{_context.dialect, _ast}.functionSideEffectsNamed(), |
65 | 111k | values, |
66 | 111k | ignoreMemory |
67 | 111k | }; |
68 | 111k | rse(_ast); |
69 | | |
70 | 111k | auto evmDialect = dynamic_cast<EVMDialect const*>(&_context.dialect); |
71 | 111k | if (evmDialect && evmDialect->providesObjectAccess()) |
72 | 107k | rse.clearActive(Location::Memory); |
73 | 3.88k | else |
74 | 3.88k | rse.markActiveAsUsed(Location::Memory); |
75 | 111k | rse.markActiveAsUsed(Location::Storage); |
76 | 111k | rse.m_storesToRemove += rse.m_allStores - rse.m_usedStores; |
77 | | |
78 | 111k | std::set<Statement const*> toRemove{rse.m_storesToRemove.begin(), rse.m_storesToRemove.end()}; |
79 | 111k | StatementRemover remover{toRemove}; |
80 | 111k | remover(_ast); |
81 | 111k | } |
82 | | |
83 | | UnusedStoreEliminator::UnusedStoreEliminator( |
84 | | Dialect const& _dialect, |
85 | | std::map<FunctionHandle, SideEffects> const& _functionSideEffects, |
86 | | std::map<YulName, ControlFlowSideEffects> _controlFlowSideEffects, |
87 | | std::map<YulName, AssignedValue> const& _ssaValues, |
88 | | bool _ignoreMemory |
89 | | ): |
90 | 111k | UnusedStoreBase(_dialect), |
91 | 111k | m_ignoreMemory(_ignoreMemory), |
92 | 111k | m_functionSideEffects(_functionSideEffects), |
93 | 111k | m_controlFlowSideEffects(std::move(_controlFlowSideEffects)), |
94 | 111k | m_ssaValues(_ssaValues), |
95 | 111k | m_knowledgeBase(_ssaValues, _dialect) |
96 | 111k | {} |
97 | | |
98 | | void UnusedStoreEliminator::operator()(FunctionCall const& _functionCall) |
99 | 6.73M | { |
100 | 6.73M | UnusedStoreBase::operator()(_functionCall); |
101 | | |
102 | 6.73M | for (Operation const& op: operationsFromFunctionCall(_functionCall)) |
103 | 5.14M | applyOperation(op); |
104 | | |
105 | 6.73M | ControlFlowSideEffects sideEffects; |
106 | 6.73M | if (auto builtin = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) |
107 | 6.22M | sideEffects = builtin->controlFlowSideEffects; |
108 | 509k | else |
109 | 509k | { |
110 | 509k | yulAssert(std::holds_alternative<Identifier>(_functionCall.functionName)); |
111 | 509k | sideEffects = m_controlFlowSideEffects.at(std::get<Identifier>(_functionCall.functionName).name); |
112 | 509k | } |
113 | | |
114 | 6.73M | if (sideEffects.canTerminate) |
115 | 78.1k | markActiveAsUsed(Location::Storage); |
116 | 6.73M | if (!sideEffects.canContinue) |
117 | 136k | { |
118 | 136k | clearActive(Location::Memory); |
119 | 136k | if (!sideEffects.canTerminate) |
120 | 89.2k | clearActive(Location::Storage); |
121 | 136k | } |
122 | 6.73M | } |
123 | | |
124 | | void UnusedStoreEliminator::operator()(FunctionDefinition const& _functionDefinition) |
125 | 237k | { |
126 | 237k | ScopedSaveAndRestore storeOperations(m_storeOperations, {}); |
127 | 237k | UnusedStoreBase::operator()(_functionDefinition); |
128 | 237k | } |
129 | | |
130 | | |
131 | | void UnusedStoreEliminator::operator()(Leave const&) |
132 | 15.4k | { |
133 | 15.4k | markActiveAsUsed(); |
134 | 15.4k | } |
135 | | |
136 | | void UnusedStoreEliminator::visit(Statement const& _statement) |
137 | 26.5M | { |
138 | 26.5M | using evmasm::Instruction; |
139 | | |
140 | 26.5M | UnusedStoreBase::visit(_statement); |
141 | | |
142 | 26.5M | auto const* exprStatement = std::get_if<ExpressionStatement>(&_statement); |
143 | 26.5M | if (!exprStatement) |
144 | 23.6M | return; |
145 | | |
146 | 2.90M | FunctionCall const* funCall = std::get_if<FunctionCall>(&exprStatement->expression); |
147 | 2.90M | yulAssert(funCall); |
148 | 2.90M | std::optional<Instruction> instruction = toEVMInstruction(m_dialect, funCall->functionName); |
149 | 2.90M | if (!instruction) |
150 | 375k | return; |
151 | | |
152 | 5.21M | if (!ranges::all_of(funCall->arguments, [](Expression const& _expr) -> bool { |
153 | 5.21M | return std::get_if<Identifier>(&_expr) || std::get_if<Literal>(&_expr); |
154 | 5.21M | })) |
155 | 0 | return; |
156 | | |
157 | | // We determine if this is a store instruction without additional side-effects |
158 | | // both by querying a combination of semantic information and by listing the instructions. |
159 | | // This way the assert below should be triggered on any change. |
160 | 2.52M | using evmasm::SemanticInformation; |
161 | 2.52M | bool isStorageWrite = (*instruction == Instruction::SSTORE); |
162 | 2.52M | bool isMemoryWrite = |
163 | 2.52M | *instruction == Instruction::EXTCODECOPY || |
164 | 2.45M | *instruction == Instruction::CODECOPY || |
165 | 2.44M | *instruction == Instruction::CALLDATACOPY || |
166 | 2.41M | *instruction == Instruction::RETURNDATACOPY || |
167 | | // TODO: Removing MCOPY is complicated because it's not just a store but also a load. |
168 | | //*instruction == Instruction::MCOPY || |
169 | 2.40M | *instruction == Instruction::MSTORE || |
170 | 1.86M | *instruction == Instruction::MSTORE8; |
171 | 2.52M | bool isCandidateForRemoval = |
172 | 2.52M | *instruction != Instruction::MCOPY && |
173 | 2.51M | SemanticInformation::otherState(*instruction) != SemanticInformation::Write && ( |
174 | 2.50M | SemanticInformation::storage(*instruction) == SemanticInformation::Write || |
175 | 965k | (!m_ignoreMemory && SemanticInformation::memory(*instruction) == SemanticInformation::Write) |
176 | 2.50M | ); |
177 | 2.52M | yulAssert(isCandidateForRemoval == (isStorageWrite || (!m_ignoreMemory && isMemoryWrite))); |
178 | 2.52M | if (isCandidateForRemoval) |
179 | 2.14M | { |
180 | 2.14M | if (*instruction == Instruction::RETURNDATACOPY) |
181 | 8.69k | { |
182 | | // Out-of-bounds access to the returndata buffer results in a revert, |
183 | | // so we are careful not to remove a potentially reverting call to a builtin. |
184 | | // The only way the Solidity compiler uses `returndatacopy` is |
185 | | // `returndatacopy(X, 0, returndatasize())`, so we only allow to remove this pattern |
186 | | // (which is guaranteed to never cause an out-of-bounds revert). |
187 | 8.69k | bool allowReturndatacopyToBeRemoved = false; |
188 | 8.69k | auto startOffset = identifierNameIfSSA(funCall->arguments.at(1)); |
189 | 8.69k | auto length = identifierNameIfSSA(funCall->arguments.at(2)); |
190 | 8.69k | if (length && startOffset) |
191 | 8.46k | { |
192 | 8.46k | FunctionCall const* lengthCall = std::get_if<FunctionCall>(m_ssaValues.at(*length).value); |
193 | 8.46k | if ( |
194 | 8.46k | m_knowledgeBase.knownToBeZero(*startOffset) && |
195 | 1.73k | lengthCall && |
196 | 1.18k | toEVMInstruction(m_dialect, lengthCall->functionName) == Instruction::RETURNDATASIZE |
197 | 8.46k | ) |
198 | 829 | allowReturndatacopyToBeRemoved = true; |
199 | 8.46k | } |
200 | 8.69k | if (!allowReturndatacopyToBeRemoved) |
201 | 7.86k | return; |
202 | 8.69k | } |
203 | 2.14M | m_allStores.insert(&_statement); |
204 | 2.14M | std::vector<Operation> operations = operationsFromFunctionCall(*funCall); |
205 | 2.14M | yulAssert(operations.size() == 1, ""); |
206 | 2.14M | if (operations.front().location == Location::Storage) |
207 | 1.53M | activeStorageStores().insert(&_statement); |
208 | 603k | else |
209 | 603k | { |
210 | 603k | yulAssert(operations.front().location == Location::Memory, ""); |
211 | 603k | activeMemoryStores().insert(&_statement); |
212 | 603k | } |
213 | 2.14M | m_storeOperations[&_statement] = std::move(operations.front()); |
214 | 2.14M | } |
215 | 2.52M | } |
216 | | |
217 | | std::vector<UnusedStoreEliminator::Operation> UnusedStoreEliminator::operationsFromFunctionCall( |
218 | | FunctionCall const& _functionCall |
219 | | ) const |
220 | 8.87M | { |
221 | 8.87M | using evmasm::Instruction; |
222 | | |
223 | 8.87M | SideEffects sideEffects; |
224 | 8.87M | if (BuiltinFunction const* f = resolveBuiltinFunction(_functionCall.functionName, m_dialect)) |
225 | 8.36M | sideEffects = f->sideEffects; |
226 | 509k | else |
227 | 509k | { |
228 | 509k | yulAssert(std::holds_alternative<Identifier>(_functionCall.functionName)); |
229 | 509k | sideEffects = m_functionSideEffects.at(std::get<Identifier>(_functionCall.functionName).name); |
230 | 509k | } |
231 | | |
232 | 8.87M | std::optional<Instruction> instruction = toEVMInstruction(m_dialect, _functionCall.functionName); |
233 | 8.87M | if (!instruction) |
234 | 558k | { |
235 | 558k | std::vector<Operation> result; |
236 | | // Unknown read is worse than unknown write. |
237 | 558k | if (sideEffects.memory != SideEffects::Effect::None) |
238 | 291k | result.emplace_back(Operation{Location::Memory, Effect::Read, {}, {}}); |
239 | 558k | if (sideEffects.storage != SideEffects::Effect::None) |
240 | 232k | result.emplace_back(Operation{Location::Storage, Effect::Read, {}, {}}); |
241 | 558k | return result; |
242 | 558k | } |
243 | | |
244 | 8.31M | using evmasm::SemanticInformation; |
245 | | |
246 | 8.31M | return util::applyMap( |
247 | 8.31M | SemanticInformation::readWriteOperations(*instruction), |
248 | 8.31M | [&](SemanticInformation::Operation const& _op) -> Operation |
249 | 8.31M | { |
250 | 6.75M | yulAssert(!(_op.lengthParameter && _op.lengthConstant)); |
251 | 6.75M | yulAssert(_op.effect != Effect::None); |
252 | 6.75M | Operation ourOp{_op.location, _op.effect, {}, {}}; |
253 | 6.75M | if (_op.startParameter) |
254 | 5.73M | ourOp.start = identifierNameIfSSA(_functionCall.arguments.at(*_op.startParameter)); |
255 | 6.75M | if (_op.lengthParameter) |
256 | 701k | ourOp.length = identifierNameIfSSA(_functionCall.arguments.at(*_op.lengthParameter)); |
257 | 6.75M | if (_op.lengthConstant) |
258 | 4.82M | switch (*_op.lengthConstant) |
259 | 4.82M | { |
260 | 3.52M | case 1: ourOp.length = u256(1); break; |
261 | 1.30M | case 32: ourOp.length = u256(32); break; |
262 | 0 | default: yulAssert(false); |
263 | 4.82M | } |
264 | 6.75M | return ourOp; |
265 | 6.75M | } |
266 | 8.31M | ); |
267 | 8.87M | } |
268 | | |
269 | | void UnusedStoreEliminator::applyOperation(UnusedStoreEliminator::Operation const& _operation) |
270 | 5.14M | { |
271 | 5.14M | std::set<Statement const*>& active = |
272 | 5.14M | _operation.location == Location::Storage ? |
273 | 2.64M | activeStorageStores() : |
274 | 5.14M | activeMemoryStores(); |
275 | | |
276 | | |
277 | 13.5M | for (auto it = active.begin(); it != active.end();) |
278 | 8.44M | { |
279 | 8.44M | Statement const* statement = *it; |
280 | 8.44M | Operation const& storeOperation = m_storeOperations.at(statement); |
281 | 8.44M | if (_operation.effect == Effect::Read && !knownUnrelated(storeOperation, _operation)) |
282 | 691k | { |
283 | | // This store is read from, mark it as used and remove it from the active set. |
284 | 691k | m_usedStores.insert(statement); |
285 | 691k | it = active.erase(it); |
286 | 691k | } |
287 | 7.75M | else if (_operation.effect == Effect::Write && knownCovered(storeOperation, _operation)) |
288 | | // This store is overwritten before being read, remove it from the active set. |
289 | 1.08M | it = active.erase(it); |
290 | 6.66M | else |
291 | 6.66M | ++it; |
292 | 8.44M | } |
293 | 5.14M | } |
294 | | |
295 | | bool UnusedStoreEliminator::knownUnrelated( |
296 | | UnusedStoreEliminator::Operation const& _op1, |
297 | | UnusedStoreEliminator::Operation const& _op2 |
298 | | ) const |
299 | 1.24M | { |
300 | 1.24M | if (_op1.location != _op2.location) |
301 | 76.1k | return true; |
302 | 1.17M | if (_op1.location == Location::Storage) |
303 | 621k | { |
304 | 621k | if (_op1.start && _op2.start) |
305 | 393k | { |
306 | 393k | yulAssert( |
307 | 393k | _op1.length && |
308 | 393k | _op2.length && |
309 | 393k | lengthValue(*_op1.length) == 1 && |
310 | 393k | lengthValue(*_op2.length) == 1 |
311 | 393k | ); |
312 | 393k | return m_knowledgeBase.knownToBeDifferent(*_op1.start, *_op2.start); |
313 | 393k | } |
314 | 621k | } |
315 | 551k | else |
316 | 551k | { |
317 | 551k | yulAssert(_op1.location == Location::Memory, ""); |
318 | 551k | if ( |
319 | 551k | (_op1.length && lengthValue(*_op1.length) == 0) || |
320 | 536k | (_op2.length && lengthValue(*_op2.length) == 0) |
321 | 551k | ) |
322 | 85.3k | return true; |
323 | | |
324 | 465k | if (_op1.start && _op1.length && _op2.start) |
325 | 362k | { |
326 | 362k | std::optional<u256> length1 = lengthValue(*_op1.length); |
327 | 362k | std::optional<u256> start1 = m_knowledgeBase.valueIfKnownConstant(*_op1.start); |
328 | 362k | std::optional<u256> start2 = m_knowledgeBase.valueIfKnownConstant(*_op2.start); |
329 | 362k | if ( |
330 | 362k | (length1 && start1 && start2) && |
331 | 195k | *start1 + *length1 >= *start1 && // no overflow |
332 | 194k | *start1 + *length1 <= *start2 |
333 | 362k | ) |
334 | 75.7k | return true; |
335 | 362k | } |
336 | 390k | if (_op2.start && _op2.length && _op1.start) |
337 | 286k | { |
338 | 286k | std::optional<u256> length2 = lengthValue(*_op2.length); |
339 | 286k | std::optional<u256> start2 = m_knowledgeBase.valueIfKnownConstant(*_op2.start); |
340 | 286k | std::optional<u256> start1 = m_knowledgeBase.valueIfKnownConstant(*_op1.start); |
341 | 286k | if ( |
342 | 286k | (length2 && start2 && start1) && |
343 | 125k | *start2 + *length2 >= *start2 && // no overflow |
344 | 125k | *start2 + *length2 <= *start1 |
345 | 286k | ) |
346 | 43.5k | return true; |
347 | 286k | } |
348 | | |
349 | 346k | if (_op1.start && _op1.length && _op2.start && _op2.length) |
350 | 243k | { |
351 | 243k | std::optional<u256> length1 = lengthValue(*_op1.length); |
352 | 243k | std::optional<u256> length2 = lengthValue(*_op2.length); |
353 | 243k | if ( |
354 | 243k | (length1 && *length1 <= 32) && |
355 | 211k | (length2 && *length2 <= 32) && |
356 | 96.7k | m_knowledgeBase.knownToBeDifferentByAtLeast32(*_op1.start, *_op2.start) |
357 | 243k | ) |
358 | 4 | return true; |
359 | 243k | } |
360 | 346k | } |
361 | | |
362 | 574k | return false; |
363 | 1.17M | } |
364 | | |
365 | | bool UnusedStoreEliminator::knownCovered( |
366 | | UnusedStoreEliminator::Operation const& _covered, |
367 | | UnusedStoreEliminator::Operation const& _covering |
368 | | ) const |
369 | 7.19M | { |
370 | 7.19M | if (_covered.location != _covering.location) |
371 | 175k | return false; |
372 | 7.01M | if ( |
373 | 7.01M | (_covered.start && _covered.start == _covering.start) && |
374 | 1.07M | (_covered.length && _covered.length == _covering.length) |
375 | 7.01M | ) |
376 | 1.03M | return true; |
377 | 5.98M | if (_covered.location == Location::Memory) |
378 | 1.41M | { |
379 | 1.41M | if (_covered.length && lengthValue(*_covered.length) == 0) |
380 | 9.85k | return true; |
381 | | |
382 | | // Condition (i = cover_i_ng, e = cover_e_d): |
383 | | // i.start <= e.start && e.start + e.length <= i.start + i.length |
384 | 1.40M | if (!_covered.start || !_covering.start || !_covered.length || !_covering.length) |
385 | 61.4k | return false; |
386 | 1.34M | std::optional<u256> coveredLength = lengthValue(*_covered.length); |
387 | 1.34M | std::optional<u256> coveringLength = lengthValue(*_covering.length); |
388 | 1.34M | if (*_covered.start == *_covering.start) |
389 | 28.9k | if (coveredLength && coveringLength && *coveredLength <= *coveringLength) |
390 | 4.47k | return true; |
391 | 1.34M | std::optional<u256> coveredStart = m_knowledgeBase.valueIfKnownConstant(*_covered.start); |
392 | 1.34M | std::optional<u256> coveringStart = m_knowledgeBase.valueIfKnownConstant(*_covering.start); |
393 | 1.34M | if (coveredStart && coveringStart && coveredLength && coveringLength) |
394 | 665k | if ( |
395 | 665k | *coveringStart <= *coveredStart && |
396 | 352k | *coveringStart + *coveringLength >= *coveringStart && // no overflow |
397 | 351k | *coveredStart + *coveredLength >= *coveredStart && // no overflow |
398 | 349k | *coveredStart + *coveredLength <= *coveringStart + *coveringLength |
399 | 665k | ) |
400 | 33.2k | return true; |
401 | | |
402 | | // TODO for this we probably need a non-overflow assumption as above. |
403 | | // Condition (i = cover_i_ng, e = cover_e_d): |
404 | | // i.start <= e.start && e.start + e.length <= i.start + i.length |
405 | 1.34M | } |
406 | 5.87M | return false; |
407 | 5.98M | } |
408 | | |
409 | | void UnusedStoreEliminator::markActiveAsUsed( |
410 | | std::optional<UnusedStoreEliminator::Location> _onlyLocation |
411 | | ) |
412 | 497k | { |
413 | 497k | if (_onlyLocation == std::nullopt || _onlyLocation == Location::Memory) |
414 | 308k | for (Statement const* statement: activeMemoryStores()) |
415 | 179k | m_usedStores.insert(statement); |
416 | 497k | if (_onlyLocation == std::nullopt || _onlyLocation == Location::Storage) |
417 | 493k | for (Statement const* statement: activeStorageStores()) |
418 | 524k | m_usedStores.insert(statement); |
419 | 497k | clearActive(_onlyLocation); |
420 | 497k | } |
421 | | |
422 | | void UnusedStoreEliminator::clearActive( |
423 | | std::optional<UnusedStoreEliminator::Location> _onlyLocation |
424 | | ) |
425 | 831k | { |
426 | 831k | if (_onlyLocation == std::nullopt || _onlyLocation == Location::Memory) |
427 | 552k | activeMemoryStores() = {}; |
428 | 831k | if (_onlyLocation == std::nullopt || _onlyLocation == Location::Storage) |
429 | 583k | activeStorageStores() = {}; |
430 | 831k | } |
431 | | |
432 | | std::optional<YulName> UnusedStoreEliminator::identifierNameIfSSA(Expression const& _expression) const |
433 | 6.45M | { |
434 | 6.45M | if (Identifier const* identifier = std::get_if<Identifier>(&_expression)) |
435 | 6.42M | if (m_ssaValues.count(identifier->name)) |
436 | 6.36M | return {identifier->name}; |
437 | 87.2k | return std::nullopt; |
438 | 6.45M | } |