Add ffi jit example.

This commit is contained in:
Calvin Rose 2022-12-03 17:52:23 -06:00
parent c731f01067
commit aa60c1f36a
4 changed files with 46 additions and 8 deletions

BIN
examples/jitfn/hello.bin Normal file

Binary file not shown.

17
examples/jitfn/hello.nasm Normal file
View File

@ -0,0 +1,17 @@
BITS 64
;;;
;;; Code
;;;
mov rax, 1 ; write(
mov rdi, 1 ; STDOUT_FILENO,
lea rsi, [rel msg] ; msg,
mov rdx, msglen ; sizeof(msg)
syscall ; );
ret ; return;
;;;
;;; Constants
;;;
msg: db "Hello, world!", 10
msglen: equ $ - msg

View File

@ -0,0 +1,12 @@
###
### Relies on NASM being installed to assemble code.
### Only works on x86-64 Linux.
###
### Before running, compile hello.nasm to hello.bin with
### $ nasm hello.nasm -o hello.bin
(def bin (slurp "hello.bin"))
(def f (ffi/jitfn bin))
(def signature (ffi/signature :default :void))
(ffi/call f signature)
(print "called a jitted function with FFI!")

View File

@ -1280,27 +1280,36 @@ static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointe
#endif
/* Allocate executable memory chunks in sizes of a page. Ideally we would keep
* an allocator around so that multiple JIT allocations would point to the same
* region but it isn't really worth it. */
#define FFI_PAGE_MASK 0xFFF
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);
/* Quick hack to align to page boundary, we should query OS. FIXME */
size_t alloc_size = ((size_t) bytes.len + FFI_PAGE_MASK) & ~FFI_PAGE_MASK;
#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);
void *ptr = VirtualAlloc(NULL, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#elif defined(MAP_ANONYMOUS)
void *ptr = mmap(0, bytes.len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
#elif defined(MAP_ANON)
/* macos doesn't have MAP_ANONYMOUS */
void *ptr = mmap(0, bytes.len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
#else
/* -std=c99 gets in the way */
/* #define MAP_ANONYMOUS 0x20 should work, though. */
void *ptr = mmap(0, bytes.len, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0);
void *ptr = mmap(0, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, -1, 0);
#endif
if (!ptr) {
janet_panic("failed to memory map writable memory");
@ -1308,16 +1317,16 @@ JANET_CORE_FN(cfun_ffi_jitfn,
memcpy(ptr, bytes.bytes, bytes.len);
#ifdef JANET_WINDOWS
DWORD old = 0;
if (!VirtualProtect(ptr, fn->size, PAGE_EXECUTE_READ, &old)) {
if (!VirtualProtect(ptr, alloc_size, PAGE_EXECUTE_READ, &old)) {
janet_panic("failed to make mapped memory executable");
}
#else
if (mprotect(ptr, fn->size, PROT_READ | PROT_EXEC) == -1) {
if (mprotect(ptr, alloc_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;
fn->size = alloc_size;
fn->function_pointer = ptr;
return janet_wrap_abstract(fn);
#else
janet_panic("ffi/jitfn not available on this platform");