Skip to content

Commit

Permalink
Refactor the analysis loop
Browse files Browse the repository at this point in the history
  • Loading branch information
chfast committed Sep 10, 2019
1 parent 65e5b8a commit c23e3bb
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 68 deletions.
104 changes: 44 additions & 60 deletions lib/evmone/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,38 +54,20 @@ code_analysis analyze(
block_info* block = nullptr;

int block_stack_change = 0;
int instr_index = 0;

// Create new block.
block = &analysis.blocks.emplace_back();
block_stack_change = 0;
auto& beginblock_instr = analysis.instrs.emplace_back(fns[OPX_BEGINBLOCK]);
beginblock_instr.arg.p.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.p.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 @@ -104,8 +86,31 @@ code_analysis analyze(
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 @@ -147,18 +152,6 @@ code_analysis analyze(
break;
}

case ANY_DUP:
// TODO: This is not needed, but we keep it
// otherwise compiler will not use the jumptable for switch implementation.
instr.arg.p.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.p.number = opcode - OP_SWAP1 + 1;
break;

case OP_GAS:
instr.arg.p.number = block->gas_cost;
break;
Expand All @@ -177,31 +170,22 @@ code_analysis analyze(
case OP_PC:
instr.arg.p.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.p.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;
analysis.instrs.emplace_back(fns[OPX_BEGINBLOCK]).arg.p.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
87 changes: 79 additions & 8 deletions test/unittests/analysis_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,28 @@ TEST(analysis, push)
EXPECT_EQ(analysis.push_values[0], intx::uint256{0xee} << 240);
}

TEST(analysis, jumpdest_skip)
{
// If the JUMPDEST is the first instruction in a basic block it should be just omitted
// and no new block should be created in this place.

const auto code = bytecode{} + OP_STOP + OP_JUMPDEST;
auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size());

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

TEST(analysis, jump1)
{
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.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 @@ -98,14 +114,15 @@ TEST(analysis, empty)
bytes code;
auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size());

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

TEST(analysis, only_jumpdest)
{
auto code = from_hex("5b");
const auto code = bytecode{OP_JUMPDEST};
auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size());

ASSERT_EQ(analysis.blocks.size(), 1);
Expand All @@ -117,9 +134,63 @@ TEST(analysis, only_jumpdest)

TEST(analysis, jumpi_at_the_end)
{
auto code = from_hex("57");
const auto code = bytecode{OP_JUMPI};
auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size());

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

TEST(analysis, terminated_last_block)
{
// TODO: Even if the last basic block is properly terminated an additional artificial block
// is going to be created with only STOP instruction.
const auto code = ret(0, 0);
auto analysis = evmone::analyze(fake_fn_table, rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 2);
ASSERT_EQ(analysis.instrs.size(), 6);
EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[3].fn, fake_fn_table[OP_RETURN]);
EXPECT_EQ(analysis.instrs[4].fn, fake_fn_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[5].fn, fake_fn_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(fake_fn_table, rev, &code[0], code.size());

EXPECT_EQ(analysis.blocks.size(), 7);
ASSERT_EQ(analysis.instrs.size(), 11);
EXPECT_EQ(analysis.instrs[0].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[1].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[2].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[3].fn, fake_fn_table[OP_PUSH1]);
EXPECT_EQ(analysis.instrs[4].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[5].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[6].fn, fake_fn_table[OP_JUMPDEST]);
EXPECT_EQ(analysis.instrs[7].fn, fake_fn_table[OP_PUSH1]);
EXPECT_EQ(analysis.instrs[8].fn, fake_fn_table[OP_JUMPI]);
EXPECT_EQ(analysis.instrs[9].fn, fake_fn_table[OPX_BEGINBLOCK]);
EXPECT_EQ(analysis.instrs[10].fn, fake_fn_table[OP_STOP]);


ASSERT_EQ(analysis.jumpdest_offsets.size(), 6);
ASSERT_EQ(analysis.jumpdest_targets.size(), 6);
EXPECT_EQ(analysis.jumpdest_offsets[0], 0);
EXPECT_EQ(analysis.jumpdest_targets[0], 0);
EXPECT_EQ(analysis.jumpdest_offsets[1], 1);
EXPECT_EQ(analysis.jumpdest_targets[1], 1);
EXPECT_EQ(analysis.jumpdest_offsets[2], 2);
EXPECT_EQ(analysis.jumpdest_targets[2], 2);
EXPECT_EQ(analysis.jumpdest_offsets[3], 5);
EXPECT_EQ(analysis.jumpdest_targets[3], 4);
EXPECT_EQ(analysis.jumpdest_offsets[4], 6);
EXPECT_EQ(analysis.jumpdest_targets[4], 5);
EXPECT_EQ(analysis.jumpdest_offsets[5], 7);
EXPECT_EQ(analysis.jumpdest_targets[5], 6);
}

0 comments on commit c23e3bb

Please sign in to comment.