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

baseline: Split analysis and execution implementations #946

Merged
merged 4 commits into from
Jul 4, 2024
Merged
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
3 changes: 2 additions & 1 deletion lib/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ add_library(evmone
advanced_execution.cpp
advanced_execution.hpp
advanced_instructions.cpp
baseline.cpp
baseline.hpp
baseline_analysis.cpp
baseline_execution.cpp
baseline_instruction_table.cpp
baseline_instruction_table.hpp
constants.hpp
Expand Down
4 changes: 0 additions & 4 deletions lib/evmone/baseline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ class CodeAnalysis
: executable_code{code}, eof_header{std::move(header)}
{}
};
static_assert(std::is_move_constructible_v<CodeAnalysis>);
static_assert(std::is_move_assignable_v<CodeAnalysis>);
static_assert(!std::is_copy_constructible_v<CodeAnalysis>);
static_assert(!std::is_copy_assignable_v<CodeAnalysis>);

/// Analyze the code to build the bitmap of valid JUMPDEST locations.
EVMC_EXPORT CodeAnalysis analyze(evmc_revision rev, bytes_view code);
Expand Down
81 changes: 81 additions & 0 deletions lib/evmone/baseline_analysis.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2020 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "baseline.hpp"
#include "eof.hpp"
#include "instructions.hpp"
#include <memory>

namespace evmone::baseline
{
static_assert(std::is_move_constructible_v<CodeAnalysis>);
static_assert(std::is_move_assignable_v<CodeAnalysis>);
static_assert(!std::is_copy_constructible_v<CodeAnalysis>);
static_assert(!std::is_copy_assignable_v<CodeAnalysis>);

namespace
{
CodeAnalysis::JumpdestMap analyze_jumpdests(bytes_view code)
{
// 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.
static_assert(OP_PUSH32 == std::numeric_limits<int8_t>::max());

CodeAnalysis::JumpdestMap map(code.size()); // Allocate and init bitmap with zeros.
for (size_t i = 0; i < code.size(); ++i)
{
const auto op = code[i];
if (static_cast<int8_t>(op) >= OP_PUSH1) // If any PUSH opcode (see explanation above).
i += op - size_t{OP_PUSH1 - 1}; // Skip PUSH data.
else if (INTX_UNLIKELY(op == OP_JUMPDEST))
map[i] = true;
}

return map;
}

std::unique_ptr<uint8_t[]> pad_code(bytes_view code)
{
// We need at most 33 bytes of code padding: 32 for possible missing all data bytes of PUSH32
// at the very end of the code; and one more byte for STOP to guarantee there is a terminating
// instruction at the code end.
constexpr auto padding = 32 + 1;

auto padded_code = std::make_unique_for_overwrite<uint8_t[]>(code.size() + padding);
std::copy(std::begin(code), std::end(code), padded_code.get());
std::fill_n(&padded_code[code.size()], padding, uint8_t{OP_STOP});
return padded_code;
}


CodeAnalysis analyze_legacy(bytes_view code)
{
// TODO: The padded code buffer and jumpdest bitmap can be created with single allocation.
return {pad_code(code), code.size(), analyze_jumpdests(code)};
}

CodeAnalysis analyze_eof1(bytes_view container)
{
auto header = read_valid_eof1_header(container);

// Extract all code sections as single buffer reference.
// TODO: It would be much easier if header had code_sections_offset and data_section_offset
// with code_offsets[] being relative to code_sections_offset.
const auto code_sections_offset = header.code_offsets[0];
const auto code_sections_end = size_t{header.code_offsets.back()} + header.code_sizes.back();
const auto executable_code =
container.substr(code_sections_offset, code_sections_end - code_sections_offset);

return CodeAnalysis{executable_code, std::move(header)};
}
} // namespace

CodeAnalysis analyze(evmc_revision rev, bytes_view code)
{
if (rev < EVMC_PRAGUE || !is_eof_container(code))
return analyze_legacy(code);
return analyze_eof1(code);
}
} // namespace evmone::baseline
65 changes: 0 additions & 65 deletions lib/evmone/baseline.cpp → lib/evmone/baseline_execution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,71 +24,6 @@

namespace evmone::baseline
{
namespace
{
CodeAnalysis::JumpdestMap analyze_jumpdests(bytes_view code)
{
// 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.
static_assert(OP_PUSH32 == std::numeric_limits<int8_t>::max());

CodeAnalysis::JumpdestMap map(code.size()); // Allocate and init bitmap with zeros.
for (size_t i = 0; i < code.size(); ++i)
{
const auto op = code[i];
if (static_cast<int8_t>(op) >= OP_PUSH1) // If any PUSH opcode (see explanation above).
i += op - size_t{OP_PUSH1 - 1}; // Skip PUSH data.
else if (INTX_UNLIKELY(op == OP_JUMPDEST))
map[i] = true;
}

return map;
}

std::unique_ptr<uint8_t[]> pad_code(bytes_view code)
{
// We need at most 33 bytes of code padding: 32 for possible missing all data bytes of PUSH32
// at the very end of the code; and one more byte for STOP to guarantee there is a terminating
// instruction at the code end.
constexpr auto padding = 32 + 1;

auto padded_code = std::make_unique_for_overwrite<uint8_t[]>(code.size() + padding);
std::copy(std::begin(code), std::end(code), padded_code.get());
std::fill_n(&padded_code[code.size()], padding, uint8_t{OP_STOP});
return padded_code;
}


CodeAnalysis analyze_legacy(bytes_view code)
{
// TODO: The padded code buffer and jumpdest bitmap can be created with single allocation.
return {pad_code(code), code.size(), analyze_jumpdests(code)};
}

CodeAnalysis analyze_eof1(bytes_view container)
{
auto header = read_valid_eof1_header(container);

// Extract all code sections as single buffer reference.
// TODO: It would be much easier if header had code_sections_offset and data_section_offset
// with code_offsets[] being relative to code_sections_offset.
const auto code_sections_offset = header.code_offsets[0];
const auto code_sections_end = size_t{header.code_offsets.back()} + header.code_sizes.back();
const auto executable_code =
container.substr(code_sections_offset, code_sections_end - code_sections_offset);

return CodeAnalysis{executable_code, std::move(header)};
}
} // namespace

CodeAnalysis analyze(evmc_revision rev, bytes_view code)
{
if (rev < EVMC_PRAGUE || !is_eof_container(code))
return analyze_legacy(code);
return analyze_eof1(code);
}

namespace
{
/// Checks instruction requirements before execution.
Expand Down