Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BEGINBLOCK intrinsic instruction #74

Merged
merged 4 commits into from
Jun 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions lib/evmone/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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<int>(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<int>(analysis.blocks.size() - 1);

if (jumpdest) // Add the jumpdest to the map.
analysis.jumpdest_map.emplace_back(static_cast<int>(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;
Expand Down
18 changes: 16 additions & 2 deletions lib/evmone/analysis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <evmc/evmc.hpp>
#include <evmc/instructions.h>
#include <evmc/utils.h>
#include <intx/intx.hpp>
#include <array>
Expand Down Expand Up @@ -66,13 +67,26 @@ 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<exec_fn, 256>;

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{} {};
};
Expand All @@ -99,7 +113,7 @@ struct code_analysis
std::vector<std::pair<int, int>> 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(
Expand Down
25 changes: 0 additions & 25 deletions lib/evmone/execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(instr.block_index)];

state.gas_left -= block.gas_cost;
if (state.gas_left < 0)
{
state.status = EVMC_OUT_OF_GAS;
break;
}

if (static_cast<int>(state.stack.size()) < block.stack_req)
{
state.status = EVMC_STACK_UNDERFLOW;
break;
}

if (static_cast<int>(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;
Expand Down
42 changes: 36 additions & 6 deletions lib/evmone/instructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <evmc/helpers.hpp>
#include <evmc/instructions.h>

#include <cassert>

namespace evmone
{
namespace
Expand Down Expand Up @@ -576,6 +578,9 @@ void op_jumpi(execution_state& state, instr_argument) noexcept
state.pc = static_cast<size_t>(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();
}
Expand All @@ -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);
Expand Down Expand Up @@ -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<size_t>(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<int>(state.stack.size()) < block.stack_req)
{
state.status = EVMC_STACK_UNDERFLOW;
state.run = false;
return;
}

if (static_cast<int>(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{};
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 33 additions & 24 deletions test/unittests/analysis_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
#include <evmc/instructions.h>
#include <evmone/analysis.hpp>
#include <gtest/gtest.h>
#include <test/utils/bytecode.hpp>
#include <test/utils/utils.hpp>

using namespace evmone;

constexpr auto rev = EVMC_BYZANTIUM;

const auto fake_fn_table = []() noexcept
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}

Expand Down
7 changes: 7 additions & 0 deletions test/unittests/execution_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 +
Expand Down
15 changes: 15 additions & 0 deletions test/utils/bytecode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,26 @@ 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 jumpi(bytecode target, bytecode condition)
{
return condition + target + OP_JUMPI;
}

inline bytecode ret(bytecode index, bytecode size)
{
return size + index + OP_RETURN;
Expand Down
5 changes: 2 additions & 3 deletions test/utils/dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down