From b6d881b400d953d004e5eda0f1c845cd4923c2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 4 Jul 2024 12:15:36 +0200 Subject: [PATCH 1/3] baseline: Move execution to separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Baseline execution implementation to separate file: baseline.cpp → baseline_execution.cpp --- lib/evmone/CMakeLists.txt | 2 +- .../{baseline.cpp => baseline_execution.cpp} | 65 ------------------- 2 files changed, 1 insertion(+), 66 deletions(-) rename lib/evmone/{baseline.cpp => baseline_execution.cpp} (84%) diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index 659bcc19e4..3fbfbff940 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -11,8 +11,8 @@ add_library(evmone advanced_execution.cpp advanced_execution.hpp advanced_instructions.cpp - baseline.cpp baseline.hpp + baseline_execution.cpp baseline_instruction_table.cpp baseline_instruction_table.hpp constants.hpp diff --git a/lib/evmone/baseline.cpp b/lib/evmone/baseline_execution.cpp similarity index 84% rename from lib/evmone/baseline.cpp rename to lib/evmone/baseline_execution.cpp index 130070dee4..4f9a4b5828 100644 --- a/lib/evmone/baseline.cpp +++ b/lib/evmone/baseline_execution.cpp @@ -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(op) <= OP_PUSH32 is always true and can be skipped. - static_assert(OP_PUSH32 == std::numeric_limits::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(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 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(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. From 3c0df6f7c7f6f6afcc047f625d3df2791956e537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 4 Jul 2024 12:05:42 +0200 Subject: [PATCH 2/3] =?UTF-8?q?baseline:=20Temporary=20rename=20baseline.c?= =?UTF-8?q?pp=20=E2=86=92=20baseline=5Fanalysis.cpp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/evmone/CMakeLists.txt | 2 +- lib/evmone/{baseline.cpp => baseline_analysis.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename lib/evmone/{baseline.cpp => baseline_analysis.cpp} (100%) diff --git a/lib/evmone/CMakeLists.txt b/lib/evmone/CMakeLists.txt index 659bcc19e4..4a3ba39125 100644 --- a/lib/evmone/CMakeLists.txt +++ b/lib/evmone/CMakeLists.txt @@ -11,8 +11,8 @@ add_library(evmone advanced_execution.cpp advanced_execution.hpp advanced_instructions.cpp - baseline.cpp baseline.hpp + baseline_analysis.cpp baseline_instruction_table.cpp baseline_instruction_table.hpp constants.hpp diff --git a/lib/evmone/baseline.cpp b/lib/evmone/baseline_analysis.cpp similarity index 100% rename from lib/evmone/baseline.cpp rename to lib/evmone/baseline_analysis.cpp From 27b31153d332650cae1b0b76bcc63c34b9e1079e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 4 Jul 2024 12:09:33 +0200 Subject: [PATCH 3/3] baseline: Move analysis to separate file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move Baseline analysis implementation to separate file: baseline.cpp → baseline_analysis.cpp. --- lib/evmone/baseline.hpp | 4 - lib/evmone/baseline_analysis.cpp | 346 +------------------------------ 2 files changed, 5 insertions(+), 345 deletions(-) diff --git a/lib/evmone/baseline.hpp b/lib/evmone/baseline.hpp index cc22da25b4..fbf6d62e20 100644 --- a/lib/evmone/baseline.hpp +++ b/lib/evmone/baseline.hpp @@ -42,10 +42,6 @@ class CodeAnalysis : executable_code{code}, eof_header{std::move(header)} {} }; -static_assert(std::is_move_constructible_v); -static_assert(std::is_move_assignable_v); -static_assert(!std::is_copy_constructible_v); -static_assert(!std::is_copy_assignable_v); /// Analyze the code to build the bitmap of valid JUMPDEST locations. EVMC_EXPORT CodeAnalysis analyze(evmc_revision rev, bytes_view code); diff --git a/lib/evmone/baseline_analysis.cpp b/lib/evmone/baseline_analysis.cpp index 130070dee4..85efe8f2a4 100644 --- a/lib/evmone/baseline_analysis.cpp +++ b/lib/evmone/baseline_analysis.cpp @@ -3,27 +3,17 @@ // SPDX-License-Identifier: Apache-2.0 #include "baseline.hpp" -#include "baseline_instruction_table.hpp" #include "eof.hpp" -#include "execution_state.hpp" #include "instructions.hpp" -#include "vm.hpp" #include -#ifdef NDEBUG -#define release_inline gnu::always_inline, msvc::forceinline -#else -#define release_inline -#endif - -#if defined(__GNUC__) -#define ASM_COMMENT(COMMENT) asm("# " #COMMENT) // NOLINT(hicpp-no-assembler) -#else -#define ASM_COMMENT(COMMENT) -#endif - namespace evmone::baseline { +static_assert(std::is_move_constructible_v); +static_assert(std::is_move_assignable_v); +static_assert(!std::is_copy_constructible_v); +static_assert(!std::is_copy_assignable_v); + namespace { CodeAnalysis::JumpdestMap analyze_jumpdests(bytes_view code) @@ -88,330 +78,4 @@ CodeAnalysis analyze(evmc_revision rev, bytes_view code) return analyze_legacy(code); return analyze_eof1(code); } - -namespace -{ -/// Checks instruction requirements before execution. -/// -/// This checks: -/// - if the instruction is defined -/// - if stack height requirements are fulfilled (stack overflow, stack underflow) -/// - charges the instruction base gas cost and checks is there is any gas left. -/// -/// @tparam Op Instruction opcode. -/// @param cost_table Table of base gas costs. -/// @param [in,out] gas_left Gas left. -/// @param stack_top Pointer to the stack top item. -/// @param stack_bottom Pointer to the stack bottom. -/// The stack height is stack_top - stack_bottom. -/// @return Status code with information which check has failed -/// or EVMC_SUCCESS if everything is fine. -template -inline evmc_status_code check_requirements(const CostTable& cost_table, int64_t& gas_left, - const uint256* stack_top, const uint256* stack_bottom) noexcept -{ - static_assert( - !instr::has_const_gas_cost(Op) || instr::gas_costs[EVMC_FRONTIER][Op] != instr::undefined, - "undefined instructions must not be handled by check_requirements()"); - - auto gas_cost = instr::gas_costs[EVMC_FRONTIER][Op]; // Init assuming const cost. - if constexpr (!instr::has_const_gas_cost(Op)) - { - gas_cost = cost_table[Op]; // If not, load the cost from the table. - - // Negative cost marks an undefined instruction. - // This check must be first to produce correct error code. - if (INTX_UNLIKELY(gas_cost < 0)) - return EVMC_UNDEFINED_INSTRUCTION; - } - - // Check stack requirements first. This is order is not required, - // but it is nicer because complete gas check may need to inspect operands. - if constexpr (instr::traits[Op].stack_height_change > 0) - { - static_assert(instr::traits[Op].stack_height_change == 1, - "unexpected instruction with multiple results"); - if (INTX_UNLIKELY(stack_top == stack_bottom + StackSpace::limit)) - return EVMC_STACK_OVERFLOW; - } - if constexpr (instr::traits[Op].stack_height_required > 0) - { - // Check stack underflow using pointer comparison <= (better optimization). - static constexpr auto min_offset = instr::traits[Op].stack_height_required - 1; - if (INTX_UNLIKELY(stack_top <= stack_bottom + min_offset)) - return EVMC_STACK_UNDERFLOW; - } - - if (INTX_UNLIKELY((gas_left -= gas_cost) < 0)) - return EVMC_OUT_OF_GAS; - - return EVMC_SUCCESS; -} - - -/// The execution position. -struct Position -{ - code_iterator code_it; ///< The position in the code. - uint256* stack_top; ///< The pointer to the stack top. -}; - -/// Helpers for invoking instruction implementations of different signatures. -/// @{ -[[release_inline]] inline code_iterator invoke(void (*instr_fn)(StackTop) noexcept, Position pos, - int64_t& /*gas*/, ExecutionState& /*state*/) noexcept -{ - instr_fn(pos.stack_top); - return pos.code_it + 1; -} - -[[release_inline]] inline code_iterator invoke( - Result (*instr_fn)(StackTop, int64_t, ExecutionState&) noexcept, Position pos, int64_t& gas, - ExecutionState& state) noexcept -{ - const auto o = instr_fn(pos.stack_top, gas, state); - gas = o.gas_left; - if (o.status != EVMC_SUCCESS) - { - state.status = o.status; - return nullptr; - } - return pos.code_it + 1; -} - -[[release_inline]] inline code_iterator invoke(void (*instr_fn)(StackTop, ExecutionState&) noexcept, - Position pos, int64_t& /*gas*/, ExecutionState& state) noexcept -{ - instr_fn(pos.stack_top, state); - return pos.code_it + 1; -} - -[[release_inline]] inline code_iterator invoke( - code_iterator (*instr_fn)(StackTop, ExecutionState&, code_iterator) noexcept, Position pos, - int64_t& /*gas*/, ExecutionState& state) noexcept -{ - return instr_fn(pos.stack_top, state, pos.code_it); -} - -[[release_inline]] inline code_iterator invoke( - code_iterator (*instr_fn)(StackTop, code_iterator) noexcept, Position pos, int64_t& /*gas*/, - ExecutionState& /*state*/) noexcept -{ - return instr_fn(pos.stack_top, pos.code_it); -} - -[[release_inline]] inline code_iterator invoke( - TermResult (*instr_fn)(StackTop, int64_t, ExecutionState&) noexcept, Position pos, int64_t& gas, - ExecutionState& state) noexcept -{ - const auto result = instr_fn(pos.stack_top, gas, state); - gas = result.gas_left; - state.status = result.status; - return nullptr; -} - -[[release_inline]] inline code_iterator invoke( - Result (*instr_fn)(StackTop, int64_t, ExecutionState&, code_iterator&) noexcept, Position pos, - int64_t& gas, ExecutionState& state) noexcept -{ - const auto result = instr_fn(pos.stack_top, gas, state, pos.code_it); - gas = result.gas_left; - if (result.status != EVMC_SUCCESS) - { - state.status = result.status; - return nullptr; - } - return pos.code_it; -} - -[[release_inline]] inline code_iterator invoke( - TermResult (*instr_fn)(StackTop, int64_t, ExecutionState&, code_iterator) noexcept, - Position pos, int64_t& gas, ExecutionState& state) noexcept -{ - const auto result = instr_fn(pos.stack_top, gas, state, pos.code_it); - gas = result.gas_left; - state.status = result.status; - return nullptr; -} - -/// A helper to invoke the instruction implementation of the given opcode Op. -template -[[release_inline]] inline Position invoke(const CostTable& cost_table, const uint256* stack_bottom, - Position pos, int64_t& gas, ExecutionState& state) noexcept -{ - if (const auto status = check_requirements(cost_table, gas, pos.stack_top, stack_bottom); - status != EVMC_SUCCESS) - { - state.status = status; - return {nullptr, pos.stack_top}; - } - const auto new_pos = invoke(instr::core::impl, pos, gas, state); - const auto new_stack_top = pos.stack_top + instr::traits[Op].stack_height_change; - return {new_pos, new_stack_top}; -} - - -template -int64_t dispatch(const CostTable& cost_table, ExecutionState& state, int64_t gas, - const uint8_t* code, Tracer* tracer = nullptr) noexcept -{ - const auto stack_bottom = state.stack_space.bottom(); - - // Code iterator and stack top pointer for interpreter loop. - Position position{code, stack_bottom}; - - while (true) // Guaranteed to terminate because padded code ends with STOP. - { - if constexpr (TracingEnabled) - { - const auto offset = static_cast(position.code_it - code); - const auto stack_height = static_cast(position.stack_top - stack_bottom); - if (offset < state.original_code.size()) // Skip STOP from code padding. - { - tracer->notify_instruction_start( - offset, position.stack_top, stack_height, gas, state); - } - } - - const auto op = *position.code_it; - switch (op) - { -#define ON_OPCODE(OPCODE) \ - case OPCODE: \ - ASM_COMMENT(OPCODE); \ - if (const auto next = invoke(cost_table, stack_bottom, position, gas, state); \ - next.code_it == nullptr) \ - { \ - return gas; \ - } \ - else \ - { \ - /* Update current position only when no error, \ - this improves compiler optimization. */ \ - position = next; \ - } \ - break; - - MAP_OPCODES -#undef ON_OPCODE - - default: - state.status = EVMC_UNDEFINED_INSTRUCTION; - return gas; - } - } - intx::unreachable(); -} - -#if EVMONE_CGOTO_SUPPORTED -int64_t dispatch_cgoto( - const CostTable& cost_table, ExecutionState& state, int64_t gas, const uint8_t* code) noexcept -{ -#pragma GCC diagnostic ignored "-Wpedantic" - - static constexpr void* cgoto_table[] = { -#define ON_OPCODE(OPCODE) &&TARGET_##OPCODE, -#undef ON_OPCODE_UNDEFINED -#define ON_OPCODE_UNDEFINED(_) &&TARGET_OP_UNDEFINED, - MAP_OPCODES -#undef ON_OPCODE -#undef ON_OPCODE_UNDEFINED -#define ON_OPCODE_UNDEFINED ON_OPCODE_UNDEFINED_DEFAULT - }; - static_assert(std::size(cgoto_table) == 256); - - const auto stack_bottom = state.stack_space.bottom(); - - // Code iterator and stack top pointer for interpreter loop. - Position position{code, stack_bottom}; - - goto* cgoto_table[*position.code_it]; - -#define ON_OPCODE(OPCODE) \ - TARGET_##OPCODE : ASM_COMMENT(OPCODE); \ - if (const auto next = invoke(cost_table, stack_bottom, position, gas, state); \ - next.code_it == nullptr) \ - { \ - return gas; \ - } \ - else \ - { \ - /* Update current position only when no error, \ - this improves compiler optimization. */ \ - position = next; \ - } \ - goto* cgoto_table[*position.code_it]; - - MAP_OPCODES -#undef ON_OPCODE - -TARGET_OP_UNDEFINED: - state.status = EVMC_UNDEFINED_INSTRUCTION; - return gas; -} -#endif -} // namespace - -evmc_result execute( - const VM& vm, int64_t gas, ExecutionState& state, const CodeAnalysis& analysis) noexcept -{ - state.analysis.baseline = &analysis; // Assign code analysis for instruction implementations. - - const auto code = analysis.executable_code; - - const auto& cost_table = get_baseline_cost_table(state.rev, analysis.eof_header.version); - - auto* tracer = vm.get_tracer(); - if (INTX_UNLIKELY(tracer != nullptr)) - { - tracer->notify_execution_start(state.rev, *state.msg, analysis.executable_code); - gas = dispatch(cost_table, state, gas, code.data(), tracer); - } - else - { -#if EVMONE_CGOTO_SUPPORTED - if (vm.cgoto) - gas = dispatch_cgoto(cost_table, state, gas, code.data()); - else -#endif - gas = dispatch(cost_table, state, gas, code.data()); - } - - const auto gas_left = (state.status == EVMC_SUCCESS || state.status == EVMC_REVERT) ? gas : 0; - const auto gas_refund = (state.status == EVMC_SUCCESS) ? state.gas_refund : 0; - - assert(state.output_size != 0 || state.output_offset == 0); - const auto result = - (state.deploy_container.has_value() ? - evmc::make_result(state.status, gas_left, gas_refund, - state.deploy_container->data(), state.deploy_container->size()) : - evmc::make_result(state.status, gas_left, gas_refund, - state.output_size != 0 ? &state.memory[state.output_offset] : nullptr, - state.output_size)); - - if (INTX_UNLIKELY(tracer != nullptr)) - tracer->notify_execution_end(result); - - return result; -} - -evmc_result execute(evmc_vm* c_vm, const evmc_host_interface* host, evmc_host_context* ctx, - evmc_revision rev, const evmc_message* msg, const uint8_t* code, size_t code_size) noexcept -{ - auto vm = static_cast(c_vm); - const bytes_view container{code, code_size}; - - if (vm->validate_eof && rev >= EVMC_PRAGUE && is_eof_container(container)) - { - const auto container_kind = - (msg->depth == 0 ? ContainerKind::initcode : ContainerKind::runtime); - if (validate_eof(rev, container_kind, container) != EOFValidationError::success) - return evmc_make_result(EVMC_CONTRACT_VALIDATION_FAILURE, 0, 0, nullptr, 0); - } - - const auto code_analysis = analyze(rev, container); - const auto data = code_analysis.eof_header.get_data(container); - auto state = std::make_unique(*msg, rev, *host, ctx, container, data); - return execute(*vm, msg->gas, *state, code_analysis); -} } // namespace evmone::baseline