Skip to content

Commit

Permalink
Merge pull request #19 from AntelopeIO/decouple_exec_ctx
Browse files Browse the repository at this point in the history
Use a single execution context per wasm interface (per thread) instead of per contract
  • Loading branch information
linh2931 authored Aug 16, 2023
2 parents 169835e + 8e1d571 commit 74dd3f0
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 90 deletions.
117 changes: 78 additions & 39 deletions include/eosio/vm/backend.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ namespace eosio { namespace vm {
using parser_t = typename Impl::template parser<HostFunctions, Options, DebugInfo>;
void construct(host_t* host=nullptr) {
mod.finalize();
ctx.set_wasm_allocator(memory_alloc);
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) {
Expand All @@ -74,49 +76,67 @@ namespace eosio { namespace vm {
// Important. Release the memory used by parsing.
mod.allocator.release_base_memory();
}
ctx.initialize_globals();
if (exec_ctx_created_by_backend) {
ctx->initialize_globals();
}
if constexpr (!std::is_same_v<HostFunctions, std::nullptr_t>)
HostFunctions::resolve(mod);
// FIXME: should not hard code knowledge of null_backend here
if constexpr (!std::is_same_v<Impl, null_backend>)
initialize(host);
if (exec_ctx_created_by_backend) {
if constexpr (!std::is_same_v<Impl, null_backend>)
initialize(host);
}
}
public:
backend(wasm_code&& code, host_t& host, wasm_allocator* alloc, const Options& options = Options{})
: memory_alloc(alloc), ctx(parse_module(code, options), detail::get_max_call_depth(options)) {
ctx.set_max_pages(detail::get_max_pages(options));
: memory_alloc(alloc), 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(parse_module(code, options), detail::get_max_call_depth(options)) {
ctx.set_max_pages(detail::get_max_pages(options));
: memory_alloc(alloc), 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(parse_module(code, options), detail::get_max_call_depth(options)) {
ctx.set_max_pages(detail::get_max_pages(options));
: memory_alloc(alloc), 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(parse_module(code, options), detail::get_max_call_depth(options)) {
ctx.set_max_pages(detail::get_max_pages(options));
: memory_alloc(alloc), 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(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));
: memory_alloc(alloc), 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);
}
// Leap:
// * Contract validation only needs single parsing as the instantiated module is not cached.
// * JIT execution needs single parsing only.
// * Interpreter execution requires two-passes parsing to prevent memory mappings exhaustion
backend(wasm_code_ptr& ptr, size_t sz, wasm_allocator* alloc, const Options& options = Options{}, bool single_parsing = true)
: memory_alloc(alloc), ctx(parse_module2(ptr, sz, options, single_parsing), detail::get_max_call_depth(options)) {
ctx.set_max_pages(detail::get_max_pages(options));
// * 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)) {
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);
} else {
parse_module2(ptr, sz, options, single_parsing);
}
construct();
}

~backend() {
if (exec_ctx_created_by_backend && ctx) {
// delete only if the context was created by the backend
delete ctx;
}
}

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);
Expand Down Expand Up @@ -148,6 +168,22 @@ namespace eosio { namespace vm {
}
}

void set_context(context_t* ctx_ptr) {
// execution context can be only set when it is not already created by the backend
assert(!exec_ctx_created_by_backend);
ctx = ctx_ptr;
}

inline void reset_max_call_depth() {
assert(!exec_ctx_created_by_backend);
ctx->set_max_call_depth(initial_max_call_depth);
}

inline void reset_max_pages() {
assert(!exec_ctx_created_by_backend);
ctx->set_max_pages(initial_max_pages);
}

template <typename... Args>
inline auto operator()(host_t& host, const std::string_view& mod, const std::string_view& func, Args... args) {
return call(host, mod, func, args...);
Expand All @@ -160,16 +196,16 @@ namespace eosio { namespace vm {

// Only dynamic options matter. Parser options will be ignored.
inline backend& initialize(host_t* host, const Options& new_options) {
ctx.set_max_call_depth(detail::get_max_call_depth(new_options));
ctx.set_max_pages(detail::get_max_pages(new_options));
ctx->set_max_call_depth(detail::get_max_call_depth(new_options));
ctx->set_max_pages(detail::get_max_pages(new_options));
initialize(host);
return *this;
}

inline backend& initialize(host_t* host=nullptr) {
if (memory_alloc) {
ctx.reset();
ctx.execute_start(host, interpret_visitor(ctx));
ctx->reset();
ctx->execute_start(host, interpret_visitor(*ctx));
}
return *this;
}
Expand All @@ -181,58 +217,58 @@ namespace eosio { namespace vm {
template <typename... Args>
inline bool call_indirect(host_t* host, uint32_t func_index, Args... args) {
if constexpr (eos_vm_debug) {
ctx.execute_func_table(host, debug_visitor(ctx), func_index, args...);
ctx->execute_func_table(host, debug_visitor(*ctx), func_index, args...);
} else {
ctx.execute_func_table(host, interpret_visitor(ctx), func_index, args...);
ctx->execute_func_table(host, interpret_visitor(*ctx), func_index, args...);
}
return true;
}

template <typename... Args>
inline bool call(host_t* host, uint32_t func_index, Args... args) {
if constexpr (eos_vm_debug) {
ctx.execute(host, debug_visitor(ctx), func_index, args...);
ctx->execute(host, debug_visitor(*ctx), func_index, args...);
} else {
ctx.execute(host, interpret_visitor(ctx), func_index, args...);
ctx->execute(host, interpret_visitor(*ctx), func_index, args...);
}
return true;
}

template <typename... Args>
inline bool call(host_t& host, const std::string_view& mod, const std::string_view& func, Args... args) {
if constexpr (eos_vm_debug) {
ctx.execute(&host, debug_visitor(ctx), func, args...);
ctx->execute(&host, debug_visitor(*ctx), func, args...);
} else {
ctx.execute(&host, interpret_visitor(ctx), func, args...);
ctx->execute(&host, interpret_visitor(*ctx), func, args...);
}
return true;
}

template <typename... Args>
inline bool call(const std::string_view& mod, const std::string_view& func, Args... args) {
if constexpr (eos_vm_debug) {
ctx.execute(nullptr, debug_visitor(ctx), func, args...);
ctx->execute(nullptr, debug_visitor(*ctx), func, args...);
} else {
ctx.execute(nullptr, interpret_visitor(ctx), func, args...);
ctx->execute(nullptr, interpret_visitor(*ctx), func, args...);
}
return true;
}

template <typename... Args>
inline auto call_with_return(host_t& host, const std::string_view& mod, const std::string_view& func, Args... args ) {
if constexpr (eos_vm_debug) {
return ctx.execute(&host, debug_visitor(ctx), func, args...);
return ctx->execute(&host, debug_visitor(*ctx), func, args...);
} else {
return ctx.execute(&host, interpret_visitor(ctx), func, args...);
return ctx->execute(&host, interpret_visitor(*ctx), func, args...);
}
}

template <typename... Args>
inline auto call_with_return(const std::string_view& mod, const std::string_view& func, Args... args) {
if constexpr (eos_vm_debug) {
return ctx.execute(nullptr, debug_visitor(ctx), func, args...);
return ctx->execute(nullptr, debug_visitor(*ctx), func, args...);
} else {
return ctx.execute(nullptr, interpret_visitor(ctx), func, args...);
return ctx->execute(nullptr, interpret_visitor(*ctx), func, args...);
}
}

Expand Down Expand Up @@ -265,7 +301,7 @@ namespace eosio { namespace vm {
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);
ctx->execute(host, interpret_visitor(*ctx), s);
}
}
});
Expand All @@ -277,28 +313,31 @@ namespace eosio { namespace vm {
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);
ctx->execute(nullptr, interpret_visitor(*ctx), s);
}
}
});
}

inline void set_wasm_allocator(wasm_allocator* alloc) {
memory_alloc = alloc;
ctx.set_wasm_allocator(memory_alloc);
ctx->set_wasm_allocator(memory_alloc);
}

inline wasm_allocator* get_wasm_allocator() { return memory_alloc; }
inline module& get_module() { return mod; }
inline void exit(const std::error_code& ec) { ctx.exit(ec); }
inline auto& get_context() { return ctx; }
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;
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)
uint32_t initial_max_call_depth = 0;
uint32_t initial_max_pages = 0;
};
}} // namespace eosio::vm
Loading

0 comments on commit 74dd3f0

Please sign in to comment.