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;