mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add ZeroByReturndatasizeReplacer.
This commit is contained in:
parent
5433a640fb
commit
767dcc2e5d
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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((
|
||||
|
166
libyul/optimiser/ZeroByReturndatasizeReplacer.cpp
Normal file
166
libyul/optimiser/ZeroByReturndatasizeReplacer.cpp
Normal 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);
|
||||
}
|
76
libyul/optimiser/ZeroByReturndatasizeReplacer.h
Normal file
76
libyul/optimiser/ZeroByReturndatasizeReplacer.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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())
|
||||
// }
|
@ -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())
|
||||
// }
|
@ -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))
|
||||
// }
|
||||
// }
|
@ -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())
|
||||
// }
|
||||
// }
|
@ -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())
|
||||
// }
|
@ -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())
|
||||
// }
|
@ -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)
|
||||
// }
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
let x := 0
|
||||
sstore(x,x)
|
||||
}
|
||||
// ----
|
||||
// step: zeroByReturndatasizeReplacer
|
||||
//
|
||||
// {
|
||||
// let x := returndatasize()
|
||||
// sstore(x, x)
|
||||
// }
|
Loading…
Reference in New Issue
Block a user