Equal store eliminator.

This commit is contained in:
chriseth 2021-11-11 15:54:38 +01:00
parent 223395bcad
commit 772e100813
14 changed files with 210 additions and 27 deletions

View File

@ -5,6 +5,7 @@ Language Features:
Compiler Features:
* Yul Optimizer: Remove ``mstore`` and ``sstore`` operations if the slot already contains the same value.

View File

@ -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

View File

@ -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

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <libyul/optimiser/EqualStoreEliminator.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/SideEffects.h>
#include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <libevmasm/GasMeter.h>
#include <libsolutil/Keccak256.h>
#include <limits>
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<ExpressionStatement>(&_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);
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// 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 <libyul/optimiser/DataFlowAnalyzer.h>
#include <libyul/optimiser/OptimiserStep.h>
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<YulString, SideEffects> _functionSideEffects
):
DataFlowAnalyzer(_dialect, std::move(_functionSideEffects))
{}
protected:
using ASTModifier::visit;
void visit(Statement& _statement) override;
std::set<Statement const*> m_pendingRemovals;
};
}

View File

@ -57,3 +57,18 @@ optional<evmasm::Instruction> yul::toEVMInstruction(Dialect const& _dialect, Yul
return builtin->instruction;
return nullopt;
}
void StatementRemover::operator()(Block& _block)
{
util::iterateReplacing(
_block.statements,
[&](Statement& _statement) -> std::optional<vector<Statement>>
{
if (m_toRemove.count(&_statement))
return {vector<Statement>{}};
else
return nullopt;
}
);
ASTModifier::operator()(_block);
}

View File

@ -25,6 +25,7 @@
#include <libyul/ASTForward.h>
#include <libyul/Dialect.h>
#include <libyul/YulString.h>
#include <libyul/optimiser/ASTWalker.h>
#include <optional>
@ -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<evmasm::Instruction> toEVMInstruction(Dialect const& _dialect, YulString const& _name);
class StatementRemover: public ASTModifier
{
public:
explicit StatementRemover(std::set<Statement const*> const& _toRemove): m_toRemove(_toRemove) {}
void operator()(Block& _block) override;
private:
std::set<Statement const*> const& m_toRemove;
};
}

View File

@ -32,6 +32,7 @@
#include <libyul/optimiser/DeadCodeEliminator.h>
#include <libyul/optimiser/FunctionGrouper.h>
#include <libyul/optimiser/FunctionHoister.h>
#include <libyul/optimiser/EqualStoreEliminator.h>
#include <libyul/optimiser/EquivalentFunctionCombiner.h>
#include <libyul/optimiser/ExpressionSplitter.h>
#include <libyul/optimiser/ExpressionJoiner.h>
@ -204,6 +205,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
ConditionalUnsimplifier,
ControlFlowSimplifier,
DeadCodeEliminator,
EqualStoreEliminator,
EquivalentFunctionCombiner,
ExpressionInliner,
ExpressionJoiner,
@ -244,6 +246,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{ConditionalUnsimplifier::name, 'U'},
{ControlFlowSimplifier::name, 'n'},
{DeadCodeEliminator::name, 'D'},
{EqualStoreEliminator::name, 'E'},
{EquivalentFunctionCombiner::name, 'v'},
{ExpressionInliner::name, 'e'},
{ExpressionJoiner::name, 'j'},

View File

@ -23,6 +23,7 @@
#include <libyul/optimiser/UnusedAssignEliminator.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/AST.h>
#include <libsolutil/CommonData.h>

View File

@ -23,6 +23,7 @@
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/OptimizerUtilities.h>
#include <libyul/AST.h>
#include <libsolutil/CommonData.h>
@ -156,18 +157,3 @@ void UnusedStoreBase::merge(TrackedStores& _target, vector<TrackedStores>&& _sou
merge(_target, move(ts));
_source.clear();
}
void StatementRemover::operator()(Block& _block)
{
util::iterateReplacing(
_block.statements,
[&](Statement& _statement) -> std::optional<vector<Statement>>
{
if (m_toRemove.count(&_statement))
return {vector<Statement>{}};
else
return nullopt;
}
);
ASTModifier::operator()(_block);
}

View File

@ -105,14 +105,4 @@ protected:
size_t m_forLoopNestingDepth = 0;
};
class StatementRemover: public ASTModifier
{
public:
explicit StatementRemover(std::set<Statement const*> const& _toRemove): m_toRemove(_toRemove) {}
void operator()(Block& _block) override;
private:
std::set<Statement const*> const& m_toRemove;
};
}

View File

@ -28,6 +28,7 @@
#include <libyul/optimiser/ConditionalUnsimplifier.h>
#include <libyul/optimiser/ConditionalSimplifier.h>
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
#include <libyul/optimiser/EqualStoreEliminator.h>
#include <libyul/optimiser/EquivalentFunctionCombiner.h>
#include <libyul/optimiser/ExpressionSplitter.h>
#include <libyul/optimiser/FunctionGrouper.h>
@ -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);

View File

@ -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)
// }

View File

@ -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)