MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

rewrite entire ffi module

+1404 -790
+36 -35
examples/ffi/sqlite/demo.js
··· 1 1 import { sqlite3 } from './sqlite3'; 2 - import { alloc, free, read, callback, freeCallback, readPtr, FFIType } from 'ant:ffi'; 2 + import { alloc, callback, FFIType } from 'ant:ffi'; 3 3 4 4 const dbPtrPtr = alloc(8); 5 5 const result = sqlite3.call('sqlite3_open', ':memory:', dbPtrPtr); 6 6 7 7 if (result !== 0) { 8 8 console.log('Failed to open database'); 9 - free(dbPtrPtr); 10 - } else { 11 - const db = read(dbPtrPtr, FFIType.pointer); 12 - free(dbPtrPtr); 9 + dbPtrPtr.free(); 10 + process.exit(0); 11 + } 13 12 14 - sqlite3.call('sqlite3_exec', db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)', 0, 0, 0); 15 - sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Alice', 30)", 0, 0, 0); 16 - sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Bob', 25)", 0, 0, 0); 17 - sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Charlie', 35)", 0, 0, 0); 13 + const db = dbPtrPtr.read(FFIType.pointer); 14 + dbPtrPtr.free(); 18 15 19 - console.log('\nQuerying users with callback:'); 16 + sqlite3.call('sqlite3_exec', db, 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)', null, null, null); 17 + sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Alice', 30)", null, null, null); 18 + sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Bob', 25)", null, null, null); 19 + sqlite3.call('sqlite3_exec', db, "INSERT INTO users (name, age) VALUES ('Charlie', 35)", null, null, null); 20 20 21 - const rowCallback = callback( 22 - function (_, argc, argv, colNames) { 23 - let row = ''; 24 - for (let i = 0; i < argc; i++) { 25 - const colNamePtr = readPtr(colNames + i * 8, FFIType.pointer); 26 - const valuePtr = readPtr(argv + i * 8, FFIType.pointer); 27 - const colName = colNamePtr ? readPtr(colNamePtr, FFIType.string) : 'NULL'; 28 - const value = valuePtr ? readPtr(valuePtr, FFIType.string) : 'NULL'; 29 - row += `${colName}=${value} `; 30 - } 31 - console.log(` Row: ${row}`); 32 - return 0; 33 - }, 34 - { 35 - args: [FFIType.pointer, FFIType.int, FFIType.pointer, FFIType.pointer], 36 - returns: FFIType.int 21 + console.log('\nQuerying users with callback:'); 22 + 23 + const rowCallback = callback( 24 + function (_, argc, argv, colNames) { 25 + let row = ''; 26 + for (let i = 0; i < argc; i++) { 27 + const colNamePtr = colNames.offset(i * 8).read(FFIType.pointer); 28 + const valuePtr = argv.offset(i * 8).read(FFIType.pointer); 29 + const colName = colNamePtr ? colNamePtr.read(FFIType.string) : 'NULL'; 30 + const value = valuePtr ? valuePtr.read(FFIType.string) : 'NULL'; 31 + row += `${colName}=${value} `; 37 32 } 38 - ); 33 + console.log(` Row: ${row}`); 34 + return 0; 35 + }, 36 + { 37 + args: [FFIType.pointer, FFIType.int, FFIType.pointer, FFIType.pointer], 38 + returns: FFIType.int 39 + } 40 + ); 39 41 40 - const execResult = sqlite3.call('sqlite3_exec', db, 'SELECT * FROM users', rowCallback, 0, 0); 42 + const execResult = sqlite3.call('sqlite3_exec', db, 'SELECT * FROM users', rowCallback, null, null); 41 43 42 - if (execResult !== 0) { 43 - console.log(`Query error: ${sqlite3.call('sqlite3_errmsg', db)}`); 44 - } 44 + if (execResult !== 0) { 45 + console.log(`Query error: ${sqlite3.call('sqlite3_errmsg', db)}`); 46 + } 45 47 46 - freeCallback(rowCallback); 47 - sqlite3.call('sqlite3_close', db); 48 - console.log('\nDatabase closed.'); 49 - } 48 + rowCallback.close(); 49 + sqlite3.call('sqlite3_close', db); 50 + console.log('\nDatabase closed.');
+20 -2
include/modules/ffi.h
··· 4 4 #include "types.h" 5 5 6 6 ant_value_t ffi_library(ant_t *js); 7 - ant_value_t ffi_call_by_index(ant_t *js, unsigned int func_index, ant_value_t *args, int nargs); 8 7 9 - #endif 8 + ant_value_t ffi_library_close(ant_t *js, ant_value_t *args, int nargs); 9 + ant_value_t ffi_library_define(ant_t *js, ant_value_t *args, int nargs); 10 + ant_value_t ffi_library_call(ant_t *js, ant_value_t *args, int nargs); 11 + ant_value_t ffi_pointer_address(ant_t *js, ant_value_t *args, int nargs); 12 + ant_value_t ffi_pointer_is_null(ant_t *js, ant_value_t *args, int nargs); 13 + ant_value_t ffi_pointer_read(ant_t *js, ant_value_t *args, int nargs); 14 + ant_value_t ffi_pointer_write(ant_t *js, ant_value_t *args, int nargs); 15 + ant_value_t ffi_pointer_offset(ant_t *js, ant_value_t *args, int nargs); 16 + ant_value_t ffi_pointer_free(ant_t *js, ant_value_t *args, int nargs); 17 + ant_value_t ffi_callback_address(ant_t *js, ant_value_t *args, int nargs); 18 + ant_value_t ffi_callback_close(ant_t *js, ant_value_t *args, int nargs); 19 + ant_value_t ffi_function_address(ant_t *js, ant_value_t *args, int nargs); 20 + ant_value_t ffi_function_call(ant_t *js, ant_value_t *args, int nargs); 21 + 22 + void ffi_library_finalize(ant_t *js, ant_object_t *obj); 23 + void ffi_function_finalize(ant_t *js, ant_object_t *obj); 24 + void ffi_pointer_finalize(ant_t *js, ant_object_t *obj); 25 + void ffi_callback_finalize(ant_t *js, ant_object_t *obj); 26 + 27 + #endif
-1
src/ant.c
··· 15241 15241 ant_value_t *args, int nargs 15242 15242 ) { 15243 15243 if (vtype(func) == T_CFUNC) return sv_call_cfunc(js, args, nargs, func, this_val); 15244 - if (vtype(func) == T_FFI) return ffi_call_by_index(js, (unsigned int)vdata(func), args, nargs); 15245 15244 15246 15245 if (vtype(func) == T_FUNC) { 15247 15246 ant_value_t func_obj = js_func_obj(func);
+1218 -741
src/modules/ffi.c
··· 1 1 #ifdef _WIN32 2 2 #define WIN32_LEAN_AND_MEAN 3 3 #include <windows.h> 4 - #define dlopen(name, flags) ((void*)LoadLibraryA(name)) 5 - #define dlsym(handle, name) ((void*)GetProcAddress((HMODULE)(handle), (name))) 4 + #define dlopen(name, flags) ((void *)LoadLibraryA(name)) 5 + #define dlsym(handle, name) ((void *)GetProcAddress((HMODULE)(handle), (name))) 6 6 #define dlclose(handle) FreeLibrary((HMODULE)(handle)) 7 7 #define dlerror() "LoadLibrary failed" 8 8 #define RTLD_LAZY 0 9 9 #else 10 10 #include <dlfcn.h> 11 11 #endif 12 + 12 13 #include <ffi.h> 13 14 #include <pthread.h> 15 + #include <stdbool.h> 16 + #include <stdint.h> 17 + #include <stdio.h> 14 18 #include <stdlib.h> 15 19 #include <string.h> 16 - #include <utarray.h> 17 - #include <uthash.h> 18 20 21 + #include "ant.h" 19 22 #include "ptr.h" 20 23 #include "errors.h" 21 24 #include "gc/modules.h" 22 25 #include "internal.h" 23 26 #include "silver/engine.h" 24 27 28 + #include "modules/buffer.h" 25 29 #include "modules/ffi.h" 26 30 #include "modules/symbol.h" 27 31 28 - typedef struct ffi_lib { 29 - char name[256]; 32 + enum { 33 + FFI_LIBRARY_NATIVE_TAG = 0x4646494cu, // FFIL 34 + FFI_FUNCTION_NATIVE_TAG = 0x46464946u, // FFIF 35 + FFI_POINTER_NATIVE_TAG = 0x46464950u, // FFIP 36 + FFI_CALLBACK_NATIVE_TAG = 0x46464943u, // FFIC 37 + }; 38 + 39 + typedef enum { 40 + FFI_VALUE_VOID = 0, 41 + FFI_VALUE_INT8, 42 + FFI_VALUE_INT16, 43 + FFI_VALUE_INT, 44 + FFI_VALUE_INT64, 45 + FFI_VALUE_UINT8, 46 + FFI_VALUE_UINT16, 47 + FFI_VALUE_UINT64, 48 + FFI_VALUE_FLOAT, 49 + FFI_VALUE_DOUBLE, 50 + FFI_VALUE_POINTER, 51 + FFI_VALUE_STRING, 52 + FFI_VALUE_SPREAD, 53 + FFI_VALUE_UNKNOWN, 54 + } ffi_value_type_id_t; 55 + 56 + typedef struct { 57 + ffi_value_type_id_t id; 58 + ffi_type *ffi_type; 59 + const char *name; 60 + } ffi_marshaled_type_t; 61 + 62 + typedef struct { 63 + ffi_marshaled_type_t returns; 64 + ffi_marshaled_type_t *args; 65 + ffi_type **ffi_arg_types; 66 + size_t arg_count; 67 + size_t fixed_arg_count; 68 + bool variadic; 69 + } ffi_signature_t; 70 + 71 + typedef struct { 72 + ant_value_t obj; 30 73 void *handle; 31 - ant_value_t js_obj; 32 - UT_hash_handle hh; 33 - } ffi_lib_t; 74 + char *path; 75 + bool closed; 76 + } ffi_library_handle_t; 34 77 35 - typedef struct ffi_func { 36 - char name[256]; 37 - void *func_ptr; 78 + typedef struct { 79 + ffi_library_handle_t *library; 80 + ffi_signature_t signature; 38 81 ffi_cif cif; 39 - ffi_type **arg_types; 40 - ffi_type *ret_type; 41 - char ret_type_str[32]; 42 - int arg_count; 43 - bool is_variadic; 44 - UT_hash_handle hh; 45 - } ffi_func_t; 82 + void *func_ptr; 83 + char *symbol_name; 84 + } ffi_function_handle_t; 46 85 47 - typedef struct ffi_ptr { 48 - void *ptr; 86 + typedef struct ffi_pointer_region_s { 87 + uint8_t *ptr; 49 88 size_t size; 50 - bool is_managed; 51 - uint64_t ptr_key; 52 - UT_hash_handle hh; 53 - } ffi_ptr_t; 89 + size_t ref_count; 90 + bool size_known; 91 + bool owned; 92 + bool freed; 93 + } ffi_pointer_region_t; 94 + 95 + typedef struct { 96 + ffi_pointer_region_t *region; 97 + size_t byte_offset; 98 + } ffi_pointer_handle_t; 54 99 55 - typedef struct ffi_callback { 100 + typedef struct { 101 + ant_t *js; 102 + ant_value_t owner_obj; 103 + ffi_signature_t signature; 104 + ffi_cif cif; 56 105 ffi_closure *closure; 57 106 void *code_ptr; 58 - ffi_cif cif; 59 - ffi_type **arg_types; 60 - ffi_type *ret_type; 61 - char ret_type_str[32]; 62 - char **arg_type_strs; 63 - int arg_count; 64 - ant_t *js; 65 - ant_value_t js_func; 66 - uint64_t cb_key; 67 - UT_hash_handle hh; 68 - } ffi_callback_t; 107 + pthread_t owner_thread; 108 + bool closed; 109 + } ffi_callback_handle_t; 110 + 111 + typedef union { 112 + int8_t i8; 113 + int16_t i16; 114 + int32_t i32; 115 + int64_t i64; 116 + uint8_t u8; 117 + uint16_t u16; 118 + uint32_t u32; 119 + uint64_t u64; 120 + float f32; 121 + double f64; 122 + void *ptr; 123 + ffi_arg raw; 124 + } ffi_value_box_t; 125 + 126 + static ant_value_t g_ffi_library_proto = 0; 127 + static ant_value_t g_ffi_function_proto = 0; 128 + static ant_value_t g_ffi_pointer_proto = 0; 129 + static ant_value_t g_ffi_callback_proto = 0; 69 130 70 - enum { FFI_LIBRARY_NATIVE_TAG = 0x4646494Cu }; // FFIL 131 + static inline bool ffi_is_nullish(ant_value_t value) { 132 + return is_null(value) || is_undefined(value); 133 + } 71 134 72 - static ffi_lib_t *ffi_libraries = NULL; 73 - static ffi_ptr_t *ffi_pointers = NULL; 74 - static ffi_callback_t *ffi_callbacks = NULL; 75 - static UT_array *ffi_functions_array = NULL; 135 + static ffi_marshaled_type_t ffi_marshaled_type_unknown(void) { 136 + ffi_marshaled_type_t type = {0}; 137 + type.id = FFI_VALUE_UNKNOWN; 138 + type.ffi_type = NULL; 139 + type.name = NULL; 140 + return type; 141 + } 76 142 77 - static pthread_mutex_t ffi_libraries_mutex = PTHREAD_MUTEX_INITIALIZER; 78 - static pthread_mutex_t ffi_functions_mutex = PTHREAD_MUTEX_INITIALIZER; 79 - static pthread_mutex_t ffi_pointers_mutex = PTHREAD_MUTEX_INITIALIZER; 80 - static pthread_mutex_t ffi_callbacks_mutex = PTHREAD_MUTEX_INITIALIZER; 143 + static ffi_marshaled_type_t ffi_marshaled_type_make(ffi_value_type_id_t id, const char *name) { 144 + ffi_marshaled_type_t type = ffi_marshaled_type_unknown(); 145 + type.id = id; 146 + type.name = name; 81 147 82 - static const UT_icd ffi_func_icd = { 83 - .sz = sizeof(ffi_func_t *), 84 - .init = NULL, 85 - .copy = NULL, 86 - .dtor = NULL, 87 - }; 148 + switch (id) { 149 + case FFI_VALUE_VOID: type.ffi_type = &ffi_type_void; break; 150 + case FFI_VALUE_INT8: type.ffi_type = &ffi_type_sint8; break; 151 + case FFI_VALUE_INT16: type.ffi_type = &ffi_type_sint16; break; 152 + case FFI_VALUE_INT: type.ffi_type = &ffi_type_sint32; break; 153 + case FFI_VALUE_INT64: type.ffi_type = &ffi_type_sint64; break; 154 + case FFI_VALUE_UINT8: type.ffi_type = &ffi_type_uint8; break; 155 + case FFI_VALUE_UINT16: type.ffi_type = &ffi_type_uint16; break; 156 + case FFI_VALUE_UINT64: type.ffi_type = &ffi_type_uint64; break; 157 + case FFI_VALUE_FLOAT: type.ffi_type = &ffi_type_float; break; 158 + case FFI_VALUE_DOUBLE: type.ffi_type = &ffi_type_double; break; 159 + case FFI_VALUE_POINTER: type.ffi_type = &ffi_type_pointer; break; 160 + case FFI_VALUE_STRING: type.ffi_type = &ffi_type_pointer; break; 161 + case FFI_VALUE_SPREAD: 162 + case FFI_VALUE_UNKNOWN: 163 + type.ffi_type = NULL; 164 + break; 165 + } 88 166 89 - static ffi_type *get_ffi_type(const char *type_str); 90 - static void *js_to_ffi_value(ant_t *js, ant_value_t val, ffi_type *type, void *buffer); 167 + return type; 168 + } 91 169 92 - static ant_value_t ffi_define(ant_t *js, ant_value_t *args, int nargs); 93 - static ant_value_t ffi_lib_call(ant_t *js, ant_value_t *args, int nargs); 94 - static ant_value_t ffi_to_js_value(ant_t *js, void *val, ffi_type *type, const char *type_str); 170 + static ffi_value_type_id_t ffi_type_id_from_name(const char *name) { 171 + if (!name) return FFI_VALUE_UNKNOWN; 172 + if (strcmp(name, "void") == 0) return FFI_VALUE_VOID; 173 + if (strcmp(name, "int8") == 0) return FFI_VALUE_INT8; 174 + if (strcmp(name, "int16") == 0) return FFI_VALUE_INT16; 175 + if (strcmp(name, "int") == 0) return FFI_VALUE_INT; 176 + if (strcmp(name, "int64") == 0) return FFI_VALUE_INT64; 177 + if (strcmp(name, "uint8") == 0) return FFI_VALUE_UINT8; 178 + if (strcmp(name, "uint16") == 0) return FFI_VALUE_UINT16; 179 + if (strcmp(name, "uint64") == 0) return FFI_VALUE_UINT64; 180 + if (strcmp(name, "float") == 0) return FFI_VALUE_FLOAT; 181 + if (strcmp(name, "double") == 0) return FFI_VALUE_DOUBLE; 182 + if (strcmp(name, "pointer") == 0) return FFI_VALUE_POINTER; 183 + if (strcmp(name, "string") == 0) return FFI_VALUE_STRING; 184 + if (strcmp(name, "...") == 0) return FFI_VALUE_SPREAD; 185 + return FFI_VALUE_UNKNOWN; 186 + } 95 187 96 - static ffi_lib_t *ffi_get_library_this(ant_t *js, ant_value_t this_obj) { 97 - if (!is_object_type(this_obj) || !js_check_native_tag(this_obj, FFI_LIBRARY_NATIVE_TAG)) return NULL; 98 - return (ffi_lib_t *)js_get_native_ptr(this_obj); 188 + static ffi_marshaled_type_t ffi_marshaled_type_from_value(ant_t *js, ant_value_t value) { 189 + if (vtype(value) != T_STR) return ffi_marshaled_type_unknown(); 190 + return ffi_marshaled_type_make( 191 + ffi_type_id_from_name(js_getstr(js, value, NULL)), 192 + js_getstr(js, value, NULL) 193 + ); 99 194 } 100 195 101 - static void ffi_init_array(void) { 102 - static int initialized = 0; 103 - if (initialized) 104 - return; 105 - initialized = 1; 106 - if (!ffi_functions_array) 107 - utarray_new(ffi_functions_array, &ffi_func_icd); 196 + static void ffi_signature_cleanup(ffi_signature_t *signature) { 197 + if (!signature) return; 198 + free(signature->args); 199 + free(signature->ffi_arg_types); 200 + signature->args = NULL; 201 + signature->ffi_arg_types = NULL; 202 + signature->arg_count = 0; 203 + signature->fixed_arg_count = 0; 204 + signature->variadic = false; 205 + signature->returns = ffi_marshaled_type_unknown(); 108 206 } 109 207 110 - static ant_value_t ffi_dlopen(ant_t *js, ant_value_t *args, int nargs) { 111 - if (nargs < 1 || vtype(args[0]) != T_STR) { 112 - return js_mkerr(js, "dlopen() requires library name string"); 208 + static bool ffi_parse_signature( 209 + ant_t *js, 210 + ant_value_t value, 211 + bool allow_variadic, 212 + bool allow_string_return, 213 + ffi_signature_t *out, 214 + ant_value_t *error_out 215 + ) { 216 + ant_value_t returns_val = js_mkundef(); 217 + ant_value_t args_val = js_mkundef(); 218 + size_t arg_count = 0; 219 + 220 + memset(out, 0, sizeof(*out)); 221 + out->returns = ffi_marshaled_type_unknown(); 222 + if (error_out) *error_out = js_mkundef(); 223 + 224 + if (!is_object_type(value)) { 225 + if (error_out) *error_out = js_mkerr_typed( 226 + js, JS_ERR_TYPE, 227 + "FFI signature must be [returnType, argTypes] or { returns, args }" 228 + ); 229 + return false; 113 230 } 114 231 115 - ffi_init_array(); 232 + returns_val = js_get(js, value, "returns"); 233 + args_val = js_get(js, value, "args"); 116 234 117 - size_t lib_name_len; 118 - const char *lib_name = js_getstr(js, args[0], &lib_name_len); 235 + if (vtype(returns_val) == T_UNDEF || vtype(args_val) == T_UNDEF) { 236 + returns_val = js_get(js, value, "0"); 237 + args_val = js_get(js, value, "1"); 238 + } 119 239 120 - pthread_mutex_lock(&ffi_libraries_mutex); 240 + if (vtype(returns_val) != T_STR) { 241 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFI return type must be a string"); 242 + return false; 243 + } 121 244 122 - ffi_lib_t *lib = NULL; 123 - HASH_FIND_STR(ffi_libraries, lib_name, lib); 124 - if (lib) { 125 - ant_value_t result = lib->js_obj; 126 - pthread_mutex_unlock(&ffi_libraries_mutex); 127 - return result; 245 + if (!is_object_type(args_val)) { 246 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFI argument list must be an array-like object"); 247 + return false; 128 248 } 129 249 130 - pthread_mutex_unlock(&ffi_libraries_mutex); 250 + out->returns = ffi_marshaled_type_from_value(js, returns_val); 251 + if (out->returns.id == FFI_VALUE_UNKNOWN || out->returns.id == FFI_VALUE_SPREAD) { 252 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI return type"); 253 + return false; 254 + } 131 255 132 - void *handle = dlopen(lib_name, RTLD_LAZY); 133 - if (!handle) { 134 - return js_mkerr(js, "Failed to load library: %s", dlerror()); 256 + if (!allow_string_return && out->returns.id == FFI_VALUE_STRING) { 257 + if (error_out) *error_out = js_mkerr_typed( 258 + js, JS_ERR_TYPE, 259 + "FFICallback does not support string return values" 260 + ); 261 + return false; 135 262 } 136 263 137 - lib = (ffi_lib_t *)malloc(sizeof(ffi_lib_t)); 138 - if (!lib) { 139 - dlclose(handle); 140 - return js_mkerr(js, "Out of memory"); 264 + ant_value_t len_val = js_get(js, args_val, "length"); 265 + arg_count = vtype(len_val) == T_NUM ? (size_t)js_getnum(len_val) : 0; 266 + 267 + if (arg_count > 0) { 268 + out->args = calloc(arg_count, sizeof(*out->args)); 269 + out->ffi_arg_types = calloc(arg_count, sizeof(*out->ffi_arg_types)); 270 + if (!out->args || !out->ffi_arg_types) { 271 + free(out->args); 272 + free(out->ffi_arg_types); 273 + out->args = NULL; 274 + out->ffi_arg_types = NULL; 275 + if (error_out) *error_out = js_mkerr(js, "Out of memory"); 276 + return false; 277 + } 141 278 } 142 279 143 - strncpy(lib->name, lib_name, sizeof(lib->name) - 1); 144 - lib->name[sizeof(lib->name) - 1] = '\0'; 145 - lib->handle = handle; 280 + out->arg_count = arg_count; 281 + out->fixed_arg_count = arg_count; 282 + out->variadic = false; 146 283 147 - lib->js_obj = js_mkobj(js); 148 - js_set_native_ptr(lib->js_obj, lib); 149 - js_set_native_tag(lib->js_obj, FFI_LIBRARY_NATIVE_TAG); 150 - 151 - js_set(js, lib->js_obj, "define", js_mkfun(ffi_define)); 152 - js_set(js, lib->js_obj, "call", js_mkfun(ffi_lib_call)); 284 + for (size_t i = 0; i < arg_count; i++) { 285 + char idx[32]; 286 + ant_value_t arg_val; 287 + ffi_marshaled_type_t arg_type; 288 + 289 + snprintf(idx, sizeof(idx), "%zu", i); 290 + arg_val = js_get(js, args_val, idx); 291 + arg_type = ffi_marshaled_type_from_value(js, arg_val); 292 + 293 + if (arg_type.id == FFI_VALUE_UNKNOWN) { 294 + ffi_signature_cleanup(out); 295 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI argument type"); 296 + return false; 297 + } 298 + 299 + if (arg_type.id == FFI_VALUE_SPREAD) { 300 + if (!allow_variadic) { 301 + ffi_signature_cleanup(out); 302 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Variadic signatures are not supported here"); 303 + return false; 304 + } 305 + 306 + if (i + 1 != arg_count) { 307 + ffi_signature_cleanup(out); 308 + if (error_out) *error_out = js_mkerr_typed( 309 + js, JS_ERR_TYPE, 310 + "FFI spread marker must be the last argument type" 311 + ); 312 + return false; 313 + } 314 + 315 + out->variadic = true; 316 + out->fixed_arg_count = i; 317 + out->arg_count = i; 318 + break; 319 + } 320 + 321 + out->args[i] = arg_type; 322 + out->ffi_arg_types[i] = arg_type.ffi_type; 323 + } 153 324 154 - pthread_mutex_lock(&ffi_libraries_mutex); 155 - HASH_ADD_STR(ffi_libraries, name, lib); 156 - pthread_mutex_unlock(&ffi_libraries_mutex); 325 + return true; 326 + } 157 327 158 - return lib->js_obj; 328 + static ffi_marshaled_type_t ffi_infer_variadic_type(ant_value_t value) { 329 + if (vtype(value) == T_STR) return ffi_marshaled_type_make(FFI_VALUE_STRING, "string"); 330 + if (ffi_is_nullish(value)) return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); 331 + 332 + if (is_object_type(value) && js_check_native_tag(value, FFI_POINTER_NATIVE_TAG)) 333 + return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); 334 + 335 + if (is_object_type(value) && js_check_native_tag(value, FFI_CALLBACK_NATIVE_TAG)) 336 + return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); 337 + 338 + if (vtype(value) == T_NUM) { 339 + double number = js_getnum(value); 340 + double truncated = js_to_int32(number); 341 + if (number == truncated) return ffi_marshaled_type_make(FFI_VALUE_INT, "int"); 342 + return ffi_marshaled_type_make(FFI_VALUE_DOUBLE, "double"); 343 + } 344 + 345 + if (vtype(value) == T_BOOL) return ffi_marshaled_type_make(FFI_VALUE_INT, "int"); 346 + return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); 159 347 } 160 348 161 - static ant_value_t ffi_define(ant_t *js, ant_value_t *args, int nargs) { 162 - ant_value_t this_obj; 163 - ffi_lib_t *lib; 349 + static ffi_library_handle_t *ffi_library_data(ant_value_t value) { 350 + if (!js_check_native_tag(value, FFI_LIBRARY_NATIVE_TAG)) return NULL; 351 + return (ffi_library_handle_t *)js_get_native_ptr(value); 352 + } 164 353 165 - if (nargs < 2 || vtype(args[0]) != T_STR) { 166 - return js_mkerr(js, "define() requires function name string and signature"); 167 - } 354 + static ffi_function_handle_t *ffi_function_data(ant_value_t value) { 355 + if (!js_check_native_tag(value, FFI_FUNCTION_NATIVE_TAG)) return NULL; 356 + return (ffi_function_handle_t *)js_get_native_ptr(value); 357 + } 168 358 169 - this_obj = js_getthis(js); 170 - lib = ffi_get_library_this(js, this_obj); 171 - if (!lib) return js_mkerr(js, "Invalid library object"); 359 + static ffi_pointer_handle_t *ffi_pointer_data(ant_value_t value) { 360 + if (!js_check_native_tag(value, FFI_POINTER_NATIVE_TAG)) return NULL; 361 + return (ffi_pointer_handle_t *)js_get_native_ptr(value); 362 + } 172 363 173 - size_t func_name_len; 174 - const char *func_name = js_getstr(js, args[0], &func_name_len); 364 + static ffi_callback_handle_t *ffi_callback_data(ant_value_t value) { 365 + if (!js_check_native_tag(value, FFI_CALLBACK_NATIVE_TAG)) return NULL; 366 + return (ffi_callback_handle_t *)js_get_native_ptr(value); 367 + } 175 368 176 - ant_value_t sig = args[1]; 177 - int sig_type = vtype(sig); 178 - 179 - if (sig_type == T_STR || sig_type == T_NUM || sig_type == T_NULL || sig_type == T_UNDEF) return js_mkerr(js, 180 - "Signature must be an array [returnType, [argTypes...]] or an object {args: [...], returns: type}" 181 - ); 369 + static void ffi_library_close_handle(ffi_library_handle_t *library) { 370 + if (!library || library->closed) return; 371 + library->closed = true; 372 + if (library->handle) dlclose(library->handle); 373 + library->handle = NULL; 374 + } 182 375 183 - const char *ret_type_str; 184 - ant_value_t arg_types_arr; 185 - int arg_count; 376 + static void ffi_pointer_region_release(ffi_pointer_region_t *region) { 377 + if (!region) return; 378 + if (region->ref_count > 0) region->ref_count--; 379 + if (region->ref_count != 0) return; 380 + if (region->owned && !region->freed && region->ptr) free(region->ptr); 381 + free(region); 382 + } 186 383 187 - ant_value_t returns_val = js_get(js, sig, "returns"); 188 - ant_value_t args_val = js_get(js, sig, "args"); 384 + static bool ffi_pointer_region_free(ffi_pointer_region_t *region) { 385 + if (!region || !region->owned || region->freed) return false; 386 + if (region->ptr) free(region->ptr); 387 + region->ptr = NULL; 388 + region->freed = true; 389 + region->size = 0; 390 + region->size_known = true; 391 + return true; 392 + } 189 393 190 - if (vtype(returns_val) != T_UNDEF && vtype(args_val) != T_UNDEF) { 191 - if (vtype(returns_val) != T_STR) { 192 - return js_mkerr(js, "Return type must be a string"); 193 - } 194 - ret_type_str = js_getstr(js, returns_val, NULL); 195 - arg_types_arr = args_val; 394 + static void ffi_pointer_close_handle(ffi_pointer_handle_t *handle, bool free_region_memory) { 395 + if (!handle) return; 396 + if (handle->region && free_region_memory) (void)ffi_pointer_region_free(handle->region); 397 + ffi_pointer_region_release(handle->region); 398 + handle->region = NULL; 399 + } 196 400 197 - if (vtype(arg_types_arr) == T_STR || vtype(arg_types_arr) == T_NUM || 198 - vtype(arg_types_arr) == T_NULL || 199 - vtype(arg_types_arr) == T_UNDEF) { 200 - return js_mkerr(js, "Argument types must be an array"); 201 - } 401 + static void ffi_callback_close_handle(ffi_callback_handle_t *callback) { 402 + if (!callback || callback->closed) return; 403 + callback->closed = true; 404 + if (callback->closure) ffi_closure_free(callback->closure); 405 + callback->closure = NULL; 406 + callback->code_ptr = NULL; 407 + ffi_signature_cleanup(&callback->signature); 408 + } 202 409 203 - ant_value_t length_val = js_get(js, arg_types_arr, "length"); 204 - arg_count = (int)js_getnum(length_val); 205 - } else { 206 - ant_value_t ret_type_val = js_get(js, sig, "0"); 207 - if (vtype(ret_type_val) != T_STR) { 208 - return js_mkerr(js, "Return type must be a string"); 209 - } 410 + static uint8_t *ffi_pointer_address_raw(ffi_pointer_handle_t *handle) { 411 + if (!handle || !handle->region || handle->region->freed || !handle->region->ptr) return NULL; 412 + return handle->region->ptr + handle->byte_offset; 413 + } 210 414 211 - ret_type_str = js_getstr(js, ret_type_val, NULL); 212 - arg_types_arr = js_get(js, sig, "1"); 415 + static bool ffi_pointer_ensure_readable( 416 + ant_t *js, 417 + ffi_pointer_handle_t *handle, 418 + size_t size, 419 + const char *op, 420 + ant_value_t *error_out 421 + ) { 422 + size_t remaining = 0; 213 423 214 - int arg_arr_type = vtype(arg_types_arr); 215 - if (arg_arr_type == T_STR || arg_arr_type == T_NUM || 216 - arg_arr_type == T_NULL || arg_arr_type == T_UNDEF) { 217 - return js_mkerr(js, "Argument types must be an array"); 218 - } 424 + if (error_out) *error_out = js_mkundef(); 425 + if (!handle || !handle->region) { 426 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid FFIPointer"); 427 + return false; 428 + } 219 429 220 - ant_value_t length_val = js_get(js, arg_types_arr, "length"); 221 - arg_count = (int)js_getnum(length_val); 430 + if (handle->region->freed) { 431 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has been freed"); 432 + return false; 222 433 } 223 434 224 - ffi_type *ret_type = get_ffi_type(ret_type_str); 225 - if (!ret_type) { 226 - return js_mkerr(js, "Unknown return type: %s", ret_type_str); 435 + if (!handle->region->ptr) { 436 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Cannot %s through a null pointer", op); 437 + return false; 227 438 } 228 439 229 - void *func_ptr = dlsym(lib->handle, func_name); 230 - if (!func_ptr) { 231 - return js_mkerr(js, "Function '%s' not found", func_name); 440 + if (handle->region->size_known) { 441 + if (handle->byte_offset > handle->region->size) { 442 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_RANGE, "Pointer offset is out of bounds"); 443 + return false; 444 + } 445 + 446 + remaining = handle->region->size - handle->byte_offset; 447 + if (size > remaining) { 448 + if (error_out) *error_out = js_mkerr_typed( 449 + js, JS_ERR_RANGE, 450 + "FFIPointer %s would read past the tracked allocation", 451 + op 452 + ); 453 + return false; 454 + } 232 455 } 233 456 234 - ffi_func_t *func = (ffi_func_t *)malloc(sizeof(ffi_func_t)); 235 - if (!func) { 457 + return true; 458 + } 459 + 460 + static ant_value_t ffi_make_pointer(ant_t *js, ffi_pointer_region_t *region, size_t byte_offset) { 461 + ant_value_t obj = js_mkobj(js); 462 + ffi_pointer_handle_t *handle = calloc(1, sizeof(*handle)); 463 + 464 + if (!handle) { 465 + ffi_pointer_region_release(region); 236 466 return js_mkerr(js, "Out of memory"); 237 467 } 238 468 239 - strncpy(func->name, func_name, sizeof(func->name) - 1); 240 - func->name[sizeof(func->name) - 1] = '\0'; 241 - func->func_ptr = func_ptr; 242 - func->ret_type = ret_type; 243 - strncpy(func->ret_type_str, ret_type_str, sizeof(func->ret_type_str) - 1); 244 - func->ret_type_str[sizeof(func->ret_type_str) - 1] = '\0'; 245 - func->arg_count = arg_count; 246 - func->is_variadic = false; 469 + handle->region = region; 470 + handle->byte_offset = byte_offset; 471 + if (region) region->ref_count++; 472 + 473 + if (g_ffi_pointer_proto) js_set_proto_init(obj, g_ffi_pointer_proto); 474 + js_set_native_ptr(obj, handle); 475 + js_set_native_tag(obj, FFI_POINTER_NATIVE_TAG); 476 + js_set_finalizer(obj, ffi_pointer_finalize); 247 477 248 - if (arg_count > 0) { 249 - func->arg_types = (ffi_type **)malloc(sizeof(ffi_type *) * arg_count); 250 - if (!func->arg_types) { 251 - free(func); 252 - return js_mkerr(js, "Out of memory"); 253 - } 478 + return obj; 479 + } 480 + 481 + static ant_value_t ffi_make_pointer_from_raw(ant_t *js, void *ptr) { 482 + ffi_pointer_region_t *region = calloc(1, sizeof(*region)); 483 + if (!region) return js_mkerr(js, "Out of memory"); 484 + region->ptr = (uint8_t *)ptr; 485 + region->owned = false; 486 + region->freed = false; 487 + region->size_known = false; 488 + return ffi_make_pointer(js, region, 0); 489 + } 254 490 255 - for (int i = 0; i < arg_count; i++) { 256 - char idx_str[16]; 257 - snprintf(idx_str, sizeof(idx_str), "%d", i); 258 - ant_value_t arg_type_val = js_get(js, arg_types_arr, idx_str); 491 + static ant_value_t ffi_make_pointer_or_null(ant_t *js, void *ptr) { 492 + if (!ptr) return js_mknull(); 493 + return ffi_make_pointer_from_raw(js, ptr); 494 + } 259 495 260 - if (vtype(arg_type_val) != T_STR) { 261 - free(func->arg_types); 262 - free(func); 263 - return js_mkerr(js, "Argument type must be a string"); 264 - } 496 + static bool ffi_pointer_from_js( 497 + ant_t *js, 498 + ant_value_t value, 499 + void **out, 500 + ant_value_t *error_out 501 + ) { 502 + ffi_pointer_handle_t *ptr_handle = NULL; 503 + ffi_callback_handle_t *cb_handle = NULL; 265 504 266 - const char *arg_type_str = js_getstr(js, arg_type_val, NULL); 505 + if (error_out) *error_out = js_mkundef(); 506 + if (out) *out = NULL; 267 507 268 - if (strcmp(arg_type_str, "...") == 0) { 269 - func->is_variadic = true; 270 - func->arg_count = i; 271 - break; 272 - } 508 + if (ffi_is_nullish(value)) return true; 273 509 274 - func->arg_types[i] = get_ffi_type(arg_type_str); 275 - if (!func->arg_types[i]) { 276 - free(func->arg_types); 277 - free(func); 278 - return js_mkerr(js, "Unknown argument type: %s", arg_type_str); 279 - } 510 + ptr_handle = ffi_pointer_data(value); 511 + if (ptr_handle) { 512 + if (ptr_handle->region && ptr_handle->region->freed) { 513 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has been freed"); 514 + return false; 280 515 } 281 - } else { 282 - func->arg_types = NULL; 516 + if (out) *out = (void *)ffi_pointer_address_raw(ptr_handle); 517 + return true; 283 518 } 284 519 285 - if (!func->is_variadic) { 286 - ffi_status status = 287 - ffi_prep_cif(&func->cif, FFI_DEFAULT_ABI, func->arg_count, ret_type, func->arg_types); 288 - if (status != FFI_OK) { 289 - if (func->arg_types) 290 - free(func->arg_types); 291 - free(func); 292 - return js_mkerr(js, "Failed to prepare function call (status=%d, argc=%d)", status, func->arg_count); 520 + cb_handle = ffi_callback_data(value); 521 + if (cb_handle) { 522 + if (cb_handle->closed) { 523 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFICallback has been closed"); 524 + return false; 293 525 } 526 + if (out) *out = cb_handle->code_ptr; 527 + return true; 294 528 } 295 529 296 - pthread_mutex_lock(&ffi_functions_mutex); 297 - utarray_push_back(ffi_functions_array, &func); 298 - unsigned int func_index = utarray_len(ffi_functions_array) - 1; 299 - pthread_mutex_unlock(&ffi_functions_mutex); 300 - 301 - js_set(js, this_obj, func_name, js_mkffi(func_index)); 302 - 303 - return js_mkundef(); 304 - } 305 - 306 - static ant_value_t ffi_lib_call(ant_t *js, ant_value_t *args, int nargs) { 307 - ant_value_t lib_obj = js_getthis(js); 530 + if (error_out) *error_out = js_mkerr_typed( 531 + js, JS_ERR_TYPE, 532 + "Pointer arguments require FFIPointer, FFICallback, null, or undefined" 533 + ); 308 534 309 - if (!ffi_get_library_this(js, lib_obj)) return js_mkerr(js, "Invalid library object"); 310 - if (nargs < 1 || vtype(args[0]) != T_STR) return js_mkerr(js, "call() requires function name string"); 535 + return false; 536 + } 311 537 312 - size_t func_name_len; 313 - const char *func_name = js_getstr(js, args[0], &func_name_len); 538 + static bool ffi_copy_js_string( 539 + ant_t *js, 540 + ant_value_t value, 541 + char **out, 542 + size_t *len_out, 543 + ant_value_t *error_out 544 + ) { 545 + ant_value_t str_val = js_tostring_val(js, value); 546 + const char *src = NULL; 547 + size_t len = 0; 548 + char *copy = NULL; 314 549 315 - ant_value_t ffi_val = js_get(js, lib_obj, func_name); 316 - int func_index = js_getffi(ffi_val); 317 - if (func_index < 0) return js_mkerr(js, "Function '%s' not defined", func_name); 550 + if (error_out) *error_out = js_mkundef(); 551 + if (out) *out = NULL; 552 + if (len_out) *len_out = 0; 318 553 319 - return ffi_call_by_index(js, (unsigned int)func_index, args + 1, nargs - 1); 320 - } 554 + if (is_err(str_val)) { 555 + if (error_out) *error_out = str_val; 556 + return false; 557 + } 321 558 322 - static ant_value_t ffi_call_function(ant_t *js, ffi_func_t *func, ant_value_t *args,int nargs) { 323 - if (!func->is_variadic && nargs != func->arg_count) { 324 - return js_mkerr(js, "Function '%s' expects %d arguments, got %d", func->name, func->arg_count, nargs); 559 + src = js_getstr(js, str_val, &len); 560 + if (!src) { 561 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid string value"); 562 + return false; 325 563 } 326 564 327 - if (func->is_variadic && nargs < func->arg_count) { 328 - return js_mkerr(js, "Function '%s' expects at least %d arguments, got %d", func->name, func->arg_count, nargs); 565 + copy = malloc(len + 1); 566 + if (!copy) { 567 + if (error_out) *error_out = js_mkerr(js, "Out of memory"); 568 + return false; 329 569 } 330 570 331 - #define MAX_ARGS 32 332 - void *arg_values_buf[MAX_ARGS]; 333 - ffi_arg arg_buffers_buf[MAX_ARGS]; 571 + memcpy(copy, src, len); 572 + copy[len] = '\0'; 573 + 574 + if (out) *out = copy; 575 + if (len_out) *len_out = len; 576 + 577 + return true; 578 + } 334 579 335 - int actual_arg_count = func->is_variadic ? nargs : func->arg_count; 580 + static bool ffi_value_to_c( 581 + ant_t *js, 582 + ant_value_t value, 583 + ffi_marshaled_type_t type, 584 + ffi_value_box_t *box, 585 + void **scratch_alloc, 586 + ant_value_t *error_out 587 + ) { 588 + void *ptr = NULL; 589 + char *str_copy = NULL; 336 590 337 - if (actual_arg_count > MAX_ARGS) { 338 - return js_mkerr(js, "Too many arguments"); 591 + if (error_out) *error_out = js_mkundef(); 592 + if (scratch_alloc) *scratch_alloc = NULL; 593 + 594 + switch (type.id) { 595 + case FFI_VALUE_INT8: box->i8 = (int8_t)js_getnum(value); return true; 596 + case FFI_VALUE_INT16: box->i16 = (int16_t)js_getnum(value); return true; 597 + case FFI_VALUE_INT: box->i32 = (int32_t)js_getnum(value); return true; 598 + case FFI_VALUE_INT64: box->i64 = (int64_t)js_getnum(value); return true; 599 + case FFI_VALUE_UINT8: box->u8 = (uint8_t)js_getnum(value); return true; 600 + case FFI_VALUE_UINT16: box->u16 = (uint16_t)js_getnum(value); return true; 601 + case FFI_VALUE_UINT64: box->u64 = (uint64_t)js_getnum(value); return true; 602 + case FFI_VALUE_FLOAT: box->f32 = (float)js_getnum(value); return true; 603 + case FFI_VALUE_DOUBLE: box->f64 = js_getnum(value); return true; 604 + case FFI_VALUE_POINTER: 605 + if (!ffi_pointer_from_js(js, value, &ptr, error_out)) return false; 606 + box->ptr = ptr; 607 + return true; 608 + case FFI_VALUE_STRING: 609 + if (!ffi_is_nullish(value) && ffi_pointer_from_js(js, value, &ptr, NULL)) { 610 + box->ptr = ptr; 611 + return true; 612 + } 613 + if (!ffi_copy_js_string(js, value, &str_copy, NULL, error_out)) return false; 614 + if (scratch_alloc) *scratch_alloc = str_copy; 615 + box->ptr = str_copy; 616 + return true; 617 + case FFI_VALUE_VOID: 618 + case FFI_VALUE_SPREAD: 619 + case FFI_VALUE_UNKNOWN: 620 + if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI argument conversion"); 621 + return false; 339 622 } 340 623 341 - void **arg_values = arg_values_buf; 342 - memset(arg_values, 0, sizeof(arg_values_buf)); 624 + return false; 625 + } 343 626 344 - for (int i = 0; i < actual_arg_count; i++) { 345 - arg_values[i] = &arg_buffers_buf[i]; 346 - memset(arg_values[i], 0, sizeof(ffi_arg)); 627 + static ant_value_t ffi_value_from_c(ant_t *js, const void *value, ffi_marshaled_type_t type) { 628 + switch (type.id) { 629 + case FFI_VALUE_VOID: return js_mkundef(); 630 + case FFI_VALUE_INT8: return js_mknum((double)*(const int8_t *)value); 631 + case FFI_VALUE_INT16: return js_mknum((double)*(const int16_t *)value); 632 + case FFI_VALUE_INT: return js_mknum((double)*(const int32_t *)value); 633 + case FFI_VALUE_INT64: return js_mknum((double)*(const int64_t *)value); 634 + case FFI_VALUE_UINT8: return js_mknum((double)*(const uint8_t *)value); 635 + case FFI_VALUE_UINT16: return js_mknum((double)*(const uint16_t *)value); 636 + case FFI_VALUE_UINT64: return js_mknum((double)*(const uint64_t *)value); 637 + case FFI_VALUE_FLOAT: return js_mknum((double)*(const float *)value); 638 + case FFI_VALUE_DOUBLE: return js_mknum(*(const double *)value); 639 + case FFI_VALUE_POINTER: return ffi_make_pointer_or_null(js, *(void *const *)value); 640 + case FFI_VALUE_STRING: { 641 + const char *str = *(const char *const *)value; 642 + return str ? js_mkstr(js, str, strlen(str)) : js_mknull(); 643 + } 644 + case FFI_VALUE_SPREAD: 645 + case FFI_VALUE_UNKNOWN: 646 + return js_mkundef(); 647 + } 347 648 348 - ffi_type *arg_type; 349 - if (i < func->arg_count) { 350 - arg_type = func->arg_types[i]; 351 - } else arg_type = &ffi_type_sint32; 649 + return js_mkundef(); 650 + } 352 651 353 - js_to_ffi_value(js, args[i], arg_type, arg_values[i]); 652 + static size_t ffi_marshaled_type_size(ffi_marshaled_type_t type) { 653 + switch (type.id) { 654 + case FFI_VALUE_STRING: return 1; 655 + case FFI_VALUE_VOID: 656 + case FFI_VALUE_SPREAD: 657 + case FFI_VALUE_UNKNOWN: 658 + return 0; 659 + default: 660 + return type.ffi_type ? type.ffi_type->size : 0; 354 661 } 662 + } 355 663 356 - ffi_arg result; 357 - memset(&result, 0, sizeof(result)); 664 + static void ffi_zero_return_value(void *ret, ffi_marshaled_type_t type) { 665 + size_t size = ffi_marshaled_type_size(type); 666 + if (size > 0) memset(ret, 0, size); 667 + } 668 + 669 + static ant_value_t ffi_read_from_pointer(ant_t *js, ffi_pointer_handle_t *handle, ffi_marshaled_type_t type) { 670 + ant_value_t err = js_mkundef(); 671 + uint8_t *addr = ffi_pointer_address_raw(handle); 672 + 673 + if (type.id == FFI_VALUE_STRING) { 674 + size_t len = 0; 675 + if (!ffi_pointer_ensure_readable(js, handle, 1, "read", &err)) return err; 676 + if (handle->region && handle->region->size_known) { 677 + size_t remaining = handle->region->size - handle->byte_offset; 678 + const char *nul = memchr(addr, '\0', remaining); 679 + if (!nul) return js_mkerr_typed(js, JS_ERR_RANGE, "String read exceeded the tracked allocation"); 680 + len = (size_t)(nul - (const char *)addr); 681 + } else len = strlen((const char *)addr); 682 + return js_mkstr(js, addr, len); 683 + } 358 684 359 - if (func->is_variadic) { 360 - #define MAX_VARIADIC_ARGS 32 361 - ffi_type *all_arg_types[MAX_VARIADIC_ARGS]; 685 + if (type.id == FFI_VALUE_POINTER) { 686 + if (!ffi_pointer_ensure_readable(js, handle, sizeof(void *), "read", &err)) return err; 687 + return ffi_make_pointer_or_null(js, *(void **)addr); 688 + } 362 689 363 - if (actual_arg_count > MAX_VARIADIC_ARGS) { 364 - return js_mkerr(js, "Too many variadic arguments"); 365 - } 690 + if (!ffi_pointer_ensure_readable(js, handle, ffi_marshaled_type_size(type), "read", &err)) return err; 691 + return ffi_value_from_c(js, addr, type); 692 + } 366 693 367 - for (int i = 0; i < func->arg_count; i++) all_arg_types[i] = func->arg_types[i]; 368 - for (int i = func->arg_count; i < actual_arg_count; i++) all_arg_types[i] = &ffi_type_sint32; 694 + static ant_value_t ffi_write_to_pointer( 695 + ant_t *js, 696 + ffi_pointer_handle_t *handle, 697 + ffi_marshaled_type_t type, 698 + ant_value_t value 699 + ) { 700 + ant_value_t err = js_mkundef(); 701 + uint8_t *addr = ffi_pointer_address_raw(handle); 702 + ffi_value_box_t box; 703 + void *scratch = NULL; 369 704 370 - ffi_cif call_cif; 371 - ffi_status status = 372 - ffi_prep_cif_var(&call_cif, FFI_DEFAULT_ABI, func->arg_count, 373 - actual_arg_count, func->ret_type, all_arg_types); 705 + memset(&box, 0, sizeof(box)); 374 706 375 - if (status != FFI_OK) { 376 - return js_mkerr(js, "Failed to prepare variadic call CIF (status=%d)", status); 707 + if (type.id == FFI_VALUE_STRING) { 708 + char *copy = NULL; 709 + size_t len = 0; 710 + if (!ffi_copy_js_string(js, value, &copy, &len, &err)) return err; 711 + if (!ffi_pointer_ensure_readable(js, handle, len + 1, "write", &err)) { 712 + free(copy); 713 + return err; 377 714 } 378 - 379 - ffi_call(&call_cif, func->func_ptr, &result, arg_values); 380 - } else { 381 - ffi_call(&func->cif, func->func_ptr, &result, arg_values); 715 + memcpy(addr, copy, len + 1); 716 + free(copy); 717 + return js_getthis(js); 382 718 } 383 719 384 - return ffi_to_js_value(js, &result, func->ret_type, func->ret_type_str); 385 - } 720 + if (!ffi_pointer_ensure_readable(js, handle, ffi_marshaled_type_size(type), "write", &err)) return err; 721 + if (!ffi_value_to_c(js, value, type, &box, &scratch, &err)) return err; 386 722 387 - ant_value_t ffi_call_by_index(ant_t *js, unsigned int func_index, ant_value_t *args, int nargs) { 388 - pthread_mutex_lock(&ffi_functions_mutex); 389 - if (func_index >= utarray_len(ffi_functions_array)) { 390 - pthread_mutex_unlock(&ffi_functions_mutex); 391 - return js_mkerr(js, "Invalid FFI function index"); 723 + switch (type.id) { 724 + case FFI_VALUE_INT8: memcpy(addr, &box.i8, sizeof(box.i8)); break; 725 + case FFI_VALUE_INT16: memcpy(addr, &box.i16, sizeof(box.i16)); break; 726 + case FFI_VALUE_INT: memcpy(addr, &box.i32, sizeof(box.i32)); break; 727 + case FFI_VALUE_INT64: memcpy(addr, &box.i64, sizeof(box.i64)); break; 728 + case FFI_VALUE_UINT8: memcpy(addr, &box.u8, sizeof(box.u8)); break; 729 + case FFI_VALUE_UINT16: memcpy(addr, &box.u16, sizeof(box.u16)); break; 730 + case FFI_VALUE_UINT64: memcpy(addr, &box.u64, sizeof(box.u64)); break; 731 + case FFI_VALUE_FLOAT: memcpy(addr, &box.f32, sizeof(box.f32)); break; 732 + case FFI_VALUE_DOUBLE: memcpy(addr, &box.f64, sizeof(box.f64)); break; 733 + case FFI_VALUE_POINTER: memcpy(addr, &box.ptr, sizeof(box.ptr)); break; 734 + default: break; 392 735 } 393 - 394 - ffi_func_t *func = *(ffi_func_t **)utarray_eltptr(ffi_functions_array, func_index); 395 - pthread_mutex_unlock(&ffi_functions_mutex); 396 - 397 - return ffi_call_function(js, func, args, nargs); 736 + 737 + free(scratch); 738 + return js_getthis(js); 398 739 } 399 740 400 - static ant_value_t ffi_alloc_memory(ant_t *js, ant_value_t *args, int nargs) { 401 - if (nargs < 1 || vtype(args[0]) != T_NUM) { 402 - return js_mkerr(js, "alloc() requires size"); 403 - } 741 + static ant_value_t ffi_make_function(ant_t *js, ffi_library_handle_t *library, const char *symbol_name, ffi_signature_t *signature, void *func_ptr) { 742 + ant_value_t obj = js_mkobj(js); 743 + ant_value_t fn = 0; 744 + ffi_function_handle_t *handle = calloc(1, sizeof(*handle)); 404 745 405 - size_t size = (size_t)js_getnum(args[0]); 406 - if (size == 0) { 407 - return js_mkerr(js, "alloc() requires non-zero size"); 746 + if (!handle) { 747 + ffi_signature_cleanup(signature); 748 + return js_mkerr(js, "Out of memory"); 408 749 } 409 750 410 - void *ptr = malloc(size); 411 - if (!ptr) { 412 - return js_mkerr(js, "alloc() failed to allocate memory"); 751 + handle->library = library; 752 + handle->signature = *signature; 753 + handle->func_ptr = func_ptr; 754 + handle->symbol_name = strdup(symbol_name); 755 + if (!handle->symbol_name) { 756 + free(handle); 757 + ffi_signature_cleanup(signature); 758 + return js_mkerr(js, "Out of memory"); 413 759 } 414 760 415 - ffi_ptr_t *ffi_ptr = (ffi_ptr_t *)malloc(sizeof(ffi_ptr_t)); 416 - if (!ffi_ptr) { 417 - free(ptr); 418 - return js_mkerr(js, "Out of memory"); 761 + if (!handle->signature.variadic) if ( 762 + ffi_prep_cif( 763 + &handle->cif, 764 + FFI_DEFAULT_ABI, 765 + (unsigned int)handle->signature.arg_count, 766 + handle->signature.returns.ffi_type, 767 + handle->signature.arg_count > 0 ? handle->signature.ffi_arg_types : NULL 768 + ) != FFI_OK) { 769 + free(handle->symbol_name); 770 + ffi_signature_cleanup(&handle->signature); 771 + free(handle); 772 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFI call interface"); 419 773 } 420 774 421 - ffi_ptr->ptr = ptr; 422 - ffi_ptr->size = size; 423 - ffi_ptr->is_managed = true; 424 - ffi_ptr->ptr_key = (uint64_t)ptr; 775 + if (g_ffi_function_proto) js_set_proto_init(obj, g_ffi_function_proto); 776 + js_set_slot(obj, SLOT_CFUNC, js_mkfun(ffi_function_call)); 777 + js_set_native_ptr(obj, handle); 778 + js_set_native_tag(obj, FFI_FUNCTION_NATIVE_TAG); 779 + js_set_slot_wb(js, obj, SLOT_ENTRIES, library ? library->obj : js_mkundef()); 780 + js_set_finalizer(obj, ffi_function_finalize); 781 + 782 + fn = js_obj_to_func(obj); 783 + return fn; 784 + } 785 + 786 + void ffi_library_finalize(ant_t *js, ant_object_t *obj) { 787 + ffi_library_handle_t *library; 788 + (void)js; 789 + if (obj->native.tag != FFI_LIBRARY_NATIVE_TAG) return; 790 + 791 + library = (ffi_library_handle_t *)obj->native.ptr; 792 + if (!library) return; 793 + 794 + ffi_library_close_handle(library); 795 + free(library->path); 796 + free(library); 797 + obj->native.ptr = NULL; 798 + obj->native.tag = 0; 799 + } 425 800 426 - pthread_mutex_lock(&ffi_pointers_mutex); 427 - HASH_ADD(hh, ffi_pointers, ptr_key, sizeof(uint64_t), ffi_ptr); 428 - pthread_mutex_unlock(&ffi_pointers_mutex); 801 + void ffi_function_finalize(ant_t *js, ant_object_t *obj) { 802 + ffi_function_handle_t *handle; 803 + (void)js; 804 + if (obj->native.tag != FFI_FUNCTION_NATIVE_TAG) return; 805 + 806 + handle = (ffi_function_handle_t *)obj->native.ptr; 807 + if (!handle) return; 808 + 809 + free(handle->symbol_name); 810 + ffi_signature_cleanup(&handle->signature); 811 + free(handle); 812 + obj->native.ptr = NULL; 813 + obj->native.tag = 0; 814 + } 429 815 430 - return js_mknum((double)ffi_ptr->ptr_key); 816 + void ffi_pointer_finalize(ant_t *js, ant_object_t *obj) { 817 + ffi_pointer_handle_t *handle; 818 + (void)js; 819 + if (obj->native.tag != FFI_POINTER_NATIVE_TAG) return; 820 + 821 + handle = (ffi_pointer_handle_t *)obj->native.ptr; 822 + if (!handle) return; 823 + 824 + ffi_pointer_close_handle(handle, false); 825 + free(handle); 826 + obj->native.ptr = NULL; 827 + obj->native.tag = 0; 431 828 } 432 829 433 - static ant_value_t ffi_free_memory(ant_t *js, ant_value_t *args, int nargs) { 434 - if (nargs < 1 || vtype(args[0]) != T_NUM) { 435 - return js_mkerr(js, "free() requires pointer"); 436 - } 830 + void ffi_callback_finalize(ant_t *js, ant_object_t *obj) { 831 + ffi_callback_handle_t *handle; 832 + (void)js; 833 + if (obj->native.tag != FFI_CALLBACK_NATIVE_TAG) return; 834 + 835 + handle = (ffi_callback_handle_t *)obj->native.ptr; 836 + if (!handle) return; 837 + 838 + ffi_callback_close_handle(handle); 839 + free(handle); 840 + obj->native.ptr = NULL; 841 + obj->native.tag = 0; 842 + } 437 843 438 - uint64_t ptr_key = (uint64_t)js_getnum(args[0]); 844 + static bool ffi_callback_result_to_c( 845 + ffi_callback_handle_t *callback, 846 + ant_value_t result, 847 + void *ret, 848 + ant_value_t *error_out 849 + ) { 850 + ffi_value_box_t box; 851 + ant_value_t err = js_mkundef(); 852 + void *scratch = NULL; 439 853 440 - pthread_mutex_lock(&ffi_pointers_mutex); 441 - ffi_ptr_t *ffi_ptr = NULL; 442 - HASH_FIND(hh, ffi_pointers, &ptr_key, sizeof(uint64_t), ffi_ptr); 854 + memset(&box, 0, sizeof(box)); 855 + if (error_out) *error_out = js_mkundef(); 856 + if (callback->signature.returns.id == FFI_VALUE_VOID) return true; 443 857 444 - if (!ffi_ptr) { 445 - pthread_mutex_unlock(&ffi_pointers_mutex); 446 - return js_mkerr(js, "Invalid pointer"); 858 + if (!ffi_value_to_c(callback->js, result, callback->signature.returns, &box, &scratch, &err)) { 859 + if (error_out) *error_out = err; 860 + free(scratch); 861 + return false; 447 862 } 448 863 449 - if (ffi_ptr->is_managed) { 450 - free(ffi_ptr->ptr); 864 + switch (callback->signature.returns.id) { 865 + case FFI_VALUE_INT8: memcpy(ret, &box.i8, sizeof(box.i8)); break; 866 + case FFI_VALUE_INT16: memcpy(ret, &box.i16, sizeof(box.i16)); break; 867 + case FFI_VALUE_INT: memcpy(ret, &box.i32, sizeof(box.i32)); break; 868 + case FFI_VALUE_INT64: memcpy(ret, &box.i64, sizeof(box.i64)); break; 869 + case FFI_VALUE_UINT8: memcpy(ret, &box.u8, sizeof(box.u8)); break; 870 + case FFI_VALUE_UINT16: memcpy(ret, &box.u16, sizeof(box.u16)); break; 871 + case FFI_VALUE_UINT64: memcpy(ret, &box.u64, sizeof(box.u64)); break; 872 + case FFI_VALUE_FLOAT: memcpy(ret, &box.f32, sizeof(box.f32)); break; 873 + case FFI_VALUE_DOUBLE: memcpy(ret, &box.f64, sizeof(box.f64)); break; 874 + case FFI_VALUE_POINTER: 875 + memcpy(ret, &box.ptr, sizeof(box.ptr)); 876 + break; 877 + default: 878 + free(scratch); 879 + return false; 451 880 } 452 881 453 - HASH_DEL(ffi_pointers, ffi_ptr); 454 - free(ffi_ptr); 455 - pthread_mutex_unlock(&ffi_pointers_mutex); 456 - 457 - return js_mkundef(); 882 + free(scratch); 883 + return true; 458 884 } 459 885 460 - static ant_value_t ffi_read_memory(ant_t *js, ant_value_t *args, int nargs) { 461 - if (nargs < 2 || vtype(args[0]) != T_NUM || vtype(args[1]) != T_STR) { 462 - return js_mkerr(js, "read() requires pointer and type"); 463 - } 886 + static void ffi_callback_trampoline(ffi_cif *cif, void *ret, void **args, void *user_data) { 887 + ffi_callback_handle_t *callback = (ffi_callback_handle_t *)user_data; 888 + ant_value_t js_args[32]; 889 + ant_value_t fn = js_mkundef(); 890 + ant_value_t result = js_mkundef(); 891 + size_t argc = 0; 464 892 465 - uint64_t ptr_key = (uint64_t)js_getnum(args[0]); 893 + (void)cif; 894 + if (!callback || callback->closed || !callback->js) return; 895 + ffi_zero_return_value(ret, callback->signature.returns); 896 + argc = callback->signature.arg_count; 466 897 467 - pthread_mutex_lock(&ffi_pointers_mutex); 468 - ffi_ptr_t *ffi_ptr = NULL; 469 - HASH_FIND(hh, ffi_pointers, &ptr_key, sizeof(uint64_t), ffi_ptr); 898 + if (!pthread_equal(pthread_self(), callback->owner_thread)) { 899 + fprintf(stderr, "ant:ffi callback invoked off the JS thread; returning a zero value\n"); 900 + return; 901 + } 470 902 471 - if (!ffi_ptr) { 472 - pthread_mutex_unlock(&ffi_pointers_mutex); 473 - return js_mkerr(js, "Invalid pointer"); 903 + if (argc > 32) argc = 32; 904 + for (size_t i = 0; i < argc; i++) { 905 + js_args[i] = ffi_value_from_c(callback->js, args[i], callback->signature.args[i]); 474 906 } 475 907 476 - void *ptr = ffi_ptr->ptr; 477 - pthread_mutex_unlock(&ffi_pointers_mutex); 908 + fn = js_get_slot(callback->owner_obj, SLOT_DATA); 909 + if (!is_callable(fn)) { 910 + fprintf(stderr, "ant:ffi callback target is no longer callable; returning a zero value\n"); 911 + return; 912 + } 478 913 479 - const char *type_str = js_getstr(js, args[1], NULL); 480 - ffi_type *type = get_ffi_type(type_str); 481 - if (!type) { 482 - return js_mkerr(js, "Unknown type: %s", type_str); 914 + result = sv_vm_call(callback->js->vm, callback->js, fn, js_mkundef(), js_args, (int)argc, NULL, false); 915 + if (is_err(result)) { 916 + fprintf(stderr, "ant:ffi callback threw an exception; returning a zero value\n"); 917 + callback->js->thrown_exists = 0; 918 + return; 483 919 } 484 920 485 - return ffi_to_js_value(js, ptr, type, type_str); 921 + if (!ffi_callback_result_to_c(callback, result, ret, NULL)) { 922 + fprintf(stderr, "ant:ffi callback returned an incompatible value; returning a zero value\n"); 923 + } 486 924 } 487 925 488 - static ant_value_t ffi_write_memory(ant_t *js, ant_value_t *args, int nargs) { 489 - if (nargs < 3 || vtype(args[0]) != T_NUM || vtype(args[1]) != T_STR) { 490 - return js_mkerr(js, "write() requires pointer, type, and value"); 491 - } 926 + static void ffi_init_prototypes(ant_t *js) { 927 + ant_value_t function_proto = 0; 492 928 493 - uint64_t ptr_key = (uint64_t)js_getnum(args[0]); 929 + if (g_ffi_library_proto) return; 494 930 495 - pthread_mutex_lock(&ffi_pointers_mutex); 496 - ffi_ptr_t *ffi_ptr = NULL; 497 - HASH_FIND(hh, ffi_pointers, &ptr_key, sizeof(uint64_t), ffi_ptr); 931 + function_proto = js_get_slot(js_glob(js), SLOT_FUNC_PROTO); 932 + if (vtype(function_proto) == T_UNDEF) function_proto = js_get_ctor_proto(js, "Function", 8); 498 933 499 - if (!ffi_ptr) { 500 - pthread_mutex_unlock(&ffi_pointers_mutex); 501 - return js_mkerr(js, "Invalid pointer"); 502 - } 934 + g_ffi_library_proto = js_mkobj(js); 935 + g_ffi_function_proto = js_mkobj(js); 936 + g_ffi_pointer_proto = js_mkobj(js); 937 + g_ffi_callback_proto = js_mkobj(js); 503 938 504 - void *ptr = ffi_ptr->ptr; 505 - pthread_mutex_unlock(&ffi_pointers_mutex); 506 - 507 - const char *type_str = js_getstr(js, args[1], NULL); 508 - ffi_type *type = get_ffi_type(type_str); 509 - if (!type) { 510 - return js_mkerr(js, "Unknown type: %s", type_str); 939 + if (is_object_type(js->sym.object_proto)) { 940 + js_set_proto_init(g_ffi_library_proto, js->sym.object_proto); 941 + js_set_proto_init(g_ffi_pointer_proto, js->sym.object_proto); 942 + js_set_proto_init(g_ffi_callback_proto, js->sym.object_proto); 511 943 } 512 944 513 - js_to_ffi_value(js, args[2], type, ptr); 945 + if (is_object_type(function_proto)) js_set_proto_init(g_ffi_function_proto, function_proto); 514 946 515 - return js_mkundef(); 516 - } 947 + js_set_sym(js, g_ffi_library_proto, get_toStringTag_sym(), ANT_STRING("FFILibrary")); 948 + js_set_sym(js, g_ffi_function_proto, get_toStringTag_sym(), ANT_STRING("FFIFunction")); 949 + js_set_sym(js, g_ffi_pointer_proto, get_toStringTag_sym(), ANT_STRING("FFIPointer")); 950 + js_set_sym(js, g_ffi_callback_proto, get_toStringTag_sym(), ANT_STRING("FFICallback")); 517 951 518 - static ant_value_t ffi_get_pointer(ant_t *js, ant_value_t *args, int nargs) { 519 - if (nargs < 1 || vtype(args[0]) != T_NUM) return js_mkerr(js, "pointer() requires pointer"); 520 - uint64_t ptr_key = (uint64_t)js_getnum(args[0]); 952 + js_set(js, g_ffi_library_proto, "define", js_mkfun(ffi_library_define)); 953 + js_set(js, g_ffi_library_proto, "call", js_mkfun(ffi_library_call)); 954 + js_set(js, g_ffi_library_proto, "close", js_mkfun(ffi_library_close)); 521 955 522 - pthread_mutex_lock(&ffi_pointers_mutex); 523 - ffi_ptr_t *ffi_ptr = NULL; 524 - HASH_FIND(hh, ffi_pointers, &ptr_key, sizeof(uint64_t), ffi_ptr); 525 - bool exists = ffi_ptr != NULL; 526 - pthread_mutex_unlock(&ffi_pointers_mutex); 956 + js_set(js, g_ffi_pointer_proto, "address", js_mkfun(ffi_pointer_address)); 957 + js_set(js, g_ffi_pointer_proto, "isNull", js_mkfun(ffi_pointer_is_null)); 958 + js_set(js, g_ffi_pointer_proto, "read", js_mkfun(ffi_pointer_read)); 959 + js_set(js, g_ffi_pointer_proto, "write", js_mkfun(ffi_pointer_write)); 960 + js_set(js, g_ffi_pointer_proto, "offset", js_mkfun(ffi_pointer_offset)); 961 + js_set(js, g_ffi_pointer_proto, "free", js_mkfun(ffi_pointer_free)); 527 962 528 - return js_bool(exists); 963 + js_set(js, g_ffi_callback_proto, "address", js_mkfun(ffi_callback_address)); 964 + js_set(js, g_ffi_callback_proto, "close", js_mkfun(ffi_callback_close)); 965 + 966 + js_set(js, g_ffi_function_proto, "address", js_mkfun(ffi_function_address)); 529 967 } 530 968 531 - static ant_value_t ffi_read_ptr(ant_t *js, ant_value_t *args, int nargs) { 532 - if (nargs < 2 || vtype(args[0]) != T_NUM || vtype(args[1]) != T_STR) { 533 - return js_mkerr(js, "readPtr() requires pointer and type"); 534 - } 969 + static ant_value_t ffi_dlopen(ant_t *js, ant_value_t *args, int nargs) { 970 + ant_value_t path_val = js_mkundef(); 971 + const char *path = NULL; 972 + 973 + size_t path_len = 0; 974 + void *dl = NULL; 975 + 976 + ffi_library_handle_t *library = NULL; 977 + ant_value_t obj = 0; 978 + 979 + if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "dlopen(path) requires a library path"); 980 + ffi_init_prototypes(js); 981 + 982 + path_val = js_tostring_val(js, args[0]); 983 + if (is_err(path_val) || vtype(path_val) != T_STR) return path_val; 984 + path = js_getstr(js, path_val, &path_len); 985 + if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid library path"); 535 986 536 - void *ptr = (void *)(uint64_t)js_getnum(args[0]); 537 - if (!ptr) return js_mknull(); 987 + dl = dlopen(path, RTLD_LAZY); 988 + if (!dl) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to load library: %s", dlerror()); 538 989 539 - const char *type_str = js_getstr(js, args[1], NULL); 990 + library = calloc(1, sizeof(*library)); 991 + if (!library) { 992 + dlclose(dl); 993 + return js_mkerr(js, "Out of memory"); 994 + } 540 995 541 - if (strcmp(type_str, "string") == 0) { 542 - const char *str = (const char *)ptr; 543 - return js_mkstr(js, str, strlen(str)); 996 + library->path = malloc(path_len + 1); 997 + if (!library->path) { 998 + dlclose(dl); 999 + free(library); 1000 + return js_mkerr(js, "Out of memory"); 544 1001 } 545 1002 546 - ffi_type *type = get_ffi_type(type_str); 547 - if (!type) return js_mkerr(js, "Unknown type: %s", type_str); 1003 + memcpy(library->path, path, path_len); 1004 + library->path[path_len] = '\0'; 1005 + library->handle = dl; 1006 + library->closed = false; 548 1007 549 - return ffi_to_js_value(js, ptr, type, type_str); 1008 + obj = js_mkobj(js); 1009 + if (g_ffi_library_proto) js_set_proto_init(obj, g_ffi_library_proto); 1010 + library->obj = obj; 1011 + js_set_native_ptr(obj, library); 1012 + js_set_native_tag(obj, FFI_LIBRARY_NATIVE_TAG); 1013 + js_set_finalizer(obj, ffi_library_finalize); 1014 + return obj; 550 1015 } 551 1016 552 - static void ffi_callback_handler(ffi_cif *cif, void *ret, void **args, void *user_data) { 553 - (void)cif; 554 - ffi_callback_t *cb = (ffi_callback_t *)user_data; 555 - ant_t *js = cb->js; 1017 + ant_value_t ffi_library_close(ant_t *js, ant_value_t *args, int nargs) { 1018 + ffi_library_handle_t *library = ffi_library_data(js_getthis(js)); 1019 + (void)args; 1020 + (void)nargs; 1021 + if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); 1022 + ffi_library_close_handle(library); 1023 + return js_getthis(js); 1024 + } 556 1025 557 - ant_value_t js_args[32]; 558 - int arg_count = cb->arg_count > 32 ? 32 : cb->arg_count; 1026 + ant_value_t ffi_library_define(ant_t *js, ant_value_t *args, int nargs) { 1027 + ffi_library_handle_t *library = ffi_library_data(js_getthis(js)); 1028 + ffi_signature_t signature; 1029 + 1030 + ant_value_t error = js_mkundef(); 1031 + ant_value_t fn = js_mkundef(); 1032 + 1033 + const char *symbol_name = NULL; 1034 + void *sym = NULL; 1035 + memset(&signature, 0, sizeof(signature)); 559 1036 560 - for (int i = 0; i < arg_count; i++) { 561 - js_args[i] = ffi_to_js_value(js, args[i], cb->arg_types[i], cb->arg_type_strs[i]); 1037 + if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); 1038 + if (library->closed) return js_mkerr_typed(js, JS_ERR_TYPE, "FFILibrary is closed"); 1039 + 1040 + if (nargs < 2 || vtype(args[0]) != T_STR) { 1041 + return js_mkerr_typed(js, JS_ERR_TYPE, "define(name, signature) requires a symbol name and signature"); 562 1042 } 563 1043 564 - ant_value_t result = sv_vm_call(js->vm, js, cb->js_func, js_mkundef(), js_args, arg_count, NULL, false); 1044 + if (!ffi_parse_signature(js, args[1], true, true, &signature, &error)) return error; 565 1045 566 - if (cb->ret_type != &ffi_type_void) { 567 - if (cb->ret_type == &ffi_type_sint8) { 568 - *(int8_t *)ret = (int8_t)js_getnum(result); 569 - } else if (cb->ret_type == &ffi_type_sint16) { 570 - *(int16_t *)ret = (int16_t)js_getnum(result); 571 - } else if (cb->ret_type == &ffi_type_sint32) { 572 - *(int32_t *)ret = (int32_t)js_getnum(result); 573 - } else if (cb->ret_type == &ffi_type_sint64) { 574 - *(int64_t *)ret = (int64_t)js_getnum(result); 575 - } else if (cb->ret_type == &ffi_type_uint8) { 576 - *(uint8_t *)ret = (uint8_t)js_getnum(result); 577 - } else if (cb->ret_type == &ffi_type_uint16) { 578 - *(uint16_t *)ret = (uint16_t)js_getnum(result); 579 - } else if (cb->ret_type == &ffi_type_uint64) { 580 - *(uint64_t *)ret = (uint64_t)js_getnum(result); 581 - } else if (cb->ret_type == &ffi_type_float) { 582 - *(float *)ret = (float)js_getnum(result); 583 - } else if (cb->ret_type == &ffi_type_double) { 584 - *(double *)ret = js_getnum(result); 585 - } else if (cb->ret_type == &ffi_type_pointer) { 586 - if (vtype(result) == T_NUM) { 587 - *(void **)ret = (void *)(uint64_t)js_getnum(result); 588 - } else { 589 - *(void **)ret = NULL; 590 - } 591 - } 1046 + symbol_name = js_getstr(js, args[0], NULL); 1047 + sym = dlsym(library->handle, symbol_name); 1048 + 1049 + if (!sym) { 1050 + ffi_signature_cleanup(&signature); 1051 + return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol '%s' was not found", symbol_name); 1052 + } 1053 + 1054 + fn = ffi_make_function(js, library, symbol_name, &signature, sym); 1055 + if (is_err(fn)) { 1056 + ffi_signature_cleanup(&signature); 1057 + return fn; 592 1058 } 1059 + 1060 + js_set(js, js_getthis(js), symbol_name, fn); 1061 + return fn; 593 1062 } 594 1063 595 - static ant_value_t ffi_create_callback(ant_t *js, ant_value_t *args, int nargs) { 596 - if (nargs < 2) return js_mkerr(js, "callback() requires function and signature"); 1064 + ant_value_t ffi_library_call(ant_t *js, ant_value_t *args, int nargs) { 1065 + ant_value_t fn = js_mkundef(); 1066 + ffi_library_handle_t *library = ffi_library_data(js_getthis(js)); 597 1067 598 - ant_value_t js_func = args[0]; 599 - ant_value_t sig = args[1]; 600 - int sig_type = vtype(sig); 601 - if (sig_type == T_STR || sig_type == T_NUM || sig_type == T_NULL || sig_type == T_UNDEF) { 602 - return js_mkerr(js, "Signature must be an object {args: [...], returns: type}"); 1068 + if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); 1069 + if (library->closed) return js_mkerr_typed(js, JS_ERR_TYPE, "FFILibrary is closed"); 1070 + if (nargs < 1 || vtype(args[0]) != T_STR) { 1071 + return js_mkerr_typed(js, JS_ERR_TYPE, "call(name, ...args) requires a symbol name"); 603 1072 } 604 1073 605 - const char *ret_type_str; 606 - ant_value_t arg_types_arr; 607 - int arg_count; 608 - 609 - ant_value_t returns_val = js_get(js, sig, "returns"); 610 - ant_value_t args_val = js_get(js, sig, "args"); 611 - 612 - if (vtype(returns_val) != T_UNDEF && vtype(args_val) != T_UNDEF) { 613 - if (vtype(returns_val) != T_STR) return js_mkerr(js, "Return type must be a string"); 614 - ret_type_str = js_getstr(js, returns_val, NULL); 615 - arg_types_arr = args_val; 616 - ant_value_t length_val = js_get(js, arg_types_arr, "length"); 617 - arg_count = (int)js_getnum(length_val); 618 - } else { 619 - ant_value_t ret_type_val = js_get(js, sig, "0"); 620 - if (vtype(ret_type_val) != T_STR) return js_mkerr(js, "Return type must be a string"); 621 - ret_type_str = js_getstr(js, ret_type_val, NULL); 622 - arg_types_arr = js_get(js, sig, "1"); 623 - ant_value_t length_val = js_get(js, arg_types_arr, "length"); 624 - arg_count = (int)js_getnum(length_val); 1074 + fn = js_get(js, js_getthis(js), js_getstr(js, args[0], NULL)); 1075 + if (!is_callable(fn)) { 1076 + return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol '%s' has not been defined", js_getstr(js, args[0], NULL)); 625 1077 } 626 1078 627 - ffi_type *ret_type = get_ffi_type(ret_type_str); 628 - if (!ret_type) return js_mkerr(js, "Unknown return type: %s", ret_type_str); 1079 + return sv_vm_call(js->vm, js, fn, js_mkundef(), args + 1, nargs - 1, NULL, false); 1080 + } 629 1081 630 - ffi_callback_t *cb = (ffi_callback_t *)malloc(sizeof(ffi_callback_t)); 631 - if (!cb) return js_mkerr(js, "Out of memory"); 1082 + ant_value_t ffi_function_call(ant_t *js, ant_value_t *args, int nargs) { 1083 + ffi_function_handle_t *function = ffi_function_data(js->current_func); 1084 + ffi_type **call_types = NULL; 1085 + ffi_value_box_t *values = NULL; 1086 + 1087 + void **call_args = NULL; 1088 + void **scratch = NULL; 1089 + 1090 + ffi_marshaled_type_t *dynamic_types = NULL; 1091 + ffi_cif call_cif; 1092 + ffi_value_box_t result; 1093 + ant_value_t error = js_mkundef(); 1094 + 1095 + int status = FFI_OK; 1096 + size_t actual_argc = 0; 632 1097 633 - cb->js = js; 634 - cb->js_func = js_func; 635 - cb->ret_type = ret_type; 636 - strncpy(cb->ret_type_str, ret_type_str, sizeof(cb->ret_type_str) - 1); 637 - cb->ret_type_str[sizeof(cb->ret_type_str) - 1] = '\0'; 638 - cb->arg_count = arg_count; 1098 + memset(&result, 0, sizeof(result)); 639 1099 640 - if (arg_count > 0) { 641 - cb->arg_types = (ffi_type **)malloc(sizeof(ffi_type *) * arg_count); 642 - cb->arg_type_strs = (char **)malloc(sizeof(char *) * arg_count); 643 - if (!cb->arg_types || !cb->arg_type_strs) { 644 - if (cb->arg_types) free(cb->arg_types); 645 - if (cb->arg_type_strs) free(cb->arg_type_strs); 646 - free(cb); 647 - return js_mkerr(js, "Out of memory"); 648 - } 1100 + if (!function) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIFunction"); 1101 + if (!function->library || function->library->closed) { 1102 + return js_mkerr_typed(js, JS_ERR_TYPE, "FFIFunction '%s' belongs to a closed library", function->symbol_name); 1103 + } 649 1104 650 - for (int i = 0; i < arg_count; i++) { 651 - char idx_str[16]; 652 - snprintf(idx_str, sizeof(idx_str), "%d", i); 653 - ant_value_t arg_type_val = js_get(js, arg_types_arr, idx_str); 1105 + if (!function->signature.variadic && nargs != (int)function->signature.arg_count) return js_mkerr_typed( 1106 + js, JS_ERR_TYPE, 1107 + "FFIFunction '%s' expects %zu arguments, got %d", 1108 + function->symbol_name, 1109 + function->signature.arg_count, 1110 + nargs 1111 + ); 654 1112 655 - if (vtype(arg_type_val) != T_STR) { 656 - for (int j = 0; j < i; j++) free(cb->arg_type_strs[j]); 657 - free(cb->arg_types); 658 - free(cb->arg_type_strs); 659 - free(cb); 660 - return js_mkerr(js, "Argument type must be a string"); 661 - } 1113 + if (function->signature.variadic && nargs < (int)function->signature.fixed_arg_count) return js_mkerr_typed( 1114 + js, JS_ERR_TYPE, 1115 + "FFIFunction '%s' expects at least %zu arguments, got %d", 1116 + function->symbol_name, 1117 + function->signature.fixed_arg_count, 1118 + nargs 1119 + ); 662 1120 663 - const char *arg_type_str = js_getstr(js, arg_type_val, NULL); 664 - cb->arg_types[i] = get_ffi_type(arg_type_str); 665 - if (!cb->arg_types[i]) { 666 - for (int j = 0; j < i; j++) free(cb->arg_type_strs[j]); 667 - free(cb->arg_types); 668 - free(cb->arg_type_strs); 669 - free(cb); 670 - return js_mkerr(js, "Unknown argument type: %s", arg_type_str); 671 - } 672 - cb->arg_type_strs[i] = strdup(arg_type_str); 1121 + actual_argc = (size_t)nargs; 1122 + if (actual_argc > 0) { 1123 + call_types = calloc(actual_argc, sizeof(*call_types)); 1124 + values = calloc(actual_argc, sizeof(*values)); 1125 + call_args = calloc(actual_argc, sizeof(*call_args)); 1126 + scratch = calloc(actual_argc, sizeof(*scratch)); 1127 + dynamic_types = calloc(actual_argc, sizeof(*dynamic_types)); 1128 + if (!call_types || !values || !call_args || !scratch || !dynamic_types) { 1129 + error = js_mkerr(js, "Out of memory"); 1130 + goto cleanup; 673 1131 } 674 - } else { 675 - cb->arg_types = NULL; 676 - cb->arg_type_strs = NULL; 677 1132 } 678 1133 679 - ffi_status status = ffi_prep_cif(&cb->cif, FFI_DEFAULT_ABI, arg_count, ret_type, cb->arg_types); 680 - if (status != FFI_OK) { 681 - for (int i = 0; i < arg_count; i++) free(cb->arg_type_strs[i]); 682 - if (cb->arg_types) free(cb->arg_types); 683 - if (cb->arg_type_strs) free(cb->arg_type_strs); 684 - free(cb); 685 - return js_mkerr(js, "Failed to prepare callback CIF (status=%d)", status); 1134 + for (size_t i = 0; i < actual_argc; i++) { 1135 + ffi_marshaled_type_t type = i < function->signature.fixed_arg_count 1136 + ? function->signature.args[i] 1137 + : ffi_infer_variadic_type(args[i]); 1138 + 1139 + dynamic_types[i] = type; 1140 + call_types[i] = type.ffi_type; 1141 + call_args[i] = &values[i]; 1142 + 1143 + if (!ffi_value_to_c(js, args[i], type, &values[i], &scratch[i], &error)) goto cleanup; 686 1144 } 687 1145 688 - cb->closure = (ffi_closure *)ffi_closure_alloc(sizeof(ffi_closure), &cb->code_ptr); 689 - if (!cb->closure) { 690 - for (int i = 0; i < arg_count; i++) free(cb->arg_type_strs[i]); 691 - if (cb->arg_types) free(cb->arg_types); 692 - if (cb->arg_type_strs) free(cb->arg_type_strs); 693 - free(cb); 694 - return js_mkerr(js, "Failed to allocate closure"); 1146 + if (function->signature.variadic) { 1147 + status = ffi_prep_cif_var( 1148 + &call_cif, 1149 + FFI_DEFAULT_ABI, 1150 + (unsigned int)function->signature.fixed_arg_count, 1151 + (unsigned int)actual_argc, 1152 + function->signature.returns.ffi_type, 1153 + call_types 1154 + ); 1155 + 1156 + if (status != FFI_OK) { 1157 + error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare variadic FFI call"); 1158 + goto cleanup; 1159 + } 1160 + 1161 + ffi_call(&call_cif, function->func_ptr, &result, call_args); 1162 + } else ffi_call(&function->cif, function->func_ptr, &result, call_args); 1163 + 1164 + cleanup: 1165 + if (vtype(error) != T_UNDEF) { 1166 + size_t i; 1167 + for (i = 0; i < actual_argc; i++) free(scratch ? scratch[i] : NULL); 1168 + free(dynamic_types); 1169 + free(scratch); 1170 + free(call_args); 1171 + free(values); 1172 + free(call_types); 1173 + return error; 695 1174 } 696 1175 697 - status = ffi_prep_closure_loc(cb->closure, &cb->cif, ffi_callback_handler, cb, cb->code_ptr); 698 - if (status != FFI_OK) { 699 - ffi_closure_free(cb->closure); 700 - for (int i = 0; i < arg_count; i++) free(cb->arg_type_strs[i]); 701 - if (cb->arg_types) free(cb->arg_types); 702 - if (cb->arg_type_strs) free(cb->arg_type_strs); 703 - free(cb); 704 - return js_mkerr(js, "Failed to prepare closure (status=%d)", status); 1176 + { 1177 + ant_value_t out = ffi_value_from_c(js, &result, function->signature.returns); 1178 + size_t i; 1179 + for (i = 0; i < actual_argc; i++) free(scratch ? scratch[i] : NULL); 1180 + free(dynamic_types); 1181 + free(scratch); 1182 + free(call_args); 1183 + free(values); 1184 + free(call_types); 1185 + return out; 705 1186 } 1187 + } 706 1188 707 - cb->cb_key = (uint64_t)cb->code_ptr; 708 - 709 - pthread_mutex_lock(&ffi_callbacks_mutex); 710 - HASH_ADD(hh, ffi_callbacks, cb_key, sizeof(uint64_t), cb); 711 - pthread_mutex_unlock(&ffi_callbacks_mutex); 712 - 713 - return js_mknum((double)cb->cb_key); 1189 + ant_value_t ffi_function_address(ant_t *js, ant_value_t *args, int nargs) { 1190 + ffi_function_handle_t *function = ffi_function_data(js_getthis(js)); 1191 + (void)args; 1192 + (void)nargs; 1193 + if (!function) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIFunction"); 1194 + return js_mknum((double)(uintptr_t)function->func_ptr); 714 1195 } 715 1196 716 - static ant_value_t ffi_free_callback(ant_t *js, ant_value_t *args, int nargs) { 1197 + static ant_value_t ffi_alloc_memory(ant_t *js, ant_value_t *args, int nargs) { 1198 + ffi_pointer_region_t *region = NULL; 1199 + size_t size = 0; 1200 + 717 1201 if (nargs < 1 || vtype(args[0]) != T_NUM) { 718 - return js_mkerr(js, "freeCallback() requires callback pointer"); 1202 + return js_mkerr_typed(js, JS_ERR_TYPE, "alloc(size) requires a numeric size"); 719 1203 } 720 1204 721 - uint64_t cb_key = (uint64_t)js_getnum(args[0]); 1205 + size = (size_t)js_getnum(args[0]); 1206 + if (size == 0) return js_mkerr_typed(js, JS_ERR_RANGE, "alloc(size) requires a positive size"); 722 1207 723 - pthread_mutex_lock(&ffi_callbacks_mutex); 724 - ffi_callback_t *cb = NULL; 725 - HASH_FIND(hh, ffi_callbacks, &cb_key, sizeof(uint64_t), cb); 1208 + region = calloc(1, sizeof(*region)); 1209 + if (!region) return js_mkerr(js, "Out of memory"); 726 1210 727 - if (!cb) { 728 - pthread_mutex_unlock(&ffi_callbacks_mutex); 729 - return js_mkerr(js, "Invalid callback pointer"); 1211 + region->ptr = malloc(size); 1212 + if (!region->ptr) { 1213 + free(region); 1214 + return js_mkerr(js, "Out of memory"); 730 1215 } 731 1216 732 - HASH_DEL(ffi_callbacks, cb); 733 - pthread_mutex_unlock(&ffi_callbacks_mutex); 1217 + region->size = size; 1218 + region->owned = true; 1219 + region->freed = false; 1220 + region->size_known = true; 1221 + return ffi_make_pointer(js, region, 0); 1222 + } 734 1223 735 - ffi_closure_free(cb->closure); 736 - for (int i = 0; i < cb->arg_count; i++) free(cb->arg_type_strs[i]); 737 - if (cb->arg_types) free(cb->arg_types); 738 - if (cb->arg_type_strs) free(cb->arg_type_strs); 739 - free(cb); 1224 + static ant_value_t ffi_pointer_value(ant_t *js, ant_value_t *args, int nargs) { 1225 + ffi_pointer_region_t *region = NULL; 1226 + ant_value_t error = js_mkundef(); 1227 + char *str_copy = NULL; 1228 + size_t str_len = 0; 1229 + const uint8_t *buffer_bytes = NULL; 1230 + size_t buffer_len = 0; 1231 + 1232 + if (nargs < 1 || ffi_is_nullish(args[0])) return ffi_make_pointer_from_raw(js, NULL); 1233 + if (ffi_pointer_data(args[0])) return args[0]; 740 1234 741 - return js_mkundef(); 742 - } 1235 + if (ffi_callback_data(args[0])) { 1236 + void *ptr = NULL; 1237 + if (!ffi_pointer_from_js(js, args[0], &ptr, &error)) return error; 1238 + return ffi_make_pointer_or_null(js, ptr); 1239 + } 743 1240 744 - typedef enum { 745 - JS_FFI_VOID = 0, 746 - JS_FFI_INT8, 747 - JS_FFI_INT16, 748 - JS_FFI_INT, 749 - JS_FFI_INT64, 750 - JS_FFI_UINT8, 751 - JS_FFI_UINT16, 752 - JS_FFI_UINT64, 753 - JS_FFI_FLOAT, 754 - JS_FFI_DOUBLE, 755 - JS_FFI_POINTER, 756 - JS_FFI_STRING, 757 - JS_FFI_UNKNOWN, 758 - JS_FFI_COUNT 759 - } js_ffi_type_id; 1241 + region = calloc(1, sizeof(*region)); 1242 + if (!region) return js_mkerr(js, "Out of memory"); 760 1243 761 - static js_ffi_type_id get_ffi_type_id(const char *type_str) { 762 - if (!type_str) return JS_FFI_UNKNOWN; 1244 + if (buffer_source_get_bytes(js, args[0], &buffer_bytes, &buffer_len)) { 1245 + region->ptr = (uint8_t *)buffer_bytes; 1246 + region->size = buffer_len; 1247 + region->size_known = true; 1248 + region->owned = false; 1249 + region->freed = false; 1250 + return ffi_make_pointer(js, region, 0); 1251 + } 763 1252 764 - switch (type_str[0]) { 765 - case 'v': if (strcmp(type_str, "void") == 0) return JS_FFI_VOID; break; 766 - case 'i': 767 - if (type_str[1] == 'n' && type_str[2] == 't') { 768 - if (type_str[3] == '\0') return JS_FFI_INT; 769 - if (strcmp(type_str + 3, "8") == 0) return JS_FFI_INT8; 770 - if (strcmp(type_str + 3, "16") == 0) return JS_FFI_INT16; 771 - if (strcmp(type_str + 3, "64") == 0) return JS_FFI_INT64; 772 - } 773 - break; 774 - case 'u': 775 - if (type_str[1] == 'i' && type_str[2] == 'n' && type_str[3] == 't') { 776 - if (strcmp(type_str + 4, "8") == 0) return JS_FFI_UINT8; 777 - if (strcmp(type_str + 4, "16") == 0) return JS_FFI_UINT16; 778 - if (strcmp(type_str + 4, "64") == 0) return JS_FFI_UINT64; 779 - } 780 - break; 781 - case 'f': if (strcmp(type_str, "float") == 0) return JS_FFI_FLOAT; break; 782 - case 'd': if (strcmp(type_str, "double") == 0) return JS_FFI_DOUBLE; break; 783 - case 'p': if (strcmp(type_str, "pointer") == 0) return JS_FFI_POINTER; break; 784 - case 's': if (strcmp(type_str, "string") == 0) return JS_FFI_STRING; break; 1253 + if (!ffi_copy_js_string(js, args[0], &str_copy, &str_len, &error)) { 1254 + free(region); 1255 + return error; 785 1256 } 786 - return JS_FFI_UNKNOWN; 1257 + 1258 + region->ptr = (uint8_t *)str_copy; 1259 + region->size = str_len + 1; 1260 + region->size_known = true; 1261 + region->owned = true; 1262 + region->freed = false; 1263 + return ffi_make_pointer(js, region, 0); 787 1264 } 788 1265 789 - static ffi_type *get_ffi_type(const char *type_str) { 790 - static ffi_type *type_map[] = { 791 - [JS_FFI_VOID] = &ffi_type_void, 792 - [JS_FFI_INT8] = &ffi_type_sint8, 793 - [JS_FFI_INT16] = &ffi_type_sint16, 794 - [JS_FFI_INT] = &ffi_type_sint32, 795 - [JS_FFI_INT64] = &ffi_type_sint64, 796 - [JS_FFI_UINT8] = &ffi_type_uint8, 797 - [JS_FFI_UINT16] = &ffi_type_uint16, 798 - [JS_FFI_UINT64] = &ffi_type_uint64, 799 - [JS_FFI_FLOAT] = &ffi_type_float, 800 - [JS_FFI_DOUBLE] = &ffi_type_double, 801 - [JS_FFI_POINTER] = &ffi_type_pointer, 802 - [JS_FFI_STRING] = &ffi_type_pointer, 803 - [JS_FFI_UNKNOWN] = NULL, 804 - }; 1266 + ant_value_t ffi_pointer_address(ant_t *js, ant_value_t *args, int nargs) { 1267 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1268 + (void)args; 1269 + (void)nargs; 1270 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1271 + return js_mknum((double)(uintptr_t)ffi_pointer_address_raw(handle)); 1272 + } 805 1273 806 - js_ffi_type_id id = get_ffi_type_id(type_str); 807 - return type_map[id]; 1274 + ant_value_t ffi_pointer_is_null(ant_t *js, ant_value_t *args, int nargs) { 1275 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1276 + (void)args; 1277 + (void)nargs; 1278 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1279 + return js_bool(ffi_pointer_address_raw(handle) == NULL); 808 1280 } 809 1281 810 - static void *js_to_ffi_value(ant_t *js, ant_value_t val, ffi_type *type, void *buffer) { 811 - static const void *dispatch[] = { 812 - &&do_sint8, &&do_sint16, &&do_sint32, &&do_sint64, 813 - &&do_uint8, &&do_uint16, &&do_uint64, 814 - &&do_float, &&do_double, &&do_pointer, &&do_done 815 - }; 1282 + ant_value_t ffi_pointer_read(ant_t *js, ant_value_t *args, int nargs) { 1283 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1284 + ffi_marshaled_type_t type; 816 1285 817 - int idx; 818 - if (type == &ffi_type_sint8) idx = 0; 819 - else if (type == &ffi_type_sint16) idx = 1; 820 - else if (type == &ffi_type_sint32) idx = 2; 821 - else if (type == &ffi_type_sint64) idx = 3; 822 - else if (type == &ffi_type_uint8) idx = 4; 823 - else if (type == &ffi_type_uint16) idx = 5; 824 - else if (type == &ffi_type_uint64) idx = 6; 825 - else if (type == &ffi_type_float) idx = 7; 826 - else if (type == &ffi_type_double) idx = 8; 827 - else if (type == &ffi_type_pointer) idx = 9; 828 - else idx = 10; 1286 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1287 + type = nargs > 0 ? ffi_marshaled_type_from_value(js, args[0]) : ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); 1288 + if (type.id == FFI_VALUE_UNKNOWN || type.id == FFI_VALUE_SPREAD || type.id == FFI_VALUE_VOID) { 1289 + return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFIPointer.read() type"); 1290 + } 1291 + return ffi_read_from_pointer(js, handle, type); 1292 + } 829 1293 830 - goto *dispatch[idx]; 1294 + ant_value_t ffi_pointer_write(ant_t *js, ant_value_t *args, int nargs) { 1295 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1296 + ffi_marshaled_type_t type; 831 1297 832 - do_sint8: { 833 - int8_t v = (int8_t)js_getnum(val); 834 - memcpy(buffer, &v, sizeof(v)); 835 - goto do_done; 1298 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1299 + if (nargs < 2) { 1300 + return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer.write(type, value) requires a type and value"); 836 1301 } 837 - do_sint16: { 838 - int16_t v = (int16_t)js_getnum(val); 839 - memcpy(buffer, &v, sizeof(v)); 840 - goto do_done; 1302 + 1303 + type = ffi_marshaled_type_from_value(js, args[0]); 1304 + if (type.id == FFI_VALUE_UNKNOWN || type.id == FFI_VALUE_SPREAD || type.id == FFI_VALUE_VOID) { 1305 + return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFIPointer.write() type"); 841 1306 } 842 - do_sint32: { 843 - int32_t v = (int32_t)js_getnum(val); 844 - memcpy(buffer, &v, sizeof(v)); 845 - goto do_done; 1307 + 1308 + return ffi_write_to_pointer(js, handle, type, args[1]); 1309 + } 1310 + 1311 + ant_value_t ffi_pointer_offset(ant_t *js, ant_value_t *args, int nargs) { 1312 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1313 + ffi_pointer_handle_t *next = NULL; 1314 + ant_value_t out = 0; 1315 + size_t offset = 0; 1316 + 1317 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1318 + if (nargs < 1 || vtype(args[0]) != T_NUM) { 1319 + return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer.offset(bytes) requires a numeric byte offset"); 846 1320 } 847 - do_sint64: { 848 - int64_t v = (int64_t)js_getnum(val); 849 - memcpy(buffer, &v, sizeof(v)); 850 - goto do_done; 1321 + 1322 + if (js_getnum(args[0]) < 0) return js_mkerr_typed(js, JS_ERR_RANGE, "FFIPointer.offset() requires a non-negative offset"); 1323 + offset = (size_t)js_getnum(args[0]); 1324 + 1325 + if (handle->region && handle->region->size_known && handle->byte_offset + offset > handle->region->size) { 1326 + return js_mkerr_typed(js, JS_ERR_RANGE, "FFIPointer.offset() is out of bounds"); 851 1327 } 852 - do_uint8: { 853 - uint8_t v = (uint8_t)js_getnum(val); 854 - memcpy(buffer, &v, sizeof(v)); 855 - goto do_done; 1328 + 1329 + out = ffi_make_pointer(js, handle->region, handle->byte_offset + offset); 1330 + next = ffi_pointer_data(out); 1331 + if (!next) return out; 1332 + return out; 1333 + } 1334 + 1335 + ant_value_t ffi_pointer_free(ant_t *js, ant_value_t *args, int nargs) { 1336 + ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); 1337 + (void)args; 1338 + (void)nargs; 1339 + if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); 1340 + if (!handle->region || !handle->region->owned) { 1341 + return js_mkerr_typed(js, JS_ERR_TYPE, "Only owned FFIPointers can be freed"); 856 1342 } 857 - do_uint16: { 858 - uint16_t v = (uint16_t)js_getnum(val); 859 - memcpy(buffer, &v, sizeof(v)); 860 - goto do_done; 1343 + if (!ffi_pointer_region_free(handle->region)) { 1344 + return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has already been freed"); 861 1345 } 862 - do_uint64: { 863 - uint64_t v = (uint64_t)js_getnum(val); 864 - memcpy(buffer, &v, sizeof(v)); 865 - goto do_done; 1346 + return js_getthis(js); 1347 + } 1348 + 1349 + static ant_value_t ffi_create_callback(ant_t *js, ant_value_t *args, int nargs) { 1350 + ant_value_t signature_val = js_mkundef(); 1351 + ant_value_t fn = js_mkundef(); 1352 + ant_value_t obj = 0; 1353 + ffi_callback_handle_t *callback = NULL; 1354 + ant_value_t error = js_mkundef(); 1355 + 1356 + if (nargs < 2) { 1357 + return js_mkerr_typed(js, JS_ERR_TYPE, "callback(signature, fn) requires a signature and function"); 866 1358 } 867 - do_float: { 868 - float v = (float)js_getnum(val); 869 - memcpy(buffer, &v, sizeof(v)); 870 - goto do_done; 1359 + 1360 + if (is_callable(args[0]) && is_object_type(args[1])) { 1361 + fn = args[0]; 1362 + signature_val = args[1]; 1363 + } else if (is_object_type(args[0]) && is_callable(args[1])) { 1364 + signature_val = args[0]; 1365 + fn = args[1]; 1366 + } else return js_mkerr_typed(js, JS_ERR_TYPE, "callback(signature, fn) requires a signature object and callable function"); 1367 + 1368 + ffi_init_prototypes(js); 1369 + 1370 + callback = calloc(1, sizeof(*callback)); 1371 + if (!callback) return js_mkerr(js, "Out of memory"); 1372 + 1373 + if (!ffi_parse_signature(js, signature_val, false, false, &callback->signature, &error)) { 1374 + free(callback); 1375 + return error; 871 1376 } 872 - do_double: { 873 - double v = js_getnum(val); 874 - memcpy(buffer, &v, sizeof(v)); 875 - goto do_done; 1377 + 1378 + if (ffi_prep_cif( 1379 + &callback->cif, 1380 + FFI_DEFAULT_ABI, 1381 + (unsigned int)callback->signature.arg_count, 1382 + callback->signature.returns.ffi_type, 1383 + callback->signature.arg_count > 0 ? callback->signature.ffi_arg_types : NULL 1384 + ) != FFI_OK) { 1385 + ffi_signature_cleanup(&callback->signature); 1386 + free(callback); 1387 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFICallback signature"); 876 1388 } 877 - do_pointer: { 878 - if (vtype(val) == T_STR) { 879 - size_t str_len; 880 - const char *str = js_getstr(js, val, &str_len); 881 - void *ptr = (void *)str; 882 - memcpy(buffer, &ptr, sizeof(ptr)); 883 - } else { 884 - void *ptr = (void *)(uint64_t)js_getnum(val); 885 - memcpy(buffer, &ptr, sizeof(ptr)); 886 - } 887 - goto do_done; 1389 + 1390 + callback->closure = ffi_closure_alloc(sizeof(*callback->closure), &callback->code_ptr); 1391 + if (!callback->closure) { 1392 + ffi_signature_cleanup(&callback->signature); 1393 + free(callback); 1394 + return js_mkerr(js, "Failed to allocate FFICallback closure"); 888 1395 } 889 - do_done: 890 - return buffer; 891 - } 892 1396 893 - static ant_value_t ffi_to_js_value(ant_t *js, void *val, ffi_type *type, const char *type_str) { 894 - static const void *dispatch[] = { 895 - &&ret_void, &&ret_sint8, &&ret_sint16, &&ret_sint32, &&ret_sint64, 896 - &&ret_uint8, &&ret_uint16, &&ret_uint64, 897 - &&ret_float, &&ret_double, &&ret_pointer, &&ret_undef 898 - }; 1397 + if (ffi_prep_closure_loc(callback->closure, &callback->cif, ffi_callback_trampoline, callback, callback->code_ptr) != FFI_OK) { 1398 + ffi_closure_free(callback->closure); 1399 + ffi_signature_cleanup(&callback->signature); 1400 + free(callback); 1401 + return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFICallback closure"); 1402 + } 899 1403 900 - int idx; 901 - if (type == &ffi_type_void) idx = 0; 902 - else if (type == &ffi_type_sint8) idx = 1; 903 - else if (type == &ffi_type_sint16) idx = 2; 904 - else if (type == &ffi_type_sint32) idx = 3; 905 - else if (type == &ffi_type_sint64) idx = 4; 906 - else if (type == &ffi_type_uint8) idx = 5; 907 - else if (type == &ffi_type_uint16) idx = 6; 908 - else if (type == &ffi_type_uint64) idx = 7; 909 - else if (type == &ffi_type_float) idx = 8; 910 - else if (type == &ffi_type_double) idx = 9; 911 - else if (type == &ffi_type_pointer) idx = 10; 912 - else idx = 11; 1404 + obj = js_mkobj(js); 1405 + if (g_ffi_callback_proto) js_set_proto_init(obj, g_ffi_callback_proto); 1406 + callback->js = js; 1407 + callback->owner_obj = obj; 1408 + callback->owner_thread = pthread_self(); 1409 + callback->closed = false; 1410 + js_set_native_ptr(obj, callback); 1411 + js_set_native_tag(obj, FFI_CALLBACK_NATIVE_TAG); 1412 + js_set_slot_wb(js, obj, SLOT_DATA, fn); 1413 + js_set_finalizer(obj, ffi_callback_finalize); 1414 + return obj; 1415 + } 913 1416 914 - goto *dispatch[idx]; 1417 + ant_value_t ffi_callback_address(ant_t *js, ant_value_t *args, int nargs) { 1418 + ffi_callback_handle_t *callback = ffi_callback_data(js_getthis(js)); 1419 + (void)args; 1420 + (void)nargs; 1421 + if (!callback) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFICallback"); 1422 + return js_mknum((double)(uintptr_t)callback->code_ptr); 1423 + } 915 1424 916 - ret_void: 917 - return js_mkundef(); 918 - ret_sint8: 919 - return js_mknum((double)(*(int8_t *)val)); 920 - ret_sint16: 921 - return js_mknum((double)(*(int16_t *)val)); 922 - ret_sint32: 923 - return js_mknum((double)(*(int32_t *)val)); 924 - ret_sint64: 925 - return js_mknum((double)(*(int64_t *)val)); 926 - ret_uint8: 927 - return js_mknum((double)(*(uint8_t *)val)); 928 - ret_uint16: 929 - return js_mknum((double)(*(uint16_t *)val)); 930 - ret_uint64: 931 - return js_mknum((double)(*(uint64_t *)val)); 932 - ret_float: 933 - return js_mknum((double)(*(float *)val)); 934 - ret_double: 935 - return js_mknum((double)(*(double *)val)); 936 - ret_pointer: { 937 - void *ptr = *(void **)val; 938 - if (type_str && strcmp(type_str, "string") == 0 && ptr) { 939 - const char *str = (const char *)ptr; 940 - if (str && strlen(str) < 1024) return js_mkstr(js, str, strlen(str)); 941 - } 942 - return js_mknum((double)(uint64_t)ptr); 943 - } 944 - ret_undef: 945 - return js_mkundef(); 1425 + ant_value_t ffi_callback_close(ant_t *js, ant_value_t *args, int nargs) { 1426 + ffi_callback_handle_t *callback = ffi_callback_data(js_getthis(js)); 1427 + (void)args; 1428 + (void)nargs; 1429 + if (!callback) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFICallback"); 1430 + ffi_callback_close_handle(callback); 1431 + return js_getthis(js); 946 1432 } 947 1433 948 1434 ant_value_t ffi_library(ant_t *js) { 949 1435 ant_value_t ffi_obj = js_mkobj(js); 1436 + ant_value_t ffi_types = js_mkobj(js); 1437 + const char *suffix = "so"; 1438 + 1439 + ffi_init_prototypes(js); 950 1440 951 1441 js_set(js, ffi_obj, "dlopen", js_mkfun(ffi_dlopen)); 952 1442 js_set(js, ffi_obj, "alloc", js_mkfun(ffi_alloc_memory)); 953 - js_set(js, ffi_obj, "free", js_mkfun(ffi_free_memory)); 954 - js_set(js, ffi_obj, "read", js_mkfun(ffi_read_memory)); 955 - js_set(js, ffi_obj, "write", js_mkfun(ffi_write_memory)); 956 - js_set(js, ffi_obj, "pointer", js_mkfun(ffi_get_pointer)); 1443 + js_set(js, ffi_obj, "pointer", js_mkfun(ffi_pointer_value)); 957 1444 js_set(js, ffi_obj, "callback", js_mkfun(ffi_create_callback)); 958 - js_set(js, ffi_obj, "freeCallback", js_mkfun(ffi_free_callback)); 959 - js_set(js, ffi_obj, "readPtr", js_mkfun(ffi_read_ptr)); 960 1445 961 - const char *suffix; 962 1446 #ifdef __APPLE__ 963 1447 suffix = "dylib"; 964 - #elif defined(__linux__) 965 - suffix = "so"; 966 1448 #elif defined(_WIN32) 967 1449 suffix = "dll"; 968 - #else 969 - suffix = "so"; 970 1450 #endif 1451 + 971 1452 js_set(js, ffi_obj, "suffix", js_mkstr(js, suffix, strlen(suffix))); 972 1453 973 - ant_value_t ffi_types = js_mkobj(js); 974 - js_set(js, ffi_types, "void", js_mkstr(js, "void", 4)); 975 - js_set(js, ffi_types, "int8", js_mkstr(js, "int8", 4)); 976 - js_set(js, ffi_types, "int16", js_mkstr(js, "int16", 5)); 977 - js_set(js, ffi_types, "int", js_mkstr(js, "int", 3)); 978 - js_set(js, ffi_types, "int64", js_mkstr(js, "int64", 5)); 979 - js_set(js, ffi_types, "uint8", js_mkstr(js, "uint8", 5)); 980 - js_set(js, ffi_types, "uint16", js_mkstr(js, "uint16", 6)); 981 - js_set(js, ffi_types, "uint64", js_mkstr(js, "uint64", 6)); 982 - js_set(js, ffi_types, "float", js_mkstr(js, "float", 5)); 983 - js_set(js, ffi_types, "double", js_mkstr(js, "double", 6)); 984 - js_set(js, ffi_types, "pointer", js_mkstr(js, "pointer", 7)); 985 - js_set(js, ffi_types, "string", js_mkstr(js, "string", 6)); 986 - js_set(js, ffi_types, "spread", js_mkstr(js, "...", 3)); 1454 + js_set(js, ffi_types, "void", ANT_STRING("void")); 1455 + js_set(js, ffi_types, "int8", ANT_STRING("int8")); 1456 + js_set(js, ffi_types, "int16", ANT_STRING("int16")); 1457 + js_set(js, ffi_types, "int", ANT_STRING("int")); 1458 + js_set(js, ffi_types, "int64", ANT_STRING("int64")); 1459 + js_set(js, ffi_types, "uint8", ANT_STRING("uint8")); 1460 + js_set(js, ffi_types, "uint16", ANT_STRING("uint16")); 1461 + js_set(js, ffi_types, "uint64", ANT_STRING("uint64")); 1462 + js_set(js, ffi_types, "float", ANT_STRING("float")); 1463 + js_set(js, ffi_types, "double", ANT_STRING("double")); 1464 + js_set(js, ffi_types, "pointer", ANT_STRING("pointer")); 1465 + js_set(js, ffi_types, "string", ANT_STRING("string")); 1466 + js_set(js, ffi_types, "spread", ANT_STRING("...")); 1467 + 987 1468 js_set(js, ffi_obj, "FFIType", ffi_types); 988 - js_set_sym(js, ffi_obj, get_toStringTag_sym(), js_mkstr(js, "FFI", 3)); 989 - 1469 + js_set_sym(js, ffi_obj, get_toStringTag_sym(), ANT_STRING("FFI")); 1470 + 990 1471 return ffi_obj; 991 1472 } 992 1473 993 1474 void gc_mark_ffi(ant_t *js, gc_mark_fn mark) { 994 - pthread_mutex_lock(&ffi_callbacks_mutex); 995 - ffi_callback_t *cb, *tmp; 996 - HASH_ITER(hh, ffi_callbacks, cb, tmp) { 997 - mark(js, cb->js_func); 998 - } 999 - pthread_mutex_unlock(&ffi_callbacks_mutex); 1475 + (void)js; 1476 + (void)mark; 1000 1477 }
+27 -11
src/types/modules/ffi.d.ts
··· 16 16 } 17 17 18 18 type FFIType = FFITypeConstants[keyof FFITypeConstants]; 19 + type FFIValueType = Exclude<FFIType, '...'>; 19 20 20 21 interface FunctionSignature { 21 - returns: FFIType; 22 + returns: FFIValueType; 22 23 args: FFIType[]; 23 24 } 24 25 26 + interface FFIPointer { 27 + address(): number; 28 + isNull(): boolean; 29 + offset(bytes: number): FFIPointer; 30 + read(type?: FFIValueType): unknown; 31 + write(type: FFIValueType, value: unknown): FFIPointer; 32 + free(): FFIPointer; 33 + } 34 + 35 + interface FFICallback { 36 + address(): number; 37 + close(): FFICallback; 38 + } 39 + 40 + interface FFIFunction<Args extends unknown[] = unknown[], Return = unknown> { 41 + (...args: Args): Return; 42 + address(): number; 43 + } 44 + 25 45 interface FFILibrary { 26 - define(name: string, signature: FunctionSignature | [FFIType, FFIType[]]): void; 46 + define(name: string, signature: FunctionSignature | [FFIValueType, FFIType[]]): FFIFunction; 27 47 call(name: string, ...args: unknown[]): unknown; 48 + close(): FFILibrary; 28 49 } 29 50 30 51 const suffix: 'dylib' | 'so' | 'dll'; 31 52 const FFIType: FFITypeConstants; 32 53 33 - function alloc(size: number): number; 34 - function free(ptr: number): void; 35 - function read(ptr: number, size: number, type?: FFIType): unknown; 36 - function write(ptr: number, value: unknown, type?: FFIType): void; 37 - function pointer(value: unknown): number; 38 - function freeCallback(ptr: number): void; 39 - function readPtr(ptr: number): number; 40 - 41 - function callback(signature: FunctionSignature | [FFIType, FFIType[]], fn: (...args: unknown[]) => unknown): number; 54 + function alloc(size: number): FFIPointer; 55 + function pointer(value?: string | FFIPointer | FFICallback | ArrayBuffer | ArrayBufferView | null): FFIPointer; 56 + function callback(signature: FunctionSignature | [FFIValueType, FFIType[]], fn: (...args: unknown[]) => unknown): FFICallback; 57 + function callback(fn: (...args: unknown[]) => unknown, signature: FunctionSignature | [FFIValueType, FFIType[]]): FFICallback; 42 58 function dlopen<T extends object = {}>(libraryPath: string): FFILibrary & T; 43 59 }
+103
tests/test_ffi_wrappers.cjs
··· 1 + import { alloc, callback, dlopen, pointer, suffix, FFIType } from 'ant:ffi'; 2 + 3 + function assert(condition, message) { 4 + if (!condition) throw new Error(message); 5 + } 6 + 7 + function expectThrow(fn, message) { 8 + let threw = false; 9 + try { 10 + fn(); 11 + } catch (_) { 12 + threw = true; 13 + } 14 + assert(threw, message); 15 + } 16 + 17 + let libcName = `libc.${suffix}`; 18 + if (process.platform === 'darwin') libcName = 'libSystem.dylib'; 19 + if (process.platform === 'linux') libcName = 'libc.so.6'; 20 + if (process.platform === 'win32') libcName = 'msvcrt.dll'; 21 + 22 + const libc = dlopen(libcName); 23 + const abs = libc.define('abs', { 24 + args: [FFIType.int], 25 + returns: FFIType.int 26 + }); 27 + 28 + assert(typeof abs === 'function', 'define() should return a callable FFIFunction'); 29 + assert(abs.address() > 0, 'FFIFunction.address() should expose a native address'); 30 + assert(abs(-7) === 7, 'FFIFunction should be callable directly'); 31 + assert(libc.call('abs', -9) === 9, 'FFILibrary.call() should dispatch through defined wrappers'); 32 + 33 + const strlen = libc.define('strlen', { 34 + args: [FFIType.string], 35 + returns: FFIType.int 36 + }); 37 + assert(strlen('planet') === 6, 'string arguments should support borrowed JS strings for call-only use'); 38 + 39 + const strchr = libc.define('strchr', { 40 + args: [FFIType.string, FFIType.int], 41 + returns: FFIType.pointer 42 + }); 43 + 44 + const haystack = pointer('planet'); 45 + const tail = strchr(haystack, 'n'.charCodeAt(0)); 46 + assert(tail && typeof tail.read === 'function', 'pointer returns should materialize FFIPointer objects'); 47 + assert(tail.read(FFIType.string) === 'net', 'FFIPointer.read(string) should decode C strings'); 48 + assert(strchr(haystack, 'z'.charCodeAt(0)) === null, 'null pointer returns should become null'); 49 + 50 + const owned = pointer('hello ffi'); 51 + assert(owned.read(FFIType.string) === 'hello ffi', 'pointer(string) should allocate an owned C string'); 52 + owned.free(); 53 + expectThrow(() => owned.read(FFIType.string), 'reading a freed pointer should throw'); 54 + 55 + const slot = alloc(8); 56 + const namePtr = pointer('ant'); 57 + slot.write(FFIType.pointer, namePtr); 58 + const roundTrip = slot.read(FFIType.pointer); 59 + assert(roundTrip.read(FFIType.string) === 'ant', 'pointer slots should round-trip FFIPointer values'); 60 + 61 + const values = alloc(16); 62 + values.offset(0).write(FFIType.int, 4); 63 + values.offset(4).write(FFIType.int, 1); 64 + values.offset(8).write(FFIType.int, 3); 65 + values.offset(12).write(FFIType.int, 2); 66 + 67 + const qsort = libc.define('qsort', { 68 + args: [FFIType.pointer, FFIType.int, FFIType.int, FFIType.pointer], 69 + returns: FFIType.void 70 + }); 71 + 72 + const compareInts = callback( 73 + { 74 + args: [FFIType.pointer, FFIType.pointer], 75 + returns: FFIType.int 76 + }, 77 + (left, right) => left.read(FFIType.int) - right.read(FFIType.int) 78 + ); 79 + 80 + qsort(values, 4, 4, compareInts); 81 + assert(values.offset(0).read(FFIType.int) === 1, 'qsort should sort the first element'); 82 + assert(values.offset(4).read(FFIType.int) === 2, 'qsort should sort the second element'); 83 + assert(values.offset(8).read(FFIType.int) === 3, 'qsort should sort the third element'); 84 + assert(values.offset(12).read(FFIType.int) === 4, 'qsort should sort the fourth element'); 85 + 86 + compareInts.close(); 87 + expectThrow(() => libc.define('abs', { args: [FFIType.spread, FFIType.int], returns: FFIType.int }), 'invalid spread placement should throw'); 88 + 89 + const closable = dlopen(libcName); 90 + const closableAbs = closable.define('abs', { 91 + args: [FFIType.int], 92 + returns: FFIType.int 93 + }); 94 + closable.close(); 95 + expectThrow(() => closableAbs(-1), 'FFIFunctions should fail once their library is closed'); 96 + 97 + slot.free(); 98 + namePtr.free(); 99 + values.free(); 100 + libc.close(); 101 + haystack.free(); 102 + 103 + console.log('ffi-wrappers:ok');