From 4c9122b951a86704f0ae9ca4c0098af57480be65 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Tue, 18 Aug 2020 17:07:32 -0400 Subject: [PATCH 01/19] Initial attempt at a sampling profiler --- include/eosio/vm/allocator.hpp | 2 + include/eosio/vm/backend.hpp | 31 +- include/eosio/vm/bitcode_writer.hpp | 5 + include/eosio/vm/debug_info.hpp | 102 +++++++ include/eosio/vm/exceptions.hpp | 1 + include/eosio/vm/execution_context.hpp | 59 ++++ include/eosio/vm/null_writer.hpp | 3 + include/eosio/vm/parser.hpp | 22 +- include/eosio/vm/profile.hpp | 400 +++++++++++++++++++++++++ include/eosio/vm/x86_64.hpp | 7 + tools/CMakeLists.txt | 3 + tools/interp.cpp | 5 +- tools/nm.cpp | 78 +++++ 13 files changed, 698 insertions(+), 20 deletions(-) create mode 100644 include/eosio/vm/debug_info.hpp create mode 100644 include/eosio/vm/profile.hpp create mode 100644 tools/nm.cpp diff --git a/include/eosio/vm/allocator.hpp b/include/eosio/vm/allocator.hpp index e674b28c..04aab812 100644 --- a/include/eosio/vm/allocator.hpp +++ b/include/eosio/vm/allocator.hpp @@ -316,6 +316,8 @@ namespace eosio { namespace vm { mprotect(_code_base, _code_size, PROT_NONE); } + const void* get_code_start() const { return _code_base; } + /* different semantics than free, * the memory must be at the end of the most recently allocated block. */ diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 54ba6a64..1f1a997e 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -24,32 +24,32 @@ namespace eosio { namespace vm { struct jit { template using context = jit_execution_context; - template - using parser = binary_parser>, Options>; + template + using parser = binary_parser>, Options, DebugInfo>; static constexpr bool is_jit = true; }; struct interpreter { template using context = execution_context; - template - using parser = binary_parser; + template + using parser = binary_parser; static constexpr bool is_jit = false; }; struct null_backend { template using context = null_execution_context; - template - using parser = binary_parser; + template + using parser = binary_parser; static constexpr bool is_jit = false; }; - template + template class backend { using host_t = detail::host_type_t; using context_t = typename Impl::template context; - using parser_t = typename Impl::template parser; + using parser_t = typename Impl::template parser; void construct(host_t* host=nullptr) { mod.finalize(); ctx.set_wasm_allocator(memory_alloc); @@ -61,32 +61,32 @@ namespace eosio { namespace vm { } public: backend(wasm_code&& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code&& code, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } backend(wasm_code& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code& code, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module(code, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } backend(wasm_code_ptr& ptr, size_t sz, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(&host); } backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod), detail::get_max_call_depth(options)) { + : memory_alloc(alloc), ctx(parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod, debug), detail::get_max_call_depth(options)) { ctx.set_max_pages(detail::get_max_pages(options)); construct(); } @@ -236,9 +236,12 @@ namespace eosio { namespace vm { inline void exit(const std::error_code& ec) { ctx.exit(ec); } inline auto& get_context() { return ctx; } + const DebugInfo& get_debug() const { return debug; } + private: wasm_allocator* memory_alloc = nullptr; // non owning pointer module mod; + DebugInfo debug; context_t ctx; }; }} // namespace eosio::vm diff --git a/include/eosio/vm/bitcode_writer.hpp b/include/eosio/vm/bitcode_writer.hpp index 4d734bc3..295b009d 100644 --- a/include/eosio/vm/bitcode_writer.hpp +++ b/include/eosio/vm/bitcode_writer.hpp @@ -299,7 +299,12 @@ namespace eosio { namespace vm { body.code = fb.raw(); body.size = op_index + 1; _base_offset += body.size; + op_index++; } + + const void* get_addr() const { return fb.raw() + op_index; } + const void* get_base_addr() const { return _code_segment_base; } + private: growable_allocator& _allocator; diff --git a/include/eosio/vm/debug_info.hpp b/include/eosio/vm/debug_info.hpp new file mode 100644 index 00000000..a85d44b0 --- /dev/null +++ b/include/eosio/vm/debug_info.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace eosio::vm { + +struct null_debug_info { + using builder = null_debug_info; + void on_code_start(const void* compiled_base, const void* wasm_code_start) {} + void on_function_start(const void* code_addr, const void* wasm_addr) {} + void on_instr_start(const void* code_addr, const void* wasm_addr) {} + void on_code_end(const void* code_addr, const void* wasm_addr) {} + void set(const null_debug_info&) {} + void relocate(const void*) {} +}; + +// Maps a contiguous region of code to offsets onto the code section of the original wasm. +class profile_instr_map { + struct addr_entry { + uint32_t offset; + uint32_t wasm_addr; + }; + +public: + + struct builder { + void on_code_start(const void* compiled_base, const void* wasm_code_start) { + code_base = compiled_base; + wasm_base = wasm_code_start; + } + void on_function_start(const void* code_addr, const void* wasm_addr) { + data.push_back({ + static_cast(reinterpret_cast(code_addr) - reinterpret_cast(code_base)), + static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base)) + }); + } + void on_instr_start(const void* code_addr, const void* wasm_addr) { + data.push_back({ + static_cast(reinterpret_cast(code_addr) - reinterpret_cast(code_base)), + static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base)) + }); + } + void on_code_end(const void* code_addr, const void* wasm_addr) { + code_end = code_addr; + } + + const void* code_base; + const void* wasm_base; + const void* code_end; + std::vector data; + }; + + void set(builder&& b) { + data = std::move(b.data); + std::sort(data.begin(), data.end(), [](const addr_entry& lhs, const addr_entry& rhs){ return lhs.offset < rhs.offset; }); + base_address = b.code_base; + code_size = reinterpret_cast(b.code_end) - reinterpret_cast(base_address); + offset_to_addr = data.data(); + offset_to_addr_len = data.size(); + } + + profile_instr_map() = default; + profile_instr_map(const profile_instr_map&) = delete; + profile_instr_map& operator=(const profile_instr_map&) = delete; + + // Indicate that the executable code was moved/copied/mmapped/etc to another location + void relocate(const void* new_base) { base_address = new_base; } + + // Cannot use most of the standard library as the STL is not async-signal-safe + std::uint32_t translate(const void* pc) const { + std::size_t diff = (reinterpret_cast(pc) - reinterpret_cast(base_address)); // negative values wrap + if(diff >= code_size || diff < offset_to_addr[0].offset) return 0xFFFFFFFFu; + std::uint32_t offset = diff; + + // Loop invariant: offset_to_addr[lower].offset <= offset < offset_to_addr[upper].offset + std::size_t lower = 0, upper = offset_to_addr_len; + while(upper - lower > 1) { + std::size_t mid = lower + (upper - lower) / 2; + if(offset_to_addr[mid].offset <= offset) { + lower = mid; + } else { + upper = mid; + } + } + + return offset_to_addr[lower].wasm_addr; + } +private: + const void* base_address; + std::size_t code_size; + + addr_entry* offset_to_addr; + std::size_t offset_to_addr_len; + + std::vector data; +}; + +} diff --git a/include/eosio/vm/exceptions.hpp b/include/eosio/vm/exceptions.hpp index 199db233..2cf8b14f 100644 --- a/include/eosio/vm/exceptions.hpp +++ b/include/eosio/vm/exceptions.hpp @@ -42,6 +42,7 @@ namespace eosio { namespace vm { DECLARE_EXCEPTION( timeout_exception, 4010001, "timeout" ) DECLARE_EXCEPTION( wasm_exit_exception, 4010002, "exit" ) DECLARE_EXCEPTION( span_exception, 4020000, "span exception" ) + DECLARE_EXCEPTION( profile_exception, 4030000, "profile exception" ) }} // eosio::vm #undef DECLARE_EXCEPTION diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 2a7ad9f0..3fc01cc6 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -24,6 +24,13 @@ #include #include +#ifdef __APPLE__ +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 700 +#endif +#endif +#include + namespace eosio { namespace vm { struct null_host_functions { @@ -283,6 +290,13 @@ namespace eosio { namespace vm { } auto fn = reinterpret_cast(_mod.code[func_index - _mod.get_imported_functions_size()].jit_code_offset + _mod.allocator._code_base); + std::atomic_signal_fence(std::memory_order_release); + void* old_frame = _top_frame.load(std::memory_order_relaxed); + _top_frame.store(__builtin_frame_address(0), std::memory_order_relaxed); + auto restore = scope_guard{[this, old_frame]{ + _top_frame.store(old_frame, std::memory_order_relaxed); + std::atomic_signal_fence(std::memory_order_release); // As close to the correct fence as possible. + }}; vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); }, &handle_signal); @@ -302,6 +316,37 @@ namespace eosio { namespace vm { } __builtin_unreachable(); } + + int backtrace(void** out, int count, void* uc) const { + void* end = _top_frame.load(std::memory_order_relaxed); + std::atomic_signal_fence(std::memory_order_acquire); + if(end == nullptr) return 0; + void* rbp; + int i = 0; + if(count != 0) { + if(uc) { +#ifdef __APPLE__ + out[i++] = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rip); + rbp = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rbp); +#else + out[i++] = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RIP]); + rbp = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RBP]); +#endif + } else { + rbp = __builtin_frame_address(0); + } + } + //printf("top: %p, start %p\n", end, rbp); + while(i < count) { + void* rip = static_cast(rbp)[1]; + if(rbp == end) break; + out[i++] = rip; + rbp = *static_cast(rbp); + //printf("%p, %p\n", rip, rbp); + } + return i; + } + protected: template @@ -372,6 +417,7 @@ namespace eosio { namespace vm { host_type * _host = nullptr; uint32_t _remaining_call_depth; + std::atomic _top_frame; }; template @@ -630,6 +676,19 @@ namespace eosio { namespace vm { } } + // This isn't async-signal-safe. Cross fingers and hope for the best. + // It's only used for profiling. + int backtrace(void** data, int limit) const { + int out = 0; + if(limit != 0) { + data[out++] = _state.pc; + } + for(int i = 0; out < limit && i < _as.size(); ++i) { + data[out++] = _as.get_back(i).pc; + } + return out; + } + private: template diff --git a/include/eosio/vm/null_writer.hpp b/include/eosio/vm/null_writer.hpp index f1e77442..e8f95d1f 100644 --- a/include/eosio/vm/null_writer.hpp +++ b/include/eosio/vm/null_writer.hpp @@ -201,6 +201,9 @@ class null_writer { void emit_prologue(const func_type& /*ft*/, const guarded_vector& /*locals*/, uint32_t /*idx*/) {} void emit_epilogue(const func_type& /*ft*/, const guarded_vector& /*locals*/, uint32_t /*idx*/) {} void finalize(function_body& /*body*/) {} + + const void* get_addr() const { return nullptr; } + const void* get_base_addr() const { return nullptr; } }; }} diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index b91fea55..89c3b9e7 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -195,7 +196,7 @@ namespace eosio { namespace vm { } - template + template class binary_parser { public: explicit binary_parser(growable_allocator& alloc, const Options& options = Options{}) : _allocator(alloc), _options(options) {} @@ -286,18 +287,18 @@ namespace eosio { namespace vm { return result; } - inline module& parse_module(wasm_code& code, module& mod) { + inline module& parse_module(wasm_code& code, module& mod, DebugInfo& debug) { wasm_code_ptr cp(code.data(), code.size()); - parse_module(cp, code.size(), mod); + parse_module(cp, code.size(), mod, debug); return mod; } - inline module& parse_module2(wasm_code_ptr& code_ptr, size_t sz, module& mod) { + inline module& parse_module2(wasm_code_ptr& code_ptr, size_t sz, module& mod, DebugInfo& debug) { parse_module(code_ptr, sz, mod); return mod; } - void parse_module(wasm_code_ptr& code_ptr, size_t sz, module& mod) { + void parse_module(wasm_code_ptr& code_ptr, size_t sz, module& mod, DebugInfo& debug) { _mod = &mod; EOS_VM_ASSERT(parse_magic(code_ptr) == constants::magic, wasm_parse_exception, "magic number did not match"); EOS_VM_ASSERT(parse_version(code_ptr) == constants::version, wasm_parse_exception, @@ -341,6 +342,9 @@ namespace eosio { namespace vm { } } EOS_VM_ASSERT(_mod->code.size() == _mod->functions.size(), wasm_parse_exception, "code section must have the same size as the function section" ); + + debug.set(std::move(imap)); + debug.relocate(_allocator.get_code_start()); } inline uint32_t parse_magic(wasm_code_ptr& code) { @@ -677,6 +681,8 @@ namespace eosio { namespace vm { void parse_function_body_code(wasm_code_ptr& code, size_t bounds, const detail::max_func_local_bytes_stack_checker& local_bytes_checker, Writer& code_writer, const func_type& ft, const local_types_t& local_types) { + imap.on_function_start(code_writer.get_addr(), code.raw()); + // Initialize the control stack with the current function as the sole element operand_stack_type_tracker op_stack{local_bytes_checker, _options}; std::vector pc_stack{{ @@ -742,6 +748,8 @@ namespace eosio { namespace vm { EOS_VM_ASSERT(pc_stack.size() <= detail::get_max_nested_structures(_options), wasm_parse_exception, "nested structures validation failure"); + imap.on_instr_start(code_writer.get_addr(), code.raw()); + switch (*code++) { case opcodes::unreachable: check_in_bounds(); code_writer.emit_unreachable(); op_stack.start_unreachable(); break; case opcodes::nop: code_writer.emit_nop(); break; @@ -1251,10 +1259,12 @@ namespace eosio { namespace vm { template inline void parse_section(wasm_code_ptr& code, vec>& elems) { + const void* code_start = code.raw(); parse_section_impl(code, elems, detail::get_max_function_section_elements(_options), [&](wasm_code_ptr& code, function_body& fb, std::size_t idx) { parse_function_body(code, fb, idx); }); EOS_VM_ASSERT( elems.size() == _mod->functions.size(), wasm_parse_exception, "code section must have the same size as the function section" ); Writer code_writer(_allocator, code.bounds() - code.offset(), *_mod); + imap.on_code_start(code_writer.get_base_addr(), code_start); for (size_t i = 0; i < _function_bodies.size(); i++) { function_body& fb = _mod->code[i]; func_type& ft = _mod->types.at(_mod->functions.at(i)); @@ -1264,6 +1274,7 @@ namespace eosio { namespace vm { code_writer.emit_epilogue(ft, fb.locals, i); code_writer.finalize(fb); } + imap.on_code_end(code_writer.get_addr(), code.raw()); } template inline void parse_section(wasm_code_ptr& code, @@ -1314,5 +1325,6 @@ namespace eosio { namespace vm { std::vector>> _function_bodies; detail::max_mutable_globals_checker _globals_checker; detail::eosio_max_nested_structures_checker _nested_checker; + typename DebugInfo::builder imap; }; }} // namespace eosio::vm diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp new file mode 100644 index 00000000..8116e344 --- /dev/null +++ b/include/eosio/vm/profile.hpp @@ -0,0 +1,400 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosio::vm { + +struct profile_data { + // buffer_size is the size of the I/O write buffer. The buffer must be at least large enough for one item. + // hash_table_size is the maximum number of unique traces that can be stored in memory. It must be a power of 2. + template + profile_data(const std::string& file, Backend& bkend, const std::size_t buffer_size = 65536, std::size_t hash_table_size = 1024) : + addr_map(bkend.get_debug()), + outbuf_storage(buffer_size), + items_storage(hash_table_size), + table_storage(hash_table_size) // load factor of 1 + { + init_backtrace(bkend.get_context()); + + outbuf = outbuf_storage.data(); + outpos = 0; + outsize = outbuf_storage.size(); + + list_header* prev = &mru_list; + for(auto& item : items_storage) { + item.mru.prev = prev; + item.mru.next = reinterpret_cast(&item + 1); // Avoid undefined behavior at the end + prev = &item.mru; + } + items_storage.back().mru.next = &mru_list; + mru_list.next = &items_storage.front().mru; + mru_list.prev = &items_storage.back().mru; + + table = table_storage.data(); + table_size = table_storage.size(); + + fd = open(file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755); + write_header(); + } + ~profile_data() { + flush_hash(); + write_trailer(); + flush(); + close(fd); + } + void write_header() { + uint32_t header[] = { + 0, + 3, + 0, + 10000, + 0 + }; + write(reinterpret_cast(header), sizeof(header)); + } + void write_trailer() { + uint32_t trailer[] = { 0, 1, 0 }; + write(reinterpret_cast(trailer), sizeof(trailer)); + } + void flush() { + for(std::size_t i = 0; i < outpos;) { + auto res = ::write(fd, outbuf + i, outpos - i); + if(res == -1) { + // report_error + break; + } else { + i += res; + } + } + } + + void write(const char* data, std::size_t size) { + if(size + outpos >= outsize) { + flush(); + } + std::memcpy(outbuf + outpos, data, size); + outpos += size; + } + + static constexpr std::size_t max_frames = 251; + + struct list_header { + list_header* next; + list_header* prev; + void unlink() { + next->prev = prev; + prev->next = next; + } + }; + + struct item { + list_header mru; + item* next; + uint32_t bucket = 0xFFFFFFFF; + uint32_t count = 0; + uint32_t len; + uint32_t frames[max_frames]; + // std::hash is not async-signal-safe + std::size_t hash() const { + // MurmurHash64A + // Including len gives a multiple of 2. + static_assert(max_frames % 2 == 1); + // Not strictly necessary for correctness, but avoids unaligned loads + static_assert(offsetof(item, len) % 8 == 0); + constexpr std::uint64_t mul = 0xc6a4a7935bd1e995ull; + constexpr std::uint64_t seed = 0xbadd00d00ull; + constexpr auto shift_mix = [](std::uint64_t v) { return v ^ (v >> 47); }; + int word_len = len/2+1; // if len is even, add an extra 0 word. + uint64_t hash = seed ^ (word_len * 8 * mul); + const char* base_addr = reinterpret_cast(&len); + for(int i = 0; i < word_len; ++i) { + std::uint64_t val; + memcpy(&val, base_addr + 8*i, 8); + hash = (hash ^ shift_mix(val * mul) * mul) * mul; + } + return shift_mix(shift_mix(hash) * mul); + } + }; + + static bool traces_equal(const item* lhs, const item* rhs) { + if(lhs->len != rhs->len) { + return false; + } + for(uint32_t i = 0; i < lhs->len; ++i) { + if(lhs->frames[i] != rhs->frames[i]) { + return false; + } + } + return true; + } + + void write(const item* item) { + write(reinterpret_cast(&item->count), (2+item->len) * sizeof(std::uint32_t)); + } + + item* evict_oldest() { + item* result = reinterpret_cast(mru_list.prev); + if(result->bucket != 0xFFFFFFFFu) { + write(result); + for(item** entry = &table[result->bucket]; ; entry = &(*entry)->next) { + if(*entry == result) { + *entry = result->next; + break; + } + } + result->bucket = 0xFFFFFFFFu; + } + return result; + } + + void move_to_head(list_header* new_head) { + new_head->unlink(); + new_head->next = mru_list.next; + new_head->prev = &mru_list; + mru_list.next->prev = new_head; + mru_list.next = new_head; + } + + // Inserts an item into the hash table OR combines it with an existing entry + // The new or modified item will be moved to the head of the MRU list. + void insert_hash(item* new_item) { + std::size_t hash = new_item->hash(); + std::size_t idx = hash & (table_size - 1); + for(item* bucket_entry = table[idx]; bucket_entry; bucket_entry = bucket_entry->next) { + if(traces_equal(new_item, bucket_entry)) { + ++bucket_entry->count; + move_to_head(&bucket_entry->mru); + return; + } + } + new_item->next = table[idx]; + new_item->bucket = idx; + new_item->count = 1; + table[idx] = new_item; + move_to_head(&new_item->mru); + } + + void flush_hash() { + for(std::size_t i = 0; i < table_size; ++i) { + for(item* entry = table[i]; entry; entry = entry->next) { + write(entry); + entry->bucket = 0xFFFFFFFF; + } + table[i] = nullptr; + } + } + + // The signal handler calling this must not use SA_NODEFER + void handle_tick(void** data, int count) { + item* entry = evict_oldest(); + + int out = 0; + // Translate addresses to wasm addresses; skip any frames that are outside wasm + for(int i = 0; i < count && out < max_frames; ++i) { + auto addr = addr_map.translate(data[i]); + if(addr != 0xFFFFFFFFu) { + if(out > 0) { + ++addr; + } + entry->frames[out++] = addr; + } + } + if(out != 0) { + entry->len = out; + if(out % 2 == 0) { + entry->frames[out] = 0; // so hashing can align to a 64-bit boundary + } + insert_hash(entry); + } + } + + int fd; + + char* outbuf; + std::size_t outpos; + std::size_t outsize; + + list_header mru_list; + item** table; + std::size_t table_size; // must be a power of 2 + + // Backend specific backtrace + const profile_instr_map& addr_map; + int (*get_backtrace_fn)(const void*, void**, int, void*); + const void* exec_context; + + template + void init_backtrace(const Context& context) { + get_backtrace_fn = [](const void* ctx, void** data, int len, void* uc) { + return static_cast(ctx)->backtrace(data, len, uc); + }; + exec_context = &context; + } + + // Unsafe to access from a signal handler + std::vector outbuf_storage; + std::vector items_storage; + std::vector table_storage; +}; + +inline void profile_handler(int sig, siginfo_t *info, void *); + +inline void register_profile_signal_handler_impl() { + struct sigaction sa; + sa.sa_sigaction = profile_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sigaction(SIGPROF, &sa, nullptr); +} + +inline void register_profile_signal_handler() { + static int init_helper = (register_profile_signal_handler_impl(), 0); + ignore_unused_variable_warning(init_helper); +} + +#define get_backtrace_impl(data, count, uc) ptr->get_backtrace_fn(ptr->exec_context, data, count, uc) + +#if USE_POSIX_TIMERS + +inline void profile_handler(int sig, siginfo_t *info, void * uc) { + static_assert(std::atomic::is_always_lock_free); + auto * ptr = std::atomic_load(static_cast*>(info->si_value.sival_ptr)); + if(ptr) { + int saved_errno = errno; + void* data[profile_data::max_frames*2]; // Includes both wasm and native frames + int count = get_backtrace_impl(data, sizeof(data)/sizeof(data[0]), uc); + ptr->handle_tick(data, count); + errno = saved_errno; + } +} + +struct profile_manager { + profile_manager() { + register_profile_signal_handler(); + sigevent event; + event.sigev_notify = SIGEV_THREAD_ID; + event.sigev_signo = SIGPROF; + event.sigev_value.sival_ptr = ¤t_data; + event._sigev_un._tid = gettid(); + int res = timer_create(CLOCK_MONOTONIC, &event, &timer); + EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); + struct itimerspec spec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = 10000000; + spec.it_value.tv_sec = 0; + spec.it_value.tv_nsec = 10000000; + res = timer_settime(timer, 0, &spec, nullptr); + EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); + } + void start(profile_data* data) { + EOS_VM_ASSERT(!current_data, profile_exception, "Already profiling in the current thread"); + current_data = data; + } + void stop() { + current_data = nullptr; + } + ~profile_manager() { + current_data = nullptr; + timer_delete(timer); + } + timer_t timer; + std::atomic current_data; +}; + +__attribute__((visibility("default"))) +inline std::unique_ptr per_thread_profile_manager; + +struct scoped_profile { + explicit scoped_profile(profile_data& data) { + if(!per_thread_profile_manager) { + per_thread_profile_manager = std::make_unique(); + } + per_thread_profile_manager->start(&data); + } + ~scoped_profile() { + per_thread_profile_manager->stop(); + } +}; + +#else + +__attribute__((visibility("default"))) +inline thread_local std::atomic per_thread_profile_data = ATOMIC_VAR_INIT(nullptr); + +inline void profile_handler(int sig, siginfo_t* info, void* uc) { + static_assert(std::atomic::is_always_lock_free); + auto * ptr = std::atomic_load(&per_thread_profile_data); + if(ptr) { + int saved_errno = errno; + void* data[profile_data::max_frames*2]; // Includes both wasm and native frames + int count = get_backtrace_impl(data, sizeof(data)/sizeof(data[0]), uc); + ptr->handle_tick(data, count); + errno = saved_errno; + } +} + +struct profile_manager { + profile_manager() { + register_profile_signal_handler(); + timer_thread = std::thread([this]{ + auto lock = std::unique_lock(timer_mutex); + while(!timer_cond.wait_for(lock, std::chrono::milliseconds(10), [&]{ return done; })) { + for(pthread_t notify : threads_to_notify) { + pthread_kill(notify, SIGPROF); + } + } + }); + } + void start(profile_data* data) { + auto lock = std::lock_guard(mutex); + + per_thread_profile_data = data; + threads_to_notify.push_back(pthread_self()); + } + void stop() { + per_thread_profile_data = nullptr; + auto lock = std::lock_guard(mutex); + threads_to_notify.erase(std::find(threads_to_notify.begin(), threads_to_notify.end(), pthread_self())); + } + ~profile_manager() { + { + auto lock = std::unique_lock(timer_mutex); + done = true; + } + timer_cond.notify_one(); + timer_thread.join(); + } + static profile_manager& instance() { + static profile_manager result; + return result; + } + std::mutex mutex; + std::vector threads_to_notify; + + std::thread timer_thread; + bool done = false; + std::mutex timer_mutex; + std::condition_variable timer_cond; +}; + +struct scoped_profile { + explicit scoped_profile(profile_data& data) { + profile_manager::instance().start(&data); + } + ~scoped_profile() { + profile_manager::instance().stop(); + } +}; + +#endif + +} diff --git a/include/eosio/vm/x86_64.hpp b/include/eosio/vm/x86_64.hpp index d6921f42..0b809587 100644 --- a/include/eosio/vm/x86_64.hpp +++ b/include/eosio/vm/x86_64.hpp @@ -2094,6 +2094,13 @@ namespace eosio { namespace vm { body.jit_code_offset = _code_start - (unsigned char*)_code_segment_base; } + // returns the current write address + const void* get_addr() const { + return code; + } + + const void* get_base_addr() const { return _code_segment_base; } + private: auto fixed_size_instr(std::size_t expected_bytes) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 7dba7381..ee6a2288 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -9,3 +9,6 @@ target_link_libraries(bench-interp eos-vm) add_executable(hello-driver ${CMAKE_CURRENT_SOURCE_DIR}/hello_driver.cpp) target_link_libraries(hello-driver eos-vm) + +add_executable(eos-vm-nm nm.cpp) +target_link_libraries(eos-vm-nm eos-vm) diff --git a/tools/interp.cpp b/tools/interp.cpp index 80c338b6..411aa48f 100644 --- a/tools/interp.cpp +++ b/tools/interp.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -26,7 +27,9 @@ int main(int argc, char** argv) { auto code = read_wasm( argv[1] ); // Instaniate a new backend using the wasm provided. - backend bkend( code, &wa ); + backend bkend( code, &wa ); + profile_data prof("profile.out", bkend); + scoped_profile profile_runner(prof); // Execute any exported functions provided by the wasm. bkend.execute_all(wd); diff --git a/tools/nm.cpp b/tools/nm.cpp new file mode 100644 index 00000000..296905c6 --- /dev/null +++ b/tools/nm.cpp @@ -0,0 +1,78 @@ +#include +#include +#include + +struct nm_debug_info { + using builder = nm_debug_info; + void on_code_start(const void* compiled_base, const void* wasm_code_start) { + wasm_base = wasm_code_start; + } + void on_function_start(const void* code_addr, const void* wasm_addr) { + function_offsets.push_back(static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base))); + } + void on_instr_start(const void* code_addr, const void* wasm_addr) {} + void on_code_end(const void* code_addr, const void* wasm_addr) {} + void set(nm_debug_info&& other) { *this = std::move(other); } + void relocate(const void*) {} + + const void* wasm_base; + std::vector function_offsets; +}; + +enum class nm_sort_order { + alphabetic, numeric, none +}; + +eosio::vm::export_entry* find_export_name(eosio::vm::module& mod, uint32_t idx) { + for(uint32_t i = 0; i < mod.exports.size(); ++i) { + if(mod.exports[i].index == idx && mod.exports[i].kind == eosio::vm::Function) { + return &mod.exports[i]; + } + } + return nullptr; +} + +int main(int argc, const char** argv) { + bool print_file_name = false; + nm_sort_order sort = nm_sort_order::alphabetic; + std::vector filenames; + for(int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if(std::strcmp(argv[i], "-A") == 0 || std::strcmp(argv[i], "-o") == 0 || std::strcmp(argv[i], "--print-file-name") == 0) { + print_file_name = true; + } else if(std::strcmp(argv[i], "-a") == 0 || std::strcmp(argv[i], "--debug-syms") == 0) { + // Ignored because we don't know how to parse debug symbols + } else if(std::strcmp(argv[i], "-C") == 0 || std::strncmp(argv[i], "--demangle=", 11) == 0) { + // Demangling not implemented + } else if(std::strcmp(argv[i], "--no-demangle") == 0) { + // Default + } else if(std::strcmp(argv[i], "-D") == 0 || std::strcmp(argv[i], "--dynamic") == 0) { + // Not a dynamic object + } else if(arg == "-n" || arg == "-v" || arg == "--numeric-sort") { + sort = nm_sort_order::numeric; + } else if(arg[0] != '-') { + filenames.push_back(arg); + } else { + std::cerr << "unexpected argument: " << arg << std::endl; + return 2; + } + } + + for(const std::string& filename : filenames) { + using namespace eosio::vm; + auto code = read_wasm(filename); + nm_debug_info info; + module mod; + binary_parser parser(mod.allocator); + parser.parse_module(code, mod, info); + for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { + std::cout << std::hex << std::setw(8) << std::setfill('0') << info.function_offsets[i] << " T "; + if(export_entry* name = find_export_name(mod, i + mod.get_imported_functions_size())) { + std::cout << std::string_view(reinterpret_cast(name->field_str.raw()), name->field_str.size()); + } else { + std::cout << "fn" << i + mod.get_imported_functions_size(); + } + std::cout << std::endl; + } + } +} From bd8ca56d65e83c8405b8bb9a550a260a60438c5f Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 19 Aug 2020 14:18:47 -0400 Subject: [PATCH 02/19] Add parsing of the "name" custom section --- include/eosio/vm/parser.hpp | 58 +++++++++++++++++++++++++++++++++++-- include/eosio/vm/types.hpp | 17 +++++++++++ tools/nm.cpp | 21 ++++++++++---- 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index 89c3b9e7..ea663114 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -191,6 +191,8 @@ namespace eosio { namespace vm { PARSER_OPTION(allow_zero_blocktype, false, bool) + PARSER_OPTION(parse_custom_section_name, false, bool); + #undef MAX_ELEMENTS #undef PARSER_OPTION @@ -359,9 +361,59 @@ namespace eosio { namespace vm { } inline void parse_custom(wasm_code_ptr& code) { - parse_utf8_string(code, 0xFFFFFFFFu); // ignored, but needs to be validated - // skip to the end of the section - code += code.bounds() - code.offset(); + auto section_name = parse_utf8_string(code, 0xFFFFFFFFu); // ignored, but needs to be validated + if(detail::get_parse_custom_section_name(_options) && + section_name.size() == 4 && std::memcmp(section_name.raw(), "name", 4) == 0) { + parse_name_section(code); + } else { + // skip to the end of the section + code += code.bounds() - code.offset(); + } + } + + inline void parse_name_map(wasm_code_ptr& code, guarded_vector& map) { + for(uint32_t i = 0; i < map.size(); ++i) { + map[i].idx = parse_varuint32(code); + map[i].name = parse_utf8_string(code, 0xFFFFFFFFu); + } + } + + inline void parse_name_section(wasm_code_ptr& code) { + _mod->names = _allocator.alloc(1); + new (_mod->names) name_section; + if(code.bounds() == code.offset()) return; + if(*code == 0) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + _mod->names->module_name = _allocator.alloc>(1); + new (_mod->names->module_name) guarded_vector(parse_utf8_string(code, 0xFFFFFFFFu)); + } + if(code.bounds() == code.offset()) return; + if(*code == 1) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + uint32_t size = parse_varuint32(code); + _mod->names->function_names = _allocator.alloc>(1); + new (_mod->names->function_names) guarded_vector(_allocator, size); + parse_name_map(code, *_mod->names->function_names); + } + if(code.bounds() == code.offset()) return; + if(*code == 2) { + ++code; + auto subsection_guard = code.scoped_consume_items(parse_varuint32(code)); + uint32_t size = parse_varuint32(code); + _mod->names->local_names = _allocator.alloc>(1); + new (_mod->names->local_names) guarded_vector(_allocator, size); + for(uint32_t i = 0; i < size; ++i) { + auto& [idx,namemap] = (*_mod->names->local_names)[i]; + idx = parse_varuint32(code); + uint32_t local_size = parse_varuint32(code); + namemap = guarded_vector(_allocator, local_size); + parse_name_map(code, namemap); + } + } + if(code.bounds() == code.offset()) return; + EOS_VM_ASSERT(false, wasm_parse_exception, "Invalid subsection Id"); } void parse_import_entry(wasm_code_ptr& code, import_entry& entry) { diff --git a/include/eosio/vm/types.hpp b/include/eosio/vm/types.hpp index c04a0ab2..43e81653 100644 --- a/include/eosio/vm/types.hpp +++ b/include/eosio/vm/types.hpp @@ -149,6 +149,20 @@ namespace eosio { namespace vm { typedef std::uint32_t wasm_ptr_t; typedef std::uint32_t wasm_size_t; + struct name_assoc { + std::uint32_t idx; + guarded_vector name; + }; + struct indirect_name_assoc { + std::uint32_t idx; + guarded_vector namemap; + }; + struct name_section { + guarded_vector* module_name = nullptr; + guarded_vector* function_names = nullptr; + guarded_vector* local_names = nullptr; + }; + struct module { growable_allocator allocator = { constants::initial_module_size }; uint32_t start = std::numeric_limits::max(); @@ -163,6 +177,9 @@ namespace eosio { namespace vm { guarded_vector code = { allocator, 0 }; guarded_vector data = { allocator, 0 }; + // Custom sections: + name_section* names = nullptr; + // not part of the spec for WASM guarded_vector import_functions = { allocator, 0 }; guarded_vector type_aliases = { allocator, 0 }; diff --git a/tools/nm.cpp b/tools/nm.cpp index 296905c6..6c999f10 100644 --- a/tools/nm.cpp +++ b/tools/nm.cpp @@ -23,15 +23,26 @@ enum class nm_sort_order { alphabetic, numeric, none }; -eosio::vm::export_entry* find_export_name(eosio::vm::module& mod, uint32_t idx) { +eosio::vm::guarded_vector* find_export_name(eosio::vm::module& mod, uint32_t idx) { + if(mod.names && mod.names->function_names) { + for(uint32_t i = 0; i < mod.names->function_names->size(); ++i) { + if((*mod.names->function_names)[i].idx == idx) { + return &(*mod.names->function_names)[i].name; + } + } + } for(uint32_t i = 0; i < mod.exports.size(); ++i) { if(mod.exports[i].index == idx && mod.exports[i].kind == eosio::vm::Function) { - return &mod.exports[i]; + return &mod.exports[i].field_str; } } return nullptr; } +struct nm_options { + static constexpr bool parse_custom_section_name = true; +}; + int main(int argc, const char** argv) { bool print_file_name = false; nm_sort_order sort = nm_sort_order::alphabetic; @@ -63,12 +74,12 @@ int main(int argc, const char** argv) { auto code = read_wasm(filename); nm_debug_info info; module mod; - binary_parser parser(mod.allocator); + binary_parser parser(mod.allocator); parser.parse_module(code, mod, info); for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { std::cout << std::hex << std::setw(8) << std::setfill('0') << info.function_offsets[i] << " T "; - if(export_entry* name = find_export_name(mod, i + mod.get_imported_functions_size())) { - std::cout << std::string_view(reinterpret_cast(name->field_str.raw()), name->field_str.size()); + if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { + std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); } else { std::cout << "fn" << i + mod.get_imported_functions_size(); } From df83d4edc2d30e380c1e914b7b0b0202aa3bae22 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Mon, 24 Aug 2020 13:54:08 -0400 Subject: [PATCH 03/19] Some fixes --- include/eosio/vm/execution_context.hpp | 2 +- include/eosio/vm/parser.hpp | 2 +- include/eosio/vm/profile.hpp | 7 ++++--- tools/nm.cpp | 5 +++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 3fc01cc6..bfc7230f 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -678,7 +678,7 @@ namespace eosio { namespace vm { // This isn't async-signal-safe. Cross fingers and hope for the best. // It's only used for profiling. - int backtrace(void** data, int limit) const { + int backtrace(void** data, int limit, void* uc) const { int out = 0; if(limit != 0) { data[out++] = _state.pc; diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index ea663114..3c24b590 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -296,7 +296,7 @@ namespace eosio { namespace vm { } inline module& parse_module2(wasm_code_ptr& code_ptr, size_t sz, module& mod, DebugInfo& debug) { - parse_module(code_ptr, sz, mod); + parse_module(code_ptr, sz, mod, debug); return mod; } diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index 8116e344..113f0441 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,7 @@ struct profile_data { i += res; } } + outpos = 0; } void write(const char* data, std::size_t size) { @@ -346,7 +348,7 @@ struct profile_manager { profile_manager() { register_profile_signal_handler(); timer_thread = std::thread([this]{ - auto lock = std::unique_lock(timer_mutex); + auto lock = std::unique_lock(mutex); while(!timer_cond.wait_for(lock, std::chrono::milliseconds(10), [&]{ return done; })) { for(pthread_t notify : threads_to_notify) { pthread_kill(notify, SIGPROF); @@ -367,7 +369,7 @@ struct profile_manager { } ~profile_manager() { { - auto lock = std::unique_lock(timer_mutex); + auto lock = std::unique_lock(mutex); done = true; } timer_cond.notify_one(); @@ -382,7 +384,6 @@ struct profile_manager { std::thread timer_thread; bool done = false; - std::mutex timer_mutex; std::condition_variable timer_cond; }; diff --git a/tools/nm.cpp b/tools/nm.cpp index 6c999f10..c3df414f 100644 --- a/tools/nm.cpp +++ b/tools/nm.cpp @@ -1,6 +1,7 @@ #include #include #include +#include struct nm_debug_info { using builder = nm_debug_info; @@ -79,9 +80,9 @@ int main(int argc, const char** argv) { for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { std::cout << std::hex << std::setw(8) << std::setfill('0') << info.function_offsets[i] << " T "; if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { - std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); + std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); } else { - std::cout << "fn" << i + mod.get_imported_functions_size(); + std::cout << "fn" << std::dec << i + mod.get_imported_functions_size(); } std::cout << std::endl; } From 9318af24fcc82d6849ed0b28ae2ab1d88c0798a4 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 11 Sep 2020 15:57:56 -0400 Subject: [PATCH 04/19] Add addr2line --- tools/CMakeLists.txt | 3 + tools/addr2line.cpp | 134 +++++++++++++++++++++++++++++++++++++++++++ tools/nm.cpp | 2 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tools/addr2line.cpp diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index ee6a2288..05c3b363 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,3 +12,6 @@ target_link_libraries(hello-driver eos-vm) add_executable(eos-vm-nm nm.cpp) target_link_libraries(eos-vm-nm eos-vm) + +add_executable(eos-vm-addr2line addr2line.cpp) +target_link_libraries(eos-vm-addr2line eos-vm) diff --git a/tools/addr2line.cpp b/tools/addr2line.cpp new file mode 100644 index 00000000..f26d773d --- /dev/null +++ b/tools/addr2line.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +struct nm_debug_info { + using builder = nm_debug_info; + void on_code_start(const void* compiled_base, const void* wasm_code_start) { + wasm_base = wasm_code_start; + } + void on_function_start(const void* code_addr, const void* wasm_addr) { + function_offsets.push_back(static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base))); + } + void on_instr_start(const void* code_addr, const void* wasm_addr) {} + void on_code_end(const void* code_addr, const void* wasm_addr) {} + void set(nm_debug_info&& other) { *this = std::move(other); } + void relocate(const void*) {} + + uint32_t get_function(std::uint32_t addr) { + auto pos = std::lower_bound(function_offsets.begin(), function_offsets.end(), addr + 1); + if(pos == function_offsets.begin()) return 0; + return (pos - function_offsets.begin()) - 1; + } + + const void* wasm_base; + std::vector function_offsets; +}; + +eosio::vm::guarded_vector* find_export_name(eosio::vm::module& mod, uint32_t idx) { + if(mod.names && mod.names->function_names) { + for(uint32_t i = 0; i < mod.names->function_names->size(); ++i) { + if((*mod.names->function_names)[i].idx == idx) { + return &(*mod.names->function_names)[i].name; + } + } + } + for(uint32_t i = 0; i < mod.exports.size(); ++i) { + if(mod.exports[i].index == idx && mod.exports[i].kind == eosio::vm::Function) { + return &mod.exports[i].field_str; + } + } + return nullptr; +} + +struct nm_options { + static constexpr bool parse_custom_section_name = true; +}; + +bool starts_with(std::string_view arg, std::string_view prefix) { + return arg.substr(0, prefix.size()) == prefix; +} + +int main(int argc, const char** argv) { + bool print_addresses = false; + bool print_functions = false; + bool pretty = false; + std::vector addresses; + std::string filename = "a.out"; + for(int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if(arg == "-h" || arg == "--help") { + std::cerr << "Usage: " << argv[0] << " [-h] [-a] [-C] [-e wasm] [-f] [-s] [-i] [-p] address..." << std::endl; + return 0; + } else if(arg == "-a" || arg == "--addresses") { + print_addresses = true; + } else if(arg == "-C" || starts_with(arg, "--demangle=")) { + // Ignore requests to demangle + } else if(arg == "-e") { + if(++i < argc) { + filename = argv[i]; + } else { + std::cerr << "Missing argument to -e" << std::endl; + return 2; + } + } else if(starts_with(arg, "--exe=")) { + filename = arg.substr(6); + } else if(arg == "-f" || arg == "--functions") { + print_functions = true; + } else if(arg == "-s" || arg == "--basenames") { + // No effect without filenames + } else if(arg == "-i" || arg == "--inlines") { + // No effect without source information + } else if(arg == "-p" || arg == "--pretty-print") { + pretty = true; + } else if(arg[0] != '-') { + addresses.push_back(arg); + } else { + std::cerr << "unexpected argument: " << arg << std::endl; + return 2; + } + } + + std::vector function_names; + + using namespace eosio::vm; + auto code = read_wasm(filename); + nm_debug_info info; + module mod; + binary_parser parser(mod.allocator); + parser.parse_module(code, mod, info); + { + for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { + if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { + function_names.push_back(std::string(reinterpret_cast(name->raw()), name->size())); + } else { + function_names.push_back("fn" + std::to_string( i + mod.get_imported_functions_size())); + } + } + } + + std::string_view sep = pretty ? " " : "\n"; + + auto print_one_address = [&](const std::string& addr) { + unsigned x = std::stoul(addr, nullptr, 16); + if(print_addresses) { + std::cout << std::showbase << std::hex << x << sep; + } + if(print_functions) { + std::cout << std::hex << function_names[info.get_function(x)] << sep; + } + std::cout << "??:0\n"; + }; + + if(!addresses.empty()) { + for(const auto& addr : addresses) { + print_one_address(addr); + } + } else { + std::string addr; + while(std::cin >> addr) { + print_one_address(addr); + } + } +} diff --git a/tools/nm.cpp b/tools/nm.cpp index c3df414f..3fedbc88 100644 --- a/tools/nm.cpp +++ b/tools/nm.cpp @@ -80,7 +80,7 @@ int main(int argc, const char** argv) { for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { std::cout << std::hex << std::setw(8) << std::setfill('0') << info.function_offsets[i] << " T "; if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { - std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); + std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); } else { std::cout << "fn" << std::dec << i + mod.get_imported_functions_size(); } From 78767ef9e9ad0894a97306d239cb2fe7e15ff065 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 11 Sep 2020 16:32:47 -0400 Subject: [PATCH 05/19] Show offsets from the start of the wasm instead of from the start of the code section. --- include/eosio/vm/parser.hpp | 2 +- include/eosio/vm/profile.hpp | 15 +++++++++++---- tools/interp.cpp | 16 +++++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index 3c24b590..59d46ab3 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -1311,7 +1311,7 @@ namespace eosio { namespace vm { template inline void parse_section(wasm_code_ptr& code, vec>& elems) { - const void* code_start = code.raw(); + const void* code_start = code.raw() - code.offset(); parse_section_impl(code, elems, detail::get_max_function_section_elements(_options), [&](wasm_code_ptr& code, function_body& fb, std::size_t idx) { parse_function_body(code, fb, idx); }); EOS_VM_ASSERT( elems.size() == _mod->functions.size(), wasm_parse_exception, "code section must have the same size as the function section" ); diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index 113f0441..b1ca47ad 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -387,13 +387,20 @@ struct profile_manager { std::condition_variable timer_cond; }; -struct scoped_profile { - explicit scoped_profile(profile_data& data) { - profile_manager::instance().start(&data); +class scoped_profile { + public: + explicit scoped_profile(profile_data* data) : data(data) { + if(data) { + profile_manager::instance().start(data); + } } ~scoped_profile() { - profile_manager::instance().stop(); + if(data) { + profile_manager::instance().stop(); + } } + private: + profile_data* data; }; #endif diff --git a/tools/interp.cpp b/tools/interp.cpp index 411aa48f..f0cdd3cc 100644 --- a/tools/interp.cpp +++ b/tools/interp.cpp @@ -20,16 +20,26 @@ int main(int argc, char** argv) { return -1; } + bool profile = false; + std::string filename; + + if(argv[1] == std::string("-p")) { + profile = true; + filename = argv[2]; + } else { + filename = argv[1]; + } + watchdog wd{std::chrono::seconds(3)}; try { // Read the wasm into memory. - auto code = read_wasm( argv[1] ); + auto code = read_wasm( filename ); // Instaniate a new backend using the wasm provided. backend bkend( code, &wa ); - profile_data prof("profile.out", bkend); - scoped_profile profile_runner(prof); + auto prof = profile? std::make_unique("profile.out", bkend) : nullptr; + scoped_profile profile_runner(prof.get()); // Execute any exported functions provided by the wasm. bkend.execute_all(wd); From a7beb294535e8e8a122521a46f8ce233dcde22c4 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Mon, 14 Sep 2020 16:14:22 -0400 Subject: [PATCH 06/19] Fix uninitialized variable and missing header --- include/eosio/vm/execution_context.hpp | 2 +- include/eosio/vm/profile.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index bfc7230f..28bc6436 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -417,7 +417,7 @@ namespace eosio { namespace vm { host_type * _host = nullptr; uint32_t _remaining_call_depth; - std::atomic _top_frame; + std::atomic _top_frame = nullptr; }; template diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index b1ca47ad..b51caeb4 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include From 34b860bf79e919337006f1f1696de6d20ef6d409 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Tue, 22 Sep 2020 15:50:09 -0400 Subject: [PATCH 07/19] Fix some backtrace weirdnesses - Functions were incorrectly marked as starting after the function prologue - An interrupt that comes during the function prologue or epilogue skipped the parent frame. --- include/eosio/vm/execution_context.hpp | 23 +++++++++++++++++++++-- include/eosio/vm/parser.hpp | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 28bc6436..90acf5c2 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -326,12 +326,31 @@ namespace eosio { namespace vm { if(count != 0) { if(uc) { #ifdef __APPLE__ - out[i++] = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rip); + auto rip = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rip); rbp = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rbp); + auto rsp = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rsp); #else - out[i++] = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RIP]); + auto rip = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RIP]); rbp = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RBP]); + auto rsp = reinterpret_cast(static_cast(uc)->uc_mcontext.gregs[REG_RSP]); #endif + out[i++] = rip; + // If we were interrupted in the function prologue or epilogue, + // avoid dropping the parent frame. + auto code_base = reinterpret_cast(_mod.allocator.get_code_start()); + auto code_end = code_base + _mod.allocator._code_size; + if(rip >= code_base && rip < code_end && count > 1) { + // function prologue + if(*reinterpret_cast(rip) == 0x55) { + out[i++] = *static_cast(rsp); + } else if(rip[0] == 0x48 && rip[1] == 0x89 && rip[2] == 0xe5) { + out[i++] = static_cast(rsp)[1]; + } + // function epilogue + else if(rip[0] == 0xc3) { + out[i++] = *static_cast(rsp); + } + } } else { rbp = __builtin_frame_address(0); } diff --git a/include/eosio/vm/parser.hpp b/include/eosio/vm/parser.hpp index 59d46ab3..b6bc7035 100644 --- a/include/eosio/vm/parser.hpp +++ b/include/eosio/vm/parser.hpp @@ -733,7 +733,6 @@ namespace eosio { namespace vm { void parse_function_body_code(wasm_code_ptr& code, size_t bounds, const detail::max_func_local_bytes_stack_checker& local_bytes_checker, Writer& code_writer, const func_type& ft, const local_types_t& local_types) { - imap.on_function_start(code_writer.get_addr(), code.raw()); // Initialize the control stack with the current function as the sole element operand_stack_type_tracker op_stack{local_bytes_checker, _options}; @@ -1321,6 +1320,7 @@ namespace eosio { namespace vm { function_body& fb = _mod->code[i]; func_type& ft = _mod->types.at(_mod->functions.at(i)); local_types_t local_types(ft, fb.locals); + imap.on_function_start(code_writer.get_addr(), _function_bodies[i].first.raw()); code_writer.emit_prologue(ft, fb.locals, i); parse_function_body_code(_function_bodies[i].first, fb.size, _function_bodies[i].second, code_writer, ft, local_types); code_writer.emit_epilogue(ft, fb.locals, i); From 5b5689859c6b07f4b64168cc2a72dcb8d3e1f01a Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 23 Sep 2020 12:28:06 -0400 Subject: [PATCH 08/19] Avoid relying on the ABI of code outside our control for backtrace. Allows profiling to work with -fomit-frame-pointer. --- include/eosio/vm/execution_context.hpp | 37 ++++++---- include/eosio/vm/signals.hpp | 2 + include/eosio/vm/x86_64.hpp | 98 +++++++++++++++++++++----- 3 files changed, 103 insertions(+), 34 deletions(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 90acf5c2..a450d303 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -199,8 +199,13 @@ namespace eosio { namespace vm { null_execution_context(module& m, std::uint32_t max_call_depth) : base_type(m) {} }; + struct frame_info_holder { + void* _bottom_frame = nullptr; + void* _top_frame = nullptr; + }; + template - class jit_execution_context : public execution_context_base, Host> { + class jit_execution_context : public frame_info_holder, public execution_context_base, Host> { using base_type = execution_context_base, Host>; using host_type = detail::host_type_t; public: @@ -290,16 +295,15 @@ namespace eosio { namespace vm { } auto fn = reinterpret_cast(_mod.code[func_index - _mod.get_imported_functions_size()].jit_code_offset + _mod.allocator._code_base); - std::atomic_signal_fence(std::memory_order_release); - void* old_frame = _top_frame.load(std::memory_order_relaxed); - _top_frame.store(__builtin_frame_address(0), std::memory_order_relaxed); - auto restore = scope_guard{[this, old_frame]{ - _top_frame.store(old_frame, std::memory_order_relaxed); - std::atomic_signal_fence(std::memory_order_release); // As close to the correct fence as possible. - }}; + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); + auto restore = scope_guard{[this, &block_mask] { pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); } }; + vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, &handle_signal); + }, [this](int sig){ _top_frame = nullptr; handle_signal(sig); }); } } catch(wasm_exit_exception&) { return {}; @@ -318,12 +322,13 @@ namespace eosio { namespace vm { } int backtrace(void** out, int count, void* uc) const { - void* end = _top_frame.load(std::memory_order_relaxed); - std::atomic_signal_fence(std::memory_order_acquire); + void* end = _top_frame; if(end == nullptr) return 0; void* rbp; int i = 0; - if(count != 0) { + if(_bottom_frame) { + rbp = _bottom_frame; + } else if(count != 0) { if(uc) { #ifdef __APPLE__ auto rip = reinterpret_cast(static_cast(uc)->uc_mcontext->__ss.__rip); @@ -355,17 +360,17 @@ namespace eosio { namespace vm { rbp = __builtin_frame_address(0); } } - //printf("top: %p, start %p\n", end, rbp); while(i < count) { void* rip = static_cast(rbp)[1]; if(rbp == end) break; out[i++] = rip; rbp = *static_cast(rbp); - //printf("%p, %p\n", rip, rbp); } return i; } + static constexpr bool async_backtrace() { return true; } + protected: template @@ -414,7 +419,10 @@ namespace eosio { namespace vm { "dec %%rax; " "jnz 1b; " "2: " + "movq %%rbp, 8(%[context]); " "callq *%[fun]; " + "xor %[fun], %[fun]; " + "mov %[fun], 8(%[context]); " "add %[StackOffset], %%rsp; " "ldmxcsr 16(%%rsp); " "mov (%%rsp), %%rsp; " @@ -436,7 +444,6 @@ namespace eosio { namespace vm { host_type * _host = nullptr; uint32_t _remaining_call_depth; - std::atomic _top_frame = nullptr; }; template diff --git a/include/eosio/vm/signals.hpp b/include/eosio/vm/signals.hpp index 0ae917e0..ed8a96b1 100644 --- a/include/eosio/vm/signals.hpp +++ b/include/eosio/vm/signals.hpp @@ -87,6 +87,7 @@ namespace eosio { namespace vm { struct sigaction sa; sa.sa_sigaction = &signal_handler; sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGPROF); sa.sa_flags = SA_NODEFER | SA_SIGINFO; sigaction(SIGSEGV, &sa, &prev_signal_handler); sigaction(SIGBUS, &sa, &prev_signal_handler); @@ -130,6 +131,7 @@ namespace eosio { namespace vm { sigaddset(&unblock_mask, SIGSEGV); sigaddset(&unblock_mask, SIGBUS); sigaddset(&unblock_mask, SIGFPE); + sigaddset(&unblock_mask, SIGPROF); pthread_sigmask(SIG_UNBLOCK, &unblock_mask, &old_sigmask); try { f(); diff --git a/include/eosio/vm/x86_64.hpp b/include/eosio/vm/x86_64.hpp index 0b809587..436936b4 100644 --- a/include/eosio/vm/x86_64.hpp +++ b/include/eosio/vm/x86_64.hpp @@ -624,6 +624,7 @@ namespace eosio { namespace vm { void emit_current_memory() { auto icount = fixed_size_instr(17); + emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi @@ -637,6 +638,7 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // pop %rdi emit_bytes(0x5f); + emit_restore_backtrace(); // push %rax emit_bytes(0x50); } @@ -644,6 +646,7 @@ namespace eosio { namespace vm { auto icount = fixed_size_instr(21); // popq %rax emit_bytes(0x58); + emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi @@ -659,6 +662,7 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // pop %rdi emit_bytes(0x5f); + emit_restore_backtrace(); // push %rax emit_bytes(0x50); } @@ -2273,16 +2277,17 @@ namespace eosio { namespace vm { template void emit_softfloat_unop(T(*softfloatfun)(U)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if constexpr(sizeof(U) == 4) { // movq 16(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2295,6 +2300,7 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace(); if constexpr(sizeof(T) == 4) { static_assert(sizeof(U) == 4, "Can only push 4-byte item if the upper 4 bytes are already 0"); // movq %eax, (%rsp) @@ -2306,14 +2312,15 @@ namespace eosio { namespace vm { } void emit_f32_binop_softfloat(float32_t (*softfloatfun)(float32_t, float32_t)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); // movq 16(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x8b, 0x7c, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $softfloatfun, %rax emit_bytes(0x48, 0xb8); @@ -2325,21 +2332,23 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %eax, (%rsp) emit_bytes(0x89, 0x04, 0x24); } void emit_f64_binop_softfloat(float64_t (*softfloatfun)(float64_t, float64_t)) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); // movq 16(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $softfloatfun, %rax emit_bytes(0x48, 0xb8); @@ -2351,28 +2360,30 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } void emit_f32_relop(uint8_t opcode, uint64_t (*softfloatfun)(float32_t, float32_t), bool switch_params, bool flip_result) { if constexpr (use_softfloat) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if(switch_params) { // movq 24(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x18); + emit_bytes(0x8b, 0x74, 0x24, 0x18 + extra); // movq 16(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %esi - emit_bytes(0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %edi - emit_bytes(0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x8b, 0x7c, 0x24, 0x18 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2385,12 +2396,13 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); if (flip_result) { // xor $0x1, %al emit_bytes(0x34, 0x01); } // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } else { @@ -2424,20 +2436,21 @@ namespace eosio { namespace vm { void emit_f64_relop(uint8_t opcode, uint64_t (*softfloatfun)(float64_t, float64_t), bool switch_params, bool flip_result) { if constexpr (use_softfloat) { + auto extra = emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); // pushq %rsi emit_bytes(0x56); if(switch_params) { // movq 24(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x18 + extra); // movq 16(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x10 + extra); } else { // movq 16(%rsp), %rsi - emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10); + emit_bytes(0x48, 0x8b, 0x74, 0x24, 0x10 + extra); // movq 24(%rsp), %rdi - emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18); + emit_bytes(0x48, 0x8b, 0x7c, 0x24, 0x18 + extra); } emit_align_stack(); // movabsq $softfloatfun, %rax @@ -2450,12 +2463,13 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + emit_restore_backtrace_basic(); if (flip_result) { // xor $0x1, %al emit_bytes(0x34, 0x01); } // addq $8, %rsp - emit_bytes(0x48, 0x83, 0xc4, 0x08); + emit_bytes(0x48, 0x83, 0xc4, 0x08 + extra); // movq %rax, (%rsp) emit_bytes(0x48, 0x89, 0x04, 0x24); } else { @@ -2586,6 +2600,14 @@ namespace eosio { namespace vm { } void emit_host_call(uint32_t funcnum) { + uint32_t extra = 0; + if constexpr (Context::async_backtrace()) { + // pushq %rbp + emit_bytes(0x55); + // movq %rsp, (%rdi) + emit_bytes(0x48, 0x89, 0x27); + extra = 8; + } // mov $funcnum, %edx emit_bytes(0xba); emit_operand32(funcnum); @@ -2594,7 +2616,7 @@ namespace eosio { namespace vm { // pushq %rsi emit_bytes(0x56); // lea 24(%rsp), %rsi - emit_bytes(0x48, 0x8d, 0x74, 0x24, 0x18); + emit_bytes(0x48, 0x8d, 0x74, 0x24, 0x18 + extra); emit_align_stack(); // movabsq $call_host_function, %rax emit_bytes(0x48, 0xb8); @@ -2606,10 +2628,48 @@ namespace eosio { namespace vm { emit_bytes(0x5e); // popq %rdi emit_bytes(0x5f); + if constexpr (Context::async_backtrace()) { + // popq %rbp + emit_bytes(0x5d); + } // retq emit_bytes(0xc3); } + // Needs to run before saving %rdi. Returns the number of bytes pushed onto the stack. + uint32_t emit_setup_backtrace() { + if constexpr (Context::async_backtrace()) { + // callq next + emit_bytes(0xe8); + emit_operand32(0); + // next: + // pushq %rbp + emit_bytes(0x55); + // movq %rsp, (%rdi) + emit_bytes(0x48, 0x89, 0x27); + return 16; + } else { + return 0; + } + } + // Does not adjust the stack pointer. Use this if the + // stack pointer adjustment is combined with another instruction. + void emit_restore_backtrace_basic() { + if constexpr (Context::async_backtrace()) { + // xorl %edx, %edx + emit_bytes(0x31, 0xd2); + // movq %rdx, (%rdi) + emit_bytes(0x48, 0x89, 0x17); + } + } + void emit_restore_backtrace() { + if constexpr (Context::async_backtrace()) { + emit_restore_backtrace_basic(); + // addq $16, %rsp + emit_bytes(0x48, 0x83, 0xc4, 0x10); + } + } + bool is_host_function(uint32_t funcnum) { return funcnum < _mod.get_imported_functions_size(); } static native_value call_host_function(Context* context /*rdi*/, native_value* stack /*rsi*/, uint32_t idx /*edx*/) { From cc194037765f03bb36c82a03f7cce0c9fc446adc Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Tue, 29 Sep 2020 15:12:23 -0400 Subject: [PATCH 09/19] Make the extra code required to get backtraces optional. --- include/eosio/vm/backend.hpp | 8 ++ include/eosio/vm/execution_context.hpp | 140 ++++++++++++++----------- tools/interp.cpp | 2 +- 3 files changed, 88 insertions(+), 62 deletions(-) diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 1f1a997e..420a7631 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -29,6 +29,14 @@ namespace eosio { namespace vm { static constexpr bool is_jit = true; }; + struct jit_profile { + template + using context = jit_execution_context; + template + using parser = binary_parser>, Options, DebugInfo>; + static constexpr bool is_jit = true; + }; + struct interpreter { template using context = execution_context; diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index a450d303..6d27c047 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -199,14 +199,17 @@ namespace eosio { namespace vm { null_execution_context(module& m, std::uint32_t max_call_depth) : base_type(m) {} }; - struct frame_info_holder { + template + struct frame_info_holder {}; + template<> + struct frame_info_holder { void* _bottom_frame = nullptr; void* _top_frame = nullptr; }; - template - class jit_execution_context : public frame_info_holder, public execution_context_base, Host> { - using base_type = execution_context_base, Host>; + template + class jit_execution_context : public frame_info_holder, public execution_context_base, Host> { + using base_type = execution_context_base, Host>; using host_type = detail::host_type_t; public: using base_type::execute; @@ -295,15 +298,21 @@ namespace eosio { namespace vm { } auto fn = reinterpret_cast(_mod.code[func_index - _mod.get_imported_functions_size()].jit_code_offset + _mod.allocator._code_base); - sigset_t block_mask; - sigemptyset(&block_mask); - sigaddset(&block_mask, SIGPROF); - pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); - auto restore = scope_guard{[this, &block_mask] { pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); } }; - - vm::invoke_with_signal_handler([&]() { - result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, [this](int sig){ _top_frame = nullptr; handle_signal(sig); }); + if constexpr(EnableBacktrace) { + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); + auto restore = scope_guard{[this, &block_mask] { pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); } }; + + vm::invoke_with_signal_handler([&]() { + result = execute(args_raw, fn, this, base_type::linear_memory(), stack); + }, [this](int sig){ this->_top_frame = nullptr; handle_signal(sig); }); + } else { + vm::invoke_with_signal_handler([&]() { + result = execute(args_raw, fn, this, base_type::linear_memory(), stack); + }, handle_signal); + } } } catch(wasm_exit_exception&) { return {}; @@ -322,12 +331,13 @@ namespace eosio { namespace vm { } int backtrace(void** out, int count, void* uc) const { - void* end = _top_frame; + static_assert(EnableBacktrace); + void* end = this->_top_frame; if(end == nullptr) return 0; void* rbp; int i = 0; - if(_bottom_frame) { - rbp = _bottom_frame; + if(this->_bottom_frame) { + rbp = this->_bottom_frame; } else if(count != 0) { if(uc) { #ifdef __APPLE__ @@ -369,7 +379,7 @@ namespace eosio { namespace vm { return i; } - static constexpr bool async_backtrace() { return true; } + static constexpr bool async_backtrace() { return EnableBacktrace; } protected: @@ -394,51 +404,59 @@ namespace eosio { namespace vm { // currently ignoring register c++17 warning register void* stack_top asm ("r12") = stack; // 0x1f80 is the default MXCSR value - asm volatile( - "test %[stack_top], %[stack_top]; " - "jnz 3f; " - "mov %%rsp, %[stack_top]; " - "sub $0x98, %%rsp; " // red-zone + 24 bytes - "mov %[stack_top], (%%rsp); " - "jmp 4f; " - "3: " - "mov %%rsp, (%[stack_top]); " - "mov %[stack_top], %%rsp; " - "4: " - "stmxcsr 16(%%rsp); " - "mov $0x1f80, %%rax; " - "mov %%rax, 8(%%rsp); " - "ldmxcsr 8(%%rsp); " - "mov %[Count], %%rax; " - "test %%rax, %%rax; " - "jz 2f; " - "1: " - "movq (%[data]), %%r8; " - "lea 8(%[data]), %[data]; " - "pushq %%r8; " - "dec %%rax; " - "jnz 1b; " - "2: " - "movq %%rbp, 8(%[context]); " - "callq *%[fun]; " - "xor %[fun], %[fun]; " - "mov %[fun], 8(%[context]); " - "add %[StackOffset], %%rsp; " - "ldmxcsr 16(%%rsp); " - "mov (%%rsp), %%rsp; " - // Force explicit register allocation, because otherwise it's too hard to get the clobbers right. - : [result] "=&a" (result), // output, reused as a scratch register - [data] "+d" (data), [fun] "+c" (fun), [stack_top] "+r" (stack_top) // input only, but may be clobbered - : [context] "D" (context), [linear_memory] "S" (linear_memory), - [StackOffset] "n" (Count*8), [Count] "n" (Count), "b" (stack_check) // input - : "memory", "cc", // clobber - // call clobbered registers, that are not otherwise used - /*"rax", "rcx", "rdx", "rsi", "rdi",*/ "r8", "r9", "r10", "r11", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", - "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", - "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" +#define ASM_CODE(before, after) \ + asm volatile( \ + "test %[stack_top], %[stack_top]; " \ + "jnz 3f; " \ + "mov %%rsp, %[stack_top]; " \ + "sub $0x98, %%rsp; " /* red-zone + 24 bytes*/ \ + "mov %[stack_top], (%%rsp); " \ + "jmp 4f; " \ + "3: " \ + "mov %%rsp, (%[stack_top]); " \ + "mov %[stack_top], %%rsp; " \ + "4: " \ + "stmxcsr 16(%%rsp); " \ + "mov $0x1f80, %%rax; " \ + "mov %%rax, 8(%%rsp); " \ + "ldmxcsr 8(%%rsp); " \ + "mov %[Count], %%rax; " \ + "test %%rax, %%rax; " \ + "jz 2f; " \ + "1: " \ + "movq (%[data]), %%r8; " \ + "lea 8(%[data]), %[data]; " \ + "pushq %%r8; " \ + "dec %%rax; " \ + "jnz 1b; " \ + "2: " \ + before \ + "callq *%[fun]; " \ + after \ + "add %[StackOffset], %%rsp; " \ + "ldmxcsr 16(%%rsp); " \ + "mov (%%rsp), %%rsp; " \ + /* Force explicit register allocation, because otherwise it's too hard to get the clobbers right. */ \ + : [result] "=&a" (result), /* output, reused as a scratch register */ \ + [data] "+d" (data), [fun] "+c" (fun), [stack_top] "+r" (stack_top) /* input only, but may be clobbered */ \ + : [context] "D" (context), [linear_memory] "S" (linear_memory), \ + [StackOffset] "n" (Count*8), [Count] "n" (Count), "b" (stack_check) /* input */ \ + : "memory", "cc", /* clobber */ \ + /* call clobbered registers, that are not otherwise used */ \ + /*"rax", "rcx", "rdx", "rsi", "rdi",*/ "r8", "r9", "r10", "r11", \ + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", \ + "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", \ + "mm0","mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm6", \ + "st", "st(1)", "st(2)", "st(3)", "st(4)", "st(5)", "st(6)", "st(7)" \ ); + if constexpr (!EnableBacktrace) { + ASM_CODE("", ""); + } else { + ASM_CODE("movq %%rbp, 8(%[context]); ", + "xor %[fun], %[fun]; " + "mov %[fun], 8(%[context]); "); + } +#undef ASM_CODE return result; } diff --git a/tools/interp.cpp b/tools/interp.cpp index f0cdd3cc..357d445e 100644 --- a/tools/interp.cpp +++ b/tools/interp.cpp @@ -37,7 +37,7 @@ int main(int argc, char** argv) { auto code = read_wasm( filename ); // Instaniate a new backend using the wasm provided. - backend bkend( code, &wa ); + backend bkend( code, &wa ); auto prof = profile? std::make_unique("profile.out", bkend) : nullptr; scoped_profile profile_runner(prof.get()); From fc8ec3576fa64d97f428315fe4d4ea200ddca846 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 08:30:58 -0400 Subject: [PATCH 10/19] Pass the correct execution_context to the machine_code_writer --- include/eosio/vm/backend.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 420a7631..f74cba41 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -33,7 +33,7 @@ namespace eosio { namespace vm { template using context = jit_execution_context; template - using parser = binary_parser>, Options, DebugInfo>; + using parser = binary_parser>, Options, DebugInfo>; static constexpr bool is_jit = true; }; From 49dbb891443f9a6a6511565ddbe8d1d403a88a9d Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 08:31:38 -0400 Subject: [PATCH 11/19] Fix posix timer implementation + minor cleanup. --- include/eosio/vm/profile.hpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index b51caeb4..c92f2f8e 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -264,8 +264,6 @@ inline void register_profile_signal_handler() { ignore_unused_variable_warning(init_helper); } -#define get_backtrace_impl(data, count, uc) ptr->get_backtrace_fn(ptr->exec_context, data, count, uc) - #if USE_POSIX_TIMERS inline void profile_handler(int sig, siginfo_t *info, void * uc) { @@ -274,7 +272,7 @@ inline void profile_handler(int sig, siginfo_t *info, void * uc) { if(ptr) { int saved_errno = errno; void* data[profile_data::max_frames*2]; // Includes both wasm and native frames - int count = get_backtrace_impl(data, sizeof(data)/sizeof(data[0]), uc); + int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc); ptr->handle_tick(data, count); errno = saved_errno; } @@ -317,14 +315,18 @@ __attribute__((visibility("default"))) inline std::unique_ptr per_thread_profile_manager; struct scoped_profile { - explicit scoped_profile(profile_data& data) { - if(!per_thread_profile_manager) { - per_thread_profile_manager = std::make_unique(); + explicit scoped_profile(profile_data* data) { + if(data) { + if(!per_thread_profile_manager) { + per_thread_profile_manager = std::make_unique(); + } + per_thread_profile_manager->start(data); } - per_thread_profile_manager->start(&data); } ~scoped_profile() { - per_thread_profile_manager->stop(); + if(per_thread_profile_manager) { + per_thread_profile_manager->stop(); + } } }; @@ -339,7 +341,7 @@ inline void profile_handler(int sig, siginfo_t* info, void* uc) { if(ptr) { int saved_errno = errno; void* data[profile_data::max_frames*2]; // Includes both wasm and native frames - int count = get_backtrace_impl(data, sizeof(data)/sizeof(data[0]), uc); + int count = ptr->get_backtrace_fn(ptr->exec_context, data, sizeof(data)/sizeof(data[0]), uc); ptr->handle_tick(data, count); errno = saved_errno; } From 4a8695956ee739c13b612b236a90f4fa5e3f591a Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 08:46:02 -0400 Subject: [PATCH 12/19] Make the profile interval configurable --- include/eosio/vm/profile.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index c92f2f8e..6fc99067 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -15,6 +15,8 @@ namespace eosio::vm { +inline uint32_t profile_interval_us = 10000; + struct profile_data { // buffer_size is the size of the I/O write buffer. The buffer must be at least large enough for one item. // hash_table_size is the maximum number of unique traces that can be stored in memory. It must be a power of 2. @@ -58,7 +60,7 @@ struct profile_data { 0, 3, 0, - 10000, + profile_interval_us, 0 }; write(reinterpret_cast(header), sizeof(header)); @@ -264,6 +266,12 @@ inline void register_profile_signal_handler() { ignore_unused_variable_warning(init_helper); } +// Sets the profile interval. Should only be called before starting any profiler. +// The interval should be between 1 and 999999. +inline void set_profile_interval_us(uint32_t value) { + profile_interval_us = value; +} + #if USE_POSIX_TIMERS inline void profile_handler(int sig, siginfo_t *info, void * uc) { @@ -290,9 +298,9 @@ struct profile_manager { EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); struct itimerspec spec; spec.it_interval.tv_sec = 0; - spec.it_interval.tv_nsec = 10000000; + spec.it_interval.tv_nsec = profile_interval_us * 1000; spec.it_value.tv_sec = 0; - spec.it_value.tv_nsec = 10000000; + spec.it_value.tv_nsec = profile_interval_us * 1000; res = timer_settime(timer, 0, &spec, nullptr); EOS_VM_ASSERT(res == 0, profile_exception, "Failed to start timer"); } @@ -352,7 +360,7 @@ struct profile_manager { register_profile_signal_handler(); timer_thread = std::thread([this]{ auto lock = std::unique_lock(mutex); - while(!timer_cond.wait_for(lock, std::chrono::milliseconds(10), [&]{ return done; })) { + while(!timer_cond.wait_for(lock, std::chrono::microseconds(profile_interval_us), [&]{ return done; })) { for(pthread_t notify : threads_to_notify) { pthread_kill(notify, SIGPROF); } From 53e1b23e28245e6c16f66e61f93ff88dbca759b3 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 13:42:56 -0400 Subject: [PATCH 13/19] Update instruction sizes --- include/eosio/vm/x86_64.hpp | 116 ++++++++++++++++++------------------ tests/utils.hpp | 2 +- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/include/eosio/vm/x86_64.hpp b/include/eosio/vm/x86_64.hpp index 436936b4..06b8c2ec 100644 --- a/include/eosio/vm/x86_64.hpp +++ b/include/eosio/vm/x86_64.hpp @@ -50,7 +50,7 @@ namespace eosio { namespace vm { // emit host functions const uint32_t num_imported = mod.get_imported_functions_size(); - const std::size_t host_functions_size = 40 * num_imported; + const std::size_t host_functions_size = (40 + 5 * Context::async_backtrace()) * num_imported; _code_start = _mod.allocator.alloc(host_functions_size); _code_end = _code_start + host_functions_size; // code already set @@ -103,7 +103,7 @@ namespace eosio { namespace vm { void emit_prologue(const func_type& /*ft*/, const guarded_vector& locals, uint32_t funcnum) { _ft = &_mod.types[_mod.functions[funcnum]]; // FIXME: This is not a tight upper bound - const std::size_t instruction_size_ratio_upper_bound = use_softfloat?49:79; + const std::size_t instruction_size_ratio_upper_bound = use_softfloat?(Context::async_backtrace()?63:49):79; std::size_t code_size = max_prologue_size + _mod.code[funcnum].size * instruction_size_ratio_upper_bound + max_epilogue_size; _code_start = _mod.allocator.alloc(code_size); _code_end = _code_start + code_size; @@ -623,7 +623,7 @@ namespace eosio { namespace vm { } void emit_current_memory() { - auto icount = fixed_size_instr(17); + auto icount = variable_size_instr(17, 35); emit_setup_backtrace(); // pushq %rdi emit_bytes(0x57); @@ -643,7 +643,7 @@ namespace eosio { namespace vm { emit_bytes(0x50); } void emit_grow_memory() { - auto icount = fixed_size_instr(21); + auto icount = variable_size_instr(21, 39); // popq %rax emit_bytes(0x58); emit_setup_backtrace(); @@ -978,63 +978,63 @@ namespace eosio { namespace vm { // --------------- f32 relops ---------------------- void emit_f32_eq() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x00, CHOOSE_FN(_eosio_f32_eq), false, false); } void emit_f32_ne() { - auto icount = softfloat_instr(24,47); + auto icount = softfloat_instr(24,47,61); emit_f32_relop(0x00, CHOOSE_FN(_eosio_f32_eq), false, true); } void emit_f32_lt() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x01, CHOOSE_FN(_eosio_f32_lt), false, false); } void emit_f32_gt() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x01, CHOOSE_FN(_eosio_f32_lt), true, false); } void emit_f32_le() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x02, CHOOSE_FN(_eosio_f32_le), false, false); } void emit_f32_ge() { - auto icount = softfloat_instr(25,45); + auto icount = softfloat_instr(25,45,59); emit_f32_relop(0x02, CHOOSE_FN(_eosio_f32_le), true, false); } // --------------- f64 relops ---------------------- void emit_f64_eq() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x00, CHOOSE_FN(_eosio_f64_eq), false, false); } void emit_f64_ne() { - auto icount = softfloat_instr(24,49); + auto icount = softfloat_instr(24,49,63); emit_f64_relop(0x00, CHOOSE_FN(_eosio_f64_eq), false, true); } void emit_f64_lt() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x01, CHOOSE_FN(_eosio_f64_lt), false, false); } void emit_f64_gt() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x01, CHOOSE_FN(_eosio_f64_lt), true, false); } void emit_f64_le() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x02, CHOOSE_FN(_eosio_f64_le), false, false); } void emit_f64_ge() { - auto icount = softfloat_instr(25,47); + auto icount = softfloat_instr(25,47,61); emit_f64_relop(0x02, CHOOSE_FN(_eosio_f64_le), true, false); } @@ -1372,7 +1372,7 @@ namespace eosio { namespace vm { } void emit_f32_ceil() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_ceil)); } @@ -1383,7 +1383,7 @@ namespace eosio { namespace vm { } void emit_f32_floor() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_floor)); } @@ -1394,7 +1394,7 @@ namespace eosio { namespace vm { } void emit_f32_trunc() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_trunc)); } @@ -1405,7 +1405,7 @@ namespace eosio { namespace vm { } void emit_f32_nearest() { - auto icount = softfloat_instr(12, 36); + auto icount = softfloat_instr(12, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_nearest)); } @@ -1416,7 +1416,7 @@ namespace eosio { namespace vm { } void emit_f32_sqrt() { - auto icount = softfloat_instr(10, 36); + auto icount = softfloat_instr(10, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_sqrt)); } @@ -1429,23 +1429,23 @@ namespace eosio { namespace vm { // --------------- f32 binops ---------------------- void emit_f32_add() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x58, CHOOSE_FN(_eosio_f32_add)); } void emit_f32_sub() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x5c, CHOOSE_FN(_eosio_f32_sub)); } void emit_f32_mul() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x59, CHOOSE_FN(_eosio_f32_mul)); } void emit_f32_div() { - auto icount = softfloat_instr(21, 44); + auto icount = softfloat_instr(21, 44, 58); emit_f32_binop(0x5e, CHOOSE_FN(_eosio_f32_div)); } void emit_f32_min() { - auto icount = softfloat_instr(47, 44); + auto icount = softfloat_instr(47, 44, 58); if constexpr(use_softfloat) { emit_f32_binop_softfloat(CHOOSE_FN(_eosio_f32_min)); return; @@ -1478,7 +1478,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_max() { - auto icount = softfloat_instr(47, 44); + auto icount = softfloat_instr(47, 44, 58); if(use_softfloat) { emit_f32_binop_softfloat(CHOOSE_FN(_eosio_f32_max)); return; @@ -1558,7 +1558,7 @@ namespace eosio { namespace vm { } void emit_f64_ceil() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_ceil)); } @@ -1569,7 +1569,7 @@ namespace eosio { namespace vm { } void emit_f64_floor() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_floor)); } @@ -1580,7 +1580,7 @@ namespace eosio { namespace vm { } void emit_f64_trunc() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_trunc)); } @@ -1591,7 +1591,7 @@ namespace eosio { namespace vm { } void emit_f64_nearest() { - auto icount = softfloat_instr(12, 38); + auto icount = softfloat_instr(12, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_nearest)); } @@ -1602,7 +1602,7 @@ namespace eosio { namespace vm { } void emit_f64_sqrt() { - auto icount = softfloat_instr(10, 38); + auto icount = softfloat_instr(10, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_sqrt)); } @@ -1615,23 +1615,23 @@ namespace eosio { namespace vm { // --------------- f64 binops ---------------------- void emit_f64_add() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x58, CHOOSE_FN(_eosio_f64_add)); } void emit_f64_sub() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x5c, CHOOSE_FN(_eosio_f64_sub)); } void emit_f64_mul() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x59, CHOOSE_FN(_eosio_f64_mul)); } void emit_f64_div() { - auto icount = softfloat_instr(21, 47); + auto icount = softfloat_instr(21, 47, 61); emit_f64_binop(0x5e, CHOOSE_FN(_eosio_f64_div)); } void emit_f64_min() { - auto icount = softfloat_instr(49, 47); + auto icount = softfloat_instr(49, 47, 61); if(use_softfloat) { emit_f64_binop_softfloat(CHOOSE_FN(_eosio_f64_min)); return; @@ -1664,7 +1664,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_max() { - auto icount = softfloat_instr(49, 47); + auto icount = softfloat_instr(49, 47, 61); if(use_softfloat) { emit_f64_binop_softfloat(CHOOSE_FN(_eosio_f64_max)); return; @@ -1731,7 +1731,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_s_f32() { - auto icount = softfloat_instr(33, 36); + auto icount = softfloat_instr(33, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i32s>())); } @@ -1742,7 +1742,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_u_f32() { - auto icount = softfloat_instr(46, 36); + auto icount = softfloat_instr(46, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i32u>())); } @@ -1759,7 +1759,7 @@ namespace eosio { namespace vm { fix_branch(emit_branch_target32(), fpe_handler); } void emit_i32_trunc_s_f64() { - auto icount = softfloat_instr(34, 38); + auto icount = softfloat_instr(34, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i32s>())); } @@ -1770,7 +1770,7 @@ namespace eosio { namespace vm { } void emit_i32_trunc_u_f64() { - auto icount = softfloat_instr(47, 38); + auto icount = softfloat_instr(47, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i32u>())); } @@ -1798,7 +1798,7 @@ namespace eosio { namespace vm { void emit_i64_extend_u_i32() { /* Nothing to do */ } void emit_i64_trunc_s_f32() { - auto icount = softfloat_instr(35, 37); + auto icount = softfloat_instr(35, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i64s>())); } @@ -1808,7 +1808,7 @@ namespace eosio { namespace vm { emit_bytes(0x48, 0x89, 0x04 ,0x24); } void emit_i64_trunc_u_f32() { - auto icount = softfloat_instr(101, 37); + auto icount = softfloat_instr(101, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f32_trunc_i64u>())); } @@ -1851,7 +1851,7 @@ namespace eosio { namespace vm { fix_branch(emit_branch_target32(), fpe_handler); } void emit_i64_trunc_s_f64() { - auto icount = softfloat_instr(35, 38); + auto icount = softfloat_instr(35, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i64s>())); } @@ -1861,7 +1861,7 @@ namespace eosio { namespace vm { emit_bytes(0x48, 0x89, 0x04 ,0x24); } void emit_i64_trunc_u_f64() { - auto icount = softfloat_instr(109, 38); + auto icount = softfloat_instr(109, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(softfloat_trap<&_eosio_f64_trunc_i64u>())); } @@ -1905,7 +1905,7 @@ namespace eosio { namespace vm { } void emit_f32_convert_s_i32() { - auto icount = softfloat_instr(10, 36); + auto icount = softfloat_instr(10, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i32_to_f32)); } @@ -1915,7 +1915,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_u_i32() { - auto icount = softfloat_instr(11, 36); + auto icount = softfloat_instr(11, 36, 54); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui32_to_f32)); } @@ -1926,7 +1926,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_s_i64() { - auto icount = softfloat_instr(11, 38); + auto icount = softfloat_instr(11, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i64_to_f32)); } @@ -1936,7 +1936,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_convert_u_i64() { - auto icount = softfloat_instr(55, 38); + auto icount = softfloat_instr(55, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui64_to_f32)); } @@ -1976,7 +1976,7 @@ namespace eosio { namespace vm { emit_bytes(0xf3, 0x0f, 0x11, 0x04, 0x24); } void emit_f32_demote_f64() { - auto icount = softfloat_instr(16, 38); + auto icount = softfloat_instr(16, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f64_demote)); } @@ -1991,7 +1991,7 @@ namespace eosio { namespace vm { emit_bytes(0x89, 0x44, 0x24, 0x04); } void emit_f64_convert_s_i32() { - auto icount = softfloat_instr(10, 37); + auto icount = softfloat_instr(10, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i32_to_f64)); } @@ -2001,7 +2001,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_u_i32() { - auto icount = softfloat_instr(11, 37); + auto icount = softfloat_instr(11, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui32_to_f64)); } @@ -2011,7 +2011,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_s_i64() { - auto icount = softfloat_instr(11, 38); + auto icount = softfloat_instr(11, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_i64_to_f64)); } @@ -2021,7 +2021,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_convert_u_i64() { - auto icount = softfloat_instr(49, 38); + auto icount = softfloat_instr(49, 38, 56); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_ui64_to_f64)); } @@ -2057,7 +2057,7 @@ namespace eosio { namespace vm { emit_bytes(0xf2, 0x0f, 0x11, 0x04, 0x24); } void emit_f64_promote_f32() { - auto icount = softfloat_instr(10, 37); + auto icount = softfloat_instr(10, 37, 55); if constexpr (use_softfloat) { return emit_softfloat_unop(CHOOSE_FN(_eosio_f32_promote)); } @@ -2123,8 +2123,8 @@ namespace eosio { namespace vm { ignore_unused_variable_warning(code, min_code, max_code); }}; } - auto softfloat_instr(std::size_t hard_expected, std::size_t soft_expected) { - return fixed_size_instr(use_softfloat?soft_expected:hard_expected); + auto softfloat_instr(std::size_t hard_expected, std::size_t soft_expected, std::size_t softbt_expected) { + return fixed_size_instr(use_softfloat?(Context::async_backtrace()?softbt_expected:soft_expected):hard_expected); } module& _mod; diff --git a/tests/utils.hpp b/tests/utils.hpp index 2e749da2..6a66bf7d 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -53,4 +53,4 @@ inline eosio::vm::wasm_allocator* get_wasm_allocator() { } #define BACKEND_TEST_CASE(name, tags) \ - TEMPLATE_TEST_CASE(name, tags, eosio::vm::interpreter, eosio::vm::jit) + TEMPLATE_TEST_CASE(name, tags, eosio::vm::interpreter, eosio::vm::jit, eosio::vm::jit_profile) From 76530093ed99f78bdc0390c5971d2a5dc7635e1d Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 15:30:59 -0400 Subject: [PATCH 14/19] Make sure that the backtrace range is correctly managed around host-functions, and exceptions. --- include/eosio/vm/execution_context.hpp | 12 ++++++++---- include/eosio/vm/signals.hpp | 8 ++++++++ include/eosio/vm/x86_64.hpp | 3 ++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 6d27c047..fd2f92fc 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -203,8 +203,8 @@ namespace eosio { namespace vm { struct frame_info_holder {}; template<> struct frame_info_holder { - void* _bottom_frame = nullptr; - void* _top_frame = nullptr; + void* volatile _bottom_frame = nullptr; + void* volatile _top_frame = nullptr; }; template @@ -303,11 +303,15 @@ namespace eosio { namespace vm { sigemptyset(&block_mask); sigaddset(&block_mask, SIGPROF); pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); - auto restore = scope_guard{[this, &block_mask] { pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); } }; + auto restore = scope_guard{[this, &block_mask] { + this->_top_frame = nullptr; + this->_bottom_frame = nullptr; + pthread_sigmask(SIG_UNBLOCK, &block_mask, nullptr); + }}; vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, [this](int sig){ this->_top_frame = nullptr; handle_signal(sig); }); + }, handle_signal); } else { vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); diff --git a/include/eosio/vm/signals.hpp b/include/eosio/vm/signals.hpp index ed8a96b1..3de07498 100644 --- a/include/eosio/vm/signals.hpp +++ b/include/eosio/vm/signals.hpp @@ -71,6 +71,10 @@ namespace eosio { namespace vm { caught_exception = true; } if (caught_exception) { + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); sigjmp_buf* dest = std::atomic_load(&signal_dest); siglongjmp(*dest, -1); } @@ -79,6 +83,10 @@ namespace eosio { namespace vm { template [[noreturn]] inline void throw_(const char* msg) { saved_exception = std::make_exception_ptr(E{msg}); + sigset_t block_mask; + sigemptyset(&block_mask); + sigaddset(&block_mask, SIGPROF); + pthread_sigmask(SIG_BLOCK, &block_mask, nullptr); sigjmp_buf* dest = std::atomic_load(&signal_dest); siglongjmp(*dest, -1); } diff --git a/include/eosio/vm/x86_64.hpp b/include/eosio/vm/x86_64.hpp index 06b8c2ec..dc76ca17 100644 --- a/include/eosio/vm/x86_64.hpp +++ b/include/eosio/vm/x86_64.hpp @@ -50,7 +50,7 @@ namespace eosio { namespace vm { // emit host functions const uint32_t num_imported = mod.get_imported_functions_size(); - const std::size_t host_functions_size = (40 + 5 * Context::async_backtrace()) * num_imported; + const std::size_t host_functions_size = (40 + 10 * Context::async_backtrace()) * num_imported; _code_start = _mod.allocator.alloc(host_functions_size); _code_end = _code_start + host_functions_size; // code already set @@ -2629,6 +2629,7 @@ namespace eosio { namespace vm { // popq %rdi emit_bytes(0x5f); if constexpr (Context::async_backtrace()) { + emit_restore_backtrace_basic(); // popq %rbp emit_bytes(0x5d); } From 7d393f438f8c99229beac8827e6dce4bd3259aa7 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Wed, 30 Sep 2020 15:38:36 -0400 Subject: [PATCH 15/19] Fix one more backtrace weirdness. --- include/eosio/vm/execution_context.hpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index fd2f92fc..10aae7e0 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -361,9 +361,13 @@ namespace eosio { namespace vm { if(rip >= code_base && rip < code_end && count > 1) { // function prologue if(*reinterpret_cast(rip) == 0x55) { - out[i++] = *static_cast(rsp); - } else if(rip[0] == 0x48 && rip[1] == 0x89 && rip[2] == 0xe5) { - out[i++] = static_cast(rsp)[1]; + if(rip != *static_cast(rsp)) { // Ignore fake frame set up for softfloat calls + out[i++] = *static_cast(rsp); + } + } else if(rip[0] == 0x48 && rip[1] == 0x89 && (rip[2] == 0xe5 || rip[2] == 0x27)) { + if((rip - 1) != static_cast(rsp)[1]) { // Ignore fake frame set up for softfloat calls + out[i++] = static_cast(rsp)[1]; + } } // function epilogue else if(rip[0] == 0xc3) { From 2a5e7e43fc12d575a385b0a376acb7a8d0368169 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Thu, 1 Oct 2020 13:26:51 -0400 Subject: [PATCH 16/19] Remove nm. It isn't fully implemented and isn't needed. --- tools/CMakeLists.txt | 3 -- tools/nm.cpp | 90 -------------------------------------------- 2 files changed, 93 deletions(-) delete mode 100644 tools/nm.cpp diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 05c3b363..85fb5b35 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -10,8 +10,5 @@ target_link_libraries(bench-interp eos-vm) add_executable(hello-driver ${CMAKE_CURRENT_SOURCE_DIR}/hello_driver.cpp) target_link_libraries(hello-driver eos-vm) -add_executable(eos-vm-nm nm.cpp) -target_link_libraries(eos-vm-nm eos-vm) - add_executable(eos-vm-addr2line addr2line.cpp) target_link_libraries(eos-vm-addr2line eos-vm) diff --git a/tools/nm.cpp b/tools/nm.cpp deleted file mode 100644 index 3fedbc88..00000000 --- a/tools/nm.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include -#include -#include -#include - -struct nm_debug_info { - using builder = nm_debug_info; - void on_code_start(const void* compiled_base, const void* wasm_code_start) { - wasm_base = wasm_code_start; - } - void on_function_start(const void* code_addr, const void* wasm_addr) { - function_offsets.push_back(static_cast(reinterpret_cast(wasm_addr) - reinterpret_cast(wasm_base))); - } - void on_instr_start(const void* code_addr, const void* wasm_addr) {} - void on_code_end(const void* code_addr, const void* wasm_addr) {} - void set(nm_debug_info&& other) { *this = std::move(other); } - void relocate(const void*) {} - - const void* wasm_base; - std::vector function_offsets; -}; - -enum class nm_sort_order { - alphabetic, numeric, none -}; - -eosio::vm::guarded_vector* find_export_name(eosio::vm::module& mod, uint32_t idx) { - if(mod.names && mod.names->function_names) { - for(uint32_t i = 0; i < mod.names->function_names->size(); ++i) { - if((*mod.names->function_names)[i].idx == idx) { - return &(*mod.names->function_names)[i].name; - } - } - } - for(uint32_t i = 0; i < mod.exports.size(); ++i) { - if(mod.exports[i].index == idx && mod.exports[i].kind == eosio::vm::Function) { - return &mod.exports[i].field_str; - } - } - return nullptr; -} - -struct nm_options { - static constexpr bool parse_custom_section_name = true; -}; - -int main(int argc, const char** argv) { - bool print_file_name = false; - nm_sort_order sort = nm_sort_order::alphabetic; - std::vector filenames; - for(int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if(std::strcmp(argv[i], "-A") == 0 || std::strcmp(argv[i], "-o") == 0 || std::strcmp(argv[i], "--print-file-name") == 0) { - print_file_name = true; - } else if(std::strcmp(argv[i], "-a") == 0 || std::strcmp(argv[i], "--debug-syms") == 0) { - // Ignored because we don't know how to parse debug symbols - } else if(std::strcmp(argv[i], "-C") == 0 || std::strncmp(argv[i], "--demangle=", 11) == 0) { - // Demangling not implemented - } else if(std::strcmp(argv[i], "--no-demangle") == 0) { - // Default - } else if(std::strcmp(argv[i], "-D") == 0 || std::strcmp(argv[i], "--dynamic") == 0) { - // Not a dynamic object - } else if(arg == "-n" || arg == "-v" || arg == "--numeric-sort") { - sort = nm_sort_order::numeric; - } else if(arg[0] != '-') { - filenames.push_back(arg); - } else { - std::cerr << "unexpected argument: " << arg << std::endl; - return 2; - } - } - - for(const std::string& filename : filenames) { - using namespace eosio::vm; - auto code = read_wasm(filename); - nm_debug_info info; - module mod; - binary_parser parser(mod.allocator); - parser.parse_module(code, mod, info); - for(std::size_t i = 0; i < info.function_offsets.size(); ++i) { - std::cout << std::hex << std::setw(8) << std::setfill('0') << info.function_offsets[i] << " T "; - if(guarded_vector* name = find_export_name(mod, i + mod.get_imported_functions_size())) { - std::cout << std::string_view(reinterpret_cast(name->raw()), name->size()); - } else { - std::cout << "fn" << std::dec << i + mod.get_imported_functions_size(); - } - std::cout << std::endl; - } - } -} From 9876bc2aa1a2b11b70ab8a5ef8a0de357ac2af4d Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Thu, 1 Oct 2020 13:31:14 -0400 Subject: [PATCH 17/19] Don't kill the compiler --- tests/utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils.hpp b/tests/utils.hpp index 6a66bf7d..2e749da2 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -53,4 +53,4 @@ inline eosio::vm::wasm_allocator* get_wasm_allocator() { } #define BACKEND_TEST_CASE(name, tags) \ - TEMPLATE_TEST_CASE(name, tags, eosio::vm::interpreter, eosio::vm::jit, eosio::vm::jit_profile) + TEMPLATE_TEST_CASE(name, tags, eosio::vm::interpreter, eosio::vm::jit) From 8583b0d0ceacf9c2e32ac605cc5a12dc22d5b542 Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 2 Oct 2020 08:50:20 -0400 Subject: [PATCH 18/19] Add missing thread_local --- include/eosio/vm/profile.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index 6fc99067..f226941c 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -320,7 +320,7 @@ struct profile_manager { }; __attribute__((visibility("default"))) -inline std::unique_ptr per_thread_profile_manager; +inline thread_local std::unique_ptr per_thread_profile_manager; struct scoped_profile { explicit scoped_profile(profile_data* data) { From 530e758c580ce6f0d842ab63f262539ab257edcc Mon Sep 17 00:00:00 2001 From: Steven Watanabe Date: Fri, 30 Oct 2020 11:55:01 -0400 Subject: [PATCH 19/19] Feedback --- include/eosio/vm/bitcode_writer.hpp | 6 +++--- include/eosio/vm/debug_info.hpp | 14 +++++++------- include/eosio/vm/execution_context.hpp | 1 + include/eosio/vm/profile.hpp | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/include/eosio/vm/bitcode_writer.hpp b/include/eosio/vm/bitcode_writer.hpp index 295b009d..ee469e01 100644 --- a/include/eosio/vm/bitcode_writer.hpp +++ b/include/eosio/vm/bitcode_writer.hpp @@ -295,11 +295,11 @@ namespace eosio { namespace vm { } void finalize(function_body& body) { - fb.resize(op_index + 1); + op_index++; + fb.resize(op_index); body.code = fb.raw(); - body.size = op_index + 1; + body.size = op_index; _base_offset += body.size; - op_index++; } const void* get_addr() const { return fb.raw() + op_index; } diff --git a/include/eosio/vm/debug_info.hpp b/include/eosio/vm/debug_info.hpp index a85d44b0..a666c8ec 100644 --- a/include/eosio/vm/debug_info.hpp +++ b/include/eosio/vm/debug_info.hpp @@ -48,9 +48,9 @@ class profile_instr_map { code_end = code_addr; } - const void* code_base; - const void* wasm_base; - const void* code_end; + const void* code_base = nullptr; + const void* wasm_base = nullptr; + const void* code_end = nullptr; std::vector data; }; @@ -90,11 +90,11 @@ class profile_instr_map { return offset_to_addr[lower].wasm_addr; } private: - const void* base_address; - std::size_t code_size; + const void* base_address = nullptr; + std::size_t code_size = 0; - addr_entry* offset_to_addr; - std::size_t offset_to_addr_len; + addr_entry* offset_to_addr = nullptr; + std::size_t offset_to_addr_len = 0; std::vector data; }; diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index 10aae7e0..4207b5fe 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -24,6 +24,7 @@ #include #include +// OSX requires _XOPEN_SOURCE to #include #ifdef __APPLE__ #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 diff --git a/include/eosio/vm/profile.hpp b/include/eosio/vm/profile.hpp index f226941c..c9160278 100644 --- a/include/eosio/vm/profile.hpp +++ b/include/eosio/vm/profile.hpp @@ -380,7 +380,7 @@ struct profile_manager { } ~profile_manager() { { - auto lock = std::unique_lock(mutex); + auto lock = std::scoped_lock(mutex); done = true; } timer_cond.notify_one();