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

Analysis refactoring #153

Merged
merged 1 commit into from
Sep 10, 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
108 changes: 46 additions & 62 deletions lib/evmone/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ inline constexpr uint64_t load64be(const unsigned char* data) noexcept
code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size) noexcept
{
const auto& fns = get_op_table(rev);
const auto opx_beginblock_fn = fns[OPX_BEGINBLOCK];

code_analysis analysis;

const auto max_instrs_size = code_size + 1;
Expand All @@ -29,41 +31,21 @@ code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size)

const auto* instr_table = evmc_get_instruction_metrics_table(rev);

block_info* block = nullptr;

// Create new block.
auto block = &analysis.blocks.emplace_back();
int block_stack_change = 0;
int instr_index = 0;
{
auto& beginblock_instr = analysis.instrs.emplace_back(opx_beginblock_fn);
beginblock_instr.arg.number = static_cast<int>(analysis.blocks.size() - 1);
}

const auto code_end = code + code_size;
for (auto code_pos = code; code_pos < code_end; ++instr_index)
auto code_pos = code;

while (code_pos != code_end)
{
// TODO: Loop in reverse order for easier GAS analysis.
const auto opcode = *code_pos++;

const bool jumpdest = opcode == OP_JUMPDEST;

if (!block || jumpdest)
{
// Create new block.
block = &analysis.blocks.emplace_back();
block_stack_change = 0;

// 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.number = static_cast<int>(analysis.blocks.size() - 1);

if (jumpdest) // Add the jumpdest to the map.
{
analysis.jumpdest_offsets.emplace_back(static_cast<int16_t>(code_pos - code - 1));
analysis.jumpdest_targets.emplace_back(static_cast<int16_t>(instr_index));
}
else // Increase instruction count because additional BEGINBLOCK was injected.
++instr_index;
}

auto& instr = jumpdest ? analysis.instrs.back() : analysis.instrs.emplace_back(fns[opcode]);

const auto metrics = instr_table[opcode];
const auto instr_stack_req = metrics.num_stack_arguments;
const auto instr_stack_change = metrics.num_stack_returned_items - instr_stack_req;
Expand All @@ -82,8 +64,31 @@ code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size)
if (metrics.gas_cost > 0) // can be -1 for undefined instruction
block->gas_cost += metrics.gas_cost;

if (opcode == OP_JUMPDEST)
{
// The JUMPDEST is always the first instruction in the block.
// We don't have to insert anything to the instruction table.
analysis.jumpdest_offsets.emplace_back(static_cast<int16_t>(code_pos - code - 1));
analysis.jumpdest_targets.emplace_back(
static_cast<int16_t>(analysis.instrs.size() - 1));
}
else
analysis.instrs.emplace_back(fns[opcode]);

auto& instr = analysis.instrs.back();

bool is_terminator = false; // A flag whenever this is a block terminating instruction.
switch (opcode)
{
case OP_JUMP:
case OP_JUMPI:
case OP_STOP:
case OP_RETURN:
case OP_REVERT:
case OP_SELFDESTRUCT:
is_terminator = true;
break;

case ANY_SMALL_PUSH:
{
const auto push_size = size_t(opcode - OP_PUSH1 + 1);
Expand Down Expand Up @@ -125,18 +130,6 @@ code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size)
break;
}

case ANY_DUP:
// TODO: This is not needed, but we keep it
chfast marked this conversation as resolved.
Show resolved Hide resolved
// otherwise compiler will not use the jumptable for switch implementation.
instr.arg.number = opcode - OP_DUP1;
break;

case ANY_SWAP:
// TODO: This is not needed, but we keep it
// otherwise compiler will not use the jumptable for switch implementation.
instr.arg.number = opcode - OP_SWAP1 + 1;
break;

case OP_GAS:
case OP_CALL:
case OP_CALLCODE:
Expand All @@ -150,31 +143,22 @@ code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size)
case OP_PC:
instr.arg.number = static_cast<int>(code_pos - code - 1);
break;
}

case OP_LOG0:
case OP_LOG1:
case OP_LOG2:
case OP_LOG3:
case OP_LOG4:
// TODO: This is not needed, but we keep it
// otherwise compiler will not use the jumptable for switch implementation.
instr.arg.number = opcode - OP_LOG0;
break;

case OP_JUMP:
case OP_JUMPI:
case OP_STOP:
case OP_RETURN:
case OP_REVERT:
case OP_SELFDESTRUCT:
block = nullptr;
break;
if (is_terminator || (code_pos != code_end && *code_pos == OP_JUMPDEST))
{
// Create new basic block if
// this is a terminating instruction or the next instruction is a JUMPDEST.
block = &analysis.blocks.emplace_back();
block_stack_change = 0;
auto& beginblock_instr = analysis.instrs.emplace_back(opx_beginblock_fn);
beginblock_instr.arg.number = static_cast<int>(analysis.blocks.size() - 1);
}
}

// Not terminated block or empty code.
if (block || code_size == 0 || code[code_size - 1] == OP_JUMPI)
analysis.instrs.emplace_back(fns[OP_STOP]);
// Make sure the last block is terminated.
// TODO: This is not needed if the last instruction is a terminating one.
analysis.instrs.emplace_back(fns[OP_STOP]);

// FIXME: assert(analysis.instrs.size() <= max_instrs_size);

Expand Down
29 changes: 17 additions & 12 deletions test/unittests/analysis_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ TEST(analysis, jump1)
const auto code = jump(add(4, 2)) + OP_JUMPDEST + mstore(0, 3) + ret(0, 0x20) + jump(6);
const auto analysis = analyze(rev, &code[0], code.size());

ASSERT_EQ(analysis.blocks.size(), 3);
ASSERT_EQ(analysis.blocks.size(), 4);
ASSERT_EQ(analysis.jumpdest_offsets.size(), 1);
ASSERT_EQ(analysis.jumpdest_targets.size(), 1);
EXPECT_EQ(analysis.jumpdest_offsets[0], 6);
Expand All @@ -105,9 +105,10 @@ TEST(analysis, empty)
bytes code;
auto analysis = evmone::analyze(rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 0);
ASSERT_EQ(analysis.instrs.size(), 1);
EXPECT_EQ(analysis.instrs[0].fn, op_table[OP_STOP]);
EXPECT_EQ(analysis.blocks.size(), 1);
ASSERT_EQ(analysis.instrs.size(), 2);
EXPECT_EQ(analysis.instrs[0].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[1].fn, op_table[OP_STOP]);
}

TEST(analysis, only_jumpdest)
Expand All @@ -127,11 +128,12 @@ TEST(analysis, jumpi_at_the_end)
const auto code = bytecode{OP_JUMPI};
auto analysis = evmone::analyze(rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 1);
ASSERT_EQ(analysis.instrs.size(), 3);
EXPECT_EQ(analysis.blocks.size(), 2);
ASSERT_EQ(analysis.instrs.size(), 4);
EXPECT_EQ(analysis.instrs[0].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[1].fn, op_table[OP_JUMPI]);
EXPECT_EQ(analysis.instrs[2].fn, op_table[OP_STOP]);
EXPECT_EQ(analysis.instrs[2].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[3].fn, op_table[OP_STOP]);
}

TEST(analysis, terminated_last_block)
Expand All @@ -141,19 +143,21 @@ TEST(analysis, terminated_last_block)
const auto code = ret(0, 0);
auto analysis = evmone::analyze(rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 1);
ASSERT_EQ(analysis.instrs.size(), 4);
EXPECT_EQ(analysis.blocks.size(), 2);
ASSERT_EQ(analysis.instrs.size(), 6);
EXPECT_EQ(analysis.instrs[0].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[3].fn, op_table[OP_RETURN]);
EXPECT_EQ(analysis.instrs[4].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[5].fn, op_table[OP_STOP]);
}

TEST(analysis, jumpdests_groups)
{
const auto code = 3 * OP_JUMPDEST + push(1) + 3 * OP_JUMPDEST + push(2) + OP_JUMPI;
auto analysis = evmone::analyze(rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 6);
ASSERT_EQ(analysis.instrs.size(), 10);
EXPECT_EQ(analysis.blocks.size(), 7);
ASSERT_EQ(analysis.instrs.size(), 11);
EXPECT_EQ(analysis.instrs[0].fn, op_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[1].fn, op_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[2].fn, op_table[OP_JUMPDEST]);
Expand All @@ -163,7 +167,8 @@ TEST(analysis, jumpdests_groups)
EXPECT_EQ(analysis.instrs[6].fn, op_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[7].fn, op_table[OP_PUSH1]);
EXPECT_EQ(analysis.instrs[8].fn, op_table[OP_JUMPI]);
EXPECT_EQ(analysis.instrs[9].fn, op_table[OP_STOP]);
EXPECT_EQ(analysis.instrs[9].fn, op_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[10].fn, op_table[OP_STOP]);


ASSERT_EQ(analysis.jumpdest_offsets.size(), 6);
Expand Down