Add ZeroByReturndatasizeReplacer.

This commit is contained in:
Daniel Kirchner 2021-03-11 16:10:13 +01:00
parent 5433a640fb
commit 767dcc2e5d
17 changed files with 449 additions and 5 deletions

View File

@ -324,6 +324,25 @@ SemanticInformation::Effect SemanticInformation::storage(Instruction _instructio
}
}
SemanticInformation::Effect SemanticInformation::returndata(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::DELEGATECALL:
case Instruction::CREATE:
case Instruction::CREATE2:
case Instruction::STATICCALL:
return SemanticInformation::Write;
case Instruction::RETURNDATACOPY:
case Instruction::RETURNDATASIZE:
return SemanticInformation::Read;
default:
return SemanticInformation::None;
}
}
SemanticInformation::Effect SemanticInformation::otherState(Instruction _instruction)
{
switch (_instruction)

View File

@ -82,6 +82,7 @@ struct SemanticInformation
static bool canBeRemovedIfNoMSize(Instruction _instruction);
static Effect memory(Instruction _instruction);
static Effect storage(Instruction _instruction);
static Effect returndata(Instruction _instruction);
static Effect otherState(Instruction _instruction);
static bool invalidInPureFunctions(Instruction _instruction);
static bool invalidInViewFunctions(Instruction _instruction);

View File

@ -47,7 +47,7 @@ struct OptimiserSettings
"gvif" // Run full inliner
"CTUcarrLsTFOtfDncarrIulc" // SSA plus simplify
"]"
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
"jmuljuljul VcTOcul jmulz"; // Make source short and pretty
/// No optimisations at all - not recommended.
static OptimiserSettings none()

View File

@ -198,6 +198,8 @@ add_library(yul
optimiser/VarDeclInitializer.h
optimiser/VarNameCleaner.cpp
optimiser/VarNameCleaner.h
optimiser/ZeroByReturndatasizeReplacer.cpp
optimiser/ZeroByReturndatasizeReplacer.h
)
target_link_libraries(yul PUBLIC evmasm solutil langutil smtutil)

View File

@ -77,10 +77,15 @@ struct SideEffects
/// effect on `msize()`.
Effect memory = None;
/// Can write, read or have no effect on returndata, when the value of `memory` is `Write`, `Read`
/// or `None` respectively. Note that, when the value is `Read`, the expression can have an
/// effect on `returndatasize()`.
Effect returndata = None;
/// @returns the worst-case side effects.
static SideEffects worst()
{
return SideEffects{false, false, false, false, false, Write, Write, Write};
return SideEffects{false, false, false, false, false, Write, Write, Write, Write};
}
/// @returns the combined side effects of two pieces of code.
@ -94,7 +99,8 @@ struct SideEffects
cannotLoop && _other.cannotLoop,
otherState + _other.otherState,
storage + _other.storage,
memory + _other.memory
memory + _other.memory,
returndata + _other.returndata
};
}
@ -115,7 +121,8 @@ struct SideEffects
cannotLoop == _other.cannotLoop &&
otherState == _other.otherState &&
storage == _other.storage &&
memory == _other.memory;
memory == _other.memory &&
returndata == _other.returndata;
}
};

View File

@ -339,6 +339,7 @@ SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instructio
translate(evmasm::SemanticInformation::otherState(_instruction)),
translate(evmasm::SemanticInformation::storage(_instruction)),
translate(evmasm::SemanticInformation::memory(_instruction)),
translate(evmasm::SemanticInformation::returndata(_instruction))
};
}

View File

@ -61,6 +61,7 @@
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/optimiser/NameSimplifier.h>
#include <libyul/optimiser/ZeroByReturndatasizeReplacer.h>
#include <libyul/backends/evm/ConstantOptimiser.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/AsmAnalysisInfo.h>
@ -206,7 +207,8 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
StructuralSimplifier,
UnusedFunctionParameterPruner,
UnusedPruner,
VarDeclInitializer
VarDeclInitializer,
ZeroByReturndatasizeReplacer
>();
// Does not include VarNameCleaner because it destroys the property of unique names.
// Does not include NameSimplifier.
@ -247,6 +249,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
{UnusedFunctionParameterPruner::name, 'p'},
{UnusedPruner::name, 'u'},
{VarDeclInitializer::name, 'd'},
{ZeroByReturndatasizeReplacer::name, 'z'},
};
yulAssert(lookupTable.size() == allSteps().size(), "");
yulAssert((

View File

@ -0,0 +1,166 @@
/*
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 replaces the literal 0 by returndatasize() in case there could not have been a call yet.
*/
#include <libyul/optimiser/ZeroByReturndatasizeReplacer.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/SideEffects.h>
#include <libyul/Utilities.h>
#include <libyul/AST.h>
#include <libsolutil/Algorithms.h>
#include <libsolutil/CommonData.h>
#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/reverse.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::yul;
namespace
{
bool invalidatesReturndata(Dialect const& _dialect, map<YulString, SideEffects> const& _functionSideEffects, FunctionCall const& _funCall) {
if (BuiltinFunction const* builtin = _dialect.builtin(_funCall.functionName.name))
if (builtin->sideEffects.returndata == SideEffects::Write)
return true;
if (auto const* sideEffects = util::valueOrNullptr(_functionSideEffects, _funCall.functionName.name))
if (sideEffects->returndata == SideEffects::Write)
return true;
return false;
}
class CallTracer: public ASTModifier
{
public:
CallTracer(
Dialect const& _dialect,
map<YulString, SideEffects> const& _functionSideEffects,
map<YulString, set<YulString>> const& _functionCalls
): m_dialect(_dialect), m_functionSideEffects(move(_functionSideEffects)), m_functionCalls(move(_functionCalls))
{}
using ASTModifier::operator();
void operator()(FunctionCall& _funCall) override
{
ASTModifier::operator()(_funCall);
if (!m_hasZeroReturndata)
util::BreadthFirstSearch<YulString>{{_funCall.functionName.name}, m_badFunctions}.run([&](YulString _fn, auto&& _addChild) {
m_badFunctions.insert(_fn);
if (set<YulString> const* callees = util::valueOrNullptr(m_functionCalls, _fn))
for (YulString f: *callees)
_addChild(f);
});
if (invalidatesReturndata(m_dialect, m_functionSideEffects, _funCall))
m_hasZeroReturndata = false;
}
void operator()(ForLoop& _loop) override
{
bool hadZeroReturndata = m_hasZeroReturndata;
ASTModifier::operator()(_loop);
if (hadZeroReturndata && !m_hasZeroReturndata)
{
m_badLoops.insert(&_loop);
ASTModifier::operator()(_loop);
}
}
void operator()(FunctionDefinition& _funDef) override
{
bool hadZeroReturndata = m_hasZeroReturndata;
m_hasZeroReturndata = true;
ASTModifier::operator()(_funDef);
m_hasZeroReturndata = hadZeroReturndata;
}
set<YulString> const& badFunctions() const { return m_badFunctions; }
set<ForLoop const*> const& badLoops() const { return m_badLoops; }
private:
set<YulString> m_badFunctions;
set<ForLoop const*> m_badLoops;
Dialect const& m_dialect;
map<YulString, SideEffects> const& m_functionSideEffects;
map<YulString, set<YulString>> const& m_functionCalls;
bool m_hasZeroReturndata = true;
};
}
void ZeroByReturndatasizeReplacer::operator()(FunctionCall& _funCall)
{
BuiltinFunction const* builtin = m_dialect.builtin(_funCall.functionName.name);
for (auto&& [index, arg]: ranges::views::enumerate(_funCall.arguments) | ranges::views::reverse)
if (!builtin || !builtin->literalArgument(index))
visit(arg);
if (invalidatesReturndata(m_dialect, m_functionSideEffects, _funCall))
m_hasZeroReturndata = false;
}
void ZeroByReturndatasizeReplacer::operator()(FunctionDefinition& _funDef)
{
if (!m_badFunctions.count(_funDef.name))
{
bool hadZeroReturndata = m_hasZeroReturndata;
m_hasZeroReturndata = true;
ASTModifier::operator()(_funDef);
m_hasZeroReturndata = hadZeroReturndata;
}
}
void ZeroByReturndatasizeReplacer::operator()(ForLoop& _loop)
{
if (m_badLoops.count(&_loop))
m_hasZeroReturndata = false;
else
ASTModifier::operator()(_loop);
}
void ZeroByReturndatasizeReplacer::visit(Expression& _e)
{
if (
Literal* literal = std::get_if<Literal>(&_e);
m_hasZeroReturndata && literal && valueOfLiteral(*literal) == 0
)
_e = FunctionCall{
literal->location,
Identifier{literal->location, "returndatasize"_yulstring},
{}
};
else
ASTModifier::visit(_e);
}
void ZeroByReturndatasizeReplacer::run(OptimiserStepContext& _context, Block& _ast)
{
if (
EVMDialect const* dialect = dynamic_cast<EVMDialect const*>(&_context.dialect);
!dialect || !dialect->providesObjectAccess() || !dialect->evmVersion().supportsReturndata()
)
return;
CallGraph callGraph = CallGraphGenerator::callGraph(_ast);
auto sideEffects = SideEffectsPropagator::sideEffects(_context.dialect, callGraph);
CallTracer tracer{_context.dialect, sideEffects, callGraph.functionCalls};
tracer(_ast);
ZeroByReturndatasizeReplacer{_context.dialect, sideEffects, tracer.badFunctions(), tracer.badLoops()}(_ast);
}

View File

@ -0,0 +1,76 @@
/*
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 replaces the literal 0 by returndatasize() in case there could not have been a call yet.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/OptimiserStep.h>
#include <libyul/ASTForward.h>
#include <libyul/Dialect.h>
#include <libsolutil/Common.h>
namespace solidity::yul
{
/**
* Optimisation stage that replaces the literal 0 by returndatasize() in case there could not have been a call yet.
*
* Only works for an EVMDialect that supports returndata and only works for a dialect with object access, since
* the analysis requires the entire contract to be available as Yul AST.
*
* Prerequisite: Disambiguator
*/
class ZeroByReturndatasizeReplacer: public ASTModifier
{
public:
static constexpr char const* name{"ZeroByReturndatasizeReplacer"};
static void run(OptimiserStepContext& _context, Block& _ast);
protected:
using ASTModifier::operator();
using ASTModifier::visit;
void operator()(FunctionCall& _funCall) override;
void operator()(FunctionDefinition& _funDef) override;
void operator()(ForLoop& _loop) override;
void visit(Expression& _e) override;
private:
explicit ZeroByReturndatasizeReplacer(
Dialect const& _dialect,
std::map<YulString, SideEffects> const& _functionSideEffects,
std::set<YulString> const& _badFunctions,
std::set<ForLoop const*> const& _badLoops
):
m_dialect(_dialect),
m_functionSideEffects(_functionSideEffects),
m_badFunctions(_badFunctions),
m_badLoops(_badLoops)
{}
bool m_hasZeroReturndata = true;
Dialect const& m_dialect;
std::map<YulString, SideEffects> const& m_functionSideEffects;
std::set<YulString> const& m_badFunctions;
std::set<ForLoop const*> const& m_badLoops;
};
}

View File

@ -0,0 +1,20 @@
{
let x := 0
function f() -> r {
r := call(0, 0, 0, 0, 0, 0, 0)
r := add(r, call(0, 0, 0, 0, 0, 0, 0))
}
sstore(0, f())
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// function f() -> r
// {
// r := call(returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize())
// r := add(r, call(0, 0, 0, 0, 0, 0, 0))
// }
// sstore(0, f())
// }

View File

@ -0,0 +1,20 @@
{
let x := 0
function f() -> r {
r := call(0, 0, 0, 0, 0, 0, 0)
r := add(r, call(0, 0, 0, 0, 0, 0, 0))
}
sstore(f(), 0)
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// function f() -> r
// {
// r := call(returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize())
// r := add(r, call(0, 0, 0, 0, 0, 0, 0))
// }
// sstore(f(), returndatasize())
// }

View File

@ -0,0 +1,22 @@
{
for { let i := 0 } lt(i, 4) { i := add(i, 1) } {
mstore(i, 0)
}
for { let i := 0 } lt(i, 4) { i := add(i, 1) } {
sstore(i, 0)
sstore(0, call(0, 0, 0, 0, 0, 0, 0))
}
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// for { let i := returndatasize() } lt(i, 4) { i := add(i, 1) }
// { mstore(i, returndatasize()) }
// for { let i_1 := 0 } lt(i_1, 4) { i_1 := add(i_1, 1) }
// {
// sstore(i_1, 0)
// sstore(0, call(0, 0, 0, 0, 0, 0, 0))
// }
// }

View File

@ -0,0 +1,39 @@
{
for { let i := 0 } lt(i, 4) { i := add(i, 1) } {
g()
mstore(i, 0)
}
for { let i := 0 } lt(i, 4) { i := add(i, 1) } {
f()
sstore(0, call(0, 0, 0, 0, 0, 0, 0))
}
function f() {
sstore(0, 0)
}
function g() {
sstore(0, 0)
}
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// for { let i := returndatasize() } lt(i, 4) { i := add(i, 1) }
// {
// g()
// mstore(i, returndatasize())
// }
// for { let i_1 := 0 } lt(i_1, 4) { i_1 := add(i_1, 1) }
// {
// f()
// sstore(0, call(0, 0, 0, 0, 0, 0, 0))
// }
// function f()
// { sstore(0, 0) }
// function g()
// {
// sstore(returndatasize(), returndatasize())
// }
// }

View File

@ -0,0 +1,23 @@
{
let x := 0
function f() -> r {
// cannot replace zeroes due to the second call to f below
r := call(0, 0, 0, 0, 0, 0, 0)
r := add(r, call(0, 0, 0, 0, 0, 0, 0))
}
pop(f())
pop(f())
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// function f() -> r
// {
// r := call(0, 0, 0, 0, 0, 0, 0)
// r := add(r, call(0, 0, 0, 0, 0, 0, 0))
// }
// pop(f())
// pop(f())
// }

View File

@ -0,0 +1,21 @@
{
let x := 0
function f() -> r {
// cannot replace zeroes due to the recursive call to f
r := call(0, 0, 0, 0, 0, 0, 0)
pop(f())
}
pop(f())
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// function f() -> r
// {
// r := call(0, 0, 0, 0, 0, 0, 0)
// pop(f())
// }
// pop(f())
// }

View File

@ -0,0 +1,13 @@
{
let x := 0
let y := call(0, 0, 0, 0, 0, 0, 0)
sstore(y, 0)
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// let y := call(returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize(), returndatasize())
// sstore(y, 0)
// }

View File

@ -0,0 +1,11 @@
{
let x := 0
sstore(x,x)
}
// ----
// step: zeroByReturndatasizeReplacer
//
// {
// let x := returndatasize()
// sstore(x, x)
// }