Merge pull request #7234 from ethereum/extractSideEffects

[Yul] Extract side-effects into their own struct.
This commit is contained in:
chriseth 2019-08-14 16:00:17 +02:00 committed by GitHub
commit bb4e7de38c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 107 deletions

View File

@ -21,6 +21,7 @@
#pragma once
#include <libyul/YulString.h>
#include <libyul/SideEffects.h>
#include <boost/noncopyable.hpp>
@ -45,24 +46,9 @@ struct BuiltinFunction
YulString name;
std::vector<Type> parameters;
std::vector<Type> returns;
/// If true, calls to this function can be freely moved and copied (as long as their
/// arguments are either variables or also movable) without altering the semantics.
/// This means the function cannot depend on storage or memory, cannot have any side-effects,
/// but it can depend on state that is constant across an EVM-call.
bool movable = false;
/// If true, a call to this function can be omitted without changing semantics.
bool sideEffectFree = false;
/// If true, a call to this function can be omitted without changing semantics if the
/// program does not contain the msize instruction.
bool sideEffectFreeIfNoMSize = false;
SideEffects sideEffects;
/// If true, this is the msize instruction.
bool isMSize = false;
/// If false, storage of the current contract before and after the function is the same
/// under every circumstance. If the function does not return, this can be false.
bool invalidatesStorage = true;
/// If false, memory before and after the function is the same under every circumstance.
/// If the function does not return, this can be false.
bool invalidatesMemory = true;
/// If true, can only accept literals as arguments and they cannot be moved to variables.
bool literalArguments = false;
};

78
libyul/SideEffects.h Normal file
View File

@ -0,0 +1,78 @@
/*
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/>.
*/
#pragma once
#include <set>
namespace yul
{
/**
* Side effects of code.
*
* The default-constructed value applies to the "empty code".
*/
struct SideEffects
{
/// If true, expressions in this code can be freely moved and copied without altering the
/// semantics.
/// At statement level, it means that functions containing this code can be
/// called multiple times, their calls can be rearranged and calls can also be
/// deleted without changing the semantics.
/// This means it cannot depend on storage or memory, cannot have any side-effects,
/// but it can depend on state that is constant across an EVM-call.
bool movable = true;
/// If true, the code can be removed without changing the semantics.
bool sideEffectFree = true;
/// If true, the code can be removed without changing the semantics as long as
/// the whole program does not contain the msize instruction.
bool sideEffectFreeIfNoMSize = true;
/// If false, storage is guaranteed to be unchanged by the code under all
/// circumstances.
bool invalidatesStorage = false;
/// If false, memory is guaranteed to be unchanged by the code under all
/// circumstances.
bool invalidatesMemory = false;
/// @returns the worst-case side effects.
static SideEffects worst()
{
return SideEffects{false, false, false, true, true};
}
/// @returns the combined side effects of two pieces of code.
SideEffects operator+(SideEffects const& _other)
{
return SideEffects{
movable && _other.movable,
sideEffectFree && _other.sideEffectFree,
sideEffectFreeIfNoMSize && _other.sideEffectFreeIfNoMSize,
invalidatesStorage || _other.invalidatesStorage,
invalidatesMemory || _other.invalidatesMemory
};
}
/// Adds the side effects of another piece of code to this side effect.
SideEffects& operator+=(SideEffects const& _other)
{
*this = *this + _other;
return *this;
}
};
}

View File

@ -50,12 +50,8 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.name = YulString{_name};
f.parameters.resize(info.args);
f.returns.resize(info.ret);
f.movable = eth::SemanticInformation::movable(_instruction);
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
f.literalArguments = false;
f.instruction = _instruction;
f.generateCode = [_instruction](
@ -75,11 +71,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
string _name,
size_t _params,
size_t _returns,
bool _movable,
bool _sideEffectFree,
bool _sideEffectFreeIfNoMSize,
bool _invalidatesStorage,
bool _invalidatesMemory,
SideEffects _sideEffects,
bool _literalArguments,
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
)
@ -89,13 +81,9 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
f.name = name;
f.parameters.resize(_params);
f.returns.resize(_returns);
f.movable = _movable;
f.sideEffects = std::move(_sideEffects);
f.literalArguments = _literalArguments;
f.sideEffectFree = _sideEffectFree;
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
f.isMSize = false;
f.invalidatesStorage = _invalidatesStorage;
f.invalidatesMemory = _invalidatesMemory;
f.instruction = {};
f.generateCode = std::move(_generateCode);
return {name, f};
@ -116,7 +104,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
if (_objectAccess)
{
builtins.emplace(createFunction("datasize", 1, 1, true, true, true, false, false, true, [](
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context,
@ -137,7 +125,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataSize(_context.subIDs.at(dataName));
}
}));
builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, false, false, true, [](
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context,
@ -158,15 +146,22 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataOffset(_context.subIDs.at(dataName));
}
}));
builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, [](
FunctionCall const&,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void()> _visitArguments
) {
_visitArguments();
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
}));
builtins.emplace(createFunction(
"datacopy",
3,
0,
SideEffects{false, false, false, false, true},
false,
[](
FunctionCall const&,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void()> _visitArguments
) {
_visitArguments();
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
}
));
}
return builtins;
}
@ -225,3 +220,14 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version)
dialects[_version] = make_unique<EVMDialect>(AsmFlavour::Yul, false, _version);
return *dialects[_version];
}
SideEffects EVMDialect::sideEffectsOfInstruction(eth::Instruction _instruction)
{
return SideEffects{
eth::SemanticInformation::movable(_instruction),
eth::SemanticInformation::sideEffectFree(_instruction),
eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction),
eth::SemanticInformation::invalidatesStorage(_instruction),
eth::SemanticInformation::invalidatesMemory(_instruction)
};
}

View File

@ -80,6 +80,8 @@ struct EVMDialect: public Dialect
bool providesObjectAccess() const { return m_objectAccess; }
static SideEffects sideEffectsOfInstruction(dev::eth::Instruction _instruction);
protected:
bool const m_objectAccess;
langutil::EVMVersion const m_evmVersion;

View File

@ -49,19 +49,19 @@ WasmDialect::WasmDialect():
addFunction("i64.eqz", 1, 1);
addFunction("i64.store", 2, 0, false);
m_functions["i64.store"_yulstring].invalidatesStorage = false;
m_functions["i64.store"_yulstring].sideEffects.invalidatesStorage = false;
addFunction("i64.load", 1, 1, false);
m_functions["i64.load"_yulstring].invalidatesStorage = false;
m_functions["i64.load"_yulstring].invalidatesMemory = false;
m_functions["i64.load"_yulstring].sideEffectFree = true;
m_functions["i64.load"_yulstring].sideEffectFreeIfNoMSize = true;
m_functions["i64.load"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["i64.load"_yulstring].sideEffects.invalidatesMemory = false;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFree = true;
m_functions["i64.load"_yulstring].sideEffects.sideEffectFreeIfNoMSize = true;
addFunction("drop", 1, 0);
addFunction("unreachable", 0, 0, false);
m_functions["unreachable"_yulstring].invalidatesStorage = false;
m_functions["unreachable"_yulstring].invalidatesMemory = false;
m_functions["unreachable"_yulstring].sideEffects.invalidatesStorage = false;
m_functions["unreachable"_yulstring].sideEffects.invalidatesMemory = false;
addFunction("datasize", 1, 4, true, true);
addFunction("dataoffset", 1, 4, true, true);
@ -138,14 +138,10 @@ void WasmDialect::addEthereumExternals()
f.parameters.emplace_back(YulString(p));
for (string const& p: ext.returns)
f.returns.emplace_back(YulString(p));
f.movable = false;
// TODO some of them are side effect free.
f.sideEffectFree = false;
f.sideEffectFreeIfNoMSize = false;
f.sideEffects = SideEffects::worst();
f.isMSize = false;
f.invalidatesStorage = (ext.name == "storageStore");
// TODO some of them do not invalidate memory
f.invalidatesMemory = true;
f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
f.literalArguments = false;
}
}
@ -163,11 +159,7 @@ void WasmDialect::addFunction(
f.name = name;
f.parameters.resize(_params);
f.returns.resize(_returns);
f.movable = _movable;
f.sideEffectFree = _movable;
f.sideEffectFreeIfNoMSize = _movable;
f.sideEffects = _movable ? SideEffects{} : SideEffects::worst();
f.isMSize = false;
f.invalidatesStorage = !_movable;
f.invalidatesMemory = !_movable;
f.literalArguments = _literalArguments;
}

View File

@ -33,6 +33,7 @@ using namespace std;
using namespace dev;
using namespace yul;
SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Expression const& _expression):
SideEffectsCollector(_dialect)
{
@ -55,16 +56,7 @@ void SideEffectsCollector::operator()(FunctionalInstruction const& _instr)
{
ASTWalker::operator()(_instr);
if (!eth::SemanticInformation::movable(_instr.instruction))
m_movable = false;
if (!eth::SemanticInformation::sideEffectFree(_instr.instruction))
m_sideEffectFree = false;
if (!eth::SemanticInformation::sideEffectFreeIfNoMSize(_instr.instruction))
m_sideEffectFreeIfNoMSize = false;
if (eth::SemanticInformation::invalidatesStorage(_instr.instruction))
m_invalidatesStorage = true;
if (eth::SemanticInformation::invalidatesMemory(_instr.instruction))
m_invalidatesMemory = true;
m_sideEffects += EVMDialect::sideEffectsOfInstruction(_instr.instruction);
}
void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
@ -72,26 +64,9 @@ void SideEffectsCollector::operator()(FunctionCall const& _functionCall)
ASTWalker::operator()(_functionCall);
if (BuiltinFunction const* f = m_dialect.builtin(_functionCall.functionName.name))
{
if (!f->movable)
m_movable = false;
if (!f->sideEffectFree)
m_sideEffectFree = false;
if (!f->sideEffectFreeIfNoMSize)
m_sideEffectFreeIfNoMSize = false;
if (f->invalidatesStorage)
m_invalidatesStorage = true;
if (f->invalidatesMemory)
m_invalidatesMemory = true;
}
m_sideEffects += f->sideEffects;
else
{
m_movable = false;
m_sideEffectFree = false;
m_sideEffectFreeIfNoMSize = false;
m_invalidatesStorage = true;
m_invalidatesMemory = true;
}
m_sideEffects += SideEffects::worst();
}
bool MSizeFinder::containsMSize(Dialect const& _dialect, Block const& _ast)

View File

@ -21,6 +21,7 @@
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/SideEffects.h>
#include <set>
@ -44,32 +45,21 @@ public:
void operator()(FunctionalInstruction const& _functionalInstruction) override;
void operator()(FunctionCall const& _functionCall) override;
bool movable() const { return m_movable; }
bool movable() const { return m_sideEffects.movable; }
bool sideEffectFree(bool _allowMSizeModification = false) const
{
if (_allowMSizeModification)
return sideEffectFreeIfNoMSize();
else
return m_sideEffectFree;
return m_sideEffects.sideEffectFree;
}
bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; }
bool invalidatesStorage() const { return m_invalidatesStorage; }
bool invalidatesMemory() const { return m_invalidatesMemory; }
bool sideEffectFreeIfNoMSize() const { return m_sideEffects.sideEffectFreeIfNoMSize; }
bool invalidatesStorage() const { return m_sideEffects.invalidatesStorage; }
bool invalidatesMemory() const { return m_sideEffects.invalidatesMemory; }
private:
Dialect const& m_dialect;
/// Is the current expression movable or not.
bool m_movable = true;
/// Is the current expression side-effect free, i.e. can be removed
/// without changing the semantics.
bool m_sideEffectFree = true;
/// Is the current expression side-effect free up to msize, i.e. can be removed
/// without changing the semantics except for the value returned by the msize instruction.
bool m_sideEffectFreeIfNoMSize = true;
/// If false, storage is guaranteed to be unchanged by the code under all
/// circumstances.
bool m_invalidatesStorage = false;
bool m_invalidatesMemory = false;
SideEffects m_sideEffects;
};
/**

View File

@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis)
{
return _name == "builtin"_yulstring ? &f : nullptr;
}
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), false, false};
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}};
};
SimpleDialect dialect;