diff --git a/Changelog.md b/Changelog.md index b88e1a971..6e3602c1f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: + * Yul Optimizer: Remove ``mstore`` and ``sstore`` operations if the slot already contains the same value. diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index 5317acee4..e1c35e9ea 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -44,7 +44,7 @@ struct OptimiserSettings static char constexpr DefaultYulOptimiserSteps[] = "dhfoDgvulfnTUtnIf" // None of these can make stack problems worse "[" - "xa[r]scLM" // Turn into SSA and simplify + "xa[r]EscLM" // Turn into SSA and simplify "cCTUtTOntnfDIul" // Perform structural simplification "Lcul" // Simplify again "Vcul [j]" // Reverse SSA diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 16b68535a..fde673e3d 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(yul optimiser/DeadCodeEliminator.h optimiser/Disambiguator.cpp optimiser/Disambiguator.h + optimiser/EqualStoreEliminator.cpp + optimiser/EqualStoreEliminator.h optimiser/EquivalentFunctionDetector.cpp optimiser/EquivalentFunctionDetector.h optimiser/EquivalentFunctionCombiner.cpp diff --git a/libyul/optimiser/EqualStoreEliminator.cpp b/libyul/optimiser/EqualStoreEliminator.cpp new file mode 100644 index 000000000..c89567868 --- /dev/null +++ b/libyul/optimiser/EqualStoreEliminator.cpp @@ -0,0 +1,77 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::util; +using namespace solidity::evmasm; +using namespace solidity::yul; + +void EqualStoreEliminator::run(OptimiserStepContext const& _context, Block& _ast) +{ + EqualStoreEliminator eliminator{ + _context.dialect, + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)) + }; + eliminator(_ast); + + StatementRemover remover{eliminator.m_pendingRemovals}; + remover(_ast); +} + +void EqualStoreEliminator::visit(Statement& _statement) +{ + // No need to consider potential changes through complex arguments since + // isSimpleStore only returns something if the arguments are identifiers. + if (ExpressionStatement const* expression = get_if(&_statement)) + { + if (auto vars = isSimpleStore(StoreLoadLocation::Storage, *expression)) + { + if (auto const* currentValue = valueOrNullptr(m_storage, vars->first)) + if (*currentValue == vars->second) + m_pendingRemovals.insert(&_statement); + } + else if (auto vars = isSimpleStore(StoreLoadLocation::Memory, *expression)) + { + if (auto const* currentValue = valueOrNullptr(m_memory, vars->first)) + if (*currentValue == vars->second) + m_pendingRemovals.insert(&_statement); + } + } + + DataFlowAnalyzer::visit(_statement); +} diff --git a/libyul/optimiser/EqualStoreEliminator.h b/libyul/optimiser/EqualStoreEliminator.h new file mode 100644 index 000000000..796fcc538 --- /dev/null +++ b/libyul/optimiser/EqualStoreEliminator.h @@ -0,0 +1,60 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + */ + +#pragma once + +#include +#include + +namespace solidity::yul +{ + +/** + * Optimisation stage that removes mstore and sstore operations if they store the same + * value that is already known to be in that slot. + * + * Works best if the code is in SSA form - without literal arguments. + * + * Prerequisite: Disambiguator, ForLoopInitRewriter. + */ +class EqualStoreEliminator: public DataFlowAnalyzer +{ +public: + static constexpr char const* name{"EqualStoreEliminator"}; + static void run(OptimiserStepContext const&, Block& _ast); + +private: + EqualStoreEliminator( + Dialect const& _dialect, + std::map _functionSideEffects + ): + DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)) + {} + +protected: + using ASTModifier::visit; + void visit(Statement& _statement) override; + + std::set m_pendingRemovals; +}; + +} diff --git a/libyul/optimiser/OptimizerUtilities.cpp b/libyul/optimiser/OptimizerUtilities.cpp index 23596a745..ba06b2180 100644 --- a/libyul/optimiser/OptimizerUtilities.cpp +++ b/libyul/optimiser/OptimizerUtilities.cpp @@ -57,3 +57,18 @@ optional yul::toEVMInstruction(Dialect const& _dialect, Yul return builtin->instruction; return nullopt; } + +void StatementRemover::operator()(Block& _block) +{ + util::iterateReplacing( + _block.statements, + [&](Statement& _statement) -> std::optional> + { + if (m_toRemove.count(&_statement)) + return {vector{}}; + else + return nullopt; + } + ); + ASTModifier::operator()(_block); +} diff --git a/libyul/optimiser/OptimizerUtilities.h b/libyul/optimiser/OptimizerUtilities.h index d80b16316..b491e57e1 100644 --- a/libyul/optimiser/OptimizerUtilities.h +++ b/libyul/optimiser/OptimizerUtilities.h @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -48,4 +49,14 @@ bool isRestrictedIdentifier(Dialect const& _dialect, YulString const& _identifie /// Helper function that returns the instruction, if the `_name` is a BuiltinFunction std::optional toEVMInstruction(Dialect const& _dialect, YulString const& _name); +class StatementRemover: public ASTModifier +{ +public: + explicit StatementRemover(std::set const& _toRemove): m_toRemove(_toRemove) {} + + void operator()(Block& _block) override; +private: + std::set const& m_toRemove; +}; + } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 4012fa970..0f2194061 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -204,6 +205,7 @@ map> const& OptimiserSuite::allSteps() ConditionalUnsimplifier, ControlFlowSimplifier, DeadCodeEliminator, + EqualStoreEliminator, EquivalentFunctionCombiner, ExpressionInliner, ExpressionJoiner, @@ -244,6 +246,7 @@ map const& OptimiserSuite::stepNameToAbbreviationMap() {ConditionalUnsimplifier::name, 'U'}, {ControlFlowSimplifier::name, 'n'}, {DeadCodeEliminator::name, 'D'}, + {EqualStoreEliminator::name, 'E'}, {EquivalentFunctionCombiner::name, 'v'}, {ExpressionInliner::name, 'e'}, {ExpressionJoiner::name, 'j'}, diff --git a/libyul/optimiser/UnusedAssignEliminator.cpp b/libyul/optimiser/UnusedAssignEliminator.cpp index 74dd599a1..273aa6b79 100644 --- a/libyul/optimiser/UnusedAssignEliminator.cpp +++ b/libyul/optimiser/UnusedAssignEliminator.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include diff --git a/libyul/optimiser/UnusedStoreBase.cpp b/libyul/optimiser/UnusedStoreBase.cpp index 27700bdf7..73f957ecc 100644 --- a/libyul/optimiser/UnusedStoreBase.cpp +++ b/libyul/optimiser/UnusedStoreBase.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -156,18 +157,3 @@ void UnusedStoreBase::merge(TrackedStores& _target, vector&& _sou merge(_target, move(ts)); _source.clear(); } - -void StatementRemover::operator()(Block& _block) -{ - util::iterateReplacing( - _block.statements, - [&](Statement& _statement) -> std::optional> - { - if (m_toRemove.count(&_statement)) - return {vector{}}; - else - return nullopt; - } - ); - ASTModifier::operator()(_block); -} diff --git a/libyul/optimiser/UnusedStoreBase.h b/libyul/optimiser/UnusedStoreBase.h index 3bd4e4297..15dccb04a 100644 --- a/libyul/optimiser/UnusedStoreBase.h +++ b/libyul/optimiser/UnusedStoreBase.h @@ -105,14 +105,4 @@ protected: size_t m_forLoopNestingDepth = 0; }; -class StatementRemover: public ASTModifier -{ -public: - explicit StatementRemover(std::set const& _toRemove): m_toRemove(_toRemove) {} - - void operator()(Block& _block) override; -private: - std::set const& m_toRemove; -}; - } diff --git a/test/libyul/YulOptimizerTestCommon.cpp b/test/libyul/YulOptimizerTestCommon.cpp index cc315c4b9..bb8458d78 100644 --- a/test/libyul/YulOptimizerTestCommon.cpp +++ b/test/libyul/YulOptimizerTestCommon.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -236,6 +237,11 @@ YulOptimizerTestCommon::YulOptimizerTestCommon( ForLoopInitRewriter::run(*m_context, *m_ast); UnusedAssignEliminator::run(*m_context, *m_ast); }}, + {"equalStoreEliminator", [&]() { + disambiguate(); + ForLoopInitRewriter::run(*m_context, *m_ast); + EqualStoreEliminator::run(*m_context, *m_ast); + }}, {"ssaPlusCleanup", [&]() { disambiguate(); ForLoopInitRewriter::run(*m_context, *m_ast); diff --git a/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul new file mode 100644 index 000000000..395fea6d0 --- /dev/null +++ b/test/libyul/yulOptimizerTests/equalStoreEliminator/mstore_with_keccak.yul @@ -0,0 +1,31 @@ +{ + let var_k := calldataload(0) + let _1 := 0x00 + let _2 := 0x20 + mstore(_1, var_k) + mstore(_2, _1) + sstore(keccak256(_1, 0x40), 0x01) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 0x01), 0x03) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 2), 0x04) + mstore(_1, var_k) + mstore(_2, _1) + sstore(add(keccak256(_1, 0x40), 0x03), 2) +} +// ---- +// step: equalStoreEliminator +// +// { +// let var_k := calldataload(0) +// let _1 := 0x00 +// let _2 := 0x20 +// mstore(_1, var_k) +// mstore(_2, _1) +// sstore(keccak256(_1, 0x40), 0x01) +// sstore(add(keccak256(_1, 0x40), 0x01), 0x03) +// sstore(add(keccak256(_1, 0x40), 2), 0x04) +// sstore(add(keccak256(_1, 0x40), 0x03), 2) +// } diff --git a/test/yulPhaser/Chromosome.cpp b/test/yulPhaser/Chromosome.cpp index 19616cde0..d3e31b5e8 100644 --- a/test/yulPhaser/Chromosome.cpp +++ b/test/yulPhaser/Chromosome.cpp @@ -138,7 +138,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) == "flcCUnDvejsxIOoighFTLMRmVatrpud"); + BOOST_TEST(toString(chromosome) == "flcCUnDEvejsxIOoighFTLMRrmVatpud"); } BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names)