/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ // SPDX-License-Identifier: GPL-3.0 /** * Optimiser suite that combines all steps and also provides the settings for the heuristics. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef PROFILE_OPTIMIZER_STEPS #include #include #endif using namespace std; using namespace solidity; using namespace solidity::yul; #ifdef PROFILE_OPTIMIZER_STEPS using namespace std::chrono; #endif namespace { #ifdef PROFILE_OPTIMIZER_STEPS void outputPerformanceMetrics(map const& _metrics) { vector> durations(_metrics.begin(), _metrics.end()); sort( durations.begin(), durations.end(), [](pair const& _lhs, pair const& _rhs) -> bool { return _lhs.second < _rhs.second; } ); int64_t totalDurationInMicroseconds = 0; for (auto&& [step, durationInMicroseconds]: durations) totalDurationInMicroseconds += durationInMicroseconds; cerr << "Performance metrics of optimizer steps" << endl; cerr << "======================================" << endl; constexpr double microsecondsInSecond = 1000000; for (auto&& [step, durationInMicroseconds]: durations) { double percentage = 100.0 * static_cast(durationInMicroseconds) / static_cast(totalDurationInMicroseconds); double sec = static_cast(durationInMicroseconds) / microsecondsInSecond; cerr << fmt::format("{:>7.3f}% ({} s): {}", percentage, sec, step) << endl; } double totalDurationInSeconds = static_cast(totalDurationInMicroseconds) / microsecondsInSecond; cerr << "--------------------------------------" << endl; cerr << fmt::format("{:>7}% ({:.3f} s)", 100, totalDurationInSeconds) << endl; } #endif } void OptimiserSuite::run( Dialect const& _dialect, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, string_view _optimisationSequence, string_view _optimisationCleanupSequence, optional _expectedExecutionsPerDeployment, set const& _externallyUsedIdentifiers ) { EVMDialect const* evmDialect = dynamic_cast(&_dialect); bool usesOptimizedCodeGenerator = _optimizeStackAllocation && evmDialect && evmDialect->evmVersion().canOverchargeGasForCall() && evmDialect->providesObjectAccess(); set reservedIdentifiers = _externallyUsedIdentifiers; reservedIdentifiers += _dialect.fixedFunctionNames(); *_object.code = std::get(Disambiguator( _dialect, *_object.analysisInfo, reservedIdentifiers )(*_object.code)); Block& ast = *_object.code; NameDispenser dispenser{_dialect, ast, reservedIdentifiers}; OptimiserStepContext context{_dialect, dispenser, reservedIdentifiers, _expectedExecutionsPerDeployment}; OptimiserSuite suite(context, Debug::None); // Some steps depend on properties ensured by FunctionHoister, BlockFlattener, FunctionGrouper and // ForLoopInitRewriter. Run them first to be able to run arbitrary sequences safely. suite.runSequence("hgfo", ast); NameSimplifier::run(suite.m_context, ast); // Now the user-supplied part suite.runSequence(_optimisationSequence, ast); // This is a tuning parameter, but actually just prevents infinite loops. size_t stackCompressorMaxIterations = 16; suite.runSequence("g", ast); // We ignore the return value because we will get a much better error // message once we perform code generation. if (!usesOptimizedCodeGenerator) StackCompressor::run( _dialect, _object, _optimizeStackAllocation, stackCompressorMaxIterations ); // Run the user-supplied clean up sequence suite.runSequence(_optimisationCleanupSequence, ast); // Hard-coded FunctionGrouper step is used to bring the AST into a canonical form required by the StackCompressor // and StackLimitEvader. This is hard-coded as the last step, as some previously executed steps may break the // aforementioned form, thus causing the StackCompressor/StackLimitEvader to throw. suite.runSequence("g", ast); if (evmDialect) { yulAssert(_meter, ""); ConstantOptimiser{*evmDialect, *_meter}(ast); if (usesOptimizedCodeGenerator) { StackCompressor::run( _dialect, _object, _optimizeStackAllocation, stackCompressorMaxIterations ); if (evmDialect->providesObjectAccess()) StackLimitEvader::run(suite.m_context, _object); } else if (evmDialect->providesObjectAccess() && _optimizeStackAllocation) StackLimitEvader::run(suite.m_context, _object); } dispenser.reset(ast); NameSimplifier::run(suite.m_context, ast); VarNameCleaner::run(suite.m_context, ast); #ifdef PROFILE_OPTIMIZER_STEPS outputPerformanceMetrics(suite.m_durationPerStepInMicroseconds); #endif *_object.analysisInfo = AsmAnalyzer::analyzeStrictAssertCorrect(_dialect, _object); } namespace { template map> optimiserStepCollection() { map> ret; for (unique_ptr& s: util::make_vector>( (make_unique>())... )) { yulAssert(!ret.count(s->name), ""); ret[s->name] = std::move(s); } return ret; } } map> const& OptimiserSuite::allSteps() { static map> instance; if (instance.empty()) instance = optimiserStepCollection< BlockFlattener, CircularReferencesPruner, CommonSubexpressionEliminator, ConditionalSimplifier, ConditionalUnsimplifier, ControlFlowSimplifier, DeadCodeEliminator, EqualStoreEliminator, EquivalentFunctionCombiner, ExpressionInliner, ExpressionJoiner, ExpressionSimplifier, ExpressionSplitter, ForLoopConditionIntoBody, ForLoopConditionOutOfBody, ForLoopInitRewriter, FullInliner, FunctionGrouper, FunctionHoister, FunctionSpecializer, LiteralRematerialiser, LoadResolver, LoopInvariantCodeMotion, UnusedAssignEliminator, UnusedStoreEliminator, Rematerialiser, SSAReverser, SSATransform, StructuralSimplifier, UnusedFunctionParameterPruner, UnusedPruner, VarDeclInitializer >(); // Does not include VarNameCleaner because it destroys the property of unique names. // Does not include NameSimplifier. return instance; } map const& OptimiserSuite::stepNameToAbbreviationMap() { static map lookupTable{ {BlockFlattener::name, 'f'}, {CircularReferencesPruner::name, 'l'}, {CommonSubexpressionEliminator::name, 'c'}, {ConditionalSimplifier::name, 'C'}, {ConditionalUnsimplifier::name, 'U'}, {ControlFlowSimplifier::name, 'n'}, {DeadCodeEliminator::name, 'D'}, {EqualStoreEliminator::name, 'E'}, {EquivalentFunctionCombiner::name, 'v'}, {ExpressionInliner::name, 'e'}, {ExpressionJoiner::name, 'j'}, {ExpressionSimplifier::name, 's'}, {ExpressionSplitter::name, 'x'}, {ForLoopConditionIntoBody::name, 'I'}, {ForLoopConditionOutOfBody::name, 'O'}, {ForLoopInitRewriter::name, 'o'}, {FullInliner::name, 'i'}, {FunctionGrouper::name, 'g'}, {FunctionHoister::name, 'h'}, {FunctionSpecializer::name, 'F'}, {LiteralRematerialiser::name, 'T'}, {LoadResolver::name, 'L'}, {LoopInvariantCodeMotion::name, 'M'}, {UnusedAssignEliminator::name, 'r'}, {UnusedStoreEliminator::name, 'S'}, {Rematerialiser::name, 'm'}, {SSAReverser::name, 'V'}, {SSATransform::name, 'a'}, {StructuralSimplifier::name, 't'}, {UnusedFunctionParameterPruner::name, 'p'}, {UnusedPruner::name, 'u'}, {VarDeclInitializer::name, 'd'}, }; yulAssert(lookupTable.size() == allSteps().size(), ""); yulAssert(( util::convertContainer>(string(NonStepAbbreviations)) - util::convertContainer>(lookupTable | ranges::views::values) ).size() == string(NonStepAbbreviations).size(), "Step abbreviation conflicts with a character reserved for another syntactic element" ); return lookupTable; } map const& OptimiserSuite::stepAbbreviationToNameMap() { static map lookupTable = util::invertMap(stepNameToAbbreviationMap()); return lookupTable; } void OptimiserSuite::validateSequence(string_view _stepAbbreviations) { int8_t nestingLevel = 0; int8_t colonDelimiters = 0; for (char abbreviation: _stepAbbreviations) switch (abbreviation) { case ' ': case '\n': break; case '[': assertThrow(nestingLevel < numeric_limits::max(), OptimizerException, "Brackets nested too deep"); nestingLevel++; break; case ']': nestingLevel--; assertThrow(nestingLevel >= 0, OptimizerException, "Unbalanced brackets"); break; case ':': ++colonDelimiters; assertThrow(nestingLevel == 0, OptimizerException, "Cleanup sequence delimiter cannot be placed inside the brackets"); assertThrow(colonDelimiters <= 1, OptimizerException, "Too many cleanup sequence delimiters"); break; default: { yulAssert( string(NonStepAbbreviations).find(abbreviation) == string::npos, "Unhandled syntactic element in the abbreviation sequence" ); assertThrow( stepAbbreviationToNameMap().find(abbreviation) != stepAbbreviationToNameMap().end(), OptimizerException, "'"s + abbreviation + "' is not a valid step abbreviation" ); optional invalid = allSteps().at(stepAbbreviationToNameMap().at(abbreviation))->invalidInCurrentEnvironment(); assertThrow( !invalid.has_value(), OptimizerException, "'"s + abbreviation + "' is invalid in the current environment: " + *invalid ); } } assertThrow(nestingLevel == 0, OptimizerException, "Unbalanced brackets"); } void OptimiserSuite::runSequence(string_view _stepAbbreviations, Block& _ast, bool _repeatUntilStable) { validateSequence(_stepAbbreviations); // This splits 'aaa[bbb]ccc...' into 'aaa' and '[bbb]ccc...'. auto extractNonNestedPrefix = [](string_view _tail) -> tuple { for (size_t i = 0; i < _tail.size(); ++i) { yulAssert(_tail[i] != ']'); if (_tail[i] == '[') return {_tail.substr(0, i), _tail.substr(i)}; } return {_tail, {}}; }; // This splits '[bbb]ccc...' into 'bbb' and 'ccc...'. auto extractBracketContent = [](string_view _tail) -> tuple { yulAssert(!_tail.empty() && _tail[0] == '['); size_t contentLength = 0; int8_t nestingLevel = 1; for (char abbreviation: _tail.substr(1)) { if (abbreviation == '[') { yulAssert(nestingLevel < numeric_limits::max()); ++nestingLevel; } else if (abbreviation == ']') { --nestingLevel; if (nestingLevel == 0) break; } ++contentLength; } yulAssert(nestingLevel == 0); yulAssert(_tail[contentLength + 1] == ']'); return {_tail.substr(1, contentLength), _tail.substr(contentLength + 2)}; }; auto abbreviationsToSteps = [](string_view _sequence) -> vector { vector steps; for (char abbreviation: _sequence) if (abbreviation != ' ' && abbreviation != '\n') steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation)); return steps; }; vector> subsequences; string_view tail = _stepAbbreviations; while (!tail.empty()) { string_view subsequence; tie(subsequence, tail) = extractNonNestedPrefix(tail); if (subsequence.size() > 0) subsequences.push_back({subsequence, false}); if (tail.empty()) break; tie(subsequence, tail) = extractBracketContent(tail); if (subsequence.size() > 0) subsequences.push_back({subsequence, true}); } size_t codeSize = 0; for (size_t round = 0; round < MaxRounds; ++round) { for (auto const& [subsequence, repeat]: subsequences) { if (repeat) runSequence(subsequence, _ast, true); else runSequence(abbreviationsToSteps(subsequence), _ast); } if (!_repeatUntilStable) break; size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast); if (newSize == codeSize) break; codeSize = newSize; } } void OptimiserSuite::runSequence(std::vector const& _steps, Block& _ast) { unique_ptr copy; if (m_debug == Debug::PrintChanges) copy = make_unique(std::get(ASTCopier{}(_ast))); for (string const& step: _steps) { if (m_debug == Debug::PrintStep) cout << "Running " << step << endl; #ifdef PROFILE_OPTIMIZER_STEPS steady_clock::time_point startTime = steady_clock::now(); #endif allSteps().at(step)->run(m_context, _ast); #ifdef PROFILE_OPTIMIZER_STEPS steady_clock::time_point endTime = steady_clock::now(); m_durationPerStepInMicroseconds[step] += duration_cast(endTime - startTime).count(); #endif if (m_debug == Debug::PrintChanges) { // TODO should add switch to also compare variable names! if (SyntacticallyEqual{}.statementEqual(_ast, *copy)) cout << "== Running " << step << " did not cause changes." << endl; else { cout << "== Running " << step << " changed the AST." << endl; cout << AsmPrinter{}(_ast) << endl; copy = make_unique(std::get(ASTCopier{}(_ast))); } } } }