diff --git a/lib/evmone/analysis.cpp b/lib/evmone/analysis.cpp index 0cd6712a20..78066516a8 100644 --- a/lib/evmone/analysis.cpp +++ b/lib/evmone/analysis.cpp @@ -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(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(analysis.blocks.size() - 1); - - if (jumpdest) // Add the jumpdest to the map. - { - analysis.jumpdest_offsets.emplace_back(static_cast(code_pos - code - 1)); - analysis.jumpdest_targets.emplace_back(static_cast(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; @@ -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(code_pos - code - 1)); + analysis.jumpdest_targets.emplace_back( + static_cast(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); @@ -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: @@ -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(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(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); diff --git a/test/unittests/analysis_test.cpp b/test/unittests/analysis_test.cpp index 8d0cbe1516..7d3b42e676 100644 --- a/test/unittests/analysis_test.cpp +++ b/test/unittests/analysis_test.cpp @@ -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); @@ -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) @@ -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) @@ -141,10 +143,12 @@ 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) @@ -152,8 +156,8 @@ 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]); @@ -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);