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