diff --git a/Changelog.md b/Changelog.md index 90ebf4bad..6b92a54f2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Compiler Features: * ABI Output: Change sorting order of functions from selector to kind, name. * Optimizer: Add rule that replaces the BYTE opcode by 0 if the first argument is larger than 31. * Yul Optimizer: Take side-effect-freeness of user-defined functions into account. + * Yul Optimizer: Remove redundant mload/sload operations. Bugfixes: diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index 3f3b31f2a..c2042a19c 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -297,7 +297,7 @@ void DataFlowAnalyzer::clearValues(set _variables) void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) { - SideEffectsCollector sideEffects(m_dialect, _block); + SideEffectsCollector sideEffects(m_dialect, _block, &m_functionSideEffects); if (sideEffects.invalidatesStorage()) m_storage.clear(); if (sideEffects.invalidatesMemory()) @@ -306,7 +306,7 @@ void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Block const& _block) void DataFlowAnalyzer::clearKnowledgeIfInvalidated(Expression const& _expr) { - SideEffectsCollector sideEffects(m_dialect, _expr); + SideEffectsCollector sideEffects(m_dialect, _expr, &m_functionSideEffects); if (sideEffects.invalidatesStorage()) m_storage.clear(); if (sideEffects.invalidatesMemory()) diff --git a/libyul/optimiser/LoadResolver.cpp b/libyul/optimiser/LoadResolver.cpp index 2a68149fe..3ff064fd7 100644 --- a/libyul/optimiser/LoadResolver.cpp +++ b/libyul/optimiser/LoadResolver.cpp @@ -23,6 +23,8 @@ #include #include +#include +#include #include using namespace std; @@ -32,35 +34,53 @@ using namespace yul; void LoadResolver::run(Dialect const& _dialect, Block& _ast) { bool containsMSize = MSizeFinder::containsMSize(_dialect, _ast); - LoadResolver{_dialect, !containsMSize}(_ast); + LoadResolver{ + _dialect, + SideEffectsPropagator::sideEffects(_dialect, CallGraphGenerator::callGraph(_ast)), + !containsMSize + }(_ast); } void LoadResolver::visit(Expression& _e) { + DataFlowAnalyzer::visit(_e); + + if (!dynamic_cast(&m_dialect)) + return; + if (_e.type() == typeid(FunctionCall)) { FunctionCall const& funCall = boost::get(_e); if (auto const* builtin = dynamic_cast(m_dialect).builtin(funCall.functionName.name)) - if (!builtin->parameters.empty() && funCall.arguments.at(0).type() == typeid(Identifier)) - { - YulString key = boost::get(funCall.arguments.at(0)).name; - if ( - builtin->instruction == dev::eth::Instruction::SLOAD && - m_storage.values.count(key) - ) - { - _e = Identifier{locationOf(_e), m_storage.values[key]}; - return; - } - else if ( - m_optimizeMLoad && - builtin->instruction == dev::eth::Instruction::MLOAD && - m_memory.values.count(key) - ) - { - _e = Identifier{locationOf(_e), m_memory.values[key]}; - return; - } - } + if (builtin->instruction) + tryResolve(_e, *builtin->instruction, funCall.arguments); + } + else if (_e.type() == typeid(FunctionalInstruction)) + { + FunctionalInstruction const& instruction = boost::get(_e); + tryResolve(_e, instruction.instruction, instruction.arguments); } } + +void LoadResolver::tryResolve( + Expression& _e, + dev::eth::Instruction _instruction, + vector const& _arguments +) +{ + if (_arguments.empty() || _arguments.at(0).type() != typeid(Identifier)) + return; + + YulString key = boost::get(_arguments.at(0)).name; + if ( + _instruction == dev::eth::Instruction::SLOAD && + m_storage.values.count(key) + ) + _e = Identifier{locationOf(_e), m_storage.values[key]}; + else if ( + m_optimizeMLoad && + _instruction == dev::eth::Instruction::MLOAD && + m_memory.values.count(key) + ) + _e = Identifier{locationOf(_e), m_memory.values[key]}; +} diff --git a/libyul/optimiser/LoadResolver.h b/libyul/optimiser/LoadResolver.h index 3db124594..37afc6b6e 100644 --- a/libyul/optimiser/LoadResolver.h +++ b/libyul/optimiser/LoadResolver.h @@ -22,11 +22,13 @@ #pragma once #include +#include namespace yul { struct EVMDialect; +struct BuiltinFunctionForEVM; /** * Optimisation stage that replaces expressions of type ``sload(x)`` and ``mload(x)`` by the value @@ -39,11 +41,16 @@ struct EVMDialect; class LoadResolver: public DataFlowAnalyzer { public: + /// Run the load resolver on the given complete AST. static void run(Dialect const& _dialect, Block& _ast); private: - LoadResolver(Dialect const& _dialect, bool _optimizeMLoad): - DataFlowAnalyzer(_dialect), + LoadResolver( + Dialect const& _dialect, + std::map _functionSideEffects, + bool _optimizeMLoad + ): + DataFlowAnalyzer(_dialect, std::move(_functionSideEffects)), m_optimizeMLoad(_optimizeMLoad) {} @@ -51,6 +58,12 @@ protected: using ASTModifier::visit; void visit(Expression& _e) override; + void tryResolve( + Expression& _e, + dev::eth::Instruction _instruction, + std::vector const& _arguments + ); + bool m_optimizeMLoad = false; }; diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index a4784af66..001193533 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -51,8 +51,12 @@ SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Statement co visit(_statement); } -SideEffectsCollector::SideEffectsCollector(Dialect const& _dialect, Block const& _ast): - SideEffectsCollector(_dialect) +SideEffectsCollector::SideEffectsCollector( + Dialect const& _dialect, + Block const& _ast, + map const* _functionSideEffects +): + SideEffectsCollector(_dialect, _functionSideEffects) { operator()(_ast); } diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 1135b7070..8953ab321 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -47,7 +47,11 @@ public: std::map const* _functionSideEffects = nullptr ); SideEffectsCollector(Dialect const& _dialect, Statement const& _statement); - SideEffectsCollector(Dialect const& _dialect, Block const& _ast); + SideEffectsCollector( + Dialect const& _dialect, + Block const& _ast, + std::map const* _functionSideEffects = nullptr + ); using ASTWalker::operator(); void operator()(FunctionalInstruction const& _functionalInstruction) override; diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index d1677bf53..ac92761d7 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ void OptimiserSuite::run( ExpressionSimplifier::run(_dialect, ast); CommonSubexpressionEliminator::run(_dialect, ast); + LoadResolver::run(_dialect, ast); } { @@ -132,6 +134,7 @@ void OptimiserSuite::run( { // simplify again + LoadResolver::run(_dialect, ast); CommonSubexpressionEliminator::run(_dialect, ast); UnusedPruner::runUntilStabilisedOnFullAST(_dialect, ast, reservedIdentifiers); } @@ -161,6 +164,7 @@ void OptimiserSuite::run( RedundantAssignEliminator::run(_dialect, ast); RedundantAssignEliminator::run(_dialect, ast); CommonSubexpressionEliminator::run(_dialect, ast); + LoadResolver::run(_dialect, ast); } { @@ -176,6 +180,7 @@ void OptimiserSuite::run( SSATransform::run(ast, dispenser); RedundantAssignEliminator::run(_dialect, ast); RedundantAssignEliminator::run(_dialect, ast); + LoadResolver::run(_dialect, ast); ExpressionSimplifier::run(_dialect, ast); StructuralSimplifier{_dialect}(ast); BlockFlattener{}(ast); diff --git a/test/libyul/yulOptimizerTests/fullSuite/medium.yul b/test/libyul/yulOptimizerTests/fullSuite/medium.yul index bebb8c418..a27acf809 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/medium.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/medium.yul @@ -21,11 +21,10 @@ // ---- // { // { -// let _1 := 0x40 -// mstore(_1, add(mload(_1), 0x20)) -// let p := mload(_1) -// mstore(_1, add(p, _1)) -// mstore(add(p, 96), 2) -// mstore(_1, 0x20) +// let _1 := mload(0x40) +// mstore(0x40, add(_1, 0x20)) +// mstore(0x40, add(_1, 96)) +// mstore(add(_1, 128), 2) +// mstore(0x40, 0x20) // } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/storage.yul b/test/libyul/yulOptimizerTests/fullSuite/storage.yul index 9867345fa..4daff379f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/storage.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/storage.yul @@ -10,6 +10,6 @@ // { // sstore(4, 5) // sstore(4, 3) -// sstore(8, sload(4)) +// sstore(8, 3) // } // } diff --git a/test/libyul/yulOptimizerTests/loadResolver/loop.yul b/test/libyul/yulOptimizerTests/loadResolver/loop.yul new file mode 100644 index 000000000..4f7b27734 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/loop.yul @@ -0,0 +1,18 @@ +{ + sstore(0, 123213) + for {let x := 0 let y} lt(x, sload(0)) { + x := add(x, 1)} {y := add(x, y) + } +} +// ==== +// step: loadResolver +// ---- +// { +// let _1 := 123213 +// let _2 := 0 +// sstore(_2, _1) +// let x := _2 +// let y +// for { } lt(x, _1) { x := add(x, 1) } +// { y := add(x, y) } +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul index 65312c8b4..a9487afa7 100644 --- a/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul +++ b/test/libyul/yulOptimizerTests/loadResolver/memory_with_different_kinds_of_invalidation.yul @@ -31,5 +31,5 @@ // mstore8(calldataload(_5), 4) // sstore(_5, mload(_2)) // mstore(_2, _17) -// sstore(_5, mload(_2)) +// sstore(_5, _17) // } diff --git a/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul b/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul new file mode 100644 index 000000000..b07883c3a --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/side_effects_of_user_functions.yul @@ -0,0 +1,28 @@ +{ + function stores() { mstore(0, 1) } + function reads() { sstore(9, mload(7)) } + + mstore(2, 9) + reads() + sstore(0, mload(2)) + stores() + sstore(0, mload(2)) +} +// ==== +// step: loadResolver +// ---- +// { +// function stores() +// { mstore(0, 1) } +// function reads() +// { sstore(9, mload(7)) } +// let _6 := 9 +// let _7 := 2 +// mstore(_7, _6) +// reads() +// let _9 := _6 +// let _10 := 0 +// sstore(_10, _9) +// stores() +// sstore(_10, mload(_7)) +// }