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