Add common switch case prefix mover.

This commit is contained in:
Daniel Kirchner 2021-01-11 22:33:44 +01:00
parent 643140e2d6
commit 8916e64a1b
16 changed files with 441 additions and 2 deletions

View File

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

View File

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

View File

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

View 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 {};
}
);
}

View 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;
};
}

View File

@ -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'},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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) == "flcCUnDvejsxIOoighFTLMRrmVatpud");
BOOST_TEST(toString(chromosome) == "flcSCUnDvejsxIOoighFTLMRrmVatpud");
}
BOOST_AUTO_TEST_CASE(optimisationSteps_should_translate_chromosomes_genes_to_optimisation_step_names)