mkxp-z/libretro/ruby-bindings.h

179 lines
7.8 KiB
C

/*
** ruby-bindings.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/>.
*/
/* This file contains bindings that expose low-level functionality of the Ruby VM to the outside of the sandbox it's running in. They are used by sandbox-bindgen. */
#ifndef SANDBOX_RUBY_BINDINGS_H
#define SANDBOX_RUBY_BINDINGS_H
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include "wasm/asyncify.h"
#include "wasm/fiber.h"
#include "wasm/machine.h"
#include "wasm/setjmp.h"
#define MKXP_SANDBOX_API __attribute__((__visibility__("default")))
MKXP_SANDBOX_API struct __rb_wasm_asyncify_jmp_buf mkxp_sandbox_async_buf;
MKXP_SANDBOX_API void (*mkxp_sandbox_fiber_entry_point)(void *, void *) = NULL;
MKXP_SANDBOX_API void *mkxp_sandbox_fiber_arg0 = NULL;
MKXP_SANDBOX_API void *mkxp_sandbox_fiber_arg1 = NULL;
MKXP_SANDBOX_API char mkxp_sandbox_cwd[PATH_MAX] = {0};
/* This function should be called immediately after initializing the sandbox to perform initialization, before calling any other functions.
* The arguments to this function are the Ruby GC parameters.
* Each one can be set to 0 to leave the corresponding GC parameters at the default value, or to anything else to set the parameter to the given value. */
MKXP_SANDBOX_API void mkxp_sandbox_init(size_t heap_free_slots, double growth_factor, size_t growth_max_slots, double heap_free_slots_min_ratio, double heap_free_slots_goal_ratio, double heap_free_slots_max_ratio, double uncollectible_wb_unprotected_objects_limit_ratio, double oldobject_limit_factor, size_t malloc_limit_min, size_t malloc_limit_max, double malloc_limit_growth_factor, size_t oldmalloc_limit_min, size_t oldmalloc_limit_max, double oldmalloc_limit_growth_factor) {
void __wasm_call_ctors(void); /* Defined by the LLVM linker */
__wasm_call_ctors();
void async_buf_init(struct __rb_wasm_asyncify_jmp_buf *); /* Defined in wasm/setjmp.c in Ruby source code */
async_buf_init(&mkxp_sandbox_async_buf);
if (heap_free_slots != 0) gc_params.heap_free_slots = heap_free_slots;
if (growth_factor != 0) gc_params.growth_factor = growth_factor;
if (growth_max_slots != 0) gc_params.growth_max_slots = growth_max_slots;
if (heap_free_slots_min_ratio != 0) gc_params.heap_free_slots_min_ratio = heap_free_slots_min_ratio;
if (heap_free_slots_goal_ratio != 0) gc_params.heap_free_slots_goal_ratio = heap_free_slots_goal_ratio;
if (heap_free_slots_max_ratio != 0) gc_params.heap_free_slots_max_ratio = heap_free_slots_max_ratio;
if (uncollectible_wb_unprotected_objects_limit_ratio != 0) gc_params.uncollectible_wb_unprotected_objects_limit_ratio = uncollectible_wb_unprotected_objects_limit_ratio;
if (oldobject_limit_factor != 0) gc_params.oldobject_limit_factor = oldobject_limit_factor;
if (malloc_limit_min != 0) gc_params.malloc_limit_min = malloc_limit_min;
if (malloc_limit_max != 0) gc_params.malloc_limit_max = malloc_limit_max;
if (malloc_limit_growth_factor != 0) gc_params.malloc_limit_growth_factor = malloc_limit_growth_factor;
if (oldmalloc_limit_min != 0) gc_params.oldmalloc_limit_min = oldmalloc_limit_min;
if (oldmalloc_limit_max != 0) gc_params.oldmalloc_limit_max = oldmalloc_limit_max;
if (oldmalloc_limit_growth_factor != 0) gc_params.oldmalloc_limit_growth_factor = oldmalloc_limit_growth_factor;
}
/* Exposes the `malloc()` function. */
MKXP_SANDBOX_API void *mkxp_sandbox_malloc(size_t size) {
return malloc(size);
}
/* Exposes the `free()` function. */
MKXP_SANDBOX_API void mkxp_sandbox_free(void *ptr) {
free(ptr);
}
/* The offset of the `data` field within a `struct RTypedData`. */
MKXP_SANDBOX_API size_t mkxp_sandbox_rtypeddata_data_offset = offsetof(struct RTypedData, data);
/* Calls the `dmark()` function from a `struct RTypedData *` on a given memory location. */
MKXP_SANDBOX_API void mkxp_sandbox_rtypeddata_dmark(struct RTypedData *data, void *ptr) {
if (data->type->function.dmark != NULL) {
data->type->function.dmark(ptr);
}
}
/* Calls the `dfree()` function from a `struct RTypedData *` on a given memory location. */
MKXP_SANDBOX_API void mkxp_sandbox_rtypeddata_dfree(struct RTypedData *data, void *ptr) {
if (data->type->function.dfree != NULL) {
data->type->function.dfree(ptr);
}
}
/* Calls the `dsize()` function from a `struct RTypedData *` on a given memory location. */
MKXP_SANDBOX_API size_t mkxp_sandbox_rtypeddata_dsize(struct RTypedData *data, const void *ptr) {
if (data->type->function.dsize != NULL) {
return data->type->function.dsize(ptr);
} else {
return 0;
}
}
/* Calls the `dcompact()` function from a `struct RTypedData *` on a given memory location. */
MKXP_SANDBOX_API void mkxp_sandbox_rtypeddata_dcompact(struct RTypedData *data, void *ptr) {
if (data->type->function.dcompact != NULL) {
data->type->function.dcompact(ptr);
}
}
/* Calls `chdir()` and returns whether or not the call succeeded. */
MKXP_SANDBOX_API bool mkxp_sandbox_chdir(const char *path) {
return chdir(path) == 0;
}
/* Calls `getcwd()` on `mkxp_sandbox_cwd` and returns whether or not the call succeeded. */
MKXP_SANDBOX_API bool mkxp_sandbox_getcwd(void) {
return getcwd(mkxp_sandbox_cwd, PATH_MAX) != NULL;
}
/* This function drives Ruby's asynchronous runtime. It's based on the `rb_wasm_rt_start()` function from wasm/runtime.c in the Ruby source code.
* After calling any function that starts with `rb_` or `ruby_` other than `ruby_sysinit()`, you need to call `mkxp_sandbox_yield()`.
* If `mkxp_sandbox_yield()` returns false, you may proceed as usual.
* However, if it returns true, then you need to call the `rb_`/`ruby_` function again with the same arguments
* and then call `mkxp_sandbox_yield()` again, and repeat until `mkxp_sandbox_yield()` returns false. */
MKXP_SANDBOX_API bool mkxp_sandbox_yield(void) {
static bool new_fiber_started = false;
void *asyncify_buf;
bool unwound = false;
extern void *rb_asyncify_unwind_buf; /* Defined in wasm/setjmp.c in Ruby source code */
while (1) {
if (unwound) {
if (mkxp_sandbox_fiber_entry_point != NULL) {
mkxp_sandbox_fiber_entry_point(mkxp_sandbox_fiber_arg0, mkxp_sandbox_fiber_arg1);
} else {
return true;
}
} else {
unwound = true;
}
if (rb_asyncify_unwind_buf == NULL) {
break;
}
asyncify_stop_unwind();
if ((asyncify_buf = rb_wasm_handle_jmp_unwind()) != NULL) {
asyncify_start_rewind(asyncify_buf);
continue;
}
if ((asyncify_buf = rb_wasm_handle_scan_unwind()) != NULL) {
asyncify_start_rewind(asyncify_buf);
continue;
}
asyncify_buf = rb_wasm_handle_fiber_unwind(&mkxp_sandbox_fiber_entry_point, &mkxp_sandbox_fiber_arg0, &mkxp_sandbox_fiber_arg1, &new_fiber_started);
if (asyncify_buf != NULL) {
asyncify_start_rewind(asyncify_buf);
continue;
} else if (new_fiber_started) {
continue;
}
break;
}
mkxp_sandbox_fiber_entry_point = NULL;
new_fiber_started = false;
return false;
}
#endif /* SANDBOX_RUBY_BINDINGS_H */