diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt
index 85aad725c..ff4d2cdbb 100644
--- a/libyul/CMakeLists.txt
+++ b/libyul/CMakeLists.txt
@@ -56,6 +56,8 @@ add_library(yul
optimiser/BlockFlattener.h
optimiser/CommonSubexpressionEliminator.cpp
optimiser/CommonSubexpressionEliminator.h
+ optimiser/ControlFlowSimplifier.cpp
+ optimiser/ControlFlowSimplifier.h
optimiser/DataFlowAnalyzer.cpp
optimiser/DataFlowAnalyzer.h
optimiser/DeadCodeEliminator.cpp
diff --git a/libyul/optimiser/ControlFlowSimplifier.cpp b/libyul/optimiser/ControlFlowSimplifier.cpp
new file mode 100644
index 000000000..7040891df
--- /dev/null
+++ b/libyul/optimiser/ControlFlowSimplifier.cpp
@@ -0,0 +1,158 @@
+/*
+ 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
+#include
+
+#include
+#include
+
+using namespace std;
+using namespace dev;
+using namespace yul;
+
+using OptionalStatements = boost::optional>;
+
+namespace
+{
+
+ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression)
+{
+ return {_location, FunctionalInstruction{
+ _location,
+ dev::eth::Instruction::POP,
+ {std::move(_expression)}
+ }};
+}
+
+void removeEmptyDefaultFromSwitch(Switch& _switchStmt)
+{
+ boost::remove_erase_if(
+ _switchStmt.cases,
+ [](Case const& _case) { return !_case.value && _case.body.statements.empty(); }
+ );
+}
+
+void removeEmptyCasesFromSwitch(Switch& _switchStmt)
+{
+ bool hasDefault = boost::algorithm::any_of(
+ _switchStmt.cases,
+ [](Case const& _case) { return !_case.value; }
+ );
+
+ if (hasDefault)
+ return;
+
+ boost::remove_erase_if(
+ _switchStmt.cases,
+ [](Case const& _case) { return _case.body.statements.empty(); }
+ );
+}
+
+OptionalStatements reduceNoCaseSwitch(Switch& _switchStmt)
+{
+ yulAssert(_switchStmt.cases.empty(), "Expected no case!");
+
+ auto loc = locationOf(*_switchStmt.expression);
+
+ OptionalStatements s = vector{};
+
+ s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
+
+ return s;
+}
+
+OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt)
+{
+ yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
+
+ auto& switchCase = _switchStmt.cases.front();
+ auto loc = locationOf(*_switchStmt.expression);
+ if (switchCase.value)
+ {
+ OptionalStatements s = vector{};
+ s->emplace_back(If{
+ std::move(_switchStmt.location),
+ make_unique(FunctionalInstruction{
+ std::move(loc),
+ dev::eth::Instruction::EQ,
+ {std::move(*switchCase.value), std::move(*_switchStmt.expression)}
+ }),
+ std::move(switchCase.body)
+ });
+ return s;
+ }
+ else
+ {
+ OptionalStatements s = vector{};
+ s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
+ s->emplace_back(std::move(switchCase.body));
+ return s;
+ }
+}
+
+}
+
+void ControlFlowSimplifier::operator()(Block& _block)
+{
+ simplify(_block.statements);
+}
+
+void ControlFlowSimplifier::simplify(std::vector& _statements)
+{
+ GenericFallbackReturnsVisitor const visitor(
+ [&](If& _ifStmt) -> OptionalStatements {
+ if (_ifStmt.body.statements.empty())
+ {
+ OptionalStatements s = vector{};
+ s->emplace_back(makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition)));
+ return s;
+ }
+ return {};
+ },
+ [&](Switch& _switchStmt) -> OptionalStatements {
+ removeEmptyDefaultFromSwitch(_switchStmt);
+ removeEmptyCasesFromSwitch(_switchStmt);
+
+ if (_switchStmt.cases.empty())
+ return reduceNoCaseSwitch(_switchStmt);
+ else if (_switchStmt.cases.size() == 1)
+ return reduceSingleCaseSwitch(_switchStmt);
+
+ return {};
+ },
+ [&](ForLoop&) -> OptionalStatements {
+ return {};
+ }
+ );
+
+ iterateReplacing(
+ _statements,
+ [&](Statement& _stmt) -> OptionalStatements
+ {
+ OptionalStatements result = boost::apply_visitor(visitor, _stmt);
+ if (result)
+ simplify(*result);
+ else
+ visit(_stmt);
+ return result;
+ }
+ );
+}
diff --git a/libyul/optimiser/ControlFlowSimplifier.h b/libyul/optimiser/ControlFlowSimplifier.h
new file mode 100644
index 000000000..4b0b83873
--- /dev/null
+++ b/libyul/optimiser/ControlFlowSimplifier.h
@@ -0,0 +1,54 @@
+/*
+ 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 .
+*/
+#pragma once
+
+#include
+
+namespace yul
+{
+
+/**
+ * Simplifies several control-flow structures:
+ * - replace if with empty body with pop(condition)
+ * - remove empty default switch case
+ * - remove empty switch case if no default case exists
+ * - replace switch with no cases with pop(expression)
+ * - turn switch with single case into if
+ * - replace switch with only default case with pop(expression) and body
+ * - replace switch with const expr with matching case body
+ * - replace ``for`` with terminating control flow and without other break/continue by ``if``
+ *
+ * None of these operations depend on the data flow. The StructuralSimplifier
+ * performs similar tasks that do depend on data flow.
+ *
+ * The ControlFlowSimplifier does record the presence or absence of ``break``
+ * and ``continue`` statements during its traversal.
+ *
+ * Prerequisite: Disambiguator, ForLoopInitRewriter.
+ *
+ * Important: Introduces EVM opcodes and thus can only be used on EVM code for now.
+ */
+class ControlFlowSimplifier: public ASTModifier
+{
+public:
+ using ASTModifier::operator();
+ void operator()(Block& _block) override;
+private:
+ void simplify(std::vector& _statements);
+};
+
+}
diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp
index 391e4153c..322dbe48e 100644
--- a/libyul/optimiser/StructuralSimplifier.cpp
+++ b/libyul/optimiser/StructuralSimplifier.cpp
@@ -32,39 +32,6 @@ using OptionalStatements = boost::optional>;
namespace {
-ExpressionStatement makePopExpressionStatement(langutil::SourceLocation const& _location, Expression&& _expression)
-{
- return {_location, FunctionalInstruction{
- _location,
- dev::eth::Instruction::POP,
- {std::move(_expression)}
- }};
-}
-
-void removeEmptyDefaultFromSwitch(Switch& _switchStmt)
-{
- boost::remove_erase_if(
- _switchStmt.cases,
- [](Case const& _case) { return !_case.value && _case.body.statements.empty(); }
- );
-}
-
-void removeEmptyCasesFromSwitch(Switch& _switchStmt)
-{
- bool hasDefault = boost::algorithm::any_of(
- _switchStmt.cases,
- [](Case const& _case) { return !_case.value; }
- );
-
- if (hasDefault)
- return;
-
- boost::remove_erase_if(
- _switchStmt.cases,
- [](Case const& _case) { return _case.body.statements.empty(); }
- );
-}
-
OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _constExprVal)
{
Block* matchingCaseBlock = nullptr;
@@ -92,35 +59,6 @@ OptionalStatements replaceConstArgSwitch(Switch& _switchStmt, u256 const& _const
return s;
}
-OptionalStatements reduceSingleCaseSwitch(Switch& _switchStmt)
-{
- yulAssert(_switchStmt.cases.size() == 1, "Expected only one case!");
-
- auto& switchCase = _switchStmt.cases.front();
- auto loc = locationOf(*_switchStmt.expression);
- if (switchCase.value)
- {
- OptionalStatements s = vector{};
- s->emplace_back(If{
- std::move(_switchStmt.location),
- make_unique(FunctionalInstruction{
- std::move(loc),
- dev::eth::Instruction::EQ,
- {std::move(*switchCase.value), std::move(*_switchStmt.expression)}
- }),
- std::move(switchCase.body)
- });
- return s;
- }
- else
- {
- OptionalStatements s = vector{};
- s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
- s->emplace_back(std::move(switchCase.body));
- return s;
- }
-}
-
}
void StructuralSimplifier::operator()(Block& _block)
@@ -130,19 +68,6 @@ void StructuralSimplifier::operator()(Block& _block)
popScope();
}
-OptionalStatements StructuralSimplifier::reduceNoCaseSwitch(Switch& _switchStmt) const
-{
- yulAssert(_switchStmt.cases.empty(), "Expected no case!");
-
- auto loc = locationOf(*_switchStmt.expression);
-
- OptionalStatements s = vector{};
-
- s->emplace_back(makePopExpressionStatement(loc, std::move(*_switchStmt.expression)));
-
- return s;
-}
-
boost::optional StructuralSimplifier::hasLiteralValue(Expression const& _expression) const
{
Expression const* expr = &_expression;
@@ -167,12 +92,6 @@ void StructuralSimplifier::simplify(std::vector& _statements)
{
GenericFallbackReturnsVisitor const visitor(
[&](If& _ifStmt) -> OptionalStatements {
- if (_ifStmt.body.statements.empty())
- {
- OptionalStatements s = vector{};
- s->emplace_back(makePopExpressionStatement(_ifStmt.location, std::move(*_ifStmt.condition)));
- return s;
- }
if (expressionAlwaysTrue(*_ifStmt.condition))
return {std::move(_ifStmt.body.statements)};
else if (expressionAlwaysFalse(*_ifStmt.condition))
@@ -182,22 +101,12 @@ void StructuralSimplifier::simplify(std::vector& _statements)
[&](Switch& _switchStmt) -> OptionalStatements {
if (boost::optional const constExprVal = hasLiteralValue(*_switchStmt.expression))
return replaceConstArgSwitch(_switchStmt, constExprVal.get());
-
- removeEmptyDefaultFromSwitch(_switchStmt);
- removeEmptyCasesFromSwitch(_switchStmt);
-
- if (_switchStmt.cases.empty())
- return reduceNoCaseSwitch(_switchStmt);
- else if (_switchStmt.cases.size() == 1)
- return reduceSingleCaseSwitch(_switchStmt);
-
return {};
},
[&](ForLoop& _forLoop) -> OptionalStatements {
if (expressionAlwaysFalse(*_forLoop.condition))
return {std::move(_forLoop.pre.statements)};
- else
- return {};
+ return {};
}
);
diff --git a/libyul/optimiser/StructuralSimplifier.h b/libyul/optimiser/StructuralSimplifier.h
index a4c90a7d3..a003abd6e 100644
--- a/libyul/optimiser/StructuralSimplifier.h
+++ b/libyul/optimiser/StructuralSimplifier.h
@@ -25,14 +25,8 @@ namespace yul
/**
* Structural simplifier. Performs the following simplification steps:
- * - replace if with empty body with pop(condition)
* - replace if with true condition with its body
* - remove if with false condition
- * - remove empty default switch case
- * - remove empty switch case if no default case exists
- * - replace switch with no cases with pop(expression)
- * - turn switch with single case into if
- * - replace switch with only default case with pop(expression) and body
* - replace switch with const expr with matching case body
* - replace for with false condition by its initialization part
*
@@ -52,7 +46,6 @@ private:
bool expressionAlwaysTrue(Expression const& _expression);
bool expressionAlwaysFalse(Expression const& _expression);
boost::optional hasLiteralValue(Expression const& _expression) const;
- boost::optional> reduceNoCaseSwitch(Switch& _switchStmt) const;
};
}
diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp
index e0ff847c6..f2d30fcd8 100644
--- a/libyul/optimiser/Suite.cpp
+++ b/libyul/optimiser/Suite.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -77,7 +78,9 @@ void OptimiserSuite::run(
EquivalentFunctionCombiner::run(ast);
UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers);
BlockFlattener{}(ast);
+ ControlFlowSimplifier{}(ast);
StructuralSimplifier{*_dialect}(ast);
+ ControlFlowSimplifier{}(ast);
BlockFlattener{}(ast);
// None of the above can make stack problems worse.
@@ -107,7 +110,9 @@ void OptimiserSuite::run(
{
// still in SSA, perform structural simplification
+ ControlFlowSimplifier{}(ast);
StructuralSimplifier{*_dialect}(ast);
+ ControlFlowSimplifier{}(ast);
BlockFlattener{}(ast);
DeadCodeEliminator{}(ast);
UnusedPruner::runUntilStabilised(*_dialect, ast, reservedIdentifiers);
@@ -162,6 +167,7 @@ void OptimiserSuite::run(
StructuralSimplifier{*_dialect}(ast);
BlockFlattener{}(ast);
DeadCodeEliminator{}(ast);
+ ControlFlowSimplifier{}(ast);
CommonSubexpressionEliminator{*_dialect}(ast);
SSATransform::run(ast, dispenser);
RedundantAssignEliminator::run(*_dialect, ast);
@@ -197,6 +203,7 @@ void OptimiserSuite::run(
StackCompressor::run(_dialect, ast, _optimizeStackAllocation, stackCompressorMaxIterations);
BlockFlattener{}(ast);
DeadCodeEliminator{}(ast);
+ ControlFlowSimplifier{}(ast);
FunctionGrouper{}(ast);
VarNameCleaner{ast, *_dialect, reservedIdentifiers}(ast);
diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp
index f1295af3d..c2a2b009e 100644
--- a/test/libyul/YulOptimizerTest.cpp
+++ b/test/libyul/YulOptimizerTest.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -230,6 +231,11 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
SSATransform::run(*m_ast, nameDispenser);
RedundantAssignEliminator::run(*m_dialect, *m_ast);
}
+ else if (m_optimizerStep == "controlFlowSimplifier")
+ {
+ disambiguate();
+ ControlFlowSimplifier{}(*m_ast);
+ }
else if (m_optimizerStep == "structuralSimplifier")
{
disambiguate();
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_movable_condition.yul
similarity index 75%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_movable_condition.yul
index 98adbb596..87450d939 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_movable_condition.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_movable_condition.yul
@@ -1,6 +1,6 @@
{ let a := mload(0) if a {} }
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// let a := mload(0)
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_non_movable_condition.yul
similarity index 64%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_non_movable_condition.yul
index 5059f6687..737ff7bf1 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/empty_if_non_movable_condition.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/empty_if_non_movable_condition.yul
@@ -1,5 +1,5 @@
{ if mload(0) {} }
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// { pop(mload(0)) }
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_only_default.yul
similarity index 79%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_only_default.yul
index 05689edd4..16e718d2e 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_only_default.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_only_default.yul
@@ -2,7 +2,7 @@
switch mload(0) default { mstore(1, 2) }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// pop(mload(0))
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_all.yul
similarity index 87%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_all.yul
index d8febde88..fb2434ec7 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_all.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_all.yul
@@ -11,7 +11,7 @@
default { }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// let y := 200
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_case.yul
similarity index 87%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_case.yul
index d96f74835..4b4f2cc0c 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_case.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_case.yul
@@ -6,7 +6,7 @@
case 2 { y := 10 }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// let y := 200
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_cases.yul
similarity index 85%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_cases.yul
index 45a89abd3..57beacaa1 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_cases.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_cases.yul
@@ -6,7 +6,7 @@
default { }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// let y := 200
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_default_case.yul
similarity index 87%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_default_case.yul
index 33a46b0bc..c5c4e44a7 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_remove_empty_default_case.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_remove_empty_default_case.yul
@@ -6,7 +6,7 @@
default { }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// let y := 200
diff --git a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_to_if.yul
similarity index 80%
rename from test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul
rename to test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_to_if.yul
index 0b3acee82..4a3558b65 100644
--- a/test/libyul/yulOptimizerTests/structuralSimplifier/switch_to_if.yul
+++ b/test/libyul/yulOptimizerTests/controlFlowSimplifier/switch_to_if.yul
@@ -2,7 +2,7 @@
switch calldataload(0) case 2 { mstore(0, 0) }
}
// ====
-// step: structuralSimplifier
+// step: controlFlowSimplifier
// ----
// {
// if eq(2, calldataload(0)) { mstore(0, 0) }
diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp
index 776759ebe..68d77fca5 100644
--- a/test/tools/yulopti.cpp
+++ b/test/tools/yulopti.cpp
@@ -32,6 +32,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -130,7 +131,7 @@ public:
cout << " (e)xpr inline/(i)nline/(s)implify/varname c(l)eaner/(u)nusedprune/ss(a) transform/" << endl;
cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-pre-rewriter/" << endl;
cout << " s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/? " << endl;
- cout << " stack com(p)ressor/(D)ead code eliminator/? " << endl;
+ cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/? " << endl;
cout.flush();
int option = readStandardInputChar();
cout << ' ' << char(option) << endl;
@@ -177,6 +178,9 @@ public:
case 't':
(StructuralSimplifier{*m_dialect})(*m_ast);
break;
+ case 'n':
+ (ControlFlowSimplifier{})(*m_ast);
+ break;
case 'u':
UnusedPruner::runUntilStabilised(*m_dialect, *m_ast);
break;