Extract side effects into their own struct.

This commit is contained in:
chriseth 2019-08-13 14:40:26 +02:00
parent d5744b3c1c
commit 7d30fbdef0
8 changed files with 136 additions and 107 deletions

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <libyul/YulString.h> #include <libyul/YulString.h>
#include <libyul/SideEffects.h>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
@ -45,24 +46,9 @@ struct BuiltinFunction
YulString name; YulString name;
std::vector<Type> parameters; std::vector<Type> parameters;
std::vector<Type> returns; std::vector<Type> returns;
/// If true, calls to this function can be freely moved and copied (as long as their SideEffects sideEffects;
/// 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;
/// If true, this is the msize instruction. /// If true, this is the msize instruction.
bool isMSize = false; 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. /// If true, can only accept literals as arguments and they cannot be moved to variables.
bool literalArguments = false; 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.name = YulString{_name};
f.parameters.resize(info.args); f.parameters.resize(info.args);
f.returns.resize(info.ret); f.returns.resize(info.ret);
f.movable = eth::SemanticInformation::movable(_instruction); f.sideEffects = EVMDialect::sideEffectsOfInstruction(_instruction);
f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction);
f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction);
f.isMSize = _instruction == dev::eth::Instruction::MSIZE; f.isMSize = _instruction == dev::eth::Instruction::MSIZE;
f.invalidatesStorage = eth::SemanticInformation::invalidatesStorage(_instruction);
f.invalidatesMemory = eth::SemanticInformation::invalidatesMemory(_instruction);
f.literalArguments = false; f.literalArguments = false;
f.instruction = _instruction; f.instruction = _instruction;
f.generateCode = [_instruction]( f.generateCode = [_instruction](
@ -75,11 +71,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
string _name, string _name,
size_t _params, size_t _params,
size_t _returns, size_t _returns,
bool _movable, SideEffects _sideEffects,
bool _sideEffectFree,
bool _sideEffectFreeIfNoMSize,
bool _invalidatesStorage,
bool _invalidatesMemory,
bool _literalArguments, bool _literalArguments,
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
) )
@ -89,13 +81,9 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
f.name = name; f.name = name;
f.parameters.resize(_params); f.parameters.resize(_params);
f.returns.resize(_returns); f.returns.resize(_returns);
f.movable = _movable; f.sideEffects = std::move(_sideEffects);
f.literalArguments = _literalArguments; f.literalArguments = _literalArguments;
f.sideEffectFree = _sideEffectFree;
f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize;
f.isMSize = false; f.isMSize = false;
f.invalidatesStorage = _invalidatesStorage;
f.invalidatesMemory = _invalidatesMemory;
f.instruction = {}; f.instruction = {};
f.generateCode = std::move(_generateCode); f.generateCode = std::move(_generateCode);
return {name, f}; return {name, f};
@ -116,7 +104,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
if (_objectAccess) 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, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -137,7 +125,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataSize(_context.subIDs.at(dataName)); _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, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -158,15 +146,22 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataOffset(_context.subIDs.at(dataName)); _assembly.appendDataOffset(_context.subIDs.at(dataName));
} }
})); }));
builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, true, false, []( builtins.emplace(createFunction(
FunctionCall const&, "datacopy",
AbstractAssembly& _assembly, 3,
BuiltinContext&, 0,
std::function<void()> _visitArguments SideEffects{false, false, false, false, true},
) { false,
_visitArguments(); [](
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY); FunctionCall const&,
})); AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void()> _visitArguments
) {
_visitArguments();
_assembly.appendInstruction(dev::eth::Instruction::CODECOPY);
}
));
} }
return builtins; return builtins;
} }
@ -225,3 +220,14 @@ EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version)
dialects[_version] = make_unique<EVMDialect>(AsmFlavour::Yul, false, _version); dialects[_version] = make_unique<EVMDialect>(AsmFlavour::Yul, false, _version);
return *dialects[_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; } bool providesObjectAccess() const { return m_objectAccess; }
static SideEffects sideEffectsOfInstruction(dev::eth::Instruction _instruction);
protected: protected:
bool const m_objectAccess; bool const m_objectAccess;
langutil::EVMVersion const m_evmVersion; langutil::EVMVersion const m_evmVersion;

View File

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

View File

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

View File

@ -21,6 +21,7 @@
#pragma once #pragma once
#include <libyul/optimiser/ASTWalker.h> #include <libyul/optimiser/ASTWalker.h>
#include <libyul/SideEffects.h>
#include <set> #include <set>
@ -44,32 +45,21 @@ public:
void operator()(FunctionalInstruction const& _functionalInstruction) override; void operator()(FunctionalInstruction const& _functionalInstruction) override;
void operator()(FunctionCall const& _functionCall) 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 bool sideEffectFree(bool _allowMSizeModification = false) const
{ {
if (_allowMSizeModification) if (_allowMSizeModification)
return sideEffectFreeIfNoMSize(); return sideEffectFreeIfNoMSize();
else else
return m_sideEffectFree; return m_sideEffects.sideEffectFree;
} }
bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } bool sideEffectFreeIfNoMSize() const { return m_sideEffects.sideEffectFreeIfNoMSize; }
bool invalidatesStorage() const { return m_invalidatesStorage; } bool invalidatesStorage() const { return m_sideEffects.invalidatesStorage; }
bool invalidatesMemory() const { return m_invalidatesMemory; } bool invalidatesMemory() const { return m_sideEffects.invalidatesMemory; }
private: private:
Dialect const& m_dialect; Dialect const& m_dialect;
/// Is the current expression movable or not. SideEffects m_sideEffects;
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;
}; };
/** /**

View File

@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis)
{ {
return _name == "builtin"_yulstring ? &f : nullptr; 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; SimpleDialect dialect;