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: Track stack evolution separately #418

Merged
merged 2 commits into from
Jan 26, 2022
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
91 changes: 57 additions & 34 deletions lib/evmone/baseline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,78 +105,96 @@ inline evmc_status_code check_requirements(
}


/// The execution position.
struct Position
gumb0 marked this conversation as resolved.
Show resolved Hide resolved
{
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.
/// @{
inline code_iterator invoke(
void (*instr_fn)(StackTop) noexcept, ExecutionState& state, code_iterator pos) noexcept
void (*instr_fn)(StackTop) noexcept, Position pos, ExecutionState& /*state*/) noexcept
{
instr_fn(state.stack.top_item);
return pos + 1;
instr_fn(pos.stack_top);
return pos.code_it + 1;
}

inline code_iterator invoke(
StopToken (*instr_fn)() noexcept, ExecutionState& state, code_iterator /*pos*/) noexcept
StopToken (*instr_fn)() noexcept, Position /*pos*/, ExecutionState& state) noexcept
{
state.status = instr_fn().status;
return nullptr;
}

inline code_iterator invoke(evmc_status_code (*instr_fn)(StackTop, ExecutionState&) noexcept,
ExecutionState& state, code_iterator pos) noexcept
Position pos, ExecutionState& state) noexcept
{
if (const auto status = instr_fn(state.stack.top_item, state); status != EVMC_SUCCESS)
if (const auto status = instr_fn(pos.stack_top, state); status != EVMC_SUCCESS)
{
state.status = status;
return nullptr;
}
return pos + 1;
return pos.code_it + 1;
}

inline code_iterator invoke(void (*instr_fn)(StackTop, ExecutionState&) noexcept,
ExecutionState& state, code_iterator pos) noexcept
inline code_iterator invoke(void (*instr_fn)(StackTop, ExecutionState&) noexcept, Position pos,
ExecutionState& state) noexcept
{
instr_fn(state.stack.top_item, state);
return pos + 1;
instr_fn(pos.stack_top, state);
return pos.code_it + 1;
}

inline code_iterator invoke(
code_iterator (*instr_fn)(StackTop, ExecutionState&, code_iterator) noexcept,
ExecutionState& state, code_iterator pos) noexcept
code_iterator (*instr_fn)(StackTop, ExecutionState&, code_iterator) noexcept, Position pos,
ExecutionState& state) noexcept
{
return instr_fn(state.stack.top_item, state, pos);
return instr_fn(pos.stack_top, state, pos.code_it);
}

inline code_iterator invoke(StopToken (*instr_fn)(StackTop, ExecutionState&) noexcept,
ExecutionState& state, code_iterator /*pos*/) noexcept
inline code_iterator invoke(StopToken (*instr_fn)(StackTop, ExecutionState&) noexcept, Position pos,
ExecutionState& state) noexcept
{
state.status = instr_fn(state.stack.top_item, state).status;
state.status = instr_fn(pos.stack_top, state).status;
return nullptr;
}
/// @}

/// A helper to invoke the instruction implementation of the given opcode Op.
template <evmc_opcode Op>
[[gnu::always_inline]] inline code_iterator invoke(
const CostTable& cost_table, ExecutionState& state, code_iterator pos) noexcept
[[gnu::always_inline]] inline Position invoke(const CostTable& cost_table,
const uint256* stack_bottom, Position pos, ExecutionState& state) noexcept
{
if (const auto status = check_requirements<Op>(cost_table, state.gas_left, state.stack.size());
const auto stack_size = static_cast<int>(pos.stack_top - stack_bottom);
if (const auto status = check_requirements<Op>(cost_table, state.gas_left, stack_size);
status != EVMC_SUCCESS)
{
state.status = status;
return nullptr;
return {nullptr, pos.stack_top};
}
const auto new_pos = invoke(instr::core::impl<Op>, state, pos);
state.stack.top_item += instr::traits[Op].stack_height_change;
return new_pos;
const auto new_pos = invoke(instr::core::impl<Op>, pos, state);
const auto new_stack_top = pos.stack_top + instr::traits[Op].stack_height_change;
return {new_pos, new_stack_top};
}


/// Implementation of a generic instruction "case".
#define DISPATCH_CASE(OPCODE) \
case OPCODE: \
ASM_COMMENT(OPCODE); \
if (code_it = invoke<OPCODE>(cost_table, state, code_it); !code_it) \
goto exit; \
#define DISPATCH_CASE(OPCODE) \
case OPCODE: \
ASM_COMMENT(OPCODE); \
\
if (const auto next = invoke<OPCODE>(cost_table, stack_bottom, position, state); \
next.code_it == nullptr) \
{ \
goto exit; \
} \
else \
{ \
/* Update current position only when no error, \
this improves compiler optimization. */ \
position = next; \
} \
break

template <bool TracingEnabled>
Expand All @@ -194,17 +212,22 @@ evmc_result execute(const VM& vm, ExecutionState& state, const CodeAnalysis& ana
const auto& cost_table = get_baseline_cost_table(state.rev);

const auto* const code = state.code.data();
auto code_it = code; // Code iterator for the interpreter loop.
while (true) // Guaranteed to terminate because padded code ends with STOP.
const auto stack_bottom = state.stack.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<uint32_t>(code_it - code);
const auto offset = static_cast<uint32_t>(position.code_it - code);
const auto stack_height = static_cast<int>(position.stack_top - stack_bottom);
if (offset < state.code.size()) // Skip STOP from code padding.
tracer->notify_instruction_start(offset, state);
tracer->notify_instruction_start(offset, position.stack_top, stack_height, state);
}

const auto op = *code_it;
const auto op = *position.code_it;
switch (op)
{
#define X(OPCODE, IGNORED) DISPATCH_CASE(OPCODE);
Expand Down
19 changes: 11 additions & 8 deletions lib/evmone/tracing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class HistogramTracer : public Tracer
m_contexts.emplace(msg.depth, code.data(), evmc_get_instruction_names_table(rev));
}

void on_instruction_start(uint32_t pc, const ExecutionState& /*state*/) noexcept override
void on_instruction_start(uint32_t pc, const intx::uint256* /*stack_top*/, int /*stack_height*/,
const ExecutionState& /*state*/) noexcept override
{
auto& ctx = m_contexts.top();
++ctx.counts[ctx.code[pc]];
Expand Down Expand Up @@ -82,15 +83,16 @@ class InstructionTracer : public Tracer
const char* const* m_opcode_names = nullptr;
std::ostream& m_out; ///< Output stream.

void output_stack(const Stack& stack)
void output_stack(const intx::uint256* stack_top, int stack_height)
{
const auto top = stack.size() - 1;
m_out << R"(,"stack":[)";
for (int i = top; i >= 0; --i)
const auto stack_end = stack_top + 1;
const auto stack_begin = stack_end - stack_height;
for (auto it = stack_begin; it != stack_end; ++it)
gumb0 marked this conversation as resolved.
Show resolved Hide resolved
{
if (i != top)
if (it != stack_begin)
m_out << ',';
m_out << R"("0x)" << to_string(stack[i], 16) << '"';
m_out << R"("0x)" << to_string(*it, 16) << '"';
}
m_out << ']';
}
Expand All @@ -109,7 +111,8 @@ class InstructionTracer : public Tracer
m_out << "}\n";
}

void on_instruction_start(uint32_t pc, const ExecutionState& state) noexcept override
void on_instruction_start(uint32_t pc, const intx::uint256* stack_top, int stack_height,
const ExecutionState& state) noexcept override
{
const auto& ctx = m_contexts.top();

Expand All @@ -119,7 +122,7 @@ class InstructionTracer : public Tracer
m_out << R"(,"op":)" << int{opcode};
m_out << R"(,"opName":")" << get_name(m_opcode_names, opcode) << '"';
m_out << R"(,"gas":)" << state.gas_left;
output_stack(state.stack);
output_stack(stack_top, stack_height);

// Full memory can be dumped as evmc::hex({state.memory.data(), state.memory.size()}),
// but this should not be done by default. Adding --tracing=+memory option would be nice.
Expand Down
13 changes: 8 additions & 5 deletions lib/evmone/tracing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#pragma once

#include <evmc/instructions.h>
#include <intx/intx.hpp>
#include <memory>
#include <ostream>
#include <string_view>
Expand All @@ -16,7 +17,7 @@ struct ExecutionState;

class Tracer
{
friend class VM; // Has access the the m_next_tracer to traverse the list forward.
friend class VM; // Has access the m_next_tracer to traverse the list forward.
std::unique_ptr<Tracer> m_next_tracer;

public:
Expand All @@ -38,17 +39,19 @@ class Tracer
}

void notify_instruction_start( // NOLINT(misc-no-recursion)
uint32_t pc, const ExecutionState& state) noexcept
uint32_t pc, intx::uint256* stack_top, int stack_height,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters are not beautiful here. The best option seems to be std::span<const uint256>, but we wait for C++20.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Silkworm we use gsl::span from https://github.com/microsoft/GSL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not using std::span from C++20?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because Silkworm is compatible with C++17.

const ExecutionState& state) noexcept
{
on_instruction_start(pc, state);
on_instruction_start(pc, stack_top, stack_height, state);
if (m_next_tracer)
m_next_tracer->notify_instruction_start(pc, state);
m_next_tracer->notify_instruction_start(pc, stack_top, stack_height, state);
}

private:
virtual void on_execution_start(
evmc_revision rev, const evmc_message& msg, bytes_view code) noexcept = 0;
virtual void on_instruction_start(uint32_t pc, const ExecutionState& state) noexcept = 0;
virtual void on_instruction_start(uint32_t pc, const intx::uint256* stack_top, int stack_height,
const ExecutionState& state) noexcept = 0;
virtual void on_execution_end(const evmc_result& result) noexcept = 0;
};

Expand Down
4 changes: 2 additions & 2 deletions test/unittests/tracing_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class tracing : public Test

void on_execution_end(const evmc_result& /*result*/) noexcept override { m_code = nullptr; }

void on_instruction_start(
uint32_t pc, const evmone::ExecutionState& /*state*/) noexcept override
void on_instruction_start(uint32_t pc, const intx::uint256* /*stack_top*/,
int /*stack_height*/, const evmone::ExecutionState& /*state*/) noexcept override
{
const auto opcode = m_code[pc];
m_trace << m_name << pc << ":"
Expand Down