diff --git a/libyul/Dialect.h b/libyul/Dialect.h index 64377cc37..5fd938c1e 100644 --- a/libyul/Dialect.h +++ b/libyul/Dialect.h @@ -61,6 +61,7 @@ struct Dialect: boost::noncopyable virtual BuiltinFunction const* discardFunction() const { return nullptr; } virtual BuiltinFunction const* equalityFunction() const { return nullptr; } + virtual BuiltinFunction const* booleanNegationFunction() const { return nullptr; } virtual std::set fixedFunctionNames() const { return {}; } diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index f4c0e4eed..1c47c8005 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -70,6 +70,7 @@ struct EVMDialect: public Dialect BuiltinFunctionForEVM const* discardFunction() const override { return builtin("pop"_yulstring); } BuiltinFunctionForEVM const* equalityFunction() const override { return builtin("eq"_yulstring); } + BuiltinFunctionForEVM const* booleanNegationFunction() const override { return builtin("iszero"_yulstring); } static EVMDialect const& looseAssemblyForEVM(langutil::EVMVersion _version); static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version); diff --git a/libyul/backends/wasm/WasmDialect.h b/libyul/backends/wasm/WasmDialect.h index 45bea3bca..e77381a13 100644 --- a/libyul/backends/wasm/WasmDialect.h +++ b/libyul/backends/wasm/WasmDialect.h @@ -47,6 +47,7 @@ struct WasmDialect: public Dialect BuiltinFunction const* builtin(YulString _name) const override; BuiltinFunction const* discardFunction() const override { return builtin("drop"_yulstring); } BuiltinFunction const* equalityFunction() const override { return builtin("i64.eq"_yulstring); } + BuiltinFunction const* booleanNegationFunction() const override { return builtin("i64.eqz"_yulstring); } std::set fixedFunctionNames() const override { return {"main"_yulstring}; } diff --git a/libyul/optimiser/ForLoopConditionIntoBody.cpp b/libyul/optimiser/ForLoopConditionIntoBody.cpp index b169d0a51..919b194f1 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.cpp +++ b/libyul/optimiser/ForLoopConditionIntoBody.cpp @@ -25,7 +25,7 @@ using namespace yul; void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) { - if (_forLoop.condition->type() != typeid(Literal)) + if (m_dialect.booleanNegationFunction() && _forLoop.condition->type() != typeid(Literal)) { langutil::SourceLocation loc = locationOf(*_forLoop.condition); _forLoop.body.statements.insert( @@ -33,9 +33,9 @@ void ForLoopConditionIntoBody::operator()(ForLoop& _forLoop) If { loc, make_unique( - FunctionalInstruction { + FunctionCall { loc, - eth::Instruction::ISZERO, + {loc, m_dialect.booleanNegationFunction()->name}, make_vector(std::move(*_forLoop.condition)) } ), diff --git a/libyul/optimiser/ForLoopConditionIntoBody.h b/libyul/optimiser/ForLoopConditionIntoBody.h index bbb1b47c2..508511758 100644 --- a/libyul/optimiser/ForLoopConditionIntoBody.h +++ b/libyul/optimiser/ForLoopConditionIntoBody.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace yul { @@ -29,17 +30,22 @@ namespace yul * By moving the iteration check part into the ForLoop body, we can apply expression splitter * to the condition expression. * - * This rewritter will skip loops that already have literal constant as iteration condition. + * This rewriter will skip loops that already have literal constant as iteration condition. * * Requirements: * - The Disambiguator must be run upfront. * - To avoid unnecessary rewrite, it is recommended to run this rewriter after StructuralSimplifier. + * - Only works for dialects with a builtin boolean negation function. */ class ForLoopConditionIntoBody: public ASTModifier { public: + ForLoopConditionIntoBody(Dialect const& _dialect): m_dialect(_dialect) {} using ASTModifier::operator(); void operator()(ForLoop& _forLoop) override; + +private: + Dialect const& m_dialect; }; } diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index bcd1a0b5e..97feac36b 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,7 @@ void OptimiserSuite::run( LiteralRematerialiser{_dialect}(ast); StructuralSimplifier{}(ast); ControlFlowSimplifier{_dialect}(ast); + ForLoopConditionIntoBody{_dialect}(ast); BlockFlattener{}(ast); // None of the above can make stack problems worse. diff --git a/test/libsolidity/gasTests/abiv2_optimised.sol b/test/libsolidity/gasTests/abiv2_optimised.sol index dcd52b1e2..f22576380 100644 --- a/test/libsolidity/gasTests/abiv2_optimised.sol +++ b/test/libsolidity/gasTests/abiv2_optimised.sol @@ -17,9 +17,9 @@ contract C { // optimize-yul: true // ---- // creation: -// codeDepositCost: 610600 -// executionCost: 645 -// totalCost: 611245 +// codeDepositCost: 624200 +// executionCost: 657 +// totalCost: 624857 // external: // a(): 429 // b(uint256): 884 diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 431443685..b0d6d928c 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -141,7 +141,7 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line else if (m_optimizerStep == "forLoopConditionIntoBody") { disambiguate(); - ForLoopConditionIntoBody{}(*m_ast); + ForLoopConditionIntoBody{*m_dialect}(*m_ast); } else if (m_optimizerStep == "forLoopInitRewriter") { diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul new file mode 100644 index 000000000..4725a444e --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/loop.yul @@ -0,0 +1,77 @@ +{ + let _1 := 0 + let _33 := calldataload(_1) + let sum_50_141 := _1 + let sum_50_146 := _1 + let sum_50 := _1 + let length_51 := calldataload(_33) + let i_53_142 := _1 + let i_53_147 := _1 + let i_53 := _1 + for { } + 1 + { + let _108 := 1 + let i_53_121 := add(i_53, _108) + let i_53_144 := i_53_121 + let i_53_149 := i_53_121 + i_53 := i_53_121 + } + { + let _109 := lt(i_53, length_51) + let _110 := iszero(_109) + if _110 { break } + let _114_128 := iszero(_109) + if _114_128 { revert(_1, _1) } + let _13_129 := 0x20 + let _115_130 := mul(i_53, _13_129) + let _116_131 := add(_33, _115_130) + let _117_132 := add(_116_131, _13_129) + let v_122_133 := calldataload(_117_132) + let sum_50_120 := add(sum_50, v_122_133) + let sum_50_143 := sum_50_120 + let sum_50_148 := sum_50_120 + sum_50 := sum_50_120 + } + sstore(_1, sum_50) +} +// ==== +// step: commonSubexpressionEliminator +// ---- +// { +// let _1 := 0 +// let _33 := calldataload(_1) +// let sum_50_141 := _1 +// let sum_50_146 := _1 +// let sum_50 := _1 +// let length_51 := calldataload(_33) +// let i_53_142 := _1 +// let i_53_147 := _1 +// let i_53 := _1 +// for { } +// 1 +// { +// let _108 := 1 +// let i_53_121 := add(i_53, _108) +// let i_53_144 := i_53_121 +// let i_53_149 := i_53_121 +// i_53 := i_53_121 +// } +// { +// let _109 := lt(i_53, length_51) +// let _110 := iszero(_109) +// if _110 { break } +// let _114_128 := _110 +// if _110 { revert(_1, _1) } +// let _13_129 := 0x20 +// let _115_130 := mul(i_53, _13_129) +// let _116_131 := add(_33, _115_130) +// let _117_132 := add(_116_131, _13_129) +// let v_122_133 := calldataload(_117_132) +// let sum_50_120 := add(sum_50, v_122_133) +// let sum_50_143 := sum_50_120 +// let sum_50_148 := sum_50_120 +// sum_50 := sum_50_120 +// } +// sstore(_1, sum_50) +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index 6ac882c70..6d2a36895 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -471,8 +471,9 @@ // pos := 64 // let srcPtr := add(_2, pos_1) // let i := _1 -// for { } lt(i, length) { i := add(i, 1) } +// for { } 1 { i := add(i, 1) } // { +// if iszero(lt(i, length)) { break } // abi_encode_t_array$_t_contract$_C_$55_$3_memory_to_t_array$_t_address_$3_memory_ptr(mload(srcPtr), pos) // srcPtr := add(srcPtr, pos_1) // pos := add(pos, 0x60) @@ -503,8 +504,9 @@ // let src := add(offset, _1) // if gt(add(add(offset, mul(length, 0x40)), _1), end) { revert(0, 0) } // let i := 0 -// for { } lt(i, length) { i := add(i, 1) } +// for { } 1 { i := add(i, 1) } // { +// if iszero(lt(i, length)) { break } // if iszero(slt(add(src, 0x1f), end)) { revert(0, 0) } // let dst_1 := allocateMemory(array_allocation_size_t_array$_t_uint256_$2_memory(0x2)) // let dst_2 := dst_1 @@ -512,8 +514,9 @@ // let _2 := add(src, 0x40) // if gt(_2, end) { revert(0, 0) } // let i_1 := 0 -// for { } lt(i_1, 0x2) { i_1 := add(i_1, 1) } +// for { } 1 { i_1 := add(i_1, 1) } // { +// if iszero(lt(i_1, 0x2)) { break } // mstore(dst_1, calldataload(src_1)) // dst_1 := add(dst_1, _1) // src_1 := add(src_1, _1) @@ -535,8 +538,9 @@ // let src := add(offset, _1) // if gt(add(add(offset, mul(length, _1)), _1), end) { revert(0, 0) } // let i := 0 -// for { } lt(i, length) { i := add(i, 1) } +// for { } 1 { i := add(i, 1) } // { +// if iszero(lt(i, length)) { break } // mstore(dst, calldataload(src)) // dst := add(dst, _1) // src := add(src, _1) @@ -546,8 +550,9 @@ // { // let srcPtr := value // let i := 0 -// for { } lt(i, 0x3) { i := add(i, 1) } +// for { } 1 { i := add(i, 1) } // { +// if iszero(lt(i, 0x3)) { break } // mstore(pos, and(mload(srcPtr), sub(shl(160, 1), 1))) // srcPtr := add(srcPtr, 0x20) // pos := add(pos, 0x20) diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index 54193a4e6..20662f53a 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -254,8 +254,9 @@ // let b := add(0x300, mul(n, 0x80)) // let i := 0 // let i_1 := i -// for { } lt(i, n) { i := add(i, 0x01) } +// for { } 1 { i := add(i, 0x01) } // { +// if iszero(lt(i, n)) { break } // let _2 := add(calldataload(0x04), mul(i, 0xc0)) // let noteIndex := add(_2, 0x24) // let k := i_1 @@ -373,8 +374,9 @@ // function hashCommitments(notes, n) // { // let i := 0 -// for { } lt(i, n) { i := add(i, 0x01) } +// for { } 1 { i := add(i, 0x01) } // { +// if iszero(lt(i, n)) { break } // calldatacopy(add(0x300, mul(i, 0x80)), add(add(notes, mul(i, 0xc0)), 0x60), 0x80) // } // mstore(0, keccak256(0x300, mul(n, 0x80))) diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index 875275328..e916e8206 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -151,7 +151,7 @@ public: ForLoopInitRewriter{}(*m_ast); break; case 'O': - ForLoopConditionIntoBody{}(*m_ast); + ForLoopConditionIntoBody{m_dialect}(*m_ast); break; case 'c': CommonSubexpressionEliminator::run(m_dialect, *m_ast);