Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455#ifdef _WIN32#define WIN32_LEAN_AND_MEAN#include <windows.h>#define dlopen(name, flags) ((void *)LoadLibraryA(name))#define dlsym(handle, name) ((void *)GetProcAddress((HMODULE)(handle), (name)))#define dlclose(handle) FreeLibrary((HMODULE)(handle))#define dlerror() "LoadLibrary failed"#define RTLD_LAZY 0#else#include <dlfcn.h>#endif
#include <ffi.h>#include <pthread.h>#include <stdbool.h>#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <string.h>
#include "ant.h"#include "cfunc.h"#include "ptr.h"#include "errors.h"#include "internal.h"#include "silver/call.h"
#include "modules/buffer.h"#include "modules/ffi.h"
static constexpr uint32_t FFI_LIBRARY_NATIVE_TAG = Ant_NativeTag("FFIL");static constexpr uint32_t FFI_FUNCTION_NATIVE_TAG = Ant_NativeTag("FFIF");static constexpr uint32_t FFI_POINTER_NATIVE_TAG = Ant_NativeTag("FFIP");static constexpr uint32_t FFI_CALLBACK_NATIVE_TAG = Ant_NativeTag("FFIC");
typedef enum { FFI_VALUE_VOID = 0, FFI_VALUE_INT8, FFI_VALUE_INT16, FFI_VALUE_INT, FFI_VALUE_INT64, FFI_VALUE_UINT8, FFI_VALUE_UINT16, FFI_VALUE_UINT64, FFI_VALUE_FLOAT, FFI_VALUE_DOUBLE, FFI_VALUE_POINTER, FFI_VALUE_STRING, FFI_VALUE_SPREAD, FFI_VALUE_UNKNOWN,} ffi_value_type_id_t;
typedef struct { ffi_value_type_id_t id; ffi_type *ffi_type; const char *name;} ffi_marshaled_type_t;
typedef struct { ffi_marshaled_type_t returns; ffi_marshaled_type_t *args; ffi_type **ffi_arg_types; size_t arg_count; size_t fixed_arg_count; bool variadic;} ffi_signature_t;
typedef struct { ant_value_t obj; void *handle; char *path; bool closed;} ffi_library_handle_t;
typedef struct { ffi_library_handle_t *library; ffi_signature_t signature; ffi_cif cif; void *func_ptr; char *symbol_name;} ffi_function_handle_t;
typedef struct ffi_pointer_region_s { uint8_t *ptr; size_t size; size_t ref_count; bool size_known; bool owned; bool freed;} ffi_pointer_region_t;
typedef struct { ffi_pointer_region_t *region; size_t byte_offset;} ffi_pointer_handle_t;
typedef struct { ant_t *js; ant_value_t owner_obj; ffi_signature_t signature; ffi_cif cif; ffi_closure *closure; void *code_ptr; pthread_t owner_thread; bool closed;} ffi_callback_handle_t;
typedef union { int8_t i8; int16_t i16; int32_t i32; int64_t i64; uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; float f32; double f64; void *ptr; ffi_arg raw;} ffi_value_box_t;
static inline bool ffi_is_nullish(ant_value_t value) { return is_null(value) || is_undefined(value);}
static ffi_marshaled_type_t ffi_marshaled_type_unknown(void) { ffi_marshaled_type_t type = {0}; type.id = FFI_VALUE_UNKNOWN; type.ffi_type = NULL; type.name = NULL; return type;}
static ffi_marshaled_type_t ffi_marshaled_type_make(ffi_value_type_id_t id, const char *name) { ffi_marshaled_type_t type = ffi_marshaled_type_unknown(); type.id = id; type.name = name;
switch (id) { case FFI_VALUE_VOID: type.ffi_type = &ffi_type_void; break; case FFI_VALUE_INT8: type.ffi_type = &ffi_type_sint8; break; case FFI_VALUE_INT16: type.ffi_type = &ffi_type_sint16; break; case FFI_VALUE_INT: type.ffi_type = &ffi_type_sint32; break; case FFI_VALUE_INT64: type.ffi_type = &ffi_type_sint64; break; case FFI_VALUE_UINT8: type.ffi_type = &ffi_type_uint8; break; case FFI_VALUE_UINT16: type.ffi_type = &ffi_type_uint16; break; case FFI_VALUE_UINT64: type.ffi_type = &ffi_type_uint64; break; case FFI_VALUE_FLOAT: type.ffi_type = &ffi_type_float; break; case FFI_VALUE_DOUBLE: type.ffi_type = &ffi_type_double; break; case FFI_VALUE_POINTER: type.ffi_type = &ffi_type_pointer; break; case FFI_VALUE_STRING: type.ffi_type = &ffi_type_pointer; break; case FFI_VALUE_SPREAD: case FFI_VALUE_UNKNOWN: type.ffi_type = NULL; break; }
return type;}
static ffi_value_type_id_t ffi_type_id_from_name(const char *name) { if (!name) return FFI_VALUE_UNKNOWN; if (strcmp(name, "void") == 0) return FFI_VALUE_VOID; if (strcmp(name, "int8") == 0) return FFI_VALUE_INT8; if (strcmp(name, "int16") == 0) return FFI_VALUE_INT16; if (strcmp(name, "int") == 0) return FFI_VALUE_INT; if (strcmp(name, "int64") == 0) return FFI_VALUE_INT64; if (strcmp(name, "uint8") == 0) return FFI_VALUE_UINT8; if (strcmp(name, "uint16") == 0) return FFI_VALUE_UINT16; if (strcmp(name, "uint64") == 0) return FFI_VALUE_UINT64; if (strcmp(name, "float") == 0) return FFI_VALUE_FLOAT; if (strcmp(name, "double") == 0) return FFI_VALUE_DOUBLE; if (strcmp(name, "pointer") == 0) return FFI_VALUE_POINTER; if (strcmp(name, "string") == 0) return FFI_VALUE_STRING; if (strcmp(name, "...") == 0) return FFI_VALUE_SPREAD; return FFI_VALUE_UNKNOWN;}
static ffi_marshaled_type_t ffi_marshaled_type_from_value(ant_t *js, ant_value_t value) { if (vtype(value) != kTypeString) return ffi_marshaled_type_unknown(); return ffi_marshaled_type_make( ffi_type_id_from_name(js_getstr(js, value, NULL)), js_getstr(js, value, NULL) );}
static void ffi_signature_cleanup(ffi_signature_t *signature) { if (!signature) return; free(signature->args); free(signature->ffi_arg_types); signature->args = NULL; signature->ffi_arg_types = NULL; signature->arg_count = 0; signature->fixed_arg_count = 0; signature->variadic = false; signature->returns = ffi_marshaled_type_unknown();}
static bool ffi_parse_signature( ant_t *js, ant_value_t value, bool allow_variadic, bool allow_string_return, ffi_signature_t *out, ant_value_t *error_out) { ant_value_t returns_val = js_mkundef(); ant_value_t args_val = js_mkundef(); size_t arg_count = 0;
memset(out, 0, sizeof(*out)); out->returns = ffi_marshaled_type_unknown(); if (error_out) *error_out = js_mkundef();
if (!is_object_type(value)) { if (error_out) *error_out = js_mkerr_typed( js, JS_ERR_TYPE, "FFI signature must be [returnType, argTypes] or { returns, args }" ); return false; }
returns_val = js_get(js, value, "returns"); args_val = js_get(js, value, "args");
if (vtype(returns_val) == kTypeUndefined || vtype(args_val) == kTypeUndefined) { returns_val = js_get(js, value, "0"); args_val = js_get(js, value, "1"); }
if (vtype(returns_val) != kTypeString) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFI return type must be a string"); return false; }
if (!is_object_type(args_val)) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFI argument list must be an array-like object"); return false; }
out->returns = ffi_marshaled_type_from_value(js, returns_val); if (out->returns.id == FFI_VALUE_UNKNOWN || out->returns.id == FFI_VALUE_SPREAD) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI return type"); return false; }
if (!allow_string_return && out->returns.id == FFI_VALUE_STRING) { if (error_out) *error_out = js_mkerr_typed( js, JS_ERR_TYPE, "FFICallback does not support string return values" ); return false; }
ant_value_t len_val = js_get(js, args_val, "length"); arg_count = vtype(len_val) == kTypeNumber ? (size_t)js_getnum(len_val) : 0;
if (arg_count > 0) { out->args = calloc(arg_count, sizeof(*out->args)); out->ffi_arg_types = calloc(arg_count, sizeof(*out->ffi_arg_types)); if (!out->args || !out->ffi_arg_types) { free(out->args); free(out->ffi_arg_types); out->args = NULL; out->ffi_arg_types = NULL; if (error_out) *error_out = js_mkerr(js, "Out of memory"); return false; } }
out->arg_count = arg_count; out->fixed_arg_count = arg_count; out->variadic = false;
for (size_t i = 0; i < arg_count; i++) { char idx[32]; ant_value_t arg_val; ffi_marshaled_type_t arg_type; snprintf(idx, sizeof(idx), "%zu", i); arg_val = js_get(js, args_val, idx); arg_type = ffi_marshaled_type_from_value(js, arg_val); if (arg_type.id == FFI_VALUE_UNKNOWN) { ffi_signature_cleanup(out); if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI argument type"); return false; } if (arg_type.id == FFI_VALUE_SPREAD) { if (!allow_variadic) { ffi_signature_cleanup(out); if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Variadic signatures are not supported here"); return false; } if (i + 1 != arg_count) { ffi_signature_cleanup(out); if (error_out) *error_out = js_mkerr_typed( js, JS_ERR_TYPE, "FFI spread marker must be the last argument type" ); return false; } out->variadic = true; out->fixed_arg_count = i; out->arg_count = i; break; } out->args[i] = arg_type; out->ffi_arg_types[i] = arg_type.ffi_type; }
return true;}
static ffi_marshaled_type_t ffi_infer_variadic_type(ant_value_t value) { if (vtype(value) == kTypeString) return ffi_marshaled_type_make(FFI_VALUE_STRING, "string"); if (ffi_is_nullish(value)) return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); if (is_object_type(value) && js_check_native_tag(value, FFI_POINTER_NATIVE_TAG)) return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); if (is_object_type(value) && js_check_native_tag(value, FFI_CALLBACK_NATIVE_TAG)) return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); if (vtype(value) == kTypeNumber) { double number = js_getnum(value); double truncated = js_to_int32(number); if (number == truncated) return ffi_marshaled_type_make(FFI_VALUE_INT, "int"); return ffi_marshaled_type_make(FFI_VALUE_DOUBLE, "double"); } if (vtype(value) == kTypeBool) return ffi_marshaled_type_make(FFI_VALUE_INT, "int"); return ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer");}
static ffi_library_handle_t *ffi_library_data(ant_value_t value) { return (ffi_library_handle_t *)js_get_native(value, FFI_LIBRARY_NATIVE_TAG);}
static ffi_function_handle_t *ffi_function_data(ant_value_t value) { return (ffi_function_handle_t *)js_get_native(value, FFI_FUNCTION_NATIVE_TAG);}
static ffi_pointer_handle_t *ffi_pointer_data(ant_value_t value) { return (ffi_pointer_handle_t *)js_get_native(value, FFI_POINTER_NATIVE_TAG);}
static ffi_callback_handle_t *ffi_callback_data(ant_value_t value) { return (ffi_callback_handle_t *)js_get_native(value, FFI_CALLBACK_NATIVE_TAG);}
static void ffi_library_close_handle(ffi_library_handle_t *library) { if (!library || library->closed) return; library->closed = true; if (library->handle) dlclose(library->handle); library->handle = NULL;}
static void ffi_pointer_region_release(ffi_pointer_region_t *region) { if (!region) return; if (region->ref_count > 0) region->ref_count--; if (region->ref_count != 0) return; if (region->owned && !region->freed && region->ptr) free(region->ptr); free(region);}
static bool ffi_pointer_region_free(ffi_pointer_region_t *region) { if (!region || !region->owned || region->freed) return false; if (region->ptr) free(region->ptr); region->ptr = NULL; region->freed = true; region->size = 0; region->size_known = true; return true;}
static void ffi_pointer_close_handle(ffi_pointer_handle_t *handle, bool free_region_memory) { if (!handle) return; if (handle->region && free_region_memory) (void)ffi_pointer_region_free(handle->region); ffi_pointer_region_release(handle->region); handle->region = NULL;}
static void ffi_callback_close_handle(ffi_callback_handle_t *callback) { if (!callback || callback->closed) return; callback->closed = true; if (callback->closure) ffi_closure_free(callback->closure); callback->closure = NULL; callback->code_ptr = NULL; ffi_signature_cleanup(&callback->signature);}
static uint8_t *ffi_pointer_address_raw(ffi_pointer_handle_t *handle) { if (!handle || !handle->region || handle->region->freed || !handle->region->ptr) return NULL; return handle->region->ptr + handle->byte_offset;}
static bool ffi_pointer_ensure_readable( ant_t *js, ffi_pointer_handle_t *handle, size_t size, const char *op, ant_value_t *error_out) { size_t remaining = 0;
if (error_out) *error_out = js_mkundef(); if (!handle || !handle->region) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid FFIPointer"); return false; }
if (handle->region->freed) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has been freed"); return false; }
if (!handle->region->ptr) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Cannot %s through a null pointer", op); return false; }
if (handle->region->size_known) { if (handle->byte_offset > handle->region->size) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_RANGE, "Pointer offset is out of bounds"); return false; } remaining = handle->region->size - handle->byte_offset; if (size > remaining) { if (error_out) *error_out = js_mkerr_typed( js, JS_ERR_RANGE, "FFIPointer %s would read past the tracked allocation", op ); return false; } }
return true;}
static ant_value_t ffi_make_pointer(ant_t *js, ffi_pointer_region_t *region, size_t byte_offset) { ant_value_t obj = Ant_Object_CreateBare(js); ffi_pointer_handle_t *handle = calloc(1, sizeof(*handle));
if (!handle) { ffi_pointer_region_release(region); return js_mkerr(js, "Out of memory"); }
handle->region = region; handle->byte_offset = byte_offset; if (region) region->ref_count++;
if (js->builtins.ffi_pointer_proto) js_set_proto_init(obj, js->builtins.ffi_pointer_proto); js_set_native(obj, handle, FFI_POINTER_NATIVE_TAG); js_set_finalizer(obj, ffi_pointer_finalize);
return obj;}
static ant_value_t ffi_make_pointer_from_raw(ant_t *js, void *ptr) { ffi_pointer_region_t *region = calloc(1, sizeof(*region)); if (!region) return js_mkerr(js, "Out of memory"); region->ptr = (uint8_t *)ptr; region->owned = false; region->freed = false; region->size_known = false; return ffi_make_pointer(js, region, 0);}
static ant_value_t ffi_make_pointer_or_null(ant_t *js, void *ptr) { if (!ptr) return js_mknull(); return ffi_make_pointer_from_raw(js, ptr);}
static bool ffi_pointer_from_js( ant_t *js, ant_value_t value, void **out, ant_value_t *error_out) { ffi_pointer_handle_t *ptr_handle = NULL; ffi_callback_handle_t *cb_handle = NULL;
if (error_out) *error_out = js_mkundef(); if (out) *out = NULL;
if (ffi_is_nullish(value)) return true;
ptr_handle = ffi_pointer_data(value); if (ptr_handle) { if (ptr_handle->region && ptr_handle->region->freed) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has been freed"); return false; } if (out) *out = (void *)ffi_pointer_address_raw(ptr_handle); return true; }
cb_handle = ffi_callback_data(value); if (cb_handle) { if (cb_handle->closed) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "FFICallback has been closed"); return false; } if (out) *out = cb_handle->code_ptr; return true; }
if (error_out) *error_out = js_mkerr_typed( js, JS_ERR_TYPE, "Pointer arguments require FFIPointer, FFICallback, null, or undefined" ); return false;}
static bool ffi_copy_js_string( ant_t *js, ant_value_t value, char **out, size_t *len_out, ant_value_t *error_out) { ant_value_t str_val = js_tostring_val(js, value); const char *src = NULL; size_t len = 0; char *copy = NULL;
if (error_out) *error_out = js_mkundef(); if (out) *out = NULL; if (len_out) *len_out = 0;
if (is_err(str_val)) { if (error_out) *error_out = str_val; return false; }
src = js_getstr(js, str_val, &len); if (!src) { if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid string value"); return false; }
copy = malloc(len + 1); if (!copy) { if (error_out) *error_out = js_mkerr(js, "Out of memory"); return false; }
memcpy(copy, src, len); copy[len] = '\0';
if (out) *out = copy; if (len_out) *len_out = len; return true;}
static bool ffi_value_to_c( ant_t *js, ant_value_t value, ffi_marshaled_type_t type, ffi_value_box_t *box, void **scratch_alloc, ant_value_t *error_out) { void *ptr = NULL; char *str_copy = NULL;
if (error_out) *error_out = js_mkundef(); if (scratch_alloc) *scratch_alloc = NULL;
switch (type.id) { case FFI_VALUE_INT8: box->i8 = (int8_t)js_getnum(value); return true; case FFI_VALUE_INT16: box->i16 = (int16_t)js_getnum(value); return true; case FFI_VALUE_INT: box->i32 = (int32_t)js_getnum(value); return true; case FFI_VALUE_INT64: box->i64 = (int64_t)js_getnum(value); return true; case FFI_VALUE_UINT8: box->u8 = (uint8_t)js_getnum(value); return true; case FFI_VALUE_UINT16: box->u16 = (uint16_t)js_getnum(value); return true; case FFI_VALUE_UINT64: box->u64 = (uint64_t)js_getnum(value); return true; case FFI_VALUE_FLOAT: box->f32 = (float)js_getnum(value); return true; case FFI_VALUE_DOUBLE: box->f64 = js_getnum(value); return true; case FFI_VALUE_POINTER: if (!ffi_pointer_from_js(js, value, &ptr, error_out)) return false; box->ptr = ptr; return true; case FFI_VALUE_STRING: if (!ffi_is_nullish(value) && ffi_pointer_from_js(js, value, &ptr, NULL)) { box->ptr = ptr; return true; } if (!ffi_copy_js_string(js, value, &str_copy, NULL, error_out)) return false; if (scratch_alloc) *scratch_alloc = str_copy; box->ptr = str_copy; return true; case FFI_VALUE_VOID: case FFI_VALUE_SPREAD: case FFI_VALUE_UNKNOWN: if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFI argument conversion"); return false; }
return false;}
static ant_value_t ffi_value_from_c(ant_t *js, const void *value, ffi_marshaled_type_t type) { switch (type.id) { case FFI_VALUE_VOID: return js_mkundef(); case FFI_VALUE_INT8: return js_mknum((double)*(const int8_t *)value); case FFI_VALUE_INT16: return js_mknum((double)*(const int16_t *)value); case FFI_VALUE_INT: return js_mknum((double)*(const int32_t *)value); case FFI_VALUE_INT64: return js_mknum((double)*(const int64_t *)value); case FFI_VALUE_UINT8: return js_mknum((double)*(const uint8_t *)value); case FFI_VALUE_UINT16: return js_mknum((double)*(const uint16_t *)value); case FFI_VALUE_UINT64: return js_mknum((double)*(const uint64_t *)value); case FFI_VALUE_FLOAT: return js_mknum((double)*(const float *)value); case FFI_VALUE_DOUBLE: return js_mknum(*(const double *)value); case FFI_VALUE_POINTER: return ffi_make_pointer_or_null(js, *(void *const *)value); case FFI_VALUE_STRING: { const char *str = *(const char *const *)value; return str ? js_mkstr(js, str, strlen(str)) : js_mknull(); } case FFI_VALUE_SPREAD: case FFI_VALUE_UNKNOWN: return js_mkundef(); }
return js_mkundef();}
static size_t ffi_marshaled_type_size(ffi_marshaled_type_t type) { switch (type.id) { case FFI_VALUE_STRING: return 1; case FFI_VALUE_VOID: case FFI_VALUE_SPREAD: case FFI_VALUE_UNKNOWN: return 0; default: return type.ffi_type ? type.ffi_type->size : 0; }}
static void ffi_zero_return_value(void *ret, ffi_marshaled_type_t type) { size_t size = ffi_marshaled_type_size(type); if (size > 0) memset(ret, 0, size);}
static ant_value_t ffi_read_from_pointer(ant_t *js, ffi_pointer_handle_t *handle, ffi_marshaled_type_t type) { ant_value_t err = js_mkundef(); uint8_t *addr = ffi_pointer_address_raw(handle);
if (type.id == FFI_VALUE_STRING) { size_t len = 0; if (!ffi_pointer_ensure_readable(js, handle, 1, "read", &err)) return err; if (handle->region && handle->region->size_known) { size_t remaining = handle->region->size - handle->byte_offset; const char *nul = memchr(addr, '\0', remaining); if (!nul) return js_mkerr_typed(js, JS_ERR_RANGE, "String read exceeded the tracked allocation"); len = (size_t)(nul - (const char *)addr); } else len = strlen((const char *)addr); return js_mkstr(js, addr, len); }
if (type.id == FFI_VALUE_POINTER) { if (!ffi_pointer_ensure_readable(js, handle, sizeof(void *), "read", &err)) return err; return ffi_make_pointer_or_null(js, *(void **)addr); }
if (!ffi_pointer_ensure_readable(js, handle, ffi_marshaled_type_size(type), "read", &err)) return err; return ffi_value_from_c(js, addr, type);}
static ant_value_t ffi_write_to_pointer( ant_t *js, ffi_pointer_handle_t *handle, ffi_marshaled_type_t type, ant_value_t value) { ant_value_t err = js_mkundef(); uint8_t *addr = ffi_pointer_address_raw(handle); ffi_value_box_t box; void *scratch = NULL;
memset(&box, 0, sizeof(box));
if (type.id == FFI_VALUE_STRING) { char *copy = NULL; size_t len = 0; if (!ffi_copy_js_string(js, value, ©, &len, &err)) return err; if (!ffi_pointer_ensure_readable(js, handle, len + 1, "write", &err)) { free(copy); return err; } memcpy(addr, copy, len + 1); free(copy); return js_getthis(js); }
if (!ffi_pointer_ensure_readable(js, handle, ffi_marshaled_type_size(type), "write", &err)) return err; if (!ffi_value_to_c(js, value, type, &box, &scratch, &err)) return err;
switch (type.id) { case FFI_VALUE_INT8: memcpy(addr, &box.i8, sizeof(box.i8)); break; case FFI_VALUE_INT16: memcpy(addr, &box.i16, sizeof(box.i16)); break; case FFI_VALUE_INT: memcpy(addr, &box.i32, sizeof(box.i32)); break; case FFI_VALUE_INT64: memcpy(addr, &box.i64, sizeof(box.i64)); break; case FFI_VALUE_UINT8: memcpy(addr, &box.u8, sizeof(box.u8)); break; case FFI_VALUE_UINT16: memcpy(addr, &box.u16, sizeof(box.u16)); break; case FFI_VALUE_UINT64: memcpy(addr, &box.u64, sizeof(box.u64)); break; case FFI_VALUE_FLOAT: memcpy(addr, &box.f32, sizeof(box.f32)); break; case FFI_VALUE_DOUBLE: memcpy(addr, &box.f64, sizeof(box.f64)); break; case FFI_VALUE_POINTER: memcpy(addr, &box.ptr, sizeof(box.ptr)); break; default: break; }
free(scratch); return js_getthis(js);}
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) { ant_value_t obj = Ant_Object_CreateBare(js); ant_value_t fn = 0; ffi_function_handle_t *handle = calloc(1, sizeof(*handle));
if (!handle) { ffi_signature_cleanup(signature); return js_mkerr(js, "Out of memory"); }
handle->library = library; handle->signature = *signature; handle->func_ptr = func_ptr; handle->symbol_name = strdup(symbol_name); if (!handle->symbol_name) { free(handle); ffi_signature_cleanup(signature); return js_mkerr(js, "Out of memory"); }
if (!handle->signature.variadic) if ( ffi_prep_cif( &handle->cif, FFI_DEFAULT_ABI, (unsigned int)handle->signature.arg_count, handle->signature.returns.ffi_type, handle->signature.arg_count > 0 ? handle->signature.ffi_arg_types : NULL ) != FFI_OK) { free(handle->symbol_name); ffi_signature_cleanup(&handle->signature); free(handle); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFI call interface"); }
if (js->builtins.ffi_function_proto) js_set_proto_init(obj, js->builtins.ffi_function_proto); js_set_slot(obj, SLOT_CFUNC, Ant_Function_Create(ffi_function_call)); js_set_native(obj, handle, FFI_FUNCTION_NATIVE_TAG); js_set_slot_wb(js, obj, SLOT_ENTRIES, library ? library->obj : js_mkundef()); js_set_finalizer(obj, ffi_function_finalize);
fn = js_obj_to_func(js, obj); return fn;}
void ffi_library_finalize(ant_t *js, ant_object_t *obj) { ffi_library_handle_t *library; ant_value_t value = js_obj_from_ptr(obj); library = (ffi_library_handle_t *)js_get_native(value, FFI_LIBRARY_NATIVE_TAG); if (!library) return; ffi_library_close_handle(library); free(library->path); free(library); js_clear_native(value, FFI_LIBRARY_NATIVE_TAG);}
void ffi_function_finalize(ant_t *js, ant_object_t *obj) { ffi_function_handle_t *handle; ant_value_t value = js_obj_from_ptr(obj); handle = (ffi_function_handle_t *)js_get_native(value, FFI_FUNCTION_NATIVE_TAG); if (!handle) return; free(handle->symbol_name); ffi_signature_cleanup(&handle->signature); free(handle); js_clear_native(value, FFI_FUNCTION_NATIVE_TAG);}
void ffi_pointer_finalize(ant_t *js, ant_object_t *obj) { ffi_pointer_handle_t *handle; ant_value_t value = js_obj_from_ptr(obj); handle = (ffi_pointer_handle_t *)js_get_native(value, FFI_POINTER_NATIVE_TAG); if (!handle) return; ffi_pointer_close_handle(handle, false); free(handle); js_clear_native(value, FFI_POINTER_NATIVE_TAG);}
void ffi_callback_finalize(ant_t *js, ant_object_t *obj) { ffi_callback_handle_t *handle; ant_value_t value = js_obj_from_ptr(obj); handle = (ffi_callback_handle_t *)js_get_native(value, FFI_CALLBACK_NATIVE_TAG); if (!handle) return; ffi_callback_close_handle(handle); free(handle); js_clear_native(value, FFI_CALLBACK_NATIVE_TAG);}
static bool ffi_callback_result_to_c( ffi_callback_handle_t *callback, ant_value_t result, void *ret, ant_value_t *error_out) { ffi_value_box_t box; ant_value_t err = js_mkundef(); void *scratch = NULL;
memset(&box, 0, sizeof(box)); if (error_out) *error_out = js_mkundef(); if (callback->signature.returns.id == FFI_VALUE_VOID) return true;
if (!ffi_value_to_c(callback->js, result, callback->signature.returns, &box, &scratch, &err)) { if (error_out) *error_out = err; free(scratch); return false; }
switch (callback->signature.returns.id) { case FFI_VALUE_INT8: memcpy(ret, &box.i8, sizeof(box.i8)); break; case FFI_VALUE_INT16: memcpy(ret, &box.i16, sizeof(box.i16)); break; case FFI_VALUE_INT: memcpy(ret, &box.i32, sizeof(box.i32)); break; case FFI_VALUE_INT64: memcpy(ret, &box.i64, sizeof(box.i64)); break; case FFI_VALUE_UINT8: memcpy(ret, &box.u8, sizeof(box.u8)); break; case FFI_VALUE_UINT16: memcpy(ret, &box.u16, sizeof(box.u16)); break; case FFI_VALUE_UINT64: memcpy(ret, &box.u64, sizeof(box.u64)); break; case FFI_VALUE_FLOAT: memcpy(ret, &box.f32, sizeof(box.f32)); break; case FFI_VALUE_DOUBLE: memcpy(ret, &box.f64, sizeof(box.f64)); break; case FFI_VALUE_POINTER: memcpy(ret, &box.ptr, sizeof(box.ptr)); break; default: free(scratch); return false; }
free(scratch); return true;}
static void ffi_callback_trampoline(ffi_cif *cif, void *ret, void **args, void *user_data) { ffi_callback_handle_t *callback = (ffi_callback_handle_t *)user_data; ant_value_t js_args[32]; ant_value_t fn = js_mkundef(); ant_value_t result = js_mkundef(); size_t argc = 0;
(void)cif; if (!callback || callback->closed || !callback->js) return; ffi_zero_return_value(ret, callback->signature.returns); argc = callback->signature.arg_count;
if (!pthread_equal(pthread_self(), callback->owner_thread)) { fprintf(stderr, "ant:ffi callback invoked off the JS thread; returning a zero value\n"); return; }
if (argc > 32) argc = 32; for (size_t i = 0; i < argc; i++) { js_args[i] = ffi_value_from_c(callback->js, args[i], callback->signature.args[i]); if (is_err(js_args[i])) { js_take_thrown(callback->js, js_args[i]); fprintf(stderr, "ant:ffi callback argument conversion failed; returning a zero value\n"); return; } }
fn = js_get_slot(callback->owner_obj, SLOT_DATA); if (!is_callable(fn)) { fprintf(stderr, "ant:ffi callback target is no longer callable; returning a zero value\n"); return; }
result = sv_vm_call(callback->js->vm, callback->js, fn, js_mkundef(), js_args, (int)argc, NULL, js_mkundef()); if (is_err(result)) { fprintf(stderr, "ant:ffi callback threw an exception; returning a zero value\n"); js_take_thrown(callback->js, result); return; }
ant_value_t error = js_mkundef(); if (!ffi_callback_result_to_c(callback, result, ret, &error)) { if (is_err(error)) js_take_thrown(callback->js, error); fprintf(stderr, "ant:ffi callback returned an incompatible value; returning a zero value\n"); }}
static void ffi_init_prototypes(ant_t *js) { if (js->builtins.ffi_library_proto) return; ant_value_t function_proto = js->sym.function_proto;
js->builtins.ffi_library_proto = Ant_Object_CreateBare(js); js->builtins.ffi_function_proto = Ant_Object_CreateBare(js); js->builtins.ffi_pointer_proto = Ant_Object_CreateBare(js); js->builtins.ffi_callback_proto = Ant_Object_CreateBare(js);
if (is_object_type(js->sym.object_proto)) { js_set_proto_init(js->builtins.ffi_library_proto, js->sym.object_proto); js_set_proto_init(js->builtins.ffi_pointer_proto, js->sym.object_proto); js_set_proto_init(js->builtins.ffi_callback_proto, js->sym.object_proto); }
if (is_object_type(function_proto)) js_set_proto_init(js->builtins.ffi_function_proto, function_proto);
js_set_tostringtag(js, js->builtins.ffi_library_proto, ANT_STRING("FFILibrary")); js_set_tostringtag(js, js->builtins.ffi_function_proto, ANT_STRING("FFIFunction")); js_set_tostringtag(js, js->builtins.ffi_pointer_proto, ANT_STRING("FFIPointer")); js_set_tostringtag(js, js->builtins.ffi_callback_proto, ANT_STRING("FFICallback"));
js_set(js, js->builtins.ffi_library_proto, "define", Ant_Function_Create(ffi_library_define)); js_set(js, js->builtins.ffi_library_proto, "call", Ant_Function_Create(ffi_library_call)); js_set(js, js->builtins.ffi_library_proto, "close", Ant_Function_Create(ffi_library_close));
js_set(js, js->builtins.ffi_pointer_proto, "address", Ant_Function_Create(ffi_pointer_address)); js_set(js, js->builtins.ffi_pointer_proto, "isNull", Ant_Function_Create(ffi_pointer_is_null)); js_set(js, js->builtins.ffi_pointer_proto, "read", Ant_Function_Create(ffi_pointer_read)); js_set(js, js->builtins.ffi_pointer_proto, "write", Ant_Function_Create(ffi_pointer_write)); js_set(js, js->builtins.ffi_pointer_proto, "offset", Ant_Function_Create(ffi_pointer_offset)); js_set(js, js->builtins.ffi_pointer_proto, "free", Ant_Function_Create(ffi_pointer_free));
js_set(js, js->builtins.ffi_callback_proto, "address", Ant_Function_Create(ffi_callback_address)); js_set(js, js->builtins.ffi_callback_proto, "close", Ant_Function_Create(ffi_callback_close));
js_set(js, js->builtins.ffi_function_proto, "address", Ant_Function_Create(ffi_function_address));}
static ant_value_t ffi_dlopen(ant_params_t) { ant_value_t path_val = js_mkundef(); const char *path = NULL; size_t path_len = 0; void *dl = NULL; ffi_library_handle_t *library = NULL; ant_value_t obj = 0;
if (nargs < 1) return js_mkerr_typed(js, JS_ERR_TYPE, "dlopen(path) requires a library path"); ffi_init_prototypes(js);
path_val = js_tostring_val(js, args[0]); if (is_err(path_val) || vtype(path_val) != kTypeString) return path_val; path = js_getstr(js, path_val, &path_len); if (!path) return js_mkerr_typed(js, JS_ERR_TYPE, "Invalid library path");
dl = dlopen(path, RTLD_LAZY); if (!dl) return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to load library: %s", dlerror());
library = calloc(1, sizeof(*library)); if (!library) { dlclose(dl); return js_mkerr(js, "Out of memory"); }
library->path = malloc(path_len + 1); if (!library->path) { dlclose(dl); free(library); return js_mkerr(js, "Out of memory"); }
memcpy(library->path, path, path_len); library->path[path_len] = '\0'; library->handle = dl; library->closed = false;
obj = Ant_Object_CreateBare(js); if (js->builtins.ffi_library_proto) js_set_proto_init(obj, js->builtins.ffi_library_proto); library->obj = obj; js_set_native(obj, library, FFI_LIBRARY_NATIVE_TAG); js_set_finalizer(obj, ffi_library_finalize); return obj;}
ant_value_t ffi_library_close(ant_params_t) { ffi_library_handle_t *library = ffi_library_data(js_getthis(js)); (void)args; (void)nargs; if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); ffi_library_close_handle(library); return js_getthis(js);}
ant_value_t ffi_library_define(ant_params_t) { ffi_library_handle_t *library = ffi_library_data(js_getthis(js)); ffi_signature_t signature; ant_value_t error = js_mkundef(); ant_value_t fn = js_mkundef(); const char *symbol_name = NULL; void *sym = NULL; memset(&signature, 0, sizeof(signature));
if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); if (library->closed) return js_mkerr_typed(js, JS_ERR_TYPE, "FFILibrary is closed"); if (nargs < 2 || vtype(args[0]) != kTypeString) { return js_mkerr_typed(js, JS_ERR_TYPE, "define(name, signature) requires a symbol name and signature"); }
if (!ffi_parse_signature(js, args[1], true, true, &signature, &error)) return error;
symbol_name = js_getstr(js, args[0], NULL); sym = dlsym(library->handle, symbol_name); if (!sym) { ffi_signature_cleanup(&signature); return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol '%s' was not found", symbol_name); }
fn = ffi_make_function(js, library, symbol_name, &signature, sym); if (is_err(fn)) { ffi_signature_cleanup(&signature); return fn; }
js_set(js, js_getthis(js), symbol_name, fn); return fn;}
ant_value_t ffi_library_call(ant_params_t) { ant_value_t fn = js_mkundef(); ffi_library_handle_t *library = ffi_library_data(js_getthis(js));
if (!library) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFILibrary"); if (library->closed) return js_mkerr_typed(js, JS_ERR_TYPE, "FFILibrary is closed"); if (nargs < 1 || vtype(args[0]) != kTypeString) { return js_mkerr_typed(js, JS_ERR_TYPE, "call(name, ...args) requires a symbol name"); }
fn = js_get(js, js_getthis(js), js_getstr(js, args[0], NULL)); if (!is_callable(fn)) { return js_mkerr_typed(js, JS_ERR_TYPE, "Symbol '%s' has not been defined", js_getstr(js, args[0], NULL)); }
return sv_vm_call(js->vm, js, fn, js_mkundef(), args + 1, nargs - 1, NULL, js_mkundef());}
ant_value_t ffi_function_call(ant_params_t) { ffi_function_handle_t *function = ffi_function_data(js->current_func); ffi_type **call_types = NULL; ffi_value_box_t *values = NULL; void **call_args = NULL; void **scratch = NULL; ffi_marshaled_type_t *dynamic_types = NULL; ffi_cif call_cif; ffi_value_box_t result; ant_value_t error = js_mkundef(); int status = FFI_OK; size_t actual_argc = 0;
memset(&result, 0, sizeof(result));
if (!function) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIFunction"); if (!function->library || function->library->closed) { return js_mkerr_typed(js, JS_ERR_TYPE, "FFIFunction '%s' belongs to a closed library", function->symbol_name); }
if (!function->signature.variadic && nargs != (int)function->signature.arg_count) return js_mkerr_typed( js, JS_ERR_TYPE, "FFIFunction '%s' expects %zu arguments, got %d", function->symbol_name, function->signature.arg_count, nargs );
if (function->signature.variadic && nargs < (int)function->signature.fixed_arg_count) return js_mkerr_typed( js, JS_ERR_TYPE, "FFIFunction '%s' expects at least %zu arguments, got %d", function->symbol_name, function->signature.fixed_arg_count, nargs );
actual_argc = (size_t)nargs; if (actual_argc > 0) { call_types = calloc(actual_argc, sizeof(*call_types)); values = calloc(actual_argc, sizeof(*values)); call_args = calloc(actual_argc, sizeof(*call_args)); scratch = calloc(actual_argc, sizeof(*scratch)); dynamic_types = calloc(actual_argc, sizeof(*dynamic_types)); if (!call_types || !values || !call_args || !scratch || !dynamic_types) { error = js_mkerr(js, "Out of memory"); goto cleanup; } }
for (size_t i = 0; i < actual_argc; i++) { ffi_marshaled_type_t type = i < function->signature.fixed_arg_count ? function->signature.args[i] : ffi_infer_variadic_type(args[i]); dynamic_types[i] = type; call_types[i] = type.ffi_type; call_args[i] = &values[i]; if (!ffi_value_to_c(js, args[i], type, &values[i], &scratch[i], &error)) goto cleanup; }
if (function->signature.variadic) { status = ffi_prep_cif_var( &call_cif, FFI_DEFAULT_ABI, (unsigned int)function->signature.fixed_arg_count, (unsigned int)actual_argc, function->signature.returns.ffi_type, call_types ); if (status != FFI_OK) { error = js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare variadic FFI call"); goto cleanup; } ffi_call(&call_cif, function->func_ptr, &result, call_args); } else ffi_call(&function->cif, function->func_ptr, &result, call_args);
cleanup: if (vtype(error) != kTypeUndefined) { size_t i; for (i = 0; i < actual_argc; i++) free(scratch ? scratch[i] : NULL); free(dynamic_types); free(scratch); free(call_args); free(values); free(call_types); return error; }
{ ant_value_t out = ffi_value_from_c(js, &result, function->signature.returns); size_t i; for (i = 0; i < actual_argc; i++) free(scratch ? scratch[i] : NULL); free(dynamic_types); free(scratch); free(call_args); free(values); free(call_types); return out; }}
ant_value_t ffi_function_address(ant_params_t) { ffi_function_handle_t *function = ffi_function_data(js_getthis(js)); (void)args; (void)nargs; if (!function) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIFunction"); return js_mknum((double)(uintptr_t)function->func_ptr);}
static ant_value_t ffi_alloc_memory(ant_params_t) { ffi_pointer_region_t *region = NULL; size_t size = 0;
if (nargs < 1 || vtype(args[0]) != kTypeNumber) { return js_mkerr_typed(js, JS_ERR_TYPE, "alloc(size) requires a numeric size"); }
size = (size_t)js_getnum(args[0]); if (size == 0) return js_mkerr_typed(js, JS_ERR_RANGE, "alloc(size) requires a positive size");
region = calloc(1, sizeof(*region)); if (!region) return js_mkerr(js, "Out of memory");
region->ptr = malloc(size); if (!region->ptr) { free(region); return js_mkerr(js, "Out of memory"); }
region->size = size; region->owned = true; region->freed = false; region->size_known = true; return ffi_make_pointer(js, region, 0);}
static ant_value_t ffi_pointer_value(ant_params_t) { ffi_pointer_region_t *region = NULL; ant_value_t error = js_mkundef(); char *str_copy = NULL; size_t str_len = 0; const uint8_t *buffer_bytes = NULL; size_t buffer_len = 0;
if (nargs < 1 || ffi_is_nullish(args[0])) return ffi_make_pointer_from_raw(js, NULL); if (ffi_pointer_data(args[0])) return args[0];
if (ffi_callback_data(args[0])) { void *ptr = NULL; if (!ffi_pointer_from_js(js, args[0], &ptr, &error)) return error; return ffi_make_pointer_or_null(js, ptr); }
region = calloc(1, sizeof(*region)); if (!region) return js_mkerr(js, "Out of memory");
if (buffer_source_get_bytes(js, args[0], &buffer_bytes, &buffer_len)) { region->ptr = (uint8_t *)buffer_bytes; region->size = buffer_len; region->size_known = true; region->owned = false; region->freed = false; return ffi_make_pointer(js, region, 0); }
if (!ffi_copy_js_string(js, args[0], &str_copy, &str_len, &error)) { free(region); return error; }
region->ptr = (uint8_t *)str_copy; region->size = str_len + 1; region->size_known = true; region->owned = true; region->freed = false; return ffi_make_pointer(js, region, 0);}
ant_value_t ffi_pointer_address(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); (void)args; (void)nargs; if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); return js_mknum((double)(uintptr_t)ffi_pointer_address_raw(handle));}
ant_value_t ffi_pointer_is_null(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); (void)args; (void)nargs; if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); return js_bool(ffi_pointer_address_raw(handle) == NULL);}
ant_value_t ffi_pointer_read(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); ffi_marshaled_type_t type;
if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); type = nargs > 0 ? ffi_marshaled_type_from_value(js, args[0]) : ffi_marshaled_type_make(FFI_VALUE_POINTER, "pointer"); if (type.id == FFI_VALUE_UNKNOWN || type.id == FFI_VALUE_SPREAD || type.id == FFI_VALUE_VOID) { return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFIPointer.read() type"); } return ffi_read_from_pointer(js, handle, type);}
ant_value_t ffi_pointer_write(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); ffi_marshaled_type_t type;
if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); if (nargs < 2) { return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer.write(type, value) requires a type and value"); }
type = ffi_marshaled_type_from_value(js, args[0]); if (type.id == FFI_VALUE_UNKNOWN || type.id == FFI_VALUE_SPREAD || type.id == FFI_VALUE_VOID) { return js_mkerr_typed(js, JS_ERR_TYPE, "Unsupported FFIPointer.write() type"); }
return ffi_write_to_pointer(js, handle, type, args[1]);}
ant_value_t ffi_pointer_offset(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); ffi_pointer_handle_t *next = NULL; ant_value_t out = 0; size_t offset = 0;
if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); if (nargs < 1 || vtype(args[0]) != kTypeNumber) { return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer.offset(bytes) requires a numeric byte offset"); }
if (js_getnum(args[0]) < 0) return js_mkerr_typed(js, JS_ERR_RANGE, "FFIPointer.offset() requires a non-negative offset"); offset = (size_t)js_getnum(args[0]);
if (handle->region && handle->region->size_known && handle->byte_offset + offset > handle->region->size) { return js_mkerr_typed(js, JS_ERR_RANGE, "FFIPointer.offset() is out of bounds"); }
out = ffi_make_pointer(js, handle->region, handle->byte_offset + offset); next = ffi_pointer_data(out); if (!next) return out; return out;}
ant_value_t ffi_pointer_free(ant_params_t) { ffi_pointer_handle_t *handle = ffi_pointer_data(js_getthis(js)); (void)args; (void)nargs; if (!handle) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFIPointer"); if (!handle->region || !handle->region->owned) { return js_mkerr_typed(js, JS_ERR_TYPE, "Only owned FFIPointers can be freed"); } if (!ffi_pointer_region_free(handle->region)) { return js_mkerr_typed(js, JS_ERR_TYPE, "FFIPointer has already been freed"); } return js_getthis(js);}
static ant_value_t ffi_create_callback(ant_params_t) { ant_value_t signature_val = js_mkundef(); ant_value_t fn = js_mkundef(); ant_value_t obj = 0; ffi_callback_handle_t *callback = NULL; ant_value_t error = js_mkundef();
if (nargs < 2) { return js_mkerr_typed(js, JS_ERR_TYPE, "callback(signature, fn) requires a signature and function"); }
if (is_callable(args[0]) && is_object_type(args[1])) { fn = args[0]; signature_val = args[1]; } else if (is_object_type(args[0]) && is_callable(args[1])) { signature_val = args[0]; fn = args[1]; } else return js_mkerr_typed(js, JS_ERR_TYPE, "callback(signature, fn) requires a signature object and callable function");
ffi_init_prototypes(js);
callback = calloc(1, sizeof(*callback)); if (!callback) return js_mkerr(js, "Out of memory");
if (!ffi_parse_signature(js, signature_val, false, false, &callback->signature, &error)) { free(callback); return error; }
if (ffi_prep_cif( &callback->cif, FFI_DEFAULT_ABI, (unsigned int)callback->signature.arg_count, callback->signature.returns.ffi_type, callback->signature.arg_count > 0 ? callback->signature.ffi_arg_types : NULL ) != FFI_OK) { ffi_signature_cleanup(&callback->signature); free(callback); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFICallback signature"); }
callback->closure = ffi_closure_alloc(sizeof(*callback->closure), &callback->code_ptr); if (!callback->closure) { ffi_signature_cleanup(&callback->signature); free(callback); return js_mkerr(js, "Failed to allocate FFICallback closure"); }
if (ffi_prep_closure_loc(callback->closure, &callback->cif, ffi_callback_trampoline, callback, callback->code_ptr) != FFI_OK) { ffi_closure_free(callback->closure); ffi_signature_cleanup(&callback->signature); free(callback); return js_mkerr_typed(js, JS_ERR_TYPE, "Failed to prepare FFICallback closure"); }
obj = Ant_Object_CreateBare(js); if (js->builtins.ffi_callback_proto) js_set_proto_init(obj, js->builtins.ffi_callback_proto); callback->js = js; callback->owner_obj = obj; callback->owner_thread = pthread_self(); callback->closed = false; js_set_native(obj, callback, FFI_CALLBACK_NATIVE_TAG); js_set_slot_wb(js, obj, SLOT_DATA, fn); js_set_finalizer(obj, ffi_callback_finalize); return obj;}
ant_value_t ffi_callback_address(ant_params_t) { ffi_callback_handle_t *callback = ffi_callback_data(js_getthis(js)); (void)args; (void)nargs; if (!callback) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFICallback"); return js_mknum((double)(uintptr_t)callback->code_ptr);}
ant_value_t ffi_callback_close(ant_params_t) { ffi_callback_handle_t *callback = ffi_callback_data(js_getthis(js)); (void)args; (void)nargs; if (!callback) return js_mkerr_typed(js, JS_ERR_TYPE, "Expected an FFICallback"); ffi_callback_close_handle(callback); return js_getthis(js);}
ant_value_t ffi_library(ant_t *js) { ant_value_t ffi_obj = Ant_Object_CreateBare(js); ant_value_t ffi_types = Ant_Object_CreateBare(js); const char *suffix = "so";
ffi_init_prototypes(js);
js_set(js, ffi_obj, "dlopen", Ant_Function_Create(ffi_dlopen)); js_set(js, ffi_obj, "alloc", Ant_Function_Create(ffi_alloc_memory)); js_set(js, ffi_obj, "pointer", Ant_Function_Create(ffi_pointer_value)); js_set(js, ffi_obj, "callback", Ant_Function_Create(ffi_create_callback));
#ifdef __APPLE__ suffix = "dylib";#elif defined(_WIN32) suffix = "dll";#endif
js_set(js, ffi_obj, "suffix", js_mkstr(js, suffix, strlen(suffix)));
js_set(js, ffi_types, "void", ANT_STRING("void")); js_set(js, ffi_types, "int8", ANT_STRING("int8")); js_set(js, ffi_types, "int16", ANT_STRING("int16")); js_set(js, ffi_types, "int", ANT_STRING("int")); js_set(js, ffi_types, "int64", ANT_STRING("int64")); js_set(js, ffi_types, "uint8", ANT_STRING("uint8")); js_set(js, ffi_types, "uint16", ANT_STRING("uint16")); js_set(js, ffi_types, "uint64", ANT_STRING("uint64")); js_set(js, ffi_types, "float", ANT_STRING("float")); js_set(js, ffi_types, "double", ANT_STRING("double")); js_set(js, ffi_types, "pointer", ANT_STRING("pointer")); js_set(js, ffi_types, "string", ANT_STRING("string")); js_set(js, ffi_types, "spread", ANT_STRING("...")); js_set(js, ffi_obj, "FFIType", ffi_types); js_set_tostringtag(js, ffi_obj, ANT_STRING("FFI")); return ffi_obj;}