Implemented UnusedFunctionReturnParameterPruner

Optimization rule that rewrites functions whose return parameters are not used at callsite.
This commit is contained in:
hrkrshnn 2020-10-20 13:48:14 +02:00
parent 61069ec77d
commit 4553b8c0d5
7 changed files with 382 additions and 38 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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)

View File

@ -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((

View 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;
});
}

View 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);
};
}

View File

@ -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,