mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Implemented UnusedFunctionReturnParameterPruner
Optimization rule that rewrites functions whose return parameters are not used at callsite.
This commit is contained in:
parent
61069ec77d
commit
4553b8c0d5
@ -45,6 +45,7 @@ struct OptimiserSettings
|
||||
"xarulrul" // Prune a bit more in SSA
|
||||
"xarrcL" // Turn into SSA again and simplify
|
||||
"gvif" // Run full inliner
|
||||
"xaTPrci" // Prune unused return values and inline
|
||||
"CTUcarrLsTOtfDncarrIulc" // SSA plus simplify
|
||||
"]"
|
||||
"jmuljuljul VcTOcul jmulN"; // Make source short and pretty
|
||||
|
@ -93,10 +93,8 @@ template <class T>
|
||||
inline std::vector<T> operator+(std::vector<T>&& _a, std::vector<T>&& _b)
|
||||
{
|
||||
std::vector<T> ret(std::move(_a));
|
||||
if (&_a == &_b)
|
||||
ret += ret;
|
||||
else
|
||||
ret += std::move(_b);
|
||||
assert(&_a != &_b);
|
||||
ret += std::move(_b);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -178,6 +178,8 @@ add_library(yul
|
||||
optimiser/TypeInfo.h
|
||||
optimiser/UnusedFunctionParameterPruner.cpp
|
||||
optimiser/UnusedFunctionParameterPruner.h
|
||||
optimiser/UnusedFunctionReturnParameterPruner.cpp
|
||||
optimiser/UnusedFunctionReturnParameterPruner.h
|
||||
optimiser/UnusedFunctionsCommon.h
|
||||
optimiser/UnusedPruner.cpp
|
||||
optimiser/UnusedPruner.h
|
||||
@ -198,4 +200,4 @@ foreach(polyfill IN LISTS POLYFILLS)
|
||||
configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/ewasm_polyfill.in" ${CMAKE_BINARY_DIR}/include/ewasmPolyfills/${polyfill}.h @ONLY)
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil)
|
||||
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil)
|
||||
|
@ -44,6 +44,7 @@
|
||||
#include <libyul/optimiser/ReasoningBasedSimplifier.h>
|
||||
#include <libyul/optimiser/Rematerialiser.h>
|
||||
#include <libyul/optimiser/UnusedFunctionParameterPruner.h>
|
||||
#include <libyul/optimiser/UnusedFunctionReturnParameterPruner.h>
|
||||
#include <libyul/optimiser/UnusedPruner.h>
|
||||
#include <libyul/optimiser/ExpressionSimplifier.h>
|
||||
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
|
||||
@ -199,6 +200,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
||||
SSATransform,
|
||||
StructuralSimplifier,
|
||||
UnusedFunctionParameterPruner,
|
||||
UnusedFunctionReturnParameterPruner,
|
||||
UnusedPruner,
|
||||
VarDeclInitializer
|
||||
>();
|
||||
@ -209,37 +211,38 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
||||
map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
|
||||
{
|
||||
static map<string, char> 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'},
|
||||
{LiteralRematerialiser::name, 'T'},
|
||||
{LoadResolver::name, 'L'},
|
||||
{LoopInvariantCodeMotion::name, 'M'},
|
||||
{NameSimplifier::name, 'N'},
|
||||
{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'},
|
||||
{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'},
|
||||
{LiteralRematerialiser::name, 'T'},
|
||||
{LoadResolver::name, 'L'},
|
||||
{LoopInvariantCodeMotion::name, 'M'},
|
||||
{NameSimplifier::name, 'N'},
|
||||
{ReasoningBasedSimplifier::name, 'R'},
|
||||
{RedundantAssignEliminator::name, 'r'},
|
||||
{Rematerialiser::name, 'm'},
|
||||
{SSAReverser::name, 'V'},
|
||||
{SSATransform::name, 'a'},
|
||||
{StructuralSimplifier::name, 't'},
|
||||
{UnusedFunctionParameterPruner::name, 'p'},
|
||||
{UnusedFunctionReturnParameterPruner::name, 'P'},
|
||||
{UnusedPruner::name, 'u'},
|
||||
{VarDeclInitializer::name, 'd'},
|
||||
};
|
||||
yulAssert(lookupTable.size() == allSteps().size(), "");
|
||||
yulAssert((
|
||||
|
270
libyul/optimiser/UnusedFunctionReturnParameterPruner.cpp
Normal file
270
libyul/optimiser/UnusedFunctionReturnParameterPruner.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
#include <libyul/optimiser/UnusedFunctionReturnParameterPruner.h>
|
||||
#include <libyul/optimiser/UnusedFunctionsCommon.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/optimiser/NameDisplacer.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <libyul/AsmDataForward.h>
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Dialect.h>
|
||||
|
||||
#include <libsolutil/CommonData.h>
|
||||
#include <variant>
|
||||
|
||||
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<vector<Statement>> {
|
||||
if (holds_alternative<ExpressionStatement>(_s))
|
||||
if (
|
||||
auto& expressionStatement = get<ExpressionStatement>(_s);
|
||||
holds_alternative<FunctionCall>(expressionStatement.expression)
|
||||
)
|
||||
{
|
||||
FunctionCall& f = get<FunctionCall>(expressionStatement.expression);
|
||||
if (f.functionName.name == m_dialect.discardFunction(m_dialect.boolType)->name)
|
||||
if (holds_alternative<Identifier>(f.arguments[0]))
|
||||
return vector<Statement>{};
|
||||
}
|
||||
|
||||
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<YulString, vector<bool>> functions(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, size_t> _references,
|
||||
Block& _block
|
||||
);
|
||||
|
||||
private:
|
||||
explicit FindFunctionsWithUnusedReturns(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, size_t> 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<YulString, vector<bool>> m_unusedReturnParameter;
|
||||
/// A count of all references to all Declarations.
|
||||
map<YulString, size_t> const& m_references;
|
||||
/// Functions whose return parameters are all used.
|
||||
set<YulString> 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<FunctionCall>(*_v.value))
|
||||
{
|
||||
FunctionCall const& function = get<FunctionCall>(*_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<YulString, vector<bool>> FindFunctionsWithUnusedReturns::functions(
|
||||
Dialect const& _dialect,
|
||||
map<YulString, size_t> _references,
|
||||
Block& _block
|
||||
)
|
||||
{
|
||||
FindFunctionsWithUnusedReturns find{_dialect, _references};
|
||||
find(_block);
|
||||
|
||||
map<YulString, vector<bool>> 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<YulString, size_t> references = ReferencesCounter::countReferences(_ast);
|
||||
map<YulString, vector<bool>> functions = FindFunctionsWithUnusedReturns::functions(_context.dialect, references, _ast);
|
||||
|
||||
set<YulString> simpleFunctions;
|
||||
for (auto const& s: _ast.statements)
|
||||
if (holds_alternative<FunctionDefinition>(s))
|
||||
{
|
||||
FunctionDefinition const& f = get<FunctionDefinition>(s);
|
||||
if (tooSimpleToBePruned(f))
|
||||
simpleFunctions.insert(f.name);
|
||||
}
|
||||
|
||||
set<YulString> 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<YulString, YulString> 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<vector<Statement>> {
|
||||
if (holds_alternative<FunctionDefinition>(_s))
|
||||
{
|
||||
// The original function except that it has a new name (e.g., `f_1`)
|
||||
FunctionDefinition& originalFunction = get<FunctionDefinition>(_s);
|
||||
if (newToOriginalNames.count(originalFunction.name))
|
||||
{
|
||||
YulString linkingFunctionName = originalFunction.name;
|
||||
YulString originalFunctionName = newToOriginalNames.at(linkingFunctionName);
|
||||
pair<vector<bool>, vector<bool>> used = {
|
||||
vector<bool>(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<Statement> missingVariableDeclarations =
|
||||
applyMap(missingVariables, [&](auto& _v) -> Statement {
|
||||
return VariableDeclaration{
|
||||
originalFunction.location,
|
||||
vector<TypedName>{_v},
|
||||
nullptr
|
||||
};
|
||||
});
|
||||
|
||||
originalFunction.body.statements =
|
||||
move(missingVariableDeclarations) + move(originalFunction.body.statements);
|
||||
|
||||
return make_vector<Statement>(move(originalFunction), move(linkingFunction));
|
||||
}
|
||||
}
|
||||
|
||||
return nullopt;
|
||||
});
|
||||
|
||||
}
|
70
libyul/optimiser/UnusedFunctionReturnParameterPruner.h
Normal file
70
libyul/optimiser/UnusedFunctionReturnParameterPruner.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* UnusedFunctionReturnParameterPruner: Optimiser step that removes unused return parameters at function callsite.
|
||||
*
|
||||
* If a parameter is unused, like `z` and correspondingly `x` in the following snippet:
|
||||
*
|
||||
* {
|
||||
* let z := f(sload(1), sload(2))
|
||||
* function f(a,b) -> x { x := div(a,b) }
|
||||
* }
|
||||
*
|
||||
* We remove the parameter and create a new "linking" function `f2` as follows:
|
||||
*
|
||||
* function f(a,b) {
|
||||
* let x
|
||||
* x := div(a,b)
|
||||
* }
|
||||
*
|
||||
* function f2(a,b) -> x { x := f(a,b) }
|
||||
*
|
||||
* and replace all references to `f` by `f2`.
|
||||
* The inliner should be run afterwards to make sure that all references to `f2` are replaced by
|
||||
* `f`.
|
||||
*
|
||||
* Prerequisites: Disambiguator, FunctionHoister, SSATransform, ExpressionSplitter
|
||||
*
|
||||
* Only Disambiguator and FunctionHoister are needed for correctness.
|
||||
*
|
||||
* Ideally, we want all function calls to appear in the code in the following form:
|
||||
* `let x_1, ..., x_n = f(y_1, ..., y_m)`.
|
||||
*
|
||||
* ExpressionSplitter can reduce expressions into the above form. SSATransform allows us to realize
|
||||
* that the variable `x` is unused in the following example:
|
||||
*
|
||||
* let x := f()
|
||||
* x := 1
|
||||
*
|
||||
* Note that ForLoopConditionIntoBody and ForLoopInitRewriter are prerequisites for ExpressionSplitter and SSATransform.
|
||||
*/
|
||||
struct UnusedFunctionReturnParameterPruner
|
||||
{
|
||||
static constexpr char const* name{"UnusedFunctionReturnParameterPruner"};
|
||||
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||
};
|
||||
|
||||
}
|
@ -48,12 +48,12 @@ std::vector<T> filter(std::vector<T> const& _vec, std::vector<bool> const& _mask
|
||||
|
||||
/// Returns true if applying UnusedFunctionParameterPruner is not helpful or redundant because the
|
||||
/// inliner will be able to handle it anyway.
|
||||
bool tooSimpleToBePruned(FunctionDefinition const& _f)
|
||||
inline bool tooSimpleToBePruned(FunctionDefinition const& _f)
|
||||
{
|
||||
return _f.body.statements.size() <= 1 && CodeSize::codeSize(_f.body) <= 1;
|
||||
}
|
||||
|
||||
FunctionDefinition createLinkingFunction(
|
||||
inline FunctionDefinition createLinkingFunction(
|
||||
FunctionDefinition const& _original,
|
||||
std::pair<std::vector<bool>, std::vector<bool>> const& _usedParametersAndReturns,
|
||||
YulString const& _originalFunctionName,
|
||||
|
Loading…
Reference in New Issue
Block a user