Refactor the non-autogenerated parts of sandbox-bindgen into separate files

This commit is contained in:
刘皓 2025-02-05 01:11:23 -05:00
parent f1ad41814a
commit 1c4d65e02e
No known key found for this signature in database
GPG key ID: 7901753DB465B711
7 changed files with 426 additions and 292 deletions

View file

@ -0,0 +1,137 @@
/*
** binding-base.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
**
** 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 <http://www.gnu.org/licenses/>.
*/
#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<struct w2c_ruby> 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);
}

View file

@ -0,0 +1,198 @@
/*
** binding-base.h
**
** This file is part of mkxp.
**
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
**
** 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 <http://www.gnu.org/licenses/>.
*/
#ifndef MKXPZ_SANDBOX_BINDING_BASE_H
#define MKXPZ_SANDBOX_BINDING_BASE_H
#include <cassert>
#include <cstdint>
#include <cstring>
#include <memory>
#include <unordered_map>
#include <vector>
#include <boost/container_hash/hash.hpp>
#include <boost/type_index.hpp>
#include <boost/asio/coroutine.hpp>
#include <mkxp-retro-ruby.h>
#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<wasm_ptr_t, wasm_ptr_t, wasm_ptr_t> 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<struct stack_frame> stack;
size_t stack_ptr;
};
wasm_ptr_t next_func_ptr;
std::shared_ptr<struct w2c_ruby> _instance;
std::unordered_map<key_t, struct fiber, boost::hash<key_t>> fibers;
public:
binding_base(std::shared_ptr<struct w2c_ruby> 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 <typename T> 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<T>(),
(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<T>()) {
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<T>(),
(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<T>());
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 <typename T> struct stack_frame_guard<T> bind() {
return *this;
}
};
}
#undef SERIALIZE_32
#undef SERIALIZE_64
#undef SERIALIZE_VALUE
#endif // MKXPZ_SANDBOX_BINDING_BASE

View file

@ -27,7 +27,7 @@
#include <mkxp-sandbox-bindgen.h>
#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 { \

View file

@ -24,8 +24,20 @@
#include <cstdint>
// 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

View file

@ -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',

View file

@ -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 <mkxp-retro-ruby.h>'
# 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 <cassert>
#include <cstdint>
#include <cstring>
#include <memory>
#include <unordered_map>
#include <vector>
#include <boost/container_hash/hash.hpp>
#include <boost/type_index.hpp>
#include <boost/asio/coroutine.hpp>
#{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<wasm_ptr_t, wasm_ptr_t, wasm_ptr_t> 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<struct stack_frame> stack;
size_t stack_ptr;
};
wasm_ptr_t next_func_ptr;
std::shared_ptr<struct w2c_#{MODULE_NAME}> instance;
std::unordered_map<key_t, struct fiber, boost::hash<key_t>> fibers;
wasm_ptr_t sandbox_create_func_ptr();
public:
inline bindings(std::shared_ptr<struct w2c_#{MODULE_NAME}> 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<struct w2c_#{MODULE_NAME}> 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 <typename T> 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<T>(),
(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<T>()) {
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<T>(),
(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<T>());
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 <typename T> struct stack_frame_guard<T> 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<struct w2c_#{MODULE_NAME}> 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<struct #{func_name}>;
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)

View file

@ -30,9 +30,9 @@
#include <fluidlite.h>
#include <fluidsynth_priv.h>
#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;