mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
b3230b0bdc
test: updat filereader test deprecate namespace std deprecate namespace std deprecate namespace std deprecate namespace std deprecate namespace std deprecate namespace std deprecate namespace std deprecate namespace std deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace deprecated std namespace depecrate namespace std deprecated namespace std check ci clean line Co-authored-by: Nikola Matić <nikola.matic@ethereum.org> purge line pure line deprecate std deprecate std deprecate std deprecate std deprecate std deprecate deprecate std bye namespace
1921 lines
43 KiB
C++
1921 lines
43 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
/**
|
|
* @author Christian <c@ethdev.com>
|
|
* @date 2014
|
|
* Tests for the Solidity optimizer.
|
|
*/
|
|
|
|
#include <test/Common.h>
|
|
|
|
#include <libevmasm/CommonSubexpressionEliminator.h>
|
|
#include <libevmasm/PeepholeOptimiser.h>
|
|
#include <libevmasm/Inliner.h>
|
|
#include <libevmasm/JumpdestRemover.h>
|
|
#include <libevmasm/ControlFlowGraph.h>
|
|
#include <libevmasm/BlockDeduplicator.h>
|
|
#include <libevmasm/Assembly.h>
|
|
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <range/v3/algorithm/any_of.hpp>
|
|
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <memory>
|
|
|
|
using namespace solidity::langutil;
|
|
using namespace solidity::evmasm;
|
|
|
|
namespace solidity::frontend::test
|
|
{
|
|
|
|
namespace
|
|
{
|
|
AssemblyItems addDummyLocations(AssemblyItems const& _input)
|
|
{
|
|
// add dummy locations to each item so that we can check that they are not deleted
|
|
AssemblyItems input = _input;
|
|
for (AssemblyItem& item: input)
|
|
item.setLocation({1, 3, nullptr});
|
|
return input;
|
|
}
|
|
|
|
evmasm::KnownState createInitialState(AssemblyItems const& _input)
|
|
{
|
|
evmasm::KnownState state;
|
|
for (auto const& item: addDummyLocations(_input))
|
|
state.feedItem(item, true);
|
|
return state;
|
|
}
|
|
|
|
AssemblyItems CSE(AssemblyItems const& _input, evmasm::KnownState const& _state = evmasm::KnownState())
|
|
{
|
|
AssemblyItems input = addDummyLocations(_input);
|
|
|
|
bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) {
|
|
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
|
|
});
|
|
evmasm::CommonSubexpressionEliminator cse(_state);
|
|
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
|
|
AssemblyItems output = cse.getOptimizedItems();
|
|
|
|
for (AssemblyItem const& item: output)
|
|
{
|
|
BOOST_CHECK(item == Instruction::POP || item.location().isValid());
|
|
}
|
|
return output;
|
|
}
|
|
|
|
void checkCSE(
|
|
AssemblyItems const& _input,
|
|
AssemblyItems const& _expectation,
|
|
KnownState const& _state = evmasm::KnownState()
|
|
)
|
|
{
|
|
AssemblyItems output = CSE(_input, _state);
|
|
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<size_t>(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;
|
|
// Running it four times should be enough for these tests.
|
|
for (unsigned i = 0; i < 4; ++i)
|
|
{
|
|
ControlFlowGraph cfg(output);
|
|
AssemblyItems optItems;
|
|
for (BasicBlock const& block: cfg.optimisedBlocks())
|
|
copy(output.begin() + static_cast<int>(block.begin), output.begin() + static_cast<int>(block.end),
|
|
back_inserter(optItems));
|
|
output = std::move(optItems);
|
|
}
|
|
return output;
|
|
}
|
|
|
|
void checkCFG(AssemblyItems const& _input, AssemblyItems const& _expectation)
|
|
{
|
|
AssemblyItems output = CFG(_input);
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end());
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE(Optimiser)
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_push_immutable_same)
|
|
{
|
|
AssemblyItem pushImmutable{PushImmutable, 0x1234};
|
|
checkCSE({pushImmutable, pushImmutable}, {pushImmutable, Instruction::DUP1});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_push_immutable_different)
|
|
{
|
|
AssemblyItems input{{PushImmutable, 0x1234},{PushImmutable, 0xABCD}};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_assign_immutable)
|
|
{
|
|
{
|
|
AssemblyItems input{u256(0x42), {AssignImmutable, 0x1234}};
|
|
checkCSE(input, input);
|
|
}
|
|
{
|
|
AssemblyItems input{{AssignImmutable, 0x1234}};
|
|
checkCSE(input, input);
|
|
}
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_assign_immutable_breaks)
|
|
{
|
|
AssemblyItems input = addDummyLocations(AssemblyItems{
|
|
u256(0x42),
|
|
{AssignImmutable, 0x1234},
|
|
Instruction::ORIGIN
|
|
});
|
|
|
|
evmasm::CommonSubexpressionEliminator cse{evmasm::KnownState()};
|
|
// Make sure CSE breaks after AssignImmutable.
|
|
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.begin() + 2);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
|
|
{
|
|
evmasm::KnownState state;
|
|
evmasm::CommonSubexpressionEliminator cse(state);
|
|
AssemblyItems input{
|
|
Instruction::SWAP1, Instruction::POP, Instruction::ADD, u256(0), Instruction::SWAP1,
|
|
Instruction::SLOAD, Instruction::SWAP1, u256(100), Instruction::EXP, Instruction::SWAP1,
|
|
Instruction::DIV, u256(0xff), Instruction::AND
|
|
};
|
|
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), false) == input.end());
|
|
AssemblyItems output = cse.getOptimizedItems();
|
|
BOOST_CHECK(!output.empty());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_negative_stack_access)
|
|
{
|
|
AssemblyItems input{Instruction::DUP2, u256(0)};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_negative_stack_end)
|
|
{
|
|
AssemblyItems input{Instruction::ADD};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack)
|
|
{
|
|
AssemblyItems input{Instruction::ADD, u256(1), Instruction::DUP1};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_pop)
|
|
{
|
|
checkCSE({Instruction::POP}, {Instruction::POP});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_unneeded_items)
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::ADD,
|
|
Instruction::SWAP1,
|
|
Instruction::POP,
|
|
u256(7),
|
|
u256(8),
|
|
};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_constant_addition)
|
|
{
|
|
AssemblyItems input{u256(7), u256(8), Instruction::ADD};
|
|
checkCSE(input, {u256(7 + 8)});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_invariants)
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::DUP1,
|
|
Instruction::DUP1,
|
|
u256(0),
|
|
Instruction::OR,
|
|
Instruction::OR
|
|
};
|
|
checkCSE(input, {Instruction::DUP1});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_subself)
|
|
{
|
|
checkCSE({Instruction::DUP1, Instruction::SUB}, {Instruction::POP, u256(0)});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_subother)
|
|
{
|
|
checkCSE({Instruction::SUB}, {Instruction::SUB});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_double_negation)
|
|
{
|
|
checkCSE({Instruction::DUP5, Instruction::NOT, Instruction::NOT}, {Instruction::DUP5});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_double_iszero)
|
|
{
|
|
checkCSE({Instruction::GT, Instruction::ISZERO, Instruction::ISZERO}, {Instruction::GT});
|
|
checkCSE({Instruction::GT, Instruction::ISZERO}, {Instruction::GT, Instruction::ISZERO});
|
|
checkCSE(
|
|
{Instruction::ISZERO, Instruction::ISZERO, Instruction::ISZERO},
|
|
{Instruction::ISZERO}
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_associativity)
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::DUP1,
|
|
Instruction::DUP1,
|
|
u256(0),
|
|
Instruction::OR,
|
|
Instruction::OR
|
|
};
|
|
checkCSE(input, {Instruction::DUP1});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_associativity2)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0),
|
|
Instruction::DUP2,
|
|
u256(2),
|
|
u256(1),
|
|
Instruction::DUP6,
|
|
Instruction::ADD,
|
|
u256(2),
|
|
Instruction::ADD,
|
|
Instruction::ADD,
|
|
Instruction::ADD,
|
|
Instruction::ADD
|
|
};
|
|
checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_double_shift_right_overflow)
|
|
{
|
|
if (solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting())
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::CALLVALUE,
|
|
u256(2),
|
|
Instruction::SHR,
|
|
u256(-1),
|
|
Instruction::SHR
|
|
};
|
|
checkCSE(input, {u256(0)});
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_double_shift_left_overflow)
|
|
{
|
|
if (solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting())
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::DUP1,
|
|
u256(2),
|
|
Instruction::SHL,
|
|
u256(-1),
|
|
Instruction::SHL
|
|
};
|
|
checkCSE(input, {u256(0)});
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_byte_ordering_bug)
|
|
{
|
|
AssemblyItems input{
|
|
u256(31),
|
|
Instruction::CALLVALUE,
|
|
Instruction::BYTE
|
|
};
|
|
checkCSE(input, {u256(31), Instruction::CALLVALUE, Instruction::BYTE});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_byte_ordering_fix)
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::CALLVALUE,
|
|
u256(31),
|
|
Instruction::BYTE
|
|
};
|
|
checkCSE(input, {u256(0xff), Instruction::CALLVALUE, Instruction::AND});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_storage)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
Instruction::ADD,
|
|
u256(0),
|
|
Instruction::SSTORE
|
|
};
|
|
checkCSE(input, {
|
|
u256(0),
|
|
Instruction::DUP1,
|
|
Instruction::SLOAD,
|
|
Instruction::DUP1,
|
|
Instruction::ADD,
|
|
Instruction::SWAP1,
|
|
Instruction::SSTORE
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_noninterleaved_storage)
|
|
{
|
|
// two stores to the same location should be replaced by only one store, even if we
|
|
// read in the meantime
|
|
AssemblyItems input{
|
|
u256(7),
|
|
Instruction::DUP2,
|
|
Instruction::SSTORE,
|
|
Instruction::DUP1,
|
|
Instruction::SLOAD,
|
|
u256(8),
|
|
Instruction::DUP3,
|
|
Instruction::SSTORE
|
|
};
|
|
checkCSE(input, {
|
|
u256(8),
|
|
Instruction::DUP2,
|
|
Instruction::SSTORE,
|
|
u256(7)
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_interleaved_storage)
|
|
{
|
|
// stores and reads to/from two unknown locations, should not optimize away the first store
|
|
AssemblyItems input{
|
|
u256(7),
|
|
Instruction::DUP2,
|
|
Instruction::SSTORE, // store to "DUP1"
|
|
Instruction::DUP2,
|
|
Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1"
|
|
u256(0),
|
|
Instruction::DUP3,
|
|
Instruction::SSTORE // store different value to "DUP1"
|
|
};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_same_value)
|
|
{
|
|
// stores and reads to/from two unknown locations, should not optimize away the first store
|
|
// but it should optimize away the second, since we already know the value will be the same
|
|
AssemblyItems input{
|
|
u256(7),
|
|
Instruction::DUP2,
|
|
Instruction::SSTORE, // store to "DUP1"
|
|
Instruction::DUP2,
|
|
Instruction::SLOAD, // read from "DUP2", might be equal to "DUP1"
|
|
u256(6),
|
|
u256(1),
|
|
Instruction::ADD,
|
|
Instruction::DUP3,
|
|
Instruction::SSTORE // store same value to "DUP1"
|
|
};
|
|
checkCSE(input, {
|
|
u256(7),
|
|
Instruction::DUP2,
|
|
Instruction::SSTORE,
|
|
Instruction::DUP2,
|
|
Instruction::SLOAD
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location)
|
|
{
|
|
// stores and reads to/from two known locations, should optimize away the first store,
|
|
// because we know that the location is different
|
|
AssemblyItems input{
|
|
u256(0x70),
|
|
u256(1),
|
|
Instruction::SSTORE, // store to 1
|
|
u256(2),
|
|
Instruction::SLOAD, // read from 2, is different from 1
|
|
u256(0x90),
|
|
u256(1),
|
|
Instruction::SSTORE // store different value at 1
|
|
};
|
|
checkCSE(input, {
|
|
u256(2),
|
|
Instruction::SLOAD,
|
|
u256(0x90),
|
|
u256(1),
|
|
Instruction::SSTORE
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_interleaved_storage_at_known_location_offset)
|
|
{
|
|
// stores and reads to/from two locations which are known to be different,
|
|
// should optimize away the first store, because we know that the location is different
|
|
AssemblyItems input{
|
|
u256(0x70),
|
|
Instruction::DUP2,
|
|
u256(1),
|
|
Instruction::ADD,
|
|
Instruction::SSTORE, // store to "DUP1"+1
|
|
Instruction::DUP1,
|
|
u256(2),
|
|
Instruction::ADD,
|
|
Instruction::SLOAD, // read from "DUP1"+2, is different from "DUP1"+1
|
|
u256(0x90),
|
|
Instruction::DUP3,
|
|
u256(1),
|
|
Instruction::ADD,
|
|
Instruction::SSTORE // store different value at "DUP1"+1
|
|
};
|
|
checkCSE(input, {
|
|
u256(2),
|
|
Instruction::DUP2,
|
|
Instruction::ADD,
|
|
Instruction::SLOAD,
|
|
u256(0x90),
|
|
u256(1),
|
|
Instruction::DUP4,
|
|
Instruction::ADD,
|
|
Instruction::SSTORE
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_deep_stack)
|
|
{
|
|
AssemblyItems input{
|
|
Instruction::ADD,
|
|
Instruction::SWAP1,
|
|
Instruction::POP,
|
|
Instruction::SWAP8,
|
|
Instruction::POP,
|
|
Instruction::SWAP8,
|
|
Instruction::POP,
|
|
Instruction::SWAP8,
|
|
Instruction::SWAP5,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
};
|
|
checkCSE(input, {
|
|
Instruction::SWAP4,
|
|
Instruction::SWAP12,
|
|
Instruction::SWAP3,
|
|
Instruction::SWAP11,
|
|
Instruction::POP,
|
|
Instruction::SWAP1,
|
|
Instruction::SWAP3,
|
|
Instruction::ADD,
|
|
Instruction::SWAP8,
|
|
Instruction::POP,
|
|
Instruction::SWAP6,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
Instruction::POP,
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_jumpi_no_jump)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0),
|
|
u256(1),
|
|
Instruction::DUP2,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI
|
|
};
|
|
checkCSE(input, {
|
|
u256(0),
|
|
u256(1)
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_jumpi_jump)
|
|
{
|
|
AssemblyItems input{
|
|
u256(1),
|
|
u256(1),
|
|
Instruction::DUP2,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI
|
|
};
|
|
checkCSE(input, {
|
|
u256(1),
|
|
Instruction::DUP1,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_empty_keccak256)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0),
|
|
Instruction::DUP2,
|
|
Instruction::KECCAK256
|
|
};
|
|
checkCSE(input, {
|
|
u256(util::keccak256(bytesConstRef()))
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_partial_keccak256)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0xabcd) << (256 - 16),
|
|
u256(0),
|
|
Instruction::MSTORE,
|
|
u256(2),
|
|
u256(0),
|
|
Instruction::KECCAK256
|
|
};
|
|
checkCSE(input, {
|
|
u256(0xabcd) << (256 - 16),
|
|
u256(0),
|
|
Instruction::MSTORE,
|
|
u256(util::keccak256(bytes{0xab, 0xcd}))
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_location)
|
|
{
|
|
// Keccak-256 twice from same dynamic location
|
|
AssemblyItems input{
|
|
Instruction::DUP2,
|
|
Instruction::DUP1,
|
|
Instruction::MSTORE,
|
|
u256(64),
|
|
Instruction::DUP2,
|
|
Instruction::KECCAK256,
|
|
u256(64),
|
|
Instruction::DUP3,
|
|
Instruction::KECCAK256
|
|
};
|
|
checkCSE(input, {
|
|
Instruction::DUP2,
|
|
Instruction::DUP1,
|
|
Instruction::MSTORE,
|
|
u256(64),
|
|
Instruction::DUP2,
|
|
Instruction::KECCAK256,
|
|
Instruction::DUP1
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content)
|
|
{
|
|
// Keccak-256 twice from different dynamic location but with same content
|
|
AssemblyItems input{
|
|
Instruction::DUP1,
|
|
u256(0x80),
|
|
Instruction::MSTORE, // m[128] = DUP1
|
|
u256(0x20),
|
|
u256(0x80),
|
|
Instruction::KECCAK256, // keccak256(m[128..(128+32)])
|
|
Instruction::DUP2,
|
|
u256(12),
|
|
Instruction::MSTORE, // m[12] = DUP1
|
|
u256(0x20),
|
|
u256(12),
|
|
Instruction::KECCAK256 // keccak256(m[12..(12+32)])
|
|
};
|
|
checkCSE(input, {
|
|
u256(0x80),
|
|
Instruction::DUP2,
|
|
Instruction::DUP2,
|
|
Instruction::MSTORE,
|
|
u256(0x20),
|
|
Instruction::SWAP1,
|
|
Instruction::KECCAK256,
|
|
u256(12),
|
|
Instruction::DUP3,
|
|
Instruction::SWAP1,
|
|
Instruction::MSTORE,
|
|
Instruction::DUP1
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_dynamic_store_in_between)
|
|
{
|
|
// Keccak-256 twice from different dynamic location but with same content,
|
|
// dynamic mstore in between, which forces us to re-calculate the hash
|
|
AssemblyItems input{
|
|
u256(0x80),
|
|
Instruction::DUP2,
|
|
Instruction::DUP2,
|
|
Instruction::MSTORE, // m[128] = DUP1
|
|
u256(0x20),
|
|
Instruction::DUP1,
|
|
Instruction::DUP3,
|
|
Instruction::KECCAK256, // keccak256(m[128..(128+32)])
|
|
u256(12),
|
|
Instruction::DUP5,
|
|
Instruction::DUP2,
|
|
Instruction::MSTORE, // m[12] = DUP1
|
|
Instruction::DUP12,
|
|
Instruction::DUP14,
|
|
Instruction::MSTORE, // destroys memory knowledge
|
|
Instruction::SWAP2,
|
|
Instruction::SWAP1,
|
|
Instruction::SWAP2,
|
|
Instruction::KECCAK256 // keccak256(m[12..(12+32)])
|
|
};
|
|
checkCSE(input, input);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_keccak256_twice_same_content_noninterfering_store_in_between)
|
|
{
|
|
// Keccak-256 twice from different dynamic location but with same content,
|
|
// dynamic mstore in between, but does not force us to re-calculate the hash
|
|
AssemblyItems input{
|
|
u256(0x80),
|
|
Instruction::DUP2,
|
|
Instruction::DUP2,
|
|
Instruction::MSTORE, // m[128] = DUP1
|
|
u256(0x20),
|
|
Instruction::DUP1,
|
|
Instruction::DUP3,
|
|
Instruction::KECCAK256, // keccak256(m[128..(128+32)])
|
|
u256(12),
|
|
Instruction::DUP5,
|
|
Instruction::DUP2,
|
|
Instruction::MSTORE, // m[12] = DUP1
|
|
Instruction::DUP12,
|
|
u256(12 + 32),
|
|
Instruction::MSTORE, // does not destroy memory knowledge
|
|
Instruction::DUP13,
|
|
u256(128 - 32),
|
|
Instruction::MSTORE, // does not destroy memory knowledge
|
|
u256(0x20),
|
|
u256(12),
|
|
Instruction::KECCAK256 // keccak256(m[12..(12+32)])
|
|
};
|
|
// if this changes too often, only count the number of SHA3 and MSTORE instructions
|
|
AssemblyItems output = CSE(input);
|
|
BOOST_CHECK_EQUAL(4, count(output.begin(), output.end(), AssemblyItem(Instruction::MSTORE)));
|
|
BOOST_CHECK_EQUAL(1, count(output.begin(), output.end(), AssemblyItem(Instruction::KECCAK256)));
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_with_initially_known_stack)
|
|
{
|
|
evmasm::KnownState state = createInitialState(AssemblyItems{
|
|
u256(0x12),
|
|
u256(0x20),
|
|
Instruction::ADD
|
|
});
|
|
AssemblyItems input{
|
|
u256(0x12 + 0x20)
|
|
};
|
|
checkCSE(input, AssemblyItems{Instruction::DUP1}, state);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_equality_on_initially_known_stack)
|
|
{
|
|
evmasm::KnownState state = createInitialState(AssemblyItems{Instruction::DUP1});
|
|
AssemblyItems input{
|
|
Instruction::EQ
|
|
};
|
|
AssemblyItems output = CSE(input, state);
|
|
// check that it directly pushes 1 (true)
|
|
BOOST_CHECK(find(output.begin(), output.end(), AssemblyItem(u256(1))) != output.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_access_previous_sequence)
|
|
{
|
|
// Tests that the code generator detects whether it tries to access SLOAD instructions
|
|
// from a sequenced expression which is not in its scope.
|
|
evmasm::KnownState state = createInitialState(AssemblyItems{
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
u256(1),
|
|
Instruction::ADD,
|
|
u256(0),
|
|
Instruction::SSTORE
|
|
});
|
|
// now stored: val_1 + 1 (value at sequence 1)
|
|
// if in the following instructions, the SLOAD cresolves to "val_1 + 1",
|
|
// this cannot be generated because we cannot load from sequence 1 anymore.
|
|
AssemblyItems input{
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
};
|
|
BOOST_CHECK_THROW(CSE(input, state), StackTooDeepException);
|
|
// @todo for now, this throws an exception, but it should recover to the following
|
|
// (or an even better version) at some point:
|
|
// 0, SLOAD, 1, ADD, SSTORE, 0 SLOAD
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_optimise_return)
|
|
{
|
|
checkCSE(
|
|
AssemblyItems{u256(0), u256(7), Instruction::RETURN},
|
|
AssemblyItems{Instruction::STOP}
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused)
|
|
{
|
|
// remove parts of the code that are unused
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
u256(7),
|
|
AssemblyItem(Tag, 1),
|
|
};
|
|
checkCFG(input, {});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(control_flow_graph_remove_unused_loop)
|
|
{
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 3),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(7),
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(8),
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 3),
|
|
u256(11)
|
|
};
|
|
checkCFG(input, {u256(11)});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(control_flow_graph_reconnect_single_jump_source)
|
|
{
|
|
// move code that has only one unconditional jump source
|
|
AssemblyItems input{
|
|
u256(1),
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(2),
|
|
AssemblyItem(PushTag, 3),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(3),
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 3),
|
|
u256(4),
|
|
};
|
|
checkCFG(input, {u256(1), u256(3), u256(2), u256(4)});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(control_flow_graph_do_not_remove_returned_to)
|
|
{
|
|
// do not remove parts that are "returned to"
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(2)
|
|
};
|
|
checkCFG(input, {u256(2)});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(block_deduplicator)
|
|
{
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 2),
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 3),
|
|
u256(6),
|
|
Instruction::SWAP3,
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(6),
|
|
Instruction::SWAP3,
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(6),
|
|
Instruction::SWAP3,
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 3)
|
|
};
|
|
BlockDeduplicator deduplicator(input);
|
|
deduplicator.deduplicate();
|
|
|
|
std::set<u256> pushTags;
|
|
for (AssemblyItem const& item: input)
|
|
if (item.type() == PushTag)
|
|
pushTags.insert(item.data());
|
|
BOOST_CHECK_EQUAL(pushTags.size(), 2);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_same)
|
|
{
|
|
AssemblyItems blocks{
|
|
AssemblyItem(Tag, 1),
|
|
u256(42),
|
|
AssemblyItem{AssignImmutable, 0x1234},
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(42),
|
|
AssemblyItem{AssignImmutable, 0x1234},
|
|
Instruction::JUMP
|
|
};
|
|
|
|
AssemblyItems input = AssemblyItems{
|
|
AssemblyItem(PushTag, 2),
|
|
AssemblyItem(PushTag, 1),
|
|
} + blocks;
|
|
AssemblyItems output = AssemblyItems{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 1),
|
|
} + blocks;
|
|
BlockDeduplicator deduplicator(input);
|
|
deduplicator.deduplicate();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_value)
|
|
{
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 2),
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(Tag, 1),
|
|
u256(42),
|
|
AssemblyItem{AssignImmutable, 0x1234},
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(23),
|
|
AssemblyItem{AssignImmutable, 0x1234},
|
|
Instruction::JUMP
|
|
};
|
|
BlockDeduplicator deduplicator(input);
|
|
BOOST_CHECK(!deduplicator.deduplicate());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(block_deduplicator_assign_immutable_different_hash)
|
|
{
|
|
AssemblyItems input{
|
|
AssemblyItem(PushTag, 2),
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(Tag, 1),
|
|
u256(42),
|
|
AssemblyItem{AssignImmutable, 0x1234},
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(42),
|
|
AssemblyItem{AssignImmutable, 0xABCD},
|
|
Instruction::JUMP
|
|
};
|
|
BlockDeduplicator deduplicator(input);
|
|
BOOST_CHECK(!deduplicator.deduplicate());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(block_deduplicator_loops)
|
|
{
|
|
AssemblyItems input{
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMPI,
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(5),
|
|
u256(6),
|
|
Instruction::SSTORE,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(5),
|
|
u256(6),
|
|
Instruction::SSTORE,
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMP,
|
|
};
|
|
BlockDeduplicator deduplicator(input);
|
|
deduplicator.deduplicate();
|
|
|
|
std::set<u256> pushTags;
|
|
for (AssemblyItem const& item: input)
|
|
if (item.type() == PushTag)
|
|
pushTags.insert(item.data());
|
|
BOOST_CHECK_EQUAL(pushTags.size(), 1);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(clear_unreachable_code)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
u256(0),
|
|
Instruction::SLOAD,
|
|
AssemblyItem(Tag, 2),
|
|
u256(5),
|
|
u256(6),
|
|
Instruction::SSTORE,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
u256(5),
|
|
u256(6)
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 2),
|
|
u256(5),
|
|
u256(6),
|
|
Instruction::SSTORE,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_double_push)
|
|
{
|
|
AssemblyItems items{
|
|
u256(0),
|
|
u256(0),
|
|
u256(5),
|
|
u256(5),
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
AssemblyItems expectation{
|
|
u256(0),
|
|
Instruction::DUP1,
|
|
u256(5),
|
|
Instruction::DUP1,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_pop_calldatasize)
|
|
{
|
|
AssemblyItems items{
|
|
u256(4),
|
|
Instruction::CALLDATASIZE,
|
|
Instruction::LT,
|
|
Instruction::POP
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
for (size_t i = 0; i < 3; i++)
|
|
BOOST_CHECK(peepOpt.optimise());
|
|
BOOST_CHECK(items.empty());
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_commutative_swap1)
|
|
{
|
|
std::vector<Instruction> ops{
|
|
Instruction::ADD,
|
|
Instruction::MUL,
|
|
Instruction::EQ,
|
|
Instruction::AND,
|
|
Instruction::OR,
|
|
Instruction::XOR
|
|
};
|
|
for (Instruction const op: ops)
|
|
{
|
|
AssemblyItems items{
|
|
u256(1),
|
|
u256(2),
|
|
Instruction::SWAP1,
|
|
op,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
AssemblyItems expectation{
|
|
u256(1),
|
|
u256(2),
|
|
op,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_noncommutative_swap1)
|
|
{
|
|
// NOTE: not comprehensive
|
|
std::vector<Instruction> ops{
|
|
Instruction::SUB,
|
|
Instruction::DIV,
|
|
Instruction::SDIV,
|
|
Instruction::MOD,
|
|
Instruction::SMOD,
|
|
Instruction::EXP
|
|
};
|
|
for (Instruction const op: ops)
|
|
{
|
|
AssemblyItems items{
|
|
u256(1),
|
|
u256(2),
|
|
Instruction::SWAP1,
|
|
op,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
AssemblyItems expectation{
|
|
u256(1),
|
|
u256(2),
|
|
Instruction::SWAP1,
|
|
op,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(!peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_swap_comparison)
|
|
{
|
|
std::map<Instruction, Instruction> swappableOps{
|
|
{ Instruction::LT, Instruction::GT },
|
|
{ Instruction::GT, Instruction::LT },
|
|
{ Instruction::SLT, Instruction::SGT },
|
|
{ Instruction::SGT, Instruction::SLT }
|
|
};
|
|
|
|
for (auto const& op: swappableOps)
|
|
{
|
|
AssemblyItems items{
|
|
u256(1),
|
|
u256(2),
|
|
Instruction::SWAP1,
|
|
op.first,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
AssemblyItems expectation{
|
|
u256(1),
|
|
u256(2),
|
|
op.second,
|
|
u256(4),
|
|
u256(5)
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_truthy_and)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::BALANCE,
|
|
u256(0),
|
|
Instruction::NOT,
|
|
Instruction::AND,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::BALANCE,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(peephole_iszero_iszero_jumpi)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(Tag, 1),
|
|
u256(0),
|
|
Instruction::CALLDATALOAD,
|
|
Instruction::ISZERO,
|
|
Instruction::ISZERO,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI,
|
|
u256(0),
|
|
u256(0x20),
|
|
Instruction::RETURN
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(Tag, 1),
|
|
u256(0),
|
|
Instruction::CALLDATALOAD,
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI,
|
|
u256(0),
|
|
u256(0x20),
|
|
Instruction::RETURN
|
|
};
|
|
PeepholeOptimiser peepOpt(items);
|
|
BOOST_REQUIRE(peepOpt.optimise());
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(jumpdest_removal)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(Tag, 2),
|
|
AssemblyItem(PushTag, 1),
|
|
u256(5),
|
|
AssemblyItem(Tag, 10),
|
|
AssemblyItem(Tag, 3),
|
|
u256(6),
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::JUMP,
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(PushTag, 1),
|
|
u256(5),
|
|
u256(6),
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::JUMP
|
|
};
|
|
JumpdestRemover jdr(items);
|
|
BOOST_REQUIRE(jdr.optimise({}));
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(jumpdest_removal_subassemblies)
|
|
{
|
|
// This tests that tags from subassemblies are not removed
|
|
// if they are referenced by a super-assembly. Furthermore,
|
|
// tag unifications (due to block deduplication) is also
|
|
// visible at the super-assembly.
|
|
|
|
Assembly::OptimiserSettings settings;
|
|
settings.runInliner = false;
|
|
settings.runJumpdestRemover = true;
|
|
settings.runPeephole = true;
|
|
settings.runDeduplicate = true;
|
|
settings.runCSE = true;
|
|
settings.runConstantOptimiser = true;
|
|
settings.evmVersion = solidity::test::CommonOptions::get().evmVersion();
|
|
settings.expectedExecutionsPerDeployment = OptimiserSettings{}.expectedExecutionsPerDeployment;
|
|
|
|
Assembly main{settings.evmVersion, false, {}};
|
|
AssemblyPointer sub = std::make_shared<Assembly>(settings.evmVersion, true, std::string{});
|
|
|
|
sub->append(u256(1));
|
|
auto t1 = sub->newTag();
|
|
sub->append(t1);
|
|
sub->append(u256(2));
|
|
sub->append(Instruction::JUMP);
|
|
auto t2 = sub->newTag();
|
|
sub->append(t2); // Identical to T1, will be unified
|
|
sub->append(u256(2));
|
|
sub->append(Instruction::JUMP);
|
|
auto t3 = sub->newTag();
|
|
sub->append(t3);
|
|
auto t4 = sub->newTag();
|
|
sub->append(t4);
|
|
auto t5 = sub->newTag();
|
|
sub->append(t5); // This will be removed
|
|
sub->append(u256(7));
|
|
sub->append(t4.pushTag());
|
|
sub->append(Instruction::JUMP);
|
|
|
|
size_t subId = static_cast<size_t>(main.appendSubroutine(sub).data());
|
|
main.append(t1.toSubAssemblyTag(subId));
|
|
main.append(t1.toSubAssemblyTag(subId));
|
|
main.append(u256(8));
|
|
|
|
main.optimise(settings);
|
|
|
|
AssemblyItems expectationMain{
|
|
AssemblyItem(PushSubSize, 0),
|
|
t1.toSubAssemblyTag(subId).pushTag(),
|
|
t1.toSubAssemblyTag(subId).pushTag(),
|
|
u256(8)
|
|
};
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
main.items().begin(), main.items().end(),
|
|
expectationMain.begin(), expectationMain.end()
|
|
);
|
|
|
|
AssemblyItems expectationSub{
|
|
u256(1), t1.tag(), u256(2), Instruction::JUMP, t4.tag(), u256(7), t4.pushTag(), Instruction::JUMP
|
|
};
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
sub->items().begin(), sub->items().end(),
|
|
expectationSub.begin(), expectationSub.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_sub_zero)
|
|
{
|
|
checkCSE({
|
|
u256(0),
|
|
Instruction::DUP2,
|
|
Instruction::SUB
|
|
}, {
|
|
Instruction::DUP1
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::DUP1,
|
|
u256(0),
|
|
Instruction::SUB
|
|
}, {
|
|
u256(0),
|
|
Instruction::DUP2,
|
|
Instruction::SWAP1,
|
|
Instruction::SUB
|
|
});
|
|
}
|
|
|
|
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
|
|
});
|
|
std::map<int, unsigned> 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())
|
|
return;
|
|
|
|
for (unsigned i = 1; i < 256; i++)
|
|
{
|
|
checkCSE({
|
|
u256(boost::multiprecision::pow(u256(2), i) - 1),
|
|
Instruction::CALLVALUE,
|
|
u256(256-i),
|
|
Instruction::SHR,
|
|
Instruction::AND
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256(256-i),
|
|
Instruction::SHR,
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(256-i),
|
|
Instruction::SHR,
|
|
u256(boost::multiprecision::pow(u256(2), i)-1),
|
|
Instruction::AND
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256(256-i),
|
|
Instruction::SHR,
|
|
});
|
|
}
|
|
|
|
// Check that opt. does NOT trigger
|
|
for (unsigned i = 1; i < 255; i++)
|
|
{
|
|
checkCSE({
|
|
u256(boost::multiprecision::pow(u256(2), i) - 1),
|
|
Instruction::CALLVALUE,
|
|
u256(255-i),
|
|
Instruction::SHR,
|
|
Instruction::AND
|
|
}, { // Opt. did some reordering
|
|
Instruction::CALLVALUE,
|
|
u256(255-i),
|
|
Instruction::SHR,
|
|
u256(boost::multiprecision::pow(u256(2), i)-1),
|
|
Instruction::AND
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(255-i),
|
|
Instruction::SHR,
|
|
u256(boost::multiprecision::pow(u256(2), i)-1),
|
|
Instruction::AND
|
|
}, { // Opt. did some reordering
|
|
u256(boost::multiprecision::pow(u256(2), i)-1),
|
|
Instruction::CALLVALUE,
|
|
u256(255-i),
|
|
Instruction::SHR,
|
|
Instruction::AND
|
|
});
|
|
}
|
|
|
|
//(x >> (31*8)) & 0xffffffff
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(31*8),
|
|
Instruction::SHR,
|
|
u256(0xffffffff),
|
|
Instruction::AND
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256(31*8),
|
|
Instruction::SHR
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_remove_unwanted_masking_of_address)
|
|
{
|
|
std::vector<Instruction> ops{
|
|
Instruction::ADDRESS,
|
|
Instruction::CALLER,
|
|
Instruction::ORIGIN,
|
|
Instruction::COINBASE
|
|
};
|
|
for (auto const& op: ops)
|
|
{
|
|
checkCSE({
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
op,
|
|
Instruction::AND
|
|
}, {
|
|
op
|
|
});
|
|
|
|
checkCSE({
|
|
op,
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
Instruction::AND
|
|
}, {
|
|
op
|
|
});
|
|
|
|
// do not remove mask for other masking
|
|
checkCSE({
|
|
u256(1234),
|
|
op,
|
|
Instruction::AND
|
|
}, {
|
|
op,
|
|
u256(1234),
|
|
Instruction::AND
|
|
});
|
|
|
|
checkCSE({
|
|
op,
|
|
u256(1234),
|
|
Instruction::AND
|
|
}, {
|
|
u256(1234),
|
|
op,
|
|
Instruction::AND
|
|
});
|
|
}
|
|
|
|
// leave other opcodes untouched
|
|
checkCSE({
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
Instruction::CALLVALUE,
|
|
Instruction::AND
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
Instruction::AND
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
Instruction::AND
|
|
}, {
|
|
u256("0xffffffffffffffffffffffffffffffffffffffff"),
|
|
Instruction::CALLVALUE,
|
|
Instruction::AND
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(cse_replace_too_large_shift)
|
|
{
|
|
if (!solidity::test::CommonOptions::get().evmVersion().hasBitwiseShifting())
|
|
return;
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(299),
|
|
Instruction::SHL
|
|
}, {
|
|
u256(0)
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(299),
|
|
Instruction::SHR
|
|
}, {
|
|
u256(0)
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(255),
|
|
Instruction::SHL
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256(255),
|
|
Instruction::SHL
|
|
});
|
|
|
|
checkCSE({
|
|
Instruction::CALLVALUE,
|
|
u256(255),
|
|
Instruction::SHR
|
|
}, {
|
|
Instruction::CALLVALUE,
|
|
u256(255),
|
|
Instruction::SHR
|
|
});
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner)
|
|
{
|
|
AssemblyItem jumpInto{Instruction::JUMP};
|
|
jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction);
|
|
AssemblyItem jumpOutOf{Instruction::JUMP};
|
|
jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction);
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
jumpInto,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
Instruction::CALLVALUE,
|
|
Instruction::SWAP1,
|
|
jumpOutOf,
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::CALLVALUE,
|
|
Instruction::SWAP1,
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
Instruction::CALLVALUE,
|
|
Instruction::SWAP1,
|
|
jumpOutOf,
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_no_inline_type)
|
|
{
|
|
// Will not inline due to jump types.
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
Instruction::CALLVALUE,
|
|
Instruction::SWAP1,
|
|
Instruction::JUMP,
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
items.begin(), items.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_no_inline)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::CALLVALUE,
|
|
Instruction::JUMPI,
|
|
Instruction::JUMP,
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::CALLVALUE,
|
|
Instruction::JUMPI,
|
|
Instruction::JUMP,
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_single_jump)
|
|
{
|
|
AssemblyItem jumpInto{Instruction::JUMP};
|
|
jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction);
|
|
AssemblyItem jumpOutOf{Instruction::JUMP};
|
|
jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction);
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
jumpInto,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
jumpOutOf,
|
|
};
|
|
AssemblyItems expectation{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
jumpOutOf,
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_end_of_bytecode)
|
|
{
|
|
AssemblyItem jumpInto{Instruction::JUMP};
|
|
jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction);
|
|
// Cannot inline, since the block at Tag_2 does not end in a jump.
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
jumpInto,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
items.begin(), items.end()
|
|
);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_cse_break)
|
|
{
|
|
AssemblyItem jumpInto{Instruction::JUMP};
|
|
jumpInto.setJumpType(AssemblyItem::JumpType::IntoFunction);
|
|
AssemblyItem jumpOutOf{Instruction::JUMP};
|
|
jumpOutOf.setJumpType(AssemblyItem::JumpType::OutOfFunction);
|
|
// Could be inlined, but we only consider non-CSE-breaking blocks ending in JUMP so far.
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
AssemblyItem(PushTag, 2),
|
|
jumpInto,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 2),
|
|
Instruction::STOP, // CSE breaking instruction
|
|
jumpOutOf
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
items.begin(), items.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_stop)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP
|
|
};
|
|
AssemblyItems expectation{
|
|
Instruction::STOP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_stop_jumpi)
|
|
{
|
|
// Because of `jumpi`, will not be inlined.
|
|
AssemblyItems items{
|
|
u256(1),
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMPI,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::STOP
|
|
};
|
|
AssemblyItems expectation = items;
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_revert)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(0),
|
|
Instruction::DUP1,
|
|
Instruction::REVERT
|
|
};
|
|
AssemblyItems expectation{
|
|
u256(0),
|
|
Instruction::DUP1,
|
|
Instruction::REVERT,
|
|
AssemblyItem(Tag, 1),
|
|
u256(0),
|
|
Instruction::DUP1,
|
|
Instruction::REVERT
|
|
};
|
|
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_revert_increased_datagas)
|
|
{
|
|
// Inlining this would increase data gas (5 bytes v/s 4 bytes), therefore, skipped.
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
u256(0),
|
|
u256(0),
|
|
Instruction::REVERT
|
|
};
|
|
|
|
AssemblyItems expectation = items;
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(inliner_invalid)
|
|
{
|
|
AssemblyItems items{
|
|
AssemblyItem(PushTag, 1),
|
|
Instruction::JUMP,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::INVALID
|
|
};
|
|
|
|
AssemblyItems expectation = {
|
|
Instruction::INVALID,
|
|
AssemblyItem(Tag, 1),
|
|
Instruction::INVALID
|
|
};
|
|
Inliner{items, {}, Assembly::OptimiserSettings{}.expectedExecutionsPerDeployment, false, {}}.optimise();
|
|
BOOST_CHECK_EQUAL_COLLECTIONS(
|
|
items.begin(), items.end(),
|
|
expectation.begin(), expectation.end()
|
|
);
|
|
}
|
|
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|
|
|
|
} // end namespaces
|