From 7d184fb359683ff8653ea0ac37f4fc10df8f2fe8 Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Wed, 23 Aug 2023 12:05:20 -0400 Subject: [PATCH 1/3] support single wasm interface --- include/eosio/vm/backend.hpp | 70 +++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 0eac144..1e2069e 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -64,23 +64,23 @@ namespace eosio { namespace vm { using context_t = typename Impl::template context; using parser_t = typename Impl::template parser; void construct(host_t* host=nullptr) { - mod.finalize(); + mod->finalize(); if (exec_ctx_created_by_backend) { ctx->set_wasm_allocator(memory_alloc); } // Now data required by JIT is finalized; create JIT module // such that memory used in parsing can be released. if constexpr (Impl::is_jit) { - mod.make_jit_module(); + mod->make_jit_module(); // Important. Release the memory used by parsing. - mod.allocator.release_base_memory(); + mod->allocator.release_base_memory(); } if (exec_ctx_created_by_backend) { ctx->initialize_globals(); } if constexpr (!std::is_same_v) - HostFunctions::resolve(mod); + HostFunctions::resolve(*mod); // FIXME: should not hard code knowledge of null_backend here if (exec_ctx_created_by_backend) { if constexpr (!std::is_same_v) @@ -88,28 +88,29 @@ namespace eosio { namespace vm { } } public: + backend() {} backend(wasm_code&& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), 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(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), 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(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), 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(new context_t{(parse_module(code, options)), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{(parse_module(code, options)), 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(new context_t{parse_module2(ptr, sz, options, true), detail::get_max_call_depth(options)}) { // single parsing. original behavior { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module2(ptr, sz, options, true), detail::get_max_call_depth(options)}) { // single parsing. original behavior { ctx->set_max_pages(detail::get_max_pages(options)); construct(&host); } @@ -119,8 +120,8 @@ namespace eosio { namespace vm { // * Interpreter execution requires two-passes parsing to prevent memory mappings exhaustion // * Leap reuses execution context per thread; is_exec_ctx_created_by_backend is set // to false when a backend is constructued - backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}, bool single_parsing = true, bool is_exec_ctx_created_by_backend = true) - : memory_alloc(alloc), exec_ctx_created_by_backend(is_exec_ctx_created_by_backend), initial_max_call_depth(detail::get_max_call_depth(options)), initial_max_pages(detail::get_max_pages(options)) { + backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}, bool single_parsing = true, bool exec_ctx_by_backend = true) + : memory_alloc(alloc), mod(std::make_shared()), exec_ctx_created_by_backend(exec_ctx_by_backend), initial_max_call_depth(detail::get_max_call_depth(options)), initial_max_pages(detail::get_max_pages(options)) { if (exec_ctx_created_by_backend) { ctx = new context_t{parse_module2(ptr, sz, options, single_parsing), initial_max_call_depth}; ctx->set_max_pages(initial_max_pages); @@ -130,6 +131,16 @@ namespace eosio { namespace vm { construct(); } + backend& operator=(const backend& other) { + if (this != &other) { + mod = other.mod; + exec_ctx_created_by_backend = other.exec_ctx_created_by_backend; + initial_max_call_depth = other.initial_max_call_depth; + initial_max_pages = other.initial_max_pages; + } + return *this; + } + ~backend() { if (exec_ctx_created_by_backend && ctx) { // delete only if the context was created by the backend @@ -138,14 +149,14 @@ namespace eosio { namespace vm { } module& parse_module(wasm_code& code, const Options& options) { - mod.allocator.use_default_memory(); - return parser_t{ mod.allocator, options }.parse_module(code, mod, debug); + mod->allocator.use_default_memory(); + return parser_t{ mod->allocator, options }.parse_module(code, *mod, debug); } module& parse_module2(wasm_code_ptr& ptr, size_t sz, const Options& options, bool single_parsing) { if (single_parsing) { - mod.allocator.use_default_memory(); - return parser_t{ mod.allocator, options }.parse_module2(ptr, sz, mod, debug); + mod->allocator.use_default_memory(); + return parser_t{ mod->allocator, options }.parse_module2(ptr, sz, *mod, debug); } else { // To prevent large number of memory mappings used, two-passes of // parsing are performed. @@ -163,23 +174,25 @@ namespace eosio { namespace vm { } // Second pass: uses actual required memory for final parsing - mod.allocator.use_fixed_memory(largest_size); - return parser_t{ mod.allocator, options }.parse_module2(orig_ptr, sz, mod, debug); + mod->allocator.use_fixed_memory(largest_size); + return parser_t{ mod->allocator, options }.parse_module2(orig_ptr, sz, *mod, debug); } } void set_context(context_t* ctx_ptr) { - // execution context can be only set when it is not already created by the backend + // ctx cannot be set if it is created by the backend assert(!exec_ctx_created_by_backend); ctx = ctx_ptr; } inline void reset_max_call_depth() { + // max_call_depth cannot be reset if ctx is created by the backend assert(!exec_ctx_created_by_backend); ctx->set_max_call_depth(initial_max_call_depth); } inline void reset_max_pages() { + // max_pages cannot be reset if ctx is created by the backend assert(!exec_ctx_created_by_backend); ctx->set_max_pages(initial_max_pages); } @@ -277,13 +290,13 @@ namespace eosio { namespace vm { std::atomic _timed_out = false; auto reenable_code = scope_guard{[&](){ if (_timed_out) { - mod.allocator.enable_code(Impl::is_jit); + mod->allocator.enable_code(Impl::is_jit); } }}; try { auto wd_guard = wd.scoped_run([this,&_timed_out]() { _timed_out = true; - mod.allocator.disable_code(); + mod->allocator.disable_code(); }); static_cast(f)(); } catch(wasm_memory_exception&) { @@ -298,9 +311,9 @@ namespace eosio { namespace vm { template inline void execute_all(Watchdog&& wd, host_t& host) { timed_run(static_cast(wd), [&]() { - for (int i = 0; i < mod.exports.size(); i++) { - if (mod.exports[i].kind == external_kind::Function) { - std::string s{ (const char*)mod.exports[i].field_str.raw(), mod.exports[i].field_str.size() }; + for (int i = 0; i < mod->exports.size(); i++) { + if (mod->exports[i].kind == external_kind::Function) { + std::string s{ (const char*)mod->exports[i].field_str.raw(), mod->exports[i].field_str.size() }; ctx->execute(host, interpret_visitor(*ctx), s); } } @@ -310,9 +323,9 @@ namespace eosio { namespace vm { template inline void execute_all(Watchdog&& wd) { timed_run(static_cast(wd), [&]() { - for (int i = 0; i < mod.exports.size(); i++) { - if (mod.exports[i].kind == external_kind::Function) { - std::string s{ (const char*)mod.exports[i].field_str.raw(), mod.exports[i].field_str.size() }; + for (int i = 0; i < mod->exports.size(); i++) { + if (mod->exports[i].kind == external_kind::Function) { + std::string s{ (const char*)mod->exports[i].field_str.raw(), mod->exports[i].field_str.size() }; ctx->execute(nullptr, interpret_visitor(*ctx), s); } } @@ -324,8 +337,7 @@ namespace eosio { namespace vm { ctx->set_wasm_allocator(memory_alloc); } - inline wasm_allocator* get_wasm_allocator() { return memory_alloc; } - inline module& get_module() { return mod; } + inline module& get_module() { return *mod; } inline void exit(const std::error_code& ec) { ctx->exit(ec); } inline auto& get_context() { return *ctx; } @@ -333,7 +345,7 @@ namespace eosio { namespace vm { private: wasm_allocator* memory_alloc = nullptr; // non owning pointer - module mod; + std::shared_ptr mod = nullptr; DebugInfo debug; context_t* ctx = nullptr; bool exec_ctx_created_by_backend = true; // true if execution context is created by backend (legacy behavior), false if provided by users (Leap uses this) From b79fd167ef39dc147e148ad1d1604e0cb82e9dab Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Sat, 26 Aug 2023 11:55:21 -0400 Subject: [PATCH 2/3] remove copy constructor from backend and use a share method instead --- include/eosio/vm/backend.hpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index 1e2069e..ec662f6 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -131,15 +131,6 @@ namespace eosio { namespace vm { construct(); } - backend& operator=(const backend& other) { - if (this != &other) { - mod = other.mod; - exec_ctx_created_by_backend = other.exec_ctx_created_by_backend; - initial_max_call_depth = other.initial_max_call_depth; - initial_max_pages = other.initial_max_pages; - } - return *this; - } ~backend() { if (exec_ctx_created_by_backend && ctx) { @@ -179,6 +170,13 @@ namespace eosio { namespace vm { } } + void share(const backend& from) { + mod = from.mod; + exec_ctx_created_by_backend = from.exec_ctx_created_by_backend; + initial_max_call_depth = from.initial_max_call_depth; + initial_max_pages = from.initial_max_pages; + } + void set_context(context_t* ctx_ptr) { // ctx cannot be set if it is created by the backend assert(!exec_ctx_created_by_backend); From 23d897d38b8de8db59e164d762ede935255f990e Mon Sep 17 00:00:00 2001 From: Lin Huang Date: Thu, 31 Aug 2023 21:21:20 -0400 Subject: [PATCH 3/3] prevent invalid sharing of compiled mod in backend --- include/eosio/vm/backend.hpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/include/eosio/vm/backend.hpp b/include/eosio/vm/backend.hpp index ec662f6..25dcb91 100644 --- a/include/eosio/vm/backend.hpp +++ b/include/eosio/vm/backend.hpp @@ -90,27 +90,27 @@ namespace eosio { namespace vm { public: backend() {} backend(wasm_code&& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{}) - : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}), mod_sharable{true} { 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), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}), mod_sharable{true} { 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), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module(code, options), detail::get_max_call_depth(options)}), mod_sharable{true} { 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), mod(std::make_shared()), ctx(new context_t{(parse_module(code, options)), detail::get_max_call_depth(options)}) { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{(parse_module(code, options)), detail::get_max_call_depth(options)}), mod_sharable{true} { 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), mod(std::make_shared()), ctx(new context_t{parse_module2(ptr, sz, options, true), detail::get_max_call_depth(options)}) { // single parsing. original behavior { + : memory_alloc(alloc), mod(std::make_shared()), ctx(new context_t{parse_module2(ptr, sz, options, true), detail::get_max_call_depth(options)}), mod_sharable{true} { // single parsing. original behavior { ctx->set_max_pages(detail::get_max_pages(options)); construct(&host); } @@ -121,7 +121,7 @@ namespace eosio { namespace vm { // * Leap reuses execution context per thread; is_exec_ctx_created_by_backend is set // to false when a backend is constructued backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}, bool single_parsing = true, bool exec_ctx_by_backend = true) - : memory_alloc(alloc), mod(std::make_shared()), exec_ctx_created_by_backend(exec_ctx_by_backend), initial_max_call_depth(detail::get_max_call_depth(options)), initial_max_pages(detail::get_max_pages(options)) { + : memory_alloc(alloc), mod(std::make_shared()), exec_ctx_created_by_backend(exec_ctx_by_backend), mod_sharable{true}, initial_max_call_depth(detail::get_max_call_depth(options)), initial_max_pages(detail::get_max_pages(options)) { if (exec_ctx_created_by_backend) { ctx = new context_t{parse_module2(ptr, sz, options, single_parsing), initial_max_call_depth}; ctx->set_max_pages(initial_max_pages); @@ -170,8 +170,12 @@ namespace eosio { namespace vm { } } + // Shares compiled module with another backend which never compiles + // module itself. void share(const backend& from) { - mod = from.mod; + assert(from.mod_sharable); // `from` backend's mod is sharable + assert(!mod_sharable); // `to` backend's mod must not be sharable + mod = from.mod; exec_ctx_created_by_backend = from.exec_ctx_created_by_backend; initial_max_call_depth = from.initial_max_call_depth; initial_max_pages = from.initial_max_pages; @@ -347,6 +351,7 @@ namespace eosio { namespace vm { DebugInfo debug; context_t* ctx = nullptr; bool exec_ctx_created_by_backend = true; // true if execution context is created by backend (legacy behavior), false if provided by users (Leap uses this) + bool mod_sharable = false; // true if mod is sharable (compiled by the backend) uint32_t initial_max_call_depth = 0; uint32_t initial_max_pages = 0; };