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)