/* * Copyright (c) 2023 Calvin Rose * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #ifndef JANET_AMALG #include "features.h" #include #include "util.h" #endif #ifdef JANET_FFI #ifdef _MSC_VER #define alloca _alloca #elif defined(JANET_LINUX) #include #elif !defined(alloca) /* Last ditch effort to get alloca - works for gcc and clang */ #define alloca __builtin_alloca #endif /* FFI jit includes */ #ifdef JANET_FFI_JIT #ifndef JANET_WINDOWS #include #endif #endif #define JANET_FFI_MAX_RECUR 64 /* Compiler, OS, and arch detection. Used * to enable a set of calling conventions. The * :none calling convention is always enabled. */ #if defined(JANET_WINDOWS) && (defined(__x86_64__) || defined(_M_X64)) #define JANET_FFI_WIN64_ENABLED #endif #if (defined(__x86_64__) || defined(_M_X64)) && !defined(JANET_WINDOWS) #define JANET_FFI_SYSV64_ENABLED #endif typedef struct JanetFFIType JanetFFIType; typedef struct JanetFFIStruct JanetFFIStruct; typedef enum { JANET_FFI_TYPE_VOID, JANET_FFI_TYPE_BOOL, JANET_FFI_TYPE_PTR, JANET_FFI_TYPE_STRING, JANET_FFI_TYPE_FLOAT, JANET_FFI_TYPE_DOUBLE, JANET_FFI_TYPE_INT8, JANET_FFI_TYPE_UINT8, JANET_FFI_TYPE_INT16, JANET_FFI_TYPE_UINT16, JANET_FFI_TYPE_INT32, JANET_FFI_TYPE_UINT32, JANET_FFI_TYPE_INT64, JANET_FFI_TYPE_UINT64, JANET_FFI_TYPE_STRUCT } JanetFFIPrimType; /* Custom alignof since alignof not in c99 standard */ #define ALIGNOF(type) offsetof(struct { char c; type member; }, member) typedef struct { size_t size; size_t align; } JanetFFIPrimInfo; static const JanetFFIPrimInfo janet_ffi_type_info[] = { {0, 0}, /* JANET_FFI_TYPE_VOID */ {sizeof(char), ALIGNOF(char)}, /* JANET_FFI_TYPE_BOOL */ {sizeof(void *), ALIGNOF(void *)}, /* JANET_FFI_TYPE_PTR */ {sizeof(char *), ALIGNOF(char *)}, /* JANET_FFI_TYPE_STRING */ {sizeof(float), ALIGNOF(float)}, /* JANET_FFI_TYPE_FLOAT */ {sizeof(double), ALIGNOF(double)}, /* JANET_FFI_TYPE_DOUBLE */ {sizeof(int8_t), ALIGNOF(int8_t)}, /* JANET_FFI_TYPE_INT8 */ {sizeof(uint8_t), ALIGNOF(uint8_t)}, /* JANET_FFI_TYPE_UINT8 */ {sizeof(int16_t), ALIGNOF(int16_t)}, /* JANET_FFI_TYPE_INT16 */ {sizeof(uint16_t), ALIGNOF(uint16_t)}, /* JANET_FFI_TYPE_UINT16 */ {sizeof(int32_t), ALIGNOF(int32_t)}, /* JANET_FFI_TYPE_INT32 */ {sizeof(uint32_t), ALIGNOF(uint32_t)}, /* JANET_FFI_TYPE_UINT32 */ {sizeof(int64_t), ALIGNOF(int64_t)}, /* JANET_FFI_TYPE_INT64 */ {sizeof(uint64_t), ALIGNOF(uint64_t)}, /* JANET_FFI_TYPE_UINT64 */ {0, ALIGNOF(uint64_t)} /* JANET_FFI_TYPE_STRUCT */ }; struct JanetFFIType { JanetFFIStruct *st; JanetFFIPrimType prim; int32_t array_count; }; typedef struct { JanetFFIType type; size_t offset; } JanetFFIStructMember; /* Also used to store array types */ struct JanetFFIStruct { uint32_t size; uint32_t align; uint32_t field_count; uint32_t is_aligned; JanetFFIStructMember fields[]; }; /* Specifies how the registers are classified. This is used * to determine if a certain argument should be passed in a register, * on the stack, special floating pointer register, etc. */ typedef enum { JANET_SYSV64_INTEGER, JANET_SYSV64_SSE, JANET_SYSV64_SSEUP, JANET_SYSV64_PAIR_INTINT, JANET_SYSV64_PAIR_INTSSE, JANET_SYSV64_PAIR_SSEINT, JANET_SYSV64_PAIR_SSESSE, JANET_SYSV64_NO_CLASS, JANET_SYSV64_MEMORY, JANET_WIN64_REGISTER, JANET_WIN64_STACK, JANET_WIN64_REGISTER_REF, JANET_WIN64_STACK_REF } JanetFFIWordSpec; /* Describe how each Janet argument is interpreted in terms of machine words * that will be mapped to registers/stack. */ typedef struct { JanetFFIType type; JanetFFIWordSpec spec; uint32_t offset; /* point to the exact register / stack offset depending on spec. */ uint32_t offset2; /* for reference passing apis (windows), use to allocate reference */ } JanetFFIMapping; typedef enum { JANET_FFI_CC_NONE, JANET_FFI_CC_SYSV_64, JANET_FFI_CC_WIN_64 } JanetFFICallingConvention; #ifdef JANET_FFI_WIN64_ENABLED #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_WIN_64 #elif defined(JANET_FFI_SYSV64_ENABLED) #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_SYSV_64 #else #define JANET_FFI_CC_DEFAULT JANET_FFI_CC_NONE #endif #define JANET_FFI_MAX_ARGS 32 typedef struct { uint32_t frame_size; uint32_t arg_count; uint32_t word_count; uint32_t variant; uint32_t stack_count; JanetFFICallingConvention cc; JanetFFIMapping ret; JanetFFIMapping args[JANET_FFI_MAX_ARGS]; } JanetFFISignature; int signature_mark(void *p, size_t s) { (void) s; JanetFFISignature *sig = p; for (uint32_t i = 0; i < sig->arg_count; i++) { JanetFFIType t = sig->args[i].type; if (t.prim == JANET_FFI_TYPE_STRUCT) { janet_mark(janet_wrap_abstract(t.st)); } } return 0; } static const JanetAbstractType janet_signature_type = { "core/ffi-signature", NULL, signature_mark, JANET_ATEND_GCMARK }; int struct_mark(void *p, size_t s) { (void) s; JanetFFIStruct *st = p; for (uint32_t i = 0; i < st->field_count; i++) { JanetFFIType t = st->fields[i].type; if (t.prim == JANET_FFI_TYPE_STRUCT) { janet_mark(janet_wrap_abstract(t.st)); } } return 0; } typedef struct { void *function_pointer; size_t size; } JanetFFIJittedFn; static const JanetAbstractType janet_struct_type = { "core/ffi-struct", NULL, struct_mark, 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 = (int32_t) fn->size; return bytes; } static size_t janet_ffijit_length(void *p, size_t s) { (void) s; JanetFFIJittedFn *fn = p; return fn->size; } const JanetAbstractType janet_type_ffijit = { .name = "ffi/jitfn", .gc = janet_ffijit_gc, .bytes = janet_ffijit_getbytes, .length = janet_ffijit_length }; typedef struct { Clib clib; int closed; int is_self; } JanetAbstractNative; static const JanetAbstractType janet_native_type = { "core/ffi-native", JANET_ATEND_NAME }; static JanetFFIType prim_type(JanetFFIPrimType pt) { JanetFFIType t; t.prim = pt; t.st = NULL; t.array_count = -1; return t; } static size_t type_size(JanetFFIType t) { size_t count = t.array_count < 0 ? 1 : (size_t) t.array_count; if (t.prim == JANET_FFI_TYPE_STRUCT) { return t.st->size * count; } else { return janet_ffi_type_info[t.prim].size * count; } } static size_t type_align(JanetFFIType t) { if (t.prim == JANET_FFI_TYPE_STRUCT) { return t.st->align; } else { return janet_ffi_type_info[t.prim].align; } } static JanetFFICallingConvention decode_ffi_cc(const uint8_t *name) { if (!janet_cstrcmp(name, "none")) return JANET_FFI_CC_NONE; #ifdef JANET_FFI_WIN64_ENABLED if (!janet_cstrcmp(name, "win64")) return JANET_FFI_CC_WIN_64; #endif #ifdef JANET_FFI_SYSV64_ENABLED if (!janet_cstrcmp(name, "sysv64")) return JANET_FFI_CC_SYSV_64; #endif if (!janet_cstrcmp(name, "default")) return JANET_FFI_CC_DEFAULT; janet_panicf("unknown calling convention %s", name); } static JanetFFIPrimType decode_ffi_prim(const uint8_t *name) { if (!janet_cstrcmp(name, "void")) return JANET_FFI_TYPE_VOID; if (!janet_cstrcmp(name, "bool")) return JANET_FFI_TYPE_BOOL; if (!janet_cstrcmp(name, "ptr")) return JANET_FFI_TYPE_PTR; if (!janet_cstrcmp(name, "string")) return JANET_FFI_TYPE_STRING; if (!janet_cstrcmp(name, "float")) return JANET_FFI_TYPE_FLOAT; if (!janet_cstrcmp(name, "double")) return JANET_FFI_TYPE_DOUBLE; if (!janet_cstrcmp(name, "int8")) return JANET_FFI_TYPE_INT8; if (!janet_cstrcmp(name, "uint8")) return JANET_FFI_TYPE_UINT8; if (!janet_cstrcmp(name, "int16")) return JANET_FFI_TYPE_INT16; if (!janet_cstrcmp(name, "uint16")) return JANET_FFI_TYPE_UINT16; if (!janet_cstrcmp(name, "int32")) return JANET_FFI_TYPE_INT32; if (!janet_cstrcmp(name, "uint32")) return JANET_FFI_TYPE_UINT32; if (!janet_cstrcmp(name, "int64")) return JANET_FFI_TYPE_INT64; if (!janet_cstrcmp(name, "uint64")) return JANET_FFI_TYPE_UINT64; #ifdef JANET_64 if (!janet_cstrcmp(name, "size")) return JANET_FFI_TYPE_UINT64; if (!janet_cstrcmp(name, "ssize")) return JANET_FFI_TYPE_INT64; #else if (!janet_cstrcmp(name, "size")) return JANET_FFI_TYPE_UINT32; if (!janet_cstrcmp(name, "ssize")) return JANET_FFI_TYPE_INT32; #endif /* aliases */ if (!janet_cstrcmp(name, "r32")) return JANET_FFI_TYPE_FLOAT; if (!janet_cstrcmp(name, "r64")) return JANET_FFI_TYPE_DOUBLE; if (!janet_cstrcmp(name, "s8")) return JANET_FFI_TYPE_INT8; if (!janet_cstrcmp(name, "u8")) return JANET_FFI_TYPE_UINT8; if (!janet_cstrcmp(name, "s16")) return JANET_FFI_TYPE_INT16; if (!janet_cstrcmp(name, "u16")) return JANET_FFI_TYPE_UINT16; if (!janet_cstrcmp(name, "s32")) return JANET_FFI_TYPE_INT32; if (!janet_cstrcmp(name, "u32")) return JANET_FFI_TYPE_UINT32; if (!janet_cstrcmp(name, "s64")) return JANET_FFI_TYPE_INT64; if (!janet_cstrcmp(name, "u64")) return JANET_FFI_TYPE_UINT64; if (!janet_cstrcmp(name, "char")) return JANET_FFI_TYPE_INT8; if (!janet_cstrcmp(name, "short")) return JANET_FFI_TYPE_INT16; if (!janet_cstrcmp(name, "int")) return JANET_FFI_TYPE_INT32; if (!janet_cstrcmp(name, "long")) return JANET_FFI_TYPE_INT64; if (!janet_cstrcmp(name, "byte")) return JANET_FFI_TYPE_UINT8; if (!janet_cstrcmp(name, "uchar")) return JANET_FFI_TYPE_UINT8; if (!janet_cstrcmp(name, "ushort")) return JANET_FFI_TYPE_UINT16; if (!janet_cstrcmp(name, "uint")) return JANET_FFI_TYPE_UINT32; if (!janet_cstrcmp(name, "ulong")) return JANET_FFI_TYPE_UINT64; janet_panicf("unknown machine type %s", name); } /* A common callback function signature. To avoid runtime code generation, which is prohibited * on many platforms, often buggy (see libffi), and generally complicated, instead provide * a single (or small set of commonly used function signatures). All callbacks should * eventually call this. */ void janet_ffi_trampoline(void *ctx, void *userdata) { if (NULL == userdata) { /* Userdata not set. */ janet_eprintf("no userdata found for janet callback"); return; } Janet context = janet_wrap_pointer(ctx); JanetFunction *fun = userdata; janet_call(fun, 1, &context); } static JanetFFIType decode_ffi_type(Janet x); static JanetFFIStruct *build_struct_type(int32_t argc, const Janet *argv) { /* Use :pack to indicate a single packed struct member and :pack-all * to pack the remaining members */ int32_t member_count = argc; int all_packed = 0; for (int32_t i = 0; i < argc; i++) { if (janet_keyeq(argv[i], "pack")) { member_count--; } else if (janet_keyeq(argv[i], "pack-all")) { member_count--; all_packed = 1; } } JanetFFIStruct *st = janet_abstract(&janet_struct_type, sizeof(JanetFFIStruct) + argc * sizeof(JanetFFIStructMember)); st->field_count = member_count; st->size = 0; st->align = 1; if (argc == 0) { janet_panic("invalid empty struct"); } uint32_t is_aligned = 1; int32_t i = 0; for (int32_t j = 0; j < argc; j++) { int pack_one = 0; if (janet_keyeq(argv[j], "pack") || janet_keyeq(argv[j], "pack-all")) { pack_one = 1; j++; if (j == argc) break; } st->fields[i].type = decode_ffi_type(argv[j]); size_t el_size = type_size(st->fields[i].type); size_t el_align = type_align(st->fields[i].type); if (all_packed || pack_one) { if (st->size % el_align != 0) is_aligned = 0; st->fields[i].offset = st->size; st->size += (uint32_t) el_size; } else { if (el_align > st->align) st->align = (uint32_t) el_align; st->fields[i].offset = (uint32_t)(((st->size + el_align - 1) / el_align) * el_align); st->size = (uint32_t)(el_size + st->fields[i].offset); } i++; } st->is_aligned = is_aligned; st->size += (st->align - 1); st->size /= st->align; st->size *= st->align; return st; } static JanetFFIType decode_ffi_type(Janet x) { if (janet_checktype(x, JANET_KEYWORD)) { return prim_type(decode_ffi_prim(janet_unwrap_keyword(x))); } JanetFFIType ret; ret.array_count = -1; ret.prim = JANET_FFI_TYPE_STRUCT; if (janet_checkabstract(x, &janet_struct_type)) { ret.st = janet_unwrap_abstract(x); return ret; } int32_t len; const Janet *els; if (janet_indexed_view(x, &els, &len)) { if (janet_checktype(x, JANET_ARRAY)) { if (len != 2 && len != 1) janet_panicf("array type must be of form @[type count], got %v", x); ret = decode_ffi_type(els[0]); int32_t array_count = len == 1 ? 0 : janet_getnat(els, 1); ret.array_count = array_count; } else { ret.st = build_struct_type(len, els); } return ret; } else { janet_panicf("bad native type %v", x); } } JANET_CORE_FN(cfun_ffi_struct, "(ffi/struct & types)", "Create a struct type definition that can be used to pass structs into native functions. ") { janet_arity(argc, 1, -1); return janet_wrap_abstract(build_struct_type(argc, argv)); } JANET_CORE_FN(cfun_ffi_size, "(ffi/size type)", "Get the size of an ffi type in bytes.") { janet_fixarity(argc, 1); size_t size = type_size(decode_ffi_type(argv[0])); return janet_wrap_number((double) size); } JANET_CORE_FN(cfun_ffi_align, "(ffi/align type)", "Get the align of an ffi type in bytes.") { janet_fixarity(argc, 1); size_t size = type_align(decode_ffi_type(argv[0])); return janet_wrap_number((double) size); } static void *janet_ffi_getpointer(const Janet *argv, int32_t n) { switch (janet_type(argv[n])) { default: janet_panicf("bad slot #%d, expected ffi pointer convertable type, got %v", n, argv[n]); case JANET_POINTER: case JANET_STRING: case JANET_KEYWORD: case JANET_SYMBOL: 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: /* Users may pass in a function. Any function passed is almost certainly * being used as a callback, so we add it to the root set. */ janet_gcroot(argv[n]); return janet_unwrap_pointer(argv[n]); case JANET_NIL: return NULL; } } 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) { if (recur == 0) janet_panic("recursion too deep"); if (type.array_count >= 0) { JanetFFIType el_type = type; el_type.array_count = -1; size_t el_size = type_size(el_type); JanetView els = janet_getindexed(argv, n); if (els.len != type.array_count) { janet_panicf("bad array length, expected %d, got %d", type.array_count, els.len); } char *cursor = to; for (int32_t i = 0; i < els.len; i++) { janet_ffi_write_one(cursor, els.items, i, el_type, recur - 1); cursor += el_size; } return; } switch (type.prim) { case JANET_FFI_TYPE_VOID: if (!janet_checktype(argv[n], JANET_NIL)) { janet_panicf("expected nil, got %v", argv[n]); } break; case JANET_FFI_TYPE_STRUCT: { JanetView els = janet_getindexed(argv, n); JanetFFIStruct *st = type.st; if ((uint32_t) els.len != st->field_count) { janet_panicf("wrong number of fields in struct, expected %d, got %d", (int32_t) st->field_count, els.len); } for (int32_t i = 0; i < els.len; i++) { JanetFFIType tp = st->fields[i].type; janet_ffi_write_one((char *) to + st->fields[i].offset, els.items, i, tp, recur - 1); } } break; case JANET_FFI_TYPE_DOUBLE: ((double *)(to))[0] = janet_getnumber(argv, n); break; case JANET_FFI_TYPE_FLOAT: ((float *)(to))[0] = (float) janet_getnumber(argv, n); break; case JANET_FFI_TYPE_PTR: ((void **)(to))[0] = janet_ffi_getpointer(argv, n); break; case JANET_FFI_TYPE_STRING: ((const char **)(to))[0] = janet_getcstring(argv, n); break; case JANET_FFI_TYPE_BOOL: ((bool *)(to))[0] = janet_getboolean(argv, n); break; case JANET_FFI_TYPE_INT8: ((int8_t *)(to))[0] = janet_getinteger(argv, n); break; case JANET_FFI_TYPE_INT16: ((int16_t *)(to))[0] = janet_getinteger(argv, n); break; case JANET_FFI_TYPE_INT32: ((int32_t *)(to))[0] = janet_getinteger(argv, n); break; case JANET_FFI_TYPE_INT64: ((int64_t *)(to))[0] = janet_getinteger64(argv, n); break; case JANET_FFI_TYPE_UINT8: ((uint8_t *)(to))[0] = (uint8_t) janet_getuinteger64(argv, n); break; case JANET_FFI_TYPE_UINT16: ((uint16_t *)(to))[0] = (uint16_t) janet_getuinteger64(argv, n); break; case JANET_FFI_TYPE_UINT32: ((uint32_t *)(to))[0] = (uint32_t) janet_getuinteger64(argv, n); break; case JANET_FFI_TYPE_UINT64: ((uint64_t *)(to))[0] = janet_getuinteger64(argv, n); break; } } /* Read a value from memory and construct a Janet data structure that can be passed back into * the interpreter. This should be the inverse to janet_ffi_write_one. It is assumed that the * size of the data is correct. */ static Janet janet_ffi_read_one(const uint8_t *from, JanetFFIType type, int recur) { if (recur == 0) janet_panic("recursion too deep"); if (type.array_count >= 0) { JanetFFIType el_type = type; el_type.array_count = -1; size_t el_size = type_size(el_type); JanetArray *array = janet_array(type.array_count); for (int32_t i = 0; i < type.array_count; i++) { janet_array_push(array, janet_ffi_read_one(from, el_type, recur - 1)); from += el_size; } return janet_wrap_array(array); } switch (type.prim) { default: case JANET_FFI_TYPE_VOID: return janet_wrap_nil(); case JANET_FFI_TYPE_STRUCT: { JanetFFIStruct *st = type.st; Janet *tup = janet_tuple_begin(st->field_count); for (uint32_t i = 0; i < st->field_count; i++) { JanetFFIType tp = st->fields[i].type; tup[i] = janet_ffi_read_one(from + st->fields[i].offset, tp, recur - 1); } return janet_wrap_tuple(janet_tuple_end(tup)); } case JANET_FFI_TYPE_DOUBLE: return janet_wrap_number(((double *)(from))[0]); case JANET_FFI_TYPE_FLOAT: return janet_wrap_number(((float *)(from))[0]); case JANET_FFI_TYPE_PTR: { void *ptr = ((void **)(from))[0]; return (NULL == ptr) ? janet_wrap_nil() : janet_wrap_pointer(ptr); } case JANET_FFI_TYPE_STRING: return janet_cstringv(((char **)(from))[0]); case JANET_FFI_TYPE_BOOL: return janet_wrap_boolean(((bool *)(from))[0]); case JANET_FFI_TYPE_INT8: return janet_wrap_number(((int8_t *)(from))[0]); case JANET_FFI_TYPE_INT16: return janet_wrap_number(((int16_t *)(from))[0]); case JANET_FFI_TYPE_INT32: return janet_wrap_number(((int32_t *)(from))[0]); case JANET_FFI_TYPE_UINT8: return janet_wrap_number(((uint8_t *)(from))[0]); case JANET_FFI_TYPE_UINT16: return janet_wrap_number(((uint16_t *)(from))[0]); case JANET_FFI_TYPE_UINT32: return janet_wrap_number(((uint32_t *)(from))[0]); #ifdef JANET_INT_TYPES case JANET_FFI_TYPE_INT64: return janet_wrap_s64(((int64_t *)(from))[0]); case JANET_FFI_TYPE_UINT64: return janet_wrap_u64(((uint64_t *)(from))[0]); #else case JANET_FFI_TYPE_INT64: return janet_wrap_number(((int64_t *)(from))[0]); case JANET_FFI_TYPE_UINT64: return janet_wrap_number(((uint64_t *)(from))[0]); #endif } } static JanetFFIMapping void_mapping(void) { JanetFFIMapping m; m.type = prim_type(JANET_FFI_TYPE_VOID); m.spec = JANET_SYSV64_NO_CLASS; m.offset = 0; return m; } #ifdef JANET_FFI_SYSV64_ENABLED /* AMD64 ABI Draft 0.99.7 – November 17, 2014 – 15:08 * See section 3.2.3 Parameter Passing */ static JanetFFIWordSpec sysv64_classify_ext(JanetFFIType type, size_t shift) { switch (type.prim) { case JANET_FFI_TYPE_PTR: case JANET_FFI_TYPE_STRING: case JANET_FFI_TYPE_BOOL: case JANET_FFI_TYPE_INT8: case JANET_FFI_TYPE_INT16: case JANET_FFI_TYPE_INT32: case JANET_FFI_TYPE_INT64: case JANET_FFI_TYPE_UINT8: case JANET_FFI_TYPE_UINT16: case JANET_FFI_TYPE_UINT32: case JANET_FFI_TYPE_UINT64: return JANET_SYSV64_INTEGER; case JANET_FFI_TYPE_DOUBLE: case JANET_FFI_TYPE_FLOAT: return JANET_SYSV64_SSE; case JANET_FFI_TYPE_STRUCT: { JanetFFIStruct *st = type.st; if (st->size > 16) return JANET_SYSV64_MEMORY; if (!st->is_aligned) return JANET_SYSV64_MEMORY; JanetFFIWordSpec clazz = JANET_SYSV64_NO_CLASS; if (st->size > 8 && st->size <= 16) { /* map to pair classification */ int has_int_lo = 0; int has_int_hi = 0; for (uint32_t i = 0; i < st->field_count; i++) { JanetFFIWordSpec next_class = sysv64_classify_ext(st->fields[i].type, shift + st->fields[i].offset); switch (next_class) { default: break; case JANET_SYSV64_INTEGER: if (shift + st->fields[i].offset + type_size(st->fields[i].type) <= 8) { has_int_lo = 1; } else { has_int_hi = 2; } break; case JANET_SYSV64_PAIR_INTINT: has_int_lo = 1; has_int_hi = 2; break; case JANET_SYSV64_PAIR_INTSSE: has_int_lo = 1; break; case JANET_SYSV64_PAIR_SSEINT: has_int_hi = 2; break; break; } } switch (has_int_hi + has_int_lo) { case 0: clazz = JANET_SYSV64_PAIR_SSESSE; break; case 1: clazz = JANET_SYSV64_PAIR_INTSSE; break; case 2: clazz = JANET_SYSV64_PAIR_SSEINT; break; case 3: clazz = JANET_SYSV64_PAIR_INTINT; break; } } else { /* Normal struct classification */ for (uint32_t i = 0; i < st->field_count; i++) { JanetFFIWordSpec next_class = sysv64_classify_ext(st->fields[i].type, shift + st->fields[i].offset); if (next_class != clazz) { if (clazz == JANET_SYSV64_NO_CLASS) { clazz = next_class; } else if (clazz == JANET_SYSV64_MEMORY || next_class == JANET_SYSV64_MEMORY) { clazz = JANET_SYSV64_MEMORY; } else if (clazz == JANET_SYSV64_INTEGER || next_class == JANET_SYSV64_INTEGER) { clazz = JANET_SYSV64_INTEGER; } else { clazz = JANET_SYSV64_SSE; } } } } return clazz; } case JANET_FFI_TYPE_VOID: return JANET_SYSV64_NO_CLASS; default: janet_panic("nyi"); return JANET_SYSV64_NO_CLASS; } } static JanetFFIWordSpec sysv64_classify(JanetFFIType type) { return sysv64_classify_ext(type, 0); } #endif JANET_CORE_FN(cfun_ffi_signature, "(ffi/signature calling-convention ret-type & arg-types)", "Create a function signature object that can be used to make calls " "with raw function pointers.") { janet_arity(argc, 2, -1); uint32_t frame_size = 0; uint32_t variant = 0; uint32_t arg_count = argc - 2; uint32_t stack_count = 0; JanetFFICallingConvention cc = decode_ffi_cc(janet_getkeyword(argv, 0)); JanetFFIType ret_type = decode_ffi_type(argv[1]); JanetFFIMapping ret = { ret_type, JANET_SYSV64_NO_CLASS, 0, 0 }; JanetFFIMapping mappings[JANET_FFI_MAX_ARGS]; for (int i = 0; i < JANET_FFI_MAX_ARGS; i++) mappings[i] = void_mapping(); switch (cc) { default: case JANET_FFI_CC_NONE: { /* Even if unsupported, we can check that the signature is valid * and error at runtime */ for (uint32_t i = 0; i < arg_count; i++) { decode_ffi_type(argv[i + 2]); } } break; #ifdef JANET_FFI_WIN64_ENABLED case JANET_FFI_CC_WIN_64: { size_t ret_size = type_size(ret.type); uint32_t ref_stack_count = 0; ret.spec = JANET_WIN64_REGISTER; uint32_t next_register = 0; if (ret_size != 0 && ret_size != 1 && ret_size != 2 && ret_size != 4 && ret_size != 8) { ret.spec = JANET_WIN64_REGISTER_REF; next_register++; } else if (ret.type.prim == JANET_FFI_TYPE_FLOAT || ret.type.prim == JANET_FFI_TYPE_DOUBLE) { variant += 16; } for (uint32_t i = 0; i < arg_count; i++) { mappings[i].type = decode_ffi_type(argv[i + 2]); size_t el_size = type_size(mappings[i].type); int is_register_sized = (el_size == 1 || el_size == 2 || el_size == 4 || el_size == 8); if (next_register < 4) { mappings[i].offset = next_register; if (is_register_sized) { mappings[i].spec = JANET_WIN64_REGISTER; if (mappings[i].type.prim == JANET_FFI_TYPE_FLOAT || mappings[i].type.prim == JANET_FFI_TYPE_DOUBLE) { variant += 1 << (3 - next_register); } } else { mappings[i].spec = JANET_WIN64_REGISTER_REF; mappings[i].offset2 = ref_stack_count; ref_stack_count += (uint32_t)((el_size + 15) / 16); } next_register++; } else { if (is_register_sized) { mappings[i].spec = JANET_WIN64_STACK; mappings[i].offset = stack_count; stack_count++; } else { mappings[i].spec = JANET_WIN64_STACK_REF; mappings[i].offset = stack_count; stack_count++; mappings[i].offset2 = ref_stack_count; ref_stack_count += (uint32_t)((el_size + 15) / 16); } } } /* Add reference items */ stack_count += 2 * ref_stack_count; if (stack_count & 0x1) { stack_count++; } /* Invert stack * Offsets are in units of 8-bytes */ for (uint32_t i = 0; i < arg_count; i++) { if (mappings[i].spec == JANET_WIN64_STACK_REF || mappings[i].spec == JANET_WIN64_REGISTER_REF) { /* Align size to 16 bytes */ size_t size = (type_size(mappings[i].type) + 15) & ~0xFUL; mappings[i].offset2 = (uint32_t)(stack_count - mappings[i].offset2 - (size / 8)); } } } break; #endif #ifdef JANET_FFI_SYSV64_ENABLED case JANET_FFI_CC_SYSV_64: { JanetFFIWordSpec ret_spec = sysv64_classify(ret.type); ret.spec = ret_spec; if (ret_spec == JANET_SYSV64_SSE) variant = 1; if (ret_spec == JANET_SYSV64_PAIR_INTSSE) variant = 2; if (ret_spec == JANET_SYSV64_PAIR_SSEINT) variant = 3; /* Spill register overflow to memory */ uint32_t next_register = 0; uint32_t next_fp_register = 0; const uint32_t max_regs = 6; const uint32_t max_fp_regs = 8; if (ret_spec == JANET_SYSV64_MEMORY) { /* First integer reg is pointer. */ next_register = 1; } for (uint32_t i = 0; i < arg_count; i++) { mappings[i].type = decode_ffi_type(argv[i + 2]); mappings[i].offset = 0; mappings[i].spec = sysv64_classify(mappings[i].type); if (mappings[i].spec == JANET_SYSV64_NO_CLASS) { janet_panic("unexpected void parameter"); } size_t el_size = (type_size(mappings[i].type) + 7) / 8; switch (mappings[i].spec) { default: janet_panicf("nyi: %d", mappings[i].spec); case JANET_SYSV64_INTEGER: { if (next_register < max_regs) { mappings[i].offset = next_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; case JANET_SYSV64_SSE: { if (next_fp_register < max_fp_regs) { mappings[i].offset = next_fp_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; case JANET_SYSV64_MEMORY: { mappings[i].offset = stack_count; stack_count += el_size; } break; case JANET_SYSV64_PAIR_INTINT: { if (next_register + 1 < max_regs) { mappings[i].offset = next_register++; mappings[i].offset2 = next_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; case JANET_SYSV64_PAIR_INTSSE: { if (next_register < max_regs && next_fp_register < max_fp_regs) { mappings[i].offset = next_register++; mappings[i].offset2 = next_fp_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; case JANET_SYSV64_PAIR_SSEINT: { if (next_register < max_regs && next_fp_register < max_fp_regs) { mappings[i].offset = next_fp_register++; mappings[i].offset2 = next_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; case JANET_SYSV64_PAIR_SSESSE: { if (next_fp_register < max_fp_regs) { mappings[i].offset = next_fp_register++; mappings[i].offset2 = next_fp_register++; } else { mappings[i].spec = JANET_SYSV64_MEMORY; mappings[i].offset = stack_count; stack_count += el_size; } } break; } } } break; #endif } /* Create signature abstract value */ JanetFFISignature *abst = janet_abstract(&janet_signature_type, sizeof(JanetFFISignature)); abst->frame_size = frame_size; abst->cc = cc; abst->ret = ret; abst->arg_count = arg_count; abst->variant = variant; abst->stack_count = stack_count; memcpy(abst->args, mappings, sizeof(JanetFFIMapping) * JANET_FFI_MAX_ARGS); return janet_wrap_abstract(abst); } #ifdef JANET_FFI_SYSV64_ENABLED static void janet_ffi_sysv64_standard_callback(void *ctx, void *userdata) { janet_ffi_trampoline(ctx, userdata); } /* Functions that set all argument registers. Two variants - one to read rax and rdx returns, another * to read xmm0 and xmm1 returns. */ typedef struct { uint64_t x; uint64_t y; } sysv64_int_return; typedef struct { double x; double y; } sysv64_sse_return; typedef struct { uint64_t x; double y; } sysv64_intsse_return; typedef struct { double y; uint64_t x; } sysv64_sseint_return; typedef sysv64_int_return janet_sysv64_variant_1(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8); typedef sysv64_sse_return janet_sysv64_variant_2(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8); typedef sysv64_intsse_return janet_sysv64_variant_3(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8); typedef sysv64_sseint_return janet_sysv64_variant_4(uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e, uint64_t f, double r1, double r2, double r3, double r4, double r5, double r6, double r7, double r8); static Janet janet_ffi_sysv64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) { union { sysv64_int_return int_return; sysv64_sse_return sse_return; sysv64_sseint_return sseint_return; sysv64_intsse_return intsse_return; } retu; uint64_t pair[2]; uint64_t regs[6]; double fp_regs[8]; JanetFFIWordSpec ret_spec = signature->ret.spec; void *ret_mem = &retu.int_return; if (ret_spec == JANET_SYSV64_MEMORY) { ret_mem = alloca(type_size(signature->ret.type)); regs[0] = (uint64_t) ret_mem; } uint64_t *stack = alloca(sizeof(uint64_t) * signature->stack_count); for (uint32_t i = 0; i < signature->arg_count; i++) { uint64_t *to; int32_t n = i + 2; JanetFFIMapping arg = signature->args[i]; switch (arg.spec) { default: janet_panic("nyi"); case JANET_SYSV64_INTEGER: to = regs + arg.offset; break; case JANET_SYSV64_SSE: to = (uint64_t *)(fp_regs + arg.offset); break; case JANET_SYSV64_MEMORY: to = stack + arg.offset; break; case JANET_SYSV64_PAIR_INTINT: janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR); regs[arg.offset] = pair[0]; regs[arg.offset2] = pair[1]; continue; case JANET_SYSV64_PAIR_INTSSE: janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR); regs[arg.offset] = pair[0]; ((uint64_t *) fp_regs)[arg.offset2] = pair[1]; continue; case JANET_SYSV64_PAIR_SSEINT: janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR); ((uint64_t *) fp_regs)[arg.offset] = pair[0]; regs[arg.offset2] = pair[1]; continue; case JANET_SYSV64_PAIR_SSESSE: janet_ffi_write_one(pair, argv, n, arg.type, JANET_FFI_MAX_RECUR); ((uint64_t *) fp_regs)[arg.offset] = pair[0]; ((uint64_t *) fp_regs)[arg.offset2] = pair[1]; continue; } janet_ffi_write_one(to, argv, n, arg.type, JANET_FFI_MAX_RECUR); } switch (signature->variant) { case 0: retu.int_return = ((janet_sysv64_variant_1 *)(function_pointer))( regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); break; case 1: retu.sse_return = ((janet_sysv64_variant_2 *)(function_pointer))( regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); break; case 2: retu.intsse_return = ((janet_sysv64_variant_3 *)(function_pointer))( regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); break; case 3: retu.sseint_return = ((janet_sysv64_variant_4 *)(function_pointer))( regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], fp_regs[0], fp_regs[1], fp_regs[2], fp_regs[3], fp_regs[4], fp_regs[5], fp_regs[6], fp_regs[7]); break; } return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR); } #endif #ifdef JANET_FFI_WIN64_ENABLED static void janet_ffi_win64_standard_callback(void *ctx, void *userdata) { janet_ffi_trampoline(ctx, userdata); } /* Variants that allow setting all required registers for 64 bit windows calling convention. * win64 calling convention has up to 4 arguments on registers, and one register for returns. * Each register can either be an integer or floating point register, resulting in * 2^5 = 32 variants. Unlike sysv, there are no function signatures that will fill * all of the possible registers which is why we have so many variants. If you were using * assembly, you could manually fill all of the registers and only have a single variant. * And msvc does not support inline assembly on 64 bit targets, so yeah, we have this hackery. */ typedef uint64_t (win64_variant_i_iiii)(uint64_t, uint64_t, uint64_t, uint64_t); typedef uint64_t (win64_variant_i_iiif)(uint64_t, uint64_t, uint64_t, double); typedef uint64_t (win64_variant_i_iifi)(uint64_t, uint64_t, double, uint64_t); typedef uint64_t (win64_variant_i_iiff)(uint64_t, uint64_t, double, double); typedef uint64_t (win64_variant_i_ifii)(uint64_t, double, uint64_t, uint64_t); typedef uint64_t (win64_variant_i_ifif)(uint64_t, double, uint64_t, double); typedef uint64_t (win64_variant_i_iffi)(uint64_t, double, double, uint64_t); typedef uint64_t (win64_variant_i_ifff)(uint64_t, double, double, double); typedef uint64_t (win64_variant_i_fiii)(double, uint64_t, uint64_t, uint64_t); typedef uint64_t (win64_variant_i_fiif)(double, uint64_t, uint64_t, double); typedef uint64_t (win64_variant_i_fifi)(double, uint64_t, double, uint64_t); typedef uint64_t (win64_variant_i_fiff)(double, uint64_t, double, double); typedef uint64_t (win64_variant_i_ffii)(double, double, uint64_t, uint64_t); typedef uint64_t (win64_variant_i_ffif)(double, double, uint64_t, double); typedef uint64_t (win64_variant_i_fffi)(double, double, double, uint64_t); typedef uint64_t (win64_variant_i_ffff)(double, double, double, double); typedef double (win64_variant_f_iiii)(uint64_t, uint64_t, uint64_t, uint64_t); typedef double (win64_variant_f_iiif)(uint64_t, uint64_t, uint64_t, double); typedef double (win64_variant_f_iifi)(uint64_t, uint64_t, double, uint64_t); typedef double (win64_variant_f_iiff)(uint64_t, uint64_t, double, double); typedef double (win64_variant_f_ifii)(uint64_t, double, uint64_t, uint64_t); typedef double (win64_variant_f_ifif)(uint64_t, double, uint64_t, double); typedef double (win64_variant_f_iffi)(uint64_t, double, double, uint64_t); typedef double (win64_variant_f_ifff)(uint64_t, double, double, double); typedef double (win64_variant_f_fiii)(double, uint64_t, uint64_t, uint64_t); typedef double (win64_variant_f_fiif)(double, uint64_t, uint64_t, double); typedef double (win64_variant_f_fifi)(double, uint64_t, double, uint64_t); typedef double (win64_variant_f_fiff)(double, uint64_t, double, double); typedef double (win64_variant_f_ffii)(double, double, uint64_t, uint64_t); typedef double (win64_variant_f_ffif)(double, double, uint64_t, double); typedef double (win64_variant_f_fffi)(double, double, double, uint64_t); typedef double (win64_variant_f_ffff)(double, double, double, double); static Janet janet_ffi_win64(JanetFFISignature *signature, void *function_pointer, const Janet *argv) { union { uint64_t integer; double real; } regs[4]; union { uint64_t integer; double real; } ret_reg; JanetFFIWordSpec ret_spec = signature->ret.spec; void *ret_mem = &ret_reg.integer; if (ret_spec == JANET_WIN64_REGISTER_REF) { ret_mem = alloca(type_size(signature->ret.type)); regs[0].integer = (uint64_t) ret_mem; } size_t stack_size = signature->stack_count * 8; size_t stack_shift = 2; uint64_t *stack = alloca(stack_size); for (uint32_t i = 0; i < signature->arg_count; i++) { int32_t n = i + 2; JanetFFIMapping arg = signature->args[i]; if (arg.spec == JANET_WIN64_STACK) { janet_ffi_write_one(stack + arg.offset, argv, n, arg.type, JANET_FFI_MAX_RECUR); } else if (arg.spec == JANET_WIN64_STACK_REF) { uint8_t *ptr = (uint8_t *)(stack + arg.offset2); janet_ffi_write_one(ptr, argv, n, arg.type, JANET_FFI_MAX_RECUR); stack[arg.offset] = (uint64_t)(ptr - stack_shift * sizeof(uint64_t)); } else if (arg.spec == JANET_WIN64_REGISTER_REF) { uint8_t *ptr = (uint8_t *)(stack + arg.offset2); janet_ffi_write_one(ptr, argv, n, arg.type, JANET_FFI_MAX_RECUR); regs[arg.offset].integer = (uint64_t)(ptr - stack_shift * sizeof(uint64_t)); } else { janet_ffi_write_one((uint8_t *) ®s[arg.offset].integer, argv, n, arg.type, JANET_FFI_MAX_RECUR); } } /* hack to get proper stack placement and avoid clobbering from logic above - shift stack down, otherwise we have issues. * Technically, this writes into 16 bytes of unallocated stack memory */ #ifdef JANET_MINGW #pragma GCC diagnostic ignored "-Wstringop-overflow" #endif if (stack_size) memmove(stack - stack_shift, stack, stack_size); #ifdef JANET_MINGW #pragma GCC diagnostic pop #endif switch (signature->variant) { default: janet_panicf("unknown variant %d", signature->variant); case 0: ret_reg.integer = ((win64_variant_i_iiii *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].integer, regs[3].integer); break; case 1: ret_reg.integer = ((win64_variant_i_iiif *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].integer, regs[3].real); break; case 2: ret_reg.integer = ((win64_variant_i_iifi *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].real, regs[3].integer); break; case 3: ret_reg.integer = ((win64_variant_i_iiff *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].real, regs[3].real); break; case 4: ret_reg.integer = ((win64_variant_i_ifii *) function_pointer)(regs[0].integer, regs[1].real, regs[2].integer, regs[3].integer); break; case 5: ret_reg.integer = ((win64_variant_i_ifif *) function_pointer)(regs[0].integer, regs[1].real, regs[2].integer, regs[3].real); break; case 6: ret_reg.integer = ((win64_variant_i_iffi *) function_pointer)(regs[0].integer, regs[1].real, regs[2].real, regs[3].integer); break; case 7: ret_reg.integer = ((win64_variant_i_ifff *) function_pointer)(regs[0].integer, regs[1].real, regs[2].real, regs[3].real); break; case 8: ret_reg.integer = ((win64_variant_i_fiii *) function_pointer)(regs[0].real, regs[1].integer, regs[2].integer, regs[3].integer); break; case 9: ret_reg.integer = ((win64_variant_i_fiif *) function_pointer)(regs[0].real, regs[1].integer, regs[2].integer, regs[3].real); break; case 10: ret_reg.integer = ((win64_variant_i_fifi *) function_pointer)(regs[0].real, regs[1].integer, regs[2].real, regs[3].integer); break; case 11: ret_reg.integer = ((win64_variant_i_fiff *) function_pointer)(regs[0].real, regs[1].integer, regs[2].real, regs[3].real); break; case 12: ret_reg.integer = ((win64_variant_i_ffii *) function_pointer)(regs[0].real, regs[1].real, regs[2].integer, regs[3].integer); break; case 13: ret_reg.integer = ((win64_variant_i_ffif *) function_pointer)(regs[0].real, regs[1].real, regs[2].integer, regs[3].real); break; case 14: ret_reg.integer = ((win64_variant_i_fffi *) function_pointer)(regs[0].real, regs[1].real, regs[2].real, regs[3].integer); break; case 15: ret_reg.integer = ((win64_variant_i_ffff *) function_pointer)(regs[0].real, regs[1].real, regs[2].real, regs[3].real); break; case 16: ret_reg.real = ((win64_variant_f_iiii *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].integer, regs[3].integer); break; case 17: ret_reg.real = ((win64_variant_f_iiif *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].integer, regs[3].real); break; case 18: ret_reg.real = ((win64_variant_f_iifi *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].real, regs[3].integer); break; case 19: ret_reg.real = ((win64_variant_f_iiff *) function_pointer)(regs[0].integer, regs[1].integer, regs[2].real, regs[3].real); break; case 20: ret_reg.real = ((win64_variant_f_ifii *) function_pointer)(regs[0].integer, regs[1].real, regs[2].integer, regs[3].integer); break; case 21: ret_reg.real = ((win64_variant_f_ifif *) function_pointer)(regs[0].integer, regs[1].real, regs[2].integer, regs[3].real); break; case 22: ret_reg.real = ((win64_variant_f_iffi *) function_pointer)(regs[0].integer, regs[1].real, regs[2].real, regs[3].integer); break; case 23: ret_reg.real = ((win64_variant_f_ifff *) function_pointer)(regs[0].integer, regs[1].real, regs[2].real, regs[3].real); break; case 24: ret_reg.real = ((win64_variant_f_fiii *) function_pointer)(regs[0].real, regs[1].integer, regs[2].integer, regs[3].integer); break; case 25: ret_reg.real = ((win64_variant_f_fiif *) function_pointer)(regs[0].real, regs[1].integer, regs[2].integer, regs[3].real); break; case 26: ret_reg.real = ((win64_variant_f_fifi *) function_pointer)(regs[0].real, regs[1].integer, regs[2].real, regs[3].integer); break; case 27: ret_reg.real = ((win64_variant_f_fiff *) function_pointer)(regs[0].real, regs[1].integer, regs[2].real, regs[3].real); break; case 28: ret_reg.real = ((win64_variant_f_ffii *) function_pointer)(regs[0].real, regs[1].real, regs[2].integer, regs[3].integer); break; case 29: ret_reg.real = ((win64_variant_f_ffif *) function_pointer)(regs[0].real, regs[1].real, regs[2].integer, regs[3].real); break; case 30: ret_reg.real = ((win64_variant_f_fffi *) function_pointer)(regs[0].real, regs[1].real, regs[2].real, regs[3].integer); break; case 31: ret_reg.real = ((win64_variant_f_ffff *) function_pointer)(regs[0].real, regs[1].real, regs[2].real, regs[3].real); break; } return janet_ffi_read_one(ret_mem, signature->ret.type, JANET_FFI_MAX_RECUR); } #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_sandbox_assert(JANET_SANDBOX_FFI); 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, alloc_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); #elif defined(MAP_ANONYMOUS) 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, 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, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, -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, alloc_size, PAGE_EXECUTE_READ, &old)) { janet_panic("failed to make mapped memory executable"); } #else if (mprotect(ptr, alloc_size, PROT_READ | PROT_EXEC) == -1) { janet_panic("failed to make mapped memory executable"); } #endif fn->size = alloc_size; fn->function_pointer = 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_sandbox_assert(JANET_SANDBOX_FFI); janet_arity(argc, 2, -1); 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) { default: case JANET_FFI_CC_NONE: janet_panic("calling convention not supported"); #ifdef JANET_FFI_WIN64_ENABLED case JANET_FFI_CC_WIN_64: return janet_ffi_win64(signature, function_pointer, argv); #endif #ifdef JANET_FFI_SYSV64_ENABLED case JANET_FFI_CC_SYSV_64: return janet_ffi_sysv64(signature, function_pointer, argv); #endif } } JANET_CORE_FN(cfun_ffi_buffer_write, "(ffi/write ffi-type data &opt buffer)", "Append a native tyep to a buffer such as it would appear in memory. This can be used " "to pass pointers to structs in the ffi, or send C/C++/native structs over the network " "or to files. Returns a modifed buffer or a new buffer if one is not supplied.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_arity(argc, 2, 3); JanetFFIType type = decode_ffi_type(argv[0]); uint32_t el_size = (uint32_t) type_size(type); JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, el_size); janet_buffer_extra(buffer, el_size); memset(buffer->data, 0, el_size); janet_ffi_write_one(buffer->data, argv, 1, type, JANET_FFI_MAX_RECUR); buffer->count += el_size; return janet_wrap_buffer(buffer); } JANET_CORE_FN(cfun_ffi_buffer_read, "(ffi/read ffi-type bytes &opt offset)", "Parse a native struct out of a buffer and convert it to normal Janet data structures. " "This function is the inverse of `ffi/write`. `bytes` can also be a raw pointer, although " "this is unsafe.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_arity(argc, 2, 3); JanetFFIType type = decode_ffi_type(argv[0]); size_t offset = (size_t) janet_optnat(argv, argc, 2, 0); if (janet_checktype(argv[1], JANET_POINTER)) { uint8_t *ptr = janet_unwrap_pointer(argv[1]); return janet_ffi_read_one(ptr + offset, type, JANET_FFI_MAX_RECUR); } else { size_t el_size = type_size(type); JanetByteView bytes = janet_getbytes(argv, 1); if ((size_t) bytes.len < offset + el_size) janet_panic("read out of range"); return janet_ffi_read_one(bytes.bytes + offset, type, JANET_FFI_MAX_RECUR); } } JANET_CORE_FN(cfun_ffi_get_callback_trampoline, "(ffi/trampoline cc)", "Get a native function pointer that can be used as a callback and passed to C libraries. " "This callback trampoline has the signature `void trampoline(void \\*ctx, void \\*userdata)` in " "the given calling convention. This is the only function signature supported. " "It is up to the programmer to ensure that the `userdata` argument contains a janet function " "the will be called with one argument, `ctx` which is an opaque pointer. This pointer can " "be further inspected with `ffi/read`.") { janet_arity(argc, 0, 1); JanetFFICallingConvention cc = JANET_FFI_CC_DEFAULT; if (argc >= 1) cc = decode_ffi_cc(janet_getkeyword(argv, 0)); switch (cc) { default: case JANET_FFI_CC_NONE: janet_panic("calling convention not supported"); #ifdef JANET_FFI_WIN64_ENABLED case JANET_FFI_CC_WIN_64: return janet_wrap_pointer(janet_ffi_win64_standard_callback); #endif #ifdef JANET_FFI_SYSV64_ENABLED case JANET_FFI_CC_SYSV_64: return janet_wrap_pointer(janet_ffi_sysv64_standard_callback); #endif } } JANET_CORE_FN(janet_core_raw_native, "(ffi/native &opt path)", "Load a shared object or dll from the given path, and do not extract" " or run any code from it. This is different than `native`, which will " "run initialization code to get a module table. If `path` is nil, opens the current running binary. " "Returns a `core/native`.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_arity(argc, 0, 1); const char *path = janet_optcstring(argv, argc, 0, NULL); Clib lib = load_clib(path); if (!lib) janet_panic(error_clib()); JanetAbstractNative *anative = janet_abstract(&janet_native_type, sizeof(JanetAbstractNative)); anative->clib = lib; anative->closed = 0; anative->is_self = path == NULL; return janet_wrap_abstract(anative); } JANET_CORE_FN(janet_core_native_lookup, "(ffi/lookup native symbol-name)", "Lookup a symbol from a native object. All symbol lookups will return a raw pointer " "if the symbol is found, else nil.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_fixarity(argc, 2); JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type); const char *sym = janet_getcstring(argv, 1); if (anative->closed) janet_panic("native object already closed"); void *value = symbol_clib(anative->clib, sym); if (NULL == value) return janet_wrap_nil(); return janet_wrap_pointer(value); } JANET_CORE_FN(janet_core_native_close, "(ffi/close native)", "Free a native object. Dereferencing pointers to symbols in the object will have undefined " "behavior after freeing.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_fixarity(argc, 1); JanetAbstractNative *anative = janet_getabstract(argv, 0, &janet_native_type); if (anative->closed) janet_panic("native object already closed"); if (anative->is_self) janet_panic("cannot close self"); anative->closed = 1; free_clib(anative->clib); return janet_wrap_nil(); } JANET_CORE_FN(cfun_ffi_malloc, "(ffi/malloc size)", "Allocates memory directly using the system memory allocator. Memory allocated in this way must be freed manually! Returns a raw pointer, or nil if size = 0.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_fixarity(argc, 1); size_t size = janet_getsize(argv, 0); if (size == 0) return janet_wrap_nil(); return janet_wrap_pointer(malloc(size)); } JANET_CORE_FN(cfun_ffi_free, "(ffi/free pointer)", "Free memory allocated with `ffi/malloc`.") { janet_sandbox_assert(JANET_SANDBOX_FFI); janet_fixarity(argc, 1); if (janet_checktype(argv[0], JANET_NIL)) return janet_wrap_nil(); void *pointer = janet_getpointer(argv, 0); free(pointer); return janet_wrap_nil(); } void janet_lib_ffi(JanetTable *env) { JanetRegExt ffi_cfuns[] = { JANET_CORE_REG("ffi/native", janet_core_raw_native), JANET_CORE_REG("ffi/lookup", janet_core_native_lookup), JANET_CORE_REG("ffi/close", janet_core_native_close), JANET_CORE_REG("ffi/signature", cfun_ffi_signature), JANET_CORE_REG("ffi/call", cfun_ffi_call), JANET_CORE_REG("ffi/struct", cfun_ffi_struct), JANET_CORE_REG("ffi/write", cfun_ffi_buffer_write), JANET_CORE_REG("ffi/read", cfun_ffi_buffer_read), 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_CORE_REG("ffi/malloc", cfun_ffi_malloc), JANET_CORE_REG("ffi/free", cfun_ffi_free), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, ffi_cfuns); } #endif