Merge pull request #12272 from ethereum/equalStoreEliminator

Equal store eliminator.
This commit is contained in:
chriseth 2022-01-05 11:24:59 +01:00 committed by GitHub
commit c16867cb83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 393 additions and 36 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

@ -290,6 +290,7 @@ on the individual steps and their sequence below.
- :ref:`conditional-unsimplifier`.
- :ref:`control-flow-simplifier`.
- :ref:`dead-code-eliminator`.
- :ref:`equal-store-eliminator`.
- :ref:`equivalent-function-combiner`.
- :ref:`expression-joiner`.
- :ref:`expression-simplifier`.
@ -938,6 +939,22 @@ we require ForLoopInitRewriter to run before this step.
Prerequisite: ForLoopInitRewriter, Function Hoister, Function Grouper
.. _equal-store-eliminator:
EqualStoreEliminator
^^^^^^^^^^^^^^^^^^^^
This steps removes ``mstore(k, v)`` and ``sstore(k, v)`` calls if
there was a previous call to ``mstore(k, v)`` / ``sstore(k, v)``,
no other store in between and the values of ``k`` and ``v`` did not change.
This simple step is effective if run after the SSA transform and the
Common Subexpression Eliminator, because SSA will make sure that the variables
will not change and the Common Subexpression Eliminator re-uses exactly the same
variable if the value is known to be the same.
Prerequisites: Disambiguator, ForLoopInitRewriter
.. _unused-pruner:
UnusedPruner

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,70 @@
/*
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/AST.h>
#include <libyul/Utilities.h>
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

@ -156,18 +156,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

@ -178,7 +178,7 @@ contract DepositContract is IDepositContract, ERC165 {
// compileViaYul: also
// ----
// constructor()
// gas irOptimized: 1558001
// gas irOptimized: 1557137
// gas legacy: 2436584
// gas legacyOptimized: 1776483
// supportsInterface(bytes4): 0x0 -> 0

View File

@ -20,7 +20,7 @@ contract test {
// compileViaYul: also
// ----
// set(uint8,uint8,uint8,uint8,uint8): 1, 21, 22, 42, 43 -> 0, 0, 0, 0
// gas irOptimized: 111965
// gas irOptimized: 111896
// gas legacy: 113806
// gas legacyOptimized: 111781
// get(uint8): 1 -> 21, 22, 42, 43

View File

@ -38,12 +38,12 @@ contract c {
// compileViaYul: also
// ----
// set(uint256): 7 -> true
// gas irOptimized: 110011
// gas irOptimized: 110119
// gas legacy: 110616
// gas legacyOptimized: 110006
// retrieve(uint256): 7 -> 1, 3, 4, 2
// copy(uint256,uint256): 7, 8 -> true
// gas irOptimized: 118707
// gas irOptimized: 118698
// gas legacy: 119166
// gas legacyOptimized: 118622
// retrieve(uint256): 7 -> 1, 3, 4, 2

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,25 @@
{
let a := calldataload(0)
let b := 20
sstore(a, b)
if calldataload(32) {
sstore(a, b)
pop(staticcall(0, 0, 0, 0, 0, 0))
sstore(a, b)
}
sstore(a, b)
}
// ====
// EVMVersion: >=byzantium
// ----
// step: equalStoreEliminator
//
// {
// let a := calldataload(0)
// let b := 20
// sstore(a, b)
// if calldataload(32)
// {
// pop(staticcall(0, 0, 0, 0, 0, 0))
// }
// }

View File

@ -0,0 +1,20 @@
{
let x := calldataload(0)
let y := calldataload(1)
sstore(x, y)
for {let a := 1} lt(a, 10) {a := add(a, 1) } {
sstore(x, y)
}
}
// ----
// step: equalStoreEliminator
//
// {
// let x := calldataload(0)
// let y := calldataload(1)
// sstore(x, y)
// let a := 1
// for { } lt(a, 10) { a := add(a, 1) }
// { sstore(x, y) }
// }

View File

@ -0,0 +1,56 @@
{
f(calldataload(0), calldataload(32))
h(calldataload(64), calldataload(96))
function f(a, b) {
// gets removed
sstore(a, b)
g()
sstore(a, b)
}
function g() {
pop(staticcall(0, 0, 0, 0, 0, 0))
}
function h(a_, b_) {
// cannot be removed
sstore(a_, b_)
i()
sstore(a_, b_)
}
function i() {
pop(delegatecall(0, 0, 0, 0, 0, 0))
}
}
// ====
// EVMVersion: >=byzantium
// ----
// step: equalStoreEliminator
//
// {
// f(calldataload(0), calldataload(32))
// h(calldataload(64), calldataload(96))
// function f(a, b)
// {
// sstore(a, b)
// g()
// }
// function g()
// {
// pop(staticcall(0, 0, 0, 0, 0, 0))
// }
// function h(a_, b_)
// {
// sstore(a_, b_)
// i()
// sstore(a_, b_)
// }
// function i()
// {
// pop(delegatecall(0, 0, 0, 0, 0, 0))
// }
// }

View File

@ -0,0 +1,24 @@
{
let x := calldataload(0)
let y := sload(x)
// both of these can be removed
sstore(x, y)
sstore(x, y)
let a := x
let b := mload(a)
// both of these can be removed
mstore(a, b)
mstore(a, b)
}
// ====
// EVMVersion: >=byzantium
// ----
// step: equalStoreEliminator
//
// {
// let x := calldataload(0)
// let y := sload(x)
// let a := x
// let b := mload(a)
// }

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

@ -0,0 +1,18 @@
{
let x := calldataload(0)
let y := calldataload(32)
sstore(x, y)
y := calldataload(64)
// cannot be removed
sstore(x, y)
}
// ----
// step: equalStoreEliminator
//
// {
// let x := calldataload(0)
// let y := calldataload(32)
// sstore(x, y)
// y := calldataload(64)
// sstore(x, y)
// }

View File

@ -0,0 +1,27 @@
{
let a := calldataload(0)
let b := 20
sstore(a, b)
if calldataload(32) {
sstore(a, b)
pop(staticcall(0, 0, 0, 0, 0, 0))
verbatim_0i_0o("xyz")
}
sstore(a, b)
}
// ====
// EVMVersion: >=byzantium
// ----
// step: equalStoreEliminator
//
// {
// let a := calldataload(0)
// let b := 20
// sstore(a, b)
// if calldataload(32)
// {
// pop(staticcall(0, 0, 0, 0, 0, 0))
// verbatim_0i_0o("xyz")
// }
// sstore(a, b)
// }

View File

@ -55,7 +55,6 @@
// sstore(0, 0)
// sstore(2, _1)
// extcodecopy(_1, msize(), _1, _1)
// sstore(0, 0)
// sstore(3, _1)
// }
// function gcd(_a, _b) -> out

View File

@ -20,9 +20,7 @@
// f()
// sstore(0, 1)
// f()
// sstore(0, 1)
// f()
// sstore(0, 1)
// }
// function f()
// {

View File

@ -18,9 +18,7 @@
// f()
// sstore(0, 1)
// f()
// sstore(0, 1)
// f()
// sstore(0, 1)
// }
// function f()
// {

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) == "flcCUnDEvejsxIOoighFTLMRmVatrpud");
}
BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names)