diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index e6d5fc350..6b6b4611d 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -213,20 +213,15 @@ bool SemanticInformation::sideEffectFree(Instruction _instruction) // These are not really functional. assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, ""); - InstructionInfo info = instructionInfo(_instruction); - switch (_instruction) - { - // All the instructions that merely read memory are fine - // even though they are marked "sideEffects" in Instructions.cpp - case Instruction::KECCAK256: - case Instruction::MLOAD: + return !instructionInfo(_instruction).sideEffects; +} + +bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction) +{ + if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD) return true; - default: - break; - } - if (info.sideEffects) - return false; - return true; + else + return sideEffectFree(_instruction); } bool SemanticInformation::invalidatesMemory(Instruction _instruction) diff --git a/libevmasm/SemanticInformation.h b/libevmasm/SemanticInformation.h index 9b256b58d..5f2d3b056 100644 --- a/libevmasm/SemanticInformation.h +++ b/libevmasm/SemanticInformation.h @@ -60,6 +60,12 @@ struct SemanticInformation /// This does not mean that it has to be deterministic or retrieve information from /// somewhere else than purely the values of its arguments. static bool sideEffectFree(Instruction _instruction); + /// @returns true if the instruction can be removed without changing the semantics. + /// This does not mean that it has to be deterministic or retrieve information from + /// somewhere else than purely the values of its arguments. + /// If true, the instruction is still allowed to influence the value returned by the + /// msize instruction. + static bool sideEffectFreeIfNoMSize(Instruction _instruction); /// @returns true if the given instruction modifies memory. static bool invalidatesMemory(Instruction _instruction); /// @returns true if the given instruction modifies storage (even indirectly). diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 462187309..4fc33edbc 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -51,6 +51,11 @@ struct BuiltinFunction 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. + bool isMSize = false; /// If true, can only accept literals as arguments and they cannot be moved to variables. bool literalArguments = false; }; diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 36244d576..388383324 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -52,6 +52,8 @@ pair createEVMFunction( f.returns.resize(info.ret); f.movable = eth::SemanticInformation::movable(_instruction); f.sideEffectFree = eth::SemanticInformation::sideEffectFree(_instruction); + f.sideEffectFreeIfNoMSize = eth::SemanticInformation::sideEffectFreeIfNoMSize(_instruction); + f.isMSize = _instruction == dev::eth::Instruction::MSIZE; f.literalArguments = false; f.instruction = _instruction; f.generateCode = [_instruction]( @@ -73,6 +75,7 @@ pair createFunction( size_t _returns, bool _movable, bool _sideEffectFree, + bool _sideEffectFreeIfNoMSize, bool _literalArguments, std::function)> _generateCode ) @@ -85,6 +88,8 @@ pair createFunction( f.movable = _movable; f.literalArguments = _literalArguments; f.sideEffectFree = _sideEffectFree; + f.sideEffectFreeIfNoMSize = _sideEffectFreeIfNoMSize; + f.isMSize = false; f.instruction = {}; f.generateCode = std::move(_generateCode); return {name, f}; @@ -105,7 +110,7 @@ map createBuiltins(langutil::EVMVersion _evmVe if (_objectAccess) { - builtins.emplace(createFunction("datasize", 1, 1, true, true, true, []( + builtins.emplace(createFunction("datasize", 1, 1, true, true, true, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -126,7 +131,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataSize(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, []( + builtins.emplace(createFunction("dataoffset", 1, 1, true, true, true, true, []( FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, @@ -147,7 +152,7 @@ map createBuiltins(langutil::EVMVersion _evmVe _assembly.appendDataOffset(_context.subIDs.at(dataName)); } })); - builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, []( + builtins.emplace(createFunction("datacopy", 3, 0, false, false, false, false, []( FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, diff --git a/libyul/backends/wasm/WasmDialect.cpp b/libyul/backends/wasm/WasmDialect.cpp index 8b6ff0e73..b41b95d57 100644 --- a/libyul/backends/wasm/WasmDialect.cpp +++ b/libyul/backends/wasm/WasmDialect.cpp @@ -72,5 +72,7 @@ void WasmDialect::addFunction(string _name, size_t _params, size_t _returns) f.returns.resize(_returns); f.movable = false; f.sideEffectFree = false; + f.sideEffectFreeIfNoMSize = false; + f.isMSize = false; f.literalArguments = false; } diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 6291ce688..f9e9e8338 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -33,9 +33,59 @@ using namespace std; using namespace dev; using namespace yul; -MovableChecker::MovableChecker(Dialect const& _dialect): - m_dialect(_dialect) +SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Expression const& _expression): + SideEffectsCollector(_dialect) { + visit(_expression); +} + +SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement const& _statement): + SideEffectsCollector(_dialect) +{ + visit(_statement); +} + +SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Block const& _ast): + SideEffectsCollector(_dialect) +{ + operator()(_ast); +} + +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 (_instr.instruction == eth::Instruction::MSIZE) + m_containsMSize = true; +} + +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->isMSize) + m_containsMSize = true; + } + else + { + m_movable = false; + m_sideEffectFree = false; + m_sideEffectFreeIfNoMSize = false; + } } MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expression): @@ -46,38 +96,10 @@ MovableChecker::MovableChecker(Dialect const& _dialect, Expression const& _expre void MovableChecker::operator()(Identifier const& _identifier) { - ASTWalker::operator()(_identifier); + SideEffectsCollector::operator()(_identifier); m_variableReferences.emplace(_identifier.name); } -void MovableChecker::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; -} - -void MovableChecker::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; - } - else - { - m_movable = false; - m_sideEffectFree = false; - } -} - void MovableChecker::visit(Statement const&) { assertThrow(false, OptimizerException, "Movability for statement requested."); diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index b50169f97..3d9966756 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -29,38 +29,67 @@ namespace yul struct Dialect; /** - * Specific AST walker that determines whether an expression is movable. + * Specific AST walker that determines side-effect free-ness and movability of code. + * Enters into function definitions. */ -class MovableChecker: public ASTWalker +class SideEffectsCollector: public ASTWalker { public: - explicit MovableChecker(Dialect const& _dialect); - MovableChecker(Dialect const& _dialect, Expression const& _expression); + explicit SideEffectsCollector(Dialect const& _dialect): m_dialect(_dialect) {} + SideEffectsCollector(Dialect const& _dialect, Expression const& _expression); + SideEffectsCollector(Dialect const& _dialect, Statement const& _statement); + SideEffectsCollector(Dialect const& _dialect, Block const& _ast); - void operator()(Identifier const& _identifier) override; + using ASTWalker::operator(); void operator()(FunctionalInstruction const& _functionalInstruction) override; void operator()(FunctionCall const& _functionCall) override; - /// Disallow visiting anything apart from Expressions (this throws). - void visit(Statement const&) override; - using ASTWalker::visit; - bool movable() const { return m_movable; } bool sideEffectFree() const { return m_sideEffectFree; } - - std::set const& referencedVariables() const { return m_variableReferences; } + bool sideEffectFreeIfNoMSize() const { return m_sideEffectFreeIfNoMSize; } + bool containsMSize() const { return m_containsMSize; } private: Dialect const& m_dialect; - /// Which variables the current expression references. - std::set m_variableReferences; /// 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; + /// Does the current code contain the MSize operation? + /// Note that this is a purely syntactic property meaning that even if this is false, + /// the code can still contain calls to functions that contain the msize instruction. + bool m_containsMSize = false; }; +/** + * Specific AST walker that determines whether an expression is movable + * and collects the referenced variables. + * Can only be used on expressions. + */ +class MovableChecker: public SideEffectsCollector +{ +public: + explicit MovableChecker(Dialect const& _dialect): SideEffectsCollector(_dialect) {} + MovableChecker(Dialect const& _dialect, Expression const& _expression); + + void operator()(Identifier const& _identifier) override; + + /// Disallow visiting anything apart from Expressions (this throws). + void visit(Statement const&) override; + using ASTWalker::visit; + + std::set const& referencedVariables() const { return m_variableReferences; } + +private: + /// Which variables the current expression references. + std::set m_variableReferences; +}; + + /** * Helper class to find "irregular" control flow. * This includes termination, break and continue.