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

Add heuristic to skip jumpdest analysis #990

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
30 changes: 30 additions & 0 deletions lib/evmone/baseline_analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "eof.hpp"
#include "instructions.hpp"
#include <memory>
#include <ranges>

namespace evmone::baseline
{
Expand All @@ -16,8 +17,37 @@ static_assert(!std::is_copy_assignable_v<CodeAnalysis>);

namespace
{
/// Heuristic that checks if the given opcode as the first instruction in a code
/// terminates the execution for any reason.
/// This heuristic may have false negatives.
constexpr bool first_instruction_terminates(uint8_t op) noexcept
{
return op < OP_ADDRESS || op > OP_PUSH32;
}

consteval bool proof_first_instruction_terminates(uint8_t op) noexcept
{
if (!first_instruction_terminates(op)) // ignore if not positive
return true;

const auto& tr = instr::traits[op];
if (!tr.since.has_value()) // is undefined in all revisions
return true;
if (tr.is_terminating) // terminates normally
return true;
if (tr.stack_height_required > 0) // causes stack underflow
return true;
return false;
}
static_assert(std::ranges::all_of(
std::views::iota(uint8_t{0}) | std::views::take(256), proof_first_instruction_terminates));
static_assert(first_instruction_terminates(0xEF)); // EOF is included.

CodeAnalysis::JumpdestMap analyze_jumpdests(bytes_view code)
{
if (code.empty() || first_instruction_terminates(code[0]))
return {};

// To find if op is any PUSH opcode (OP_PUSH1 <= op <= OP_PUSH32)
// it can be noticed that OP_PUSH32 is INT8_MAX (0x7f) therefore
// static_cast<int8_t>(op) <= OP_PUSH32 is always true and can be skipped.
Expand Down
99 changes: 10 additions & 89 deletions lib/evmone/baseline_instruction_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,108 +9,29 @@ namespace evmone::baseline
{
namespace
{
constexpr auto common_cost_tables = []() noexcept {
consteval auto build_cost_tables(bool eof) noexcept
{
std::array<CostTable, EVMC_MAX_REVISION + 1> tables{};
for (size_t r = EVMC_FRONTIER; r <= EVMC_MAX_REVISION; ++r)
{
auto& table = tables[r];
for (size_t i = 0; i < table.size(); ++i)
for (size_t op = 0; op < table.size(); ++op)
{
table[i] = instr::gas_costs[r][i]; // Include instr::undefined in the table.
const auto& tr = instr::traits[op];
const auto since = eof ? tr.eof_since : tr.since;
table[op] = (since && r >= *since) ? instr::gas_costs[r][op] : instr::undefined;
}
}
return tables;
}();

constexpr auto legacy_cost_tables = []() noexcept {
auto tables = common_cost_tables;
tables[EVMC_PRAGUE][OP_RJUMP] = instr::undefined;
tables[EVMC_PRAGUE][OP_RJUMPI] = instr::undefined;
tables[EVMC_PRAGUE][OP_RJUMPV] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALLF] = instr::undefined;
tables[EVMC_PRAGUE][OP_RETF] = instr::undefined;
tables[EVMC_PRAGUE][OP_JUMPF] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATALOAD] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATALOADN] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATASIZE] = instr::undefined;
tables[EVMC_PRAGUE][OP_DATACOPY] = instr::undefined;
tables[EVMC_PRAGUE][OP_DUPN] = instr::undefined;
tables[EVMC_PRAGUE][OP_SWAPN] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXCHANGE] = instr::undefined;
tables[EVMC_PRAGUE][OP_RETURNDATALOAD] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTSTATICCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTDELEGATECALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_EOFCREATE] = instr::undefined;
tables[EVMC_PRAGUE][OP_RETURNCONTRACT] = instr::undefined;

tables[EVMC_OSAKA][OP_RJUMP] = instr::undefined;
tables[EVMC_OSAKA][OP_RJUMPI] = instr::undefined;
tables[EVMC_OSAKA][OP_RJUMPV] = instr::undefined;
tables[EVMC_OSAKA][OP_CALLF] = instr::undefined;
tables[EVMC_OSAKA][OP_RETF] = instr::undefined;
tables[EVMC_OSAKA][OP_JUMPF] = instr::undefined;
tables[EVMC_OSAKA][OP_DATALOAD] = instr::undefined;
tables[EVMC_OSAKA][OP_DATALOADN] = instr::undefined;
tables[EVMC_OSAKA][OP_DATASIZE] = instr::undefined;
tables[EVMC_OSAKA][OP_DATACOPY] = instr::undefined;
tables[EVMC_OSAKA][OP_DUPN] = instr::undefined;
tables[EVMC_OSAKA][OP_SWAPN] = instr::undefined;
tables[EVMC_OSAKA][OP_EXCHANGE] = instr::undefined;
tables[EVMC_OSAKA][OP_RETURNDATALOAD] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTCALL] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTSTATICCALL] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTDELEGATECALL] = instr::undefined;
tables[EVMC_OSAKA][OP_EOFCREATE] = instr::undefined;
tables[EVMC_OSAKA][OP_RETURNCONTRACT] = instr::undefined;
tables[EVMC_OSAKA][OP_TXCREATE] = instr::undefined;

return tables;
}();

constexpr auto eof_cost_tables = []() noexcept {
auto tables = common_cost_tables;
tables[EVMC_PRAGUE][OP_JUMP] = instr::undefined;
tables[EVMC_PRAGUE][OP_JUMPI] = instr::undefined;
tables[EVMC_PRAGUE][OP_PC] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALLCODE] = instr::undefined;
tables[EVMC_PRAGUE][OP_SELFDESTRUCT] = instr::undefined;
tables[EVMC_PRAGUE][OP_CALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_STATICCALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_DELEGATECALL] = instr::undefined;
tables[EVMC_PRAGUE][OP_CREATE] = instr::undefined;
tables[EVMC_PRAGUE][OP_CREATE2] = instr::undefined;
tables[EVMC_PRAGUE][OP_CODESIZE] = instr::undefined;
tables[EVMC_PRAGUE][OP_CODECOPY] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTCODESIZE] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTCODECOPY] = instr::undefined;
tables[EVMC_PRAGUE][OP_EXTCODEHASH] = instr::undefined;
tables[EVMC_PRAGUE][OP_GAS] = instr::undefined;

tables[EVMC_OSAKA][OP_JUMP] = instr::undefined;
tables[EVMC_OSAKA][OP_JUMPI] = instr::undefined;
tables[EVMC_OSAKA][OP_PC] = instr::undefined;
tables[EVMC_OSAKA][OP_CALLCODE] = instr::undefined;
tables[EVMC_OSAKA][OP_SELFDESTRUCT] = instr::undefined;
tables[EVMC_OSAKA][OP_CALL] = instr::undefined;
tables[EVMC_OSAKA][OP_STATICCALL] = instr::undefined;
tables[EVMC_OSAKA][OP_DELEGATECALL] = instr::undefined;
tables[EVMC_OSAKA][OP_CREATE] = instr::undefined;
tables[EVMC_OSAKA][OP_CREATE2] = instr::undefined;
tables[EVMC_OSAKA][OP_CODESIZE] = instr::undefined;
tables[EVMC_OSAKA][OP_CODECOPY] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTCODESIZE] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTCODECOPY] = instr::undefined;
tables[EVMC_OSAKA][OP_EXTCODEHASH] = instr::undefined;
tables[EVMC_OSAKA][OP_GAS] = instr::undefined;
return tables;
}();
}

constexpr auto LEGACY_COST_TABLES = build_cost_tables(false);
constexpr auto EOF_COST_TABLES = build_cost_tables(true);
} // namespace

const CostTable& get_baseline_cost_table(evmc_revision rev, uint8_t eof_version) noexcept
{
const auto& tables = (eof_version == 0) ? legacy_cost_tables : eof_cost_tables;
const auto& tables = (eof_version == 0) ? LEGACY_COST_TABLES : EOF_COST_TABLES;
return tables[rev];
}
} // namespace evmone::baseline
Loading