/* 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 using namespace std; using namespace solidity; using namespace solidity::yul; void OptimiserSuite::run( Dialect const& _dialect, GasMeter const* _meter, Object& _object, bool _optimizeStackAllocation, string const& _optimisationSequence, optional _expectedExecutionsPerDeployment, set const& _externallyUsedIdentifiers ) { set reservedIdentifiers = _externallyUsedIdentifiers; reservedIdentifiers += _dialect.fixedFunctionNames(); *_object.code = std::get(Disambiguator( _dialect, *_object.analysisInfo, reservedIdentifiers )(*_object.code)); Block& ast = *_object.code; OptimiserSuite suite(_dialect, reservedIdentifiers, Debug::None, ast, _expectedExecutionsPerDeployment); // 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("hfgo", 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. StackCompressor::run( _dialect, _object, _optimizeStackAllocation, stackCompressorMaxIterations ); suite.runSequence("fDnTOc g", ast); if (EVMDialect const* dialect = dynamic_cast(&_dialect)) { yulAssert(_meter, ""); ConstantOptimiser{*dialect, *_meter}(ast); if (dialect->providesObjectAccess() && _optimizeStackAllocation) StackLimitEvader::run(suite.m_context, _object, CompilabilityChecker{ _dialect, _object, _optimizeStackAllocation }.unreachableVariables); } else if (dynamic_cast(&_dialect)) { // If the first statement is an empty block, remove it. // We should only have function definitions after that. if (ast.statements.size() > 1 && std::get(ast.statements.front()).statements.empty()) ast.statements.erase(ast.statements.begin()); } suite.m_dispenser.reset(ast); NameSimplifier::run(suite.m_context, ast); VarNameCleaner::run(suite.m_context, ast); *_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, EquivalentFunctionCombiner, ExpressionInliner, ExpressionJoiner, ExpressionSimplifier, ExpressionSplitter, ForLoopConditionIntoBody, ForLoopConditionOutOfBody, ForLoopInitRewriter, FullInliner, FunctionGrouper, FunctionHoister, FunctionSpecializer, LiteralRematerialiser, LoadResolver, LoopInvariantCodeMotion, RedundantAssignEliminator, ReasoningBasedSimplifier, 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'}, {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'}, {ReasoningBasedSimplifier::name, 'R'}, {RedundantAssignEliminator::name, 'r'}, {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 const& _stepAbbreviations) { bool insideLoop = false; for (char abbreviation: _stepAbbreviations) switch (abbreviation) { case ' ': case '\n': break; case '[': assertThrow(!insideLoop, OptimizerException, "Nested brackets are not supported"); insideLoop = true; break; case ']': assertThrow(insideLoop, OptimizerException, "Unbalanced brackets"); insideLoop = false; 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(!insideLoop, OptimizerException, "Unbalanced brackets"); } void OptimiserSuite::runSequence(string const& _stepAbbreviations, Block& _ast) { validateSequence(_stepAbbreviations); string input = _stepAbbreviations; ranges::actions::remove(input, ' '); ranges::actions::remove(input, '\n'); auto abbreviationsToSteps = [](string const& _sequence) -> vector { vector steps; for (char abbreviation: _sequence) steps.emplace_back(stepAbbreviationToNameMap().at(abbreviation)); return steps; }; // The sequence has now been validated and must consist of pairs of segments that look like this: `aaa[bbb]` // `aaa` or `[bbb]` can be empty. For example we consider a sequence like `fgo[aaf]Oo` to have // four segments, the last of which is an empty bracket. size_t currentPairStart = 0; while (currentPairStart < input.size()) { size_t openingBracket = input.find('[', currentPairStart); size_t closingBracket = input.find(']', openingBracket); size_t firstCharInside = (openingBracket == string::npos ? input.size() : openingBracket + 1); yulAssert((openingBracket == string::npos) == (closingBracket == string::npos), ""); runSequence(abbreviationsToSteps(input.substr(currentPairStart, openingBracket - currentPairStart)), _ast); runSequenceUntilStable(abbreviationsToSteps(input.substr(firstCharInside, closingBracket - firstCharInside)), _ast); currentPairStart = (closingBracket == string::npos ? input.size() : closingBracket + 1); } } 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; allSteps().at(step)->run(m_context, _ast); 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))); } } } } void OptimiserSuite::runSequenceUntilStable( std::vector const& _steps, Block& _ast, size_t maxRounds ) { if (_steps.empty()) return; size_t codeSize = 0; for (size_t rounds = 0; rounds < maxRounds; ++rounds) { size_t newSize = CodeSize::codeSizeIncludingFunctions(_ast); if (newSize == codeSize) break; codeSize = newSize; runSequence(_steps, _ast); } }