From d9831c8b96c70af6a3e4c091b9f60b27a1d5b80c Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 9 May 2019 21:56:56 +0200 Subject: [PATCH] Split structural simplifier. --- libyul/CMakeLists.txt | 2 + libyul/optimiser/ControlFlowSimplifier.cpp | 158 +++++++++++++++++++++ libyul/optimiser/ControlFlowSimplifier.h | 54 +++++++ libyul/optimiser/StructuralSimplifier.cpp | 93 +----------- libyul/optimiser/StructuralSimplifier.h | 7 - libyul/optimiser/Suite.cpp | 7 + test/libyul/YulOptimizerTest.cpp | 2 + 7 files changed, 224 insertions(+), 99 deletions(-) create mode 100644 libyul/optimiser/ControlFlowSimplifier.cpp create mode 100644 libyul/optimiser/ControlFlowSimplifier.h 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..03039677d 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -233,6 +234,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line else if (m_optimizerStep == "structuralSimplifier") { disambiguate(); + ControlFlowSimplifier{}(*m_ast); StructuralSimplifier{*m_dialect}(*m_ast); } else if (m_optimizerStep == "equivalentFunctionCombiner")