diff --git a/Changelog.md b/Changelog.md index 9bde3fffd..7fcfc08b6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 416a02455..12d2866c2 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -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 diff --git a/libyul/optimiser/CircularReferencesPruner.cpp b/libyul/optimiser/CircularReferencesPruner.cpp new file mode 100644 index 000000000..f9c849568 --- /dev/null +++ b/libyul/optimiser/CircularReferencesPruner.cpp @@ -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 . +*/ +#include + +#include +#include +#include + +#include + +using namespace std; +using namespace solidity::yul; + +void CircularReferencesPruner::run(OptimiserStepContext& _context, Block& _ast) +{ + CircularReferencesPruner{_context.reservedIdentifiers}(_ast); +} + +void CircularReferencesPruner::operator()(Block& _block) +{ + set functionsToKeep = + functionsCalledFromOutermostContext(CallGraphGenerator::callGraph(_block)); + + for (auto&& statement: _block.statements) + if (holds_alternative(statement)) + { + FunctionDefinition const& funDef = std::get(statement); + if (!functionsToKeep.count(funDef.name)) + statement = Block{}; + } + + removeEmptyBlocks(_block); +} + +set CircularReferencesPruner::functionsCalledFromOutermostContext(CallGraph const& _callGraph) +{ + set verticesToTraverse = m_reservedIdentifiers; + verticesToTraverse.insert(YulString("")); + + return util::BreadthFirstSearch{{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; +} diff --git a/libyul/optimiser/CircularReferencesPruner.h b/libyul/optimiser/CircularReferencesPruner.h new file mode 100644 index 000000000..2a51045d1 --- /dev/null +++ b/libyul/optimiser/CircularReferencesPruner.h @@ -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 . +*/ +/** + * Optimization stage that removes functions that call each other but are + * otherwise unreferenced. + * + * Prerequisites: Disambiguator, FunctionHoister. + */ + +#pragma once + +#include +#include +#include + +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 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 functionsCalledFromOutermostContext(CallGraph const& _callGraph); + + std::set const& m_reservedIdentifiers; +}; + +} diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 47c245752..a2fec4cc4 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -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> const& OptimiserSuite::allSteps() if (instance.empty()) instance = optimiserStepCollection< BlockFlattener, + CircularReferencesPruner, CommonSubexpressionEliminator, ConditionalSimplifier, ConditionalUnsimplifier, @@ -374,6 +389,7 @@ map const& OptimiserSuite::stepNameToAbbreviationMap() { static map lookupTable{ {BlockFlattener::name, 'f'}, + {CircularReferencesPruner::name, 'l'}, {CommonSubexpressionEliminator::name, 'c'}, {ConditionalSimplifier::name, 'C'}, {ConditionalUnsimplifier::name, 'U'}, diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index f3906aead..3ac745d6f 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/test/libyul/yulOptimizerTests/circularReferencesPruner/called_from_non_function.yul b/test/libyul/yulOptimizerTests/circularReferencesPruner/called_from_non_function.yul new file mode 100644 index 000000000..07eca2709 --- /dev/null +++ b/test/libyul/yulOptimizerTests/circularReferencesPruner/called_from_non_function.yul @@ -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() } +// } diff --git a/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_different_names.yul b/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_different_names.yul new file mode 100644 index 000000000..0630fe35f --- /dev/null +++ b/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_different_names.yul @@ -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 +// ---- +// { } diff --git a/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_same_name.yul b/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_same_name.yul new file mode 100644 index 000000000..9873c717f --- /dev/null +++ b/test/libyul/yulOptimizerTests/circularReferencesPruner/nested_same_name.yul @@ -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 +// ---- +// { } diff --git a/test/libyul/yulOptimizerTests/circularReferencesPruner/trivial.yul b/test/libyul/yulOptimizerTests/circularReferencesPruner/trivial.yul new file mode 100644 index 000000000..c0fe6834a --- /dev/null +++ b/test/libyul/yulOptimizerTests/circularReferencesPruner/trivial.yul @@ -0,0 +1,8 @@ +{ + function f() -> x { x := g() } + function g() -> x { x := f() } +} +// ==== +// step: circularReferencesPruner +// ---- +// { } diff --git a/test/yulPhaser/Chromosome.cpp b/test/yulPhaser/Chromosome.cpp index dd250d0ec..bd07e1b2e 100644 --- a/test/yulPhaser/Chromosome.cpp +++ b/test/yulPhaser/Chromosome.cpp @@ -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()