Skip to content

Commit

Permalink
Merge pull request #121 from ethereum/analysis-opt
Browse files Browse the repository at this point in the history
Analysis optimization - use switch()
  • Loading branch information
chfast authored Aug 12, 2019
2 parents e3a8e55 + 6ac59ff commit 77ebf0a
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 46 deletions.
1 change: 1 addition & 0 deletions lib/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ add_library(evmone
execution.cpp
execution.hpp
instructions.cpp
opcodes_helpers.h
)
target_link_libraries(evmone PUBLIC evmc::evmc PRIVATE intx::intx evmc::instructions ethash::keccak)
target_include_directories(evmone PUBLIC
Expand Down
120 changes: 76 additions & 44 deletions lib/evmone/analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,13 @@
// Licensed under the Apache License, Version 2.0.

#include "analysis.hpp"

#include "opcodes_helpers.h"
#include <evmc/instructions.h>
#include <cassert>

namespace evmone
{
namespace
{
bool is_terminator(uint8_t c) noexcept
{
return c == OP_JUMP || c == OP_JUMPI || c == OP_STOP || c == OP_RETURN || c == OP_REVERT ||
c == OP_SELFDESTRUCT;
}
} // namespace

evmc_call_kind op2call_kind(uint8_t opcode) noexcept
inline constexpr evmc_call_kind op2call_kind(uint8_t opcode) noexcept
{
switch (opcode)
{
Expand All @@ -40,19 +32,26 @@ 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);

const auto max_instrs_size = code_size + 1;
analysis.instrs.reserve(max_instrs_size);

// This is 2x more than needed but using (code_size / 2 + 1) increases page-faults 1000x.
const auto max_args_storage_size = code_size + 1;
analysis.args_storage.reserve(max_args_storage_size);

const auto* instr_table = evmc_get_instruction_metrics_table(rev);

block_info* block = nullptr;

int instr_index = 0;
for (size_t i = 0; i < code_size; ++i, ++instr_index)

for (auto code_pos = code; code_pos < code + code_size; ++code_pos, ++instr_index)
{
// TODO: Loop in reverse order for easier GAS analysis.
const auto c = code[i];
const auto opcode = *code_pos;

const bool jumpdest = c == OP_JUMPDEST;
const bool jumpdest = opcode == OP_JUMPDEST;

if (!block || jumpdest)
{
Expand All @@ -66,65 +65,98 @@ code_analysis analyze(

if (jumpdest) // Add the jumpdest to the map.
{
analysis.jumpdest_offsets.emplace_back(static_cast<int16_t>(i));
analysis.jumpdest_offsets.emplace_back(static_cast<int16_t>(code_pos - code));
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[c]);
auto& instr = jumpdest ? analysis.instrs.back() : analysis.instrs.emplace_back(fns[opcode]);

const auto metrics = instr_table[c];
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;

block->stack_req = std::max(block->stack_req, instr_stack_req - block->stack_change);
block->stack_change += instr_stack_change;
block->stack_max = std::max(block->stack_max, block->stack_change);

if (metrics.gas_cost > 0) // can be -1 for undefined instruction
block->gas_cost += metrics.gas_cost;

// Skip PUSH data.
if (c >= OP_PUSH1 && c <= OP_PUSH32)
switch (opcode)
{
case ANY_PUSH:
{
// OPT: bswap data here.
++i;
const auto push_size = size_t(c - OP_PUSH1 + 1);
const auto push_size = size_t(opcode - OP_PUSH1 + 1);
auto& data = analysis.args_storage.emplace_back();

const auto leading_zeros = 32 - push_size;
const auto i = code_pos - code + 1;
for (size_t j = 0; j < push_size && (i + j) < code_size; ++j)
data[leading_zeros + j] = code[i + j];
instr.arg.data = &data[0];
i += push_size - 1;
code_pos += push_size;
break;
}
else if (c >= OP_DUP1 && c <= OP_DUP16)
instr.arg.p.number = c - OP_DUP1;
else if (c >= OP_SWAP1 && c <= OP_SWAP16)
instr.arg.p.number = c - OP_SWAP1 + 1;
else if (c == OP_GAS)
instr.arg.p.number = static_cast<int>(block->gas_cost);
else if (c == OP_DELEGATECALL || c == OP_CALL || c == OP_CALLCODE || c == OP_STATICCALL ||
c == OP_CREATE || c == OP_CREATE2)
{
instr.arg.p.number = static_cast<int>(block->gas_cost);
instr.arg.p.call_kind = op2call_kind(c == OP_STATICCALL ? uint8_t{OP_CALL} : c);
}
else if (c == OP_PC)
instr.arg.p.number = static_cast<int>(i);
else if (c >= OP_LOG0 && c <= OP_LOG4)
instr.arg.p.number = c - OP_LOG0;

block->stack_req = std::max(block->stack_req, instr_stack_req - block->stack_change);
block->stack_change += instr_stack_change;
block->stack_max = std::max(block->stack_max, block->stack_change);
case ANY_DUP:
instr.arg.p.number = opcode - OP_DUP1;
break;

if (is_terminator(c))
case ANY_SWAP:
instr.arg.p.number = opcode - OP_SWAP1 + 1;
break;

case OP_GAS:
instr.arg.p.number = static_cast<int>(block->gas_cost);
break;

case OP_CALL:
case OP_CALLCODE:
case OP_DELEGATECALL:
case OP_STATICCALL:
case OP_CREATE:
case OP_CREATE2:
instr.arg.p.number = static_cast<int>(block->gas_cost);
instr.arg.p.call_kind =
op2call_kind(opcode == OP_STATICCALL ? uint8_t{OP_CALL} : opcode);
break;

case OP_PC:
instr.arg.p.number = static_cast<int>(code_pos - code);
break;

case OP_LOG0:
case OP_LOG1:
case OP_LOG2:
case OP_LOG3:
case OP_LOG4:
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;
}
}

// Not terminated block or empty code.
if (block || code_size == 0 || code[code_size - 1] == OP_JUMPI)
analysis.instrs.emplace_back(fns[OP_STOP]);

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

// Make sure the args_storage has not been reallocated. Otherwise iterators are invalid.
assert(analysis.args_storage.size() <= max_args_storage_size);

return analysis;
}

Expand Down
3 changes: 1 addition & 2 deletions lib/evmone/analysis.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <intx/intx.hpp>
#include <array>
#include <cstdint>
#include <deque>
#include <vector>

namespace evmone
Expand Down Expand Up @@ -177,7 +176,7 @@ struct code_analysis
///
/// The deque container is used because pointers to its elements are not
/// invalidated when the container grows.
std::deque<bytes32> args_storage;
std::vector<bytes32> args_storage;

/// The offsets of JUMPDESTs in the original code.
/// These are values that JUMP/JUMPI receives as an argument.
Expand Down
74 changes: 74 additions & 0 deletions lib/evmone/opcodes_helpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2019 Pawel Bylica.
// Licensed under the Apache License, Version 2.0.
#pragma once

#define ANY_PUSH \
OP_PUSH1: \
case OP_PUSH2: \
case OP_PUSH3: \
case OP_PUSH4: \
case OP_PUSH5: \
case OP_PUSH6: \
case OP_PUSH7: \
case OP_PUSH8: \
case OP_PUSH9: \
case OP_PUSH10: \
case OP_PUSH11: \
case OP_PUSH12: \
case OP_PUSH13: \
case OP_PUSH14: \
case OP_PUSH15: \
case OP_PUSH16: \
case OP_PUSH17: \
case OP_PUSH18: \
case OP_PUSH19: \
case OP_PUSH20: \
case OP_PUSH21: \
case OP_PUSH22: \
case OP_PUSH23: \
case OP_PUSH24: \
case OP_PUSH25: \
case OP_PUSH26: \
case OP_PUSH27: \
case OP_PUSH28: \
case OP_PUSH29: \
case OP_PUSH30: \
case OP_PUSH31: \
case OP_PUSH32

#define ANY_DUP \
OP_DUP1: \
case OP_DUP2: \
case OP_DUP3: \
case OP_DUP4: \
case OP_DUP5: \
case OP_DUP6: \
case OP_DUP7: \
case OP_DUP8: \
case OP_DUP9: \
case OP_DUP10: \
case OP_DUP11: \
case OP_DUP12: \
case OP_DUP13: \
case OP_DUP14: \
case OP_DUP15: \
case OP_DUP16

#define ANY_SWAP \
OP_SWAP1: \
case OP_SWAP2: \
case OP_SWAP3: \
case OP_SWAP4: \
case OP_SWAP5: \
case OP_SWAP6: \
case OP_SWAP7: \
case OP_SWAP8: \
case OP_SWAP9: \
case OP_SWAP10: \
case OP_SWAP11: \
case OP_SWAP12: \
case OP_SWAP13: \
case OP_SWAP14: \
case OP_SWAP15: \
case OP_SWAP16
15 changes: 15 additions & 0 deletions test/unittests/evm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

using namespace intx;

constexpr auto max_code_size = 0x6000;

TEST_F(evm, empty)
{
execute(0, "");
Expand Down Expand Up @@ -1194,6 +1196,19 @@ TEST_F(evm, extcodecopy_buffer_overflow)
}
}

TEST_F(evm, max_code_size_push1)
{
// TODO: Optimize the code generation.
const auto code = (max_code_size / 2) * push(1);
ASSERT_EQ(code.size(), max_code_size);

execute(code);
EXPECT_STATUS(EVMC_STACK_OVERFLOW);

execute(code.substr(0, code.size() - 1));
EXPECT_STATUS(EVMC_STACK_OVERFLOW);
}

struct memory_access_opcode
{
evmc_opcode opcode;
Expand Down

0 comments on commit 77ebf0a

Please sign in to comment.