From 74f4f946cf3a334e1dfc16dbba09f8c4375d9207 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 27 Nov 2025 20:16:28 -0500 Subject: [PATCH 01/15] Fixed applySimple return type strictly should be boolean --- libevmasm/PeepholeOptimiser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 267700e457cd..c0039b070faa 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -489,7 +489,7 @@ struct JumpToNext: SimplePeepholeOptimizerMethod struct RJumpToNext: SimplePeepholeOptimizerMethod { - static size_t applySimple( + static bool applySimple( AssemblyItem const& _rjump, AssemblyItem const& _tag, std::back_insert_iterator _out From 855b14030b4291dbea2ad97b0f00e10e201172d6 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 27 Nov 2025 22:10:45 -0500 Subject: [PATCH 02/15] Added CalldataSizeCheckSimplify pattern with unit test --- libevmasm/PeepholeOptimiser.cpp | 41 +++++++++++++++++++++++++++++++++ test/libevmasm/Optimiser.cpp | 25 ++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index c0039b070faa..35323fc5faba 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -696,6 +696,46 @@ struct DeduplicateNextTagSize1 : SimplePeepholeOptimizerMethod +{ + static bool apply(OptimiserState& _state) + { + size_t window = 4; + if (_state.i + window > _state.items.size()) + return false; + + auto a = _state.items.begin() + static_cast(_state.i + 0); // PUSH n + auto b = _state.items.begin() + static_cast(_state.i + 1); // CALLDATASIZE + auto c = _state.items.begin() + static_cast(_state.i + 2); // LT + auto d = _state.items.begin() + static_cast(_state.i + 3); // ISZERO + + // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO + if ( + a->type() == Push && + *b == Instruction::CALLDATASIZE && + *c == Instruction::LT && + *d == Instruction::ISZERO + ) + { + u256 n = a->data(); + if (n == 0) + return false; + + u256 newConst = n - 1; + + // Emit: CALLDATASIZE ; PUSH (n-1) ; GT + *_state.out = *b; + *_state.out = AssemblyItem(newConst, a->debugData()); + *_state.out = AssemblyItem(Instruction::GT, c->debugData()); + + _state.i += window; + return true; + } + + return false; + } +}; + template void applyMethods(OptimiserState& _state) { @@ -747,6 +787,7 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize1, TagConjunctions, TruthyAnd, + CalldataSizeCheckSimplify, Identity >(state); if (m_optimisedItems.size() < m_items.size() || ( diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 01e1f9ac6c83..7e277947170d 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1479,6 +1479,31 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) ); } +BOOST_AUTO_TEST_CASE(calldata_size_check_simplify) +{ + // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO + // Expect: CALLDATASIZE ; PUSH (n-1) ; GT + AssemblyItems items{ + u256(5), + Instruction::CALLDATASIZE, + Instruction::LT, + Instruction::ISZERO + }; + + AssemblyItems expectation{ + Instruction::CALLDATASIZE, + u256(4), + Instruction::GT + }; + + PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ From e6fde40867a8b344f7b2a8fda3f2a9488323f847 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 28 Nov 2025 13:50:44 -0500 Subject: [PATCH 03/15] Added power of two multiplication to shl peephole pattern with unit test removed unneeded comments --- libevmasm/PeepholeOptimiser.cpp | 43 ++++++++++++++++++++++++++++----- test/libevmasm/Optimiser.cpp | 43 +++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 35323fc5faba..7fd0d4317ed5 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -704,12 +704,11 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethod _state.items.size()) return false; - auto a = _state.items.begin() + static_cast(_state.i + 0); // PUSH n - auto b = _state.items.begin() + static_cast(_state.i + 1); // CALLDATASIZE - auto c = _state.items.begin() + static_cast(_state.i + 2); // LT - auto d = _state.items.begin() + static_cast(_state.i + 3); // ISZERO + auto a = _state.items.begin() + static_cast(_state.i + 0); + auto b = _state.items.begin() + static_cast(_state.i + 1); + auto c = _state.items.begin() + static_cast(_state.i + 2); + auto d = _state.items.begin() + static_cast(_state.i + 3); - // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO if ( a->type() == Push && *b == Instruction::CALLDATASIZE && @@ -723,7 +722,6 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethoddebugData()); *_state.out = AssemblyItem(Instruction::GT, c->debugData()); @@ -736,6 +734,38 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethod +{ + static bool apply(OptimiserState& _state) + { + if (!_state.evmVersion.hasBitwiseShifting()) + return false; + + size_t const windowSize = 2; + if (_state.i + windowSize > _state.items.size()) + return false; + + auto push = _state.items.begin() + static_cast(_state.i); + auto mul = _state.items.begin() + static_cast(_state.i + 1); + + if (push->type() != Push || mul->type() != Operation || mul->instruction() != Instruction::MUL) + return false; + + u256 const c = push->data(); + + if (c == 0 || (c & (c - 1)) != 0) + return false; + + unsigned const k = static_cast(boost::multiprecision::msb(c)); + + *_state.out = AssemblyItem(u256(k), push->debugData()); + *_state.out = AssemblyItem(Instruction::SHL, mul->debugData()); + + _state.i += windowSize; + return true; + } +}; + template void applyMethods(OptimiserState& _state) { @@ -788,6 +818,7 @@ bool PeepholeOptimiser::optimise() TagConjunctions, TruthyAnd, CalldataSizeCheckSimplify, + MulByPowerOfTwoToShl, Identity >(state); if (m_optimisedItems.size() < m_items.size() || ( diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 7e277947170d..d958c70d50aa 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1481,8 +1481,6 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) BOOST_AUTO_TEST_CASE(calldata_size_check_simplify) { - // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO - // Expect: CALLDATASIZE ; PUSH (n-1) ; GT AssemblyItems items{ u256(5), Instruction::CALLDATASIZE, @@ -1504,6 +1502,47 @@ BOOST_AUTO_TEST_CASE(calldata_size_check_simplify) ); } +BOOST_AUTO_TEST_CASE(peephole_mul_power_of_two_to_shl) +{ + if (!solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting()) + return; + + { + AssemblyItems items{ + u256(1024), + Instruction::MUL + }; + + AssemblyItems expectation{ + u256(10), + Instruction::SHL + }; + + PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); + } + + { + AssemblyItems items{ + u256(12), + Instruction::MUL + }; + + AssemblyItems expectation = items; + + PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(!peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); + } +} + BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ From 8751dfb50846d33c7c6ff40d88d46900c8e7bd85 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 28 Nov 2025 16:39:49 -0500 Subject: [PATCH 04/15] Added FunctionSelectorGuard pattern with unit test --- libevmasm/PeepholeOptimiser.cpp | 36 +++++++++++++++++++++++++++++++++ test/libevmasm/Optimiser.cpp | 26 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 7fd0d4317ed5..4581ddee42dd 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -766,6 +766,41 @@ struct MulByPowerOfTwoToShl : SimplePeepholeOptimizerMethod +{ + static bool applySimple( + AssemblyItem const& _pushFour, + AssemblyItem const& _calldatasize, + AssemblyItem const& _lt, + AssemblyItem const& _iszero, + AssemblyItem const& _pushTag, + AssemblyItem const& _jumpi, + std::back_insert_iterator _out + ) + { + if ( + _pushFour.type() == Push && + _pushFour.data() == u256(4) && + _calldatasize == Instruction::CALLDATASIZE && + _lt == Instruction::LT && + _iszero == Instruction::ISZERO && + _pushTag.type() == PushTag && + _jumpi == Instruction::JUMPI + ) + { + + *_out = AssemblyItem(Instruction::CALLDATASIZE, _calldatasize.debugData()); + *_out = AssemblyItem(u256(3), _pushFour.debugData()); + *_out = AssemblyItem(Instruction::GT, _lt.debugData()); + *_out = _pushTag; + *_out = _jumpi; + return true; + } + else + return false; + } +}; + template void applyMethods(OptimiserState& _state) { @@ -816,6 +851,7 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize2, DeduplicateNextTagSize1, TagConjunctions, + FunctionSelectorGuard, TruthyAnd, CalldataSizeCheckSimplify, MulByPowerOfTwoToShl, diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index d958c70d50aa..83f0244e37b7 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1543,6 +1543,32 @@ BOOST_AUTO_TEST_CASE(peephole_mul_power_of_two_to_shl) } } +BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) +{ + AssemblyItems items{ + u256(4), + Instruction::CALLDATASIZE, + Instruction::LT, + Instruction::ISZERO, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + AssemblyItems expectation{ + Instruction::CALLDATASIZE, + u256(3), + Instruction::GT, + AssemblyItem(PushTag, 1), + Instruction::JUMPI + }; + + PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(peepOpt.optimise()); + BOOST_CHECK_EQUAL_COLLECTIONS( + items.begin(), items.end(), + expectation.begin(), expectation.end() + ); +} + BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ From 37e3dd95073357896d07602e94d8b7591959d8aa Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 28 Nov 2025 16:50:44 -0500 Subject: [PATCH 05/15] Added ErrorDispatcherStandardPanic pattern with unit tests --- libevmasm/PeepholeOptimiser.cpp | 60 +++++++++++++++++++++++++++++- test/libevmasm/Optimiser.cpp | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 4581ddee42dd..87864ff45802 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -788,7 +788,6 @@ struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod +{ + static bool applySimple( + AssemblyItem const& _pushSelector, + AssemblyItem const& _pushE0, + AssemblyItem const& _shl, + AssemblyItem const& _pushZero1, + AssemblyItem const& _mstore1, + AssemblyItem const& _pushCode, + AssemblyItem const& _push4, + AssemblyItem const& _mstore2, + AssemblyItem const& _push24, + AssemblyItem const& _pushZero2, + AssemblyItem const& _revert, + std::back_insert_iterator _out + ) + { + if (!(_pushSelector.type() == Push && _pushSelector.data() == u256(0x4E487B71))) + return false; + if (!(_pushE0.type() == Push && _pushE0.data() == u256(0xE0))) + return false; + if (!(_shl == Instruction::SHL)) + return false; + if (!(_pushZero1 == Instruction::PUSH0)) + return false; + if (!(_mstore1 == Instruction::MSTORE)) + return false; + if (_pushCode.type() != Push) + return false; + if (_pushCode.data() > u256(0xFF)) + return false; + if (!(_push4.type() == Push && _push4.data() == u256(4))) + return false; + if (!(_mstore2 == Instruction::MSTORE)) + return false; + if (!(_push24.type() == Push && _push24.data() == u256(0x24))) + return false; + if (!(_pushZero2 == Instruction::PUSH0)) + return false; + if (!(_revert == Instruction::REVERT)) + return false; + + u256 selectorWord = _pushSelector.data() << (256 - 32); + + *_out = AssemblyItem(selectorWord, _pushSelector.debugData()); + *_out = _pushZero1; + *_out = _mstore1; + *_out = _pushCode; + *_out = _push4; + *_out = _mstore2; + *_out = _push24; + *_out = _pushZero2; + *_out = _revert; + + return true; + } +}; + template void applyMethods(OptimiserState& _state) { @@ -851,6 +908,7 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize2, DeduplicateNextTagSize1, TagConjunctions, + ErrorDispatcherStandardPanic, FunctionSelectorGuard, TruthyAnd, CalldataSizeCheckSimplify, diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 83f0244e37b7..b36896f5cd27 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1569,6 +1569,72 @@ BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) ); } +BOOST_AUTO_TEST_CASE(peephole_error_dispatcher_standard_panic) +{ + AssemblyItems input{ + u256(0x4E487B71), + u256(0xE0), + Instruction::SHL, + Instruction::PUSH0, + Instruction::MSTORE, + u256(0x11), + u256(0x04), + Instruction::MSTORE, + u256(0x24), + Instruction::PUSH0, + Instruction::REVERT + }; + + u256 shiftedSelector = u256(0x4E487B71) << (256 - 32); + + AssemblyItems expectation{ + shiftedSelector, + Instruction::PUSH0, + Instruction::MSTORE, + u256(0x11), + u256(0x04), + Instruction::MSTORE, + u256(0x24), + Instruction::PUSH0, + Instruction::REVERT + }; + + PeepholeOptimiser peepOpt(input, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(peepOpt.optimise()); + + BOOST_CHECK_EQUAL_COLLECTIONS( + input.begin(), input.end(), + expectation.begin(), expectation.end() + ); +} + +BOOST_AUTO_TEST_CASE(peephole_error_dispatcher_standard_panic_negative) +{ + AssemblyItems input{ + u256(0x4E487B71), + u256(0xE0), + Instruction::SHR, + Instruction::PUSH0, + Instruction::MSTORE, + u256(0x11), + u256(0x04), + Instruction::MSTORE, + u256(0x24), + Instruction::PUSH0, + Instruction::REVERT + }; + + AssemblyItems original = input; + + PeepholeOptimiser peepOpt(input, solidity::test::CommonOptions::get().evmVersion()); + BOOST_CHECK(!peepOpt.optimise()); + + BOOST_CHECK_EQUAL_COLLECTIONS( + input.begin(), input.end(), + original.begin(), original.end() + ); +} + BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ From fa04ac8fe55714c79b94f6759c7cafd44c2e1965 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 28 Nov 2025 17:48:23 -0500 Subject: [PATCH 06/15] Revert "Added power of two multiplication to shl peephole pattern" focus on source level no inline assembly --- libevmasm/PeepholeOptimiser.cpp | 43 +++++---------------------------- test/libevmasm/Optimiser.cpp | 43 +-------------------------------- 2 files changed, 7 insertions(+), 79 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 87864ff45802..835c39162ff5 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -704,11 +704,12 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethod _state.items.size()) return false; - auto a = _state.items.begin() + static_cast(_state.i + 0); - auto b = _state.items.begin() + static_cast(_state.i + 1); - auto c = _state.items.begin() + static_cast(_state.i + 2); - auto d = _state.items.begin() + static_cast(_state.i + 3); + auto a = _state.items.begin() + static_cast(_state.i + 0); // PUSH n + auto b = _state.items.begin() + static_cast(_state.i + 1); // CALLDATASIZE + auto c = _state.items.begin() + static_cast(_state.i + 2); // LT + auto d = _state.items.begin() + static_cast(_state.i + 3); // ISZERO + // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO if ( a->type() == Push && *b == Instruction::CALLDATASIZE && @@ -722,6 +723,7 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethoddebugData()); *_state.out = AssemblyItem(Instruction::GT, c->debugData()); @@ -734,38 +736,6 @@ struct CalldataSizeCheckSimplify : SimplePeepholeOptimizerMethod -{ - static bool apply(OptimiserState& _state) - { - if (!_state.evmVersion.hasBitwiseShifting()) - return false; - - size_t const windowSize = 2; - if (_state.i + windowSize > _state.items.size()) - return false; - - auto push = _state.items.begin() + static_cast(_state.i); - auto mul = _state.items.begin() + static_cast(_state.i + 1); - - if (push->type() != Push || mul->type() != Operation || mul->instruction() != Instruction::MUL) - return false; - - u256 const c = push->data(); - - if (c == 0 || (c & (c - 1)) != 0) - return false; - - unsigned const k = static_cast(boost::multiprecision::msb(c)); - - *_state.out = AssemblyItem(u256(k), push->debugData()); - *_state.out = AssemblyItem(Instruction::SHL, mul->debugData()); - - _state.i += windowSize; - return true; - } -}; - struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod { static bool applySimple( @@ -912,7 +882,6 @@ bool PeepholeOptimiser::optimise() FunctionSelectorGuard, TruthyAnd, CalldataSizeCheckSimplify, - MulByPowerOfTwoToShl, Identity >(state); if (m_optimisedItems.size() < m_items.size() || ( diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index b36896f5cd27..ec1ce7b4e8e6 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1479,7 +1479,7 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) ); } -BOOST_AUTO_TEST_CASE(calldata_size_check_simplify) +BOOST_AUTO_TEST_CASE(peephole_calldata_size_check_simplify) { AssemblyItems items{ u256(5), @@ -1502,47 +1502,6 @@ BOOST_AUTO_TEST_CASE(calldata_size_check_simplify) ); } -BOOST_AUTO_TEST_CASE(peephole_mul_power_of_two_to_shl) -{ - if (!solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting()) - return; - - { - AssemblyItems items{ - u256(1024), - Instruction::MUL - }; - - AssemblyItems expectation{ - u256(10), - Instruction::SHL - }; - - PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); - } - - { - AssemblyItems items{ - u256(12), - Instruction::MUL - }; - - AssemblyItems expectation = items; - - PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); - BOOST_REQUIRE(!peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); - } -} - BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) { AssemblyItems items{ From 4620f463130d2f4571459efcfe9da4640a9cc7c9 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Sun, 30 Nov 2025 21:26:34 -0500 Subject: [PATCH 07/15] semantic tests have revealed CalldataSizeCheckSimplify to be unsound So we remove it --- libevmasm/PeepholeOptimiser.cpp | 49 ++++----------------------------- test/libevmasm/Optimiser.cpp | 23 ---------------- 2 files changed, 6 insertions(+), 66 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 835c39162ff5..c892c63826ef 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -696,46 +696,6 @@ struct DeduplicateNextTagSize1 : SimplePeepholeOptimizerMethod -{ - static bool apply(OptimiserState& _state) - { - size_t window = 4; - if (_state.i + window > _state.items.size()) - return false; - - auto a = _state.items.begin() + static_cast(_state.i + 0); // PUSH n - auto b = _state.items.begin() + static_cast(_state.i + 1); // CALLDATASIZE - auto c = _state.items.begin() + static_cast(_state.i + 2); // LT - auto d = _state.items.begin() + static_cast(_state.i + 3); // ISZERO - - // Match: PUSH n ; CALLDATASIZE ; LT ; ISZERO - if ( - a->type() == Push && - *b == Instruction::CALLDATASIZE && - *c == Instruction::LT && - *d == Instruction::ISZERO - ) - { - u256 n = a->data(); - if (n == 0) - return false; - - u256 newConst = n - 1; - - // Emit: CALLDATASIZE ; PUSH (n-1) ; GT - *_state.out = *b; - *_state.out = AssemblyItem(newConst, a->debugData()); - *_state.out = AssemblyItem(Instruction::GT, c->debugData()); - - _state.i += window; - return true; - } - - return false; - } -}; - struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod { static bool applySimple( @@ -758,9 +718,13 @@ struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod 3 <=> cds >= 4) *_out = AssemblyItem(Instruction::GT, _lt.debugData()); + // Preserve tag and JUMPI *_out = _pushTag; *_out = _jumpi; return true; @@ -878,10 +842,9 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize2, DeduplicateNextTagSize1, TagConjunctions, - ErrorDispatcherStandardPanic, - FunctionSelectorGuard, + //ErrorDispatcherStandardPanic, + //FunctionSelectorGuard, TruthyAnd, - CalldataSizeCheckSimplify, Identity >(state); if (m_optimisedItems.size() < m_items.size() || ( diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index ec1ce7b4e8e6..897306e3dbb3 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1479,29 +1479,6 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) ); } -BOOST_AUTO_TEST_CASE(peephole_calldata_size_check_simplify) -{ - AssemblyItems items{ - u256(5), - Instruction::CALLDATASIZE, - Instruction::LT, - Instruction::ISZERO - }; - - AssemblyItems expectation{ - Instruction::CALLDATASIZE, - u256(4), - Instruction::GT - }; - - PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); -} - BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) { AssemblyItems items{ From 6c136b9382718f71e89d7ca6bac6feccbaf36a64 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Sun, 30 Nov 2025 22:55:22 -0500 Subject: [PATCH 08/15] turns out ErrorDispatcherStandardPanic is a bad trade-off --- libevmasm/PeepholeOptimiser.cpp | 67 +----------------------- test/libevmasm/Optimiser.cpp | 93 ++++++--------------------------- 2 files changed, 17 insertions(+), 143 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index c892c63826ef..a9cda92c0a6b 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -718,13 +718,9 @@ struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod 3 <=> cds >= 4) + *_out = AssemblyItem(Instruction::CALLDATASIZE, _calldatasize.debugData()); *_out = AssemblyItem(Instruction::GT, _lt.debugData()); - // Preserve tag and JUMPI *_out = _pushTag; *_out = _jumpi; return true; @@ -734,64 +730,6 @@ struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod -{ - static bool applySimple( - AssemblyItem const& _pushSelector, - AssemblyItem const& _pushE0, - AssemblyItem const& _shl, - AssemblyItem const& _pushZero1, - AssemblyItem const& _mstore1, - AssemblyItem const& _pushCode, - AssemblyItem const& _push4, - AssemblyItem const& _mstore2, - AssemblyItem const& _push24, - AssemblyItem const& _pushZero2, - AssemblyItem const& _revert, - std::back_insert_iterator _out - ) - { - if (!(_pushSelector.type() == Push && _pushSelector.data() == u256(0x4E487B71))) - return false; - if (!(_pushE0.type() == Push && _pushE0.data() == u256(0xE0))) - return false; - if (!(_shl == Instruction::SHL)) - return false; - if (!(_pushZero1 == Instruction::PUSH0)) - return false; - if (!(_mstore1 == Instruction::MSTORE)) - return false; - if (_pushCode.type() != Push) - return false; - if (_pushCode.data() > u256(0xFF)) - return false; - if (!(_push4.type() == Push && _push4.data() == u256(4))) - return false; - if (!(_mstore2 == Instruction::MSTORE)) - return false; - if (!(_push24.type() == Push && _push24.data() == u256(0x24))) - return false; - if (!(_pushZero2 == Instruction::PUSH0)) - return false; - if (!(_revert == Instruction::REVERT)) - return false; - - u256 selectorWord = _pushSelector.data() << (256 - 32); - - *_out = AssemblyItem(selectorWord, _pushSelector.debugData()); - *_out = _pushZero1; - *_out = _mstore1; - *_out = _pushCode; - *_out = _push4; - *_out = _mstore2; - *_out = _push24; - *_out = _pushZero2; - *_out = _revert; - - return true; - } -}; - template void applyMethods(OptimiserState& _state) { @@ -842,8 +780,7 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize2, DeduplicateNextTagSize1, TagConjunctions, - //ErrorDispatcherStandardPanic, - //FunctionSelectorGuard, + FunctionSelectorGuard, TruthyAnd, Identity >(state); diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 897306e3dbb3..15cfd72a1b59 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1481,96 +1481,33 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) { - AssemblyItems items{ - u256(4), - Instruction::CALLDATASIZE, - Instruction::LT, - Instruction::ISZERO, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - AssemblyItems expectation{ - Instruction::CALLDATASIZE, - u256(3), - Instruction::GT, - AssemblyItem(PushTag, 1), - Instruction::JUMPI - }; - - PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); - BOOST_REQUIRE(peepOpt.optimise()); - BOOST_CHECK_EQUAL_COLLECTIONS( - items.begin(), items.end(), - expectation.begin(), expectation.end() - ); -} - -BOOST_AUTO_TEST_CASE(peephole_error_dispatcher_standard_panic) -{ - AssemblyItems input{ - u256(0x4E487B71), - u256(0xE0), - Instruction::SHL, - Instruction::PUSH0, - Instruction::MSTORE, - u256(0x11), - u256(0x04), - Instruction::MSTORE, - u256(0x24), - Instruction::PUSH0, - Instruction::REVERT + AssemblyItems items{ + u256(4), + Instruction::CALLDATASIZE, + Instruction::LT, + Instruction::ISZERO, + AssemblyItem(PushTag, 1), + Instruction::JUMPI }; - u256 shiftedSelector = u256(0x4E487B71) << (256 - 32); - AssemblyItems expectation{ - shiftedSelector, - Instruction::PUSH0, - Instruction::MSTORE, - u256(0x11), - u256(0x04), - Instruction::MSTORE, - u256(0x24), - Instruction::PUSH0, - Instruction::REVERT + u256(3), + Instruction::CALLDATASIZE, + Instruction::GT, + AssemblyItem(PushTag, 1), + Instruction::JUMPI }; - PeepholeOptimiser peepOpt(input, solidity::test::CommonOptions::get().evmVersion()); + PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); + BOOST_REQUIRE(peepOpt.optimise()); BOOST_CHECK_EQUAL_COLLECTIONS( - input.begin(), input.end(), + items.begin(), items.end(), expectation.begin(), expectation.end() ); } -BOOST_AUTO_TEST_CASE(peephole_error_dispatcher_standard_panic_negative) -{ - AssemblyItems input{ - u256(0x4E487B71), - u256(0xE0), - Instruction::SHR, - Instruction::PUSH0, - Instruction::MSTORE, - u256(0x11), - u256(0x04), - Instruction::MSTORE, - u256(0x24), - Instruction::PUSH0, - Instruction::REVERT - }; - - AssemblyItems original = input; - - PeepholeOptimiser peepOpt(input, solidity::test::CommonOptions::get().evmVersion()); - BOOST_CHECK(!peepOpt.optimise()); - - BOOST_CHECK_EQUAL_COLLECTIONS( - input.begin(), input.end(), - original.begin(), original.end() - ); -} - BOOST_AUTO_TEST_CASE(jumpdest_removal) { AssemblyItems items{ From b34de94c45f1ea82b2528c4adf0d3306aafc4c02 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Sun, 30 Nov 2025 23:37:27 -0500 Subject: [PATCH 09/15] FunctionSelectorGuard now passes all tests, both unit and semantic --- libevmasm/PeepholeOptimiser.cpp | 76 +++++++++++++++++++-------------- test/libevmasm/Optimiser.cpp | 19 ++++++--- 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index a9cda92c0a6b..633c08f002ff 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -696,38 +696,50 @@ struct DeduplicateNextTagSize1 : SimplePeepholeOptimizerMethod -{ - static bool applySimple( - AssemblyItem const& _pushFour, - AssemblyItem const& _calldatasize, - AssemblyItem const& _lt, - AssemblyItem const& _iszero, - AssemblyItem const& _pushTag, - AssemblyItem const& _jumpi, - std::back_insert_iterator _out - ) - { - if ( - _pushFour.type() == Push && - _pushFour.data() == u256(4) && - _calldatasize == Instruction::CALLDATASIZE && - _lt == Instruction::LT && - _iszero == Instruction::ISZERO && - _pushTag.type() == PushTag && - _jumpi == Instruction::JUMPI - ) - { - *_out = AssemblyItem(u256(3), _pushFour.debugData()); - *_out = AssemblyItem(Instruction::CALLDATASIZE, _calldatasize.debugData()); - *_out = AssemblyItem(Instruction::GT, _lt.debugData()); - *_out = _pushTag; - *_out = _jumpi; - return true; - } - else - return false; - } +struct FunctionSelectorGuard : SimplePeepholeOptimizerMethod +{ + static bool apply(OptimiserState& _state) + { + size_t window = 6; + if (_state.i + window > _state.items.size()) + return false; + + if (_state.i == 0) + return false; + if (_state.items[_state.i - 1].type() != Tag) + return false; + + auto& a = _state.items[_state.i + 0]; + auto& b = _state.items[_state.i + 1]; + auto& c = _state.items[_state.i + 2]; + auto& d = _state.items[_state.i + 3]; + auto& e = _state.items[_state.i + 4]; + auto& f = _state.items[_state.i + 5]; + + if (a.type() != Push || a.data() != u256(4)) + return false; + + if (!(b == Instruction::CALLDATASIZE)) + return false; + if (!(c == Instruction::LT)) + return false; + if (!(d == Instruction::ISZERO)) + return false; + + if (e.type() != PushTag) + return false; + if (!(f == Instruction::JUMPI)) + return false; + + *_state.out = AssemblyItem(u256(3), a.debugData()); + *_state.out = AssemblyItem(Instruction::CALLDATASIZE, b.debugData()); + *_state.out = AssemblyItem(Instruction::GT, c.debugData()); + *_state.out = e; + *_state.out = f; + + _state.i += window; + return true; + } }; template diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index 15cfd72a1b59..be03747cf640 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -1481,25 +1481,34 @@ BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi) BOOST_AUTO_TEST_CASE(peephole_function_selector_guard) { + AssemblyItem tag0 = AssemblyItem(Tag, 0); + AssemblyItem tag1 = AssemblyItem(Tag, 1); + AssemblyItem pushTag1 = AssemblyItem(PushTag, 1); + AssemblyItems items{ + tag0, u256(4), Instruction::CALLDATASIZE, Instruction::LT, Instruction::ISZERO, - AssemblyItem(PushTag, 1), - Instruction::JUMPI + pushTag1, + Instruction::JUMPI, + tag1, + Instruction::STOP }; AssemblyItems expectation{ + tag0, u256(3), Instruction::CALLDATASIZE, Instruction::GT, - AssemblyItem(PushTag, 1), - Instruction::JUMPI + pushTag1, + Instruction::JUMPI, + tag1, + Instruction::STOP }; PeepholeOptimiser peepOpt(items, solidity::test::CommonOptions::get().evmVersion()); - BOOST_REQUIRE(peepOpt.optimise()); BOOST_CHECK_EQUAL_COLLECTIONS( From 9aaec949a771ff679dd9d37f12d284cb4133ae58 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Sun, 30 Nov 2025 23:46:26 -0500 Subject: [PATCH 10/15] updated changelog --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index f772f6dd8b48..0d5f7d9d8a6b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,7 @@ Language Features: Compiler Features: * ethdebug: Experimental support for instructions and source locations under EOF. +* peepholeoptimiser: Peephole pattern added. Bugfixes: * Assembler: Fix not using a fixed-width type for IDs being assigned to subassemblies nested more than one level away, resulting in inconsistent `--asm-json` output between target architectures. From 2967b50fb6f9ff9fdaca6c3039c6c92589a31f7a Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Mon, 1 Dec 2025 17:39:33 -0500 Subject: [PATCH 11/15] coding style fix: remove trailing whitespace --- libevmasm/PeepholeOptimiser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 633c08f002ff..f60a0111184b 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -792,7 +792,7 @@ bool PeepholeOptimiser::optimise() DeduplicateNextTagSize2, DeduplicateNextTagSize1, TagConjunctions, - FunctionSelectorGuard, + FunctionSelectorGuard, TruthyAnd, Identity >(state); From 5a6e78cae6c249e99aee7e0e31214db19d2f9063 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Mon, 1 Dec 2025 17:55:51 -0500 Subject: [PATCH 12/15] reformatted with clang-format -i lib/optimiser/PeepholeOptimiser.cpp --- libevmasm/PeepholeOptimiser.cpp | 88 ++++++++++++++++----------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index f60a0111184b..c6a51855f80a 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -696,50 +696,50 @@ struct DeduplicateNextTagSize1 : SimplePeepholeOptimizerMethod -{ - static bool apply(OptimiserState& _state) - { - size_t window = 6; - if (_state.i + window > _state.items.size()) - return false; - - if (_state.i == 0) - return false; - if (_state.items[_state.i - 1].type() != Tag) - return false; - - auto& a = _state.items[_state.i + 0]; - auto& b = _state.items[_state.i + 1]; - auto& c = _state.items[_state.i + 2]; - auto& d = _state.items[_state.i + 3]; - auto& e = _state.items[_state.i + 4]; - auto& f = _state.items[_state.i + 5]; - - if (a.type() != Push || a.data() != u256(4)) - return false; - - if (!(b == Instruction::CALLDATASIZE)) - return false; - if (!(c == Instruction::LT)) - return false; - if (!(d == Instruction::ISZERO)) - return false; - - if (e.type() != PushTag) - return false; - if (!(f == Instruction::JUMPI)) - return false; - - *_state.out = AssemblyItem(u256(3), a.debugData()); - *_state.out = AssemblyItem(Instruction::CALLDATASIZE, b.debugData()); - *_state.out = AssemblyItem(Instruction::GT, c.debugData()); - *_state.out = e; - *_state.out = f; - - _state.i += window; - return true; - } +struct FunctionSelectorGuard: SimplePeepholeOptimizerMethod +{ + static bool apply(OptimiserState& _state) + { + size_t window = 6; + if (_state.i + window > _state.items.size()) + return false; + + if (_state.i == 0) + return false; + if (_state.items[_state.i - 1].type() != Tag) + return false; + + auto& a = _state.items[_state.i + 0]; + auto& b = _state.items[_state.i + 1]; + auto& c = _state.items[_state.i + 2]; + auto& d = _state.items[_state.i + 3]; + auto& e = _state.items[_state.i + 4]; + auto& f = _state.items[_state.i + 5]; + + if (a.type() != Push || a.data() != u256(4)) + return false; + + if (!(b == Instruction::CALLDATASIZE)) + return false; + if (!(c == Instruction::LT)) + return false; + if (!(d == Instruction::ISZERO)) + return false; + + if (e.type() != PushTag) + return false; + if (!(f == Instruction::JUMPI)) + return false; + + *_state.out = AssemblyItem(u256(3), a.debugData()); + *_state.out = AssemblyItem(Instruction::CALLDATASIZE, b.debugData()); + *_state.out = AssemblyItem(Instruction::GT, c.debugData()); + *_state.out = e; + *_state.out = f; + + _state.i += window; + return true; + } }; template From 4f7edaf5841929e5a19a966c83a2d009a4271ab0 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Tue, 2 Dec 2025 19:24:02 -0500 Subject: [PATCH 13/15] Trigger CI rerun From 1679161648c55eacc34003e71e19d3ac1b05f055 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Tue, 2 Dec 2025 20:03:18 -0500 Subject: [PATCH 14/15] manual merge --- Changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Changelog.md b/Changelog.md index 0d5f7d9d8a6b..612d3c6a5f8b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,12 @@ Language Features: Compiler Features: * ethdebug: Experimental support for instructions and source locations under EOF. +* EVM: Set default EVM Version to `osaka`. +* DocString Parser: Warn about deprecation of inline assembly special comment `memory-safe-assembly`. +* Syntax Checker: Warn about deprecation of ABI coder v1. +* Syntax Checker: Warn about deprecation of virtual modifiers. +* Type Checker: Warn about deprecation of `send` and `transfer` functions on instances of `address`. +* Type Checker: Warn about deprecation of comparisons between variables of contract types. * peepholeoptimiser: Peephole pattern added. Bugfixes: From f02361bff8b25fb03351ce063bf51c771f448c66 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Tue, 2 Dec 2025 21:25:59 -0500 Subject: [PATCH 15/15] Trigger CI rerun