• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/lib/ffi/ffi.c

  1 /*
  2  * FFI module for ucode - LibFFI-based Foreign Function Interface
  3  *
  4  * Copyright (C) 2005-2025 Mike Pall (LuaJIT FFI implementation)
  5  * Copyright (C) 2023-2026 Jo-Philipp Wich <jo@mein.io> (ucode integration)
  6  *
  7  * Permission to use, copy, modify, and/or distribute this software for any
  8  * purpose with or without fee is hereby granted, provided that the above
  9  * copyright notice and this permission notice appear in all copies.
 10  *
 11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 18  *
 19  * This module contains derived work from LuaJIT's FFI implementation.
 20  *
 21  * Modifications from LuaJIT:
 22  * - Replaced LuaJIT's own call infrastructure (lj_ccall.c, lj_ccallback.c) with libffi
 23  * - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.)
 24  * - Removed JIT-specific code and dependencies
 25  * - Consolidated library loading logic into this module
 26  *
 27  * See NOTICE and ATTRIBUTION.md for complete attribution details.
 28  */
 29 
 30 /**
 31  * # Foreign Function Interface (FFI)
 32  *
 33  * The `ffi` module provides a foreign function interface for ucode, allowing
 34  * direct interaction with C libraries. It combines a C declaration parser with
 35  * libffi-based function calling to enable seamless interop between ucode and C.
 36  *
 37  * The module can be imported using the wildcard import syntax:
 38  *
 39  *   ```
 40  *   import * as ffi from 'ffi';
 41  *   ```
 42  *
 43  * ## Synopsis
 44  *
 45  * ```javascript
 46  * import * as ffi from 'ffi';
 47  *
 48  * // 1. Declare C types and functions
 49  * ffi.cdef(`
 50  *     struct point { int x; int y; };
 51  *     extern char **environ;
 52  * `);
 53  *
 54  * // 2. Call C functions via the global C namespace
 55  * // Primitive return values are auto-converted to ucode types
 56  * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)');
 57  * print(strcmp("hello", "world"), "\n");  // => non-zero (number)
 58  *
 59  * // 3. String return values remain as cdata - use ffi.string() to convert
 60  * let getenv = ffi.C.wrap('char *getenv(char *)');
 61  * let path_ptr = getenv('PATH');      // Returns char* cdata
 62  * let path_str = ffi.string(path_ptr); // Convert to ucode string
 63  *
 64  * // 4. Create C data instances
 65  * ffi.cdef('struct point { int x; int y; };');
 66  * let p = ffi.ctype('struct point', 10, 20);
 67  * print(p.get('x'), p.get('y'), "\n");  // => 10 20
 68  *
 69  * // 5. Access global variables
 70  * print(ffi.C.dlsym('environ').get(0), "\n");
 71  *
 72  * // 6. Query type information
 73  * print(ffi.sizeof('int'), "\n");        // => 4
 74  * print(ffi.alignof('double'), "\n");    // => 8
 75  * print(ffi.offsetof('struct point', 'y'), "\n");  // => 4
 76  *
 77  * // 7. Load external libraries
 78  * let libz = ffi.dlopen('z');
 79  * let zlibVersion = libz.wrap('const char *zlibVersion(void)');
 80  * print(zlibVersion().slice(), "\n");  // => "1.2.11" (or similar)
 81  *
 82  * // Use in callbacks (primitives auto-converted)
 83  * let qsort = ffi.C.wrap('void qsort(void *, size_t, size_t, int (*)(const void *, const void *))');
 84  * let cmp = ffi.C.wrap('int strcmp(const char *, const char *)');
 85  * let arr = ffi.ctype('char *[5]', ["zebra", "apple", "banana", "cherry", "date"]);
 86  * // cmp() returns ucode number directly (primitives auto-converted)
 87  * qsort(arr.ptr(), arr.length(), arr.itemsize(),
 88  *       (a, b) => cmp(a.deref('const char *'), b.deref('const char *')));
 89  * ```
 90  *
 91  * ## Memory Management for char* Return Values
 92  *
 93  * When a wrapped C function returns `char*`, the return value is a **cdata pointer
 94  * object**, not an auto-converted ucode string. This design prevents memory leaks
 95  * and gives you explicit control over memory management.
 96  *
 97  * ### Converting char* to ucode Strings
 98  *
 99  * Use `ffi.string()` or `slice()` to convert a char* cdata to a ucode string:
100  *
101  * ```javascript
102  * let getenv = ffi.C.wrap('char *getenv(char *)');
103  *
104  * let path_ptr = getenv('PATH');    // Returns char* cdata
105  * let path = ffi.string(path_ptr);  // Convert to ucode string
106  * // or equivalently:
107  * let path = path_ptr.slice();      // slice() without args = string()
108  * ```
109  *
110  * **Note**: Both `ffi.string()` and `slice()` create a **copy** of the C string.
111  * The original C memory remains untouched.
112  *
113  * ### Memory Ownership Patterns
114  *
115  * #### Pattern 1: C Manages Memory (No Free Required)
116  *
117  * Functions like `getenv()`, `strerror()` return pointers to **static/internal
118  * memory** managed by the C library. Do NOT free these.
119  *
120  * ```javascript
121  * let getenv = ffi.C.wrap('char *getenv(char *)');
122  *
123  * let path_ptr = getenv('PATH');
124  * let path = ffi.string(path_ptr);  // Copies to ucode string
125  *
126  * // path_ptr points to C internal memory - DO NOT free
127  * // path is a ucode string - managed by ucode GC
128  * ```
129  *
130  * #### Pattern 2: Caller Must Free (malloc'd Memory)
131  *
132  * Functions like `strdup()`, `asprintf()`, `getline()` return **malloc'd memory**
133  * that you must free to avoid leaks.
134  *
135  * ```javascript
136  * let strdup = ffi.C.wrap('char *strdup(const char *)');
137  * let free = ffi.C.wrap('void free(void *)');
138  *
139  * let ptr = strdup("hello");      // malloc'd by strdup
140  * let str = ffi.string(ptr);      // Copies to ucode string
141  * free(ptr);                       // NOW you can safely free
142  *
143  * // str is safe - it's a ucode string copy
144  * // ptr memory is freed - no leak
145  * ```
146  *
147  * **Key**: Keep the cdata pointer until you're done copying, then free it.
148  *
149  * #### Pattern 3: Stack-Allocated Buffers
150  *
151  * When C writes into a buffer you provide (e.g., `sprintf`), the buffer is
152  * managed by ucode.
153  *
154  * ```javascript
155  * let sprintf = ffi.C.wrap('int sprintf(char *, const char *, ...)');
156  *
157  * let buf = ffi.ctype('char[256]');  // ucode-managed array
158  * sprintf(buf, "Hello %s", "World");
159  *
160  * let msg = ffi.string(buf);  // Copies to ucode string
161  *
162  * // buf is managed by ucode GC - no manual free needed
163  * ```
164  *
165  * ### Substring Operations with slice()
166  *
167  * For char* pointers, `slice()` supports substring extraction:
168  *
169  * ```javascript
170  * let getenv = ffi.C.wrap('char *getenv(char *)');
171  * let ptr = getenv('PATH');
172  *
173  * // From start to end (same as ffi.string())
174  * let full = ptr.slice();
175  *
176  * // From start index to end
177  * let rest = ptr.slice(5);
178  *
179  * // Specific range
180  * let part = ptr.slice(0, 10);
181  *
182  * // Negative indices (from end)
183  * let last = ptr.slice(-5);
184  * ```
185  *
186  * ### Common Functions Reference
187  *
188  * | Function | Memory Owner | Pattern |
189  * |----------|--------------|---------|
190  * | `getenv()` | C (static) | No free needed |
191  * | `strerror()` | C (static) | No free needed |
192  * | `strdup()` | Caller | Must `free()` |
193  * | `asprintf()` | Caller | Must `free()` |
194  * | `getline()` | Caller | Must `free()` |
195  * | `sprintf()` | Caller (buffer) | Buffer managed by you |
196  * | `strtok()` | C (static) | No free needed |
197  *
198  * ### Best Practices
199  *
200  * 1. **Always use `ffi.string()` or `slice()`** when you need a ucode string from `char*`
201  * 2. **Track ownership**: Does C manage the memory or do you?
202  * 3. **Free after copying**: Call `free(ptr)` only after `ffi.string(ptr)` or `ptr.slice()`
203  * 4. **Never free static memory**: `getenv()`, `strerror()` return static pointers
204  *
205  * ## Limitations
206  *
207  * - **No vararg closures**: `wrap()` cannot create closures with variable arguments
208  * - **Fixed ABI**: Calling convention determined at closure creation time
209  * - **Platform constraints**: Some architectures have limited support for certain type combinations
210  *
211  * ## The `ffi.C` Namespace
212  *
213  * `ffi.C` is a special CLib instance representing the process's global symbol table.
214  * It provides access to standard C library functions without explicit `dlopen()`:
215  *
216  * ```javascript
217  * // These are equivalent:
218  * let strlen1 = ffi.C.wrap('size_t strlen(const char *)');
219  *
220  * ffi.cdef('size_t strlen(const char *);');
221  * let strlen2 = ffi.C.wrap('strlen');
222  * ```
223  *
224  * Functions declared via `cdef()` are automatically registered in `ffi.C`'s symbol table.
225  *
226  * ## Pointer Arithmetic and Memory Access
227  *
228  * C data objects (cdata) provide methods for pointer arithmetic and memory access:
229  *
230  * ### Creating Pointers with ptr()
231  *
232  * Use `ptr()` to get a pointer to a cdata value:
233  *
234  * ```javascript
235  * let x = ffi.ctype('int', 42);
236  * let px = x.ptr();  // int* pointer to x
237  *
238  * // Pass to C functions expecting pointers
239  * ffi.cdef('int atoi(const char *)');
240  * let num = ffi.ctype('char[4]', "123");
241  * let result = atoi(num.ptr());  // => 123
242  * ```
243  *
244  * ### Array Indexing with get() and set()
245  *
246  * Access array elements using `get(index)` and `set(index, value)`:
247  *
248  * ```javascript
249  * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
250  *
251  * // Read elements
252  * let first = arr.get(0);  // => 10 (ucode number)
253  * let third = arr.get(2);  // => 30 (ucode number)
254  *
255  * // Modify elements
256  * arr.set(0, 100);
257  * arr.set(4, 200);
258  *
259  * // Negative indices work too
260  * let last = arr.get(-1);  // => 200 (ucode number)
261  * ```
262  *
263  * ### Understanding get() vs index()
264  *
265  * **`get()` returns converted ucode values**, while **`index()` returns
266  * raw cdata references**. This is the key distinction between the two methods.
267  *
268  * #### get() - Converted Values
269  *
270  * The `get()` method immediately converts C values to ucode types:
271  *
272  * ```javascript
273  * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
274  *
275  * // Returns ucode number directly
276  * let val1 = arr.get(0);      // => 10 (number)
277  * let val2 = arr.get(2);      // => 30 (number)
278  *
279  * // Struct field access - returns converted value
280  * ffi.cdef('struct point { int x; int y; };');
281  * let p = ffi.ctype('struct point', 10, 20);
282  * p.get('x');      // => 10 (number)
283  * p.get('y');      // => 20 (number)
284  * ```
285  *
286  * #### index() - Raw cdata References
287  *
288  * The `index()` method returns a cdata reference for further manipulation:
289  *
290  * ```javascript
291  * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
292  *
293  * // Returns cdata reference (unconverted)
294  * let ref1 = arr.index(0);    // => cdata (int)
295  * let ref2 = arr.index(2);    // => cdata (int)
296  *
297  * // Convert to ucode value explicitly
298  * ref1.get();     // => 10 (number)
299  *
300  * // Or modify through the reference
301  * arr.index(0).set(100);  // Set arr[0] = 100
302  * ```
303  *
304  * #### Pointer Arithmetic
305  *
306  * Both methods work with pointers, but return different types:
307  *
308  * ```javascript
309  * let ptr = ffi.ctype('int *', arr.ptr());
310  *
311  * // index() returns cdata reference
312  * ptr.index(0);   // => cdata at ptr[0]
313  * ptr.index(1);   // => cdata at ptr[1]
314  * ptr.index(0).get();  // => 10 (number)
315  *
316  * // get() returns converted value
317  * ptr.get(0);     // => 10 (number)
318  * ptr.get(1);     // => 20 (number)
319  * ```
320  *
321  * #### Path Syntax Support
322  *
323  * Both methods support path notation for nested access:
324  *
325  * ```javascript
326  * ffi.cdef('struct rect { struct point min; struct point max; };');
327  * let r = ffi.ctype('struct rect', {
328  *     min: {x: 0, y: 0},
329  *     max: {x: 100, y: 100}
330  * });
331  *
332  * // get() returns converted value
333  * r.get('min.x');       // => 0 (number)
334  *
335  * // index() returns cdata reference
336  * r.index('min.x');     // => cdata (int)
337  * r.index('min.x').get() // => 0 (number)
338  * ```
339  *
340  * #### Practical Guidance
341  *
342  * **Use `get()` when:**
343  * - You need the value immediately as a ucode type
344  * - Reading values for computation: `let x = arr.get(i)`
345  * - Accessing struct fields: `let y = struct.get('field')`
346  * - Most common use cases
347  *
348  * **Use `index()` when:**
349  * - You need a reference for further manipulation
350  * - Chaining operations: `arr.index(i).set(val)`
351  * - Pointer arithmetic with cdata: `ptr.index(n).deref()`
352  * - Passing references to other C functions
353  *
354  * **For writing values:**
355  * - Use `set()` for both arrays and structs: `arr.set(i, val)`, `struct.set('f', val)`
356  *
357  * **For getting pointers (not values):**
358  * - Use `ptr()` on scalars: `x.ptr()` gives you `int*`
359  * - Arrays are already pointers: `arr` can be passed to C functions
360  *
361  * ### Pointer Arithmetic via get() and index()
362  *
363  * Both `get(n)` and `index(n)` work for pointer arithmetic on pointer types:
364  *
365  * ```javascript
366  * ffi.cdef('char *strdup(const char *)');
367  * let strdup = ffi.C.wrap('char *strdup(const char *)');
368  *
369  * let ptr = strdup("hello world");
370  *
371  * // get() returns converted value (number for char)
372  * let first_char = ptr.get(0);    // 'h' (number 104)
373  * let sixth_char = ptr.get(6);    // 'w' (number 119)
374  *
375  * // index() returns cdata reference
376  * ptr.index(6);       // => cdata (char)
377  * ptr.index(6).get()  // => 119 (number)
378  *
379  * // Get substring from offset
380  * let substring = ffi.string(ptr.get(6));  // "world"
381  *
382  * free(ptr);
383  * ```
384  *
385  * ### Path-Based Access for Nested Structures
386  *
387  * Use dot notation and array indexing in paths for complex access:
388  *
389  * ```javascript
390  * ffi.cdef(`
391  *     struct point { int x; int y; };
392  *     struct rect { struct point min; struct point max; };
393  * `);
394  *
395  * let r = ffi.ctype('struct rect', {
396  *     min: {x: 0, y: 0},
397  *     max: {x: 100, y: 100}
398  * });
399  *
400  * // Nested field access
401  * r.get('min.x');    // => 0
402  * r.set('max.y', 50);
403  *
404  * // Array of structs
405  * ffi.cdef('struct point points[3];');
406  * let arr = ffi.ctype('struct point[3]', [
407  *     {x: 1, y: 2},
408  *     {x: 3, y: 4},
409  *     {x: 5, y: 6}
410  * ]);
411  *
412  * arr.get('[1].x');  // => 3
413  * arr.set('[2].y', 10);
414  * ```
415  *
416  * ### Dereferencing Pointers with deref()
417  *
418  * Use `deref(type)` to read the value pointed to:
419  *
420  * ```javascript
421  * let x = ffi.ctype('int', 42);
422  * let px = x.ptr();
423  *
424  * let value = px.deref('int');  // => 42
425  *
426  * // With char* pointers
427  * ffi.cdef('char *strdup(const char *)');
428  * let strdup = ffi.C.wrap('char *strdup(const char *)');
429  *
430  * let ptr = strdup("hello");
431  * let first_byte = ptr.deref('char');  // => 'h' (as number 104)
432  *
433  * free(ptr);
434  * ```
435  *
436  * ### Querying Array Properties
437  *
438  * Use `length()` and `itemsize()` for array information:
439  *
440  * ```javascript
441  * let arr = ffi.ctype('int[10]');
442  *
443  * arr.length();   // => 10 (number of elements)
444  * arr.itemsize(); // => 4 (size of each element in bytes)
445  *
446  * // Calculate total size
447  * let total = arr.length() * arr.itemsize();  // => 40 bytes
448  * ```
449  *
450  * ### Working with Byte Arrays
451  *
452  * For `char[]` or `uint8_t[]`, use `slice()` to extract strings:
453  *
454  * ```javascript
455  * let buf = ffi.ctype('char[10]', "hello");
456  *
457  * // Extract as ucode string
458  * let str = buf.slice();        // => "hello"
459  * let part = buf.slice(0, 3);   // => "hel"
460  *
461  * // Or use ffi.string()
462  * let str2 = ffi.string(buf);   // => "hello"
463  * ```
464  *
465  * ### Complete Example: String Manipulation
466  *
467  * ```javascript
468  * ffi.cdef(`
469  *     char *strdup(const char *);
470  *     void free(void *);
471  *     size_t strlen(const char *);
472  * `);
473  *
474  * let strdup = ffi.C.wrap('char *strdup(const char *)');
475  * let free = ffi.C.wrap('void free(void *)');
476  * let strlen = ffi.C.wrap('size_t strlen(const char *)');
477  *
478  * // Create a duplicatable string
479  * let ptr = strdup("hello world");
480  *
481  * // Get length
482  * let len = strlen(ptr).get();  // => 11
483  *
484  * // Access individual characters via indexing
485  * let first = ptr.get(0);       // 'h'
486  * let sixth = ptr.get(6);       // 'w'
487  *
488  * // Extract substrings
489  * let hello = ptr.slice(0, 5);  // "hello"
490  * let world = ptr.slice(6);     // "world"
491  *
492  * // Modify in place
493  * ptr.set(5, 0);  // Null-terminate at space
494  *
495  * let str = ffi.string(ptr);  // => "hello"
496  *
497  * // Clean up
498  * free(ptr);
499  * ```
500  *
501  * @module ffi
502  */
503 
504 #include <syslog.h>
505 #include <errno.h>
506 #include <dlfcn.h>
507 
508 #include <assert.h>
509 #include <stdio.h>
510 #include <ffi.h>
511 #include <ucode/module.h>
512 #include <ucode/util.h>
513 
514 #ifdef HAVE_ULOG
515 #include <libubox/ulog.h>
516 #endif
517 
518 #include "uc_cdata.h"
519 #include "uc_ctype.h"
520 #include "uc_cparse.h"
521 #include "uc_cconv.h"
522 
523 #define CLNS_INDEX ((1u<<CT_FUNC)|(1u<<CT_EXTERN)|(1u<<CT_CONSTVAL))
524 
525 typedef struct {
526         void *dlh;
527         char *name;
528         uc_value_t *cache;
529 } uc_ffi_clib_t;
530 
531 typedef struct {
532         ffi_cif cif;
533         ffi_abi abi;
534         size_t nargs;
535         ffi_type *rtype;
536         ffi_type **atypes;
537 } uc_ffi_cc_t;
538 
539 
540 
541 /* Check first argument for a C type and returns its ID. */
542 static CTypeID ffi_checkctype(uc_vm_t *vm, size_t nargs, size_t narg, CTState *cts, uc_value_t **param)
543 {
544         uc_value_t *arg = uc_fn_arg(narg);
545 
546         if (narg >= nargs) {
547                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
548                         "C type expected, got no value");
549 
550                 return 0;
551         }
552 
553         if (ucv_type(arg) == UC_STRING)
554         { /* Parse an abstract C type declaration. */
555                 CPState cp = {
556                         .uv_vm = vm,
557                         .cts = cts,
558                         .srcname = ucv_string_get(arg),
559                         .p = ucv_string_get(arg),
560                         .uv_param = param,
561                         .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT
562                 };
563 
564                 if (!uc_cparse(&cp))
565                         return 0;
566 
567                 return cp.val.id;
568         }
569         else
570         {
571                 GCcdata *cd = ucv_resource_data(arg, "ffi.ctype");
572 
573                 if (!cd) {
574                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
575                                 "C type expected, got %s",
576                                 (narg < nargs) ? ucv_typename(arg) : "no value");
577 
578                         return 0;
579                 }
580 
581                 if (param && param < uc_vector_last(&vm->stack)) {
582                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
583                                 "wrong number of type parameters");
584 
585                         return 0;
586                 }
587                 //cd = cdataV(o);
588                 return cd->ctypeid == CTID_CTYPEID ? *(CTypeID *)cdataptr(cd) : cd->ctypeid;
589         }
590 }
591 
592 /* Convert given value to C type. */
593 static CTypeID
594 uv_to_ct(uc_vm_t *vm, uint32_t mode, uc_value_t *uv, GCcdata **cdp)
595 {
596         GCcdata *cd = ucv_resource_data(uv, "ffi.ctype");
597 
598         if (cd && cd->ctypeid == CTID_CTYPEID) {
599                 return *(CTypeID *)cdataptr(cd);
600         }
601         else if (cd) {
602                 if (cdp)
603                         *cdp = cd;
604 
605                 return cd->ctypeid;
606         }
607         else if (ucv_type(uv) == UC_STRING) {
608                 /* Parse an abstract C type declaration. */
609                 CPState cp = {
610                         .uv_vm = vm,
611                         .cts = ctype_cts(vm),
612                         .srcname = ucv_string_get(uv),
613                         .p = ucv_string_get(uv),
614                         .mode = mode
615                 };
616 
617                 if (!uc_cparse(&cp))
618                         return 0;
619 
620                 return cp.val.id;
621         }
622         else {
623                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
624                         "C type or string expected, got %s",
625                         ucv_typename(uv));
626 
627                 return 0;
628         }
629 }
630 
631 /* Convert argument to C pointer. */
632 static void *ffi_checkptr(uc_vm_t *vm, size_t nargs, size_t narg, CTypeID id)
633 {
634         uc_value_t *arg = uc_fn_arg(narg);
635         CTState *cts = ctype_cts(vm);
636         void *p;
637 
638         if (narg >= nargs) {
639                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "value expected");
640 
641                 return NULL;
642         }
643 
644         uc_cconv_ct_tv(cts, ctype_get(cts, id), (uint8_t *)&p,
645                 arg, CCF_ARG(narg), NULL);
646 
647         return p;
648 }
649 
650 /* Get buffer size from cdata object. Returns SIZE_MAX for non-array types. */
651 static size_t
652 ffi_cdata_bufsize(CTState *cts, uc_value_t *uv)
653 {
654         GCcdata *cd = ucv_resource_data(uv, "ffi.ctype");
655         CType *ct;
656 
657         if (!cd)
658                 return SIZE_MAX;
659 
660         ct = ctype_get(cts, cd->ctypeid);
661 
662         if (ctype_isptr(ct->info))
663                 ct = ctype_rawchild(cts, ct);
664 
665         if (ctype_isrefarray(ct->info))
666                 return ct->size;
667 
668         return SIZE_MAX;
669 }
670 
671 /* Get redirected or mangled external symbol. */
672 static uc_value_t *
673 clib_extsym(CTState *cts, CType *ct, uc_value_t *name)
674 {
675         if (ct->sib) {
676                 CType *ctf = ctype_get(cts, ct->sib);
677 
678                 if (ctype_isxattrib(ctf->info, CTA_REDIR))
679                         return ctf->uv_name;
680         }
681 
682         return name;
683 }
684 
685 
686 static bool
687 uc_ctype_requires_ffi_struct(CTState *cts, CTypeID cid)
688 {
689         CType *ct = ctype_get(cts, cid);
690         CTInfo info = ct->info;
691 
692         switch (ctype_type(info)) {
693         case CT_ARRAY:
694                 switch (cid) {
695                 case CTID_COMPLEX_FLOAT:
696                 case CTID_COMPLEX_DOUBLE:
697                         return false;
698                 }
699 
700                 /* fall through */
701 
702         case CT_STRUCT:
703                 return true;
704         }
705 
706         return false;
707 }
708 
709 static ffi_type *
710 uc_ctype_to_ffi_type(CTState *cts, CTypeID cid, ffi_type *st)
711 {
712         CType *ct = ctype_get(cts, cid);
713         CTInfo info = ct->info;
714         CTSize size = ct->size;
715 
716         switch (ctype_type(info)) {
717         case CT_NUM:
718                 if (info & CTF_BOOL)
719                         return &ffi_type_uint8;
720 
721                 if (info & CTF_FP) {
722                         if (size == sizeof(double))
723                                 return &ffi_type_double;
724 
725                         if (size == sizeof(float))
726                                 return &ffi_type_float;
727 
728                         return &ffi_type_longdouble;
729                 }
730 
731                 switch (size) {
732                 case 1:
733                         return (info & CTF_UNSIGNED) ? &ffi_type_uchar : &ffi_type_schar;
734 
735                 case 2:
736                         return (info & CTF_UNSIGNED) ? &ffi_type_uint16 : &ffi_type_sint16;
737 
738                 case 4:
739                         return (info & CTF_UNSIGNED) ? &ffi_type_uint32 : &ffi_type_sint32;
740 
741                 case 8:
742                         return (info & CTF_UNSIGNED) ? &ffi_type_uint64 : &ffi_type_sint64;
743                 }
744 
745                 assert(0);
746                 return NULL;
747 
748         case CT_VOID:
749                 return &ffi_type_void;
750 
751         case CT_ENUM:
752                 switch (ctype_cid(info)) {
753                 case CTID_INT32:
754                         return &ffi_type_sint32;
755 
756                 case CTID_UINT32:
757                         return &ffi_type_uint32;
758                 }
759 
760                 assert(0);
761                 return NULL;
762 
763         case CT_PTR:
764                 return &ffi_type_pointer;
765 
766         case CT_ARRAY:
767                 switch (cid) {
768                 case CTID_COMPLEX_FLOAT:
769                         return &ffi_type_complex_float;
770 
771                 case CTID_COMPLEX_DOUBLE:
772                         return &ffi_type_complex_double;
773                 }
774 
775                 /* fall through */
776 
777         case CT_STRUCT:
778                 if (!st)
779                         st = xalloc(sizeof(ffi_type));
780 
781                 st->type = FFI_TYPE_STRUCT;
782                 st->size = size;
783                 st->alignment = ctype_align(info);
784 
785                 return st;
786         }
787 
788         return NULL;
789 }
790 
791 static uc_value_t *
792 clib_dlsym(uc_vm_t *vm, uc_ffi_clib_t *lib, uc_value_t *name)
793 {
794         uc_value_t *sym;
795         bool exists;
796 
797         if (!lib || ucv_type(name) != UC_STRING)
798                 return NULL;
799 
800         sym = ucv_object_get(lib->cache, ucv_string_get(name), &exists);
801 
802         if (!exists) {
803                 CTState *cts = ctype_cts(vm);
804                 CType *ct;
805                 CTypeID id = uc_ctype_getname(cts, &ct, name, CLNS_INDEX);
806 
807                 if (!id) {
808                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
809                                 "missing declaration for symbol '%s'",
810                                 ucv_string_get(name));
811 
812                         return NULL;
813                 }
814 
815                 if (ctype_isconstval(ct->info)) {
816                         sym = ucv_uint64_new(ct->size);
817                 }
818                 else {
819                         uc_value_t *extname = clib_extsym(cts, ct, name);
820 
821                         if (!ctype_isfunc(ct->info) && !ctype_isextern(ct->info)) {
822                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
823                                         "unexpected ctype %08x for symbol '%s' in clib",
824                                         ct->info, ucv_string_get(name));
825 
826                                 return NULL;
827                         }
828 
829 #if UC_TARGET_WINDOWS
830                         DWORD oldwerr = GetLastError();
831 #endif
832                         void *p = dlsym(lib->dlh, ucv_string_get(extname));
833 
834 #if UC_TARGET_WINDOWS
835                         SetLastError(oldwerr);
836 #endif
837 
838                         if (!p) {
839                                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
840                                         "cannot resolve symbol '%s': %s",
841                                         ucv_string_get(name),
842                                         dlerror());
843 
844                                 return NULL;
845                         }
846 
847                         /* dlsym returns a pointer to the symbol (not the value).
848                          * Wrap the symbol's type in a pointer for correct semantics. */
849                         CTypeID ptr_id = uc_ctype_intern(cts, CTINFO(CT_PTR, CTALIGN_PTR) + id, CTSIZE_PTR);
850 
851                         sym = uc_cdata_new(vm, ptr_id, CTSIZE_PTR);
852                         *(void **)uc_cdata_dataptr(sym) = p;
853                 }
854 
855                 ucv_object_add(lib->cache, ucv_string_get(name), sym);
856         }
857 
858         return ucv_get(sym);
859 }
860 
861 static uc_value_t *
862 uc_ctype_call(uc_vm_t *vm, size_t nargs);
863 
864 static uc_value_t *
865 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size,
866          uc_value_t *refs);
867 
868 /* Path token types */
869 typedef enum {
870         PATH_TOKEN_FIELD,       /* Field name: "foo" */
871         PATH_TOKEN_INDEX        /* Array index: "[0]" */
872 } path_token_type;
873 
874 typedef struct {
875         path_token_type type;
876         union {
877                 char *field;            /* Allocated field name (for PATH_TOKEN_FIELD) */
878                 size_t index;           /* For PATH_TOKEN_INDEX */
879         };
880 } path_token;
881 
882 typedef struct {
883         path_token *entries;
884         size_t count;
885 } path_tokens;
886 
887 /* Free path tokens */
888 static void
889 path_tokens_free(path_tokens *tokens)
890 {
891         uc_vector_foreach(tokens, tok)
892                 if (tok->type == PATH_TOKEN_FIELD)
893                         free(tok->field);
894 
895         uc_vector_clear(tokens);
896 }
897 
898 /* Tokenize a path string like "foo.bar[0].baz" or "foo.0.bar" */
899 static bool
900 path_tokenize(uc_vm_t *vm, uc_value_t *key_uv, path_tokens *tokens)
901 {
902         if (ucv_type(key_uv) != UC_STRING)
903                 return false;
904 
905         const char *path = ucv_string_get(key_uv);
906         size_t len = ucv_string_length(key_uv);
907 
908         size_t i = 0;
909         while (i < len) {
910                 /* Skip dots */
911                 if (path[i] == '.') {
912                         i++;
913                         continue;
914                 }
915 
916                 /* Check for array index [n] */
917                 if (path[i] == '[') {
918                         /* Find closing bracket */
919                         size_t j = i + 1;
920                         while (j < len && path[j] != ']')
921                                 j++;
922 
923                         if (j >= len) {
924                                 uc_vm_raise_exception(vm, EXCEPTION_SYNTAX,
925                                         "Invalid path syntax: missing closing bracket");
926                                 path_tokens_free(tokens);
927                                 return false;
928                         }
929 
930                         /* Parse index */
931                         char *endptr;
932                         size_t idx = strtoul(path + i + 1, &endptr, 10);
933                         if (endptr != path + j) {
934                                 uc_vm_raise_exception(vm, EXCEPTION_SYNTAX,
935                                         "Invalid path syntax: invalid array index");
936                                 path_tokens_free(tokens);
937                                 return false;
938                         }
939 
940                         /* Add index token */
941                         uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_INDEX, .index = idx });
942 
943                         i = j + 1;
944                         continue;
945                 }
946 
947                 /* Field name */
948                 size_t j = i;
949                 while (j < len && path[j] != '.' && path[j] != '[')
950                         j++;
951 
952                 if (j > i) {
953                         /* Add field token */
954                         size_t field_len = j - i;
955                         char *field = malloc(field_len + 1);
956                         if (!field) {
957                                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
958                                         "Out of memory");
959                                 path_tokens_free(tokens);
960                                 return false;
961                         }
962                         memcpy(field, path + i, field_len);
963                         field[field_len] = '\0';
964 
965                         uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_FIELD, .field = field });
966                 }
967 
968                 i = j;
969         }
970 
971         return tokens->count > 0;
972 }
973 
974 /* Navigate through a cdata using path tokens.
975  * Returns final CType, updates pointer to final location.
976  * Sets *error to true on failure.
977  */
978 static CType *
979 path_navigate(CTState *cts, GCcdata *start_cd, path_tokens *tokens,
980               uint8_t **pptr, CType **pct, bool *error)
981 {
982         *error = false;
983         uint8_t *p = cdataptr(start_cd);
984         CType *ct = ctype_get(cts, start_cd->ctypeid);
985 
986         /* Skip extern and attribute wrappers */
987         while (ctype_isextern(ct->info) || ctype_isattrib(ct->info))
988                 ct = ctype_child(cts, ct);
989 
990         /* Handle reference indirection */
991         if (ctype_isref(ct->info)) {
992                 p = *(uint8_t **)p;
993                 ct = ctype_child(cts, ct);
994         }
995 
996         uc_vector_foreach(tokens, tok) {
997                 if (tok->type == PATH_TOKEN_FIELD) {
998                         /* String key - struct field access */
999                         if (!ctype_isstruct(ct->info)) {
1000                                 *error = true;
1001                                 return NULL;
1002                         }
1003 
1004                         CTSize ofs;
1005                         CTInfo fqual = 0;
1006                         uc_value_t *field_key = ucv_string_new(tok->field);
1007 
1008                         CType *fct = uc_ctype_getfieldq(cts, ct, field_key, &ofs, &fqual);
1009                         ucv_put(field_key);
1010 
1011                         if (!fct) {
1012                                 *error = true;
1013                                 return NULL;
1014                         }
1015 
1016                         p += ofs;
1017                         ct = fct;
1018 
1019                         /* Get the actual field type */
1020                         ct = ctype_child(cts, ct);
1021 
1022                         /* Skip attributes on field */
1023                         while (ctype_isattrib(ct->info))
1024                                 ct = ctype_child(cts, ct);
1025                 }
1026                 else {
1027                         /* Integer key - array/pointer access */
1028                         if (!ctype_ispointer(ct->info) && !ctype_isarray(ct->info)) {
1029                                 *error = true;
1030                                 return NULL;
1031                         }
1032 
1033                         CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info));
1034                         if (sz == CTSIZE_INVALID) {
1035                                 *error = true;
1036                                 return NULL;
1037                         }
1038 
1039                         if (ctype_isptr(ct->info))
1040                                 p = (uint8_t *)cdata_getptr(p, ct->size);
1041 
1042                         /* Check bounds for arrays */
1043                         if (ctype_isarray(ct->info)) {
1044                                 CTSize arr_len = ct->size / sz;
1045                                 if (tok->index >= arr_len) {
1046                                         *error = true;
1047                                         return NULL;
1048                                 }
1049                         }
1050 
1051                         p += tok->index * sz;
1052                         ct = ctype_rawchild(cts, ct);
1053                 }
1054         }
1055 
1056         *pct = ct;
1057         *pptr = p;
1058         return ct;
1059 }
1060 
1061 static uc_value_t *
1062 clib_wrapped_call(uc_vm_t *vm, size_t nargs)
1063 {
1064         uc_callframe_t *call = uc_vector_last(&vm->callframes);
1065         uc_cfunction_t *cfn = call->cfunction;
1066         size_t off = ALIGN(sizeof(*cfn) + strlen(cfn->name) + 1);
1067         CTypeID cid = *(CTypeID *)((char *)cfn + off);
1068         void *fp = *(void **)((char *)cfn + off + sizeof(cid));
1069 
1070         uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR);
1071         *(void **)uc_cdata_dataptr(sym) = fp;
1072 
1073         uc_value_t *ctx = call->ctx;
1074         call->ctx = sym;
1075 
1076         uc_value_t *ret = uc_ctype_call(vm, nargs);
1077 
1078                 /* Auto-convert primitive return values for convenience */
1079                 if (ret) {
1080                         GCcdata *cd = ucv_resource_data(ret, "ffi.ctype");
1081                         if (cd) {
1082                                 CTState *cts = ctype_cts(vm);
1083                                 CType *ct = ctype_get(cts, cd->ctypeid);
1084 
1085                                 if (ct && !ctype_isfunc(ct->info) && !ctype_isptr(ct->info)) {
1086                                         /* Primitives: convert to ucode values */
1087                                         uc_value_t *converted = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), ct->size, NULL);
1088                                         ucv_put(ret);
1089                                         ret = converted;
1090                                 }
1091                                 /* Pointers remain as cdata for explicit control:
1092                                  * - Avoid memory leaks from auto-copying char*
1093                                  * - Allow explicit ffi.string() conversion when needed
1094                                  * - Enable pointer arithmetic and dereferencing
1095                                  */
1096                         }
1097                 }
1098 
1099         ucv_put(call->ctx);
1100         call->ctx = ctx;
1101 
1102         return ret;
1103 }
1104 
1105 
1106 /**
1107  * Represents a handle to a loaded shared library.
1108  *
1109  * @class module:ffi.CLib
1110  * @hideconstructor
1111  *
1112  * @see {@link module:ffi#dlopen|dlopen()}
1113  *
1114  * @example
1115  *
1116  * const lib = dlopen(…);
1117  *
1118  * lib.wrap(…);
1119  * lib.dlsym(…);
1120  */
1121 
1122 /**
1123  * Look up a symbol in the loaded library.
1124  *
1125  * The `dlsym()` method retrieves a symbol (function, variable, or constant)
1126  * from the loaded shared library or global symbol table.
1127  *
1128  * **Input patterns:**
1129  *
1130  * 1. **Bare symbol name**: Look up by symbol name directly. Returns a cdata
1131  *    pointer for functions/variables, or a number for constants.
1132  *
1133  * 2. **Full declaration**: Provide a complete declaration string. The symbol
1134  *    name is extracted and used for lookup.
1135  *
1136  * @function module:ffi.CLib#dlsym
1137  *
1138  * @param {string} name
1139  * The symbol name or full declaration string.
1140  *
1141  * @returns {?module:ffi.CData|number}
1142  * A cdata pointer for functions/variables, a number for constants,
1143  * or `null` if the symbol cannot be resolved.
1144  *
1145  * @throws {Error}
1146  * Throws an exception if the symbol cannot be found or the declaration
1147  * syntax is invalid.
1148  *
1149  * @example
1150  * // Pattern 1: Bare symbol name
1151  * ffi.cdef('extern char **environ;');
1152  * let env = ffi.C.dlsym('environ');
1153  * print(env.get(0), "\n");
1154  *
1155  * @example
1156  * // Pattern 2: Full declaration
1157  * let getenv = ffi.C.dlsym('char *getenv(char *)');
1158  * print(ffi.string(getenv.deref('char *')));
1159  *
1160  * @example
1161  * // Access constant value (returns number)
1162  * ffi.cdef('const int INT_MAX;');
1163  * let max = ffi.C.dlsym('INT_MAX');  // => number
1164  */
1165 static uc_value_t *
1166 uc_clib_dlsym(uc_vm_t *vm, size_t nargs)
1167 {
1168         uc_ffi_clib_t *lib = uc_fn_thisval("ffi.clib");
1169         uc_value_t *arg = uc_fn_arg(0);
1170         CTState *cts = ctype_cts(vm);
1171 
1172         if (ucv_type(arg) == UC_STRING) {
1173                 const char *s = ucv_string_get(arg);
1174                 if (strpbrk(s, " \t\n\r")) {
1175                         /* Parse the declaration */
1176                         CPState cp = {
1177                                 .uv_vm = vm,
1178                                 .cts = cts,
1179                                 .srcname = s,
1180                                 .p = s,
1181                                 .uv_param = NULL,
1182                                 .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT
1183                         };
1184 
1185                         if (!uc_cparse(&cp)) {
1186                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1187                                         "failed to parse C declaration: '%s'", s);
1188                                 return NULL;
1189                         }
1190 
1191                         /* Get the symbol name from the parsed type */
1192                         CType *ct = ctype_raw(cts, cp.val.id);
1193                         if (!ct || !ct->uv_name) {
1194                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1195                                         "declaration does not define a named symbol");
1196                                 return NULL;
1197                         }
1198 
1199                         /* Use the symbol name for lookup */
1200                         arg = ct->uv_name;
1201                 } else {
1202                         /* Bare symbol name: first check if there's a declaration */
1203                         CType *ct;
1204                         CTypeID id = uc_ctype_getname(cts, &ct, arg, CLNS_INDEX);
1205                         if (id) {
1206                                 /* Declaration exists, use normal clib_dlsym */
1207                                 return clib_dlsym(vm, lib, arg);
1208                         } else {
1209                                 /* No declaration: direct dlsym returning void* cdata */
1210                                 void *p = dlsym(lib->dlh, s);
1211                                 if (!p) {
1212                                         /* Symbol not found */
1213                                         return NULL;
1214                                 }
1215                                 /* Create a void* cdata */
1216                                 CTypeID voidp = CTID_P_VOID;
1217                                 uc_value_t *cd = uc_cdata_new(vm, voidp, sizeof(void*));
1218                                 void **ptr = (void**)uc_cdata_dataptr(cd);
1219                                 *ptr = p;
1220                                 return cd;
1221                         }
1222                 }
1223         }
1224 
1225         return clib_dlsym(vm, lib, arg);
1226 }
1227 
1228 static uc_value_t *
1229 uc_clib_resolve_common(CTState *cts, uc_ffi_clib_t *lib, uc_value_t *cdef,
1230                        CType **ctp, GCcdata **cdp)
1231 {
1232         const char *spec;
1233         size_t spec_len, pos;
1234         GCcdata *cd;
1235         CType *ct;
1236         void *fp;
1237 
1238         if (!lib)
1239                 return NULL;
1240 
1241         if (ucv_type(cdef) == UC_STRING) {
1242                 spec = ucv_string_get(cdef);
1243                 spec_len = ucv_string_length(cdef);
1244 
1245                 pos = strcspn(spec, " \t\r\n*[{(");
1246 
1247                 if (pos != spec_len) {
1248                         CTypeID cid = uv_to_ct(cts->vm, CPARSE_MODE_DIRECT, cdef, NULL);
1249 
1250                         if (!cid)
1251                                 return NULL;
1252 
1253                         CType *ct = ctype_raw(cts, cid);
1254 
1255                         uc_value_t *sym_name = ct->uv_name;
1256                         uc_value_t *sym = clib_dlsym(cts->vm, lib, sym_name);
1257 
1258                         if (!sym) {
1259                                 uc_value_t *repr = uc_ctype_repr(cts->vm, cid, ct->uv_name);
1260 
1261                                 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
1262                                         "unable to resolve symbol '%s' for declaration '%s'",
1263                                         ucv_string_get(sym_name), ucv_string_get(repr));
1264 
1265                                 ucv_put(repr);
1266 
1267                                 return NULL;
1268                         }
1269 
1270                         GCcdata *cd = ucv_resource_data(sym, "ffi.ctype");
1271                         assert(cd);
1272 
1273                         /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type.
1274                          * For function pointers, this gives us the function type which should match the declaration. */
1275                         CType *sym_ct = ctype_get(cts, cd->ctypeid);
1276                         if (ctype_isptr(sym_ct->info))
1277                                 sym_ct = ctype_rawchild(cts, sym_ct);
1278 
1279                         CType *decl_ct = ctype_get(cts, cid);
1280 
1281                         if (sym_ct != decl_ct) {
1282                                 uc_value_t *repr_decl = uc_ctype_repr(cts->vm, cid, ct->uv_name);
1283                                 uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL);
1284 
1285                                 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
1286                                         "type mismatch between declaration (%s) and resolved symbol (%s)",
1287                                         ucv_string_get(repr_decl),
1288                                         ucv_string_get(repr_sym));
1289 
1290                                 ucv_put(repr_decl);
1291                                 ucv_put(repr_sym);
1292                                 ucv_put(sym);
1293 
1294                                 return NULL;
1295                         }
1296 
1297                         if (ctp)
1298                                 *ctp = ct;
1299 
1300                         if (cdp)
1301                                 *cdp = cd;
1302 
1303                         return sym;
1304                 }
1305                 else {
1306                         CType *ct;
1307                         uc_value_t *sym_uv = ucv_string_new(spec);
1308                         CTypeID id = uc_ctype_getname(cts, &ct, sym_uv, CLNS_INDEX);
1309                         ucv_put(sym_uv);
1310 
1311                         if (!id) {
1312                                 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
1313                                         "unknown symbol '%s'", spec);
1314 
1315                                 return NULL;
1316                         }
1317 
1318                         uc_value_t *sym = clib_dlsym(cts->vm, lib, cdef);
1319 
1320                         if (!sym) {
1321                                 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
1322                                         "unable to resolve symbol '%s'", spec);
1323 
1324                                 return NULL;
1325                         }
1326 
1327                         GCcdata *cd = ucv_resource_data(sym, "ffi.ctype");
1328                         assert(cd);
1329 
1330                         /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type. */
1331                         CType *sym_ct = ctype_get(cts, cd->ctypeid);
1332                         if (ctype_isptr(sym_ct->info))
1333                                 sym_ct = ctype_rawchild(cts, sym_ct);
1334 
1335                         CType *decl_ct = ctype_get(cts, id);
1336 
1337                         if (sym_ct != decl_ct) {
1338                                 uc_value_t *repr_decl = uc_ctype_repr(cts->vm, id, ct->uv_name);
1339                                 uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL);
1340 
1341                                 uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
1342                                         "type mismatch between declared type '%s' and resolved symbol type '%s'",
1343                                         ucv_string_get(repr_decl),
1344                                         ucv_string_get(repr_sym));
1345 
1346                                 ucv_put(repr_decl);
1347                                 ucv_put(repr_sym);
1348                                 ucv_put(sym);
1349 
1350                                 return NULL;
1351                         }
1352 
1353                         if (ctp)
1354                                 *ctp = ct;
1355 
1356                         if (cdp)
1357                                 *cdp = cd;
1358 
1359                         return sym;
1360                 }
1361         }
1362 
1363         cd = ucv_resource_data(cdef, "ffi.ctype");
1364         if (!cd)
1365                 return NULL;
1366 
1367         /* Handle ctype resources containing function pointers */
1368         uc_vm_t *vm = cts->vm;
1369 
1370         ct = ctype_get(cts, cd->ctypeid);
1371 
1372         if (!ct)
1373                 return NULL;
1374 
1375         /* Unwrap pointer types to get to the actual function type */
1376         if (ctype_isptr(ct->info)) {
1377                 ct = ctype_rawchild(cts, ct);
1378         }
1379 
1380         if (!ct || !ctype_isfunc(ct->info)) {
1381                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
1382 
1383                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1384                         "attempt to wrap non-function cdata type '%s'",
1385                         ucv_string_get(repr));
1386 
1387                 ucv_put(repr);
1388 
1389                 return NULL;
1390         }
1391 
1392         /* Extract the function pointer from the cdata */
1393         fp = *(void **)cdataptr(cd);
1394 
1395         if (!fp) {
1396                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
1397                         "attempt to wrap NULL function pointer");
1398 
1399                 return NULL;
1400         }
1401 
1402         if (ctp)
1403                 *ctp = ct;
1404 
1405         if (cdp)
1406                 *cdp = cd;
1407 
1408         /* Create a temporary cdata to hold the function pointer for the caller */
1409         uc_value_t *sym = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR);
1410         *(void **)uc_cdata_dataptr(sym) = fp;
1411 
1412         return sym;
1413 }
1414 
1415 /**
1416  * Resolve a symbol to a cdata pointer.
1417  *
1418  * The `resolve()` method retrieves a symbol from the loaded library and
1419  * returns a cdata pointer. Unlike `wrap()`, it does not create a callable
1420  * wrapper - it returns the raw pointer for manual handling.
1421  *
1422  * @function module:ffi.CLib#resolve
1423  *
1424  * @param {string|module:ffi.CData} decl
1425  * The function declaration or cdata function pointer.
1426  *
1427  * @returns {?module:ffi.CData}
1428  * A cdata pointer to the symbol, or `null` if resolution fails.
1429  *
1430  * @throws {Error}
1431  * Throws an exception if the symbol cannot be resolved.
1432  *
1433  * @example
1434  * // Resolve function pointer
1435  * ffi.cdef('int strcmp(const char *, const char *)');
1436  * let ptr = ffi.C.resolve('strcmp');
1437  * // ptr is a cdata, not callable directly
1438  */
1439 static uc_value_t *
1440 uc_clib_resolve(uc_vm_t *vm, size_t nargs)
1441 {
1442         uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib");
1443         uc_value_t *cdef = uc_fn_arg(0);
1444         CTState *cts = ctype_cts(vm);
1445 
1446         return uc_clib_resolve_common(cts, this, cdef, NULL, NULL);
1447 }
1448 
1449 /**
1450  * Wrap a C function symbol into a callable ucode function.
1451  *
1452  * The `wrap()` method retrieves a function symbol from the library and returns
1453  * a callable wrapper that handles argument marshaling and function invocation
1454  * via libffi.
1455  *
1456  * **Input patterns:**
1457  *
1458  * 1. **Full declaration**: Provide a complete function declaration string.
1459  *    The symbol name is extracted automatically.
1460  *
1461  * 2. **Bare symbol**: Provide just the symbol name. Requires that the type
1462  *    was previously declared via `cdef()`.
1463  *
1464  * 3. **cdata pointer**: Provide a cdata containing a function pointer
1465  *    (e.g., from `dlsym()`). The type must match the cdata's type.
1466  *
1467  * @function module:ffi.CLib#wrap
1468  *
1469  * @param {string|module:ffi.CData} decl
1470  * The function declaration string, bare symbol name, or cdata function pointer.
1471  *
1472  * @returns {?function}
1473  * A callable function wrapper, or `null` if resolution fails.
1474  *
1475  * @throws {Error}
1476  * Throws an exception if the symbol cannot be resolved or is not a function.
1477  *
1478  * @example
1479  * // Pattern 1: Full declaration (no cdef needed)
1480  * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)');
1481  * print(strcmp("hello", "world"));  // => number (auto-converted)
1482  *
1483  * @example
1484  * // Pattern 2: Bare symbol (requires cdef)
1485  * ffi.cdef('int strcmp(const char *, const char *)');
1486  * let strcmp = ffi.C.wrap('strcmp');
1487  * print(strcmp("hello", "world"));  // => number (auto-converted)
1488  *
1489  * @example
1490  * // Pattern 3: cdata function pointer
1491  * ffi.cdef('size_t strlen(const char *)');
1492  * let strlen_sym = ffi.C.dlsym('strlen');
1493  * let strlen_fn = ffi.C.wrap(strlen_sym);
1494  * print(strlen_fn("hello"));  // => number (auto-converted)
1495  *
1496  * @example
1497  * // Pointer returns remain as cdata for explicit control
1498  * let getenv = ffi.C.wrap('char *getenv(char *)');
1499  * let path_ptr = getenv('PATH');  // => cdata (char*)
1500  * let path = ffi.string(path_ptr);  // Convert to ucode string
1501  */
1502 static uc_value_t *
1503 uc_clib_wrap(uc_vm_t *vm, size_t nargs)
1504 {
1505         uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib");
1506         uc_value_t *cdef = uc_fn_arg(0);
1507         CTState *cts = ctype_cts(vm);
1508         GCcdata *cd;
1509         CType *ct;
1510 
1511         uc_value_t *sym = uc_clib_resolve_common(cts, this, cdef, &ct, &cd);
1512 
1513         if (!sym)
1514                 return NULL;
1515 
1516         CTypeID cid = ctype_typeid(cts, ct);
1517         uc_value_t *sym_name = ct->uv_name;
1518 
1519         if (ctype_isptr(ct->info))
1520                 ct = ctype_rawchild(cts, ct);
1521 
1522         if (!ct || !ctype_isfunc(ct->info)) {
1523                 uc_value_t *repr_sym = uc_ctype_repr(vm, cd->ctypeid, NULL);
1524 
1525                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1526                         "attempt to wrap non-function value type '%s'",
1527                         ucv_string_get(repr_sym));
1528 
1529                 ucv_put(repr_sym);
1530                 ucv_put(sym);
1531 
1532                 return NULL;
1533         }
1534 
1535         void *fp = *(void **)uc_cdata_dataptr(sym);
1536         uc_cfunction_t *cfn = NULL;
1537         size_t namelen, off;
1538 
1539         namelen = snprintf(NULL, 0, "ffi.%s.%s",
1540                 this->name ? this->name : "C", ucv_string_get(sym_name));
1541 
1542         off = ALIGN(sizeof(*cfn) + namelen + 1);
1543 
1544         cfn = xalloc(off + sizeof(cid) + sizeof(fp));
1545         cfn->header.type = UC_CFUNCTION;
1546         cfn->cfn = clib_wrapped_call;
1547 
1548         snprintf(cfn->name, namelen + 1, "ffi.%s.%s",
1549                 this->name ? this->name : "C", ucv_string_get(sym_name));
1550 
1551         memcpy((char *)cfn + off, &cid, sizeof(cid));
1552         memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp));
1553 
1554         ucv_put(sym);
1555 
1556         return ucv_get(&cfn->header);
1557 }
1558 
1559 
1560 static size_t
1561 uc_ctype_count_custom_types(CTState *cts, CType *funcspec, CTypeID argtype)
1562 {
1563         size_t n_custom_types = 0;
1564 
1565         /* check whether return value requires a custom ffi type */
1566         if (uc_ctype_requires_ffi_struct(cts, ctype_cid(funcspec->info)))
1567                 n_custom_types++;
1568 
1569         while (true) {
1570                 if (!argtype)
1571                         break;
1572 
1573                 CType *ctf = ctype_get(cts, argtype);
1574 
1575                 assert(ctype_isfield(ctf->info));
1576 
1577                 argtype = ctf->sib;
1578 
1579                 if (uc_ctype_requires_ffi_struct(cts, ctype_cid(ctf->info)))
1580                         n_custom_types++;
1581         }
1582 
1583         return n_custom_types;
1584 }
1585 
1586 typedef struct {
1587         ffi_closure closure;
1588         ffi_cif cif;
1589         void *codeloc;
1590         uc_vm_t *vm;
1591         uc_value_t *func;
1592         CType *ct;
1593         ffi_type *argtypes[];
1594 } uc_closure_context_t;
1595 
1596 static void
1597 uc_ctype_closure_cb(ffi_cif *cif, void *ret, void *args[], void *ud)
1598 {
1599         uc_value_t *uv_arg, *uv_ret = ucv_uint64_new(0);
1600         uc_closure_context_t *context = ud;
1601         uc_exception_type_t ex;
1602 
1603         CTState *cts = ctype_cts(context->vm);
1604         CType *ct_arg, *ct_ret;
1605         CTypeID id_arg;
1606 
1607         uc_vm_stack_push(context->vm, ucv_get(context->func));
1608 
1609         /* skip attribute entries */
1610         for (id_arg = context->ct->sib;
1611              id_arg && ctype_isattrib(ctype_get(cts, id_arg)->info);
1612              id_arg = ctype_get(cts, id_arg)->sib)
1613                 ;
1614 
1615         for (size_t i = 0; i < cif->nargs; i++) {
1616                 uv_arg = NULL;
1617 
1618                 assert(id_arg);
1619                 ct_arg = ctype_get(cts, id_arg);
1620 
1621                 assert(ctype_isfield(ct_arg->info));
1622                 id_arg = ct_arg->sib;
1623 
1624                 uc_cconv_tv_ct(cts, ctype_raw(cts, ctype_cid(ct_arg->info)),
1625                         ctype_cid(ct_arg->info), &uv_arg, args[i]);
1626 
1627                 uc_vm_stack_push(context->vm, uv_arg);
1628         }
1629 
1630         ex = uc_vm_call(context->vm, false, cif->nargs);
1631 
1632         if (ex == EXCEPTION_NONE)
1633                 uv_ret = uc_vm_stack_pop(context->vm);
1634 
1635         ct_ret = ctype_get(cts, ctype_cid(context->ct->info));
1636 
1637         // FIXME: ret value ref
1638         uc_cconv_ct_init(cts, ct_ret, ct_ret->size, ret, &uv_ret, 1, NULL);
1639         ucv_put(uv_ret);
1640 }
1641 
1642 static uc_closure_context_t *
1643 ct_to_closure(uc_vm_t *vm, CTState *cts, CType *ct, uc_value_t *func)
1644 {
1645         ffi_type *custom_type, **argument_type, *atype, *rtype;
1646         uc_closure_context_t *context;
1647         ffi_abi abi = FFI_DEFAULT_ABI;
1648         size_t context_size;
1649         CTypeID cid_arg;
1650         ffi_status st;
1651         void *codeloc;
1652 
1653         if (!ucv_is_callable(func)) {
1654                 char *repr = ucv_to_string(vm, func);
1655 
1656                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1657                         "attempt to bind non-function value '%s'",
1658                         repr ? repr : "null");
1659 
1660                 free(repr);
1661 
1662                 return NULL;
1663         }
1664 
1665         /* resolve function type */
1666         if (ct && ctype_isptr(ct->info))
1667                 ct = ctype_rawchild(cts, ct);
1668 
1669         if (!ct || !ctype_isfunc(ct->info)) {
1670                 uc_value_t *repr = uc_ctype_repr(vm, ctype_typeid(cts, ct), NULL);
1671 
1672                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1673                         "attempt to wrap non-function C type '%s'",
1674                         repr ? ucv_string_get(repr) : "NULL");
1675 
1676                 ucv_put(repr);
1677 
1678                 return NULL;
1679         }
1680 
1681         /* can't wrap variadic functions */
1682         if (ct->info & CTF_VARARG) {
1683                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1684                         "wrapping variadic C function types is not supported");
1685 
1686                 return NULL;
1687         }
1688 
1689         /* skip attribute entries */
1690         for (cid_arg = ct->sib;
1691              cid_arg && ctype_isattrib(ctype_get(cts, cid_arg)->info);
1692              cid_arg = ctype_get(cts, cid_arg)->sib)
1693                 ;
1694 
1695         /* compute required size & allocate storage for closure context */
1696         context_size = sizeof(*context)
1697                 + ct->size * sizeof(ffi_type *)
1698                 + uc_ctype_count_custom_types(cts, ct, cid_arg) * sizeof(ffi_type);
1699 
1700         context = ffi_closure_alloc(context_size, &codeloc);
1701 
1702         if (!context) {
1703                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
1704                         "unable to allocate FFI closure context");
1705 
1706                 return NULL;
1707         }
1708 
1709         context->codeloc = codeloc;
1710         context->func = ucv_get(func);
1711         context->vm = vm;
1712         context->ct = ct;
1713 
1714         argument_type = (ffi_type **)context->argtypes;
1715         custom_type = (ffi_type *)&argument_type[ct->size];
1716 
1717         /* select ABI */
1718 #ifdef X86
1719         switch (ctype_cconv(ct->info)) {
1720         case CTCC_FASTCALL: abi = FFI_FASTCALL; break;
1721         case CTCC_THISCALL: abi = FFI_THISCALL; break;
1722         case CTCC_STDCALL:  abi = FFI_STDCALL;  break;
1723         case CTCC_CDECL:    abi = FFI_MS_CDECL; break;
1724         }
1725 #endif
1726 
1727         if (ctype_isvector(ct->info)) {
1728 #if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64)
1729                 if (ct->size != 8 && ct->size != 16) {
1730                         uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL);
1731 
1732                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
1733                                 "vector return type '%s' is not supported",
1734                                 ucv_string_get(repr));
1735 
1736                         ucv_put(repr);
1737 
1738                         return NULL;
1739                 }
1740 #endif
1741         }
1742 
1743         rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct->info), custom_type);
1744 
1745         if (!rtype) {
1746                 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL);
1747 
1748                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1749                         "don't know how to handle return type '%s'",
1750                         ucv_string_get(repr));
1751 
1752                 ucv_put(repr);
1753 
1754                 goto out;
1755         }
1756 
1757         if (rtype->type == FFI_TYPE_STRUCT)
1758                 custom_type++;
1759 
1760         for (size_t i = 0; i < ct->size; i++) {
1761                 assert(cid_arg);
1762 
1763                 CType *ct_arg = ctype_get(cts, cid_arg);
1764 
1765                 assert(ctype_isfield(ct_arg->info));
1766 
1767                 cid_arg = ct_arg->sib;
1768                 atype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_arg->info), custom_type);
1769 
1770                 if (!atype) {
1771                         uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL);
1772 
1773                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1774                                 "don't know how to handle argument type '%s'",
1775                                 ucv_string_get(repr));
1776 
1777                         ucv_put(repr);
1778 
1779                         goto out;
1780                 }
1781 
1782                 if (atype->type == FFI_TYPE_STRUCT)
1783                         custom_type++;
1784 
1785                 *(argument_type++) = atype;
1786         }
1787 
1788         st = ffi_prep_cif(&context->cif, abi, ct->size, rtype, context->argtypes);
1789 
1790         if (st == FFI_OK) {
1791                 st = ffi_prep_closure_loc(&context->closure, &context->cif,
1792                                           uc_ctype_closure_cb, context,
1793                                           context->codeloc);
1794         }
1795 
1796         switch (st) {
1797         case FFI_BAD_TYPEDEF:
1798                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type");
1799                 goto out;
1800 
1801         case FFI_BAD_ABI:
1802                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI");
1803                 goto out;
1804 
1805 #ifdef HAVE_FFI_BAD_ARGTYPE
1806         case FFI_BAD_ARGTYPE:
1807                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type");
1808                 goto out;
1809 #endif
1810 
1811         case FFI_OK:
1812                 return context;
1813         }
1814 
1815 out:
1816         ffi_closure_free(context);
1817 
1818         return NULL;
1819 }
1820 
1821 static uc_value_t *
1822 uc_ctype_call(uc_vm_t *vm, size_t nargs)
1823 {
1824         GCcdata *cd = uc_fn_thisval("ffi.ctype");
1825         CTState *cts = ctype_cts(vm);
1826         CType *ct = cd ? ctype_get(cts, cd->ctypeid) : NULL;
1827         CTSize sz = CTSIZE_PTR;
1828 
1829         if (ct && ctype_isptr(ct->info)) {
1830                 sz = ct->size;
1831                 ct = ctype_rawchild(cts, ct);
1832         }
1833 
1834         if (!ct || !ctype_isfunc(ct->info)) {
1835                 uc_value_t *repr = cd ? uc_ctype_repr(vm, cd->ctypeid, NULL) : NULL;
1836 
1837                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1838                         "attempt to call non-function value type '%s'",
1839                         repr ? ucv_string_get(repr) : "NULL");
1840 
1841                 ucv_put(repr);
1842 
1843                 return NULL;
1844         }
1845 
1846         ffi_cif cif;
1847         ffi_abi abi = FFI_DEFAULT_ABI;
1848         ffi_type *rtype = &ffi_type_void;
1849 
1850         /* select ABI */
1851 #ifdef X86
1852         switch (ctype_cconv(ct->info)) {
1853         case CTCC_FASTCALL: abi = FFI_FASTCALL; break;
1854         case CTCC_THISCALL: abi = FFI_THISCALL; break;
1855         case CTCC_STDCALL:  abi = FFI_STDCALL;  break;
1856         case CTCC_CDECL:    abi = FFI_MS_CDECL; break;
1857         }
1858 #endif
1859 
1860         CType *ct_ret = ct; //ctype_child(cts, ct);
1861 
1862         if (ctype_isvector(ct_ret->info)) {
1863 #if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64)
1864                 if (ct_ret->size != 8 && ct_ret->size != 16) {
1865                         uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL);
1866 
1867                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
1868                                 "vector return type '%s' is not supported",
1869                                 ucv_string_get(repr));
1870 
1871                         ucv_put(repr);
1872 
1873                         return NULL;
1874                 }
1875 #endif
1876         }
1877 
1878         rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_ret->info), NULL);
1879 
1880         if (!rtype) {
1881                 uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL);
1882 
1883                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1884                         "don't know how to handle return type '%s'",
1885                         ucv_string_get(repr));
1886 
1887                 ucv_put(repr);
1888 
1889                 return NULL;
1890         }
1891 
1892         /* skip attribute entries */
1893         CTypeID fid = ct->sib;
1894 
1895         while (fid) {
1896                 CType *ctf = ctype_get(cts, fid);
1897 
1898                 if (!ctype_isattrib(ctf->info))
1899                         break;
1900 
1901                 fid = ctf->sib;
1902         }
1903 
1904         struct {
1905                 size_t count;
1906                 ffi_type **entries;
1907         } argtypes = { 0 };
1908 
1909         struct {
1910                 size_t count;
1911                 void **entries;
1912         } argvalues = { 0 };
1913 
1914         struct {
1915                 size_t count;
1916                 void **entries;
1917         } argmem = { 0 };
1918 
1919         uc_value_t *rv = NULL;
1920         size_t nfixedargs = 0;
1921 
1922         /* Count fixed arguments from declaration (before ...) */
1923         CTypeID temp_fid = ct->sib;
1924         while (temp_fid) {
1925                 CType *ctf = ctype_get(cts, temp_fid);
1926                 if (!ctype_isattrib(ctf->info))
1927                         nfixedargs++;
1928                 temp_fid = ctf->sib;
1929         }
1930 
1931         for (size_t i = 0; i < nargs; i++) {
1932                 CTypeID did;
1933                 bool is_vararg = false;
1934 
1935                 if (fid) {
1936                         CType *ctf = ctype_get(cts, fid);
1937 
1938                         assert(ctype_isfield(ctf->info));
1939 
1940                         fid = ctf->sib;
1941                         did = ctype_cid(ctf->info);
1942                 }
1943                 else if (ct->info & CTF_VARARG) {
1944                         is_vararg = true;
1945                         /* For variadic args, infer type from ucode value */
1946                         uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i];
1947                         GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype");
1948 
1949                         if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) {
1950                                 /* cdata argument: use its type directly */
1951                                 did = arg_cd->ctypeid;
1952                         }
1953                         else if (ucv_type(*argp) == UC_STRING) {
1954                                 /* string -> char* */
1955                                 did = CTID_P_CCHAR;
1956                         }
1957                         else if (ucv_type(*argp) == UC_INTEGER) {
1958                                 /* integer -> int (promoted from smaller types) */
1959                                 did = CTID_INT32;
1960                         }
1961                         else if (ucv_type(*argp) == UC_DOUBLE) {
1962                                 /* double stays double (float would be promoted) */
1963                                 did = CTID_DOUBLE;
1964                         }
1965                         else if (ucv_is_callable(*argp)) {
1966                                 /* callback -> function pointer (void*) */
1967                                 did = CTID_P_VOID;
1968                         }
1969                         else {
1970                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1971                                         "unsupported variadic argument type %s",
1972                                         ucv_typename(*argp));
1973                                 goto out;
1974                         }
1975                 }
1976                 else {
1977                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
1978                                 "too many arguments for called function");
1979 
1980                         goto out;
1981                 }
1982 
1983                 CType *d = ctype_raw(cts, did);
1984                 CTSize sz = d->size;
1985                 ffi_type *atype = uc_ctype_to_ffi_type(cts, did, NULL);
1986 
1987                 /* Apply default argument promotions for variadic arguments */
1988                 if (is_vararg && atype) {
1989                         /* Float promotes to double */
1990                         if (atype == &ffi_type_float) {
1991                                 atype = &ffi_type_double;
1992                                 sz = sizeof(double);
1993                                 did = CTID_DOUBLE;
1994                                 d = ctype_get(cts, did);
1995                         }
1996                         /* Small integers promote to int */
1997                         else if (atype == &ffi_type_schar || atype == &ffi_type_uchar ||
1998                                  atype == &ffi_type_sint16 || atype == &ffi_type_uint16) {
1999 #if UC_SIZEOF_INT == 4
2000                                 atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16)
2001                                         ? &ffi_type_uint : &ffi_type_sint;
2002                                 sz = sizeof(int);
2003                                 did = (atype == &ffi_type_uint) ? CTID_UINT32 : CTID_INT32;
2004 #else
2005                                 atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16)
2006                                         ? &ffi_type_uint64 : &ffi_type_sint64;
2007                                 sz = sizeof(int64_t);
2008                                 did = (atype == &ffi_type_uint64) ? CTID_UINT64 : CTID_INT64;
2009 #endif
2010                                 d = ctype_get(cts, did);
2011                         }
2012                 }
2013 
2014                 if (!atype) {
2015                         uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL);
2016 
2017                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
2018                                 "don't know how to handle argument type '%s'",
2019                                 ucv_string_get(repr));
2020 
2021                         ucv_put(repr);
2022 
2023                         goto out;
2024                 }
2025 
2026                 uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i];
2027                 GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype");
2028 
2029                 if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) {
2030                         /* Check if this is an array cdata - if so, wrap pointer in pointer-sized slot */
2031                         CType *arg_ct = ctype_get(cts, arg_cd->ctypeid);
2032                         if (ctype_isarray(arg_ct->info)) {
2033                                 void *memp, *valp;
2034                                 memp = valp = xalloc(sizeof(void *));
2035                                 *(void **)valp = cdataptr(arg_cd);
2036                                 uc_vector_push(&argmem, memp);
2037                                 uc_vector_push(&argvalues, valp);
2038                         }
2039                         else {
2040                                 uc_vector_push(&argvalues, cdataptr(arg_cd));
2041                         }
2042                 }
2043                 else if (ctype_isptr(d->info) && ucv_type(*argp) == UC_OBJECT) {
2044                         CType *child = ctype_rawchild(cts, d);
2045                         if (ctype_isstruct(child->info)) {
2046                                 void *struct_mem = xalloc(child->size);
2047                                 uc_cconv_ct_init(cts, child, child->size, struct_mem, argp, 1, NULL);
2048                                 void *ptr_mem = xalloc(sizeof(void*));
2049                                 *(void**)ptr_mem = struct_mem;
2050                                 uc_vector_push(&argmem, struct_mem);
2051                                 uc_vector_push(&argmem, ptr_mem);
2052                                 uc_vector_push(&argvalues, ptr_mem);
2053                         }
2054                         else {
2055                                 void *memp, *valp;
2056                                 memp = valp = xalloc(sz);
2057                                 uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL);
2058                                 uc_vector_push(&argmem, memp);
2059                                 uc_vector_push(&argvalues, valp);
2060                         }
2061                 }
2062                 else {
2063                         void *memp, *valp;
2064 
2065                         if (ucv_type(*argp) == UC_STRING) {
2066                                 memp = valp = xalloc(sz);
2067                                 *(char **)valp = ucv_string_get(*argp);
2068                         }
2069                         else if (ucv_is_callable(*argp)) {
2070                                 uc_closure_context_t *cc = ct_to_closure(vm, cts, d, *argp);
2071 
2072                                 memp = (void *)((uintptr_t)cc | 1u);
2073                                 valp = &cc->codeloc;
2074                         }
2075                         else {
2076                                 memp = valp = xalloc(sz);
2077                                 uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL);
2078                         }
2079 
2080                         uc_vector_push(&argmem, memp);
2081                         uc_vector_push(&argvalues, valp);
2082                 }
2083 
2084                 uc_vector_push(&argtypes, atype);
2085         }
2086 
2087         if (fid) {
2088                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
2089                         "too few arguments for called function");
2090 
2091                 goto out;
2092         }
2093 
2094         ffi_status st;
2095 
2096         if (ct->info & CTF_VARARG)
2097                 st = ffi_prep_cif_var(&cif, abi, nfixedargs, argtypes.count, rtype, argtypes.entries);
2098         else
2099                 st = ffi_prep_cif(&cif, abi, argtypes.count, rtype, argtypes.entries);
2100 
2101         switch (st) {
2102         case FFI_BAD_TYPEDEF:
2103                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type");
2104                 goto out;
2105 
2106         case FFI_BAD_ABI:
2107                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI");
2108                 goto out;
2109 
2110 #ifdef HAVE_FFI_BAD_ARGTYPE
2111         case FFI_BAD_ARGTYPE:
2112                 uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type");
2113                 goto out;
2114 #endif
2115 
2116         case FFI_OK:
2117                 if (rtype != &ffi_type_void) {
2118                         CTSize rsz = ctype_get(cts, ctype_cid(ct_ret->info))->size;
2119 
2120                         if (rsz < sizeof(ffi_arg) || rsz == CTSIZE_INVALID)
2121                                 rsz = sizeof(ffi_arg);
2122 
2123                         rv = uc_cdata_new(vm, ctype_cid(ct_ret->info), rsz);
2124                 }
2125 
2126                 ffi_call(&cif,
2127                         (void (*)(void))cdata_getptr(cdataptr(cd), sz),
2128                         uc_cdata_dataptr(rv),
2129                         argvalues.entries);
2130         }
2131 
2132 out:
2133 #ifdef __clang_analyzer__
2134         /* Clang static analyzer does not understand that rtype is either a static
2135          * ffi_type or a heap-allocated value that is freed here. Pretend to free
2136          * it unconditionally to suppress the false positive memory leak warning. */
2137         free(rtype);
2138 #else
2139         if (rtype->type == FFI_TYPE_STRUCT)
2140                 free(rtype);
2141 #endif
2142 
2143         while (argtypes.count)
2144                 if (argtypes.entries[--argtypes.count]->type == FFI_TYPE_STRUCT)
2145                         free(argtypes.entries[argtypes.count]);
2146 
2147         while (argmem.count) {
2148                 void *ptr = argmem.entries[--argmem.count];
2149 
2150                 if ((uintptr_t)ptr & 1u) {
2151                         uc_closure_context_t *cc =
2152                                 (uc_closure_context_t *)((uintptr_t)ptr & ~(uintptr_t)1u);
2153 
2154                         ucv_put(cc->func);
2155                         ffi_closure_free(cc);
2156                 }
2157                 else {
2158                         free(ptr);
2159                 }
2160         }
2161 
2162         uc_vector_clear(&argvalues);
2163         uc_vector_clear(&argtypes);
2164         uc_vector_clear(&argmem);
2165 
2166         return rv;
2167 }
2168 
2169 static uc_value_t *
2170 uc_ctype_free(uc_vm_t *vm, size_t nargs)
2171 {
2172         GCcdata **cd = uc_fn_this("ffi.ctype");
2173 
2174         if (cd) {
2175                 if (UC_UNLIKELY(*cd && cdataisv(*cd)))
2176                         free(memcdatav(*cd));
2177                 else
2178                         free(*cd);
2179 
2180                 *cd = NULL;
2181         }
2182 
2183         return NULL;
2184 }
2185 
2186 static uc_value_t *
2187 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size,
2188          uc_value_t *refs);
2189 
2190 static uc_value_t *
2191 ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size,
2192          uc_value_t *refs)
2193 {
2194         CType *ct = ctype_get(cts, cid);
2195         CTInfo info = ct->info;
2196         uc_value_t *s;
2197 
2198         switch (ctype_type(info)) {
2199         case CT_PTR:
2200                 switch (ctype_cid(info)) {
2201                 case CTID_INT8:
2202                 case CTID_UINT8:
2203                         if ((info ^ CTF_UCHAR) & CTF_UNSIGNED)
2204                                 goto generic_ptr;
2205 
2206                         /* fall through */
2207 
2208                 case CTID_CCHAR:
2209                         /* special optimization case: when retrieving the ucode equivalent value of
2210                         a `const char *` pointer, attempt to return a reference to the original
2211                         uv string (if any) nstead of constructing a new heap string */
2212                         if (ctype_cid(info) == CTID_CCHAR) {
2213                                 for (size_t i = 0; i < ucv_array_length(refs); i++) {
2214                                         uc_string_t *us = (uc_string_t *)ucv_array_get(refs, i);
2215 
2216                                         if (us->str == *(char **)cdata) {
2217                                                 return ucv_get(&us->header);
2218                                         }
2219                                 }
2220                         }
2221 
2222                         return *(char **)cdata ? ucv_string_new(*(char **)cdata) : NULL;
2223 
2224                 default:
2225                 generic_ptr:
2226                         /* Return pointer value as integer for all pointer types */
2227                         return ucv_uint64_new((uintptr_t)*(void **)cdata);
2228                 }
2229 
2230                 break;
2231 
2232         case CT_NUM:
2233                 if (info & CTF_BOOL) {
2234                         return ucv_boolean_new(*(bool *)cdata);
2235                 }
2236                 else if ((info & CTF_FP)) {
2237                         if (size == sizeof(double))
2238                                 return ucv_double_new(*(double *)cdata);
2239                         else if (size == sizeof(float))
2240                                 return ucv_double_new(*(float *)cdata);
2241                 }
2242                 else if (size == 1) {
2243                         if (info & CTF_UNSIGNED)
2244                                 return ucv_uint64_new(*(uint8_t *)cdata);
2245                         else
2246                                 return ucv_int64_new(*(int8_t *)cdata);
2247                 }
2248                 else if (size == 2) {
2249                         if (info & CTF_UNSIGNED)
2250                                 return ucv_uint64_new(*(uint16_t *)cdata);
2251                         else
2252                                 return ucv_int64_new(*(int16_t *)cdata);
2253                 }
2254                 else if (size == 4) {
2255                         if (info & CTF_UNSIGNED)
2256                                 return ucv_uint64_new(*(uint32_t *)cdata);
2257                         else
2258                                 return ucv_int64_new(*(int32_t *)cdata);
2259                 }
2260                 else if (size == 8) {
2261                         if (info & CTF_UNSIGNED)
2262                                 return ucv_uint64_new(*(uint64_t *)cdata);
2263                         else
2264                                 return ucv_int64_new(*(int64_t *)cdata);
2265                 }
2266 
2267                 break;
2268 
2269         case CT_ENUM:
2270                 /* attempt to return named enum choice name */
2271                 for (CTypeID choice_id = ct->sib; choice_id; ) {
2272                         CType *choice_type = ctype_get(cts, choice_id);
2273 
2274                         choice_id = choice_type->sib;
2275 
2276                         if (!ctype_isconstval(choice_type->info) || !choice_type->uv_name)
2277                                 continue;
2278 
2279                         if (choice_type->size != *(CTSize *)cdata)
2280                                 continue;
2281 
2282                         return ucv_get(choice_type->uv_name);
2283                 }
2284 
2285                 /* no matching constant name found, return numeric value */
2286                 if (ctype_cid(info) == CTID_UINT32)
2287                         return ucv_uint64_new(*(uint32_t *)cdata);
2288                 else
2289                         return ucv_int64_new(*(int32_t *)cdata);
2290 
2291                 break;
2292 
2293         case CT_ARRAY:
2294                 if (info & CTF_COMPLEX) {
2295                         if (size == 2 * sizeof(float)) {
2296                                 uc_value_t *a = ucv_array_new_length(vm, 2);
2297                                 float *f = (float *)cdata;
2298 
2299                                 ucv_array_set(a, 0, ucv_double_new((double)f[0]));
2300                                 ucv_array_set(a, 1, ucv_double_new((double)f[1]));
2301 
2302                                 return a;
2303                         }
2304                         else if (size == 2 * sizeof(double)) {
2305                                 uc_value_t *a = ucv_array_new_length(vm, 2);
2306                                 double *d = (double *)cdata;
2307 
2308                                 ucv_array_set(a, 0, ucv_double_new(d[0]));
2309                                 ucv_array_set(a, 1, ucv_double_new(d[1]));
2310 
2311                                 return a;
2312                         }
2313                 }
2314                 else {
2315                         CType *elem_type = ctype_rawchild(cts, ct);
2316                         CTSize elem_size = elem_type->size;
2317                         uc_value_t *a = ucv_array_new_length(vm, size / elem_size);
2318 
2319                         for (size_t off = 0; off < size; off += elem_size)
2320                                 ucv_array_push(a,
2321                                         ct_to_uv(vm, cts, ctype_typeid(cts, elem_type),
2322                                                  (char *)cdata + off, elem_size, refs));
2323 
2324                         return a;
2325                 }
2326 
2327                 break;
2328 
2329         case CT_STRUCT:
2330                 s = ucv_object_new(vm);
2331 
2332                 for (CTypeID field_id = ct->sib; field_id; ) {
2333                         CType *field_type = ctype_get(cts, field_id);
2334 
2335                         field_id = field_type->sib;
2336 
2337                         if (ctype_isfield(field_type->info) || ctype_isbitfield(field_type->info)) {
2338                                 if (!field_type->uv_name)
2339                                         continue;
2340 
2341                                 ucv_object_add(s, ucv_string_get(field_type->uv_name),
2342                                         ct_to_uv(vm, cts, ctype_cid(field_type->info),
2343                                                  (char *)cdata + field_type->size,
2344                                                  ctype_rawchild(cts, field_type)->size, refs));
2345                         }
2346                 }
2347 
2348                 return s;
2349         }
2350 
2351         return NULL;
2352 }
2353 
2354 
2355 /**
2356  * Read a value from a C data object.
2357  *
2358  * The `get()` method reads values from cdata objects and returns them
2359  * converted to ucode types. It supports:
2360  *
2361  * - **Scalar values**: `int.get()` returns the scalar value directly
2362  * - **Array indexing**: `arr.get(n)` returns element at position n
2363  * - **Struct fields**: `struct.get('field')` returns field value
2364  * - **Path notation**: `struct.get('nested.field[0]')` for deep access
2365  *
2366  * For arrays and struct fields, `get()` behaves identically to `index()`.
2367  * Use `get()` as the primary method for reading values due to its
2368  * descriptive name.
2369  *
2370  * @function module:ffi.CData#get
2371  *
2372  * @param {string|number} [key]
2373  * The field name, array index, or path to read. Omit for scalar types
2374  * to get the value directly.
2375  *
2376  * @returns {*}
2377  * The value at the specified location, converted to a ucode type.
2378  * For structs without a key, returns an object with all field values.
2379  *
2380  * @throws {Error}
2381  * Throws an exception if the key is invalid for the type.
2382  *
2383  * @example
2384  * // Read scalar value (no key needed)
2385  * let x = ffi.ctype('int', 42);
2386  * x.get();    // => 42 (number)
2387  *
2388  * @example
2389  * // Read struct field
2390  * ffi.cdef('struct point { int x; int y; };');
2391  * let p = ffi.ctype('struct point', 10, 20);
2392  * p.get('x');    // => 10 (number)
2393  * p.get('y');    // => 20 (number)
2394  *
2395  * @example
2396  * // Read entire struct as object
2397  * p.get();    // => {x: 10, y: 20} (ucode object)
2398  *
2399  * @example
2400  * // Read array element
2401  * let arr = ffi.ctype('int[5]', [1, 2, 3, 4, 5]);
2402  * arr.get(0);    // => 1 (number)
2403  * arr.get(4);    // => 5 (number)
2404  *
2405  * @example
2406  * // Path notation for nested access
2407  * ffi.cdef('struct rect { struct point min; struct point max; };');
2408  * let r = ffi.ctype('struct rect', {
2409  *     min: {x: 0, y: 0},
2410  *     max: {x: 100, y: 100}
2411  * });
2412  * r.get('min.x');        // => 0
2413  * r.get('max.y');        // => 100
2414  *
2415  * @see {@link module:ffi.CData#index|index()} - Equivalent for array/field access
2416  * @see {@link module:ffi.CData#set|set()} - Write values to cdata
2417  */
2418 static uc_value_t *
2419 uc_ctype_get(uc_vm_t *vm, size_t nargs)
2420 {
2421         GCcdata *cd = uc_fn_thisval("ffi.ctype");
2422         CTState *cts = ctype_cts(vm);
2423         CTInfo qual = 0;
2424         uint8_t *p;
2425         CType *ct;
2426         CTSize sz;
2427 
2428         if (!cd)
2429                 return NULL;
2430 
2431         ct = ctype_get(cts, cd->ctypeid);
2432         sz = cdataisv(cd) ? cdatavlen(cd) : ct->size;
2433         p = cdataptr(cd);
2434 
2435         /* Handle reference types: dereference to get actual data pointer */
2436         if (ctype_isref(ct->info)) {
2437                 p = *(uint8_t **)p;
2438                 ct = ctype_get(cts, ctype_cid(ct->info));
2439                 if (!ct)
2440                         return NULL;
2441                 sz = ct->size;
2442                 if (sz == CTSIZE_INVALID)
2443                         return NULL;
2444         }
2445 
2446         if (nargs == 0 && ctype_isstruct(ct->info)) {
2447                 return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz,
2448                                 cd->refs);
2449         }
2450 
2451         if (nargs) {
2452                 uc_value_t *key = uc_fn_arg(0);
2453 
2454                 /* Check for path syntax (contains '.' or '[') */
2455                 bool is_path = false;
2456                 if (ucv_type(key) == UC_STRING) {
2457                         const char *s = ucv_string_get(key);
2458                         if (strpbrk(s, ".["))
2459                                 is_path = true;
2460                 }
2461 
2462                 if (is_path) {
2463                         /* Use path parsing for nested access */
2464                         path_tokens tokens = {0};
2465                         bool error = false;
2466 
2467                         if (!path_tokenize(vm, key, &tokens))
2468                                 return NULL;
2469 
2470                         ct = path_navigate(cts, cd, &tokens, &p, &ct, &error);
2471                         path_tokens_free(&tokens);
2472 
2473                         if (error || !ct) {
2474                                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
2475                                 char *keystr = ucv_to_string(vm, key);
2476 
2477                                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2478                                                       "Invalid path '%s' for type '%s'",
2479                                                       keystr, ucv_string_get(repr));
2480 
2481                                 ucv_put(repr);
2482                                 free(keystr);
2483 
2484                                 return NULL;
2485                         }
2486 
2487                         /* path_navigate already returns the final type */
2488                         sz = ct->size;
2489                 }
2490                 else {
2491                         /* Use original uc_cdata_index for single-level access */
2492                         ct = uc_cdata_index(cts, cd, key, &p, &qual);
2493 
2494                         if (!ct)
2495                                 return NULL;
2496 
2497                         if (qual & 1) {
2498                                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
2499                                 char *keystr = ucv_to_string(vm, key);
2500 
2501                                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2502                                                       "Invalid index '%s' for type '%s' given",
2503                                                       keystr, ucv_string_get(repr));
2504 
2505                                 ucv_put(repr);
2506                                 free(keystr);
2507 
2508                                 return NULL;
2509                         }
2510 
2511                         ct = ctype_child(cts, ct);
2512                         sz = ct->size;
2513                 }
2514         }
2515 
2516         return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz,
2517                         cd->refs);
2518 }
2519 
2520 /**
2521  * Write a value to a C data object.
2522  *
2523  * The `set()` method writes a value to a cdata. For structs, it can write
2524  * individual fields by name. For arrays, it can write elements by index.
2525  *
2526  * @function module:ffi.CData#set
2527  *
2528  * @param {string|number} key
2529  * The field name or array index to write.
2530  *
2531  * @param {*} value
2532  * The value to write. Will be converted to the appropriate C type.
2533  *
2534  * @returns {undefined}
2535  * Returns `undefined`.
2536  *
2537  * @throws {Error}
2538  * Throws an exception if the key is invalid or the value cannot be converted.
2539  *
2540  * @example
2541  * // Write scalar value
2542  * let x = ffi.ctype('int');
2543  * x.set(42);
2544  *
2545  * @example
2546  * // Write struct field
2547  * ffi.cdef('struct point { int x; int y; };');
2548  * let p = ffi.ctype('struct point');
2549  * p.set('x', 10);
2550  * p.set('y', 20);
2551  *
2552  * @example
2553  * // Write array element
2554  * let arr = ffi.ctype('int[5]');
2555  * arr.set(0, 100);
2556  * arr.set(4, 200);
2557  */
2558 static uc_value_t *
2559 uc_ctype_set(uc_vm_t *vm, size_t nargs)
2560 {
2561         GCcdata *cd = uc_fn_thisval("ffi.ctype");
2562         CTState *cts = ctype_cts(vm);
2563         CTInfo qual = 0;
2564         uint8_t *p;
2565         CType *ct;
2566 
2567         if (!cd)
2568                 return NULL;
2569 
2570         ct = ctype_get(cts, cd->ctypeid);
2571         p = cdataptr(cd);
2572 
2573         /* Handle reference types: dereference to get actual data pointer */
2574         if (ctype_isref(ct->info)) {
2575                 p = *(uint8_t **)p;
2576                 ct = ctype_get(cts, ctype_cid(ct->info));
2577                 if (!ct)
2578                         return NULL;
2579         }
2580 
2581         if (nargs > 1) {
2582                 uc_value_t *key = uc_fn_arg(0);
2583                 uc_value_t *val = uc_fn_arg(1);
2584 
2585                 /* Check for path syntax (contains '.' or '[') */
2586                 bool is_path = false;
2587                 if (ucv_type(key) == UC_STRING) {
2588                         const char *s = ucv_string_get(key);
2589                         if (strpbrk(s, ".["))
2590                                 is_path = true;
2591                 }
2592 
2593                 if (is_path) {
2594                         /* Use path parsing for nested access */
2595                         path_tokens tokens = {0};
2596                         bool error = false;
2597 
2598                         if (!path_tokenize(vm, key, &tokens))
2599                                 return NULL;
2600 
2601                         ct = path_navigate(cts, cd, &tokens, &p, &ct, &error);
2602                         path_tokens_free(&tokens);
2603 
2604                         if (error || !ct) {
2605                                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
2606                                 char *keystr = ucv_to_string(vm, key);
2607 
2608                                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2609                                                       "Invalid path '%s' for type '%s'",
2610                                                       keystr, ucv_string_get(repr));
2611 
2612                                 ucv_put(repr);
2613                                 free(keystr);
2614 
2615                                 return NULL;
2616                         }
2617 
2618                         /* path_navigate already returns the final type, no need to call ctype_child */
2619                 }
2620                 else {
2621                         /* Use original uc_cdata_index for single-level access */
2622                         ct = uc_cdata_index(cts, cd, key, &p, &qual);
2623 
2624                         if (!ct)
2625                                 return NULL;
2626 
2627                         if (qual & 1) {
2628                                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
2629                                 char *keystr = ucv_to_string(vm, key);
2630 
2631                                 uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2632                                                       "Invalid index '%s' for type '%s' given",
2633                                                       keystr, ucv_string_get(repr));
2634 
2635                                 ucv_put(repr);
2636                                 free(keystr);
2637 
2638                                 return NULL;
2639                         }
2640 
2641                         ct = ctype_child(cts, ct);
2642                 }
2643 
2644                 /* Convert and store the value */
2645                 uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL);
2646         }
2647         else if (nargs == 1) {
2648                 /* No key: set the value directly (for reference types or scalar cdata) */
2649                 uc_value_t *val = uc_fn_arg(0);
2650                 uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL);
2651         }
2652 
2653         return NULL;
2654 }
2655 
2656 /**
2657  * Get a pointer to a C data object.
2658  *
2659  * The `ptr()` method returns a pointer cdata pointing to the memory of the
2660  * current cdata. This is useful for passing to C functions that expect
2661  * pointers.
2662  *
2663  * @function module:ffi.CData#ptr
2664  *
2665  * @returns {module:ffi.CData}
2666  * A pointer cdata pointing to this cdata's memory.
2667  *
2668  * @example
2669  * // Get pointer to scalar
2670  * let x = ffi.ctype('int', 42);
2671  * let px = x.ptr();  // int* pointer
2672  *
2673  * @example
2674  * // Pass to C function expecting pointer
2675  * ffi.cdef('void memset(void *, int, size_t)');
2676  * let buf = ffi.ctype('char[10]');
2677  * ffi.C.wrap('void memset(void *, int, size_t)')(buf.ptr(), 0, 10);
2678  */
2679 static uc_value_t *
2680 uc_ctype_ptr(uc_vm_t *vm, size_t nargs)
2681 {
2682         GCcdata *cd = uc_fn_thisval("ffi.ctype");
2683 
2684         if (!cd)
2685                 return NULL;
2686 
2687         uc_value_t *pres = uc_cdata_new(vm, CTID_P_VOID, CTSIZE_PTR);
2688         *(void **)uc_cdata_dataptr(pres) = cdataptr(cd);
2689 
2690         return pres;
2691 }
2692 
2693 /**
2694  * Access array elements, struct fields, or perform pointer arithmetic.
2695  *
2696  * The `index()` method returns **raw cdata references** to the accessed
2697  * location, without converting to ucode types. This allows further
2698  * manipulation, pointer arithmetic, or explicit conversion.
2699  *
2700  * Supports:
2701  *
2702  * - **Array indexing**: `arr.index(n)` returns cdata reference to element
2703  * - **Struct fields**: `struct.index('field')` returns cdata reference
2704  * - **Pointer arithmetic**: `ptr.index(n)` returns cdata at *(ptr + n)
2705  * - **Path notation**: `struct.index('nested.field[0]')` for deep access
2706  *
2707  * **Key difference from `get()`**: `index()` returns raw cdata (unconverted),
2708  * while `get()` returns converted ucode values.
2709  *
2710  * @function module:ffi.CData#index
2711  *
2712  * @param {string|number} key
2713  * The array index, field name, or path to access.
2714  *
2715  * @returns {module:ffi.CData}
2716  * A cdata reference to the value at the specified location (unconverted).
2717  * Call `.get()` on the result to convert to a ucode value.
2718  *
2719  * @throws {Error}
2720  * Throws an exception if the key is invalid for the type.
2721  *
2722  * @example
2723  * // Array indexing - returns cdata, not number
2724  * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]);
2725  * arr.index(0);      // => cdata (int)
2726  * arr.index(0).get() // => 10 (number)
2727  *
2728  * @example
2729  * // Struct field access - returns cdata reference
2730  * ffi.cdef('struct point { int x; int y; };');
2731  * let p = ffi.ctype('struct point', 10, 20);
2732  * p.index('x');      // => cdata (int)
2733  * p.index('x').get() // => 10 (number)
2734  *
2735  * @example
2736  * // Pointer arithmetic - returns cdata at offset
2737  * let ptr = ffi.ctype('int *', arr.ptr());
2738  * ptr.index(0);      // => cdata (int) at ptr[0]
2739  * ptr.index(2);      // => cdata (int) at ptr[2]
2740  * ptr.index(2).get() // => 30 (number)
2741  *
2742  * @example
2743  * // Chaining - modify through index()
2744  * arr.index(0).set(100);  // Set arr[0] = 100
2745  *
2746  * @see {@link module:ffi.CData#get|get()} - Returns converted ucode values
2747  * @see {@link module:ffi.CData#ptr|ptr()} - Get a pointer, not a value
2748  */
2749 static uc_value_t *
2750 uc_ctype_index(uc_vm_t *vm, size_t nargs)
2751 {
2752         CTState *cts = ctype_cts(vm);
2753         CTInfo qual = 0;
2754         uint8_t *p;
2755         GCcdata *cd = uc_fn_thisval("ffi.ctype");
2756         uc_value_t *key = uc_fn_arg(0);
2757 
2758         if (!cd)
2759                 return NULL;
2760 
2761         /* Check for path syntax (contains '.' or '[') */
2762         bool is_path = false;
2763         if (ucv_type(key) == UC_STRING) {
2764                 const char *s = ucv_string_get(key);
2765                 if (strpbrk(s, ".["))
2766                         is_path = true;
2767         }
2768 
2769         CType *ct = ctype_get(cts, cd->ctypeid);
2770 
2771         /* Handle reference types: dereference to get actual type */
2772         if (ctype_isref(ct->info)) {
2773                 p = *(uint8_t **)cdataptr(cd);
2774                 ct = ctype_get(cts, ctype_cid(ct->info));
2775                 if (!ct)
2776                         return NULL;
2777         }
2778         else {
2779                 p = cdataptr(cd);
2780         }
2781 
2782         if (is_path) {
2783                 /* Use path parsing for nested access */
2784                 path_tokens tokens = {0};
2785                 bool error = false;
2786 
2787                 if (!path_tokenize(vm, key, &tokens)) {
2788                         path_tokens_free(&tokens);
2789                         return NULL;
2790                 }
2791 
2792                 ct = path_navigate(cts, cd, &tokens, &p, &ct, &error);
2793                 path_tokens_free(&tokens);
2794 
2795                 if (error || !ct)
2796                         return NULL;
2797         }
2798         else {
2799                 /* Handle integer key for pointer/array indexing */
2800                 uc_type_t ut = ucv_type(key);
2801                 bool is_integer_key = (ut == UC_INTEGER || ut == UC_DOUBLE);
2802 
2803                 if (is_integer_key && (ctype_ispointer(ct->info) || ctype_isarray(ct->info))) {
2804                         /* Pointer/array indexing: ptr[index] or arr[index] */
2805                         ptrdiff_t idx;
2806                         if (ut == UC_INTEGER)
2807                                 idx = (ptrdiff_t)ucv_int64_get(key);
2808                         else
2809                                 idx = (ptrdiff_t)ucv_double_get(key);
2810 
2811                         CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info));
2812                         if (sz == CTSIZE_INVALID) {
2813                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
2814                                         "size of C type is unknown or too large");
2815                                 return NULL;
2816                         }
2817 
2818                         /* Get the pointer value (for ptr types) or use data directly (for arrays) */
2819                         if (ctype_isptr(ct->info))
2820                                 p = (uint8_t *)cdata_getptr(p, ct->size);
2821 
2822                         /* Get element type */
2823                         CType *elt = ctype_rawchild(cts, ct);
2824 
2825                         /* Calculate offset */
2826                         p = p + idx * (int32_t)sz;
2827 
2828                         /* Return raw cdata reference (unconverted) */
2829                         CTypeID elt_id = ctype_typeid(cts, elt);
2830                         CType *elt_ct = ctype_get(cts, elt_id);
2831 
2832                         /* For pointer element types, we need to store the pointer VALUE, not the address */
2833                         if (ctype_isptr(elt_ct->info)) {
2834                                 /* Read the pointer value from p and create a cdata containing it */
2835                                 void *ptrval = cdata_getptr(p, elt_ct->size);
2836                                 uc_value_t *res = uc_cdata_new(vm, elt_id, elt_ct->size);
2837                                 *(void **)uc_cdata_dataptr(res) = ptrval;
2838                                 return res;
2839                         }
2840 
2841                         return uc_cdata_newref(vm, p, elt_id);
2842                 }
2843                 else {
2844                         /* Use original uc_cdata_index for single-level access */
2845                         ct = uc_cdata_index(cts, cd, key, &p, &qual);
2846 
2847                         if (!ct)
2848                                 return NULL;
2849 
2850                         if (qual & 1)
2851                                 return NULL;
2852 
2853                         /* Get the raw child type to avoid qualifier issues,
2854                            but preserve pointer types */
2855                         if (!ctype_ispointer(ct->info))
2856                                 ct = ctype_rawchild(cts, ct);
2857 
2858                         /* For pointer fields, create a cdata containing the pointer value */
2859                         if (ctype_ispointer(ct->info)) {
2860                                 void *ptrval = cdata_getptr(p, ct->size);
2861                                 uc_value_t *res = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR);
2862                                 *(void **)uc_cdata_dataptr(res) = ptrval;
2863                                 return res;
2864                         }
2865                 }
2866         }
2867 
2868         /* Return raw cdata reference (unconverted) */
2869         return uc_cdata_newref(vm, p, ctype_typeid(cts, ct));
2870 }
2871 
2872 /**
2873  * Read the value pointed to by a pointer cdata.
2874  *
2875  * The `deref()` method dereferences a pointer cdata and reads the value
2876  * at the pointed-to address. The target type can be specified explicitly
2877  * or inferred from the pointer type.
2878  *
2879  * @function module:ffi.CData#deref
2880  *
2881  * @param {string} [type]
2882  * The C type to read. If omitted, the pointer's element type is used.
2883  *
2884  * @returns {*}
2885  * The value at the pointer address, converted to a ucode type.
2886  *
2887  * @throws {Error}
2888  * Throws an exception if the pointer is NULL or the type is invalid.
2889  *
2890  * @example
2891  * // Dereference int pointer
2892  * let x = ffi.ctype('int', 42);
2893  * let px = x.ptr();
2894  * print(px.deref('int'));  // => 42
2895  *
2896  * @example
2897  * // Read first byte of char*
2898  * ffi.cdef('char *strdup(const char *)');
2899  * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello");
2900  * print(ptr.deref('char'));  // => 104 (ASCII for 'h')
2901  * ptr.deref();  // Also works, uses pointer's element type
2902  */
2903 static uc_value_t *
2904 uc_ctype_deref(uc_vm_t *vm, size_t nargs)
2905 {
2906         GCcdata *cd = uc_fn_thisval("ffi.ctype");
2907 
2908         if (!cd)
2909                 return NULL;
2910 
2911         CTState *cts = ctype_cts(vm);
2912         CType *ct = ctype_get(cts, cd->ctypeid);
2913 
2914         CTypeID ctid;
2915         uint8_t *p = NULL;
2916 
2917         /* Skip extern and attribute wrappers to get the actual type */
2918         while (ctype_isextern(ct->info) || ctype_isattrib(ct->info))
2919                 ct = ctype_child(cts, ct);
2920 
2921         if (ctype_isptr(ct->info)) {
2922                 ctid = nargs
2923                         ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT,
2924                                 uc_fn_arg(0), NULL)
2925                         : ctype_cid(ct->info);
2926 
2927                 if (!ctid)
2928                         return NULL;
2929 
2930                 p = *(uint8_t **)cdataptr(cd);
2931 
2932                 if (!p) {
2933                         uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2934                                 "Attempt to dereference a NULL pointer");
2935 
2936                         return NULL;
2937                 }
2938         }
2939         else if (ctype_isref(ct->info)) {
2940                 /* Reference: dereference to get actual data pointer */
2941                 ctid = nargs
2942                         ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT,
2943                                 uc_fn_arg(0), NULL)
2944                         : ctype_cid(ct->info);
2945 
2946                 if (!ctid)
2947                         return NULL;
2948 
2949                 p = *(uint8_t **)cdataptr(cd);
2950 
2951                 if (!p) {
2952                         uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2953                                 "Attempt to dereference a NULL reference");
2954 
2955                         return NULL;
2956                 }
2957         }
2958         else if (ctype_isrefarray(ct->info)) {
2959                 /* Array: dereference returns first element */
2960                 ctid = nargs
2961                         ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT,
2962                                 uc_fn_arg(0), NULL)
2963                         : ctype_cid(ct->info);
2964 
2965                 if (!ctid)
2966                         return NULL;
2967 
2968                 p = (uint8_t *)cdataptr(cd);
2969 
2970                 if (!p) {
2971                         uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
2972                                 "Attempt to dereference empty array");
2973 
2974                         return NULL;
2975                 }
2976         }
2977         else {
2978                 uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
2979 
2980                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
2981                         "Attempt to dereference non-pointer type %s",
2982                         ucv_string_get(repr));
2983 
2984                 ucv_put(repr);
2985 
2986                 return NULL;
2987         }
2988 
2989         CType *ctt = ctype_raw(cts, ctid);
2990 
2991         if (ctt->size == CTSIZE_INVALID) {
2992                 uc_value_t *repr = uc_ctype_repr(vm, ctid, NULL);
2993 
2994                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
2995                         "C type '%s' has unknown storage size",
2996                         ucv_string_get(repr));
2997 
2998                 ucv_put(repr);
2999 
3000                 return NULL;
3001         }
3002 
3003         uc_value_t *rv = NULL;
3004 
3005         uc_cconv_tv_ct(cts, ctt, ctid, &rv, p);
3006 
3007         return rv;
3008 }
3009 
3010 static CTSize
3011 uc_ctype_sizeof_common(CTState *cts, uc_value_t *uv, uc_value_t *nelem)
3012 {
3013         GCcdata *cd = NULL;
3014         CTypeID id;
3015         CTSize sz;
3016         CType *ct;
3017 
3018         id = uv_to_ct(cts->vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT,
3019                 uv, &cd);
3020 
3021         if (!id)
3022                 return CTSIZE_INVALID;
3023 
3024         if (UC_UNLIKELY(cd && cdataisv(cd))) {
3025                 ct = uc_ctype_rawref(cts, id);
3026                 if (ctype_isarray(ct->info)) {
3027                         CType *child = ctype_rawchild(cts, ct);
3028                         return cdatavlen(cd) * child->size;
3029                 }
3030                 return cdatavlen(cd) * ct->size;
3031         }
3032 
3033         ct = uc_ctype_rawref(cts, id);
3034 
3035         if (ctype_isvltype(ct->info)) {
3036                 // FIXME: transaprently handle cdata (ffi_checkint())
3037                 if (ucv_type(nelem) != UC_INTEGER) {
3038                         uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE,
3039                                 "integer argument expected, got %s",
3040                                 ucv_typename(nelem));
3041 
3042                         return CTSIZE_INVALID;
3043                 }
3044 
3045                 sz = uc_ctype_vlsize(cts, ct, (CTSize)ucv_int64_get(nelem));
3046         }
3047         else {
3048                 sz = ctype_hassize(ct->info) ? ct->size : CTSIZE_INVALID;
3049         }
3050 
3051         return sz;
3052 }
3053 
3054 /**
3055  * Get the size of a C data object in bytes.
3056  *
3057  * The `size()` method returns the total size in bytes of the cdata. For
3058  * arrays, this is the total size including all elements.
3059  *
3060  * @function module:ffi.CData#size
3061  *
3062  * @returns {number}
3063  * The size of the cdata in bytes.
3064  *
3065  * @example
3066  * // Get size of struct
3067  * ffi.cdef('struct point { int x; int y; };');
3068  * let p = ffi.ctype('struct point');
3069  * print(p.size());  // => 8 (on typical systems)
3070  *
3071  * @example
3072  * // Get size of array
3073  * let arr = ffi.ctype('int[10]');
3074  * print(arr.size());  // => 40 (10 * sizeof(int))
3075  */
3076 static uc_value_t *
3077 uc_ctype_sizeof(uc_vm_t *vm, size_t nargs)
3078 {
3079         uc_value_t *this = _uc_fn_this_res(vm);
3080         CTSize sz = uc_ctype_sizeof_common(ctype_cts(vm), this, uc_fn_arg(0));
3081 
3082         return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL;
3083 }
3084 
3085 /**
3086  * Get the number of elements in an array cdata.
3087  *
3088  * The `length()` method returns the number of elements in an array.
3089  * For non-array types, returns `null`.
3090  *
3091  * @function module:ffi.CData#length
3092  *
3093  * @returns {?number}
3094  * The number of elements in the array, or `null` if not an array.
3095  *
3096  * @example
3097  * // Get array length
3098  * let arr = ffi.ctype('int[10]');
3099  * print(arr.length());  // => 10
3100  *
3101  * @example
3102  * // Works with initialized arrays
3103  * let arr2 = ffi.ctype('char[5]', "hello");
3104  * print(arr2.length());  // => 5
3105  */
3106 static uc_value_t *
3107 uc_ctype_length(uc_vm_t *vm, size_t nargs)
3108 {
3109         uc_value_t *this = _uc_fn_this_res(vm);
3110         CTState *cts = ctype_cts(vm);
3111         CTSize sz = uc_ctype_sizeof_common(cts, this, uc_fn_arg(0));
3112 
3113         if (sz == CTSIZE_INVALID)
3114                 return NULL;
3115 
3116         GCcdata *cd = ucv_resource_data(this, "ffi.ctype");
3117         CType *ct = ctype_raw(cts, cd->ctypeid);
3118 
3119         if (!ctype_isarray(ct->info))
3120                 return NULL;
3121 
3122         CTSize item_sz = ctype_rawchild(cts, ct)->size;
3123 
3124         return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(sz / item_sz) : NULL;
3125 }
3126 
3127 /**
3128  * Get the size of an array element or struct field in bytes.
3129  *
3130  * The `itemsize()` method returns the size in bytes of each element in an
3131  * array, or the size of a specified struct field.
3132  *
3133  * @function module:ffi.CData#itemsize
3134  *
3135  * @param {string} [fieldname]
3136  * For struct types, the field name to get the size of.
3137  *
3138  * @returns {number}
3139  * The size of each array element or the struct field in bytes.
3140  *
3141  * @example
3142  * // Get array element size
3143  * let arr = ffi.ctype('int[10]');
3144  * print(arr.itemsize());  // => 4 (sizeof(int))
3145  *
3146  * @example
3147  * // Get struct field size
3148  * ffi.cdef('struct foo { char a; int b; double c; };');
3149  * let f = ffi.ctype('struct foo');
3150  * print(f.itemsize('b'));  // => 4 (size of int field)
3151  */
3152 static uc_value_t *
3153 uc_ctype_itemsize(uc_vm_t *vm, size_t nargs)
3154 {
3155         uc_value_t *this = _uc_fn_this_res(vm);
3156         GCcdata *cd = NULL;
3157         CTypeID id = uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT,
3158                 this, &cd);
3159 
3160         if (!id)
3161                 return NULL;
3162 
3163         CTState *cts = ctype_cts(vm);
3164         CTInfo info = ctype_raw(cts, id)->info;
3165         CTSize item_sz = CTSIZE_INVALID;
3166 
3167         if (ctype_isstruct(info)) {
3168                 uc_value_t *key = uc_fn_arg(0);
3169 
3170                 if (ucv_type(key) != UC_STRING) {
3171                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
3172                                 "Expecting field name for struct type, got %s",
3173                                 nargs ? ucv_typename(key) : "no argument");
3174 
3175                         return NULL;
3176                 }
3177 
3178                 CTSize ofs;
3179                 CType *fct;
3180 
3181                 fct = uc_ctype_getfieldq(cts, ctype_raw(cts, id), key, &ofs, NULL);
3182 
3183                 if (fct)
3184                         item_sz = ctype_rawchild(cts, fct)->size;
3185         }
3186         else if (ctype_isarray(info)) {
3187                 item_sz = ctype_rawchild(cts, ctype_get(cts, id))->size;
3188         }
3189 
3190         return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(item_sz) : NULL;
3191 }
3192 
3193 /**
3194  * Extract a substring from a char* or char[] cdata.
3195  *
3196  * The `slice()` method extracts a substring from a character pointer or
3197  * array. For char* pointers, it reads until the null terminator by default.
3198  * For char[] arrays, it uses the array length.
3199  *
3200  * @function module:ffi.CData#slice
3201  *
3202  * @param {number} [start=0]
3203  * The starting index (0-based). Negative values count from the end.
3204  *
3205  * @param {number} [end]
3206  * The ending index (exclusive). If omitted, uses the end of the string/array.
3207  *
3208  * @returns {string}
3209  * The extracted substring.
3210  *
3211  * @throws {Error}
3212  * Throws an exception if called without arguments on non-char* pointer types.
3213  *
3214  * @example
3215  * // Extract from char* pointer
3216  * ffi.cdef('char *strdup(const char *)');
3217  * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello world");
3218  * print(ptr.slice());      // => "hello world"
3219  * print(ptr.slice(6));     // => "world"
3220  * print(ptr.slice(0, 5));  // => "hello"
3221  *
3222  * @example
3223  * // Extract from char[] array
3224  * let buf = ffi.ctype('char[10]', "hello");
3225  * print(buf.slice());      // => "hello"
3226  * print(buf.slice(0, 3));  // => "hel"
3227  */
3228 static uc_value_t *
3229 uc_ctype_slice(uc_vm_t *vm, size_t nargs)
3230 {
3231         GCcdata *cd = uc_fn_thisval("ffi.ctype");
3232 
3233         if (!cd)
3234                 return NULL;
3235 
3236         CTState *cts = ctype_cts(vm);
3237         CType *ct = ctype_get(cts, cd->ctypeid);
3238         CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size;
3239         uint8_t *p = cdataptr(cd);
3240 
3241         /* Check if this is a char* pointer type */
3242         bool is_charptr = false;
3243         uint8_t *charptr_data = p;
3244         size_t charptr_len = sz;
3245 
3246         if (ctype_isptr(ct->info)) {
3247                 CType *child = ctype_rawchild(cts, ct);
3248                 /* Unwrap REF pointers to get the actual pointed-to type */
3249                 if (ctype_isptr(child->info)) {
3250                         /* This is a pointer to pointer - check if inner points to char */
3251                         CType *inner = ctype_rawchild(cts, child);
3252                         if (ctype_type(inner->info) == CT_NUM && inner->size == 1) {
3253                                 is_charptr = true;
3254                                 charptr_data = *(uint8_t **)p;
3255                                 if (charptr_data)
3256                                         charptr_len = strlen((char *)charptr_data);
3257                                 else
3258                                         charptr_len = 0;
3259                         }
3260                 }
3261                 else if (ctype_type(child->info) == CT_NUM && child->size == 1) {
3262                         is_charptr = true;
3263                         charptr_data = *(uint8_t **)p;
3264                         if (charptr_data)
3265                                 charptr_len = strlen((char *)charptr_data);
3266                         else
3267                                 charptr_len = 0;
3268                 }
3269         }
3270 
3271         /* No arguments: treat as string() for char* pointers */
3272         if (nargs == 0) {
3273                 if (is_charptr) {
3274                         /* char* pointer: read null-terminated string */
3275                         if (!charptr_data)
3276                                 return ucv_string_new("");
3277                         return ucv_string_new((char *)charptr_data);
3278                 }
3279                 /* For non-char* pointers, require explicit indices */
3280                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
3281                         "slice() without arguments only supported for char* pointers");
3282                 return NULL;
3283         }
3284 
3285         int64_t start_i = ucv_int64_get(uc_fn_arg(0));
3286         size_t start;
3287         size_t end;
3288 
3289         if (start_i < 0)
3290                 start = (is_charptr ? charptr_len : sz) + start_i;
3291         else
3292                 start = (size_t)start_i;
3293 
3294         if (nargs >= 2) {
3295                 int64_t end_i = ucv_int64_get(uc_fn_arg(1));
3296                 if (end_i < 0)
3297                         end = (is_charptr ? charptr_len : sz) + end_i;
3298                 else
3299                         end = (size_t)end_i;
3300         }
3301         else {
3302                 end = is_charptr ? charptr_len : sz;
3303         }
3304 
3305         /* Clamp to valid range */
3306         size_t max_len = is_charptr ? charptr_len : sz;
3307         if (start > max_len)
3308                 start = max_len;
3309         if (end > max_len)
3310                 end = max_len;
3311         if (start > end)
3312                 start = end;
3313 
3314         size_t len = end - start;
3315 
3316         if (len == 0)
3317                 return ucv_string_new_length("", 0);
3318 
3319         return ucv_string_new_length((char *)charptr_data + start, len);
3320 }
3321 
3322 static uc_value_t *
3323 uc_ctype_tostring(uc_vm_t *vm, size_t nargs)
3324 {
3325         GCcdata *cd = uc_fn_thisval("ffi.ctype");
3326 
3327         if (!cd)
3328                 return NULL;
3329 
3330         CTState *cts = ctype_cts(vm);
3331         CType *ct = ctype_get(cts, cd->ctypeid);
3332         uc_value_t *type_repr = uc_ctype_repr(vm, cd->ctypeid, NULL);
3333         uc_stringbuf_t *sb = ucv_stringbuf_new();
3334 
3335         ucv_stringbuf_addstr(sb, ucv_string_get(type_repr), ucv_string_length(type_repr));
3336         ucv_put(type_repr);
3337 
3338         /* Skip qualifiers and attributes to get the actual type */
3339         while (ctype_isattrib(ct->info) || ctype_isref(ct->info))
3340                 ct = ctype_child(cts, ct);
3341 
3342         /* Format value based on type */
3343         switch (ctype_type(ct->info)) {
3344         case CT_NUM:
3345         case CT_ENUM:
3346                 {
3347                         uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd),
3348                                                    cdataisv(cd) ? cdatavlen(cd) : ct->size,
3349                                                    cd->refs);
3350                         if (val) {
3351                                 char *str = ucv_to_string(vm, val);
3352                                 if (str) {
3353                                         ucv_stringbuf_addstr(sb, ": ", 2);
3354                                         ucv_stringbuf_addstr(sb, str, strlen(str));
3355                                         free(str);
3356                                 }
3357                                 ucv_put(val);
3358                         }
3359                 }
3360                 break;
3361 
3362         case CT_ARRAY:
3363                 {
3364                         CTSize clen = ct->size;
3365                         CType *ctt = ctype_rawchild(cts, ct);
3366 
3367                         /* Complex number: show as re+imI */
3368                         if (ct->info & CTF_COMPLEX)
3369                         {
3370                                 uc_value_t *val = uc_ctype_repr_complex(cdataptr(cd),
3371                                         cdataisv(cd) ? cdatavlen(cd) : ct->size);
3372                                 if (val) {
3373                                         ucv_stringbuf_addstr(sb, ": ", 2);
3374                                         ucv_stringbuf_addstr(sb, ucv_string_get(val), ucv_string_length(val));
3375                                         ucv_put(val);
3376                                 }
3377                         }
3378                         /* String array: show contents */
3379                         else if (ctt->size == 1 && (ctt->info & CTF_UNSIGNED) == 0)
3380                         {
3381                                 char *str = (char *)cdataptr(cd);
3382                                 ucv_stringbuf_addstr(sb, ": \"", 3);
3383                                 for (char *p = str; *p && (p - str) < 128; p++) {
3384                                         if (*p == '"')
3385                                                 ucv_stringbuf_addstr(sb, "\\\"", 2);
3386                                         else if (*p == '\\')
3387                                                 ucv_stringbuf_addstr(sb, "\\\\", 2);
3388                                         else if (*p == '\n')
3389                                                 ucv_stringbuf_addstr(sb, "\\n", 2);
3390                                         else if (*p == '\r')
3391                                                 ucv_stringbuf_addstr(sb, "\\r", 2);
3392                                         else if (*p == '\t')
3393                                                 ucv_stringbuf_addstr(sb, "\\t", 2);
3394                                         else if (*p >= 32 && *p < 127)
3395                                                 ucv_stringbuf_addstr(sb, p, 1);
3396                                         else
3397                                                 ucv_stringbuf_printf(sb, "\\x%02x", (unsigned char)*p);
3398                                 }
3399                                 ucv_stringbuf_addstr(sb, "\"", 1);
3400                         }
3401                         else if (clen != CTSIZE_INVALID && ctt->size > 0)
3402                         {
3403                                 ucv_stringbuf_printf(sb, " (len=%zu)", clen / ctt->size);
3404                         }
3405                 }
3406                 break;
3407 
3408         case CT_PTR:
3409                 {
3410                         void *ptr = *(void **)cdataptr(cd);
3411                         if (ptr)
3412                                 ucv_stringbuf_printf(sb, " @ %p", ptr);
3413                         else
3414                                 ucv_stringbuf_addstr(sb, ": NULL", 6);
3415                 }
3416                 break;
3417 
3418         case CT_STRUCT:
3419                 {
3420                         CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size;
3421                         uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), sz, cd->refs);
3422                         if (val) {
3423                                 char *str = ucv_to_string(vm, val);
3424                                 if (str) {
3425                                         ucv_stringbuf_addstr(sb, ": ", 2);
3426                                         ucv_stringbuf_addstr(sb, str, strlen(str));
3427                                         free(str);
3428                                 }
3429                                 ucv_put(val);
3430                         }
3431                 }
3432                 break;
3433 
3434         case CT_VOID:
3435                 ucv_stringbuf_addstr(sb, ": void", 6);
3436                 break;
3437         }
3438 
3439         return ucv_stringbuf_finish(sb);
3440 }
3441 
3442 
3443 /**
3444  * Represents a C data object holding a value of a C type.
3445  *
3446  * @class module:ffi.CData
3447  * @hideconstructor
3448  *
3449  * @see {@link module:ffi#ctype|ctype()}
3450  *
3451  * @example
3452  *
3453  * const val = ctype(…);
3454  *
3455  * val.get();
3456  * val.set(…);
3457  * val.ptr();
3458  * val.index(…);
3459  * val.deref(…);
3460  * val.size();
3461  * val.length();
3462  * val.itemsize(…);
3463  * val.slice(…);
3464  */
3465 
3466 /**
3467  * Create a C data instance.
3468  *
3469  * The `ctype()` function creates a new C data object (cdata) of the specified
3470  * type. It can be called with optional initializer values that will be used
3471  * to initialize the object.
3472  *
3473  * **Usage patterns:**
3474  *
3475  * 1. **Without initializer**: Creates an uninitialized cdata of the given type.
3476  *    For pointer types, the pointer is set to NULL.
3477  *
3478  * 2. **With initializer**: Creates and initializes a cdata. The initializer
3479  *    values depend on the type:
3480  *    - Scalar types: single value (number, boolean)
3481  *    - Structs: positional arguments for each field or a ucode object
3482  *    - Arrays: individual element values or a string for char arrays
3483  *
3484  *   ```javascript
3485  *   // Primitive type
3486  *   let x = ffi.ctype('int', 42);
3487  *   print(x.get());  // => 42
3488  *
3489  *   // Struct type with positional arguments
3490  *   ffi.cdef('struct point { int x; int y; };');
3491  *   let p1 = ffi.ctype('struct point', 10, 20);
3492  *   print(p1.get('x'), p1.get('y'));  // => 10 20
3493  *
3494  *   // Struct type with object initializer
3495  *   let p2 = ffi.ctype('struct point', { x: 30, y: 40 });
3496  *   print(p2.get('x'), p2.get('y'));  // => 30 40
3497  *
3498  *   // Nested struct with object initializer
3499  *   ffi.cdef('struct rect { struct point tl; struct point br; };');
3500  *   let r = ffi.ctype('struct rect', {
3501  *       tl: { x: 0, y: 0 },
3502  *       br: { x: 100, y: 200 }
3503  *   });
3504  *   let tl = r.get('tl');
3505  *   print(tl.get('x'), tl.get('y'));  // => 0 0
3506  *
3507  *   // Array type
3508  *   let arr = ffi.ctype('int[3]', 1, 2, 3);
3509  *   print(arr.get(0), arr.get(1), arr.get(2));  // => 1 2 3
3510  *
3511  *   // Char array from string
3512  *   let buf = ffi.ctype('char[10]', 'hello');
3513  *   print(buf.deref());  // => "hello"
3514  *
3515  *   // Pointer type (uninitialized)
3516  *   let ptr = ffi.ctype('void *');
3517  *   ```
3518  *
3519  * @function module:ffi#ctype
3520  *
3521  * @param {string} type
3522  * A C type declaration string. Can be a basic type, struct name, array type,
3523  * pointer type, etc. The type must have been declared via `cdef()` first.
3524  *
3525  * @param {...*} [init]
3526  * Optional initializer values.
3527  *
3528  * @returns {?module:ffi.CData}
3529  * A cdata of the specified type, or `null` if the type cannot be
3530  * parsed or has invalid size. For `typeof()` without initializer,
3531  * returns a CTypeID handle cdata.
3532  *
3533  * @throws {Error}
3534  * Throws an exception if the type declaration is invalid or wrong number
3535  * of initializers provided.
3536  *
3537  * @example
3538  * // Create integer
3539  * let x = ffi.ctype('int', 42);
3540  * print(x.get());
3541  *
3542  * @example
3543  * // Create struct
3544  * ffi.cdef('struct point { int x; int y; };');
3545  * let p = ffi.ctype('struct point', 10, 20);
3546  * print(p.get('x'));
3547  *
3548  * @example
3549  * // Create array
3550  * let arr = ffi.ctype('double[5]', 1.1, 2.2, 3.3, 4.4, 5.5);
3551  * print(arr.length());
3552  */
3553 static uc_value_t *
3554 uc_ffi_ctype(uc_vm_t *vm, size_t nargs)
3555 {
3556         uc_value_t *spec = uc_fn_arg(0);
3557         CTState *cts = ctype_cts(vm);
3558         uc_value_t *res;
3559 
3560         if (ucv_type(spec) != UC_STRING)
3561                 return NULL;
3562 
3563         CPState cp = {
3564                 .uv_vm = vm,
3565                 .cts = cts,
3566                 .srcname = ucv_string_get(spec),
3567                 .p = ucv_string_get(spec),
3568                 .uv_param = NULL,
3569                 .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT
3570         };
3571 
3572         if (!uc_cparse(&cp))
3573                 return NULL;
3574 
3575         /* initializer values provided... */
3576         if (nargs > 1) {
3577                 size_t init_arg_off = 1;
3578                 CTSize sz;
3579                 CType *ct = ctype_raw(cts, cp.val.id);
3580                 CTInfo info = uc_ctype_info(cts, cp.val.id, &sz);
3581                 uc_value_t *refs = NULL;
3582 
3583                 if (info & CTF_VLA)     {
3584                         CTSize vla_sz = ucv_uint64_get(uc_fn_arg(1));
3585 
3586                         if (errno) {
3587                                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
3588                                         "invalid size argument provided");
3589 
3590                                 return NULL;
3591                         }
3592 
3593                         init_arg_off++;
3594                         sz = uc_ctype_vlsize(cts, ct, vla_sz);
3595                 }
3596 
3597                 if (sz == CTSIZE_INVALID) {
3598                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
3599                                 "C type has invalid size");
3600 
3601                         return NULL;
3602                 }
3603 
3604                 res = uc_cdata_newx(vm, cp.val.id, sz, info);
3605 
3606                 /* Special handling: char array initialized with a string */
3607                 if (ctype_isarray(info) && ctype_isinteger(ctype_child(cts, ct)->info) &&
3608                     nargs - init_arg_off == 1 && ucv_type(vm->stack.entries[vm->stack.count - nargs + init_arg_off]) == UC_STRING) {
3609                         /* Convert string to array of char values */
3610                         uc_value_t *str = vm->stack.entries[vm->stack.count - nargs + init_arg_off];
3611                         const char *s = ucv_string_get(str);
3612                         size_t len = strlen(s);
3613                         CType *child = ctype_child(cts, ct);
3614                         CTSize elem_sz = child->size;
3615                         GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype");
3616                         uint8_t *data = (uint8_t *)cdataptr(cd_tmp);
3617 
3618                         /* Copy string including null terminator if array is large enough */
3619                         for (size_t i = 0; i < len && i * elem_sz < sz; i++) {
3620                                 if (elem_sz == 1) {
3621                                         data[i] = s[i];
3622                                 } else {
3623                                         /* For wider character types (e.g., char16_t) - truncate for now */
3624                                         data[i * elem_sz] = s[i];
3625                                 }
3626                         }
3627                         /* Null-terminate if there's space */
3628                         if (len < sz / elem_sz) {
3629                                 if (elem_sz == 1) {
3630                                         data[len] = '\0';
3631                                 } else {
3632                                         data[len * elem_sz] = '\0';
3633                                 }
3634                         }
3635                 } else {
3636                         if (nargs - init_arg_off > 0) {
3637                                 GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype");
3638                                 uint8_t *data = (uint8_t *)cdataptr(cd_tmp);
3639                                 uc_cconv_ct_init(cts, ct, sz, data,
3640                                         &vm->stack.entries[vm->stack.count - nargs + init_arg_off],
3641                                         nargs - init_arg_off, &refs);
3642                         }
3643                 }
3644 
3645                 GCcdata *cd = ucv_resource_data(res, "ffi.ctype");
3646                 cd->refs = refs;
3647         }
3648         else {
3649                 CTSize sz;
3650                 CTInfo info = uc_ctype_info(cts, cp.val.id, &sz);
3651 
3652                 if (sz == CTSIZE_INVALID) {
3653                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
3654                                 "C type has invalid size");
3655 
3656                         return NULL;
3657                 }
3658 
3659                 res = uc_cdata_newx(vm, cp.val.id, sz, info);
3660         }
3661 
3662         return res;
3663 }
3664 
3665 /**
3666  * Declare C types and functions.
3667  *
3668  * The `cdef()` function parses C declaration strings and registers the types
3669  * with the FFI system. This is required before using types with `ctype()`,
3670  * wrapping functions with `wrap()`, or resolving symbols with `dlsym()`.
3671  *
3672  * Multiple declarations can be provided in a single call, separated by
3673  * semicolons. The parser supports most C declaration syntax including:
3674  *
3675  * - Basic types (`int`, `char`, `float`, `double`, etc.)
3676  * - Type modifiers (`const`, `volatile`, `unsigned`, `signed`)
3677  * - Pointers and arrays (`int *`, `char **`, `int[10]`)
3678  * - Structs and unions (`struct foo { ... }`, `union bar { ... }`)
3679  * - Enums (`enum baz { ... }`)
3680  * - Function declarations (`int foo(int, char *)`)
3681  * - Typedefs (`typedef ...`)
3682  * - Extern declarations (`extern int var;`)
3683  *
3684  *   ```javascript
3685  *   // Declare a struct type
3686  *   ffi.cdef('struct point { int x; int y; };');
3687  *
3688  *   // Declare a function
3689  *   ffi.cdef('int strcmp(const char *, const char *);');
3690  *
3691  *   // Declare multiple items
3692  *   ffi.cdef(`
3693  *       typedef unsigned int uint32_t;
3694  *       struct sockaddr {
3695  *           sa_family_t sa_family;
3696  *           char sa_data[14];
3697  *       };
3698  *       extern char **environ;
3699  *   `);
3700  *   ```
3701  *
3702  * After declaring types, you can create instances with `ctype()`, wrap
3703  * functions with `wrap()`, or access global variables with `dlsym()`.
3704  *
3705  * @function module:ffi#cdef
3706  *
3707  * @param {string} spec
3708  * A C declaration string or multiple declarations separated by semicolons.
3709  *
3710  * @returns {module:ffi.CData}
3711  * A cdata holding the CTypeID handle for the last declared type.
3712  *
3713  * @throws {Error}
3714  * Throws an exception if the declaration syntax is invalid.
3715  *
3716  * @example
3717  * // Declare struct and create instance
3718  * ffi.cdef('struct point { int x; int y; };');
3719  * let p = ffi.ctype('struct point', 10, 20);
3720  * print(p.get('x'), p.get('y'));
3721  *
3722  * @example
3723  * // Declare function and wrap it
3724  * ffi.cdef('size_t strlen(const char *);');
3725  * let strlen = ffi.C.wrap('size_t strlen(const char *)');
3726  * print(strlen("hello").get());
3727  */
3728 static uc_value_t *
3729 uc_ffi_cdef(uc_vm_t *vm, size_t nargs)
3730 {
3731         uc_value_t *spec = uc_fn_arg(0);
3732         CTState *cts = ctype_cts(vm);
3733 
3734         if (ucv_type(spec) != UC_STRING)
3735                 return NULL;
3736 
3737         if (!vm->callframes.count)
3738                 return NULL;
3739 
3740         CPState cp = {
3741                 .uv_vm = vm,
3742                 .cts = cts,
3743                 .srcname = ucv_string_get(spec),
3744                 .p = ucv_string_get(spec),
3745                 .uv_param = &vm->stack.entries[uc_vector_last(&vm->callframes)->stackframe + 2],
3746                 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT
3747         };
3748 
3749         if (!uc_cparse(&cp))
3750                 return NULL;
3751 
3752         uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4);
3753         *(CTypeID *)uc_cdata_dataptr(res) = cp.val.id;
3754 
3755         return res;
3756 }
3757 
3758 /**
3759  * Get the CTypeID for a C type.
3760  *
3761  * The `typeof()` function returns a CTypeID handle for the specified type.
3762  * This is useful for storing type references or passing to other FFI functions.
3763  *
3764  * @function module:ffi#typeof
3765  *
3766  * @param {string} type
3767  * The C type declaration.
3768  *
3769  * @returns {module:ffi.CData}
3770  * A cdata holding the CTypeID handle (an integer type ID).
3771  *
3772  * @throws {Error}
3773  * Throws an exception if the type declaration is invalid.
3774  *
3775  * @example
3776  * // Get type ID for struct
3777  * ffi.cdef('struct point { int x; int y; };');
3778  * let point_type = ffi.typeof('struct point');
3779  *
3780  * @example
3781  * // Get type ID for function pointer
3782  * ffi.cdef('int callback(int, char *);');
3783  * let cb_type = ffi.typeof('int (*)(int, char *)');
3784  */
3785 static uc_value_t *
3786 uc_ffi_typeof(uc_vm_t *vm, size_t nargs)
3787 {
3788         CTState *cts = ctype_cts(vm);
3789         CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL);
3790 
3791         uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4);
3792 
3793         *(CTypeID *)uc_cdata_dataptr(res) = id;
3794 
3795         return res;
3796 }
3797 
3798 /**
3799  * Get the size of a C type in bytes.
3800  *
3801  * The `sizeof()` function returns the size in bytes of a C type or cdata
3802  * expression. For variable-length arrays, an element count can be provided.
3803  *
3804  * @function module:ffi#sizeof
3805  *
3806  * @param {string|module:ffi.CData} type
3807  * The C type declaration or cdata expression to measure.
3808  *
3809  * @param {number} [nelem]
3810  * For variable-length arrays, the number of elements.
3811  *
3812  * @returns {?number}
3813  * The size in bytes, or `null` if the size is unknown.
3814  *
3815  * @throws {Error}
3816  * Throws an exception if the type is invalid or nelem is required but missing.
3817  *
3818  * @example
3819  * // Get size of primitive types
3820  * print(ffi.sizeof('int'));     // => 4
3821  * print(ffi.sizeof('double'));  // => 8
3822  *
3823  * @example
3824  * // Get size of struct
3825  * ffi.cdef('struct point { int x; int y; };');
3826  * print(ffi.sizeof('struct point'));  // => 8
3827  *
3828  * @example
3829  * // Get size of VLA with element count
3830  * ffi.cdef('int vla[];');
3831  * print(ffi.sizeof('int[]', 10));  // => 40 (10 * sizeof(int))
3832  */
3833 static uc_value_t *
3834 uc_ffi_sizeof(uc_vm_t *vm, size_t nargs)
3835 {
3836         CTSize sz;
3837 
3838         sz = uc_ctype_sizeof_common(ctype_cts(vm), uc_fn_arg(0), uc_fn_arg(1));
3839 
3840         return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL;
3841 }
3842 
3843 /**
3844  * Get the alignment requirement of a C type in bytes.
3845  *
3846  * The `alignof()` function returns the minimum alignment requirement in bytes
3847  * for a C type. This is useful for understanding structure padding and memory
3848  * layout.
3849  *
3850  * @function module:ffi#alignof
3851  *
3852  * @param {string} type
3853  * The C type declaration.
3854  *
3855  * @returns {number}
3856  * The alignment requirement in bytes (typically a power of 2).
3857  *
3858  * @throws {Error}
3859  * Throws an exception if the type is invalid.
3860  *
3861  * @example
3862  * // Get alignment of primitive types
3863  * print(ffi.alignof('int'));     // => 4
3864  * print(ffi.alignof('double'));  // => 8
3865  *
3866  * @example
3867  * // Get alignment of struct
3868  * ffi.cdef('struct foo { char a; int b; };');
3869  * print(ffi.alignof('struct foo'));  // => 4 (alignment of int member)
3870  */
3871 static uc_value_t *
3872 uc_ffi_alignof(uc_vm_t *vm, size_t nargs)
3873 {
3874         CTState *cts = ctype_cts(vm);
3875         CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL);
3876 
3877         CTSize sz;
3878         CTInfo info = uc_ctype_info_raw(cts, id, &sz);
3879 
3880         return ucv_uint64_new(1 << ctype_align(info));
3881 }
3882 
3883 /**
3884  * Get the offset of a struct field in bytes.
3885  *
3886  * The `offsetof()` function returns the byte offset of a field within a struct.
3887  * For bitfields, the bit position and bit size are returned in an array passed
3888  * as the third argument.
3889  *
3890  * @function module:ffi#offsetof
3891  *
3892  * @param {string} type
3893  * The struct type declaration.
3894  *
3895  * @param {string} field
3896  * The field name to get the offset of.
3897  *
3898  * @param {array} [bitpos]
3899  * Optional array to receive [bit_position, bit_size] for bitfield members.
3900  *
3901  * @returns {?number}
3902  * The byte offset of the field, or `null` if the field doesn't exist.
3903  *
3904  * @throws {Error}
3905  * Throws an exception if the type is not a struct or the field is invalid.
3906  *
3907  * @example
3908  * // Get field offset
3909  * ffi.cdef('struct point { int x; int y; };');
3910  * print(ffi.offsetof('struct point', 'x'));  // => 0
3911  * print(ffi.offsetof('struct point', 'y'));  // => 4
3912  *
3913  * @example
3914  * // Get bitfield info
3915  * ffi.cdef('struct flags { unsigned int a:4; unsigned int b:4; };');
3916  * let bitpos = [];
3917  * let offset = ffi.offsetof('struct flags', 'b', bitpos);
3918  * print(offset, bitpos[0], bitpos[1]);  // => 0 4 4
3919  */
3920 static uc_value_t *
3921 uc_ffi_offsetof(uc_vm_t *vm, size_t nargs)
3922 {
3923         CTState *cts = ctype_cts(vm);
3924         CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL);
3925         uc_value_t *name = uc_fn_arg(1);
3926         uc_value_t *bitpos = uc_fn_arg(2);
3927         CType *ct = uc_ctype_rawref(cts, id);
3928         CTSize ofs;
3929 
3930         if (!ctype_isstruct(ct->info) || ct->size == CTSIZE_INVALID)
3931                 return NULL;
3932 
3933         if (ucv_type(name) != UC_STRING)
3934                 return NULL;
3935 
3936         CType *fct = uc_ctype_getfield(cts, ct, name, &ofs);
3937 
3938         if (ctype_isfield(fct->info))
3939                 return ucv_uint64_new(ofs);
3940 
3941         if (ctype_isbitfield(fct->info)) {
3942                 ucv_array_set(bitpos, 0, ucv_uint64_new(ctype_bitpos(fct->info)));
3943                 ucv_array_set(bitpos, 1, ucv_uint64_new(ctype_bitbsz(fct->info)));
3944 
3945                 return ucv_uint64_new(ofs);
3946         }
3947 
3948         return NULL;
3949 }
3950 
3951 /**
3952  * Get or set the C `errno` value.
3953  *
3954  * The `errno()` function retrieves the current value of the C `errno`
3955  * variable, or sets it to a new value if an argument is provided.
3956  *
3957  * @function module:ffi#errno
3958  *
3959  * @param {number} [value]
3960  * Optional value to set errno to.
3961  *
3962  * @returns {number}
3963  * The current errno value (before any set operation).
3964  *
3965  * @example
3966  * // Get current errno
3967  * let err = ffi.errno();
3968  *
3969  * @example
3970  * // Set errno
3971  * ffi.errno(0);  // Clear errno
3972  */
3973 static uc_value_t *
3974 uc_ffi_errno(uc_vm_t *vm, size_t nargs)
3975 {
3976         int err = errno;
3977 
3978         if (nargs)
3979                 errno = ucv_int64_get(uc_fn_arg(0));
3980 
3981         return ucv_int64_new(err);
3982 }
3983 
3984 /**
3985  * Preloaded C types and variables.
3986  *
3987  * The FFI module automatically preloads certain C types and global variables
3988  * that are commonly needed. These are available without explicit `cdef()` declarations.
3989  *
3990  * ### Preloaded Global Variables
3991  *
3992  * The following global variables are automatically available through `ffi.C`:
3993  *
3994  * | Variable | Type | Description |
3995  * |----------|------|-------------|
3996  * | `errno` | `int *` | Thread-local error code pointer |
3997  * | `environ` | `char ***` | Process environment variables |
3998  *
3999  * Access these via `ffi.C.dlsym()`:
4000  *
4001  * ```javascript
4002  * // Get errno pointer
4003  * let errno_ptr = ffi.C.dlsym('errno');
4004  * let err = errno_ptr.deref('int');
4005  *
4006  * // Get environment variables
4007  * let env = ffi.C.dlsym('environ');
4008  * for (let i = 0; i < 10; i++) {
4009  *     let var = env.get(i);
4010  *     if (!var) break;
4011  *     print(ffi.string(var), "\n");
4012  * }
4013  * ```
4014  *
4015  * ### Builtin Type Definitions
4016  *
4017  * The following types are pre-declared and available without `cdef()`:
4018  *
4019  * | Type | Description | Typical Size |
4020  * |------|-------------|--------------|
4021  * | `size_t` | Unsigned pointer-sized integer | 4 or 8 bytes |
4022  * | `ssize_t` | Signed pointer-sized integer | 4 or 8 bytes |
4023  * | `intptr_t` | Signed integer with same size as pointer | 4 or 8 bytes |
4024  * | `uintptr_t` | Unsigned integer with same size as pointer | 4 or 8 bytes |
4025  * | `ptrdiff_t` | Signed difference type (pointer subtraction) | 4 or 8 bytes |
4026  * | `wchar_t` | Wide character type | 2 or 4 bytes |
4027  * | `va_list` | Variable argument list (for vararg functions) | Implementation-dependent |
4028  *
4029  * ### Fixed-Width Integer Types
4030  *
4031  * The following types from `<stdint.h>` are pre-declared:
4032  *
4033  * | Type | Description | Size |
4034  * |------|-------------|------|
4035  * | `int8_t` | Signed 8-bit integer | 1 byte |
4036  * | `int16_t` | Signed 16-bit integer | 2 bytes |
4037  * | `int32_t` | Signed 32-bit integer | 4 bytes |
4038  * | `int64_t` | Signed 64-bit integer | 8 bytes |
4039  * | `uint8_t` | Unsigned 8-bit integer | 1 byte |
4040  * | `uint16_t` | Unsigned 16-bit integer | 2 bytes |
4041  * | `uint32_t` | Unsigned 32-bit integer | 4 bytes |
4042  * | `uint64_t` | Unsigned 64-bit integer | 8 bytes |
4043  *
4044  * These types can be used directly without prior declaration:
4045  *
4046  * ```javascript
4047  * // Use builtin types directly
4048  * let sz = ffi.sizeof('size_t');        // => 8 (on 64-bit systems)
4049  * let ptr = ffi.ctype('uintptr_t', 0);
4050  *
4051  * // Use fixed-width types
4052  * let i32 = ffi.ctype('int32_t', 42);
4053  * let u64 = ffi.ctype('uint64_t', 0xFFFFFFFFFFFFFFFF);
4054  *
4055  * // Create arrays of builtin types
4056  * let buf = ffi.ctype('uint8_t[256]');
4057  * let indices = ffi.ctype('size_t[10]');
4058  * ```
4059  *
4060  * Note: When wrapping functions that use these types, you still need to
4061  * declare the function prototype via `cdef()` or provide a full declaration
4062  * to `wrap()`:
4063  *
4064  * ```javascript
4065  * // Declare function using builtin types
4066  * ffi.cdef('size_t strlen(const char *);');
4067  * let strlen = ffi.C.wrap('strlen');
4068  *
4069  * // Or provide full declaration to wrap()
4070  * let strlen = ffi.C.wrap('size_t strlen(const char *)');
4071  * ```
4072  *
4073  * @section Preloaded Types
4074  */
4075 
4076 /**
4077  * Convert between ucode strings and C char arrays/pointers.
4078  *
4079  * The `string()` function has two modes:
4080  *
4081  * 1. **String to buffer**: Given a ucode string, creates a C char[] buffer
4082  *    containing the string plus null terminator. Returns a cdata that can be
4083  *    passed to C functions expecting `char*`.
4084  *
4085  * 2. **Pointer to string**: Given a char* cdata pointer, reads the C string
4086  *    and returns a ucode string. An optional length parameter can be provided
4087  *    to limit the maximum bytes read (reads up to `len` bytes or until null
4088  *    terminator, whichever comes first).
4089  *
4090  * @function module:ffi#string
4091  *
4092  * @param {string|module:ffi.CData} arg
4093  * A ucode string to convert to char[], or a char* cdata pointer to read.
4094  *
4095  * @param {number} [len]
4096  * Optional maximum length for reading C strings (reads up to `len` bytes
4097  * or until null terminator).
4098  *
4099  * @returns {string|module:ffi.CData}
4100  * When given a char* pointer: returns a ucode string.
4101  * When given a ucode string: returns a char[] cdata buffer.
4102  *
4103  * @throws {Error}
4104  * Throws an exception if the argument type is invalid.
4105  *
4106  * @example
4107  * // Convert ucode string to char[] buffer
4108  * let buf = ffi.string("hello");
4109  * // buf is now char[6] cdata (including null terminator)
4110  * // Can be passed to C functions expecting char*
4111  *
4112  * @example
4113  * // Read C string from char* pointer
4114  * ffi.cdef('char *getenv(char *);');
4115  * let ptr = ffi.C.wrap('char *getenv(char *)')("PATH");
4116  * let path = ffi.string(ptr);
4117  * print(path);  // => "/usr/bin:..."
4118  *
4119  * @example
4120  * // Read fixed-length string (no null terminator)
4121  * ffi.cdef('char *strncpy(char *, const char *, size_t);');
4122  * let src = ffi.string("hello world");
4123  * let dst = ffi.ctype('char[5]');
4124  * ffi.C.wrap('char *strncpy(char *, const char *, size_t)')(dst, src, 5);
4125  * let short_str = ffi.string(dst, 5);  // => "hello" (no null terminator)
4126  */
4127 static uc_value_t *
4128 uc_ffi_string(uc_vm_t *vm, size_t nargs)
4129 {
4130         uc_value_t *arg = uc_fn_arg(0);
4131         uc_value_t *len_arg = uc_fn_arg(1);
4132         CTState *cts = ctype_cts(vm);
4133 
4134         /* If argument is ucode string, create C char[] buffer */
4135         if (ucv_type(arg) == UC_STRING) {
4136                 size_t len = ucv_string_length(arg) + 1;
4137 
4138                 /* Create char[N] array type directly without parser invocation */
4139                 CTypeID elem_type = CTID_CCHAR;  /* char element type */
4140                 CTInfo array_info = CTINFO(CT_ARRAY, CTALIGN(0)) + elem_type;
4141                 CTSize array_size = len;  /* Total size in bytes */
4142 
4143                 /* Intern the array type */
4144                 CTypeID array_typeid = uc_ctype_intern(cts, array_info, array_size);
4145 
4146                 /* Create cdata instance */
4147                 uc_value_t *arr = uc_cdata_new(vm, array_typeid, array_size);
4148 
4149                 /* Copy string including null terminator */
4150                 const char *src = ucv_string_get(arg);
4151                 uint8_t *dst = (uint8_t *)cdataptr((GCcdata *)((uc_resource_t *)arr)->data);
4152                 memcpy(dst, src, len);
4153 
4154                 return arr;
4155         }
4156 
4157         /* Otherwise, argument is a C pointer - extract address and read as string */
4158         void *p = NULL;
4159         size_t sz;
4160 
4161         if (nargs > 1) {
4162                 /* With explicit max-length: accept any pointer type */
4163                 uc_cconv_ct_tv(cts, ctype_get(cts, CTID_P_VOID), (uint8_t *)&p, arg,
4164                         CCF_ARG(1), NULL);
4165                 size_t max_len = ucv_uint64_get(len_arg);
4166                 /* Read up to max_len bytes or until null terminator (like strncpy) */
4167                 sz = strnlen((const char *)p, max_len);
4168         }
4169         else {
4170                 /* Without length: extract pointer address and treat as char* */
4171                 GCcdata *cd = ucv_resource_data(arg, "ffi.ctype");
4172                 if (!cd) {
4173                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
4174                                 "string or cdata pointer expected, got %s",
4175                                 ucv_typename(arg));
4176 
4177                         return NULL;
4178                 }
4179 
4180                 /* Get pointer: arrays contain data directly, pointers contain address */
4181                 CType *cd_ct = ctype_get(cts, cd->ctypeid);
4182                 if (ctype_isrefarray(cd_ct->info)) {
4183                         /* Array: data is directly in cdata, use array size as limit */
4184                         p = cdataptr(cd);
4185                         sz = strnlen((const char *)p, cd_ct->size);
4186                 }
4187                 else if (ctype_isptr(cd_ct->info)) {
4188                         /* Pointer: dereference and read null-terminated string */
4189                         p = *(void **)cdataptr(cd);
4190                         if (!p)
4191                                 return ucv_string_new("");
4192                         sz = strlen((const char *)p);
4193                 }
4194                 else {
4195                         uc_vm_raise_exception(vm, EXCEPTION_TYPE,
4196                                 "string or cdata pointer expected, got %s",
4197                                 ucv_typename(arg));
4198 
4199                         return NULL;
4200                 }
4201         }
4202 
4203         return ucv_string_new_length((const char *)p, sz);
4204 }
4205 
4206 /**
4207  * Copy memory between pointers.
4208  *
4209  * The `copy()` function copies memory from a source pointer to a destination
4210  * pointer. If the source is a ucode string, it copies the string including
4211  * its null terminator. Otherwise, an explicit length must be provided.
4212  *
4213  * @function module:ffi#copy
4214  *
4215  * @param {module:ffi.CData} dest
4216  * Destination pointer.
4217  *
4218  * @param {string|module:ffi.CData} src
4219  * Source string or pointer.
4220  *
4221  * @param {number} [len]
4222  * Number of bytes to copy. Required if src is not a string.
4223  *
4224  * @returns {undefined}
4225  * Returns `undefined`.
4226  *
4227  * @example
4228  * // Copy string (includes null terminator)
4229  * let buf = ffi.ctype('char[10]');
4230  * ffi.copy(buf, "hello");
4231  *
4232  * @example
4233  * // Copy memory with explicit length
4234  * let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]);
4235  * let dst = ffi.ctype('char[5]');
4236  * ffi.copy(dst, src, 5);
4237  */
4238 static uc_value_t *
4239 uc_ffi_copy(uc_vm_t *vm, size_t nargs)
4240 {
4241         uc_value_t *dp_arg = uc_fn_arg(0);
4242         void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID);
4243         uc_value_t *sp_arg = uc_fn_arg(1);
4244         void *sp = NULL;
4245         size_t len, dp_size, sp_size = SIZE_MAX;
4246         CTState *cts = ctype_cts(vm);
4247 
4248         /* Get destination buffer size for bounds checking */
4249         dp_size = ffi_cdata_bufsize(cts, dp_arg);
4250 
4251         /* Handle string source: copy directly from the string buffer */
4252         if (ucv_type(sp_arg) == UC_STRING) {
4253                 const char *src = ucv_string_get(sp_arg);
4254                 size_t src_len = ucv_string_length(sp_arg);
4255 
4256                 /* Determine length: use explicit len if provided, else string + null */
4257                 if (nargs > 2) {
4258                         len = ucv_uint64_get(uc_fn_arg(2));
4259                 } else {
4260                         len = src_len + 1;
4261                 }
4262 
4263                 sp = (void *)src;
4264                 sp_size = src_len + 1;
4265         } else {
4266                 sp = ffi_checkptr(vm, nargs, 1, CTID_P_CVOID);
4267                 if (!sp)
4268                         return NULL;
4269 
4270                 /* Get source buffer size for bounds checking */
4271                 sp_size = ffi_cdata_bufsize(cts, sp_arg);
4272 
4273                 if (nargs > 2) {
4274                         len = ucv_uint64_get(uc_fn_arg(2));
4275                 } else {
4276                         len = strnlen((const char *)sp, sp_size);
4277                 }
4278         }
4279 
4280         /* Cap length to destination buffer size */
4281         if (len > dp_size)
4282                 len = dp_size;
4283 
4284         /* Cap length to source buffer size */
4285         if (len > sp_size)
4286                 len = sp_size;
4287 
4288         memcpy(dp, sp, len);
4289 
4290         return NULL;
4291 }
4292 
4293 /**
4294  * Fill memory with a byte value.
4295  *
4296  * The `fill()` function sets `len` bytes at the destination pointer to
4297  * the specified fill value. The fill value can be a number, boolean,
4298  * or string (first character used).
4299  *
4300  * @function module:ffi#fill
4301  *
4302  * @param {module:ffi.CData} dest
4303  * Destination pointer.
4304  *
4305  * @param {number} len
4306  * Number of bytes to fill.
4307  *
4308  * @param {number|boolean|string} [value=0]
4309  * Fill value. Numbers/booleans use the value directly; strings use
4310  * the first character's ASCII code.
4311  *
4312  * @returns {undefined}
4313  * Returns `undefined`.
4314  *
4315  * @example
4316  * // Zero-fill a buffer
4317  * let buf = ffi.ctype('char[10]');
4318  * ffi.fill(buf, 10, 0);
4319  *
4320  * // Fill with specific byte
4321  * ffi.fill(buf, 10, 0xFF);
4322  *
4323  * // Fill with character
4324  * ffi.fill(buf, 10, 'A');  // Fills with 65 (ASCII for 'A')
4325  */
4326 static uc_value_t *
4327 uc_ffi_fill(uc_vm_t *vm, size_t nargs)
4328 {
4329         void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID);
4330         size_t len = ucv_int64_get(uc_fn_arg(1));
4331         uc_value_t *fill = uc_fn_arg(2);
4332         int chr = 0;
4333 
4334         switch (ucv_type(fill))
4335         {
4336         case UC_INTEGER:
4337         case UC_DOUBLE:
4338                 chr = ucv_int64_get(fill);
4339                 break;
4340 
4341         case UC_BOOLEAN:
4342                 chr = ucv_boolean_get(fill) ? 1 : 0;
4343                 break;
4344 
4345         case UC_STRING:
4346                 chr = ucv_string_get(fill)[0];
4347                 break;
4348 
4349         default:
4350                 chr = 0;
4351                 break;
4352         }
4353 
4354         memset(dp, chr, len);
4355 
4356         return NULL;
4357 }
4358 
4359 /**
4360  * Cast a value to a different C type.
4361  *
4362  * The `cast()` function converts a value to a specified C type. It supports
4363  * casts to numbers, enums, and pointers. The cast is performed without
4364  * intermediate ucode type conversions.
4365  *
4366  * @function module:ffi#cast
4367  *
4368  * @param {string} type
4369  * The target C type declaration.
4370  *
4371  * @param {*} value
4372  * The value to cast. Can be a ucode value or cdata.
4373  *
4374  * @returns {module:ffi.CData}
4375  * A cdata of the target type holding the cast value.
4376  *
4377  * @throws {Error}
4378  * Throws an exception if the cast is invalid (e.g., casting to a struct).
4379  *
4380  * @example
4381  * // Cast number to pointer
4382  * let ptr = ffi.cast('void *', 0x1000);
4383  * print(ptr.get());  // => 4096
4384  *
4385  * @example
4386  * // Cast between pointer types
4387  * ffi.cdef('int x;');
4388  * let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr());
4389  * let pv = ffi.cast('void *', px);
4390  *
4391  * @example
4392  * // Cast pointer to integer
4393  * let str = ffi.string("hello");
4394  * let addr = ffi.cast('uintptr_t', str.ptr());
4395  * print(addr.get());  // => address as number
4396  *
4397  * @example
4398  * // Cast integer to enum
4399  * ffi.cdef('enum color { RED, GREEN, BLUE };');
4400  * let c = ffi.cast('enum color', 2);  // => BLUE
4401  */
4402 static uc_value_t *
4403 uc_ffi_cast(uc_vm_t *vm, size_t nargs)
4404 {
4405         CTState *cts = ctype_cts(vm);
4406         CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL);
4407         CType *d = ctype_raw(cts, id);
4408         uc_value_t *init = uc_fn_arg(1);
4409         GCcdata *cd = ucv_resource_data(init, "ffi.ctype");
4410 
4411         if (!ctype_isnum(d->info) && !ctype_isptr(d->info) && !ctype_isenum(d->info)) {
4412                 uc_value_t *repr = uc_ctype_repr(vm, id, NULL);
4413 
4414                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
4415                                       "invalid cast to type '%s', only casts to "
4416                                                           "numbers, enums or pointers are allowed",
4417                                                           ucv_string_get(repr));
4418 
4419                 ucv_put(repr);
4420 
4421                 return NULL;
4422         }
4423 
4424         if (cd && cd->ctypeid == id)
4425                 return ucv_get(init);
4426 
4427         uc_value_t *res = uc_cdata_new(vm, id, d->size);
4428         uc_value_t *refs = NULL;
4429 
4430         /* when we're casting to pointer, keep references to original memory */
4431         if (cd && ctype_isptr(d->info)) {
4432                 refs = ucv_array_new(vm);
4433 
4434                 /* keep reference to original value itself */
4435                 ucv_array_push(refs, ucv_get(init));
4436 
4437                 /* merge original values references */
4438                 uc_value_t *src_refs = cd->refs;
4439 
4440                 for (size_t i = 0; i < ucv_array_length(src_refs); i++)
4441                         ucv_array_push(refs, ucv_get(ucv_array_get(src_refs, i)));
4442         }
4443 
4444         uc_cconv_ct_tv(cts, d, uc_cdata_dataptr(res), init, CCF_CAST, &refs);
4445 
4446         cd = ucv_resource_data(res, "ffi.ctype");
4447         cd->refs = refs;
4448 
4449         return res;
4450 }
4451 
4452 /**
4453  * Cast a cdata to a different C type.
4454  *
4455  * The `.cast()` method converts a cdata to a specified C type. This is
4456  * equivalent to calling `ffi.cast(type, cdata)`. It supports casts to
4457  * numbers, enums, and pointers.
4458  *
4459  * @function module:ffi.CData#cast
4460  *
4461  * @param {string} type
4462  * The target C type declaration.
4463  *
4464  * @returns {module:ffi.CData}
4465  * A cdata of the target type holding the cast value.
4466  *
4467  * @throws {Error}
4468  * Throws an exception if the cast is invalid.
4469  *
4470  * @example
4471  * // Cast pointer to void*
4472  * let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr());
4473  * let pv = px.cast('void *');
4474  *
4475  * @example
4476  * // Cast pointer to integer
4477  * let str = ffi.string("hello");
4478  * let addr = str.ptr().cast('uintptr_t');
4479  * print(addr.get());
4480  *
4481  * @see {@link module:ffi#cast|ffi.cast()}
4482  */
4483 static uc_value_t *
4484 uc_ctype_cast(uc_vm_t *vm, size_t nargs)
4485 {
4486         uc_value_t *this_arg = _uc_fn_this_res(vm);
4487         GCcdata *cd = ucv_resource_data(this_arg, "ffi.ctype");
4488 
4489         if (!cd)
4490                 return NULL;
4491 
4492         CTState *cts = ctype_cts(vm);
4493         CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL);
4494         CType *d = ctype_raw(cts, id);
4495 
4496         if (!ctype_isnum(d->info) && !ctype_isptr(d->info) && !ctype_isenum(d->info)) {
4497                 uc_value_t *repr = uc_ctype_repr(vm, id, NULL);
4498 
4499                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
4500                                       "invalid cast to type '%s', only casts to "
4501                                                           "numbers, enums or pointers are allowed",
4502                                                           ucv_string_get(repr));
4503 
4504                 ucv_put(repr);
4505 
4506                 return NULL;
4507         }
4508 
4509         if (cd->ctypeid == id)
4510                 return ucv_get(this_arg);
4511 
4512         uc_value_t *res = uc_cdata_new(vm, id, d->size);
4513         uc_value_t *refs = NULL;
4514 
4515         /* when we're casting to pointer, keep references to original memory */
4516         if (ctype_isptr(d->info)) {
4517                 refs = ucv_array_new(vm);
4518 
4519                 /* keep reference to original value itself */
4520                 ucv_array_push(refs, ucv_get(this_arg));
4521 
4522                 /* merge original values references */
4523                 uc_value_t *src_refs = cd->refs;
4524 
4525                 for (size_t i = 0; i < ucv_array_length(src_refs); i++)
4526                         ucv_array_push(refs, ucv_get(ucv_array_get(src_refs, i)));
4527         }
4528 
4529         uc_cconv_ct_tv(cts, d, uc_cdata_dataptr(res), this_arg, CCF_CAST, &refs);
4530 
4531         GCcdata *res_cd = ucv_resource_data(res, "ffi.ctype");
4532         res_cd->refs = refs;
4533 
4534         return res;
4535 }
4536 
4537 /**
4538  * Copy memory to a cdata from a source.
4539  *
4540  * The `.copy()` method copies memory from a source to this cdata. If the
4541  * source is a ucode string, it copies the string including its null
4542  * terminator. Otherwise, an explicit length can be provided.
4543  *
4544  * @function module:ffi.CData#copy
4545  *
4546  * @param {string|module:ffi.CData} src
4547  * Source string or pointer.
4548  *
4549  * @param {number} [len]
4550  * Number of bytes to copy. Required if src is not a string.
4551  *
4552  * @returns {undefined}
4553  * Returns `undefined`.
4554  *
4555  * @example
4556  * // Copy string into buffer
4557  * let buf = ffi.ctype('char[10]');
4558  * buf.copy("hello");
4559  *
4560  * @example
4561  * // Copy with explicit length
4562  * let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]);
4563  * let dst = ffi.ctype('char[5]');
4564  * dst.copy(src, 5);
4565  *
4566  * @see {@link module:ffi#copy|ffi.copy()}
4567  */
4568 static uc_value_t *
4569 uc_ctype_copy(uc_vm_t *vm, size_t nargs)
4570 {
4571         GCcdata *cd = uc_fn_thisval("ffi.ctype");
4572 
4573         if (!cd)
4574                 return NULL;
4575 
4576         uc_value_t *sp_arg = uc_fn_arg(0);
4577         uc_value_t *len_arg = uc_fn_arg(1);
4578         void *dp = cdataptr(cd);
4579         void *sp = NULL;
4580         size_t len, dp_size, sp_size = SIZE_MAX;
4581         CTState *cts = ctype_cts(vm);
4582 
4583         /* Get destination buffer size for bounds checking */
4584         dp_size = ffi_cdata_bufsize(cts, (uc_value_t *)cd);
4585 
4586         /* Handle string source: copy directly from the string buffer */
4587         if (ucv_type(sp_arg) == UC_STRING) {
4588                 const char *src = ucv_string_get(sp_arg);
4589                 size_t src_len = ucv_string_length(sp_arg);
4590 
4591                 /* Determine length: use explicit len if provided, else string + null */
4592                 if (nargs > 1) {
4593                         len = ucv_uint64_get(len_arg);
4594                 } else {
4595                         len = src_len + 1;
4596                 }
4597 
4598                 sp = (void *)src;
4599                 sp_size = src_len + 1;
4600         } else {
4601                 sp = ffi_checkptr(vm, nargs, 0, CTID_P_CVOID);
4602                 if (!sp)
4603                         return NULL;
4604 
4605                 /* Get source buffer size for bounds checking */
4606                 sp_size = ffi_cdata_bufsize(cts, sp_arg);
4607 
4608                 if (nargs > 1) {
4609                         len = ucv_uint64_get(len_arg);
4610                 } else {
4611                         len = strnlen((const char *)sp, sp_size);
4612                 }
4613         }
4614 
4615         /* Cap length to destination buffer size */
4616         if (len > dp_size)
4617                 len = dp_size;
4618 
4619         /* Cap length to source buffer size */
4620         if (len > sp_size)
4621                 len = sp_size;
4622 
4623         memcpy(dp, sp, len);
4624 
4625         return NULL;
4626 }
4627 
4628 /**
4629  * Convert a cdata to a ucode string.
4630  *
4631  * The `.string()` method reads a C string from a char* pointer or char[]
4632  * array and returns a ucode string. For char* pointers, it reads until the
4633  * null terminator. For char[] arrays, it reads up to the array length.
4634  * An optional length parameter can limit the bytes read.
4635  *
4636  * @function module:ffi.CData#string
4637  *
4638  * @param {number} [len]
4639  * Optional maximum length for reading C strings (reads up to `len` bytes
4640  * or until null terminator).
4641  *
4642  * @returns {string}
4643  * The extracted ucode string.
4644  *
4645  * @throws {Error}
4646  * Throws an exception if the cdata is not a char* or char[] type.
4647  *
4648  * @example
4649  * // Read from char* pointer
4650  * ffi.cdef('char *getenv(char *);');
4651  * let ptr = ffi.C.wrap('char *getenv(char *)')("PATH");
4652  * let path = ptr.string();
4653  * print(path);
4654  *
4655  * @example
4656  * // Read from char[] array
4657  * let buf = ffi.ctype('char[10]', "hello");
4658  * print(buf.string());  // => "hello"
4659  *
4660  * @example
4661  * // Read fixed-length string
4662  * let buf = ffi.ctype('char[10]', "hello world");
4663  * print(buf.string(5));  // => "hello"
4664  *
4665  * @see {@link module:ffi#string|ffi.string()}
4666  */
4667 static uc_value_t *
4668 uc_ctype_string(uc_vm_t *vm, size_t nargs)
4669 {
4670         uc_value_t *this_arg = _uc_fn_this_res(vm);
4671         GCcdata *cd = ucv_resource_data(this_arg, "ffi.ctype");
4672 
4673         if (!cd)
4674                 return NULL;
4675 
4676         uc_value_t *len_arg = uc_fn_arg(0);
4677         CTState *cts = ctype_cts(vm);
4678         CType *cd_ct = ctype_get(cts, cd->ctypeid);
4679         void *p = NULL;
4680         size_t sz;
4681 
4682         /* Get pointer: arrays contain data directly, pointers contain address */
4683         if (ctype_isarray(cd_ct->info)) {
4684                 /* Array: data is directly in cdata, use array size as limit */
4685                 p = cdataptr(cd);
4686                 if (nargs > 0) {
4687                         size_t max_len = ucv_uint64_get(len_arg);
4688                         sz = strnlen((const char *)p, max_len);
4689                 } else {
4690                         sz = strnlen((const char *)p, cd_ct->size);
4691                 }
4692         } else if (ctype_isptr(cd_ct->info)) {
4693                 /* Pointer: dereference and read null-terminated string */
4694                 p = *(void **)cdataptr(cd);
4695                 if (!p)
4696                         return ucv_string_new("");
4697 
4698                 if (nargs > 0) {
4699                         size_t max_len = ucv_uint64_get(len_arg);
4700                         sz = strnlen((const char *)p, max_len);
4701                 } else {
4702                         sz = strlen((const char *)p);
4703                 }
4704         } else {
4705                 uc_vm_raise_exception(vm, EXCEPTION_TYPE,
4706                         "string() requires char* or char[] type, got %s",
4707                         ucv_typename(this_arg));
4708 
4709                 return NULL;
4710         }
4711 
4712         return ucv_string_new_length((const char *)p, sz);
4713 }
4714 
4715 #if UC_TARGET_CYGWIN
4716 #define CLIB_SOPREFIX "cyg"
4717 #else
4718 #define CLIB_SOPREFIX "lib"
4719 #endif
4720 
4721 #if defined(__APPLE__)
4722 #define CLIB_SOEXT "%s.dylib"
4723 #elif UC_TARGET_CYGWIN
4724 #define CLIB_SOEXT "%s.dll"
4725 #else
4726 #define CLIB_SOEXT "%s.so"
4727 #endif
4728 
4729 /**
4730  * Load a shared library.
4731  *
4732  * The `dlopen()` function loads a shared library into the process address
4733  * space and returns a CLib object that can be used to access symbols via
4734  * `dlsym()` or `wrap()`.
4735  *
4736  *   ```javascript
4737  *   // Load zlib compression library
4738  *   let libz = ffi.dlopen('z');
4739  *
4740  *   // Load OpenSSL crypto library
4741  *   let libcrypto = ffi.dlopen('crypto');
4742  *
4743  *   // Load absolute path
4744  *   let custom = ffi.dlopen('/usr/local/lib/mylib.so');
4745  *
4746  *   // Use wrap() to get function pointers
4747  *   let zlibVersion = libz.wrap('const char *zlibVersion(void)');
4748  *   print(zlibVersion().slice(), "\n");  // => "1.2.11"
4749  *   ```
4750  *
4751  * On Unix-like systems, the `.so` extension is automatically appended if
4752  * omitted. On macOS, `.dylib` is used. On Windows, `.dll` is used.
4753  *
4754  * When the optional third argument is provided, `dlopen()` will:
4755  * - Parse the C definitions to register types and function prototypes
4756  * - Resolve and wrap all declared functions
4757  * - Attach the wrapped functions as methods on the library object
4758  *
4759  * @function module:ffi#dlopen
4760  *
4761  * @param {string} name
4762  * The library name or path.
4763  *
4764  * @param {boolean} [global=false]
4765  * If `true`, make symbols available to subsequently loaded libraries.
4766  *
4767  * @param {string} [cdefs]
4768  * Optional C declaration string containing types and function prototypes.
4769  * Function declarations will be automatically wrapped and attached to the
4770  * library object as methods.
4771  *
4772  * @returns {?module:ffi.CLib}
4773  * A CLib object representing the loaded library, or `null` on error.
4774  * When `cdefs` is provided, the returned CLib will have wrapped functions
4775  * attached as methods.
4776  *
4777  * @throws {Error}
4778  * Throws an exception if the library cannot be loaded or if C definitions
4779  * cannot be parsed.
4780  *
4781  * @example
4782  * // Load zlib and call functions
4783  * let libz = ffi.dlopen('z');
4784  * let zlibVersion = libz.wrap('const char *zlibVersion(void)');
4785  * print(zlibVersion().slice());
4786  *
4787  * @example
4788  * // Load OpenSSL crypto library
4789  * let libcrypto = ffi.dlopen('crypto');
4790  * let OpenSSL_version = libcrypto.wrap('const char *OpenSSL_version(int)');
4791  * print(OpenSSL_version(0).slice());  // => "OpenSSL 3.0.0..."
4792  *
4793  * @example
4794  * // Load library with automatic wrapping
4795  * let libssl = ffi.dlopen('ssl', false, `
4796  *     typedef void SSL_METHOD;
4797  *     const SSL_METHOD *TLS_method(void);
4798  * `);
4799  * // TLS_method is now directly callable
4800  * let method = libssl.TLS_method();
4801  *
4802  * @example
4803  * // Load zlib with pre-wrapped functions
4804  * let libz = ffi.dlopen('z', false, `
4805  *     const char *zlibVersion(void);
4806  *     uLong compressBound(uLong sourceLen);
4807  * `);
4808  * print(libz.zlibVersion().slice());
4809  * print(libz.compressBound(1024));
4810  */
4811 static uc_value_t *
4812 uc_ffi_dlopen(uc_vm_t *vm, size_t nargs)
4813 {
4814         uc_value_t *name = uc_fn_arg(0);
4815         uc_value_t *global = uc_fn_arg(1);
4816         uc_value_t *cdefs = uc_fn_arg(2);
4817         uc_value_t *clibs = uc_vm_registry_get(vm, "ffi.clibs");
4818 
4819         /* Handle dlopen(null) or dlopen("") case - returns global C library */
4820         if (!name || (ucv_type(name) == UC_STRING && !ucv_string_length(name))) {
4821                 uc_value_t *global_lib = ucv_object_get(clibs, "", NULL);
4822 
4823                 /* If cdefs provided, parse them and add wrapped functions to ffi.C prototype */
4824                 if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) {
4825                         uc_ffi_clib_t *lib = ucv_resource_data(global_lib, "ffi.clib");
4826                         uc_value_t *methods = ucv_prototype_get(global_lib);
4827 
4828                         if (lib) {
4829                                 CTState *cts = ctype_cts(vm);
4830                                 CPState cp = {
4831                                         .uv_vm = vm,
4832                                         .cts = cts,
4833                                         .srcname = ucv_string_get(cdefs),
4834                                         .p = ucv_string_get(cdefs),
4835                                         .uv_param = NULL,
4836                                         .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT,
4837                                         .func_ids = &cp.func_ids_buf,
4838                                         .func_ids_buf = { .count = 0, .entries = NULL },
4839                                         .error = NULL
4840                                 };
4841 
4842                                 if (!uc_cparse(&cp))
4843                                         return NULL;
4844 
4845                                 for (size_t i = 0; i < cp.func_ids_buf.count; i++) {
4846                                         CTypeID id = cp.func_ids_buf.entries[i];
4847                                         CType *ct = ctype_get(cts, id);
4848                                         if (!ct || !ct->uv_name || ucv_type(ct->uv_name) != UC_STRING)
4849                                                 continue;
4850 
4851                                         const char *symname = ucv_string_get(ct->uv_name);
4852                                         void *fp = dlsym(RTLD_DEFAULT, symname);
4853                                         if (!fp)
4854                                                 continue;
4855 
4856                                         CTypeID cid = ctype_typeid(cts, ct);
4857                                         size_t namelen = strlen(symname);
4858                                         size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1);
4859 
4860                                         uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp));
4861                                         cfn->header.type = UC_CFUNCTION;
4862                                         cfn->cfn = clib_wrapped_call;
4863                                         snprintf(cfn->name, namelen + 1, "ffi.C.%s", symname);
4864 
4865                                         memcpy((char *)cfn + off, &cid, sizeof(cid));
4866                                         memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp));
4867 
4868                                         uc_value_t *wrapped = ucv_get(&cfn->header);
4869                                         ucv_object_add(methods, symname, wrapped);
4870                                 }
4871                                 uc_vector_clear(&cp.func_ids_buf);
4872                         }
4873                 }
4874 
4875                 return ucv_get(global_lib);
4876         }
4877 
4878         if (ucv_type(name) != UC_STRING)
4879                 return NULL;
4880 
4881         char *path = ucv_string_get(name);
4882         char *s = path;
4883 
4884         /* relative name provided */
4885         if (!strchr(path, '/') && !strchr(path, '\\') && !strchr(path, '.'))
4886                 xasprintf(&s, CLIB_SOPREFIX CLIB_SOEXT, path);
4887 
4888         int mode = RTLD_LAZY | (ucv_is_truish(global) ? RTLD_GLOBAL : RTLD_LOCAL);
4889         void *dlh = dlopen(s, mode);
4890 
4891         if (!dlh) {
4892                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
4893                         "unable to load library '%s' (%s): %s",
4894                         path, s, dlerror());
4895 
4896                 if (s != path)
4897                         free(s);
4898 
4899                 return NULL;
4900         }
4901 
4902         /* Per-name instance caching: check if this library name already exists */
4903         uc_value_t *cached = ucv_object_get(clibs, s, NULL);
4904 
4905         if (cached) {
4906                 dlclose(dlh);
4907 
4908                 if (s != path)
4909                         free(s);
4910 
4911                 return ucv_get(cached);
4912         }
4913 
4914         uc_ffi_clib_t *lib;
4915 
4916         /* Create instance prototype - wrapped functions live here as instance methods */
4917         uc_value_t *methods = ucv_object_new(vm);
4918 
4919         size_t libnamesize = strlen(s) + 1;
4920         size_t datasize = ((sizeof(uc_ffi_clib_t) + libnamesize + 7) / 8) * 8;
4921 
4922         uc_value_t *lib_obj = ucv_resource_new_with_proto(
4923                 vm, ucv_resource_type_lookup(vm, "ffi.clib"),
4924                 (void **)&lib, 0, datasize, methods);
4925 
4926         lib->cache = ucv_object_new(vm);
4927         lib->dlh = dlh;
4928 
4929         /* Copy library name into the allocated block right after the struct */
4930         lib->name = memcpy((char *)lib + sizeof(uc_ffi_clib_t), s, libnamesize);
4931 
4932         if (s != path)
4933                 free(s);
4934 
4935         /* If cdefs provided, parse them and wrap functions */
4936         if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) {
4937                 CTState *cts = ctype_cts(vm);
4938                 CPState cp = {
4939                         .uv_vm = vm,
4940                         .cts = cts,
4941                         .srcname = ucv_string_get(cdefs),
4942                         .p = ucv_string_get(cdefs),
4943                         .uv_param = NULL,
4944                         .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT,
4945                         .func_ids = &cp.func_ids_buf,
4946                         .func_ids_buf = { .count = 0, .entries = NULL },
4947                         .error = NULL
4948                 };
4949 
4950                 if (!uc_cparse(&cp)) {
4951                         if (lib->dlh != RTLD_DEFAULT)
4952                                 dlclose(lib->dlh);
4953 
4954                         ucv_put(lib_obj);
4955 
4956                         return NULL;
4957                 }
4958 
4959                 for (size_t i = 0; i < cp.func_ids_buf.count; i++) {
4960                         CTypeID id = cp.func_ids_buf.entries[i];
4961                         CType *ct = ctype_get(cts, id);
4962                         if (!ct)
4963                                 continue;
4964 
4965                         if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING)
4966                                 continue;
4967 
4968                         const char *symname = ucv_string_get(ct->uv_name);
4969 
4970                         void *fp = dlsym(dlh, symname);
4971                         if (!fp) {
4972                                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
4973                                         "unable to resolve symbol '%s' in library '%s'",
4974                                         symname, lib->name);
4975 
4976                                 if (lib->dlh != RTLD_DEFAULT)
4977                                         dlclose(lib->dlh);
4978 
4979                                 ucv_put(lib_obj);
4980 
4981                                 return NULL;
4982                         }
4983 
4984                         CTypeID cid = ctype_typeid(cts, ct);
4985                         size_t fnamelen = strlen(symname);
4986                         size_t off = ALIGN(sizeof(uc_cfunction_t) + fnamelen + 1);
4987 
4988                         uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp));
4989                         cfn->header.type = UC_CFUNCTION;
4990                         cfn->cfn = clib_wrapped_call;
4991                         snprintf(cfn->name, fnamelen + 1, "ffi.%s.%s", lib->name, symname);
4992 
4993                         memcpy((char *)cfn + off, &cid, sizeof(cid));
4994                         memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp));
4995 
4996                         uc_value_t *wrapped = ucv_get(&cfn->header);
4997                         ucv_object_add(methods, symname, wrapped);
4998                 }
4999 
5000                 uc_vector_clear(&cp.func_ids_buf);
5001         }
5002 
5003         ucv_object_add(clibs, lib->name, ucv_get(lib_obj));
5004 
5005         return lib_obj;
5006 }
5007 
5008 /**
5009  * Import a C library with automatic function wrapping.
5010  *
5011  * This is a convenience function that combines library loading, type
5012  * declaration, and function wrapping into a single call. It loads the
5013  * specified library, parses the C definitions, and returns an object
5014  * with all functions pre-wrapped and ready to call.
5015  *
5016  * @function module:ffi#import
5017  *
5018  * @param {string} libname
5019  * The library name or path to load. Can be a bare name (e.g., 'z'),
5020  * a filename (e.g., 'libcrypto.so.3'), or an absolute path.
5021  *
5022  * @param {string} cdefs
5023  * A C declaration string containing function prototypes to import.
5024  * Only function declarations are wrapped; types, structs, and other
5025  * declarations are registered but not added to the result object.
5026  *
5027  * @returns {object|null}
5028  * An object containing wrapped functions keyed by their symbol names.
5029  * Returns null if the library cannot be loaded or if parsing fails.
5030  *
5031  * @throws {Error}
5032  * Throws an exception if:
5033  * - The library cannot be loaded
5034  * - The C declarations are syntactically invalid
5035  * - A declared function cannot be resolved in the library
5036  *
5037  * @example
5038  * // Import sqlite3 with all functions
5039  * let sqlite3 = ffi.import('sqlite3', `
5040  *     const char *sqlite3_libversion(void);
5041  *     int sqlite3_libversion_number(void);
5042  *     int sqlite3_open(const char *, void **);
5043  *     int sqlite3_close(void *);
5044  * `);
5045  *
5046  * print("Version: ", sqlite3.sqlite3_libversion(), "\n");
5047  *
5048  * @example
5049  * // Import zlib functions
5050  * let zlib = ffi.import('z', `
5051  *     const char *zlibVersion(void);
5052  *     uLong compressBound(uLong sourceLen);
5053  * `);
5054  *
5055  * print(zlib.zlibVersion());
5056  * print(zlib.compressBound(1024));
5057  */
5058 static uc_value_t *
5059 uc_ffi_import(uc_vm_t *vm, size_t nargs)
5060 {
5061         uc_value_t *libname = uc_fn_arg(0);
5062         uc_value_t *cdefs = uc_fn_arg(1);
5063         CTState *cts = ctype_cts(vm);
5064 
5065         if (!libname || ucv_type(libname) != UC_STRING)
5066                 return NULL;
5067 
5068         if (!cdefs || ucv_type(cdefs) != UC_STRING)
5069                 return NULL;
5070 
5071         /* Load the library */
5072         char *path = ucv_string_get(libname);
5073         void *dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL);
5074 
5075         if (!dlh) {
5076                 uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
5077                         "unable to load library '%s' (%s): %s",
5078                         ucv_string_get(libname), path, dlerror());
5079 
5080                 return NULL;
5081         }
5082 
5083         /* Parse C definitions to register types */
5084         CPState cp = {
5085                 .uv_vm = vm,
5086                 .cts = cts,
5087                 .srcname = ucv_string_get(cdefs),
5088                 .p = ucv_string_get(cdefs),
5089                 .uv_param = NULL,
5090                 .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT,
5091                 .func_ids = &cp.func_ids_buf
5092         };
5093 
5094         if (!uc_cparse(&cp)) {
5095                 dlclose(dlh);
5096                 return NULL;
5097         }
5098 
5099         /* Create result object */
5100         uc_value_t *result = ucv_object_new(vm);
5101 
5102         /* Iterate over recorded function type IDs */
5103         for (size_t i = 0; i < cp.func_ids_buf.count; i++) {
5104                 CTypeID id = cp.func_ids_buf.entries[i];
5105                 CType *ct = ctype_get(cts, id);
5106                 if (!ct)
5107                         continue;
5108 
5109                 /* Get the function name */
5110                 if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING)
5111                         continue;
5112 
5113                 const char *symname = ucv_string_get(ct->uv_name);
5114 
5115                 /* Resolve the symbol from the library */
5116                 void *fp = dlsym(dlh, symname);
5117                 if (!fp) {
5118                         uc_vm_raise_exception(vm, EXCEPTION_RUNTIME,
5119                                 "unable to resolve symbol '%s' in library", symname);
5120 
5121                         ucv_put(result);
5122                         dlclose(dlh);
5123                         return NULL;
5124                 }
5125 
5126                 /* Wrap the function - create cfunction wrapper */
5127                 CTypeID cid = ctype_typeid(cts, ct);
5128                 size_t namelen = strlen(symname);
5129                 size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1);
5130 
5131                 uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp));
5132                 cfn->header.type = UC_CFUNCTION;
5133                 cfn->cfn = clib_wrapped_call;
5134                 snprintf(cfn->name, namelen + 1, "ffi.import.%s", symname);
5135 
5136                 /* Store cid and fp after the cfunction struct */
5137                 memcpy((char *)cfn + off, &cid, sizeof(cid));
5138                 memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp));
5139 
5140                 uc_value_t *wrapped = ucv_get(&cfn->header);
5141                 ucv_object_add(result, symname, wrapped);
5142                 /* ucv_object_add already increments refcount, no need to put */
5143         }
5144 
5145         uc_vector_clear(&cp.func_ids_buf);
5146         dlclose(dlh);
5147 
5148         return result;
5149 }
5150 
5151 
5152 static const uc_function_list_t clib_fns[] = {
5153         { "dlsym",              uc_clib_dlsym },
5154         { "resolve",    uc_clib_resolve },
5155         { "wrap",               uc_clib_wrap },
5156 };
5157 
5158 static const uc_function_list_t ctype_fns[] = {
5159         { "call",               uc_ctype_call },
5160         { "free",               uc_ctype_free },
5161         { "get",                uc_ctype_get },
5162         { "set",                uc_ctype_set },
5163         { "ptr",                uc_ctype_ptr },
5164         { "index",              uc_ctype_index },
5165         { "deref",              uc_ctype_deref },
5166         { "size",               uc_ctype_sizeof },
5167         { "length",             uc_ctype_length },
5168         { "itemsize",   uc_ctype_itemsize },
5169         { "slice",              uc_ctype_slice },
5170         { "tostring",   uc_ctype_tostring },
5171         { "cast",               uc_ctype_cast },
5172         { "copy",               uc_ctype_copy },
5173         { "string",             uc_ctype_string },
5174 };
5175 
5176 static const uc_function_list_t global_fns[] = {
5177         { "ctype",              uc_ffi_ctype },
5178         { "cdef",               uc_ffi_cdef },
5179         { "typeof",             uc_ffi_typeof },
5180         { "sizeof",             uc_ffi_sizeof },
5181         { "alignof",    uc_ffi_alignof },
5182         { "offsetof",   uc_ffi_offsetof },
5183         { "errno",              uc_ffi_errno },
5184         { "string",             uc_ffi_string },
5185         { "copy",               uc_ffi_copy },
5186         { "fill",               uc_ffi_fill },
5187         { "cast",               uc_ffi_cast },
5188         { "dlopen",             uc_ffi_dlopen },
5189         { "import",             uc_ffi_import },
5190 };
5191 
5192 
5193 static void
5194 close_clib(void *ud)
5195 {
5196         uc_ffi_clib_t *clib = ud;
5197 
5198         ucv_put(clib->cache);
5199 
5200         if (clib->dlh != RTLD_DEFAULT)
5201                 dlclose(clib->dlh);
5202 }
5203 
5204 static void
5205 close_ctype(void *ud)
5206 {
5207         GCcdata *cd = ud;
5208 
5209         /* ucode does not create libffi closure cdata objects;
5210          * closures are created transiently for callback arguments. */
5211 
5212         if (cd->refs)
5213                 ucv_put(cd->refs);
5214 }
5215 
5216 
5217 extern char **environ;
5218 
5219 static void
5220 preload_type(uc_vm_t *vm, uc_ffi_clib_t *lib, const char *cdef, void *val)
5221 {
5222         CTState *cts = ctype_cts(vm);
5223         uc_value_t *def = ucv_string_new(cdef);
5224         CTypeID cid = uv_to_ct(vm, CPARSE_MODE_DIRECT | CPARSE_MODE_NOIMPLICIT | CPARSE_MODE_MULTI, def, NULL);
5225 
5226         ucv_put(def);
5227 
5228         if (!cid)
5229                 return;
5230 
5231         CType *ct = ctype_get(cts, cid);
5232 
5233         if (!ct || ucv_type(ct->uv_name) != UC_STRING)
5234                 return;
5235 
5236         uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR);
5237 
5238         *(void **)uc_cdata_dataptr(sym) = val;
5239 
5240         ucv_object_add(lib->cache, ucv_string_get(ct->uv_name), sym);
5241 }
5242 
5243 void uc_module_init(uc_vm_t *vm, uc_value_t *scope)
5244 {
5245         uc_ctype_init(vm);
5246 
5247         uc_type_declare(vm, "ffi.clib", clib_fns, close_clib);
5248         uc_type_declare(vm, "ffi.ctype", ctype_fns, close_ctype);
5249 
5250         uc_function_list_register(scope, global_fns);
5251 
5252         uc_value_t *clibs = ucv_object_new(vm);
5253 
5254         uc_vm_registry_set(vm, "ffi.clibs", clibs);
5255 
5256         uc_ffi_clib_t *C;
5257         uc_value_t *global_proto = ucv_object_new(vm);
5258 
5259         uc_value_t *stdlib = ucv_resource_new_with_proto(
5260                 vm, ucv_resource_type_lookup(vm, "ffi.clib"),
5261                 (void **)&C, 0, sizeof(*C), global_proto);
5262 
5263         C->dlh = RTLD_DEFAULT;
5264         C->name = NULL;
5265         C->cache = ucv_object_new(vm);
5266 
5267         ucv_object_add(scope, "C", stdlib);
5268         ucv_object_add(clibs, "", ucv_get(stdlib));
5269 
5270         /* preload global variables */
5271         preload_type(vm, C, "int *errno", &errno);
5272         preload_type(vm, C, "char **environ", environ);
5273 }
5274 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt