/* ** 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 "types.h" #ifdef MKXPZ_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 // LLVM uses a stack alignment of 16 on WebAssembly targets #define WASMSTACKALIGN 16 // Rounds a number up to the nearest multiple of the WebAssembly stack alignment #define CEIL_WASMSTACKALIGN(x) (((x) + (size_t)(WASMSTACKALIGN - 1)) & ~(size_t)(WASMSTACKALIGN - 1)) // Same as `sizeof(T)`, but rounds the result up to the nearest multiple of the WebAssembly stack alignment #define SIZEOF_WASMSTACKALIGN(T) CEIL_WASMSTACKALIGN(sizeof(T)) 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 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) { uint32_t state = w2c_ruby_asyncify_get_state(&bind.instance()); if (fiber.stack_ptr > fiber.stack.size()) { std::abort(); } // If Asyncify is rewinding, restore the stack frame from before Asyncify started unwinding if (state == 2) { if (fiber.stack_ptr == fiber.stack.size()) { std::abort(); } assert(fiber.stack[fiber.stack_ptr].type == boost::typeindex::type_id()); return fiber.stack[fiber.stack_ptr++].ptr; } // Otherwise, create a new stack frame assert(state == 0); while (fiber.stack.size() > fiber.stack_ptr) { fiber.stack.pop_back(); } ++fiber.stack_ptr; wasm_ptr_t sp = w2c_ruby_rb_wasm_get_stack_pointer(&bind.instance()) - SIZEOF_WASMSTACKALIGN(T); fiber.stack.emplace_back( bind, stack_frame_destructor, boost::typeindex::type_id(), sp ); assert(sp % sizeof(VALUE) == 0); assert(sp % WASMSTACKALIGN == 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_WASMSTACKALIGN(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