diff --git a/binding-sandbox/binding-base.cpp b/binding-sandbox/binding-base.cpp new file mode 100644 index 00000000..5d85f1b1 --- /dev/null +++ b/binding-sandbox/binding-base.cpp @@ -0,0 +1,137 @@ +/* +** binding-base.cpp +** +** This file is part of mkxp. +** +** Copyright (C) 2013 - 2021 Amaryllis Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#include "binding-base.h" + +#if WABT_BIG_ENDIAN +# define SERIALIZE_32(value) __builtin_bswap32(value) +# define SERIALIZE_64(value) __builtin_bswap64(value) +#else +# define SERIALIZE_32(value) (value) +# define SERIALIZE_64(value) (value) +#endif + +#ifdef MKXPZ_RETRO_MEMORY64 +# define SERIALIZE_VALUE(value) SERIALIZE_64(value) +#else +# define SERIALIZE_VALUE(value) SERIALIZE_32(value) +#endif + +using namespace mkxp_sandbox; + +binding_base::stack_frame::stack_frame(struct binding_base &bind, void (*destructor)(void *ptr), boost::typeindex::type_index type, wasm_ptr_t ptr) : bind(bind), destructor(destructor), type(type), ptr(ptr) {} + +binding_base::stack_frame::~stack_frame() { + destructor(*bind + ptr); +} + +binding_base::binding_base(std::shared_ptr m) : next_func_ptr(-1), _instance(m) {} + +binding_base::~binding_base() { + // Destroy all stack frames in order from top to bottom to enforce a portable, compiler-independent ordering of stack frame destruction + // If we let the compiler use its default destructor, the stack frames may not be deallocated in a particular order, which can lead to hard-to-detect bugs if somehow a bug depends on the order in which the stack frames are deallocated + for (auto &it : fibers) { + while (!it.second.stack.empty()) { + it.second.stack.pop_back(); + } + } +} + +struct w2c_ruby &binding_base::instance() const noexcept { + return *_instance; +} + +uint8_t *binding_base::get() const noexcept { + return instance().w2c_memory.data; +} + +uint8_t *binding_base::operator*() const noexcept { + return get(); +} + +wasm_ptr_t binding_base::sandbox_malloc(wasm_size_t size) { + wasm_ptr_t buf = w2c_ruby_mkxp_sandbox_malloc(&instance(), size); + + // Verify that the entire allocated buffer is in valid memory + wasm_ptr_t buf_end; + if (buf == 0 || __builtin_add_overflow(buf, size, &buf_end) || buf_end >= instance().w2c_memory.size) { + return 0; + } + + return buf; +} + +void binding_base::sandbox_free(wasm_ptr_t ptr) { + w2c_ruby_mkxp_sandbox_free(&instance(), ptr); +} + +wasm_ptr_t binding_base::sandbox_create_func_ptr() { + if (next_func_ptr == (wasm_ptr_t)-1) { + next_func_ptr = instance().w2c_T0.size; + } + + if (next_func_ptr < instance().w2c_T0.max_size) { + return next_func_ptr++; + } + + // Make sure that an integer overflow won't occur if we double the max size of the funcref table + wasm_size_t new_max_size; + if (__builtin_add_overflow(instance().w2c_T0.max_size, instance().w2c_T0.max_size, &new_max_size)) { + return -1; + } + + // Double the max size of the funcref table + wasm_size_t old_max_size = instance().w2c_T0.max_size; + instance().w2c_T0.max_size = new_max_size; + + // Double the size of the funcref table buffer + if (wasm_rt_grow_funcref_table(&instance().w2c_T0, old_max_size, wasm_rt_funcref_t { + .func_type = wasm2c_ruby_get_func_type(0, 0), + .func = NULL, + .func_tailcallee = {.fn = NULL}, + .module_instance = &instance(), + }) != old_max_size) { + instance().w2c_T0.max_size = old_max_size; + return -1; + } + + return next_func_ptr++; +} + +wasm_ptr_t binding_base::rtypeddata_data(VALUE obj) const noexcept { + return SERIALIZE_VALUE(obj) + *(wasm_size_t *)(instance().w2c_memory.data + instance().w2c_mkxp_sandbox_rtypeddata_data_offset); +} + +void binding_base::rtypeddata_dmark(wasm_ptr_t data, wasm_ptr_t ptr) { + w2c_ruby_mkxp_sandbox_rtypeddata_dmark(&instance(), data, ptr); +} + +void binding_base::rtypeddata_dfree(wasm_ptr_t data, wasm_ptr_t ptr) { + w2c_ruby_mkxp_sandbox_rtypeddata_dfree(&instance(), data, ptr); +} + +wasm_size_t binding_base::rtypeddata_dsize(wasm_ptr_t data, wasm_ptr_t ptr) { + return w2c_ruby_mkxp_sandbox_rtypeddata_dsize(&instance(), data, ptr); +} + +void binding_base::rtypeddata_dcompact(wasm_ptr_t data, wasm_ptr_t ptr) { + w2c_ruby_mkxp_sandbox_rtypeddata_dcompact(&instance(), data, ptr); +} diff --git a/binding-sandbox/binding-base.h b/binding-sandbox/binding-base.h new file mode 100644 index 00000000..8f1b7023 --- /dev/null +++ b/binding-sandbox/binding-base.h @@ -0,0 +1,198 @@ +/* +** binding-base.h +** +** This file is part of mkxp. +** +** Copyright (C) 2013 - 2021 Amaryllis Kulla +** +** mkxp is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** mkxp is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with mkxp. If not, see . +*/ + +#ifndef MKXPZ_SANDBOX_BINDING_BASE_H +#define MKXPZ_SANDBOX_BINDING_BASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "binding-sandbox/types.h" + +#if WABT_BIG_ENDIAN +# define SERIALIZE_32(value) __builtin_bswap32(value) +# define SERIALIZE_64(value) __builtin_bswap64(value) +#else +# define SERIALIZE_32(value) (value) +# define SERIALIZE_64(value) (value) +#endif + +#ifdef MKXPZ_RETRO_MEMORY64 +# define SERIALIZE_VALUE(value) SERIALIZE_64(value) +#else +# define SERIALIZE_VALUE(value) SERIALIZE_32(value) +#endif + +namespace mkxp_sandbox { + struct binding_base { + private: + + typedef std::tuple key_t; + + struct stack_frame { + struct binding_base &bind; + void (*destructor)(void *ptr); + boost::typeindex::type_index type; + wasm_ptr_t ptr; + stack_frame(struct binding_base &bind, void (*destructor)(void *ptr), boost::typeindex::type_index type, wasm_ptr_t ptr); + ~stack_frame(); + }; + + struct fiber { + key_t key; + std::vector stack; + size_t stack_ptr; + }; + + wasm_ptr_t next_func_ptr; + std::shared_ptr _instance; + std::unordered_map> fibers; + + public: + + binding_base(std::shared_ptr m); + ~binding_base(); + struct w2c_ruby &instance() const noexcept; + uint8_t *get() const noexcept; + uint8_t *operator*() const noexcept; + wasm_ptr_t sandbox_malloc(wasm_size_t); + void sandbox_free(wasm_ptr_t ptr); + wasm_ptr_t sandbox_create_func_ptr(); + wasm_ptr_t rtypeddata_data(VALUE obj) const noexcept; + void rtypeddata_dmark(wasm_ptr_t data, wasm_ptr_t ptr); + void rtypeddata_dfree(wasm_ptr_t data, wasm_ptr_t ptr); + wasm_size_t rtypeddata_dsize(wasm_ptr_t data, wasm_ptr_t ptr); + void rtypeddata_dcompact(wasm_ptr_t data, wasm_ptr_t ptr); + + template struct stack_frame_guard { + friend struct binding_base; + + private: + + struct binding_base &bind; + struct fiber &fiber; + wasm_ptr_t ptr; + + static void stack_frame_destructor(void *ptr) { + ((T *)ptr)->~T(); + } + + static struct fiber &init_fiber(struct binding_base &bind) { + key_t key = { + *(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_entry_point), + *(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_arg0), + *(wasm_ptr_t *)(*bind + bind.instance().w2c_mkxp_sandbox_fiber_arg1), + }; + if (bind.fibers.count(key) == 0) { + bind.fibers[key] = (struct fiber){.key = key}; + } + return bind.fibers[key]; + } + + static wasm_ptr_t init_inner(struct binding_base &bind, struct fiber &fiber) { + wasm_ptr_t sp = w2c_ruby_rb_wasm_get_stack_pointer(&bind.instance()); + + if (fiber.stack_ptr == fiber.stack.size()) { + fiber.stack.emplace_back( + bind, + stack_frame_destructor, + boost::typeindex::type_id(), + (sp -= sizeof(T)) + ); + assert(sp % sizeof(VALUE) == 0); + new(*bind + sp) T(bind); + } else if (fiber.stack_ptr > fiber.stack.size()) { + throw SandboxTrapException(); + } + + if (fiber.stack[fiber.stack_ptr].type == boost::typeindex::type_id()) { + w2c_ruby_rb_wasm_set_stack_pointer(&bind.instance(), sp); + return fiber.stack[fiber.stack_ptr++].ptr; + } else { + while (fiber.stack.size() > fiber.stack_ptr) { + fiber.stack.pop_back(); + } + ++fiber.stack_ptr; + fiber.stack.emplace_back( + bind, + stack_frame_destructor, + boost::typeindex::type_id(), + (sp -= sizeof(T)) + ); + assert(sp % sizeof(VALUE) == 0); + new(*bind + sp) T(bind); + w2c_ruby_rb_wasm_set_stack_pointer(&bind.instance(), sp); + return sp; + } + } + + stack_frame_guard(struct binding_base &b) : bind(b), fiber(init_fiber(b)), ptr(init_inner(b, fiber)) {} + + public: + + ~stack_frame_guard() { + if (get()->is_complete()) { + while (fiber.stack.size() > fiber.stack_ptr) { + fiber.stack.pop_back(); + } + + // Check for stack corruptions + assert(fiber.stack.size() == fiber.stack_ptr); + assert(fiber.stack.back().type == boost::typeindex::type_id()); + + w2c_ruby_rb_wasm_set_stack_pointer(&bind.instance(), fiber.stack.back().ptr + sizeof(T)); + fiber.stack.pop_back(); + } + + --fiber.stack_ptr; + + if (fiber.stack.empty()) { + bind.fibers.erase(fiber.key); + } + } + + inline T *get() const noexcept { + return (T *)(*bind + ptr); + } + + inline T &operator()() const noexcept { + return *get(); + } + }; + + template struct stack_frame_guard bind() { + return *this; + } + }; +} + +#undef SERIALIZE_32 +#undef SERIALIZE_64 +#undef SERIALIZE_VALUE + +#endif // MKXPZ_SANDBOX_BINDING_BASE diff --git a/binding-sandbox/sandbox.h b/binding-sandbox/sandbox.h index e1b3c524..19b9957d 100644 --- a/binding-sandbox/sandbox.h +++ b/binding-sandbox/sandbox.h @@ -27,7 +27,7 @@ #include #include "types.h" -#define SANDBOX_COROUTINE(name, definition) struct name : boost::asio::coroutine { BOOST_TYPE_INDEX_REGISTER_CLASS inline name(struct mkxp_sandbox::bindings &bind) {} definition }; +#define SANDBOX_COROUTINE(name, definition) struct name : boost::asio::coroutine { BOOST_TYPE_INDEX_REGISTER_CLASS inline name(struct mkxp_sandbox::binding_base &bind) {} definition }; #define SANDBOX_AWAIT(coroutine, ...) \ do { \ diff --git a/binding-sandbox/types.h b/binding-sandbox/types.h index 2ea4151d..6872ed8d 100644 --- a/binding-sandbox/types.h +++ b/binding-sandbox/types.h @@ -24,8 +24,20 @@ #include -// WebAssembly pointers are currently 32-bit integers, but this may change if we decide to switch to 64-bit WebAssembly in the future! We define a pointer-sized integer here to make it easier to transition to different pointer sizes later. +#ifdef MKXPZ_RETRO_MEMORY64 +#define usize u64 +typedef int64_t wasm_ssize_t; +typedef uint64_t wasm_size_t; +#else #define usize u32 +typedef int32_t wasm_ssize_t; +typedef uint32_t wasm_size_t; +#endif + +#define ANYARGS ... +typedef wasm_size_t wasm_ptr_t; +typedef wasm_size_t VALUE; +typedef wasm_size_t ID; #ifndef WASM_RT_CORE_TYPES_DEFINED #define WASM_RT_CORE_TYPES_DEFINED diff --git a/meson.build b/meson.build index f02764c3..f58e0cee 100644 --- a/meson.build +++ b/meson.build @@ -263,6 +263,7 @@ if get_option('retro') == true 'src/etc/table.cpp', 'src/filesystem/filesystem.cpp', 'src/input/input-retro.cpp', + 'binding-sandbox/binding-base.cpp', 'binding-sandbox/binding-util.cpp', 'binding-sandbox/sandbox.cpp', 'binding-sandbox/wasi.cpp', diff --git a/retro/sandbox-bindgen.rb b/retro/sandbox-bindgen.rb index c91efd1a..ba5069ee 100644 --- a/retro/sandbox-bindgen.rb +++ b/retro/sandbox-bindgen.rb @@ -19,31 +19,15 @@ ################################################################################ -# True if generating bindings for 64-bit WebAssembly, false if generating bindings for 32-bit WebAssembly -MEMORY64 = false - # The name passed as the `-n`/`--module-name` flag to `wasm2c` MODULE_NAME = 'ruby' -# Include directive for including the header file generated by `wasm2c` -MODULE_INCLUDE = '#include ' - # The name of the `malloc()` binding defined in ruby-bindings.h MALLOC_FUNC = 'mkxp_sandbox_malloc' # The name of the `free()` binding defined in ruby-bindings.h FREE_FUNC = 'mkxp_sandbox_free' -RTYPEDDATA_DATA_OFFSET = 'mkxp_sandbox_rtypeddata_data_offset' -RTYPEDDATA_DMARK_FUNC = 'mkxp_sandbox_rtypeddata_dmark' -RTYPEDDATA_DFREE_FUNC = 'mkxp_sandbox_rtypeddata_dfree' -RTYPEDDATA_DSIZE_FUNC = 'mkxp_sandbox_rtypeddata_dsize' -RTYPEDDATA_DCOMPACT_FUNC = 'mkxp_sandbox_rtypeddata_dcompact' - -FIBER_ENTRY_POINT = 'mkxp_sandbox_fiber_entry_point' -FIBER_ARG0 = 'mkxp_sandbox_fiber_arg0' -FIBER_ARG1 = 'mkxp_sandbox_fiber_arg1' - ################################################################################ IGNORED_FUNCTIONS = Set[ @@ -65,21 +49,21 @@ ARG_HANDLERS = { 'const char *' => { keep: true, buf_size: 'std::strlen(ARG) + 1', - serialize: "std::strcpy((char *)(bind.instance->w2c_memory.data + BUF), ARG);\n", + serialize: "std::strcpy((char *)(*bind + BUF), ARG);\n", }, 'const VALUE *' => { keep: true, condition: lambda { |func_name, args, arg_index| arg_index > 0 && args[arg_index - 1] == 'int' }, # Only handle arguments of type `const VALUE *` if the previous argument is of type `int` buf_size: 'PREV_ARG * sizeof(VALUE)', serialize: <<~HEREDOC - std::memcpy(bind.instance->w2c_memory.data + BUF, ARG, PREV_ARG * sizeof(VALUE)); + std::memcpy(*bind + BUF, ARG, PREV_ARG * sizeof(VALUE)); HEREDOC }, 'volatile VALUE *' => { keep: true, buf_size: 'sizeof(VALUE)', serialize: <<~HEREDOC - *(VALUE *)(bind.instance->w2c_memory.data + BUF) = *ARG; + *(VALUE *)(*bind + BUF) = *ARG; HEREDOC }, 'void *' => { @@ -148,10 +132,10 @@ VAR_TYPE_TABLE = { } FUNC_TYPE_TABLE = { - ssize: MEMORY64 ? 'WASM_RT_I64' : 'WASM_RT_I32', - size: MEMORY64 ? 'WASM_RT_I64' : 'WASM_RT_I32', - value: MEMORY64 ? 'WASM_RT_I64' : 'WASM_RT_I32', - ptr: MEMORY64 ? 'WASM_RT_I64' : 'WASM_RT_I32', + ssize: 'WASM_RT_ISIZE', + size: 'WASM_RT_ISIZE', + value: 'WASM_RT_ISIZE', + ptr: 'WASM_RT_ISIZE', s32: 'WASM_RT_I32', u32: 'WASM_RT_I32', s64: 'WASM_RT_I64', @@ -210,210 +194,39 @@ HEADER_START = <<~HEREDOC #ifndef MKXP_SANDBOX_BINDGEN_H #define MKXP_SANDBOX_BINDGEN_H - #include - #include - #include - #include - #include - #include - #include - #include - #include - #{MODULE_INCLUDE} - #include "binding-sandbox/types.h" + #include "binding-sandbox/binding-base.h" // Autogenerated by sandbox-bindgen.rb. Don't manually modify this file - modify sandbox-bindgen.rb instead! #if WABT_BIG_ENDIAN - #define SERIALIZE_32(value) __builtin_bswap32(value) - #define SERIALIZE_64(value) __builtin_bswap64(value) + # define SERIALIZE_32(value) __builtin_bswap32(value) + # define SERIALIZE_64(value) __builtin_bswap64(value) #else - #define SERIALIZE_32(value) (value) - #define SERIALIZE_64(value) (value) + # define SERIALIZE_32(value) (value) + # define SERIALIZE_64(value) (value) #endif - #define SERIALIZE_VALUE(value) SERIALIZE_#{MEMORY64 ? '64' : '32'}(value) - #define ANYARGS ... - typedef int#{MEMORY64 ? '64' : '32'}_t wasm_ssize_t; - typedef uint#{MEMORY64 ? '64' : '32'}_t wasm_size_t; - typedef wasm_size_t wasm_ptr_t; - typedef wasm_size_t VALUE; - typedef wasm_size_t ID; + #ifdef MKXPZ_RETRO_MEMORY64 + # define SERIALIZE_VALUE(value) SERIALIZE_64(value) + #else + # define SERIALIZE_VALUE(value) SERIALIZE_32(value) + #endif namespace mkxp_sandbox { - struct bindings { - private: - - typedef std::tuple key_t; - - struct stack_frame { - struct bindings &bind; - void (*destructor)(void *ptr); - boost::typeindex::type_index type; - wasm_ptr_t ptr; - inline stack_frame(struct bindings &bind, void (*destructor)(void *ptr), boost::typeindex::type_index type, wasm_ptr_t ptr) : bind(bind), destructor(destructor), type(type), ptr(ptr) {} - inline ~stack_frame() { - destructor(bind.instance->w2c_memory.data + ptr); - } - }; - - struct fiber { - key_t key; - std::vector stack; - size_t stack_ptr; - }; - - wasm_ptr_t next_func_ptr; - std::shared_ptr instance; - std::unordered_map> fibers; - wasm_ptr_t sandbox_create_func_ptr(); - - public: - - inline bindings(std::shared_ptr m) : next_func_ptr(-1), instance(m) {} - - inline ~bindings() { - // Destroy all stack frames in order from top to bottom to enforce a portable, compiler-independent ordering of stack frame destruction - // If we let the compiler use its default destructor, the stack frames may not be deallocated in a particular order, which can lead to hard-to-detect bugs if somehow a bug depends on the order in which the stack frames are deallocated - for (auto &it : fibers) { - while (!it.second.stack.empty()) { - it.second.stack.pop_back(); - } - } - } - - wasm_ptr_t sandbox_malloc(wasm_size_t); - - inline void sandbox_free(wasm_ptr_t ptr) { w2c_#{MODULE_NAME}_#{FREE_FUNC}(instance.get(), ptr); } - - inline uint8_t *get() const noexcept { return instance->w2c_memory.data; } - - inline uint8_t *operator*() const noexcept { return get(); } - - inline wasm_ptr_t rtypeddata_data(VALUE obj) const noexcept { return SERIALIZE_VALUE(obj) + *(wasm_size_t *)(instance->w2c_memory.data + instance->w2c_#{RTYPEDDATA_DATA_OFFSET}); } - - inline void rtypeddata_dmark(wasm_ptr_t data, wasm_ptr_t ptr) { w2c_#{MODULE_NAME}_#{RTYPEDDATA_DMARK_FUNC}(instance.get(), data, ptr); } - - inline void rtypeddata_dfree(wasm_ptr_t data, wasm_ptr_t ptr) { w2c_#{MODULE_NAME}_#{RTYPEDDATA_DFREE_FUNC}(instance.get(), data, ptr); } - - inline wasm_size_t rtypeddata_dsize(wasm_ptr_t data, wasm_ptr_t ptr) { return w2c_#{MODULE_NAME}_#{RTYPEDDATA_DSIZE_FUNC}(instance.get(), data, ptr); } - - inline void rtypeddata_dcompact(wasm_ptr_t data, wasm_ptr_t ptr) { w2c_#{MODULE_NAME}_#{RTYPEDDATA_DCOMPACT_FUNC}(instance.get(), data, ptr); } + struct bindings : binding_base { + bindings(std::shared_ptr m); struct rb_data_type { friend struct bindings; - inline rb_data_type() : ptr(0) {} - inline wasm_ptr_t get() const { - if (ptr == 0) throw SandboxTrapException(); - return ptr; - } + rb_data_type(); + wasm_ptr_t get() const; private: wasm_ptr_t ptr; - inline rb_data_type(wasm_ptr_t ptr) : ptr(ptr) {} + rb_data_type(wasm_ptr_t ptr); }; struct rb_data_type rb_data_type(const char *wrap_struct_name, void (*dmark)(wasm_ptr_t), void (*dfree)(wasm_ptr_t), wasm_size_t (*dsize)(wasm_ptr_t), void (*dcompact)(wasm_ptr_t), wasm_ptr_t parent, wasm_ptr_t data, wasm_size_t flags); - template struct stack_frame_guard { - friend struct bindings; - - private: - - struct bindings &bind; - struct fiber &fiber; - wasm_ptr_t ptr; - - static void stack_frame_destructor(void *ptr) { - ((T *)ptr)->~T(); - } - - static inline struct fiber &init_fiber(struct bindings &bind) { - key_t key = { - *(wasm_ptr_t *)(bind.instance->w2c_memory.data + bind.instance->w2c_#{FIBER_ENTRY_POINT}), - *(wasm_ptr_t *)(bind.instance->w2c_memory.data + bind.instance->w2c_#{FIBER_ARG0}), - *(wasm_ptr_t *)(bind.instance->w2c_memory.data + bind.instance->w2c_#{FIBER_ARG1}), - }; - if (bind.fibers.count(key) == 0) { - bind.fibers[key] = (struct fiber){.key = key}; - } - return bind.fibers[key]; - } - - static wasm_ptr_t init_inner(struct bindings &bind, struct fiber &fiber) { - wasm_ptr_t sp = w2c_#{MODULE_NAME}_rb_wasm_get_stack_pointer(bind.instance.get()); - - if (fiber.stack_ptr == fiber.stack.size()) { - fiber.stack.emplace_back( - bind, - stack_frame_destructor, - boost::typeindex::type_id(), - (sp -= sizeof(T)) - ); - assert(sp % sizeof(VALUE) == 0); - new(bind.instance->w2c_memory.data + sp) T(bind); - } else if (fiber.stack_ptr > fiber.stack.size()) { - throw SandboxTrapException(); - } - - if (fiber.stack[fiber.stack_ptr].type == boost::typeindex::type_id()) { - w2c_#{MODULE_NAME}_rb_wasm_set_stack_pointer(bind.instance.get(), sp); - return fiber.stack[fiber.stack_ptr++].ptr; - } else { - while (fiber.stack.size() > fiber.stack_ptr) { - fiber.stack.pop_back(); - } - ++fiber.stack_ptr; - fiber.stack.emplace_back( - bind, - stack_frame_destructor, - boost::typeindex::type_id(), - (sp -= sizeof(T)) - ); - assert(sp % sizeof(VALUE) == 0); - new(bind.instance->w2c_memory.data + sp) T(bind); - w2c_#{MODULE_NAME}_rb_wasm_set_stack_pointer(bind.instance.get(), sp); - return sp; - } - } - - stack_frame_guard(struct bindings &b) : bind(b), fiber(init_fiber(b)), ptr(init_inner(b, fiber)) {} - - public: - - ~stack_frame_guard() { - if (get()->is_complete()) { - while (fiber.stack.size() > fiber.stack_ptr) { - fiber.stack.pop_back(); - } - - // Check for stack corruptions - assert(fiber.stack.size() == fiber.stack_ptr); - assert(fiber.stack.back().type == boost::typeindex::type_id()); - - w2c_#{MODULE_NAME}_rb_wasm_set_stack_pointer(bind.instance.get(), fiber.stack.back().ptr + sizeof(T)); - fiber.stack.pop_back(); - } - - --fiber.stack_ptr; - - if (fiber.stack.empty()) { - bind.fibers.erase(fiber.key); - } - } - - inline T *get() const noexcept { - return (T *)(bind.instance->w2c_memory.data + ptr); - } - - inline T &operator()() const noexcept { - return *get(); - } - }; - - template struct stack_frame_guard bind() { - return *this; - } - HEREDOC HEADER_END = <<~HEREDOC @@ -456,63 +269,33 @@ PRELUDE = <<~HEREDOC static_assert(alignof(VALUE) % sizeof(VALUE) == 0, "Alignment of `VALUE` must be divisible by size of `VALUE` for Ruby garbage collection to work. If you compiled Ruby for wasm64, try compiling it for wasm32 instead."); #if WABT_BIG_ENDIAN - #define SERIALIZE_32(value) __builtin_bswap32(value) - #define SERIALIZE_64(value) __builtin_bswap64(value) + # define SERIALIZE_32(value) __builtin_bswap32(value) + # define SERIALIZE_64(value) __builtin_bswap64(value) #else - #define SERIALIZE_32(value) (value) - #define SERIALIZE_64(value) (value) + # define SERIALIZE_32(value) (value) + # define SERIALIZE_64(value) (value) #endif - #define SERIALIZE_VALUE(value) SERIALIZE_#{MEMORY64 ? '64' : '32'}(value) + #ifdef MKXPZ_RETRO_MEMORY64 + # define SERIALIZE_VALUE(value) SERIALIZE_64(value) + # define WASM_RT_ISIZE WASM_RT_I64 + #else + # define SERIALIZE_VALUE(value) SERIALIZE_32(value) + # define WASM_RT_ISIZE WASM_RT_I32 + #endif using namespace mkxp_sandbox; + bindings::rb_data_type::rb_data_type() : ptr(0) {} - wasm_ptr_t bindings::sandbox_malloc(wasm_size_t size) { - wasm_ptr_t buf = w2c_#{MODULE_NAME}_#{MALLOC_FUNC}(instance.get(), size); + bindings::rb_data_type::rb_data_type(wasm_ptr_t ptr) : ptr(ptr) {} - // Verify that the entire allocated buffer is in valid memory - wasm_ptr_t buf_end; - if (buf == 0 || __builtin_add_overflow(buf, size, &buf_end) || buf_end >= instance->w2c_memory.size) { - return 0; - } - - return buf; + wasm_ptr_t bindings::rb_data_type::get() const { + if (ptr == 0) throw SandboxTrapException(); + return ptr; } - - wasm_ptr_t bindings::sandbox_create_func_ptr() { - if (next_func_ptr == (wasm_ptr_t)-1) { - next_func_ptr = instance->w2c_T0.size; - } - - if (next_func_ptr < instance->w2c_T0.max_size) { - return next_func_ptr++; - } - - // Make sure that an integer overflow won't occur if we double the max size of the funcref table - wasm_size_t new_max_size; - if (__builtin_add_overflow(instance->w2c_T0.max_size, instance->w2c_T0.max_size, &new_max_size)) { - return -1; - } - - // Double the max size of the funcref table - wasm_size_t old_max_size = instance->w2c_T0.max_size; - instance->w2c_T0.max_size = new_max_size; - - // Double the size of the funcref table buffer - if (wasm_rt_grow_funcref_table(&instance->w2c_T0, old_max_size, wasm_rt_funcref_t { - .func_type = wasm2c_ruby_get_func_type(0, 0), - .func = NULL, - .func_tailcallee = {.fn = NULL}, - .module_instance = instance.get(), - }) != old_max_size) { - instance->w2c_T0.max_size = old_max_size; - return -1; - } - - return next_func_ptr++; - } + bindings::bindings(std::shared_ptr m) : binding_base(m) {} ////////////////////////////////////////////////////////////////////////////// @@ -520,10 +303,6 @@ HEREDOC POSTSCRIPT = <<~HEREDOC - - ////////////////////////////////////////////////////////////////////////////// - - struct bindings::rb_data_type bindings::rb_data_type(const char *wrap_struct_name, void (*dmark)(wasm_ptr_t), void (*dfree)(wasm_ptr_t), wasm_size_t (*dsize)(wasm_ptr_t), void (*dcompact)(wasm_ptr_t), wasm_ptr_t parent, wasm_ptr_t data, wasm_size_t flags) { wasm_ptr_t ptrs[6] = {0}; bool oom = false; @@ -547,10 +326,10 @@ POSTSCRIPT = <<~HEREDOC throw SandboxOutOfMemoryException(); } - std::strcpy((char *)(instance->w2c_memory.data + ptrs[1]), wrap_struct_name); + std::strcpy((char *)(**this + ptrs[1]), wrap_struct_name); if (dmark != NULL) { - instance->w2c_T0.data[ptrs[2]] = wasm_rt_funcref_t { + instance().w2c_T0.data[ptrs[2]] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(1, 0, #{FUNC_TYPE_TABLE[:ptr]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:void, [:value]])}, .func_tailcallee = {.fn = NULL}, @@ -559,7 +338,7 @@ POSTSCRIPT = <<~HEREDOC } if (dfree != NULL) { - instance->w2c_T0.data[ptrs[3]] = wasm_rt_funcref_t { + instance().w2c_T0.data[ptrs[3]] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(1, 0, #{FUNC_TYPE_TABLE[:ptr]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:void, [:value]])}, .func_tailcallee = {.fn = NULL}, @@ -568,7 +347,7 @@ POSTSCRIPT = <<~HEREDOC } if (dsize != NULL) { - instance->w2c_T0.data[ptrs[4]] = wasm_rt_funcref_t { + instance().w2c_T0.data[ptrs[4]] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(1, 1, #{FUNC_TYPE_TABLE[:ptr]}, #{FUNC_TYPE_TABLE[:size]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:size, [:value]])}, .func_tailcallee = {.fn = NULL}, @@ -577,7 +356,7 @@ POSTSCRIPT = <<~HEREDOC } if (dcompact != NULL) { - instance->w2c_T0.data[ptrs[5]] = wasm_rt_funcref_t { + instance().w2c_T0.data[ptrs[5]] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(1, 0, #{FUNC_TYPE_TABLE[:ptr]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:void, [:value]])}, .func_tailcallee = {.fn = NULL}, @@ -586,15 +365,19 @@ POSTSCRIPT = <<~HEREDOC } for (size_t i = 1; i < 6; ++i) { - ((wasm_ptr_t *)(instance->w2c_memory.data + ptrs[0]))[i - 1] = SERIALIZE_VALUE(ptrs[i]); + ((wasm_ptr_t *)(**this + ptrs[0]))[i - 1] = SERIALIZE_VALUE(ptrs[i]); } - ((wasm_ptr_t *)(instance->w2c_memory.data + ptrs[0]))[5] = 0; - ((wasm_ptr_t *)(instance->w2c_memory.data + ptrs[0]))[6] = SERIALIZE_VALUE(parent); - ((wasm_ptr_t *)(instance->w2c_memory.data + ptrs[0]))[7] = SERIALIZE_VALUE(data); - ((wasm_ptr_t *)(instance->w2c_memory.data + ptrs[0]))[8] = SERIALIZE_VALUE(flags); + ((wasm_ptr_t *)(**this + ptrs[0]))[5] = 0; + ((wasm_ptr_t *)(**this + ptrs[0]))[6] = SERIALIZE_VALUE(parent); + ((wasm_ptr_t *)(**this + ptrs[0]))[7] = SERIALIZE_VALUE(data); + ((wasm_ptr_t *)(**this + ptrs[0]))[8] = SERIALIZE_VALUE(flags); return ptrs[0]; } + + ////////////////////////////////////////////////////////////////////////////// + + HEREDOC ################################################################################ @@ -686,7 +469,7 @@ File.readlines('tags', chomp: true).each do |line| coroutine_initializer += <<~HEREDOC switch (a#{args.length - 1}) { case -1: - bind.instance->w2c_T0.data[f#{i}] = wasm_rt_funcref_t { + bind.instance().w2c_T0.data[f#{i}] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(3, 1, #{FUNC_TYPE_TABLE[:s32]}, #{FUNC_TYPE_TABLE[:ptr]}, #{FUNC_TYPE_TABLE[:value]}, #{FUNC_TYPE_TABLE[:value]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:value, [:s32, :ptr, :value]])}, .func_tailcallee = {.fn = NULL}, @@ -694,7 +477,7 @@ File.readlines('tags', chomp: true).each do |line| }; break; case -2: - bind.instance->w2c_T0.data[f#{i}] = wasm_rt_funcref_t { + bind.instance().w2c_T0.data[f#{i}] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(2, 1, #{FUNC_TYPE_TABLE[:value]}, #{FUNC_TYPE_TABLE[:value]}, #{FUNC_TYPE_TABLE[:value]}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:value, [:value, :value]])}, .func_tailcallee = {.fn = NULL}, @@ -705,7 +488,7 @@ File.readlines('tags', chomp: true).each do |line| for j in 0..16 case_str = <<~HEREDOC case #{j}: - bind.instance->w2c_T0.data[f#{i}] = wasm_rt_funcref_t { + bind.instance().w2c_T0.data[f#{i}] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(#{j + 1}, 1, #{([FUNC_TYPE_TABLE[:value]] * (j + 2)).join(', ')}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([:value, [:value] * (j + 1)])}, .func_tailcallee = {.fn = NULL}, @@ -722,7 +505,7 @@ File.readlines('tags', chomp: true).each do |line| HEREDOC else coroutine_initializer += <<~HEREDOC - bind.instance->w2c_T0.data[f#{i}] = wasm_rt_funcref_t { + bind.instance().w2c_T0.data[f#{i}] = wasm_rt_funcref_t { .func_type = wasm2c_#{MODULE_NAME}_get_func_type(#{handler[:func_ptr_args].length}, #{handler[:func_ptr_rets].length}#{handler[:func_ptr_args].empty? && handler[:func_ptr_rets].empty? ? '' : ', ' + (handler[:func_ptr_args] + handler[:func_ptr_rets]).map { |type| FUNC_TYPE_TABLE[type] }.join(', ')}), .func = (wasm_rt_function_ptr_t)_sbindgen_call_#{call_type_hash([handler[:func_ptr_rets].empty? ? :void : handler[:func_ptr_rets][0], handler[:func_ptr_args]])}, .func_tailcallee = {.fn = NULL}, @@ -758,7 +541,7 @@ File.readlines('tags', chomp: true).each do |line| std::va_list a; va_start(a, a#{args.length - 2}); for (long i = 0; i < a#{args.length - 2}; ++i) { - ((VALUE *)(bind.instance->w2c_memory.data + f#{args.length - 1}))[i] = va_arg(a, VALUE); + ((VALUE *)(*bind + f#{args.length - 1}))[i] = va_arg(a, VALUE); } va_end(a); HEREDOC @@ -779,7 +562,7 @@ File.readlines('tags', chomp: true).each do |line| throw SandboxOutOfMemoryException(); } for (wasm_size_t i = 0; i < n; ++i) { - ((VALUE *)(bind.instance->w2c_memory.data + f#{args.length - 1}))[i] = va_arg(a, VALUE); + ((VALUE *)(*bind + f#{args.length - 1}))[i] = va_arg(a, VALUE); } HEREDOC coroutine_initializer += "\n" @@ -814,8 +597,8 @@ File.readlines('tags', chomp: true).each do |line| end coroutine_inner = <<~HEREDOC - #{handler[:primitive] == :void ? '' : 'r = '}w2c_#{MODULE_NAME}_#{func_name}(#{(['bind.instance.get()'] + (0...args.length).map { |i| args[i] == '...' || transformed_args.include?(i) ? "f#{i}" : args[i] == 'VALUE' ? "SERIALIZE_VALUE(a#{i})" : args[i] == 'const rb_data_type_t *' ? "a#{i}.get()" : "a#{i}" }).join(', ')}); - if (w2c_#{MODULE_NAME}_asyncify_get_state(bind.instance.get()) != 1) break; + #{handler[:primitive] == :void ? '' : 'r = '}w2c_#{MODULE_NAME}_#{func_name}(#{(['&bind.instance()'] + (0...args.length).map { |i| args[i] == '...' || transformed_args.include?(i) ? "f#{i}" : args[i] == 'VALUE' ? "SERIALIZE_VALUE(a#{i})" : args[i] == 'const rb_data_type_t *' ? "a#{i}.get()" : "a#{i}" }).join(', ')}); + if (w2c_#{MODULE_NAME}_asyncify_get_state(&bind.instance()) != 1) break; BOOST_ASIO_CORO_YIELD; HEREDOC @@ -826,6 +609,7 @@ File.readlines('tags', chomp: true).each do |line| HEREDOC coroutine_definition = <<~HEREDOC + #{func_name}::#{func_name}(struct binding_base &b) : #{(['bind(b)'] + buffers.map { |buffer| "#{buffer}(0)" }).join(', ')} {} #{coroutine_ret} #{func_name}::operator()(#{coroutine_args.join(', ')}) {#{coroutine_vars.empty? ? '' : (coroutine_vars.map { |var| "\n #{var} = 0;" }.join + "\n")} BOOST_ASIO_CORO_REENTER (this) { #{coroutine_initializer.empty? ? '' : (coroutine_initializer.split("\n").map { |line| " #{line}".rstrip }.join("\n") + "\n\n")} for (;;) { @@ -837,13 +621,12 @@ File.readlines('tags', chomp: true).each do |line| coroutine_declaration = <<~HEREDOC struct #{func_name} : boost::asio::coroutine { - friend struct bindings; friend struct bindings::stack_frame_guard; BOOST_TYPE_INDEX_REGISTER_CLASS #{coroutine_ret} operator()(#{declaration_args.join(', ')}); #{coroutine_destructor.empty? ? '' : "~#{func_name}();\n "}private: - struct bindings &bind; - inline #{func_name}(struct bindings &b) : #{(['bind(b)'] + buffers.map { |buffer| "#{buffer}(0)" }).join(', ')} {} + struct binding_base &bind; + #{func_name}(struct binding_base &b); #{fields.empty? ? '' : fields.map { |field| " #{field};\n" }.join}}; HEREDOC @@ -855,10 +638,7 @@ end File.open('mkxp-sandbox-bindgen.h', 'w') do |file| file.write(HEADER_START) for global_name in globals - file.write(" inline VALUE #{global_name}() const noexcept { return *(VALUE *)(instance->w2c_memory.data + instance->w2c_#{global_name}); }\n") - end - for func_name in func_names - file.write(" friend struct #{func_name};\n") + file.write(" inline VALUE #{global_name}() const noexcept { return *(VALUE *)(**this + instance().w2c_#{global_name}); }\n") end file.write(" };") for declaration in declarations @@ -866,12 +646,18 @@ File.open('mkxp-sandbox-bindgen.h', 'w') do |file| end file.write("\n\n") file.write("#if WABT_BIG_ENDIAN\n") + file.write("# ifdef MKXPZ_RETRO_MEMORY64\n") for const in consts - file.write("#define SANDBOX_#{const[0]} 0x#{[const[1]].pack(MEMORY64 ? 'Q<' : 'L<').unpack('H*')[0]}u\n") + file.write("# define SANDBOX_#{const[0]} 0x#{[const[1]].pack('Q<').unpack('H*')[0]}u\n") end + file.write("# else\n") + for const in consts + file.write("# define SANDBOX_#{const[0]} 0x#{[const[1]].pack('L<').unpack('H*')[0]}u\n") + end + file.write("# endif\n") file.write("#else\n") for const in consts - file.write("#define SANDBOX_#{const[0]} 0x#{[const[1]].pack(MEMORY64 ? 'Q>' : 'L>').unpack('H*')[0]}u\n") + file.write("# define SANDBOX_#{const[0]} 0x#{[const[1]].pack('L>').unpack('H*')[0]}u\n") end file.write("#endif\n") file.write(HEADER_END) diff --git a/src/core.cpp b/src/core.cpp index b3019de9..2793f526 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -30,9 +30,9 @@ #include #include #include "git-hash.h" -#include "../binding-sandbox/sandbox.h" -#include "../binding-sandbox/binding-sandbox.h" -#include "../binding-sandbox/core.h" +#include "binding-sandbox/sandbox.h" +#include "binding-sandbox/binding-sandbox.h" +#include "binding-sandbox/core.h" #include "filesystem.h" using namespace mkxp_retro;