From 7d30fbdef0df39d0fdc0ad275faf087d303add0d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 13 Aug 2019 14:40:26 +0200 Subject: [PATCH] Extract side effects into their own struct. --- libyul/Dialect.h | 18 +------ libyul/SideEffects.h | 78 ++++++++++++++++++++++++++++ libyul/backends/evm/EVMDialect.cpp | 58 +++++++++++---------- libyul/backends/evm/EVMDialect.h | 2 + libyul/backends/wasm/WasmDialect.cpp | 28 ++++------ libyul/optimiser/Semantics.cpp | 33 ++---------- libyul/optimiser/Semantics.h | 24 +++------ test/libyul/Parser.cpp | 2 +- 8 files changed, 136 insertions(+), 107 deletions(-) create mode 100644 libyul/SideEffects.h diff --git a/libyul/Dialect.h b/libyul/Dialect.h index bf1a352e1..64377cc37 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include @@ -45,24 +46,9 @@ struct BuiltinFunction YulString name; std::vector parameters; std::vector 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; }; diff --git a/libyul/SideEffects.h b/libyul/SideEffects.h new file mode 100644 index 000000000..fff907bec --- /dev/null +++ b/libyul/SideEffects.h @@ -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 . +*/ + +#pragma once + +#include + +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; + } +}; + +} diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index b4a7cbd49..a9cbd2cbc 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -50,12 +50,8 @@ pair 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 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)> _generateCode ) @@ -89,13 +81,9 @@ pair 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 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 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 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 _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 _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(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) + }; +} diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index bcea185f4..f4c0e4eed 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -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; diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index d06fe2edd..f781292ae 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -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; } diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 5f1846ee3..8fe3cc474 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -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) diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 393e2d114..a924fe5ff 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include @@ -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; }; /** diff --git a/test/libyul/Parser.cpp b/test/libyul/Parser.cpp index e747f66bd..899f40f72 100644 --- a/test/libyul/Parser.cpp +++ b/test/libyul/Parser.cpp @@ -565,7 +565,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis) { return _name == "builtin"_yulstring ? &f : nullptr; } - BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), false, false}; + BuiltinFunction f{"builtin"_yulstring, vector(2), vector(3), {}}; }; SimpleDialect dialect;