diff --git a/Changelog.md b/Changelog.md index 4b47af706..59ce52435 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Language Features: Compiler Features: * Standard JSON Interface: Do not run EVM bytecode code generation, if only Yul IR or EWasm output is requested. * Yul: Report error when using non-string literals for ``datasize()``, ``dataoffset()``, ``linkersymbol()``, ``loadimmutable()``, ``setimmutable()``. + * Yul Optimizer: LoopInvariantCodeMotion can move reading operations outside for-loops as long as the affected area is not modified inside the loop. Bugfixes: * Optimizer: Keep side-effects of ``x`` in ``byte(a, shr(b, x))`` even if the constants ``a`` and ``b`` would make the expression zero unconditionally. This optimizer rule is very hard if not impossible to trigger in a way that it can result in invalid code, though. diff --git a/libyul/optimiser/LoopInvariantCodeMotion.cpp b/libyul/optimiser/LoopInvariantCodeMotion.cpp index 9a81e5fc8..3cc8397a9 100644 --- a/libyul/optimiser/LoopInvariantCodeMotion.cpp +++ b/libyul/optimiser/LoopInvariantCodeMotion.cpp @@ -35,9 +35,9 @@ void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast) { map functionSideEffects = SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast)); - + bool containsMSize = MSizeFinder::containsMSize(_context.dialect, _ast); set ssaVars = SSAValueTracker::ssaVariables(_ast); - LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast); + LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects, containsMSize}(_ast); } void LoopInvariantCodeMotion::operator()(Block& _block) @@ -57,7 +57,8 @@ void LoopInvariantCodeMotion::operator()(Block& _block) bool LoopInvariantCodeMotion::canBePromoted( VariableDeclaration const& _varDecl, - set const& _varsDefinedInCurrentScope + set const& _varsDefinedInCurrentScope, + SideEffects const& _forLoopSideEffects ) const { // A declaration can be promoted iff @@ -73,7 +74,8 @@ bool LoopInvariantCodeMotion::canBePromoted( 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()) + SideEffectsCollector sideEffects{m_dialect, *_varDecl.value, &m_functionSideEffects}; + if (!sideEffects.movableRelativeTo(_forLoopSideEffects, m_containsMSize)) return false; } return true; @@ -82,6 +84,10 @@ bool LoopInvariantCodeMotion::canBePromoted( optional> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for) { assertThrow(_for.pre.statements.empty(), OptimizerException, ""); + + auto forLoopSideEffects = + SideEffectsCollector{m_dialect, _for, &m_functionSideEffects}.sideEffects(); + vector replacement; for (Block* block: {&_for.post, &_for.body}) { @@ -93,7 +99,7 @@ optional> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for) if (holds_alternative(_s)) { VariableDeclaration const& varDecl = std::get(_s); - if (canBePromoted(varDecl, varsDefinedInScope)) + if (canBePromoted(varDecl, varsDefinedInScope, forLoopSideEffects)) { replacement.emplace_back(std::move(_s)); // Do not add the variables declared here to varsDefinedInScope because we are moving them. diff --git a/libyul/optimiser/LoopInvariantCodeMotion.h b/libyul/optimiser/LoopInvariantCodeMotion.h index a3bf1784f..1b2647924 100644 --- a/libyul/optimiser/LoopInvariantCodeMotion.h +++ b/libyul/optimiser/LoopInvariantCodeMotion.h @@ -49,17 +49,24 @@ private: explicit LoopInvariantCodeMotion( Dialect const& _dialect, std::set const& _ssaVariables, - std::map const& _functionSideEffects + std::map const& _functionSideEffects, + bool _containsMSize ): + m_containsMSize(_containsMSize), 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; + bool canBePromoted( + VariableDeclaration const& _varDecl, + std::set const& _varsDefinedInCurrentScope, + SideEffects const& _forLoopSideEffects + ) const; std::optional> rewriteLoop(ForLoop& _for); + bool m_containsMSize = true; Dialect const& m_dialect; std::set const& m_ssaVariables; std::map const& m_functionSideEffects; diff --git a/libyul/optimiser/Semantics.cpp b/libyul/optimiser/Semantics.cpp index 1ceeec3e2..735ed13d0 100644 --- a/libyul/optimiser/Semantics.cpp +++ b/libyul/optimiser/Semantics.cpp @@ -62,6 +62,16 @@ SideEffectsCollector::SideEffectsCollector( operator()(_ast); } +SideEffectsCollector::SideEffectsCollector( + Dialect const& _dialect, + ForLoop const& _ast, + map const* _functionSideEffects +): + SideEffectsCollector(_dialect, _functionSideEffects) +{ + operator()(_ast); +} + void SideEffectsCollector::operator()(FunctionCall const& _functionCall) { ASTWalker::operator()(_functionCall); diff --git a/libyul/optimiser/Semantics.h b/libyul/optimiser/Semantics.h index 1ff7e0d7e..70ec119ac 100644 --- a/libyul/optimiser/Semantics.h +++ b/libyul/optimiser/Semantics.h @@ -54,11 +54,48 @@ public: Block const& _ast, std::map const* _functionSideEffects = nullptr ); + SideEffectsCollector( + Dialect const& _dialect, + ForLoop const& _ast, + std::map const* _functionSideEffects = nullptr + ); using ASTWalker::operator(); void operator()(FunctionCall const& _functionCall) override; bool movable() const { return m_sideEffects.movable; } + + bool movableRelativeTo(SideEffects const& _other, bool _codeContainsMSize) + { + if (!m_sideEffects.cannotLoop) + return false; + + if (m_sideEffects.movable) + return true; + + if ( + !m_sideEffects.movableApartFromEffects || + m_sideEffects.storage == SideEffects::Write || + m_sideEffects.otherState == SideEffects::Write || + m_sideEffects.memory == SideEffects::Write + ) + return false; + + if (m_sideEffects.otherState == SideEffects::Read) + if (_other.otherState == SideEffects::Write) + return false; + + if (m_sideEffects.storage == SideEffects::Read) + if (_other.storage == SideEffects::Write) + return false; + + if (m_sideEffects.memory == SideEffects::Read) + if (_codeContainsMSize || _other.memory == SideEffects::Write) + return false; + + return true; + } + bool canBeRemoved(bool _allowMSizeModification = false) const { if (_allowMSizeModification) @@ -70,6 +107,7 @@ public: bool invalidatesStorage() const { return m_sideEffects.storage == SideEffects::Write; } bool invalidatesMemory() const { return m_sideEffects.memory == SideEffects::Write; } + SideEffects sideEffects() { return m_sideEffects; } private: Dialect const& m_dialect; diff --git a/test/cmdlineTests/optimizer_array_sload/args b/test/cmdlineTests/optimizer_array_sload/args new file mode 100644 index 000000000..8539e69f5 --- /dev/null +++ b/test/cmdlineTests/optimizer_array_sload/args @@ -0,0 +1 @@ +--optimize --ir-optimized --metadata-hash none diff --git a/test/cmdlineTests/optimizer_array_sload/input.sol b/test/cmdlineTests/optimizer_array_sload/input.sol new file mode 100644 index 000000000..1ad596601 --- /dev/null +++ b/test/cmdlineTests/optimizer_array_sload/input.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.0; + +contract Arraysum { + uint256[] values; + + function sumArray() public view returns(uint) { + uint sum = 0; + // The optimizer should read the length of the array only once, because + // LoopInvariantCodeMotion can move the `sload` corresponding to the length outside of the + // loop. + for(uint i = 0; i < values.length; i++) + sum += values[i]; + } +} diff --git a/test/cmdlineTests/optimizer_array_sload/output b/test/cmdlineTests/optimizer_array_sload/output new file mode 100644 index 000000000..ea4ae03ff --- /dev/null +++ b/test/cmdlineTests/optimizer_array_sload/output @@ -0,0 +1,83 @@ +Optimized IR: +/******************************************************* + * WARNING * + * Solidity to Yul compilation is still EXPERIMENTAL * + * It can result in LOSS OF FUNDS or worse * + * !USE AT YOUR OWN RISK! * + *******************************************************/ + +object "Arraysum_33" { + code { + { + mstore(64, 128) + if callvalue() { revert(0, 0) } + let _1 := datasize("Arraysum_33_deployed") + codecopy(0, dataoffset("Arraysum_33_deployed"), _1) + return(0, _1) + } + } + object "Arraysum_33_deployed" { + code { + { + mstore(64, 128) + if iszero(lt(calldatasize(), 4)) + { + let _1 := 0 + if eq(0x81d73423, shr(224, calldataload(_1))) + { + if callvalue() { revert(_1, _1) } + if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) } + let vloc_sum := _1 + let vloc_i := _1 + let _2 := sload(_1) + for { } + lt(vloc_i, _2) + { + vloc_i := increment_t_uint256(vloc_i) + } + { + let _3, _4 := storage_array_index_access_t_array$_t_uint256_$dyn_storage(_1, vloc_i) + vloc_sum := checked_add_t_uint256(vloc_sum, extract_from_storage_value_dynamict_uint256(sload(_3), _4)) + } + let memPos := allocateMemory(_1) + return(memPos, sub(abi_encode_tuple_t_uint256__to_t_uint256__fromStack(memPos, _1), memPos)) + } + } + revert(0, 0) + } + function abi_encode_tuple_t_uint256__to_t_uint256__fromStack(headStart, value0) -> tail + { + tail := add(headStart, 32) + mstore(headStart, value0) + } + function allocateMemory(size) -> memPtr + { + memPtr := mload(64) + let newFreePtr := add(memPtr, size) + if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) } + mstore(64, newFreePtr) + } + function checked_add_t_uint256(x, y) -> sum + { + if gt(x, not(y)) { revert(sum, sum) } + sum := add(x, y) + } + function extract_from_storage_value_dynamict_uint256(slot_value, offset) -> value + { + value := shr(mul(offset, 8), slot_value) + } + function increment_t_uint256(value) -> ret + { + if gt(value, not(1)) { revert(ret, ret) } + ret := add(value, 1) + } + function storage_array_index_access_t_array$_t_uint256_$dyn_storage(array, index) -> slot, offset + { + if iszero(lt(index, sload(array))) { invalid() } + mstore(slot, array) + slot := add(keccak256(slot, 0x20), index) + offset := offset + } + } + } +} diff --git a/test/cmdlineTests/optimizer_user_yul/output b/test/cmdlineTests/optimizer_user_yul/output index 34f35c643..9a4301aa4 100644 --- a/test/cmdlineTests/optimizer_user_yul/output +++ b/test/cmdlineTests/optimizer_user_yul/output @@ -48,19 +48,22 @@ tag_5: 0x02 /* "optimizer_user_yul/input.sol":359:371 sstore(2, 3) */ sstore - /* "optimizer_user_yul/input.sol":376:509 for { } sload(5) { } {... */ -tag_6: /* "optimizer_user_yul/input.sol":390:391 5 */ 0x05 /* "optimizer_user_yul/input.sol":384:392 sload(5) */ sload - tag_9 - jumpi - jump(tag_8) -tag_9: + iszero /* "optimizer_user_yul/input.sol":376:509 for { } sload(5) { } {... */ +tag_6: + /* "optimizer_user_yul/input.sol":384:392 sload(5) */ + dup1 + /* "optimizer_user_yul/input.sol":376:509 for { } sload(5) { } {... */ + tag_8 + jumpi jump(tag_6) tag_8: + /* "optimizer_user_yul/input.sol":380:383 { } */ + pop /* "optimizer_user_yul/input.sol":340:513 {... */ pop /* "optimizer_user_yul/input.sol":60:518 contract C... */ diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/complex_move.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/complex_move.yul new file mode 100644 index 000000000..3c11ca65f --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/complex_move.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := extcodesize(keccak256(mul(mload(inv), 3), 32)) + a := add(x, 1) + sstore(a, inv) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// let x := extcodesize(keccak256(mul(mload(inv), 3), 32)) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(x, 1) +// sstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/create_sload.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/create_sload.yul new file mode 100644 index 000000000..f48cb78f1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/create_sload.yul @@ -0,0 +1,30 @@ +{ + function g() -> x { x := create(100, 0, 32) } + function f() -> x { x := mload(0) } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + // cannot be moved because of the create call in g() + let q := sload(5) + let r := g() + // This can be moved + let z := f() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function g() -> x +// { x := create(100, 0, 32) } +// function f() -> x_1 +// { x_1 := mload(0) } +// let b := 1 +// let a := 1 +// let z := f() +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let q := sload(5) +// let r := g() +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_memory_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_memory_function.yul new file mode 100644 index 000000000..a4506a8e5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_memory_function.yul @@ -0,0 +1,24 @@ +{ + function g() -> x { x := add(sload(mload(x)), 1) } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := mload(g()) + let s := keccak256(g(), 32) + let q := g() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function g() -> x +// { x := add(sload(mload(x)), 1) } +// let b := 1 +// let a := 1 +// let t := mload(g()) +// let s := keccak256(g(), 32) +// let q := g() +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_state_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_state_function.yul new file mode 100644 index 000000000..a5117cb61 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_state_function.yul @@ -0,0 +1,25 @@ +{ + function f() -> x { x := mload(g()) } + function g() -> x { x := add(sload(x), 1) } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := extcodesize(f()) + let q := g() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := mload(g()) } +// function g() -> x_1 +// { x_1 := add(sload(x_1), 1) } +// let b := 1 +// let a := 1 +// let t := extcodesize(f()) +// let q := g() +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_storage_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_storage_function.yul new file mode 100644 index 000000000..7790c0e6a --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/move_storage_function.yul @@ -0,0 +1,25 @@ +{ + function f() -> x { x := g() } + function g() -> x { x := add(x, 1) } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := sload(f()) + let q := g() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { x_1 := add(x_1, 1) } +// let b := 1 +// let a := 1 +// let t := sload(f()) +// let q := g() +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_immovables.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_immovables.yul new file mode 100644 index 000000000..b08899613 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_immovables.yul @@ -0,0 +1,29 @@ +{ + let a := 1 + function f() -> x {invalid()} + function g() -> y {return(0, 0)} + for { let i := 1 } iszero(eq(i, 10)) { a := add(i, 1) } { + let b := f() + let c := gas() + let d := g() + let e := sload(g()) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let a := 1 +// function f() -> x +// { invalid() } +// function g() -> y +// { return(0, 0) } +// let i := 1 +// for { } iszero(eq(i, 10)) { a := add(i, 1) } +// { +// let b := f() +// let c := gas() +// let d := g() +// let e := sload(g()) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory.yul new file mode 100644 index 000000000..942a3c580 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory.yul @@ -0,0 +1,38 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := keccak256(inv, 32) + a := add(x, 1) + mstore(a, inv) + } + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + // mload prevents moving of extcodesize + let x := extcodesize(mload(mul(inv, 3))) + a := add(x, 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) } +// { +// let x := keccak256(inv, 32) +// a := add(x, 1) +// mstore(a, inv) +// } +// let a_1 := 1 +// let inv_2 := add(b, 42) +// for { } iszero(eq(a_1, 10)) { a_1 := add(a_1, 1) } +// { +// let x_3 := extcodesize(mload(mul(inv_2, 3))) +// a_1 := add(x_3, 1) +// mstore(a_1, inv_2) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_loop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_loop.yul new file mode 100644 index 000000000..1ebef6f9c --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_loop.yul @@ -0,0 +1,29 @@ +{ + function f() -> x { x := g() } + function g() -> x { for {} 1 {} {} } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := mload(f()) + let q := g() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// for { } 1 { } +// { } +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := mload(f()) +// let q := g() +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_msize.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_msize.yul new file mode 100644 index 000000000..2033a9be9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_memory_msize.yul @@ -0,0 +1,35 @@ +{ + let b := 1 + let c := msize() // prevents moving keccak and mload + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := keccak256(inv, 32) + a := add(x, 1) + } + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := extcodesize(mload(mul(inv, 3))) + a := add(x, 1) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let c := msize() +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let x := keccak256(inv, 32) +// a := add(x, 1) +// } +// let a_1 := 1 +// let inv_2 := add(b, 42) +// for { } iszero(eq(a_1, 10)) { a_1 := add(a_1, 1) } +// { +// let x_3 := extcodesize(mload(mul(inv_2, 3))) +// a_1 := add(x_3, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state.yul new file mode 100644 index 000000000..fa6c985b4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state.yul @@ -0,0 +1,62 @@ +{ + let b := 1 + // invalidates state in post + for { let a := 1 } iszero(eq(a, 10)) {pop(call(2, 0x01, 2, 0x00, 32, 0x010, 32))} { + let inv := add(b, 42) + let x := extcodesize(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } + + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + pop(create(0x08, 0x00, 0x02)) // invalidates state + let x := extcodesize(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } + + // invalidates state in loop-condition + for { let a := 1 } iszero(create(0x08, 0x00, 0x02)) { a := add(a, 1)} { + let inv := add(b, 42) + let x := extcodesize(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } + +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } +// iszero(eq(a, 10)) +// { +// pop(call(2, 0x01, 2, 0x00, 32, 0x010, 32)) +// } +// { +// let x := extcodesize(mul(inv, 3)) +// a := add(x, 1) +// mstore(a, inv) +// } +// let a_1 := 1 +// let inv_2 := add(b, 42) +// for { } iszero(eq(a_1, 10)) { a_1 := add(a_1, 1) } +// { +// pop(create(0x08, 0x00, 0x02)) +// let x_3 := extcodesize(mul(inv_2, 3)) +// a_1 := add(x_3, 1) +// mstore(a_1, inv_2) +// } +// let a_4 := 1 +// let inv_5 := add(b, 42) +// for { } iszero(create(0x08, 0x00, 0x02)) { a_4 := add(a_4, 1) } +// { +// let x_6 := extcodesize(mul(inv_5, 3)) +// a_4 := add(x_6, 1) +// mstore(a_4, inv_5) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_function.yul new file mode 100644 index 000000000..ab5be62d9 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_function.yul @@ -0,0 +1,32 @@ +{ + function f() -> x { x := g() } + function g() -> x { + x := add(x, 1) + sstore(0x00, 0x00) + } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := extcodesize(f()) + let q := sload(g()) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// x_1 := add(x_1, 1) +// sstore(0x00, 0x00) +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := extcodesize(f()) +// let q := sload(g()) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_loop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_loop.yul new file mode 100644 index 000000000..d7e16ed68 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_loop.yul @@ -0,0 +1,29 @@ +{ + function f() -> x { x := g() } + function g() -> x { for {} 1 {} {} } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := extcodesize(f()) + let q := g() + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// for { } 1 { } +// { } +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := extcodesize(f()) +// let q := g() +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_recursive_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_recursive_function.yul new file mode 100644 index 000000000..431453941 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_state_recursive_function.yul @@ -0,0 +1,26 @@ +{ + function f() -> x { x := g() } + function g() -> x { x := g() } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := extcodesize(f()) + let q := sload(g()) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { x_1 := g() } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// let t := extcodesize(f()) +// let q := sload(g()) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_staticall_returndatasize.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_staticall_returndatasize.yul new file mode 100644 index 000000000..ae20f4904 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_staticall_returndatasize.yul @@ -0,0 +1,32 @@ +{ + let b := 1 + // invalidates state in post + for { let a := 1 } iszero(eq(a, 10)) {pop(call(2, 0x01, 2, 0x00, 32, 0x010, 32))} { + let inv := add(b, 42) + let x := returndatasize() + a := add(x, 1) + pop(staticcall(2, 3, 0, 32, 64, 32)) // prevents moving returndatasize + mstore(a, inv) + } +} +// ==== +// EVMVersion: >=byzantium +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } +// iszero(eq(a, 10)) +// { +// pop(call(2, 0x01, 2, 0x00, 32, 0x010, 32)) +// } +// { +// let x := returndatasize() +// a := add(x, 1) +// pop(staticcall(2, 3, 0, 32, 64, 32)) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage.yul new file mode 100644 index 000000000..0ef2fd78a --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage.yul @@ -0,0 +1,57 @@ +{ + let b := 1 + // invalidates storage in post + for { let a := 1 } iszero(eq(a, 10)) { sstore(0x00, 0x01)} { + let inv := add(b, 42) + let x := sload(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } + + // invalidates storage in body + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := sload(mul(inv, 3)) + a := add(x, 1) + sstore(a, inv) + } + + // invalidates state in body + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + pop(callcode(100, 0x010, 10, 0x00, 32, 0x0100, 32)) + let x := sload(mul(inv, 3)) + a := add(x, 1) + } + +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// for { } iszero(eq(a, 10)) { sstore(0x00, 0x01) } +// { +// let x := sload(mul(inv, 3)) +// a := add(x, 1) +// mstore(a, inv) +// } +// let a_1 := 1 +// let inv_2 := add(b, 42) +// for { } iszero(eq(a_1, 10)) { a_1 := add(a_1, 1) } +// { +// let x_3 := sload(mul(inv_2, 3)) +// a_1 := add(x_3, 1) +// sstore(a_1, inv_2) +// } +// let a_4 := 1 +// let inv_5 := add(b, 42) +// for { } iszero(eq(a_4, 10)) { a_4 := add(a_4, 1) } +// { +// pop(callcode(100, 0x010, 10, 0x00, 32, 0x0100, 32)) +// let x_6 := sload(mul(inv_5, 3)) +// a_4 := add(x_6, 1) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_function.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_function.yul new file mode 100644 index 000000000..f1a2d6283 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_function.yul @@ -0,0 +1,28 @@ +{ + function f() -> x { x := g() } + function g() -> x { + x := add(x, 1) + sstore(0x00, 0x00) + } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let q := sload(g()) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// x_1 := add(x_1, 1) +// sstore(0x00, 0x00) +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { let q := sload(g()) } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_loop.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_loop.yul new file mode 100644 index 000000000..d799fa6ae --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/no_move_storage_loop.yul @@ -0,0 +1,25 @@ +{ + function f() -> x { x := g() } + function g() -> x { for {} 1 {} {} } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let t := sload(f()) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function f() -> x +// { x := g() } +// function g() -> x_1 +// { +// for { } 1 { } +// { } +// } +// let b := 1 +// let a := 1 +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { let t := sload(f()) } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/not_first.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/not_first.yul new file mode 100644 index 000000000..2840d3445 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/not_first.yul @@ -0,0 +1,21 @@ +{ + function g() -> x { x := add(mload(x), 1) } + + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + sstore(0, a) + let q := keccak256(g(), 32) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// function g() -> x +// { x := add(mload(x), 1) } +// let b := 1 +// let a := 1 +// let q := keccak256(g(), 32) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { sstore(0, a) } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_memory.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_memory.yul new file mode 100644 index 000000000..223f54ee7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_memory.yul @@ -0,0 +1,25 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := mload(mul(inv, 3)) + let y := keccak256(mul(b, 13), 32) + a := add(x, 1) + sstore(a, inv) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// let x := mload(mul(inv, 3)) +// let y := keccak256(mul(b, 13), 32) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(x, 1) +// sstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_state.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_state.yul new file mode 100644 index 000000000..f8b5a3d53 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_state.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := extcodesize(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// let x := extcodesize(mul(inv, 3)) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(x, 1) +// mstore(a, inv) +// } +// } diff --git a/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_storage.yul b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_storage.yul new file mode 100644 index 000000000..022d66b2d --- /dev/null +++ b/test/libyul/yulOptimizerTests/loopInvariantCodeMotion/simple_storage.yul @@ -0,0 +1,23 @@ +{ + let b := 1 + for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } { + let inv := add(b, 42) + let x := sload(mul(inv, 3)) + a := add(x, 1) + mstore(a, inv) + } +} +// ---- +// step: loopInvariantCodeMotion +// +// { +// let b := 1 +// let a := 1 +// let inv := add(b, 42) +// let x := sload(mul(inv, 3)) +// for { } iszero(eq(a, 10)) { a := add(a, 1) } +// { +// a := add(x, 1) +// mstore(a, inv) +// } +// }