/src/solidity/libyul/optimiser/Suite.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 | | /** |
19 | | * Optimiser suite that combines all steps and also provides the settings for the heuristics. |
20 | | */ |
21 | | |
22 | | #include <libyul/optimiser/Suite.h> |
23 | | |
24 | | #include <libyul/optimiser/Disambiguator.h> |
25 | | #include <libyul/optimiser/VarDeclInitializer.h> |
26 | | #include <libyul/optimiser/BlockFlattener.h> |
27 | | #include <libyul/optimiser/CallGraphGenerator.h> |
28 | | #include <libyul/optimiser/CircularReferencesPruner.h> |
29 | | #include <libyul/optimiser/ControlFlowSimplifier.h> |
30 | | #include <libyul/optimiser/ConditionalSimplifier.h> |
31 | | #include <libyul/optimiser/ConditionalUnsimplifier.h> |
32 | | #include <libyul/optimiser/DeadCodeEliminator.h> |
33 | | #include <libyul/optimiser/FunctionGrouper.h> |
34 | | #include <libyul/optimiser/FunctionHoister.h> |
35 | | #include <libyul/optimiser/EqualStoreEliminator.h> |
36 | | #include <libyul/optimiser/EquivalentFunctionCombiner.h> |
37 | | #include <libyul/optimiser/ExpressionSplitter.h> |
38 | | #include <libyul/optimiser/ExpressionJoiner.h> |
39 | | #include <libyul/optimiser/ExpressionInliner.h> |
40 | | #include <libyul/optimiser/FullInliner.h> |
41 | | #include <libyul/optimiser/ForLoopConditionIntoBody.h> |
42 | | #include <libyul/optimiser/ForLoopConditionOutOfBody.h> |
43 | | #include <libyul/optimiser/ForLoopInitRewriter.h> |
44 | | #include <libyul/optimiser/ForLoopConditionIntoBody.h> |
45 | | #include <libyul/optimiser/FunctionSpecializer.h> |
46 | | #include <libyul/optimiser/ReasoningBasedSimplifier.h> |
47 | | #include <libyul/optimiser/Rematerialiser.h> |
48 | | #include <libyul/optimiser/UnusedFunctionParameterPruner.h> |
49 | | #include <libyul/optimiser/UnusedPruner.h> |
50 | | #include <libyul/optimiser/ExpressionSimplifier.h> |
51 | | #include <libyul/optimiser/CommonSubexpressionEliminator.h> |
52 | | #include <libyul/optimiser/Semantics.h> |
53 | | #include <libyul/optimiser/SSAReverser.h> |
54 | | #include <libyul/optimiser/SSATransform.h> |
55 | | #include <libyul/optimiser/StackCompressor.h> |
56 | | #include <libyul/optimiser/StackLimitEvader.h> |
57 | | #include <libyul/optimiser/StructuralSimplifier.h> |
58 | | #include <libyul/optimiser/SyntacticalEquality.h> |
59 | | #include <libyul/optimiser/UnusedAssignEliminator.h> |
60 | | #include <libyul/optimiser/UnusedStoreEliminator.h> |
61 | | #include <libyul/optimiser/VarNameCleaner.h> |
62 | | #include <libyul/optimiser/LoadResolver.h> |
63 | | #include <libyul/optimiser/LoopInvariantCodeMotion.h> |
64 | | #include <libyul/optimiser/Metrics.h> |
65 | | #include <libyul/optimiser/NameSimplifier.h> |
66 | | #include <libyul/backends/evm/ConstantOptimiser.h> |
67 | | #include <libyul/AsmAnalysis.h> |
68 | | #include <libyul/AsmAnalysisInfo.h> |
69 | | #include <libyul/AsmPrinter.h> |
70 | | #include <libyul/AST.h> |
71 | | #include <libyul/Object.h> |
72 | | |
73 | | #include <libyul/backends/wasm/WasmDialect.h> |
74 | | #include <libyul/backends/evm/NoOutputAssembly.h> |
75 | | |
76 | | #include <libsolutil/CommonData.h> |
77 | | |
78 | | #include <libyul/CompilabilityChecker.h> |
79 | | |
80 | | #include <range/v3/view/map.hpp> |
81 | | #include <range/v3/action/remove.hpp> |
82 | | |
83 | | #include <limits> |
84 | | #include <tuple> |
85 | | |
86 | | using namespace std; |
87 | | using namespace solidity; |
88 | | using namespace solidity::yul; |
89 | | |
90 | | void OptimiserSuite::run( |
91 | | Dialect const& _dialect, |
92 | | GasMeter const* _meter, |
93 | | Object& _object, |
94 | | bool _optimizeStackAllocation, |
95 | | string_view _optimisationSequence, |
96 | | optional<size_t> _expectedExecutionsPerDeployment, |
97 | | set<YulString> const& _externallyUsedIdentifiers |
98 | | ) |
99 | 12.9k | { |
100 | 12.9k | EVMDialect const* evmDialect = dynamic_cast<EVMDialect const*>(&_dialect); |
101 | 12.9k | bool usesOptimizedCodeGenerator = |
102 | 12.9k | _optimizeStackAllocation && |
103 | 12.9k | evmDialect && |
104 | 12.9k | evmDialect->evmVersion().canOverchargeGasForCall() && |
105 | 12.9k | evmDialect->providesObjectAccess(); |
106 | 12.9k | set<YulString> reservedIdentifiers = _externallyUsedIdentifiers; |
107 | 12.9k | reservedIdentifiers += _dialect.fixedFunctionNames(); |
108 | | |
109 | 12.9k | *_object.code = std::get<Block>(Disambiguator( |
110 | 12.9k | _dialect, |
111 | 12.9k | *_object.analysisInfo, |
112 | 12.9k | reservedIdentifiers |
113 | 12.9k | )(*_object.code)); |
114 | 12.9k | Block& ast = *_object.code; |
115 | | |
116 | 12.9k | NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; |
117 | 12.9k | OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; |
118 | | |
119 | 12.9k | OptimiserSuite suite(context, Debug::None); |
120 | | |
121 | | // Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and |
122 | | // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. |
123 | 12.9k | suite.runSequence("hgfo", ast); |
124 | | |
125 | 12.9k | NameSimplifier::run(suite.m_context, ast); |
126 | | // Now the user-supplied part |
127 | 12.9k | suite.runSequence(_optimisationSequence, ast); |
128 | | |
129 | | // This is a tuning parameter, but actually just prevents infinite loops. |
130 | 12.9k | size_t stackCompressorMaxIterations = 16; |
131 | 12.9k | suite.runSequence("g", ast); |
132 | | |
133 | | // We ignore the return value because we will get a much better error |
134 | | // message once we perform code generation. |
135 | 12.9k | if (!usesOptimizedCodeGenerator) |
136 | 0 | StackCompressor::run( |
137 | 0 | _dialect, |
138 | 0 | _object, |
139 | 0 | _optimizeStackAllocation, |
140 | 0 | stackCompressorMaxIterations |
141 | 0 | ); |
142 | 12.9k | suite.runSequence("fDnTOc g", ast); |
143 | | |
144 | 12.9k | if (evmDialect) |
145 | 12.9k | { |
146 | 12.9k | yulAssert(_meter, ""); |
147 | 12.9k | ConstantOptimiser{*evmDialect, *_meter}(ast); |
148 | 12.9k | if (usesOptimizedCodeGenerator) |
149 | 12.9k | { |
150 | 12.9k | StackCompressor::run( |
151 | 12.9k | _dialect, |
152 | 12.9k | _object, |
153 | 12.9k | _optimizeStackAllocation, |
154 | 12.9k | stackCompressorMaxIterations |
155 | 12.9k | ); |
156 | 12.9k | if (evmDialect->providesObjectAccess()) |
157 | 12.9k | StackLimitEvader::run(suite.m_context, _object); |
158 | 12.9k | } |
159 | 0 | else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation) |
160 | 0 | StackLimitEvader::run(suite.m_context, _object); |
161 | 12.9k | } |
162 | 0 | else if (dynamic_cast<WasmDialect const*>(&_dialect)) |
163 | 0 | { |
164 | | // If the first statement is an empty block, remove it. |
165 | | // We should only have function definitions after that. |
166 | 0 | if (ast.statements.size() > 1 && std::get<Block>(ast.statements.front()).statements.empty()) |
167 | 0 | ast.statements.erase(ast.statements.begin()); |
168 | 0 | } |
169 | | |
170 | 12.9k | dispenser.reset(ast); |
171 | 12.9k | NameSimplifier::run(suite.m_context, ast); |
172 | 12.9k | VarNameCleaner::run(suite.m_context, ast); |
173 | | |
174 | 12.9k | *_object.analysisInfo = AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object); |
175 | 12.9k | } |
176 | | |
177 | | namespace |
178 | | { |
179 | | |
180 | | |
181 | | template <class... Step> |
182 | | map<string, unique_ptr<OptimiserStep>> optimiserStepCollection() |
183 | 1 | { |
184 | 1 | map<string, unique_ptr<OptimiserStep>> ret; |
185 | 1 | for (unique_ptr<OptimiserStep>& s: util::make_vector<unique_ptr<OptimiserStep>>( |
186 | 1 | (make_unique<OptimiserStepInstance<Step>>())... |
187 | 1 | )) |
188 | 33 | { |
189 | 33 | yulAssert(!ret.count(s->name), ""); |
190 | 33 | ret[s->name] = std::move(s); |
191 | 33 | } |
192 | 1 | return ret; |
193 | 1 | } |
194 | | |
195 | | } |
196 | | |
197 | | map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps() |
198 | 5.93M | { |
199 | 5.93M | static map<string, unique_ptr<OptimiserStep>> instance; |
200 | 5.93M | if (instance.empty()) |
201 | 1 | instance = optimiserStepCollection< |
202 | 1 | BlockFlattener, |
203 | 1 | CircularReferencesPruner, |
204 | 1 | CommonSubexpressionEliminator, |
205 | 1 | ConditionalSimplifier, |
206 | 1 | ConditionalUnsimplifier, |
207 | 1 | ControlFlowSimplifier, |
208 | 1 | DeadCodeEliminator, |
209 | 1 | EqualStoreEliminator, |
210 | 1 | EquivalentFunctionCombiner, |
211 | 1 | ExpressionInliner, |
212 | 1 | ExpressionJoiner, |
213 | 1 | ExpressionSimplifier, |
214 | 1 | ExpressionSplitter, |
215 | 1 | ForLoopConditionIntoBody, |
216 | 1 | ForLoopConditionOutOfBody, |
217 | 1 | ForLoopInitRewriter, |
218 | 1 | FullInliner, |
219 | 1 | FunctionGrouper, |
220 | 1 | FunctionHoister, |
221 | 1 | FunctionSpecializer, |
222 | 1 | LiteralRematerialiser, |
223 | 1 | LoadResolver, |
224 | 1 | LoopInvariantCodeMotion, |
225 | 1 | UnusedAssignEliminator, |
226 | 1 | UnusedStoreEliminator, |
227 | 1 | ReasoningBasedSimplifier, |
228 | 1 | Rematerialiser, |
229 | 1 | SSAReverser, |
230 | 1 | SSATransform, |
231 | 1 | StructuralSimplifier, |
232 | 1 | UnusedFunctionParameterPruner, |
233 | 1 | UnusedPruner, |
234 | 1 | VarDeclInitializer |
235 | 1 | >(); |
236 | | // Does not include VarNameCleaner because it destroys the property of unique names. |
237 | | // Does not include NameSimplifier. |
238 | 5.93M | return instance; |
239 | 5.93M | } |
240 | | |
241 | | map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap() |
242 | 1 | { |
243 | 1 | static map<string, char> lookupTable{ |
244 | 1 | {BlockFlattener::name, 'f'}, |
245 | 1 | {CircularReferencesPruner::name, 'l'}, |
246 | 1 | {CommonSubexpressionEliminator::name, 'c'}, |
247 | 1 | {ConditionalSimplifier::name, 'C'}, |
248 | 1 | {ConditionalUnsimplifier::name, 'U'}, |
249 | 1 | {ControlFlowSimplifier::name, 'n'}, |
250 | 1 | {DeadCodeEliminator::name, 'D'}, |
251 | 1 | {EqualStoreEliminator::name, 'E'}, |
252 | 1 | {EquivalentFunctionCombiner::name, 'v'}, |
253 | 1 | {ExpressionInliner::name, 'e'}, |
254 | 1 | {ExpressionJoiner::name, 'j'}, |
255 | 1 | {ExpressionSimplifier::name, 's'}, |
256 | 1 | {ExpressionSplitter::name, 'x'}, |
257 | 1 | {ForLoopConditionIntoBody::name, 'I'}, |
258 | 1 | {ForLoopConditionOutOfBody::name, 'O'}, |
259 | 1 | {ForLoopInitRewriter::name, 'o'}, |
260 | 1 | {FullInliner::name, 'i'}, |
261 | 1 | {FunctionGrouper::name, 'g'}, |
262 | 1 | {FunctionHoister::name, 'h'}, |
263 | 1 | {FunctionSpecializer::name, 'F'}, |
264 | 1 | {LiteralRematerialiser::name, 'T'}, |
265 | 1 | {LoadResolver::name, 'L'}, |
266 | 1 | {LoopInvariantCodeMotion::name, 'M'}, |
267 | 1 | {ReasoningBasedSimplifier::name, 'R'}, |
268 | 1 | {UnusedAssignEliminator::name, 'r'}, |
269 | 1 | {UnusedStoreEliminator::name, 'S'}, |
270 | 1 | {Rematerialiser::name, 'm'}, |
271 | 1 | {SSAReverser::name, 'V'}, |
272 | 1 | {SSATransform::name, 'a'}, |
273 | 1 | {StructuralSimplifier::name, 't'}, |
274 | 1 | {UnusedFunctionParameterPruner::name, 'p'}, |
275 | 1 | {UnusedPruner::name, 'u'}, |
276 | 1 | {VarDeclInitializer::name, 'd'}, |
277 | 1 | }; |
278 | 1 | yulAssert(lookupTable.size() == allSteps().size(), ""); |
279 | 1 | yulAssert(( |
280 | 1 | util::convertContainer<set<char>>(string(NonStepAbbreviations)) - |
281 | 1 | util::convertContainer<set<char>>(lookupTable | ranges::views::values) |
282 | 1 | ).size() == string(NonStepAbbreviations).size(), |
283 | 1 | "Step abbreviation conflicts with a character reserved for another syntactic element" |
284 | 1 | ); |
285 | | |
286 | 1 | return lookupTable; |
287 | 1 | } |
288 | | |
289 | | map<char, string> const& OptimiserSuite::stepAbbreviationToNameMap() |
290 | 11.5M | { |
291 | 11.5M | static map<char, string> lookupTable = util::invertMap(stepNameToAbbreviationMap()); |
292 | | |
293 | 11.5M | return lookupTable; |
294 | 11.5M | } |
295 | | |
296 | | void OptimiserSuite::validateSequence(string_view _stepAbbreviations) |
297 | 262k | { |
298 | 262k | int8_t nestingLevel = 0; |
299 | 262k | for (char abbreviation: _stepAbbreviations) |
300 | 3.24M | switch (abbreviation) |
301 | 3.24M | { |
302 | 64.8k | case ' ': |
303 | 64.8k | case '\n': |
304 | 64.8k | break; |
305 | 181k | case '[': |
306 | 181k | assertThrow(nestingLevel < numeric_limits<int8_t>::max(), OptimizerException, "Brackets nested too deep"); |
307 | 181k | nestingLevel++; |
308 | 181k | break; |
309 | 181k | case ']': |
310 | 181k | nestingLevel--; |
311 | 181k | assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets"); |
312 | 181k | break; |
313 | 2.81M | default: |
314 | 2.81M | { |
315 | 2.81M | yulAssert( |
316 | 2.81M | string(NonStepAbbreviations).find(abbreviation) == string::npos, |
317 | 2.81M | "Unhandled syntactic element in the abbreviation sequence" |
318 | 2.81M | ); |
319 | 2.81M | assertThrow( |
320 | 2.81M | stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(), |
321 | 2.81M | OptimizerException, |
322 | 2.81M | "'"s + abbreviation + "' is not a valid step abbreviation" |
323 | 2.81M | ); |
324 | 2.81M | optional<string> invalid = allSteps().at(stepAbbreviationToNameMap().at(abbreviation))->invalidInCurrentEnvironment(); |
325 | 2.81M | assertThrow( |
326 | 2.81M | !invalid.has_value(), |
327 | 2.81M | OptimizerException, |
328 | 2.81M | "'"s + abbreviation + "' is invalid in the current environment: " + *invalid |
329 | 2.81M | ); |
330 | 2.81M | } |
331 | 3.24M | } |
332 | 262k | assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets"); |
333 | 262k | } |
334 | | |
335 | | void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable) |
336 | 262k | { |
337 | 262k | validateSequence(_stepAbbreviations); |
338 | | |
339 | | // This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'. |
340 | 262k | auto extractNonNestedPrefix = [](string_view _tail) -> tuple<string_view, string_view> |
341 | 366k | { |
342 | 2.12M | for (size_t i = 0; i < _tail.size(); ++i) |
343 | 1.86M | { |
344 | 1.86M | yulAssert(_tail[i] != ']'); |
345 | 1.86M | if (_tail[i] == '[') |
346 | 103k | return {_tail.substr(0, i), _tail.substr(i)}; |
347 | 1.86M | } |
348 | 262k | return {_tail, {}}; |
349 | 366k | }; |
350 | | |
351 | | // This splits '[bbb]ccc...' into 'bbb' and 'ccc...'. |
352 | 262k | auto extractBracketContent = [](string_view _tail) -> tuple<string_view, string_view> |
353 | 262k | { |
354 | 103k | yulAssert(!_tail.empty() && _tail[0] == '['); |
355 | | |
356 | 103k | size_t contentLength = 0; |
357 | 103k | int8_t nestingLevel = 1; |
358 | 103k | for (char abbreviation: _tail.substr(1)) |
359 | 1.37M | { |
360 | 1.37M | if (abbreviation == '[') |
361 | 77.7k | { |
362 | 77.7k | yulAssert(nestingLevel < numeric_limits<int8_t>::max()); |
363 | 77.7k | ++nestingLevel; |
364 | 77.7k | } |
365 | 1.29M | else if (abbreviation == ']') |
366 | 181k | { |
367 | 181k | --nestingLevel; |
368 | 181k | if (nestingLevel == 0) |
369 | 103k | break; |
370 | 181k | } |
371 | 1.27M | ++contentLength; |
372 | 1.27M | } |
373 | 103k | yulAssert(nestingLevel == 0); |
374 | 103k | yulAssert(_tail[contentLength + 1] == ']'); |
375 | | |
376 | 103k | return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)}; |
377 | 103k | }; |
378 | | |
379 | 262k | auto abbreviationsToSteps = [](string_view _sequence) -> vector<string> |
380 | 659k | { |
381 | 659k | vector<string> steps; |
382 | 659k | for (char abbreviation: _sequence) |
383 | 3.19M | if (abbreviation != ' ' && abbreviation != '\n') |
384 | 3.12M | steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation)); |
385 | 659k | return steps; |
386 | 659k | }; |
387 | | |
388 | 262k | vector<tuple<string_view, bool>> subsequences; |
389 | 262k | string_view tail = _stepAbbreviations; |
390 | 366k | while (!tail.empty()) |
391 | 366k | { |
392 | 366k | string_view subsequence; |
393 | 366k | tie(subsequence, tail) = extractNonNestedPrefix(tail); |
394 | 366k | if (subsequence.size() > 0) |
395 | 366k | subsequences.push_back({subsequence, false}); |
396 | | |
397 | 366k | if (tail.empty()) |
398 | 262k | break; |
399 | | |
400 | 103k | tie(subsequence, tail) = extractBracketContent(tail); |
401 | 103k | if (subsequence.size() > 0) |
402 | 103k | subsequences.push_back({subsequence, true}); |
403 | 103k | } |
404 | | |
405 | 262k | size_t codeSize = 0; |
406 | 448k | for (size_t round = 0; round < MaxRounds; ++round) |
407 | 448k | { |
408 | 448k | for (auto const& [subsequence, repeat]: subsequences) |
409 | 869k | { |
410 | 869k | if (repeat) |
411 | 210k | runSequence(subsequence, _ast, true); |
412 | 659k | else |
413 | 659k | runSequence(abbreviationsToSteps(subsequence), _ast); |
414 | 869k | } |
415 | | |
416 | 448k | if (!_repeatUntilStable) |
417 | 51.8k | break; |
418 | | |
419 | 396k | size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast); |
420 | 396k | if (newSize == codeSize) |
421 | 210k | break; |
422 | 186k | codeSize = newSize; |
423 | 186k | } |
424 | 262k | } |
425 | | |
426 | | void OptimiserSuite::runSequence(std::vector<string> const& _steps, Block& _ast) |
427 | 659k | { |
428 | 659k | unique_ptr<Block> copy; |
429 | 659k | if (m_debug == Debug::PrintChanges) |
430 | 0 | copy = make_unique<Block>(std::get<Block>(ASTCopier{}(_ast))); |
431 | 659k | for (string const& step: _steps) |
432 | 3.12M | { |
433 | 3.12M | if (m_debug == Debug::PrintStep) |
434 | 0 | cout << "Running " << step << endl; |
435 | 3.12M | allSteps().at(step)->run(m_context, _ast); |
436 | 3.12M | if (m_debug == Debug::PrintChanges) |
437 | 0 | { |
438 | | // TODO should add switch to also compare variable names! |
439 | 0 | if (SyntacticallyEqual{}.statementEqual(_ast, *copy)) |
440 | 0 | cout << "== Running " << step << " did not cause changes." << endl; |
441 | 0 | else |
442 | 0 | { |
443 | 0 | cout << "== Running " << step << " changed the AST." << endl; |
444 | 0 | cout << AsmPrinter{}(_ast) << endl; |
445 | 0 | copy = make_unique<Block>(std::get<Block>(ASTCopier{}(_ast))); |
446 | 0 | } |
447 | 0 | } |
448 | 3.12M | } |
449 | 659k | } |