[Yul] Prune functions that call each other but are otherwise unreferenced

This commit is contained in:
Gaith Hallak 2020-02-07 03:28:24 +03:00
parent d033c2f767
commit 24d6702986
11 changed files with 204 additions and 2 deletions

View File

@ -8,6 +8,7 @@ Compiler Features:
* Code Generator: Use ``calldatacopy`` instead of ``codecopy`` to zero out memory past input.
* AST: Add a new node for doxygen-style, structured documentation that can be received by contract, function, event and modifier definitions.
* Debug: Provide reason strings for compiler-generated internal reverts when using the ``--revert-strings`` option or the ``settings.debug.revertStrings`` setting on ``debug`` mode.
* Yul Optimizer: Prune functions that call each other but are otherwise unreferenced.
Bugfixes:

View File

@ -69,6 +69,8 @@ add_library(yul
optimiser/BlockHasher.h
optimiser/CallGraphGenerator.cpp
optimiser/CallGraphGenerator.h
optimiser/CircularReferencesPruner.cpp
optimiser/CircularReferencesPruner.h
optimiser/CommonSubexpressionEliminator.cpp
optimiser/CommonSubexpressionEliminator.h
optimiser/ConditionalSimplifier.cpp

View File

@ -0,0 +1,61 @@
/*
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/>.
*/
#include <libyul/optimiser/CircularReferencesPruner.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/AsmData.h>
#include <libsolutil/Algorithms.h>
using namespace std;
using namespace solidity::yul;
void CircularReferencesPruner::run(OptimiserStepContext& _context, Block& _ast)
{
CircularReferencesPruner{_context.reservedIdentifiers}(_ast);
}
void CircularReferencesPruner::operator()(Block& _block)
{
set<YulString> functionsToKeep =
functionsCalledFromOutermostContext(CallGraphGenerator::callGraph(_block));
for (auto&& statement: _block.statements)
if (holds_alternative<FunctionDefinition>(statement))
{
FunctionDefinition const& funDef = std::get<FunctionDefinition>(statement);
if (!functionsToKeep.count(funDef.name))
statement = Block{};
}
removeEmptyBlocks(_block);
}
set<YulString> CircularReferencesPruner::functionsCalledFromOutermostContext(CallGraph const& _callGraph)
{
set<YulString> verticesToTraverse = m_reservedIdentifiers;
verticesToTraverse.insert(YulString(""));
return util::BreadthFirstSearch<YulString>{{verticesToTraverse.begin(), verticesToTraverse.end()}}.run(
[&_callGraph](YulString _function, auto&& _addChild) {
if (_callGraph.functionCalls.count(_function))
for (auto const& callee: _callGraph.functionCalls.at(_function))
if (_callGraph.functionCalls.count(callee))
_addChild(callee);
}).visited;
}

View File

@ -0,0 +1,58 @@
/*
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/>.
*/
/**
* Optimization stage that removes functions that call each other but are
* otherwise unreferenced.
*
* Prerequisites: Disambiguator, FunctionHoister.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/OptimiserStep.h>
namespace solidity::yul
{
/**
* Optimization stage that removes functions that call each other but are
* neither externally referenced nor referenced from the outermost context.
*/
class CircularReferencesPruner: public ASTModifier
{
public:
static constexpr char const* name{"CircularReferencesPruner"};
static void run(OptimiserStepContext& _context, Block& _ast);
using ASTModifier::operator();
void operator()(Block& _block) override;
private:
CircularReferencesPruner(std::set<YulString> const& _reservedIdentifiers):
m_reservedIdentifiers(_reservedIdentifiers)
{}
/// Run a breadth-first search starting from the outermost context and
/// externally referenced functions to find all the functions that are
/// called from there either directly or indirectly.
std::set<YulString> functionsCalledFromOutermostContext(CallGraph const& _callGraph);
std::set<YulString> const& m_reservedIdentifiers;
};
}

View File

@ -24,6 +24,7 @@
#include <libyul/optimiser/VarDeclInitializer.h>
#include <libyul/optimiser/BlockFlattener.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/CircularReferencesPruner.h>
#include <libyul/optimiser/ControlFlowSimplifier.h>
#include <libyul/optimiser/ConditionalSimplifier.h>
#include <libyul/optimiser/ConditionalUnsimplifier.h>
@ -99,6 +100,7 @@ void OptimiserSuite::run(
FunctionGrouper::name,
EquivalentFunctionCombiner::name,
UnusedPruner::name,
CircularReferencesPruner::name,
BlockFlattener::name,
ControlFlowSimplifier::name,
LiteralRematerialiser::name,
@ -151,7 +153,8 @@ void OptimiserSuite::run(
BlockFlattener::name,
DeadCodeEliminator::name,
ForLoopConditionIntoBody::name,
UnusedPruner::name
UnusedPruner::name,
CircularReferencesPruner::name
}, ast);
}
@ -161,6 +164,7 @@ void OptimiserSuite::run(
LoadResolver::name,
CommonSubexpressionEliminator::name,
UnusedPruner::name,
CircularReferencesPruner::name,
}, ast);
}
@ -170,6 +174,7 @@ void OptimiserSuite::run(
SSAReverser::name,
CommonSubexpressionEliminator::name,
UnusedPruner::name,
CircularReferencesPruner::name,
ExpressionJoiner::name,
ExpressionJoiner::name,
@ -183,6 +188,7 @@ void OptimiserSuite::run(
suite.runSequence({
ExpressionInliner::name,
UnusedPruner::name,
CircularReferencesPruner::name,
}, ast);
}
@ -193,8 +199,10 @@ void OptimiserSuite::run(
SSATransform::name,
RedundantAssignEliminator::name,
UnusedPruner::name,
CircularReferencesPruner::name,
RedundantAssignEliminator::name,
UnusedPruner::name,
CircularReferencesPruner::name,
}, ast);
}
@ -244,6 +252,7 @@ void OptimiserSuite::run(
RedundantAssignEliminator::name,
ForLoopConditionIntoBody::name,
UnusedPruner::name,
CircularReferencesPruner::name,
CommonSubexpressionEliminator::name,
}, ast);
}
@ -255,10 +264,13 @@ void OptimiserSuite::run(
ExpressionJoiner::name,
Rematerialiser::name,
UnusedPruner::name,
CircularReferencesPruner::name,
ExpressionJoiner::name,
UnusedPruner::name,
CircularReferencesPruner::name,
ExpressionJoiner::name,
UnusedPruner::name,
CircularReferencesPruner::name,
SSAReverser::name,
CommonSubexpressionEliminator::name,
@ -266,10 +278,12 @@ void OptimiserSuite::run(
ForLoopConditionOutOfBody::name,
CommonSubexpressionEliminator::name,
UnusedPruner::name,
CircularReferencesPruner::name,
ExpressionJoiner::name,
Rematerialiser::name,
UnusedPruner::name,
CircularReferencesPruner::name,
}, ast);
// This is a tuning parameter, but actually just prevents infinite loops.
@ -339,6 +353,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
if (instance.empty())
instance = optimiserStepCollection<
BlockFlattener,
CircularReferencesPruner,
CommonSubexpressionEliminator,
ConditionalSimplifier,
ConditionalUnsimplifier,
@ -374,6 +389,7 @@ 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'},

View File

@ -28,6 +28,7 @@
#include <libyul/optimiser/DeadCodeEliminator.h>
#include <libyul/optimiser/Disambiguator.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/CircularReferencesPruner.h>
#include <libyul/optimiser/ConditionalUnsimplifier.h>
#include <libyul/optimiser/ConditionalSimplifier.h>
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
@ -250,6 +251,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
CommonSubexpressionEliminator::run(*m_context, *m_ast);
ExpressionSimplifier::run(*m_context, *m_ast);
UnusedPruner::run(*m_context, *m_ast);
CircularReferencesPruner::run(*m_context, *m_ast);
DeadCodeEliminator::run(*m_context, *m_ast);
ExpressionJoiner::run(*m_context, *m_ast);
ExpressionJoiner::run(*m_context, *m_ast);
@ -259,6 +261,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
disambiguate();
UnusedPruner::run(*m_context, *m_ast);
}
else if (m_optimizerStep == "circularReferencesPruner")
{
disambiguate();
FunctionHoister::run(*m_context, *m_ast);
CircularReferencesPruner::run(*m_context, *m_ast);
}
else if (m_optimizerStep == "deadCodeEliminator")
{
disambiguate();

View File

@ -0,0 +1,20 @@
{
let a
function f() -> x { x := g() }
function g() -> y { y := f() }
function h() -> z { z := g() }
a := h()
}
// ====
// step: circularReferencesPruner
// ----
// {
// let a
// a := h()
// function f() -> x
// { x := g() }
// function g() -> y
// { y := f() }
// function h() -> z
// { z := g() }
// }

View File

@ -0,0 +1,14 @@
{
{
function a() -> x { x := b() }
function b() -> y { y := a() }
}
{
function c() -> z { z := d() }
function d() -> w { w := c() }
}
}
// ====
// step: circularReferencesPruner
// ----
// { }

View File

@ -0,0 +1,14 @@
{
{
function z() -> x { x := y() }
function y() -> x { x := z() }
}
{
function z() -> x { x := y() }
function y() -> x { x := z() }
}
}
// ====
// step: circularReferencesPruner
// ----
// { }

View File

@ -0,0 +1,8 @@
{
function f() -> x { x := g() }
function g() -> x { x := f() }
}
// ====
// step: circularReferencesPruner
// ----
// { }

View File

@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE(output_operator_should_create_concise_and_unambiguous_strin
BOOST_TEST(chromosome.length() == allSteps.size());
BOOST_TEST(chromosome.optimisationSteps() == allSteps);
BOOST_TEST(toString(chromosome) == "fcCUnDvejsxIOoighTLMrmVatud");
BOOST_TEST(toString(chromosome) == "flcCUnDvejsxIOoighTLMrmVatud");
}
BOOST_AUTO_TEST_SUITE_END()