From 8b4eaeabbfb7ca9e645196fd1de69a999ea43f44 Mon Sep 17 00:00:00 2001 From: hrkrshnn Date: Mon, 26 Apr 2021 17:31:45 +0200 Subject: [PATCH] Added a few optimizer tests for Verbatim --- test/libevmasm/Optimiser.cpp | 168 ++++++++++++++++++ .../loadResolver/verbatim_mload.yul | 16 ++ .../loadResolver/verbatim_sload.yul | 21 +++ .../unusedPruner/verbatim.yul | 17 ++ 4 files changed, 222 insertions(+) create mode 100644 test/libyul/yulOptimizerTests/loadResolver/verbatim_mload.yul create mode 100644 test/libyul/yulOptimizerTests/loadResolver/verbatim_sload.yul create mode 100644 test/libyul/yulOptimizerTests/unusedPruner/verbatim.yul diff --git a/test/libevmasm/Optimiser.cpp b/test/libevmasm/Optimiser.cpp index cdc4a7db1..8903cc331 100644 --- a/test/libevmasm/Optimiser.cpp +++ b/test/libevmasm/Optimiser.cpp @@ -93,6 +93,45 @@ namespace BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); } + /// In contrast to the function `CSE`, this function doesn't finish the CSE optimization on an + /// instruction that breaks CSE Analysis block. Copied from Assembly.cpp + AssemblyItems fullCSE(AssemblyItems const& _input) + { + AssemblyItems optimisedItems; + + bool usesMSize = ranges::any_of(_input, [](AssemblyItem const& _i) { + return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode; + }); + + auto iter = _input.begin(); + while (iter != _input.end()) + { + KnownState emptyState; + CommonSubexpressionEliminator eliminator{emptyState}; + auto orig = iter; + iter = eliminator.feedItems(iter, _input.end(), usesMSize); + bool shouldReplace = false; + AssemblyItems optimisedChunk; + optimisedChunk = eliminator.getOptimizedItems(); + shouldReplace = (optimisedChunk.size() < static_cast(iter - orig)); + if (shouldReplace) + optimisedItems += optimisedChunk; + else + copy(orig, iter, back_inserter(optimisedItems)); + } + + return optimisedItems; + } + + void checkFullCSE( + AssemblyItems const& _input, + AssemblyItems const& _expectation + ) + { + AssemblyItems output = fullCSE(_input); + BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end()); + } + AssemblyItems CFG(AssemblyItems const& _input) { AssemblyItems output = _input; @@ -1292,6 +1331,135 @@ BOOST_AUTO_TEST_CASE(cse_sub_zero) }); } +BOOST_AUTO_TEST_CASE(cse_simple_verbatim) +{ + auto verbatim = AssemblyItem{bytes{1, 2, 3, 4, 5}, 0, 0}; + AssemblyItems input{verbatim}; + checkCSE(input, input); + checkFullCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_mload_pop) +{ + AssemblyItems input{ + u256(1000), + Instruction::MLOAD, + Instruction::POP, + }; + + AssemblyItems output{ + }; + + checkCSE(input, output); + checkFullCSE(input, output); +} + +BOOST_AUTO_TEST_CASE(cse_verbatim_mload) +{ + auto verbatim = AssemblyItem{bytes{1, 2, 3, 4, 5}, 0, 0}; + AssemblyItems input{ + u256(1000), + Instruction::MLOAD, // Should not be removed + Instruction::POP, + verbatim, + u256(1000), + Instruction::MLOAD, // Should not be removed + Instruction::POP, + }; + + checkFullCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_sload_verbatim_dup) +{ + auto verbatim = AssemblyItem{bytes{1, 2, 3, 4, 5}, 0, 0}; + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + u256(0), + Instruction::SLOAD, + verbatim + }; + + AssemblyItems output{ + u256(0), + Instruction::SLOAD, + Instruction::DUP1, + verbatim + }; + + checkCSE(input, output); + checkFullCSE(input, output); +} + +BOOST_AUTO_TEST_CASE(cse_verbatim_sload_sideeffect) +{ + auto verbatim = AssemblyItem{bytes{1, 2, 3, 4, 5}, 0, 0}; + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + verbatim, + u256(0), + Instruction::SLOAD, + }; + + checkFullCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(cse_verbatim_eq) +{ + auto verbatim = AssemblyItem{bytes{1, 2, 3, 4, 5}, 0, 0}; + AssemblyItems input{ + u256(0), + Instruction::SLOAD, + verbatim, + Instruction::DUP1, + Instruction::EQ + }; + + checkFullCSE(input, input); +} + +BOOST_AUTO_TEST_CASE(verbatim_knownstate) +{ + KnownState state = createInitialState(AssemblyItems{ + Instruction::DUP1, + Instruction::DUP2, + Instruction::DUP3, + Instruction::DUP4 + }); + map const& stackElements = state.stackElements(); + + BOOST_CHECK(state.stackHeight() == 4); + // One more than stack height because of the initial unknown element. + BOOST_CHECK(stackElements.size() == 5); + BOOST_CHECK(stackElements.count(0)); + unsigned initialElement = stackElements.at(0); + // Check if all the DUPs were correctly matched to the same class. + for (auto const& height: {1, 2, 3, 4}) + BOOST_CHECK(stackElements.at(height) == initialElement); + + auto verbatim2i5o = AssemblyItem{bytes{1, 2, 3, 4, 5}, 2, 5}; + state.feedItem(verbatim2i5o); + + BOOST_CHECK(state.stackHeight() == 7); + // Stack elements + // Before verbatim: {{0, x}, {1, x}, {2, x}, {3, x}, {4, x}} + // After verbatim: {{0, x}, {1, x}, {2, x}, {3, a}, {4, b}, {5, c}, {6, d}, {7, e}} + BOOST_CHECK(stackElements.size() == 8); + + for (auto const& height: {1, 2}) + BOOST_CHECK(stackElements.at(height) == initialElement); + + for (auto const& height: {3, 4, 5, 6, 7}) + BOOST_CHECK(stackElements.at(height) != initialElement); + + for (auto const& height1: {3, 4, 5, 6, 7}) + for (auto const& height2: {3, 4, 5, 6, 7}) + if (height1 < height2) + BOOST_CHECK(stackElements.at(height1) != stackElements.at(height2)); +} + BOOST_AUTO_TEST_CASE(cse_remove_redundant_shift_masking) { if (!solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting()) diff --git a/test/libyul/yulOptimizerTests/loadResolver/verbatim_mload.yul b/test/libyul/yulOptimizerTests/loadResolver/verbatim_mload.yul new file mode 100644 index 000000000..150c0240b --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/verbatim_mload.yul @@ -0,0 +1,16 @@ +{ + mstore(10, 20) + // cannot be resolved because of verbatim + sstore(0, mload(10)) + verbatim_0i_0o("test") +} +// ---- +// step: loadResolver +// +// { +// let _1 := 20 +// let _2 := 10 +// mstore(_2, _1) +// sstore(0, mload(_2)) +// verbatim_0i_0o("test") +// } diff --git a/test/libyul/yulOptimizerTests/loadResolver/verbatim_sload.yul b/test/libyul/yulOptimizerTests/loadResolver/verbatim_sload.yul new file mode 100644 index 000000000..ba9cb32d8 --- /dev/null +++ b/test/libyul/yulOptimizerTests/loadResolver/verbatim_sload.yul @@ -0,0 +1,21 @@ +{ + sstore(10, 20) + // will be resolved + sstore(30, sload(10)) + verbatim_0i_0o("test") + // will not be resolved + sstore(30, sload(10)) +} +// ---- +// step: loadResolver +// +// { +// let _1 := 20 +// let _2 := 10 +// sstore(_2, _1) +// let _4 := _1 +// let _5 := 30 +// sstore(_5, _4) +// verbatim_0i_0o("test") +// sstore(_5, sload(_2)) +// } diff --git a/test/libyul/yulOptimizerTests/unusedPruner/verbatim.yul b/test/libyul/yulOptimizerTests/unusedPruner/verbatim.yul new file mode 100644 index 000000000..65b464f7f --- /dev/null +++ b/test/libyul/yulOptimizerTests/unusedPruner/verbatim.yul @@ -0,0 +1,17 @@ +{ + // cannot be removed because of verbatim + let a := mload(10) + // cannot be removed because of verbatim + let b := keccak256(10, 32) + // can be removed + let c := add(a, b) + verbatim_0i_0o("test") +} +// ---- +// step: unusedPruner +// +// { +// pop(mload(10)) +// pop(keccak256(10, 32)) +// verbatim_0i_0o("test") +// }