From fdd2edd60d44e9a499b5e8a007e631db9b3d4ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Sun, 23 Jun 2019 20:35:21 +0200 Subject: [PATCH 1/4] Introduce BEGINBLOCK intrinsic instruction --- lib/evmone/analysis.cpp | 17 +++++++++++---- lib/evmone/analysis.hpp | 17 ++++++++++++++- lib/evmone/execution.cpp | 25 ---------------------- lib/evmone/instructions.cpp | 42 +++++++++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/lib/evmone/analysis.cpp b/lib/evmone/analysis.cpp index dbe6e7a722..c60972770a 100644 --- a/lib/evmone/analysis.cpp +++ b/lib/evmone/analysis.cpp @@ -17,7 +17,7 @@ bool is_terminator(uint8_t c) noexcept } } // namespace -int code_analysis::find_jumpdest(int offset) noexcept +int code_analysis::find_jumpdest(int offset) const noexcept { // TODO: Replace with lower_bound(). for (const auto& d : jumpdest_map) @@ -51,6 +51,7 @@ code_analysis analyze( const exec_fn_table& fns, evmc_revision rev, const uint8_t* code, size_t code_size) noexcept { code_analysis analysis; + // TODO: Check if final result exceeds the reservation. analysis.instrs.reserve(code_size + 1); auto* instr_table = evmc_get_instruction_metrics_table(rev); @@ -61,19 +62,27 @@ code_analysis analyze( { // TODO: Loop in reverse order for easier GAS analysis. const auto c = code[i]; - auto& instr = analysis.instrs.emplace_back(fns[c]); const bool jumpdest = c == OP_JUMPDEST; + if (!block || jumpdest) { // Create new block. block = &analysis.blocks.emplace_back(); - instr.block_index = static_cast(analysis.blocks.size() - 1); - if (jumpdest) + // Create BEGINBLOCK instruction which either replaces JUMPDEST or is injected + // in case there is no JUMPDEST. + auto& beginblock_instr = analysis.instrs.emplace_back(fns[OPX_BEGINBLOCK]); + beginblock_instr.arg.p.number = static_cast(analysis.blocks.size() - 1); + + if (jumpdest) // Add the jumpdest to the map. analysis.jumpdest_map.emplace_back(static_cast(i), instr_index); + else // Increase instruction count because additional BEGINBLOCK was injected. + ++instr_index; } + auto& instr = jumpdest ? analysis.instrs.back() : analysis.instrs.emplace_back(fns[c]); + auto metrics = instr_table[c]; block->gas_cost += metrics.gas_cost; auto stack_req = metrics.num_stack_arguments - block->stack_diff; diff --git a/lib/evmone/analysis.hpp b/lib/evmone/analysis.hpp index d62b881cb6..d1d4f35a20 100644 --- a/lib/evmone/analysis.hpp +++ b/lib/evmone/analysis.hpp @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include #include @@ -66,6 +67,20 @@ static_assert(sizeof(instr_argument) == sizeof(void*), "Incorrect size of instr_ using exec_fn = void (*)(execution_state&, instr_argument arg); +/// The evmone intrinsic opcodes. +/// +/// These intrinsic instructions may be injected to the code in the analysis phase. +/// They contain additional and required logic to be executed by the interpreter. +enum intrinsic_opcodes +{ + /// The BEGINBLOCK instruction. + /// + /// This instruction is defined as alias for JUMPDEST and replaces all JUMPDEST instructions. + /// It is also injected at beginning of basic blocks not being the valid jump destination. + /// It checks basic block execution requirements and terminates execution if they are not met. + OPX_BEGINBLOCK = OP_JUMPDEST +}; + using exec_fn_table = std::array; struct instr_info @@ -99,7 +114,7 @@ struct code_analysis std::vector> jumpdest_map; // TODO: Exported for unit tests. Rework unit tests? - EVMC_EXPORT int find_jumpdest(int offset) noexcept; + EVMC_EXPORT int find_jumpdest(int offset) const noexcept; }; EVMC_EXPORT code_analysis analyze( diff --git a/lib/evmone/execution.cpp b/lib/evmone/execution.cpp index 603a582b5c..4127bed90d 100644 --- a/lib/evmone/execution.cpp +++ b/lib/evmone/execution.cpp @@ -29,31 +29,6 @@ evmc_result execute(evmc_instance*, evmc_context* ctx, evmc_revision rev, const while (state.run) { auto& instr = analysis.instrs[state.pc]; - if (instr.block_index >= 0) - { - auto& block = analysis.blocks[static_cast(instr.block_index)]; - - state.gas_left -= block.gas_cost; - if (state.gas_left < 0) - { - state.status = EVMC_OUT_OF_GAS; - break; - } - - if (static_cast(state.stack.size()) < block.stack_req) - { - state.status = EVMC_STACK_UNDERFLOW; - break; - } - - if (static_cast(state.stack.size()) + block.stack_max > 1024) - { - state.status = EVMC_STACK_OVERFLOW; - break; - } - - state.current_block_cost = block.gas_cost; - } // Advance the PC not to allow jump opcodes to overwrite it. ++state.pc; diff --git a/lib/evmone/instructions.cpp b/lib/evmone/instructions.cpp index 45a67dfd83..bf9714cc39 100644 --- a/lib/evmone/instructions.cpp +++ b/lib/evmone/instructions.cpp @@ -8,6 +8,8 @@ #include #include +#include + namespace evmone { namespace @@ -576,6 +578,9 @@ void op_jumpi(execution_state& state, instr_argument) noexcept state.pc = static_cast(pc); } + // OPT: The pc must be the BEGINBLOCK (even in fallback case), + // so we can execute it straight away. + state.stack.pop_back(); state.stack.pop_back(); } @@ -597,11 +602,6 @@ void op_gas(execution_state& state, instr_argument arg) noexcept state.stack.push_back(gas); } -void op_jumpdest(execution_state&, instr_argument) noexcept -{ - // OPT: We can skip JUMPDEST instruction in analysis. -} - void op_gasprice(execution_state& state, instr_argument) noexcept { auto x = intx::be::uint256(state.host.get_tx_context().tx_gas_price.bytes); @@ -1359,6 +1359,36 @@ void op_selfdestruct(execution_state& state, instr_argument) noexcept state.run = false; } +void opx_beginblock(execution_state& state, instr_argument arg) noexcept +{ + assert(arg.p.number >= 0); + auto& block = state.analysis->blocks[static_cast(arg.p.number)]; + + state.gas_left -= block.gas_cost; + if (state.gas_left < 0) + { + state.status = EVMC_OUT_OF_GAS; + state.run = false; + return; + } + + if (static_cast(state.stack.size()) < block.stack_req) + { + state.status = EVMC_STACK_UNDERFLOW; + state.run = false; + return; + } + + if (static_cast(state.stack.size()) + block.stack_max > 1024) + { + state.status = EVMC_STACK_OVERFLOW; + state.run = false; + return; + } + + state.current_block_cost = block.gas_cost; +} + constexpr exec_fn_table create_op_table_frontier() noexcept { auto table = exec_fn_table{}; @@ -1421,7 +1451,7 @@ constexpr exec_fn_table create_op_table_frontier() noexcept table[OP_PC] = op_pc; table[OP_MSIZE] = op_msize; table[OP_GAS] = op_gas; - table[OP_JUMPDEST] = op_jumpdest; + table[OPX_BEGINBLOCK] = opx_beginblock; // Replaces JUMPDEST. for (auto op = size_t{OP_PUSH1}; op <= OP_PUSH32; ++op) table[op] = op_push_full; for (auto op = size_t{OP_DUP1}; op <= OP_DUP16; ++op) From bcac7bb7ce0517295209c293f3070566baaccfb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 24 Jun 2019 11:28:13 +0200 Subject: [PATCH 2/4] test: Update analysis tests to expect BEGINBLOCK instruction --- test/unittests/analysis_test.cpp | 57 ++++++++++++++++++-------------- test/utils/bytecode.hpp | 10 ++++++ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/test/unittests/analysis_test.cpp b/test/unittests/analysis_test.cpp index f80090a5ff..06209a757e 100644 --- a/test/unittests/analysis_test.cpp +++ b/test/unittests/analysis_test.cpp @@ -5,8 +5,11 @@ #include #include #include +#include #include +using namespace evmone; + constexpr auto rev = EVMC_BYZANTIUM; const auto fake_fn_table = []() noexcept @@ -21,17 +24,20 @@ const auto fake_fn_table = []() noexcept TEST(analysis, example1) { - auto code = from_hex("602a601e5359600055"); - auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size()); + const auto code = push(0x2a) + push(0x1e) + OP_MSTORE8 + OP_MSIZE + push(0) + OP_SSTORE; + const auto analysis = analyze(fake_fn_table, rev, &code[0], code.size()); - ASSERT_EQ(analysis.instrs.size(), 7); + ASSERT_EQ(analysis.instrs.size(), 8); - EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OP_PUSH1]); + EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OPX_BEGINBLOCK]); + EXPECT_EQ(analysis.instrs[0].arg.p.number, 0); EXPECT_EQ(analysis.instrs[1].fn, fake_fn_table[OP_PUSH1]); - EXPECT_EQ(analysis.instrs[2].fn, fake_fn_table[OP_MSTORE8]); - EXPECT_EQ(analysis.instrs[3].fn, fake_fn_table[OP_MSIZE]); - EXPECT_EQ(analysis.instrs[4].fn, fake_fn_table[OP_PUSH1]); - EXPECT_EQ(analysis.instrs[5].fn, fake_fn_table[OP_SSTORE]); + EXPECT_EQ(analysis.instrs[2].fn, fake_fn_table[OP_PUSH1]); + EXPECT_EQ(analysis.instrs[3].fn, fake_fn_table[OP_MSTORE8]); + EXPECT_EQ(analysis.instrs[4].fn, fake_fn_table[OP_MSIZE]); + EXPECT_EQ(analysis.instrs[5].fn, fake_fn_table[OP_PUSH1]); + EXPECT_EQ(analysis.instrs[6].fn, fake_fn_table[OP_SSTORE]); + EXPECT_EQ(analysis.instrs[7].fn, fake_fn_table[OP_STOP]); ASSERT_EQ(analysis.blocks.size(), 1); EXPECT_EQ(analysis.blocks[0].gas_cost, 14); @@ -42,14 +48,16 @@ TEST(analysis, example1) TEST(analysis, stack_up_and_down) { - auto code = from_hex("81808080808080505050505050505050506000"); - auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size()); + const auto code = OP_DUP2 + 6 * OP_DUP1 + 10 * OP_POP + push(0); + const auto analysis = analyze(fake_fn_table, rev, &code[0], code.size()); - ASSERT_EQ(analysis.instrs.size(), 19); - EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OP_DUP2]); - EXPECT_EQ(analysis.instrs[1].fn, fake_fn_table[OP_DUP1]); - EXPECT_EQ(analysis.instrs[7].fn, fake_fn_table[OP_POP]); - EXPECT_EQ(analysis.instrs[17].fn, fake_fn_table[OP_PUSH1]); + ASSERT_EQ(analysis.instrs.size(), 20); + EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OPX_BEGINBLOCK]); + EXPECT_EQ(analysis.instrs[0].arg.p.number, 0); + EXPECT_EQ(analysis.instrs[1].fn, fake_fn_table[OP_DUP2]); + EXPECT_EQ(analysis.instrs[2].fn, fake_fn_table[OP_DUP1]); + EXPECT_EQ(analysis.instrs[8].fn, fake_fn_table[OP_POP]); + EXPECT_EQ(analysis.instrs[18].fn, fake_fn_table[OP_PUSH1]); ASSERT_EQ(analysis.blocks.size(), 1); EXPECT_EQ(analysis.blocks[0].gas_cost, 7 * 3 + 10 * 2 + 3); @@ -60,26 +68,27 @@ TEST(analysis, stack_up_and_down) TEST(analysis, push) { - auto code = from_hex("6708070605040302017f00ee"); - auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size()); + const auto code = push(0x0807060504030201) + "7f00ee"; + const auto analysis = analyze(fake_fn_table, rev, &code[0], code.size()); - ASSERT_EQ(analysis.instrs.size(), 3); + ASSERT_EQ(analysis.instrs.size(), 4); ASSERT_EQ(analysis.args_storage.size(), 2); - EXPECT_EQ(analysis.instrs[0].arg.data, &analysis.args_storage[0][0]); - EXPECT_EQ(analysis.instrs[1].arg.data, &analysis.args_storage[1][0]); + EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OPX_BEGINBLOCK]); + EXPECT_EQ(analysis.instrs[1].arg.data, &analysis.args_storage[0][0]); + EXPECT_EQ(analysis.instrs[2].arg.data, &analysis.args_storage[1][0]); EXPECT_EQ(analysis.args_storage[0][31 - 7], 0x08); EXPECT_EQ(analysis.args_storage[1][1], 0xee); } TEST(analysis, jump1) { - auto code = from_hex("6002600401565b600360005260206000f3600656"); - auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size()); + const auto code = jump(add(4, 2)) + OP_JUMPDEST + mstore(0, 3) + ret(0, 0x20) + jump(6); + const auto analysis = analyze(fake_fn_table, rev, &code[0], code.size()); ASSERT_EQ(analysis.blocks.size(), 3); ASSERT_EQ(analysis.jumpdest_map.size(), 1); - EXPECT_EQ(analysis.jumpdest_map[0], std::pair(6, 4)); - EXPECT_EQ(analysis.find_jumpdest(6), 4); + EXPECT_EQ(analysis.jumpdest_map[0], std::pair(6, 5)); + EXPECT_EQ(analysis.find_jumpdest(6), 5); EXPECT_EQ(analysis.find_jumpdest(0), -1); } diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index b96695d73d..c87b423c38 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -100,11 +100,21 @@ inline bytecode mstore(bytecode index) return index + OP_MSTORE; } +inline bytecode mstore(bytecode index, bytecode value) +{ + return value + index + OP_MSTORE; +} + inline bytecode mstore8(bytecode index) { return index + OP_MSTORE8; } +inline bytecode jump(bytecode target) +{ + return target + OP_JUMP; +} + inline bytecode ret(bytecode index, bytecode size) { return size + index + OP_RETURN; From 1b57d57021eeec91a8d1828669c7ada6fe4d94a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 24 Jun 2019 11:43:31 +0200 Subject: [PATCH 3/4] Remove block_index field --- lib/evmone/analysis.hpp | 1 - test/utils/dump.cpp | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/evmone/analysis.hpp b/lib/evmone/analysis.hpp index d1d4f35a20..52d10bdbf3 100644 --- a/lib/evmone/analysis.hpp +++ b/lib/evmone/analysis.hpp @@ -87,7 +87,6 @@ struct instr_info { exec_fn fn = nullptr; instr_argument arg; - int block_index = -1; explicit constexpr instr_info(exec_fn f) noexcept : fn{f}, arg{} {}; }; diff --git a/test/utils/dump.cpp b/test/utils/dump.cpp index 030bf180b1..e61e7597ed 100644 --- a/test/utils/dump.cpp +++ b/test/utils/dump.cpp @@ -26,10 +26,9 @@ void dump_analysis(const evmone::code_analysis& analysis) if (!name) name = "XX"; - - if (instr.block_index >= 0) + if (c == OPX_BEGINBLOCK) { - block = &analysis.blocks[size_t(instr.block_index)]; + block = &analysis.blocks[size_t(instr.arg.p.number)]; auto get_jumpdest_offset = [&analysis](size_t index) noexcept { From 4c5ed7962a8cd7cb6c4cde1440e54622fda4d7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 24 Jun 2019 12:07:40 +0200 Subject: [PATCH 4/4] test: Add unit test for jumping to a block beginning --- test/unittests/execution_test.cpp | 7 +++++++ test/utils/bytecode.hpp | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/test/unittests/execution_test.cpp b/test/unittests/execution_test.cpp index 2554970271..84d54a41a5 100644 --- a/test/unittests/execution_test.cpp +++ b/test/unittests/execution_test.cpp @@ -230,6 +230,13 @@ TEST_F(execution, bad_jumpdest) } } +TEST_F(execution, jump_to_block_beginning) +{ + const auto code = jumpi(0, OP_MSIZE) + jump(4); + execute(code); + EXPECT_STATUS(EVMC_BAD_JUMP_DESTINATION); +} + TEST_F(execution, pc) { const auto code = OP_CALLDATASIZE + push(9) + OP_JUMPI + push(12) + OP_PC + OP_SWAP1 + OP_JUMP + diff --git a/test/utils/bytecode.hpp b/test/utils/bytecode.hpp index c87b423c38..5ab5d7309e 100644 --- a/test/utils/bytecode.hpp +++ b/test/utils/bytecode.hpp @@ -115,6 +115,11 @@ inline bytecode jump(bytecode target) return target + OP_JUMP; } +inline bytecode jumpi(bytecode target, bytecode condition) +{ + return condition + target + OP_JUMPI; +} + inline bytecode ret(bytecode index, bytecode size) { return size + index + OP_RETURN;