mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add common switch case prefix mover.
This commit is contained in:
parent
643140e2d6
commit
8916e64a1b
@ -8,6 +8,7 @@ Language Features:
|
||||
Compiler Features:
|
||||
* Standard JSON / combined JSON: New artifact "functionDebugData" that contains bytecode offsets of entry points of functions and potentially more information in the future.
|
||||
* Yul Optimizer: Evaluate ``keccak256(a, c)``, when the value at memory location ``a`` is known at compile time and ``c`` is a constant ``<= 32``.
|
||||
* Yul Optimizer: If all cases of a ``switch`` statement with a default case start with the same prefix, move that prefix out of the ``switch``.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -45,7 +45,7 @@ struct OptimiserSettings
|
||||
"dhfoDgvulfnTUtnIf" // None of these can make stack problems worse
|
||||
"["
|
||||
"xarrscLM" // Turn into SSA and simplify
|
||||
"cCTUtTOntnfDIul" // Perform structural simplification
|
||||
"cCTUtTOntnfDIulS" // Perform structural simplification
|
||||
"Lcul" // Simplify again
|
||||
"Vcul jj" // Reverse SSA
|
||||
|
||||
|
@ -97,6 +97,8 @@ add_library(yul
|
||||
optimiser/CircularReferencesPruner.h
|
||||
optimiser/CommonSubexpressionEliminator.cpp
|
||||
optimiser/CommonSubexpressionEliminator.h
|
||||
optimiser/CommonSwitchCasePrefixMover.cpp
|
||||
optimiser/CommonSwitchCasePrefixMover.h
|
||||
optimiser/ConditionalSimplifier.cpp
|
||||
optimiser/ConditionalSimplifier.h
|
||||
optimiser/ConditionalUnsimplifier.cpp
|
||||
|
161
libyul/optimiser/CommonSwitchCasePrefixMover.cpp
Normal file
161
libyul/optimiser/CommonSwitchCasePrefixMover.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include <libyul/optimiser/CommonSwitchCasePrefixMover.h>
|
||||
|
||||
#include <libyul/optimiser/ASTCopier.h>
|
||||
#include <libyul/optimiser/NameCollector.h>
|
||||
#include <libyul/optimiser/SyntacticalEquality.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <range/v3/algorithm/all_of.hpp>
|
||||
#include <range/v3/algorithm/transform.hpp>
|
||||
#include <range/v3/iterator.hpp>
|
||||
#include <range/v3/to_container.hpp>
|
||||
#include <range/v3/view/drop.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
#include <range/v3/view/zip.hpp>
|
||||
#include <range/v3/action/transform.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
|
||||
namespace
|
||||
{
|
||||
class IdentifierReplacer: public ASTCopier
|
||||
{
|
||||
public:
|
||||
IdentifierReplacer(map<YulString, YulString> _identifierMap): m_identifierMap(move(_identifierMap)) {}
|
||||
using ASTCopier::operator();
|
||||
protected:
|
||||
YulString translateIdentifier(YulString _name) override
|
||||
{
|
||||
if (YulString const* value = util::valueOrNullptr(m_identifierMap, _name))
|
||||
return *value;
|
||||
else
|
||||
return _name;
|
||||
}
|
||||
private:
|
||||
map<YulString, YulString> m_identifierMap;
|
||||
};
|
||||
}
|
||||
|
||||
void CommonSwitchCasePrefixMover::run(OptimiserStepContext&, Block& _ast)
|
||||
{
|
||||
CommonSwitchCasePrefixMover{}(_ast);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename Container>
|
||||
auto eraseFirst(Container& _container) { return _container.erase(begin(_container)); }
|
||||
|
||||
optional<Statement> tryExtractFirstStatement(Identifier const* _identifier, vector<Case>& _cases)
|
||||
{
|
||||
Statement& referenceStatement = _cases.front().body.statements.front();
|
||||
|
||||
if (_identifier)
|
||||
{
|
||||
Assignments assignments;
|
||||
visit(assignments, referenceStatement);
|
||||
if (assignments.names().count(_identifier->name))
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
if (!ranges::all_of(
|
||||
_cases | ranges::cpp20::views::drop(1),
|
||||
[&](Case& _case) {
|
||||
return !_case.body.statements.empty() &&
|
||||
SyntacticallyEqual{}(referenceStatement, _case.body.statements.front());
|
||||
}
|
||||
))
|
||||
return nullopt;
|
||||
|
||||
if (VariableDeclaration const* referenceVarDecl = get_if<VariableDeclaration>(&referenceStatement))
|
||||
{
|
||||
for (Case& switchCase: _cases | ranges::cpp20::views::drop(1))
|
||||
{
|
||||
VariableDeclaration* varDecl = std::get_if<VariableDeclaration>(&switchCase.body.statements.front());
|
||||
yulAssert(varDecl, "");
|
||||
yulAssert(varDecl->variables.size() == referenceVarDecl->variables.size(), "");
|
||||
|
||||
static constexpr auto nameFromTypedName = [](TypedName const& _name) { return _name.name; };
|
||||
IdentifierReplacer replacer{ranges::views::zip(
|
||||
varDecl->variables | ranges::views::transform(nameFromTypedName),
|
||||
referenceVarDecl->variables | ranges::views::transform(nameFromTypedName)
|
||||
) | ranges::to<std::map<YulString, YulString>>};
|
||||
vector<Statement> newBody;
|
||||
for (auto const& stmt: switchCase.body.statements | ranges::cpp20::views::drop(1))
|
||||
newBody.emplace_back(replacer.translate(stmt));
|
||||
switchCase.body.statements = move(newBody);
|
||||
}
|
||||
Statement result = move(referenceStatement);
|
||||
eraseFirst(_cases.front().body.statements);
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
yulAssert(!holds_alternative<FunctionDefinition>(referenceStatement), "FunctionHoister is required.");
|
||||
Statement result = move(referenceStatement);
|
||||
for (Case& switchCase: _cases)
|
||||
eraseFirst(switchCase.body.statements);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CommonSwitchCasePrefixMover::operator()(Block& _block)
|
||||
{
|
||||
util::iterateReplacing(
|
||||
_block.statements,
|
||||
[&](Statement& _s) -> optional<vector<Statement>>
|
||||
{
|
||||
visit(_s);
|
||||
if (Switch* switchStatement = get_if<Switch>(&_s))
|
||||
{
|
||||
yulAssert(!switchStatement->cases.empty(), "");
|
||||
|
||||
Identifier const* identifier = std::get_if<Identifier>(switchStatement->expression.get());
|
||||
if (!identifier && !holds_alternative<Literal>(*switchStatement->expression.get()))
|
||||
return {};
|
||||
|
||||
// We need to be able to tell how the default case behaves.
|
||||
if (switchStatement->cases.back().value)
|
||||
return {};
|
||||
|
||||
vector<Statement> result;
|
||||
while (!switchStatement->cases.front().body.statements.empty())
|
||||
if (auto extractedStatement = tryExtractFirstStatement(identifier, switchStatement->cases))
|
||||
result.emplace_back(move(*extractedStatement));
|
||||
else
|
||||
break;
|
||||
|
||||
if (!result.empty())
|
||||
{
|
||||
result.emplace_back(move(_s));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
);
|
||||
}
|
48
libyul/optimiser/CommonSwitchCasePrefixMover.h
Normal file
48
libyul/optimiser/CommonSwitchCasePrefixMover.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
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
|
||||
#pragma once
|
||||
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
/**
|
||||
* Component that moves a prefix that is shared between all cases (including the default case) of a switch statement
|
||||
* to the scope of the basic block containing the switch statement.
|
||||
*
|
||||
* In case of a shared variable declaration, the declaration is pulled out of the first case and the
|
||||
* names of the variables in the other cases are translated accordingly.
|
||||
*
|
||||
* Prerequisite: Disambiguator, FunctionHoister
|
||||
*
|
||||
* Works best, if the ExpressionSplitter is run before.
|
||||
*/
|
||||
|
||||
class CommonSwitchCasePrefixMover: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"CommonSwitchCasePrefixMover"};
|
||||
static void run(OptimiserStepContext& _context, Block& _ast);
|
||||
|
||||
void operator()(Block& _block) override;
|
||||
};
|
||||
|
||||
}
|
@ -48,6 +48,7 @@
|
||||
#include <libyul/optimiser/UnusedPruner.h>
|
||||
#include <libyul/optimiser/ExpressionSimplifier.h>
|
||||
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
|
||||
#include <libyul/optimiser/CommonSwitchCasePrefixMover.h>
|
||||
#include <libyul/optimiser/Semantics.h>
|
||||
#include <libyul/optimiser/SSAReverser.h>
|
||||
#include <libyul/optimiser/SSATransform.h>
|
||||
@ -180,6 +181,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
||||
BlockFlattener,
|
||||
CircularReferencesPruner,
|
||||
CommonSubexpressionEliminator,
|
||||
CommonSwitchCasePrefixMover,
|
||||
ConditionalSimplifier,
|
||||
ConditionalUnsimplifier,
|
||||
ControlFlowSimplifier,
|
||||
@ -220,6 +222,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
|
||||
{BlockFlattener::name, 'f'},
|
||||
{CircularReferencesPruner::name, 'l'},
|
||||
{CommonSubexpressionEliminator::name, 'c'},
|
||||
{CommonSwitchCasePrefixMover::name, 'S'},
|
||||
{ConditionalSimplifier::name, 'C'},
|
||||
{ConditionalUnsimplifier::name, 'U'},
|
||||
{ControlFlowSimplifier::name, 'n'},
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <libyul/optimiser/ConditionalUnsimplifier.h>
|
||||
#include <libyul/optimiser/ConditionalSimplifier.h>
|
||||
#include <libyul/optimiser/CommonSubexpressionEliminator.h>
|
||||
#include <libyul/optimiser/CommonSwitchCasePrefixMover.h>
|
||||
#include <libyul/optimiser/EquivalentFunctionCombiner.h>
|
||||
#include <libyul/optimiser/ExpressionSplitter.h>
|
||||
#include <libyul/optimiser/FunctionGrouper.h>
|
||||
@ -369,6 +370,11 @@ YulOptimizerTestCommon::YulOptimizerTestCommon(
|
||||
FakeUnreachableGenerator fakeUnreachableGenerator;
|
||||
fakeUnreachableGenerator(*m_ast);
|
||||
StackLimitEvader::run(*m_context, *m_object, fakeUnreachableGenerator.fakeUnreachables);
|
||||
}},
|
||||
{"commonSwitchCasePrefixMover", [&] () {
|
||||
disambiguate();
|
||||
FunctionHoister::run(*m_context, *m_ast);
|
||||
CommonSwitchCasePrefixMover::run(*m_context, *m_object->code);
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
switch x
|
||||
case 0 {
|
||||
x := calldataload(1)
|
||||
}
|
||||
default {
|
||||
x := calldataload(1)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// switch x
|
||||
// case 0 { x := calldataload(1) }
|
||||
// default { x := calldataload(1) }
|
||||
// }
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
switch mload(0)
|
||||
case 0 {
|
||||
sstore(1, 0)
|
||||
}
|
||||
default {
|
||||
sstore(1, 0)
|
||||
}
|
||||
switch msize()
|
||||
case 0 {
|
||||
sstore(1, 0)
|
||||
}
|
||||
default {
|
||||
sstore(1, 0)
|
||||
}
|
||||
switch sload(0)
|
||||
case 0 {
|
||||
sstore(1, 0)
|
||||
}
|
||||
default {
|
||||
sstore(1, 0)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// switch mload(0)
|
||||
// case 0 { sstore(1, 0) }
|
||||
// default { sstore(1, 0) }
|
||||
// switch msize()
|
||||
// case 0 { sstore(1, 0) }
|
||||
// default { sstore(1, 0) }
|
||||
// switch sload(0)
|
||||
// case 0 { sstore(1, 0) }
|
||||
// default { sstore(1, 0) }
|
||||
// }
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
switch x
|
||||
case 0 {
|
||||
for { let a := 0 } lt(a, 42) { a := add(a, 1) } {
|
||||
let b := 23
|
||||
sstore(add(b, a), a)
|
||||
}
|
||||
sstore(0, 23)
|
||||
}
|
||||
default {
|
||||
for { let b := 0 } lt(b, 42) { b := add(b, 1) } {
|
||||
let c := 23
|
||||
sstore(add(c, b), b)
|
||||
}
|
||||
sstore(0, 32)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// for { let a := 0 } lt(a, 42) { a := add(a, 1) }
|
||||
// {
|
||||
// let b := 23
|
||||
// sstore(add(b, a), a)
|
||||
// }
|
||||
// switch x
|
||||
// case 0 { sstore(0, 23) }
|
||||
// default { sstore(0, 32) }
|
||||
// }
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
let a := calldataload(42)
|
||||
switch a
|
||||
case 0 {
|
||||
let b := calldataload(23)
|
||||
switch b
|
||||
case 0 { sstore(0, 1) }
|
||||
default { sstore(0, 1) }
|
||||
}
|
||||
default { sstore(1, 2) }
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// let a := calldataload(42)
|
||||
// switch a
|
||||
// case 0 {
|
||||
// let b := calldataload(23)
|
||||
// sstore(0, 1)
|
||||
// switch b
|
||||
// case 0 { }
|
||||
// default { }
|
||||
// }
|
||||
// default { sstore(1, 2) }
|
||||
// }
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
switch calldataload(42)
|
||||
case 0 {
|
||||
sstore(0, 1)
|
||||
}
|
||||
case 1 {
|
||||
sstore(0, 1)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// switch calldataload(42)
|
||||
// case 0 { sstore(0, 1) }
|
||||
// case 1 { sstore(0, 1) }
|
||||
// }
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
function f() -> x {
|
||||
x := calldataload(0)
|
||||
sstore(0, 42)
|
||||
}
|
||||
switch f()
|
||||
case 0 {
|
||||
let x := sload(0)
|
||||
sstore(1, x)
|
||||
}
|
||||
default {
|
||||
let x := sload(0)
|
||||
sstore(1, x)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// switch f()
|
||||
// case 0 {
|
||||
// let x_1 := sload(0)
|
||||
// sstore(1, x_1)
|
||||
// }
|
||||
// default {
|
||||
// let x_2 := sload(0)
|
||||
// sstore(1, x_2)
|
||||
// }
|
||||
// function f() -> x
|
||||
// {
|
||||
// x := calldataload(0)
|
||||
// sstore(0, 42)
|
||||
// }
|
||||
// }
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
switch x
|
||||
case 0 {
|
||||
sstore(0, 1)
|
||||
sstore(1, 2)
|
||||
sstore(2, 3)
|
||||
}
|
||||
default {
|
||||
sstore(0, 1)
|
||||
sstore(1, 2)
|
||||
sstore(2, 4)
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// sstore(0, 1)
|
||||
// sstore(1, 2)
|
||||
// switch x
|
||||
// case 0 { sstore(2, 3) }
|
||||
// default { sstore(2, 4) }
|
||||
// }
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
let x := calldataload(0)
|
||||
switch x
|
||||
case 0 {
|
||||
sstore(0, 23)
|
||||
let a := calldataload(42)
|
||||
sstore(1, a)
|
||||
sstore(2, sub(a, 2))
|
||||
}
|
||||
default {
|
||||
sstore(0, 23)
|
||||
let b := calldataload(42)
|
||||
sstore(1, b)
|
||||
sstore(2, sub(b, 1))
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// step: commonSwitchCasePrefixMover
|
||||
//
|
||||
// {
|
||||
// let x := calldataload(0)
|
||||
// sstore(0, 23)
|
||||
// let a := calldataload(42)
|
||||
// sstore(1, a)
|
||||
// switch x
|
||||
// case 0 { sstore(2, sub(a, 2)) }
|
||||
// default { sstore(2, sub(a, 1)) }
|
||||
// }
|
@ -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) == "flcCUnDvejsxIOoighFTLMRrmVatpud");
|
||||
BOOST_TEST(toString(chromosome) == "flcSCUnDvejsxIOoighFTLMRrmVatpud");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names)
|
||||
|
Loading…
Reference in New Issue
Block a user