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

Histogram tracer #323

Merged
merged 5 commits into from
May 21, 2021
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
6 changes: 2 additions & 4 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ jobs:
executor: linux-gcc-latest
environment:
BUILD_TYPE: Coverage
TESTS_FILTER: unittests
TESTS_FILTER: unittests|integration
steps:
- build
- test
Expand All @@ -290,8 +290,6 @@ jobs:
environment:
CMAKE_OPTIONS: -DSANITIZE=undefined,implicit-conversion,nullability
UBSAN_OPTIONS: halt_on_error=1
# TODO: There is unresolved __ubsan_vptr_type_cache in evmone.so
TESTS_FILTER: unittests
steps:
- build
- test
Expand All @@ -300,7 +298,7 @@ jobs:
executor: linux-clang-latest
environment:
BUILD_TYPE: Coverage
TESTS_FILTER: unittests
TESTS_FILTER: unittests|integration
steps:
- build
- run:
Expand Down
1 change: 1 addition & 0 deletions lib/evmone/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ add_library(evmone
instructions_calls.cpp
limits.hpp
opcodes_helpers.h
tracing.cpp
tracing.hpp
vm.cpp
vm.hpp
Expand Down
73 changes: 73 additions & 0 deletions lib/evmone/tracing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2021 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "tracing.hpp"
#include <evmc/hex.hpp>
#include <stack>

namespace evmone
{
namespace
{
std::string get_name(const char* const* names, uint8_t opcode)
{
const auto name = names[opcode];
return (name != nullptr) ? name : "0x" + evmc::hex(opcode);
}

/// @see create_histogram_tracer()
class HistogramTracer : public Tracer
{
struct Context
{
const int32_t depth;
const uint8_t* const code;
const char* const* const opcode_names;
uint32_t counts[256]{};

Context(int32_t _depth, const uint8_t* _code, const char* const* _opcode_names) noexcept
: depth{_depth}, code{_code}, opcode_names{_opcode_names}
{}
};

std::stack<Context> m_contexts;
std::ostream& m_out;

void on_execution_start(
evmc_revision rev, const evmc_message& msg, bytes_view code) noexcept override
{
m_contexts.emplace(msg.depth, code.data(), evmc_get_instruction_names_table(rev));
}

void on_instruction_start(uint32_t pc) noexcept override
{
auto& ctx = m_contexts.top();
++ctx.counts[ctx.code[pc]];
}

void on_execution_end(const evmc_result& /*result*/) noexcept override
{
const auto& ctx = m_contexts.top();
const auto names = ctx.opcode_names;

m_out << "--- # HISTOGRAM depth=" << ctx.depth << "\nopcode,count\n";
for (size_t i = 0; i < std::size(ctx.counts); ++i)
{
if (ctx.counts[i] != 0)
m_out << get_name(names, static_cast<uint8_t>(i)) << ',' << ctx.counts[i] << '\n';
}

m_contexts.pop();
}

public:
explicit HistogramTracer(std::ostream& out) noexcept : m_out{out} {}
};
} // namespace

std::unique_ptr<Tracer> create_histogram_tracer(std::ostream& out)
Copy link
Member

Choose a reason for hiding this comment

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

Would add a comment (or on the class) explaining what this does, i.e. outputs a histogram of opcodes in a CSV format.

{
return std::make_unique<HistogramTracer>(out);
}
} // namespace evmone
8 changes: 8 additions & 0 deletions lib/evmone/tracing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <evmc/instructions.h>
#include <memory>
#include <ostream>
#include <string_view>

namespace evmone
Expand Down Expand Up @@ -48,4 +49,11 @@ class Tracer
virtual void on_execution_end(const evmc_result& result) noexcept = 0;
};

/// Creates the "histogram" tracer which counts occurrences of individual opcodes during execution
/// and reports this data in CSV format.
///
/// @param out Report output stream.
/// @return Histogram tracer object.
EVMC_EXPORT std::unique_ptr<Tracer> create_histogram_tracer(std::ostream& out);

} // namespace evmone
22 changes: 16 additions & 6 deletions lib/evmone/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "baseline.hpp"
#include "execution.hpp"
#include <evmone/evmone.h>
#include <iostream>

namespace evmone
{
Expand All @@ -25,22 +26,31 @@ constexpr evmc_capabilities_flagset get_capabilities(evmc_vm* /*vm*/) noexcept
return EVMC_CAPABILITY_EVM1;
}

evmc_set_option_result set_option(evmc_vm* vm, char const* name, char const* value) noexcept
evmc_set_option_result set_option(evmc_vm* c_vm, char const* c_name, char const* c_value) noexcept
{
if (name[0] == 'O' && name[1] == '\0')
const auto name = (c_name != nullptr) ? std::string_view{c_name} : std::string_view{};
const auto value = (c_value != nullptr) ? std::string_view{c_value} : std::string_view{};
auto& vm = *static_cast<VM*>(c_vm);

if (name == "O")
{
if (value[0] == '0' && value[1] == '\0') // O=0
if (value == "0")
{
vm->execute = evmone::baseline::execute;
c_vm->execute = evmone::baseline::execute;
return EVMC_SET_OPTION_SUCCESS;
}
else if (value[0] == '2' && value[1] == '\0') // O=2
else if (value == "2")
{
vm->execute = evmone::execute;
c_vm->execute = evmone::execute;
return EVMC_SET_OPTION_SUCCESS;
}
return EVMC_SET_OPTION_INVALID_VALUE;
}
else if (name == "histogram")
{
vm.add_tracer(create_histogram_tracer(std::cerr));
return EVMC_SET_OPTION_SUCCESS;
}
return EVMC_SET_OPTION_INVALID_NAME;
}

Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ find_package(benchmark CONFIG REQUIRED)

add_subdirectory(utils)
add_subdirectory(bench)
add_subdirectory(integration)
add_subdirectory(internal_benchmarks)
add_subdirectory(unittests)

Expand Down
24 changes: 24 additions & 0 deletions test/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# evmone: Fast Ethereum Virtual Machine implementation
# Copyright 2021 The evmone Authors.
# SPDX-License-Identifier: Apache-2.0

set(PREFIX ${PROJECT_NAME}/integration)

get_target_property(EVMONE_LIB_TYPE evmone TYPE)
if(EVMONE_LIB_TYPE STREQUAL SHARED_LIBRARY)

add_test(NAME ${PREFIX}/histogram COMMAND $<TARGET_FILE:evmc::tool> --vm $<TARGET_FILE:evmone>,O=0,histogram run 6000808080800101010200)
set_tests_properties(
${PREFIX}/histogram PROPERTIES PASS_REGULAR_EXPRESSION
"--- # HISTOGRAM depth=0
opcode,count
STOP,1
ADD,3
MUL,1
PUSH1,1
DUP1,4
")

get_property(ALL_TESTS DIRECTORY PROPERTY TESTS)
set_tests_properties("${ALL_TESTS}" PROPERTIES ENVIRONMENT LLVM_PROFILE_FILE=${CMAKE_BINARY_DIR}/integration-%p.profraw)
endif()
42 changes: 41 additions & 1 deletion test/unittests/tracing_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ class tracing : public Test
vm{*static_cast<evmone::VM*>(m_baseline_vm.get_raw_pointer())}
{}

std::string trace(bytes_view code)
std::string trace(bytes_view code, int32_t depth = 0)
{
evmc::MockedHost host;
evmc_message msg{};
msg.depth = depth;
msg.gas = 1000000;
m_baseline_vm.execute(host, EVMC_BERLIN, msg, code.data(), code.size());
auto result = trace_stream.str();
Expand Down Expand Up @@ -95,3 +96,42 @@ TEST_F(tracing, three_tracers)

EXPECT_EQ(trace(dup1(0)), "A0:PUSH1 B0:PUSH1 C0:PUSH1 A2:DUP1 B2:DUP1 C2:DUP1 ");
}

TEST_F(tracing, histogram)
{
vm.add_tracer(evmone::create_histogram_tracer(trace_stream));

trace_stream << '\n';
EXPECT_EQ(trace(add(0, 0)), R"(
--- # HISTOGRAM depth=0
Copy link
Member

Choose a reason for hiding this comment

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

Is this valid in CSV?

Copy link
Member Author

Choose a reason for hiding this comment

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

No. But this is yaml document start mark.

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 CSV can only have optional header line with column names. You cannot have any title line.

The fancier YAML wrapper would be

--- # HISTOGRAM
depth: 0
csv: |
  opcode,count
  ADD,1
  PUSH1,2

Copy link
Member

Choose a reason for hiding this comment

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

So why not output YAML and then whoever wants CSV can do the conversion themselves (or in worst-case by skipping the top 3 lines) ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because nobody wants YAML. I used --- because of no better ideas.

opcode,count
ADD,1
PUSH1,2
)");
}

TEST_F(tracing, histogram_undefined_instruction)
{
vm.add_tracer(evmone::create_histogram_tracer(trace_stream));

trace_stream << '\n';
EXPECT_EQ(trace(bytecode{"EF"}), R"(
--- # HISTOGRAM depth=0
opcode,count
0xef,1
)");
}

TEST_F(tracing, histogram_internal_call)
{
vm.add_tracer(evmone::create_histogram_tracer(trace_stream));
trace_stream << '\n';
EXPECT_EQ(trace(push(0) + OP_DUP1 + OP_SWAP1 + OP_POP + OP_POP, 1), R"(
--- # HISTOGRAM depth=1
opcode,count
POP,2
PUSH1,1
DUP1,1
SWAP1,1
)");
}