diff --git a/.circleci/README.md b/.circleci/README.md index c754b5df0..466aaa76a 100644 --- a/.circleci/README.md +++ b/.circleci/README.md @@ -4,7 +4,7 @@ The docker images are build locally on the developer machine: -```!sh +```sh cd .circleci/docker/ docker build -t ethereum/solidity-buildpack-deps:ubuntu1904- -f Dockerfile.ubuntu1904 . @@ -21,7 +21,7 @@ where the image tag reflects the target OS and revision to build Solidity and ru ### Testing docker images locally -```!sh +```sh cd solidity # Mounts your local solidity directory in docker container for testing docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904- /bin/bash diff --git a/Changelog.md b/Changelog.md index 5b866f9ec..2f9d1b5f0 100644 --- a/Changelog.md +++ b/Changelog.md @@ -47,6 +47,7 @@ Language Features: Compiler Features: * Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable. + * Yul Optimizer: Perform loop-invariant code motion. Bugfixes: diff --git a/README.md b/README.md index 0af8dd905..e7e5bdb7c 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Instructions about how to build and install the Solidity compiler can be found i A "Hello World" program in Solidity is of even less use than in other languages, but still: -``` +```solidity pragma solidity ^0.5.0; contract HelloWorld { diff --git a/docs/control-structures.rst b/docs/control-structures.rst index ea2d8f4b7..1f2305407 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -208,7 +208,7 @@ The evaluation order of expressions is not specified (more formally, the order in which the children of one node in the expression tree are evaluated is not specified, but they are of course evaluated before the node itself). It is only guaranteed that statements are executed in order and short-circuiting for -boolean expressions is done. See :ref:`order` for more information. +boolean expressions is done. .. index:: ! assignment diff --git a/libsolidity/formal/SMTEncoder.cpp b/libsolidity/formal/SMTEncoder.cpp index b0a5920c0..d253087bd 100644 --- a/libsolidity/formal/SMTEncoder.cpp +++ b/libsolidity/formal/SMTEncoder.cpp @@ -538,6 +538,15 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall) } } +bool SMTEncoder::visit(ModifierInvocation const& _node) +{ + if (auto const* args = _node.arguments()) + for (auto const& arg: *args) + if (arg) + arg->accept(*this); + return false; +} + void SMTEncoder::initContract(ContractDefinition const& _contract) { solAssert(m_currentContract == nullptr, ""); @@ -606,9 +615,7 @@ void SMTEncoder::endVisit(Identifier const& _identifier) defineExpr(_identifier, m_context.thisAddress()); m_uninterpretedTerms.insert(&_identifier); } - else if ( - _identifier.annotation().type->category() != Type::Category::Modifier - ) + else createExpr(_identifier); } diff --git a/libsolidity/formal/SMTEncoder.h b/libsolidity/formal/SMTEncoder.h index 843f771e4..078cc385c 100644 --- a/libsolidity/formal/SMTEncoder.h +++ b/libsolidity/formal/SMTEncoder.h @@ -82,6 +82,7 @@ protected: bool visit(BinaryOperation const& _node) override; void endVisit(BinaryOperation const& _node) override; void endVisit(FunctionCall const& _node) override; + bool visit(ModifierInvocation const& _node) override; void endVisit(Identifier const& _node) override; void endVisit(ElementaryTypeNameExpression const& _node) override; void endVisit(Literal const& _node) override; diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 3a748f0da..6292f07f0 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -112,6 +112,8 @@ add_library(yul optimiser/KnowledgeBase.h optimiser/LoadResolver.cpp optimiser/LoadResolver.h + optimiser/LoopInvariantCodeMotion.cpp + optimiser/LoopInvariantCodeMotion.h optimiser/MainFunction.cpp optimiser/MainFunction.h optimiser/Metrics.cpp diff --git a/libyul/optimiser/LoopInvariantCodeMotion.cpp b/libyul/optimiser/LoopInvariantCodeMotion.cpp new file mode 100644 index 000000000..00375a71d --- /dev/null +++ b/libyul/optimiser/LoopInvariantCodeMotion.cpp @@ -0,0 +1,115 @@ +/* + 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 . +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace dev; +using namespace yul; + +void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast) +{ + map functionSideEffects = + SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)); + + set ssaVars = SSAValueTracker::ssaVariables(_ast); + LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast); +} + +void LoopInvariantCodeMotion::operator()(Block& _block) +{ + iterateReplacing( + _block.statements, + [&](Statement& _s) -> optional> + { + visit(_s); + if (holds_alternative(_s)) + return rewriteLoop(get(_s)); + else + return {}; + } + ); +} + +bool LoopInvariantCodeMotion::canBePromoted( + VariableDeclaration const& _varDecl, + set const& _varsDefinedInCurrentScope +) const +{ + // A declaration can be promoted iff + // 1. Its LHS is a SSA variable + // 2. Its RHS only references SSA variables declared outside of the current scope + // 3. Its RHS is movable + + for (auto const& var: _varDecl.variables) + if (!m_ssaVariables.count(var.name)) + return false; + if (_varDecl.value) + { + for (auto const& ref: ReferencesCounter::countReferences(*_varDecl.value, ReferencesCounter::OnlyVariables)) + if (_varsDefinedInCurrentScope.count(ref.first) || !m_ssaVariables.count(ref.first)) + return false; + if (!SideEffectsCollector{m_dialect, *_varDecl.value, &m_functionSideEffects}.movable()) + return false; + } + return true; +} + +optional> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for) +{ + assertThrow(_for.pre.statements.empty(), OptimizerException, ""); + vector replacement; + for (Block* block: {&_for.post, &_for.body}) + { + set varsDefinedInScope; + iterateReplacing( + block->statements, + [&](Statement& _s) -> optional> + { + if (holds_alternative(_s)) + { + VariableDeclaration const& varDecl = std::get(_s); + if (canBePromoted(varDecl, varsDefinedInScope)) + { + replacement.emplace_back(std::move(_s)); + // Do not add the variables declared here to varsDefinedInScope because we are moving them. + return vector{}; + } + for (auto const& var: varDecl.variables) + varsDefinedInScope.insert(var.name); + } + return {}; + } + ); + } + if (replacement.empty()) + return {}; + else + { + replacement.emplace_back(std::move(_for)); + return { std::move(replacement) }; + } +} diff --git a/libyul/optimiser/LoopInvariantCodeMotion.h b/libyul/optimiser/LoopInvariantCodeMotion.h new file mode 100644 index 000000000..f5f250515 --- /dev/null +++ b/libyul/optimiser/LoopInvariantCodeMotion.h @@ -0,0 +1,67 @@ +/* + 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 +#include +#include + +namespace yul +{ + +/** + * Loop-invariant code motion. + * + * This optimization moves movable SSA variable declarations outside the loop. + * + * Only statements at the top level in a loop's body or post block are considered, i.e variable + * declarations inside conditional branches will not be moved out of the loop. + * + * Requirements: + * - The Disambiguator, ForLoopInitRewriter and FunctionHoister must be run upfront. + * - Expression splitter and SSA transform should be run upfront to obtain better result. + */ + +class LoopInvariantCodeMotion: public ASTModifier +{ +public: + static constexpr char const* name{"LoopInvariantCodeMotion"}; + static void run(OptimiserStepContext& _context, Block& _ast); + + void operator()(Block& _block) override; + +private: + explicit LoopInvariantCodeMotion( + Dialect const& _dialect, + std::set const& _ssaVariables, + std::map const& _functionSideEffects + ): + m_dialect(_dialect), + m_ssaVariables(_ssaVariables), + m_functionSideEffects(_functionSideEffects) + { } + + /// @returns true if the given variable declaration can be moved to in front of the loop. + bool canBePromoted(VariableDeclaration const& _varDecl, std::set const& _varsDefinedInCurrentScope) const; + std::optional> rewriteLoop(ForLoop& _for); + + Dialect const& m_dialect; + std::set const& m_ssaVariables; + std::map const& m_functionSideEffects; +}; + +} diff --git a/libyul/optimiser/NameCollector.cpp b/libyul/optimiser/NameCollector.cpp index 04631a86a..dab2f290a 100644 --- a/libyul/optimiser/NameCollector.cpp +++ b/libyul/optimiser/NameCollector.cpp @@ -49,27 +49,28 @@ void ReferencesCounter::operator()(Identifier const& _identifier) void ReferencesCounter::operator()(FunctionCall const& _funCall) { - ++m_references[_funCall.functionName.name]; + if (m_countWhat == VariablesAndFunctions) + ++m_references[_funCall.functionName.name]; ASTWalker::operator()(_funCall); } -map ReferencesCounter::countReferences(Block const& _block) +map ReferencesCounter::countReferences(Block const& _block, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter(_block); return counter.references(); } -map ReferencesCounter::countReferences(FunctionDefinition const& _function) +map ReferencesCounter::countReferences(FunctionDefinition const& _function, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter(_function); return counter.references(); } -map ReferencesCounter::countReferences(Expression const& _expression) +map ReferencesCounter::countReferences(Expression const& _expression, CountWhat _countWhat) { - ReferencesCounter counter; + ReferencesCounter counter(_countWhat); counter.visit(_expression); return counter.references(); } diff --git a/libyul/optimiser/NameCollector.h b/libyul/optimiser/NameCollector.h index b6b4e1e6c..46debdcba 100644 --- a/libyul/optimiser/NameCollector.h +++ b/libyul/optimiser/NameCollector.h @@ -54,16 +54,23 @@ private: class ReferencesCounter: public ASTWalker { public: + enum CountWhat { VariablesAndFunctions, OnlyVariables }; + + explicit ReferencesCounter(CountWhat _countWhat = VariablesAndFunctions): + m_countWhat(_countWhat) + {} + using ASTWalker::operator (); virtual void operator()(Identifier const& _identifier); virtual void operator()(FunctionCall const& _funCall); - static std::map countReferences(Block const& _block); - static std::map countReferences(FunctionDefinition const& _function); - static std::map countReferences(Expression const& _expression); + static std::map countReferences(Block const& _block, CountWhat _countWhat = VariablesAndFunctions); + static std::map countReferences(FunctionDefinition const& _function, CountWhat _countWhat = VariablesAndFunctions); + static std::map countReferences(Expression const& _expression, CountWhat _countWhat = VariablesAndFunctions); std::map const& references() const { return m_references; } private: + CountWhat m_countWhat = CountWhat::VariablesAndFunctions; std::map m_references; }; diff --git a/libyul/optimiser/SSAValueTracker.cpp b/libyul/optimiser/SSAValueTracker.cpp index d4feacbd9..3b599644c 100644 --- a/libyul/optimiser/SSAValueTracker.cpp +++ b/libyul/optimiser/SSAValueTracker.cpp @@ -49,6 +49,16 @@ void SSAValueTracker::operator()(VariableDeclaration const& _varDecl) setValue(_varDecl.variables.front().name, _varDecl.value.get()); } +set SSAValueTracker::ssaVariables(Block const& _ast) +{ + SSAValueTracker t; + t(_ast); + set ssaVars; + for (auto const& value: t.values()) + ssaVars.insert(value.first); + return ssaVars; +} + void SSAValueTracker::setValue(YulString _name, Expression const* _value) { assertThrow( diff --git a/libyul/optimiser/SSAValueTracker.h b/libyul/optimiser/SSAValueTracker.h index 1062ca8e7..7eac6b6a4 100644 --- a/libyul/optimiser/SSAValueTracker.h +++ b/libyul/optimiser/SSAValueTracker.h @@ -49,6 +49,8 @@ public: std::map const& values() const { return m_values; } Expression const* value(YulString _name) const { return m_values.at(_name); } + static std::set ssaVariables(Block const& _ast); + private: void setValue(YulString _name, Expression const* _value); diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index c7177e655..33d9a507a 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -129,7 +130,8 @@ void OptimiserSuite::run( RedundantAssignEliminator::name, ExpressionSimplifier::name, CommonSubexpressionEliminator::name, - LoadResolver::name + LoadResolver::name, + LoopInvariantCodeMotion::name }, ast); } @@ -357,6 +359,7 @@ map> const& OptimiserSuite::allSteps() FunctionHoister, LiteralRematerialiser, LoadResolver, + LoopInvariantCodeMotion, RedundantAssignEliminator, Rematerialiser, SSAReverser, diff --git a/scripts/codespell_whitelist.txt b/scripts/codespell_whitelist.txt index bbfe3e05c..0409dc1a2 100644 --- a/scripts/codespell_whitelist.txt +++ b/scripts/codespell_whitelist.txt @@ -10,3 +10,4 @@ fo compilability errorstring hist +otion diff --git a/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol b/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol new file mode 100644 index 000000000..8e5c57cf6 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/functions/constructor_base_basic.sol @@ -0,0 +1,17 @@ +pragma experimental SMTChecker; + +contract A { + uint x; + constructor() public { + x = 2; + } +} + +contract B is A { + constructor() A() public { + x = 3; + } +} +// ---- +// Warning: (56-90): Assertion checker does not yet support constructors. +// Warning: (113-151): Assertion checker does not yet support constructors. diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 76e9af3df..221f3414d 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -279,6 +280,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line ExpressionJoiner::run(*m_context, *m_ast); ExpressionJoiner::run(*m_context, *m_ast); } + else if (m_optimizerStep == "loopInvariantCodeMotion") + { + disambiguate(); + ForLoopInitRewriter::run(*m_context, *m_ast); + LoopInvariantCodeMotion::run(*m_context, *m_ast); + } else if (m_optimizerStep == "controlFlowSimplifier") { disambiguate(); diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index 4838eab02..0d37cf1b9 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -483,7 +483,7 @@ // let _5 := 0xffffffffffffffff // if gt(offset, _5) { revert(_1, _1) } // let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_4, offset), _3) -// let offset_1 := calldataload(add(_4, 96)) +// let offset_1 := calldataload(add(_4, 0x60)) // if gt(offset_1, _5) { revert(_1, _1) } // let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_4, offset_1), _3) // sstore(calldataload(_4), calldataload(add(_4, 0x20))) diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 0b82de7e9..35e1bb7b3 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -311,7 +311,7 @@ // } // b := add(b, _5) // } -// if lt(m, n) { validatePairing(0x64) } +// if lt(m, n) { validatePairing(100) } // if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), _2), challenge)) // { // mstore(0, 404) diff --git a/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul b/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul index 19816b445..d958a318f 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/clear_after_if_continue.yul @@ -12,7 +12,7 @@ // { // { // let y := mload(0x20) -// for { } and(y, 8) { if y { revert(0, 0) } } +// for { } iszero(iszero(and(y, 8))) { if y { revert(0, 0) } } // { // if y { continue } // sstore(1, 0) diff --git a/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul b/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul new file mode 100644 index 000000000..f570d2ecf --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/loopInvariantCodeMotion.yul @@ -0,0 +1,34 @@ +{ + sstore(0, array_sum(calldataload(0))) + + function array_sum(x) -> sum { + let length := calldataload(x) + for { let i := 0 } lt(i, length) { i := add(i, 1) } { + sum := add(sum, array_load(x, i)) + } + } + function array_load(x, i) -> v { + let len := calldataload(x) + if iszero(lt(i, len)) { revert(0, 0) } + let data := add(x, 0x20) + v := calldataload(add(data, mul(i, 0x20))) + // this is just to have some additional code that + // can be moved out of the loop. + v := add(v, calldataload(7)) + } +} +// ==== +// step: fullSuite +// ---- +// { +// { +// let _1 := calldataload(0) +// let sum := 0 +// let i := sum +// for { } lt(i, calldataload(_1)) { i := add(i, 1) } +// { +// sum := add(sum, add(calldataload(add(add(_1, mul(i, 0x20)), 0x20)), calldataload(7))) +// } +// sstore(0, sum) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul new file mode 100644 index 000000000..82ea2bf78 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/dependOnVarInLoop.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let c := mload(3) // c cannot be moved because non-movable + let not_inv := add(b, c) // no_inv cannot be moved because its value depends on c + a := add(a, 1) + mstore(a, not_inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let c := mload(3) +// let not_inv := add(b, c) +// a := add(a, 1) +// mstore(a, not_inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul new file mode 100644 index 000000000..c7e39cc70 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/multi.yul @@ -0,0 +1,26 @@ +{ + let b := 1 + // tests if c, d, and inv can be moved outside in single pass + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let c := b + let d := mul(c, 2) + let inv := add(c, d) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let c := b +// let d := mul(c, 2) +// let inv := add(c, d) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul new file mode 100644 index 000000000..86cf1274e --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/non-ssavar.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let not_inv := add(b, 42) + not_inv := add(not_inv, 1) + a := add(a, 1) + mstore(a, not_inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let not_inv := add(b, 42) +// not_inv := add(not_inv, 1) +// a := add(a, 1) +// mstore(a, not_inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul new file mode 100644 index 000000000..787c1756b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/nonMovable.yul @@ -0,0 +1,21 @@ +{ + let b := 0 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := mload(b) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 0 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let inv := mload(b) +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul new file mode 100644 index 000000000..a489b134b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/recursive.yul @@ -0,0 +1,25 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + for { let a2 := 1 } iszero(eq(a2, 10)) { a2 := add(a2, 1) } { + let inv := add(b, 42) + mstore(a, inv) + } + a := add(a, 1) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let a2 := 1 +// for { } iszero(eq(a2, 10)) { a2 := add(a2, 1) } +// { mstore(a, inv) } +// a := add(a, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul new file mode 100644 index 000000000..970fec59f --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple.yul @@ -0,0 +1,21 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + a := add(a, 1) + mstore(a, inv) + } +} +// ==== +// step: loopInvariantCodeMotion +// ---- +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(a, 1) +// mstore(a, inv) +// } +// } diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 3ad39590c..f7318f45d 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include @@ -141,7 +142,7 @@ public: cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl; cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl; cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/(L)oad resolver/" << endl; - cout << " (C)onditional simplifier?" << endl; + cout << " (C)onditional simplifier/loop-invariant code (M)otion?" << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -236,6 +237,9 @@ public: case 'L': LoadResolver::run(context, *m_ast); break; + case 'M': + LoopInvariantCodeMotion::run(context, *m_ast); + break; default: cout << "Unknown option." << endl; }