Add ffi/jitfn for JIT compilation.

Convert a byte sequence of machine code to an
an executable pointer that can be used with ffi/call.
This commit is contained in:
Calvin Rose 2022-12-03 11:26:23 -06:00
parent 0824f45e29
commit 7a3d055012
5 changed files with 107 additions and 2 deletions

View File

@ -77,6 +77,7 @@ conf.set('JANET_EV_NO_EPOLL', not get_option('epoll'))
conf.set('JANET_EV_NO_KQUEUE', not get_option('kqueue'))
conf.set('JANET_NO_INTERPRETER_INTERRUPT', not get_option('interpreter_interrupt'))
conf.set('JANET_NO_FFI', not get_option('ffi'))
conf.set('JANET_NO_FFI_JIT', not get_option('ffi_jit'))
if get_option('os_name') != ''
conf.set('JANET_OS_NAME', get_option('os_name'))
endif

View File

@ -20,6 +20,7 @@ option('epoll', type : 'boolean', value : false)
option('kqueue', type : 'boolean', value : false)
option('interpreter_interrupt', type : 'boolean', value : false)
option('ffi', type : 'boolean', value : true)
option('ffi_jit', type : 'boolean', value : true)
option('recursion_guard', type : 'integer', min : 10, max : 8000, value : 1024)
option('max_proto_depth', type : 'integer', min : 10, max : 8000, value : 200)

View File

@ -33,6 +33,8 @@
/* #define JANET_NO_SYMLINKS */
/* #define JANET_NO_UMASK */
/* #define JANET_NO_THREADS */
/* #define JANET_NO_FFI */
/* #define JANET_NO_FFI_JIT */
/* Other settings */
/* #define JANET_DEBUG */

View File

@ -37,6 +37,13 @@
#define alloca __builtin_alloca
#endif
/* FFI jit includes */
#ifdef JANET_FFI_JIT
#ifndef JANET_WINDOWS
#include <sys/mman.h>
#endif
#endif
#define JANET_FFI_MAX_RECUR 64
/* Compiler, OS, and arch detection. Used
@ -202,6 +209,11 @@ int struct_mark(void *p, size_t s) {
return 0;
}
typedef struct {
void *function_pointer;
size_t size;
} JanetFFIJittedFn;
static const JanetAbstractType janet_struct_type = {
"core/ffi-struct",
NULL,
@ -209,6 +221,35 @@ static const JanetAbstractType janet_struct_type = {
JANET_ATEND_GCMARK
};
static int janet_ffijit_gc(void *p, size_t s) {
(void) s;
JanetFFIJittedFn *fn = p;
if (fn->function_pointer == NULL) return 0;
#ifdef JANET_FFI_JIT
#ifdef JANET_WINDOWS
VirtualFree(fn->function_pointer, fn->size, MEM_RELEASE);
#else
munmap(fn->function_pointer, fn->size);
#endif
#endif
return 0;
}
static JanetByteView janet_ffijit_getbytes(void *p, size_t s) {
(void) s;
JanetFFIJittedFn *fn = p;
JanetByteView bytes;
bytes.bytes = fn->function_pointer;
bytes.len = fn->size;
return bytes;
}
const JanetAbstractType janet_type_ffijit = {
.name = "ffi/jitfn",
.gc = janet_ffijit_gc,
.bytes = janet_ffijit_getbytes
};
typedef struct {
Clib clib;
int closed;
@ -430,8 +471,10 @@ static void *janet_ffi_getpointer(const Janet *argv, int32_t n) {
case JANET_STRING:
case JANET_KEYWORD:
case JANET_SYMBOL:
case JANET_ABSTRACT:
case JANET_CFUNCTION:
return janet_unwrap_pointer(argv[n]);
case JANET_ABSTRACT:
return (void *) janet_getbytes(argv, n).bytes;
case JANET_BUFFER:
return janet_unwrap_buffer(argv[n])->data;
case JANET_FUNCTION:
@ -444,6 +487,19 @@ static void *janet_ffi_getpointer(const Janet *argv, int32_t n) {
}
}
static void *janet_ffi_get_callable_pointer(const Janet *argv, int32_t n) {
switch (janet_type(argv[n])) {
default:
break;
case JANET_POINTER:
return janet_unwrap_pointer(argv[n]);
case JANET_ABSTRACT:
if (!janet_checkabstract(argv[n], &janet_type_ffijit)) break;
return ((JanetFFIJittedFn *)janet_unwrap_abstract(argv[n]))->function_pointer;
}
janet_panicf("bad slot #%d, expected ffi callable pointer type, got %v", n, argv[n]);
}
/* Write a value given by some Janet values and an FFI type as it would appear in memory.
* The alignment and space available is assumed to already be sufficient */
static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFIType type, int recur) {
@ -1224,12 +1280,49 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
#endif
JANET_CORE_FN(cfun_ffi_jitfn,
"(ffi/jitfn bytes)",
"Create an abstract type that can be used as the pointer argument to `ffi/call`. The content "
"of `bytes` is architecture specific machine code that will be copied into executable memory.") {
janet_fixarity(argc, 1);
JanetByteView bytes = janet_getbytes(argv, 0);
#ifdef JANET_FFI_JIT
JanetFFIJittedFn *fn = janet_abstract_threaded(&janet_type_ffijit, sizeof(JanetFFIJittedFn));
fn->function_pointer = NULL;
fn->size = 0;
#ifdef JANET_WINDOWS
void *ptr = VirtualAlloc(NULL, bytes.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
void *ptr = mmap(0, bytes.len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#endif
if (!ptr) {
janet_panic("failed to memory map writable memory");
}
memcpy(ptr, bytes.bytes, bytes.len);
#ifdef JANET_WINDOWS
DWORD old = 0;
if (!VirtualProtect(ptr, fn->size, PAGE_EXECUTE_READ, &old)) {
janet_panic("failed to make mapped memory executable");
}
#else
if (mprotect(ptr, fn->size, PROT_READ | PROT_EXEC) == -1) {
janet_panic("failed to make mapped memory executable");
}
#endif
fn->size = (size_t) bytes.len;
fn->function_pointer = (JanetCFunction) ptr;
return janet_wrap_abstract(fn);
#else
janet_panic("ffi/jitfn not available on this platform");
#endif
}
JANET_CORE_FN(cfun_ffi_call,
"(ffi/call pointer signature & args)",
"Call a raw pointer as a function pointer. The function signature specifies "
"how Janet values in `args` are converted to native machine types.") {
janet_arity(argc, 2, -1);
void *function_pointer = janet_getpointer(argv, 0);
void *function_pointer = janet_ffi_get_callable_pointer(argv, 0);
JanetFFISignature *signature = janet_getabstract(argv, 1, &janet_signature_type);
janet_fixarity(argc - 2, signature->arg_count);
switch (signature->cc) {
@ -1364,6 +1457,7 @@ void janet_lib_ffi(JanetTable *env) {
JANET_CORE_REG("ffi/size", cfun_ffi_size),
JANET_CORE_REG("ffi/align", cfun_ffi_align),
JANET_CORE_REG("ffi/trampoline", cfun_ffi_get_callback_trampoline),
JANET_CORE_REG("ffi/jitfn", cfun_ffi_jitfn),
JANET_REG_END
};
janet_core_cfuns_ext(env, NULL, ffi_cfuns);

View File

@ -171,6 +171,13 @@ extern "C" {
#endif
#endif
/* If FFI is enabled and FFI-JIT is not disabled... */
#ifdef JANET_FFI
#ifndef JANET_NO_FFI_JIT
#define JANET_FFI_JIT
#endif
#endif
/* Enable or disable the assembler. Enabled by default. */
#ifndef JANET_NO_ASSEMBLER
#define JANET_ASSEMBLER