/* 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 #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace solidity::util; using namespace solidity::yul; using namespace solidity::langutil; using namespace solidity::yul::unusedFunctionsCommon; namespace { /** * A helper class that removes expressions of the form `pop(a)` where a is an identifier. Generally, * pop can be the discard function. */ class PopRemover: public ASTModifier { public: using ASTModifier::operator(); void operator()(Block& _block) override; static void removePop(Block& _ast, Dialect const& _dialect); private: explicit PopRemover(Dialect const& _dialect): m_dialect(_dialect) {} Dialect const& m_dialect; }; void PopRemover::operator()(Block& _block) { iterateReplacing(_block.statements, [&](Statement& _s) -> optional> { if (holds_alternative(_s)) if ( auto& expressionStatement = get(_s); holds_alternative(expressionStatement.expression) ) { FunctionCall& f = get(expressionStatement.expression); if (f.functionName.name == m_dialect.discardFunction(m_dialect.boolType)->name) if (holds_alternative(f.arguments[0])) return vector{}; } return nullopt; }); } void PopRemover::removePop(Block& _ast, Dialect const& _dialect) { PopRemover p{_dialect}; p(_ast); } } /** * Step 1 of UnusedFunctionReturnParameterPruner: Find functions whose return parameters are not * used in the code, i.e., * * Look at all VariableDeclaration `let x_1, ..., x_i, ..., x_n := f(y_1, ..., y_m)` and check if * `x_i` is unused. If all function calls to `f` has its i-th return parameter unused, we will mark * its i-th index so that it can be removed in later steps. */ class FindFunctionsWithUnusedReturns: public ASTWalker { public: using ASTWalker::operator(); void operator()(VariableDeclaration const& _v) override; void operator()(FunctionCall const& _v) override; /// Returns a map with function name as key and vector of bools such that if the index `i` of the /// vector is false, then the function's `i`-th return parameter is unused at *all* callsites. static map> functions( Dialect const& _dialect, map _references, Block& _block ); private: explicit FindFunctionsWithUnusedReturns( Dialect const& _dialect, map const& _references ): m_references(_references), m_dialect(_dialect) { } /// Function name and a boolean mask, where `false` at index `i` indicates that the function /// return-parameter at index `i` in `FunctionDefinition::parameter` is unused at every function /// call-site. map> m_unusedReturnParameter; /// A count of all references to all Declarations. map const& m_references; /// Functions whose return parameters are all used. set m_usedFunctions; Dialect const& m_dialect; }; // Find functions whose return parameters are unused. Assuming that the code is in SSA form and that // ForLoopConditionIntoBody, ExpressionSplitter were run, all FunctionCalls with at least one return // value will have the form `let x_1, ..., x_n := f(y_1, ..., y_m)` void FindFunctionsWithUnusedReturns::operator()(VariableDeclaration const& _v) { if (holds_alternative(*_v.value)) { FunctionCall const& function = get(*_v.value); YulString const& name = function.functionName.name; // We avoid visiting `operator()(FunctionCall const&)` on purpose. walkVector(function.arguments); if (m_dialect.builtin(name)) return; if (!m_unusedReturnParameter.count(name)) m_unusedReturnParameter[name].resize(_v.variables.size(), false); for (size_t i = 0; i < _v.variables.size(); ++i) if (m_references.count(_v.variables[i].name)) m_unusedReturnParameter.at(name)[i] = true; } } void FindFunctionsWithUnusedReturns::operator()(FunctionCall const& _f) { // Any function that can reach here is added to the "do-not-prune" list. Ideally, with the // correct pre-requisites, no function (with at least one return parameter) will visit here. // However, we have to do this to guarantee correctness for 'all' optimization sequences. m_usedFunctions.insert(_f.functionName.name); walkVector(_f.arguments); } map> FindFunctionsWithUnusedReturns::functions( Dialect const& _dialect, map _references, Block& _block ) { FindFunctionsWithUnusedReturns find{_dialect, _references}; find(_block); map> functions; for (auto const& [name, mask]: find.m_unusedReturnParameter) if ( !find.m_usedFunctions.count(name) && any_of(mask.begin(), mask.end(), [](bool _b) { return !_b; }) ) functions[name] = mask; return functions; } void UnusedFunctionReturnParameterPruner::run(OptimiserStepContext& _context, Block& _ast) { PopRemover::removePop(_ast, _context.dialect); map references = ReferencesCounter::countReferences(_ast); map> functions = FindFunctionsWithUnusedReturns::functions(_context.dialect, references, _ast); set simpleFunctions; for (auto const& s: _ast.statements) if (holds_alternative(s)) { FunctionDefinition const& f = get(s); if (tooSimpleToBePruned(f)) simpleFunctions.insert(f.name); } set functionNamesToFree = util::keys(functions) - simpleFunctions; // Step 2 of UnusedFunctionReturnParameterPruner: Renames the function and replaces all references to // the function, say `f`, by its new name, say `f_1`. NameDisplacer replace{_context.dispenser, functionNamesToFree}; replace(_ast); // Inverse-Map of the above translations. In the above example, this will store an element with // key `f_1` and value `f`. std::map newToOriginalNames = invertMap(replace.translations()); // Step 3 of UnusedFunctionReturnParameterPruner: introduce a new function in the block with body of // the old one. Replace the body of the old one with a function call to the new one with reduced // parameters. // // For example: introduce a new 'linking' function `f` with the same the body as `f_1`, but with // reduced return parameters, e.g., if `y` is unused at all callsites in the following // definition: `function f() -> y { y := 1 }`. We create a linking function `f_1` whose body // calls to `f`, i.e., `f_1(x) -> y { y := f() }`. // // Note that all parameter names of the linking function has to be renamed to avoid conflict. iterateReplacing(_ast.statements, [&](Statement& _s) -> optional> { if (holds_alternative(_s)) { // The original function except that it has a new name (e.g., `f_1`) FunctionDefinition& originalFunction = get(_s); if (newToOriginalNames.count(originalFunction.name)) { YulString linkingFunctionName = originalFunction.name; YulString originalFunctionName = newToOriginalNames.at(linkingFunctionName); pair, vector> used = { vector(originalFunction.parameters.size(), true), functions.at(originalFunctionName) }; FunctionDefinition linkingFunction = createLinkingFunction( originalFunction, used, originalFunctionName, linkingFunctionName, _context.dispenser ); originalFunction.name = originalFunctionName; auto missingVariables = filter( originalFunction.returnVariables, applyMap(used.second, [](bool _b) {return !_b;}) ); originalFunction.returnVariables = filter(originalFunction.returnVariables, used.second); // Return variables that are pruned can still be used inside the function body. // Therefore, a extra `let missingVariables` needs to be added at the beginning of // the function body. vector missingVariableDeclarations = applyMap(missingVariables, [&](auto& _v) -> Statement { return VariableDeclaration{ originalFunction.location, vector{_v}, nullptr }; }); originalFunction.body.statements = move(missingVariableDeclarations) + move(originalFunction.body.statements); return make_vector(move(originalFunction), move(linkingFunction)); } } return nullopt; }); }