Skip to content

Commit

Permalink
Merge pull request #74 from ethereum/beginblock_instr
Browse files Browse the repository at this point in the history
BEGINBLOCK intrinsic instruction
  • Loading branch information
chfast authored Jun 24, 2019
2 parents 7c0d019 + 4c5ed79 commit 0d6329d
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 64 deletions.
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

0 comments on commit 0d6329d

Please sign in to comment.