mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #7234 from ethereum/extractSideEffects
[Yul] Extract side-effects into their own struct.
This commit is contained in:
commit
bb4e7de38c
@ -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
78
libyul/SideEffects.h
Normal 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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user