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 ab68bd3 commit 1853e06
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 72 deletions.
104 changes: 44 additions & 60 deletions lib/evmone/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,20 @@ code_analysis analyze(evmc_revision rev, const uint8_t* code, size_t code_size)
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.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
// 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;
analysis.instrs.emplace_back(fns[OPX_BEGINBLOCK]).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

0 comments on commit 1853e06

Please sign in to comment.