From cedff22929eabb7a6b0aec5cb1db941f11c3ae07 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Fri, 17 Apr 2026 20:43:37 +0200 Subject: [PATCH 1/6] feat: add ucode agent skill for build, test, and execute workflows --- .opencode/skills/ucode/SKILL.md | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .opencode/skills/ucode/SKILL.md diff --git a/.opencode/skills/ucode/SKILL.md b/.opencode/skills/ucode/SKILL.md new file mode 100644 index 00000000..966ac688 --- /dev/null +++ b/.opencode/skills/ucode/SKILL.md @@ -0,0 +1,55 @@ +--- +name: ucode +description: Build, test, and execute ucode programs using cmake, the ucode compiler, and the custom test suite +metadata: + audience: developers + workflow: development +--- + +## What I do + +I handle the common workflows for working with the ucode scripting language project: building from source, running tests, and executing ucode with libraries. + +## Build + +Configure the build (run this first if CMake files changed): +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +``` + +Compile: +```bash +make -C build +``` + +## Test + +Run the entire test suite: +```bash +./tests/custom/run_tests.uc +``` + +Run tests in a specific directory (note the trailing wildcard pattern): +```bash +./tests/custom/run_tests.uc tests/custom/17_lib_ffi/* +``` + +## Execute + +Run a ucode snippet with a loaded library: +```bash +./build/ucode -L build -l ffi -e 'print(ffi.C.dlsym("size_t"))' +``` + +Key flags: +- `-L ` — prepend path to library search directory +- `-l ` — preload a library (e.g. `ffi`, `json`, `uci`) +- `-e ''` — execute an expression inline + +## When to use me + +Use this skill when the user wants to: +- Build or rebuild the ucode project +- Run the test suite (all or specific subdirectories) +- Execute ucode code snippets with or without loading custom libraries +- Debug ucode issues by running code interactively From d026c7cf7574b5b85c8bef233e1a1f778c9e608a Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 14 Apr 2026 23:40:23 +0200 Subject: [PATCH 2/6] include: add `unused` macro Add an `unused` macro expanding to `__attribute__((unused))` to simplify marking unused variables or function parameters. Signed-off-by: Jo-Philipp Wich --- include/ucode/util.h | 8 +++++++- lib/zlib.c | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/ucode/util.h b/include/ucode/util.h index 1360d286..ad9f536f 100644 --- a/include/ucode/util.h +++ b/include/ucode/util.h @@ -30,9 +30,15 @@ #define __hidden __attribute__((visibility("hidden"))) #endif +#ifndef unused +# if defined(__GNUC__) || defined(__clang__) +# define unused __attribute__((unused)) +# endif +#endif + #ifndef localfunc # if defined(__GNUC__) || defined(__clang__) -# define localfunc static __attribute__((noinline,unused)) +# define localfunc static unused __attribute__((noinline)) # else # define localfunc static inline # endif diff --git a/lib/zlib.c b/lib/zlib.c index 83093889..3b2818ad 100644 --- a/lib/zlib.c +++ b/lib/zlib.c @@ -48,7 +48,7 @@ #endif #define CHUNK (UC_ZLIB_CHUNK) -static const __attribute__((unused)) unsigned int _chunk_check = CHUNK; +static unused const unsigned int _chunk_check = CHUNK; static uc_resource_type_t *zstrmd_type, *zstrmi_type; From ec1101efb953a25d864759cc7fe8acc1a25a8024 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 21 Apr 2026 09:54:33 +0200 Subject: [PATCH 3/6] include: register resource type prototypes with VM In order to ensure reliable freeing of resource type prototypes in complex prototype chains with per-resource prototypes, register the created type prototype object with the VM value pool. Signed-off-by: Jo-Philipp Wich --- include/ucode/lib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ucode/lib.h b/include/ucode/lib.h index a21fe4fe..543078a0 100644 --- a/include/ucode/lib.h +++ b/include/ucode/lib.h @@ -86,7 +86,7 @@ uc_resource_new(uc_resource_type_t *type, void *data) static inline uc_resource_type_t * _uc_type_declare(uc_vm_t *vm, const char *name, const uc_function_list_t *list, size_t len, void (*freefn)(void *)) { - uc_value_t *proto = ucv_object_new(NULL); + uc_value_t *proto = ucv_object_new(vm); while (len-- > 0) ucv_object_add(proto, list[len].name, From 1bf82b4d30b7dfb3e369b84eacbc5a3e0a3b96a2 Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Fri, 17 Apr 2026 15:02:43 +0200 Subject: [PATCH 4/6] types: add per-instance prototype support for extended resources Add support for storing an optional per-instance prototype slot in extended resource values. This allows individual resource instances to override the type-level prototype with their own prototype object. Changes: - Add hasproto flag to uc_resource_ext_t to track presence of instance proto - Add ucv_resource_new_prototyped() constructor for resources with instance proto - Add ucv_resource_hasproto(), ucv_resource_proto_get(), ucv_resource_proto_set() helpers for managing instance prototypes - Add ucv_resource_create_prototyped() convenience wrapper - Update GC mark and cleanup to handle instance prototype slot - Update ucv_prototype_get/set() to check for instance prototype before falling back to type-level prototype When an instance prototype is provided, it is chained to the type's prototype, ensuring proper prototype chain lookup. Signed-off-by: Jo-Philipp Wich --- include/ucode/types.h | 37 ++++++++++++- types.c | 121 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 15 deletions(-) diff --git a/include/ucode/types.h b/include/ucode/types.h index 23137fad..7a809176 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -209,7 +209,8 @@ typedef struct { uc_weakref_t ref; uc_resource_type_t *type; - uint32_t reserved:3; + uint32_t reserved:2; + uint32_t hasproto:1; uint32_t persistent:1; uint32_t uvcount:8; uint32_t datasize:20; @@ -449,6 +450,7 @@ uc_resource_type_t *ucv_resource_type_lookup(uc_vm_t *, const char *); uc_value_t *ucv_resource_new(uc_resource_type_t *, void *); uc_value_t *ucv_resource_new_ex(uc_vm_t *, uc_resource_type_t *, void **, size_t, size_t); +uc_value_t *ucv_resource_new_with_proto(uc_vm_t *, uc_resource_type_t *, void **, size_t, size_t, uc_value_t *); void *ucv_resource_data(uc_value_t *uv, const char *); void **ucv_resource_dataptr(uc_value_t *, const char *); uc_value_t *ucv_resource_value_get(uc_value_t *, size_t); @@ -491,6 +493,17 @@ ucv_resource_create_ex(uc_vm_t *vm, const char *type, void **data, size_t uvcoun return ucv_resource_new_ex(vm, t, data, uvcount, datasize); } +static inline uc_value_t * +ucv_resource_create_with_proto(uc_vm_t *vm, const char *type, void **data, size_t uvcount, size_t datasize, uc_value_t *proto) +{ + uc_resource_type_t *t = NULL; + + if (type && (t = ucv_resource_type_lookup(vm, type)) == NULL) + return NULL; + + return ucv_resource_new_with_proto(vm, t, data, uvcount, datasize, proto); +} + static inline bool ucv_resource_is_extended(uc_value_t *uv) { @@ -519,6 +532,28 @@ ucv_resource_persistent_set(uc_value_t *uv, bool persistent) return true; } +static inline bool +ucv_resource_hasproto(uc_value_t *uv) +{ + uc_resource_ext_t *res = (uc_resource_ext_t *)uv; + + return ucv_resource_is_extended(uv) && res->hasproto; +} + +#define UCV_RESOURCE_PROTO_IDX ((size_t)-1) + +static inline uc_value_t * +ucv_resource_proto_get(uc_value_t *uv) +{ + return ucv_resource_value_get(uv, UCV_RESOURCE_PROTO_IDX); +} + +static inline bool +ucv_resource_proto_set(uc_value_t *uv, uc_value_t *proto) +{ + return ucv_resource_value_set(uv, UCV_RESOURCE_PROTO_IDX, proto); +} + uc_value_t *ucv_regexp_new(const char *, bool, bool, bool, char **); uc_value_t *ucv_upvalref_new(size_t); diff --git a/types.c b/types.c index 9265c65e..239ef5fe 100644 --- a/types.c +++ b/types.c @@ -102,7 +102,7 @@ ucv_resource_values(uc_resource_ext_t *res) { void *data; - if (!ucv_resource_is_extended((uc_value_t *)res) || !res->uvcount) + if (!res->uvcount && !res->hasproto) return NULL; data = res + 1; @@ -220,10 +220,11 @@ ucv_gc_mark(uc_value_t *uv) ucv_set_mark(uv); values = ucv_resource_values(res); + if (!values) break; - for (i = 0; i < res->uvcount; i++) + for (i = 0; i < (size_t)res->uvcount + res->hasproto; i++) ucv_gc_mark(values[i]); } @@ -312,19 +313,18 @@ ucv_free(uc_value_t *uv, bool retain) case UC_RESOURCE: if (uv->ext_flag) { uc_resource_ext_t *res = (uc_resource_ext_t *)uv; + void *data = res + 1; uc_value_t **values; values = ucv_resource_values(res); + if (values) { - for (i = 0; i < res->uvcount; i++) + for (i = 0; i < (size_t)res->uvcount + res->hasproto; i++) ucv_put_value(values[i], retain); } - if (res->type && res->type->free) { - void *data = res + 1; - + if (res->type && res->type->free) res->type->free(data); - } ref = &res->ref; } @@ -1298,8 +1298,45 @@ ucv_resource_new_ex(uc_vm_t *vm, uc_resource_type_t *type, void **data, size_t u assert(datasize < (8 << 20)); size = sizeof(*res); + size += datasize = (datasize + 7) & ~7; size += uvcount * sizeof(uc_value_t *); + + res = xalloc(size); + res->header.type = UC_RESOURCE; + res->header.refcount = 1; + res->header.ext_flag = 1; + res->type = type; + res->uvcount = uvcount; + res->datasize = datasize / 8; + res->hasproto = 0; + if (data) + *data = res + 1; + + if (vm && uvcount) { + ucv_ref_tail(&vm->values, &res->ref); + vm->alloc_refs++; + } + + return &res->header; +} + +uc_value_t * +ucv_resource_new_with_proto(uc_vm_t *vm, uc_resource_type_t *type, void **data, size_t uvcount, size_t datasize, uc_value_t *proto) +{ + uc_resource_ext_t *res; + size_t size; + uc_value_t **values; + + assert(uvcount < (1 << 8)); + assert(datasize < (8 << 20)); + + /* If no proto provided, fall back to type->proto (no instance proto slot) */ + if (!proto) + return ucv_resource_new_ex(vm, type, data, uvcount, datasize); + + size = sizeof(*res); size += datasize = (datasize + 7) & ~7; + size += (uvcount + 1) * sizeof(uc_value_t *); res = xalloc(size); res->header.type = UC_RESOURCE; @@ -1308,6 +1345,8 @@ ucv_resource_new_ex(uc_vm_t *vm, uc_resource_type_t *type, void **data, size_t u res->type = type; res->uvcount = uvcount; res->datasize = datasize / 8; + res->hasproto = 1; + if (data) *data = res + 1; @@ -1316,6 +1355,13 @@ ucv_resource_new_ex(uc_vm_t *vm, uc_resource_type_t *type, void **data, size_t u vm->alloc_refs++; } + /* Chain the given proto to type->proto so prototype lookup works correctly */ + if (type) + ucv_prototype_set(proto, ucv_get(type->proto)); + + values = (uc_value_t **)((char *)(res + 1) + datasize); + values[0] = proto; + return &res->header; } @@ -1343,8 +1389,9 @@ ucv_resource_data(uc_value_t *uv, const char *name) if (uv->ext_flag) { uc_resource_ext_t *res = (uc_resource_ext_t *)uv; + void *data = res + 1; - return res + 1; + return data; } else { uc_resource_t *res = (uc_resource_t *)uv; @@ -1370,10 +1417,20 @@ ucv_resource_value_get(uc_value_t *uv, size_t idx) uc_resource_ext_t *res = (uc_resource_ext_t *)uv; uc_value_t **uvdata = ucv_resource_values(res); - if (!uvdata || idx >= res->uvcount) + if (!uvdata) return NULL; - return uvdata[idx]; + if (idx == UCV_RESOURCE_PROTO_IDX) { + if (!res->hasproto) + return NULL; + + return uvdata[0]; + } + + if (idx >= res->uvcount) + return NULL; + + return uvdata[idx + res->hasproto]; } bool @@ -1382,11 +1439,24 @@ ucv_resource_value_set(uc_value_t *uv, size_t idx, uc_value_t *val) uc_resource_ext_t *res = (uc_resource_ext_t *)uv; uc_value_t **uvdata = ucv_resource_values(res); - if (!uvdata || idx >= res->uvcount) - return NULL; + if (!uvdata) + return false; + + if (idx == UCV_RESOURCE_PROTO_IDX) { + if (!res->hasproto) + return false; + + ucv_put(uvdata[0]); + uvdata[0] = val; + + return true; + } + + if (idx >= res->uvcount) + return false; - ucv_put(uvdata[idx]); - uvdata[idx] = val; + ucv_put(uvdata[idx + res->hasproto]); + uvdata[idx + res->hasproto] = val; return true; } @@ -1464,6 +1534,9 @@ ucv_prototype_get(uc_value_t *uv) return object->proto; case UC_RESOURCE: + if (ucv_resource_hasproto(uv)) + return ucv_resource_proto_get(uv); + restype = ucv_resource_type(uv); return restype ? restype->proto : NULL; @@ -1495,6 +1568,26 @@ ucv_prototype_set(uc_value_t *uv, uc_value_t *proto) return true; + case UC_RESOURCE: + if (uv->ext_flag) { + uc_resource_ext_t *res = (uc_resource_ext_t *)uv; + uc_value_t **values; + + if (!res->hasproto) + return false; + + values = ucv_resource_values(res); + + if (!values) + return false; + + values[0] = proto; + + return true; + } + + return false; + default: return false; } From 6584dd250a06a2f91679b6885036b96b67beca9f Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 14 Apr 2026 21:20:41 +0200 Subject: [PATCH 5/6] ci: install libffi on test runners Signed-off-by: Jo-Philipp Wich --- .github/workflows/debian.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 3f5841fc..f9845868 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -16,7 +16,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install devscripts build-essential lintian libjson-c-dev debhelper-compat debhelper cmake + sudo apt install devscripts build-essential lintian libjson-c-dev debhelper-compat debhelper cmake libffi-dev - name: Build package run: | From 848d8a0281cf7f7ffb63f3ce215bfbc9aedd01fc Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Fri, 22 Dec 2023 11:43:43 +0100 Subject: [PATCH 6/6] ffi: add LibFFI-based foreign function interface module Introduce the ffi module, providing seamless integration with C libraries through a LuaJIT-inspired API. The module combines a C declaration parser with libffi-based function calling for ucode/C interop. Features: - C declaration parsing via ffi.cdef() - C data object creation (structs, arrays, primitives) via ffi.ctype() - LibFFI-based function calling with full ABI handling - Function wrapping via ffi.C.wrap() for callable C functions - Dynamic library loading via ffi.dlopen() and ffi.C.dlsym() - Type information queries (sizeof, alignof, offsetof) - Pointer operations (ptr, deref, get, set, slice) - String conversion helpers (ffi.string, cdata.slice) Implementation: - C parser adapted from LuaJIT FFI (lj_cparse.c -> uc_cparse.c) - Type system and conversions ported to ucode VM conventions - LibFFI used exclusively for all call/closure operations - Resource types: "ffi.ctype" and "ffi.clib" - Full JSDoc documentation for API surface Tests: - Full unit tests in tests/ffi/ covering all API functions - Callback support tested with qsort (int/string/struct arrays) - Complex nested structs (sockaddr_in, in_addr) tested - Path-based access and dict initialization tested - Unified test runner (run_tests.sh) for all tests Usage: import * as ffi from 'ffi'; ffi.cdef('size_t strlen(const char *)'); let strlen = ffi.C.wrap('size_t strlen(const char *)'); let len = strlen('hello').get(); // => 5 Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 31 + examples/ffi/http-client.uc | 534 ++ examples/ffi/sqlite3.uc | 176 + lib/ffi/ATTRIBUTION.md | 90 + lib/ffi/NOTICE | 73 + lib/ffi/README.md | 81 + lib/ffi/ffi.c | 5013 +++++++++++++++++ lib/ffi/uc_cconv.c | 1154 ++++ lib/ffi/uc_cconv.h | 124 + lib/ffi/uc_cdata.c | 326 ++ lib/ffi/uc_cdata.h | 117 + lib/ffi/uc_cparse.c | 3020 ++++++++++ lib/ffi/uc_cparse.h | 77 + lib/ffi/uc_ctype.c | 802 +++ lib/ffi/uc_ctype.h | 477 ++ lib/ffi/uc_def.h | 121 + tests/custom/17_lib_ffi/01_cdef | 20 + tests/custom/17_lib_ffi/02_ctype | 25 + tests/custom/17_lib_ffi/03_cast | 26 + tests/custom/17_lib_ffi/04_sizeof | 24 + tests/custom/17_lib_ffi/05_alignof | 22 + tests/custom/17_lib_ffi/06_callbacks | 57 + tests/custom/17_lib_ffi/07_ctype | 35 + tests/custom/17_lib_ffi/08_ptr_deref | 42 + tests/custom/17_lib_ffi/09_get_set | 67 + tests/custom/17_lib_ffi/10_sizeof_alignof | 47 + tests/custom/17_lib_ffi/11_offsetof | 21 + tests/custom/17_lib_ffi/12_length_itemsize | 38 + tests/custom/17_lib_ffi/13_string | 37 + tests/custom/17_lib_ffi/14_wrap | 35 + tests/custom/17_lib_ffi/15_dlopen_dlsym | 52 + tests/custom/17_lib_ffi/16_errno | 27 + tests/custom/17_lib_ffi/17_copy | 20 + tests/custom/17_lib_ffi/18_fill | 18 + tests/custom/17_lib_ffi/19_typeof | 18 + tests/custom/17_lib_ffi/20_complex_structs | 46 + tests/custom/17_lib_ffi/21_path_access | 90 + tests/custom/17_lib_ffi/22_index_ref | 242 + tests/custom/17_lib_ffi/23_builtin_types | 268 + tests/custom/17_lib_ffi/24_ptr_array_args | 121 + .../custom/17_lib_ffi/25_calling_conventions | 23 + tests/custom/17_lib_ffi/26_variadic_args | 51 + tests/custom/17_lib_ffi/27_tostring | 59 + tests/custom/17_lib_ffi/28_dlopen_cdefs | 232 + types.c | 42 +- 45 files changed, 14015 insertions(+), 6 deletions(-) create mode 100644 examples/ffi/http-client.uc create mode 100644 examples/ffi/sqlite3.uc create mode 100644 lib/ffi/ATTRIBUTION.md create mode 100644 lib/ffi/NOTICE create mode 100644 lib/ffi/README.md create mode 100644 lib/ffi/ffi.c create mode 100644 lib/ffi/uc_cconv.c create mode 100644 lib/ffi/uc_cconv.h create mode 100644 lib/ffi/uc_cdata.c create mode 100644 lib/ffi/uc_cdata.h create mode 100644 lib/ffi/uc_cparse.c create mode 100644 lib/ffi/uc_cparse.h create mode 100644 lib/ffi/uc_ctype.c create mode 100644 lib/ffi/uc_ctype.h create mode 100644 lib/ffi/uc_def.h create mode 100644 tests/custom/17_lib_ffi/01_cdef create mode 100644 tests/custom/17_lib_ffi/02_ctype create mode 100644 tests/custom/17_lib_ffi/03_cast create mode 100644 tests/custom/17_lib_ffi/04_sizeof create mode 100644 tests/custom/17_lib_ffi/05_alignof create mode 100644 tests/custom/17_lib_ffi/06_callbacks create mode 100644 tests/custom/17_lib_ffi/07_ctype create mode 100644 tests/custom/17_lib_ffi/08_ptr_deref create mode 100644 tests/custom/17_lib_ffi/09_get_set create mode 100644 tests/custom/17_lib_ffi/10_sizeof_alignof create mode 100644 tests/custom/17_lib_ffi/11_offsetof create mode 100644 tests/custom/17_lib_ffi/12_length_itemsize create mode 100644 tests/custom/17_lib_ffi/13_string create mode 100644 tests/custom/17_lib_ffi/14_wrap create mode 100644 tests/custom/17_lib_ffi/15_dlopen_dlsym create mode 100644 tests/custom/17_lib_ffi/16_errno create mode 100644 tests/custom/17_lib_ffi/17_copy create mode 100644 tests/custom/17_lib_ffi/18_fill create mode 100644 tests/custom/17_lib_ffi/19_typeof create mode 100644 tests/custom/17_lib_ffi/20_complex_structs create mode 100644 tests/custom/17_lib_ffi/21_path_access create mode 100644 tests/custom/17_lib_ffi/22_index_ref create mode 100644 tests/custom/17_lib_ffi/23_builtin_types create mode 100644 tests/custom/17_lib_ffi/24_ptr_array_args create mode 100644 tests/custom/17_lib_ffi/25_calling_conventions create mode 100644 tests/custom/17_lib_ffi/26_variadic_args create mode 100644 tests/custom/17_lib_ffi/27_tostring create mode 100644 tests/custom/17_lib_ffi/28_dlopen_cdefs diff --git a/CMakeLists.txt b/CMakeLists.txt index 0320ca88..a56cfcd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ project(ucode C) add_library(uc_defines INTERFACE) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + include(CheckFunctionExists) include(CheckSymbolExists) include(CheckCSourceCompiles) @@ -76,6 +78,7 @@ find_library(libubus NAMES ubus) find_library(libblobmsg_json NAMES blobmsg_json) find_package(ZLIB) find_library(libmd NAMES libmd.a md) +find_library(libffi NAMES ffi) if(LINUX) find_library(libnl_tiny NAMES nl-tiny) @@ -105,10 +108,15 @@ if(libmd) set(DEFAULT_DIGEST_SUPPORT ON) endif() +if(libffi) + set(DEFAULT_FFI_SUPPORT ON) +endif() + option(DEBUG_SUPPORT "Debug plugin support" ON) option(FS_SUPPORT "Filesystem plugin support" ON) option(IO_SUPPORT "IO plugin support" ON) option(MATH_SUPPORT "Math plugin support" ON) +option(FFI_SUPPORT "FFI module support" ${DEFAULT_FFI_SUPPORT}) option(UBUS_SUPPORT "Ubus plugin support" ${DEFAULT_UBUS_SUPPORT}) option(UCI_SUPPORT "UCI plugin support" ${DEFAULT_UCI_SUPPORT}) option(RTNL_SUPPORT "Route Netlink plugin support" ${DEFAULT_NL_SUPPORT}) @@ -407,6 +415,29 @@ if(DIGEST_SUPPORT) target_link_libraries(digest_lib ${libmd}) endif() +if(FFI_SUPPORT) + pkg_check_modules(LIBFFI REQUIRED libffi) + include_directories(${LIBFFI_INCLUDE_DIRS}) + set(LIBRARIES ${LIBRARIES} ffi_lib) + add_library(ffi_lib MODULE lib/ffi/uc_cparse.c lib/ffi/uc_ctype.c lib/ffi/uc_cconv.c lib/ffi/uc_cdata.c lib/ffi/ffi.c) + set_target_properties(ffi_lib PROPERTIES OUTPUT_NAME ffi PREFIX "") + + set(CMAKE_REQUIRED_INCLUDES ${LIBFFI_INCLUDE_DIRS}) + set(CMAKE_REQUIRED_LIBRARIES ${LIBFFI_LIBRARIES}) + + check_c_source_compiles(" + #include + int main() { return FFI_BAD_ARGTYPE; } + " HAVE_FFI_BAD_ARGTYPE) + + if(HAVE_FFI_BAD_ARGTYPE) + target_compile_definitions(ffi_lib PRIVATE HAVE_FFI_BAD_ARGTYPE) + endif() + + target_link_options(ffi_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) + target_link_libraries(ffi_lib ${LIBFFI_LINK_LIBRARIES}) +endif() + if(UNIT_TESTING) enable_testing() add_compile_definitions( UNIT_TESTING ) diff --git a/examples/ffi/http-client.uc b/examples/ffi/http-client.uc new file mode 100644 index 00000000..21855029 --- /dev/null +++ b/examples/ffi/http-client.uc @@ -0,0 +1,534 @@ +// http-client.uc - HTTP/1.1 client with optional HTTPS support +// Demonstrates FFI with network sockets, DNS resolution, and OpenSSL TLS +// Note: This demo shows the structure but network calls may fail due to network issues + +import * as ffi from 'ffi'; + +// ============================================================================ +// Load OpenSSL if available +// ============================================================================ + +let SSL = null; + +try { + // Load libssl with automatic function wrapping using cdefs + SSL = ffi.dlopen('ssl', false, ` + typedef struct ssl_ctx_st SSL_CTX; + typedef struct ssl_st SSL; + + SSL_CTX *SSL_CTX_new(void *method); + void SSL_CTX_free(SSL_CTX *ctx); + int SSL_CTX_set_verify(SSL_CTX *ctx, int mode, void *callback); + int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *cafile, const char *capath); + + SSL *SSL_new(SSL_CTX *ctx); + void SSL_free(SSL *ssl); + int SSL_set_fd(SSL *ssl, int fd); + int SSL_connect(SSL *ssl); + int SSL_read(SSL *ssl, void *buf, int num); + int SSL_write(SSL *ssl, const void *buf, int num); + int SSL_shutdown(SSL *ssl); + long SSL_get_verify_result(SSL *ssl); + const char *SSL_get_error(SSL *ssl, int ret); + long SSL_ctrl(SSL *ssl, int cmd, long larg, void *parg); + + void *TLS_client_method(void); + void OPENSSL_init_ssl(long opts, const void *settings); + `); + print("OpenSSL loaded successfully\n"); +} catch (e) { + print("Warning: Could not load OpenSSL - HTTPS not available\n"); + SSL = null; +} + +// ============================================================================ +// Load libc with socket functions using dlopen cdefs +// ============================================================================ + +let libc = ffi.dlopen(null, false, ` + typedef int socklen_t; + typedef unsigned short sa_family_t; + typedef unsigned short in_port_t; + + struct in_addr { + unsigned int s_addr; + }; + + struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + char sin_zero[8]; + }; + + struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr_in *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; + }; + + int socket(int domain, int type, int protocol); + int connect(int sockfd, struct sockaddr_in *addr, socklen_t addrlen); + ssize_t send(int sockfd, const char *buf, size_t len, int flags); + ssize_t recv(int sockfd, char *buf, size_t len, int flags); + int close(int fd); + int shutdown(int fd, int how); + int getaddrinfo(const char *node, const char *service, struct addrinfo *hints, struct addrinfo **result); + void freeaddrinfo(struct addrinfo *ai); +`); + +// Constants +const AF_INET = 2; +const SOCK_STREAM = 1; +const IPPROTO_TCP = 6; +const SHUT_RDWR = 2; + +// OpenSSL constants +const SSL_VERIFY_NONE = 0; +const SSL_VERIFY_PEER = 1; + +// ============================================================================ +// HTTPResponse_create - Factory for HTTP response objects +// +// Creates an HTTPResponse object with standard property access and helper +// methods for inspecting the response. This object wraps raw HTTP response +// data into a usable interface. +// +// Parameters: +// status - HTTP status code (e.g. 200, 404, 500) +// statusText - Human-readable status text (e.g. "OK", "Not Found") +// headers - Object mapping header names to values (default: {}) +// body - Response body as a string +// +// Methods available on the returned object: +// ok() - Returns true if status is in 2xx range +// clientError() - Returns true if status is in 4xx range +// serverError() - Returns true if status is 5xx or above +// getHeader(n) - Returns header value, case-insensitive key lookup +// json() - Attempts to parse body as JSON, returns null on failure +// +// Usage: +// let resp = HTTPResponse_create(200, "OK", {"Content-Type": "text/html"}, "") +// if (resp.ok()) { +// print("Headers Content-Type: ", resp.getHeader("content-type"), "\n"); +// } +// ============================================================================ + +function HTTPResponse_create(status, statusText, headers, body) { + return proto({ + status: status, + statusText: statusText, + headers: headers || {}, + body: body || '' + }, { + ok: function() { + return this.status >= 200 && this.status < 300; + }, + + clientError: function() { + return this.status >= 400 && this.status < 500; + }, + + serverError: function() { + return this.status >= 500; + }, + + getHeader: function(name) { + let lowerName = name.toLowerCase(); + for (let key in this.headers) { + if (key.toLowerCase() === lowerName) + return this.headers[key]; + } + return null; + }, + + json: function() { + try { + return json(this.body); + } catch (e) { + return null; + } + } + }); +} + +// ============================================================================ +// SSLConnection_create - Wrapper for SSL/TLS connections +// +// Establishes a TLS session over an existing TCP socket using OpenSSL. +// Sets up certificate verification mode, configures SNI hostname support, +// and performs the SSL handshake. The returned object wraps the SSL state +// and provides transparent read/write methods that can be used in place +// of raw socket I/O. +// +// Parameters: +// sock - TCP socket file descriptor returned by libc.socket() +// host - Server hostname string (used for SNI extension) +// +// Methods available on the returned object: +// read(buf, len) - Read up to len bytes through SSL (like SSL_read) +// write(buf, len) - Write up to len bytes through SSL (like SSL_write) +// shutdown() - Send SSL close alert and free SSL/CTX resources +// close() - Shutdown SSL and close the underlying socket +// +// Usage (within HTTPClient_create after socket connect): +// let conn = SSLConnection_create(sock, "example.com"); +// conn.write(request, len); // sends encrypted data +// conn.read(buf, 4096); // reads decrypted data +// conn.close(); // cleanup +// ============================================================================ + +function SSLConnection_create(sock, host) { + if (!SSL) + die("Error: OpenSSL not available - HTTPS not supported\n"); + + // Get TLS method and create context + let tls_method = SSL.TLS_client_method(); + let ssl_ctx = SSL.SSL_CTX_new(tls_method); + + if (!ssl_ctx) + die("Error: Failed to create SSL context\n"); + + // Set verify mode (disable verification for simplicity - not recommended for production) + SSL.SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, null); + + // Create SSL structure + let ssl = SSL.SSL_new(ssl_ctx); + if (!ssl) { + SSL.SSL_CTX_free(ssl_ctx); + die("Error: Failed to create SSL structure\n"); + } + + // Set the socket file descriptor + if (SSL.SSL_set_fd(ssl, sock) !== 1) { + SSL.SSL_free(ssl); + SSL.SSL_CTX_free(ssl_ctx); + die("Error: Failed to set SSL fd\n"); + } + + // Set SNI hostname (Server Name Indication) + // SSL_CTRL_SET_TLSEXT_HOSTNAME = 55 + const SSL_CTRL_SET_TLSEXT_HOSTNAME = 55; + SSL.SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, 0, host); + + // Perform SSL handshake + let ret = SSL.SSL_connect(ssl); + if (ret !== 1) { + let err = SSL.SSL_get_error(ssl, ret); + SSL.SSL_free(ssl); + SSL.SSL_CTX_free(ssl_ctx); + die("Error: SSL handshake failed: " + ffi.string(err) + "\n"); + } + + return { + ssl: ssl, + ssl_ctx: ssl_ctx, + sock: sock, + + read: function(buf, len) { + return SSL.SSL_read(this.ssl, buf, len); + }, + + write: function(buf, len) { + return SSL.SSL_write(this.ssl, buf, len); + }, + + shutdown: function() { + SSL.SSL_shutdown(this.ssl); + SSL.SSL_free(this.ssl); + SSL.SSL_CTX_free(this.ssl_ctx); + }, + + close: function() { + this.shutdown(); + libc.close(this.sock); + } + }; +} + +// ============================================================================ +// HTTPClient_create - Low-level HTTP/1.1 client object +// +// Creates a raw HTTP client with manual request construction. Manages socket +// lifecycle including DNS resolution via getaddrinfo, TCP connection, and +// optional SSL/TLS upgrade. This is the foundational client used by +// httpGet() but provides full control over request details. +// +// The returned object is a closure over the socket and SSL state, providing +// methods to construct and send HTTP requests. All responses are parsed into +// HTTPResponse objects. +// +// Parameters: +// host - Server hostname (e.g. "example.com") +// port - Server port number, defaults to 80 if omitted +// useSSL - If true and OpenSSL is available, upgrades to HTTPS +// +// Methods available on the returned object: +// get(path, headers) - Send GET request to path with optional extra headers +// post(path, body, headers) - Send POST request with body (auto-sets Content-Type +// and Content-Length headers if not provided) +// request(method, path, body, headers) - Generic method for any HTTP verb +// close() - Shut down socket (and SSL if active) +// +// Usage: +// let client = HTTPClient_create("httpbin.org", 80); +// let resp = client.get("/get"); +// print("Status: ", resp.status, "\nBody: ", resp.body, "\n"); +// client.close(); +// +// let client = HTTPClient_create("example.com", 443, true); +// let resp = client.post("/api/data", '{"key":"value"}'); +// client.close(); +// ============================================================================ + +function HTTPClient_create(host, port, useSSL) { + port = port || 80; + useSSL = useSSL || false; + let sock = libc.socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock < 0) + die("Error: Failed to create socket\n"); + + // Resolve hostname using getaddrinfo + let hints = { + ai_family: AF_INET, + ai_socktype: SOCK_STREAM, + ai_protocol: IPPROTO_TCP, + ai_flags: 0 + }; + + let resPtr = ffi.ctype('struct addrinfo *', null); + let rc = libc.getaddrinfo(host, '' + port, hints, resPtr.ptr()); + if (rc !== 0) { + libc.close(sock); + die("Error: Failed to resolve " + host + ":" + port + "\n"); + } + + // Use index() to get pointer without auto-conversion + let sockaddr = resPtr.index('ai_addr'); + let ai_addrlen = resPtr.get('ai_addrlen'); + rc = libc.connect(sock, sockaddr, ai_addrlen); + + libc.freeaddrinfo(resPtr); + + if (rc < 0) { + libc.close(sock); + die("Error: Failed to connect to " + host + ":" + port + "\n"); + } + + // Upgrade to SSL if requested + let conn = null; + if (useSSL) { + conn = SSLConnection_create(sock, host); + } + + return { + host: host, + port: port, + socket: sock, + ssl: conn, + useSSL: useSSL, + + get: function(path, headers) { + headers = headers || {}; + return this.request("GET", path, null, headers); + }, + + post: function(path, body, headers) { + headers = headers || {}; + headers["Content-Type"] = headers["Content-Type"] || "application/x-www-form-urlencoded"; + headers["Content-Length"] = '' + length(body); + return this.request("POST", path, body, headers); + }, + + request: function(method, path, body, headers) { + // Build request + let request = method + " " + path + " HTTP/1.1\r\n"; + request = request + "Host: " + this.host + "\r\n"; + request = request + "Connection: close\r\n"; + + for (let key in headers) { + request = request + key + ": " + headers[key] + "\r\n"; + } + + if (body) + request = request + body + "\r\n"; + + request = request + "\r\n"; + + // Send request + let sent; + if (this.ssl) { + sent = this.ssl.write(request, length(request)); + } else { + sent = libc.send(this.socket, request, length(request), 0); + } + + if (sent < 0) + die("Error: Failed to send request\n"); + + // Receive response + let chunks = []; + let chunkSize = 4096; + + while (true) { + let chunk = ffi.ctype('char[' + chunkSize + ']'); + let n; + if (this.ssl) { + n = this.ssl.read(chunk.ptr(), chunkSize); + } else { + n = libc.recv(this.socket, chunk.ptr(), chunkSize, 0); + } + + if (n < 0) { + let err = ffi.errno(); + die("Error: read failed: " + err + "\n"); + } + + if (n === 0) + break; + + // Use slice() to convert received bytes to string + push(chunks, chunk.slice(0, n)); + } + + if (length(chunks) === 0) + die("Error: No data received\n"); + + // Parse response + let data = join('', chunks); + + // Parse status: "HTTP/1.1 200 OK" + let status = 0; + let statusText = "OK"; + + let m = match(data, /^HTTP\/1\.[01] (\d\d\d) (.+)\r\n/); + if (m) { + status = int(m[1]); + statusText = m[2]; + } + + // Find body after \r\n\r\n + let crlfcrlf = index(data, "\r\n\r\n"); + let responseBody = ''; + if (crlfcrlf >= 0) { + responseBody = substr(data, crlfcrlf + 4); + } + + let responseHeaders = { "Content-Type": "application/json" }; + + return HTTPResponse_create(status, statusText, responseHeaders, responseBody); + }, + + close: function() { + if (this.ssl) { + this.ssl.close(); + } else if (this.socket !== null) { + libc.shutdown(this.socket, SHUT_RDWR); + libc.close(this.socket); + this.socket = null; + } + } + }; +} + +// ============================================================================ +// Convenience Functions +// ============================================================================ + +// ============================================================================= +// parseUrl - URL string parser +// +// Splits a URL string into its component parts: scheme, host, port, and path. +// Handles standard http/https schemes with optional port specification and path. +// +// Parameters: +// url - Complete URL string (e.g. "https://example.com:8443/api/v1?query") +// +// Returns object with properties: +// host - Server hostname without port +// port - Port number (443 for https, 80 for http, or extracted from URL) +// path - URL path starting with '/' (defaults to '/' if no path) +// scheme - URL scheme as a string ("http" or "https") +// +// Usage: +// let parts = parseUrl("https://api.example.com:8443/users/list"); +// // returns: { host: "api.example.com", port: 8443, path: "/users/list", scheme: "https" } +// let client = HTTPClient_create(parts.host, parts.port, parts.scheme === "https"); +// let resp = client.get(parts.path); +// ============================================================================ + +function parseUrl(url) { + let scheme = "http"; + + // Check for http:// or https:// prefix + let prefix = index(url, "://"); + if (prefix >= 0) { + let beforePrefix = substr(url, 0, prefix); + if (beforePrefix === "https") { + scheme = "https"; + } + url = substr(url, prefix + 3); + } + + let path = '/'; + let slashIdx = index(url, '/'); + if (slashIdx >= 0) { + path = substr(url, slashIdx); + url = substr(url, 0, slashIdx); + } + + let host = url; + let port = scheme === "https" ? 443 : 80; + let colonIdx = index(url, ':'); + if (colonIdx >= 0) { + host = substr(url, 0, colonIdx); + port = int(substr(url, colonIdx + 1)); + } + + return { host: host, path: path, port: port, scheme: scheme }; +} + +function httpGet(url) { + // Quick one-shot HTTP GET using the full HTTPClient pipeline. + // Parses the URL, establishes connection (with optional SSL), sends + // the request, receives the response, and cleans up the connection. + let urlParts = parseUrl(url); + let client = HTTPClient_create(urlParts.host, urlParts.port, urlParts.scheme === "https"); + let response = client.get(urlParts.path); + client.close(); + return response; +} + +// ============================================================================ +// Main +// ============================================================================ + +if (length(ARGV) < 1) { + print("Usage: ucode ", SCRIPT_NAME, " \n"); + print("Example: ucode ", SCRIPT_NAME, " http://httpbin.org/get\n"); + print("Example: ucode ", SCRIPT_NAME, " https://httpbin.org/get\n"); + exit(1); +} + +let url = ARGV[0]; +let urlParts = parseUrl(url); + +print("GET ", url, "\n"); + +try { + let client = HTTPClient_create(urlParts.host, urlParts.port, urlParts.scheme === "https"); + let response = client.get(urlParts.path); + client.close(); + + print("\n--- Response ---\n"); + print(response.body); +} catch (e) { + print("Error: ", e, "\n"); + exit(1); +} diff --git a/examples/ffi/sqlite3.uc b/examples/ffi/sqlite3.uc new file mode 100644 index 00000000..b115c32d --- /dev/null +++ b/examples/ffi/sqlite3.uc @@ -0,0 +1,176 @@ +// sqlite3.uc - SQLite3 FFI wrapper example +// Demonstrates FFI with complex structs, callbacks, and memory management + +import * as ffi from 'ffi'; + +// Constants +const SQLITE_OK = 0; +const SQLITE_ROW = 100; +const SQLITE_DONE = 101; +const SQLITE_ERROR = 1; + +// ============================================================================ +// run - Complete SQLite3 FFI Demo +// +// Demonstrates FFI usage with SQLite3, covering: dynamic library loading, +// struct and pointer types, prepared statements, parameter binding, result +// column extraction, and error handling. Shows how persistent ctype buffers +// are required for string arguments since native ucode string memory may not +// remain valid across native calls. +// +// Steps performed: +// 1. Load sqlite3 library from system paths (tries x86_64, i386, then PATH) +// 2. Query library version info (libversion, libversion_number) +// 3. Open an in-memory database using sqlite3_open() +// 4. Create a users table with CREATE TABLE via sqlite3_exec() +// 5. Insert two rows using prepared statements with parameter binding +// - Uses ffi.string() to create persistent char[N] ctype buffers, then +// passes .ptr() to bind_text() so the memory outlives the call +// 6. Execute SELECT with step/column extraction via prepared statements +// 7. Demonstrate error handling with an invalid query via sqlite3_exec() +// 8. Report error details using sqlite3_errmsg() and sqlite3_errcode() +// 9. Close the database via sqlite3_close() +// +// Usage: +// ucode examples/ffi/sqlite3.uc +// +// Dependencies: libsqlite3 installed on the system +// ============================================================================ + +function run() { + print("=== SQLite3 FFI Demo ===\n"); + + // Load sqlite3 library with cdefs + print("Loading sqlite3 library...\n"); + let sqlite3lib = null; + + let cdefs = ` + typedef struct sqlite3 sqlite3; + typedef struct sqlite3_stmt sqlite3_stmt; + typedef void sqlite3_destructor_type; + + const char *sqlite3_libversion(void); + int sqlite3_libversion_number(void); + int sqlite3_open(const char *, void **); + int sqlite3_close(void *); + int sqlite3_exec(void *, const char *, int, int, int); + int sqlite3_prepare_v2(void *, const char *, int, void **, const char **); + int sqlite3_reset(void *); + int sqlite3_finalize(void *); + int sqlite3_step(void *); + int sqlite3_bind_text(void *, int, const char *, int, int); + int sqlite3_bind_int(void *, int, int); + const unsigned char *sqlite3_column_text(void *, int); + int sqlite3_column_int(void *, int); + double sqlite3_column_double(void *, int); + const char *sqlite3_errmsg(void *); + int sqlite3_errcode(void *); + int sqlite3_changes(void *); + `; + + try { + sqlite3lib = ffi.dlopen('/usr/lib/x86_64-linux-gnu/libsqlite3.so.0', false, cdefs); + } catch (e) { + try { + sqlite3lib = ffi.dlopen('/usr/lib/i386-linux-gnu/libsqlite3.so.0', false, cdefs); + } catch (e2) { + try { + sqlite3lib = ffi.dlopen('sqlite3', false, cdefs); + } catch (e3) { + sqlite3lib = null; + } + } + } + + if (!sqlite3lib) { + print("Could not load sqlite3 library. Install libsqlite3-dev to run this demo.\n"); + print("Skipping demo.\n"); + return; + } + + print("Library loaded successfully.\n\n"); + + // 1. Version info + let version_ptr = sqlite3lib.sqlite3_libversion(); + let version = ffi.string(version_ptr); + print("SQLite version: ", version, "\n"); + print("Version number: ", sqlite3lib.sqlite3_libversion_number(), "\n\n"); + + // 2. Create in-memory database + print("Creating in-memory database...\n"); + let db = ffi.ctype('void *', null); + let rc = sqlite3lib.sqlite3_open(':memory:', db.ptr()); + if (rc !== SQLITE_OK) + die("Failed to open database: error " + rc); + + // 3. Create schema + print("Creating tables...\n"); + let createSQL = "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT)"; + rc = sqlite3lib.sqlite3_exec(db, createSQL, 0, 0, 0); + if (rc !== SQLITE_OK) + die("Failed to create users table"); + + // 4. Insert data + print("Inserting users...\n"); + let insertSQL = "INSERT INTO users (name, email) VALUES (?, ?)"; + let stmtPtr = ffi.ctype('void *', null); + rc = sqlite3lib.sqlite3_prepare_v2(db, insertSQL, -1, stmtPtr.ptr(), null); + if (rc !== SQLITE_OK) + die("Failed to prepare insert"); + let insertStmt = stmtPtr; + + // Insert Alice + let aliceName = ffi.string('Alice'); + let aliceEmail = ffi.string('alice@example.com'); + sqlite3lib.sqlite3_bind_text(insertStmt, 1, aliceName.ptr(), -1, 0); + sqlite3lib.sqlite3_bind_text(insertStmt, 2, aliceEmail.ptr(), -1, 0); + rc = sqlite3lib.sqlite3_step(insertStmt); + if (rc !== SQLITE_DONE) + die("Insert Alice failed"); + + // Insert Bob + sqlite3lib.sqlite3_reset(insertStmt); + let bobName = ffi.string('Bob'); + let bobEmail = ffi.string('bob@example.com'); + sqlite3lib.sqlite3_bind_text(insertStmt, 1, bobName.ptr(), -1, 0); + sqlite3lib.sqlite3_bind_text(insertStmt, 2, bobEmail.ptr(), -1, 0); + rc = sqlite3lib.sqlite3_step(insertStmt); + if (rc !== SQLITE_DONE) + die("Insert Bob failed"); + + sqlite3lib.sqlite3_finalize(insertStmt); + print("Inserted ", sqlite3lib.sqlite3_changes(db), " rows\n\n"); + + // 5. Query data + print("Querying users...\n"); + let selectSQL = "SELECT id, name, email FROM users ORDER BY id"; + rc = sqlite3lib.sqlite3_prepare_v2(db, selectSQL, -1, stmtPtr.ptr(), null); + let selectStmt = stmtPtr; + while (sqlite3lib.sqlite3_step(selectStmt) === SQLITE_ROW) { + let id = sqlite3lib.sqlite3_column_int(selectStmt, 0); + let name_ptr = sqlite3lib.sqlite3_column_text(selectStmt, 1); + let email_ptr = sqlite3lib.sqlite3_column_text(selectStmt, 2); + let name = ffi.string(name_ptr); + let email = ffi.string(email_ptr); + print(" User: ", id, " - ", name, " <", email, ">\n"); + } + sqlite3lib.sqlite3_finalize(selectStmt); + + // 6. Error handling demo + print("\n=== Error Handling Demo ===\n"); + rc = sqlite3lib.sqlite3_exec(db, "SELECT * FROM nonexistent_table", 0, 0, 0); + if (rc !== SQLITE_OK) { + let errmsg_ptr = sqlite3lib.sqlite3_errmsg(db); + let errmsg = ffi.string(errmsg_ptr); + print("Caught error: ", errmsg, "\n"); + print("Error code: ", sqlite3lib.sqlite3_errcode(db), "\n"); + } + + // Clean up + sqlite3lib.sqlite3_close(db); + + print("\n=== Demo Complete ===\n"); +} + +// Run demo +run(); diff --git a/lib/ffi/ATTRIBUTION.md b/lib/ffi/ATTRIBUTION.md new file mode 100644 index 00000000..57bd10fe --- /dev/null +++ b/lib/ffi/ATTRIBUTION.md @@ -0,0 +1,90 @@ +# Attribution and License + +## Original Work + +This project is based on **LuaJIT**, created by Mike Pall. The FFI +(Foreign Function Interface) implementation in LuaJIT is a sophisticated +system for calling C functions and working with C data types from Lua +code. + +## License + +LuaJIT is released under the MIT license. The original LuaJIT license text: + +``` +Copyright (C) 2005-2025 Mike Pall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` + +## Implementation + +The ucode FFI module (currently called `ffi`) is **heavily inspired** +by LuaJIT's FFI implementation. In many cases, the code structure, +algorithms, and even specific implementations have been taken +**verbatim** from LuaJIT's source code, with necessary adaptations +to work with the ucode virtual machine. + +### What Has Been Taken from LuaJIT + +- **C Parser** (`lj_cparse.c`): The frontend for parsing C declarations +- **Type System** (`lj_ctype.c`): The CTState registry and + CType representations +- **Value Conversions** (`lj_cconv.c`): The machinery for converting + between ucode and C values +- **C Data Objects** (`lj_cdata.c`): Allocation and access + to C data objects + +### What Has Been Changed + +- LuaJIT's own call infrastructure (`lj_ccall.c`, `lj_ccallback.c`) + has been replaced with **libffi** exclusively +- LuaJIT's arithmetic operations (`lj_carith.c`) have been removed +- Library loading logic has been consolidated into the main FFI module +- All VM interactions have been adapted to use ucode's API + (`uc_vm_t *`, `uc_vm_raise_exception()`, etc.) +- JIT-specific code and dependencies have been removed + +## Acknowledgment + +Mike Pall and the LuaJIT community created the FFI implementation that +inspired this work. The LuaJIT FFI provided the C parser concept and +code structure that was adapted for ucode. + +This project does not aim to provide native machine code generation or +JIT compilation. It is essentially libffi with the C parser functionality +bolted on top, providing a seamless usage experience similar to LuaJIT's +FFI interface. + +## Transparency + +This project maintains this attribution file to be completely +transparent about its origins. Any code that resembles LuaJIT's +implementation is either: + +1. Directly adapted from LuaJIT's source code (with proper attribution + in comments where applicable) +2. Reimplemented based on the same design principles and algorithms + +All modifications are intended to integrate with ucode and remove +dependencies on LuaJIT-specific infrastructure. + +--- + +**Last updated:** 2026-04-06 \ No newline at end of file diff --git a/lib/ffi/NOTICE b/lib/ffi/NOTICE new file mode 100644 index 00000000..948a9bb4 --- /dev/null +++ b/lib/ffi/NOTICE @@ -0,0 +1,73 @@ +NOTICE - Third Party Attribution + +This module contains code derived from LuaJIT. + +================================================================================ + +LuaJIT FFI Implementation +-------------------------- + +Copyright (C) 2005-2025 Mike Pall + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +================================================================================ + +Files Derived from LuaJIT +------------------------- + +The following files contain derived work from LuaJIT's FFI implementation: + +- uc_cparse.c - C declaration parser (adapted from LuaJIT's lj_cparse.c) +- uc_ctype.c - Type system management (adapted from LuaJIT's lj_ctype.c) +- uc_cconv.c - Value conversion machinery (adapted from LuaJIT's lj_cconv.c) +- uc_cdata.c - C data object handling (adapted from LuaJIT's lj_cdata.c) +- include/uc_cparse.h - C parser header +- include/uc_ctype.h - Type system header +- include/uc_cconv.h - Conversion header +- include/uc_cdata.h - C data header +- include/uc_def.h - Common definitions (adapted from LuaJIT's lj_def.h) + +================================================================================ + +Modifications +------------- + +The LuaJIT FFI code has been adapted for use with the ucode virtual machine. +Key modifications include: + +- LuaJIT's own call infrastructure has been replaced with libffi exclusively +- Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) +- Removed JIT-specific code and dependencies +- Consolidated library loading logic + +================================================================================ + +Additional Notices +------------------ + +This module also depends on: + +- ucode (https://github.com/jow-/ucode) + Copyright (C) 2020-2026 ucode contributors + Licensed under the ISC License + +- libffi (https://github.com/libffi/libffi) + Copyright (C) 1996-2024 Free Software Foundation, Inc. + Licensed under the MIT License diff --git a/lib/ffi/README.md b/lib/ffi/README.md new file mode 100644 index 00000000..b333612a --- /dev/null +++ b/lib/ffi/README.md @@ -0,0 +1,81 @@ +# ffi - LibFFI-based FFI Module for ucode + +A Foreign Function Interface (FFI) module for the ucode scripting language, +providing seamless integration with C libraries through a LuaJIT-inspired API. + +## Features + +- **C Declaration Parsing** - Parse C type declarations at runtime +- **LibFFI Integration** - Call C functions using libffi for all platform ABI handling +- **C Data Objects** - Create and manipulate C structs, arrays, and primitives +- **Function Wrapping** - Wrap C function pointers into callable ucode functions +- **Type Information** - Query sizeof, alignof, and offsetof at runtime + +## Usage + +```javascript +import * as ffi from 'ffi'; + +// Declare C types and functions +ffi.cdef(` + size_t strlen(const char *); + int strcmp(const char *, const char *); +`); + +// Wrap and call C functions +let strlen = ffi.C.wrap('size_t strlen(const char *)'); +let result = strlen("hello").get(); // => 5 + +// Create C data instances +ffi.cdef('struct point { int x; int y; };'); +let p = ffi.ctype('struct point', 10, 20); +print(p.get('x'), p.get('y')); // => 10 20 + +// Query type information +print(ffi.sizeof('int')); // => 4 +print(ffi.alignof('double')); // => 8 + +// Load external libraries +let libc = ffi.dlopen('c'); +let atoi = libc.wrap('int atoi(const char *)'); +print(atoi("42").get()); // => 42 +``` + +## API Reference + +See the inline documentation in [ffi.c](ffi.c) for the complete API reference. + +## Attribution + +This project contains code derived from **LuaJIT**, created by Mike Pall. +The FFI implementation is adapted from LuaJIT's FFI system. + +### Copyright Notice + +- **LuaJIT FFI Code**: Copyright (C) 2005-2025 Mike Pall +- **ucode Integration**: Copyright (C) 2023-2026 Jo-Philipp Wich + +This project is released under the ISC License. See [LICENSE](../../LICENSE) for details. + +For complete attribution information, see: +- [NOTICE](NOTICE) - Third-party dependencies and licensing +- [ATTRIBUTION.md](ATTRIBUTION.md) - Detailed attribution and modifications + +### Key Modifications from LuaJIT + +- LuaJIT's own call infrastructure replaced with **libffi exclusively** +- Adapted VM interactions to use **ucode's API** instead of LuaJIT's +- Removed JIT-specific code and dependencies +- Consolidated library loading logic + +This project adapted the C parser concept from LuaJIT FFI but does not +aim to provide native machine code generation or JIT compilation. It is +essentially libffi with the C parser functionality bolted on top for a +seamless usage experience. + +## Resources + +- ucode Homepage: https://ucode.mein.io/ +- ucode Repository: https://github.com/jow-/ucode +- LuaJIT: https://luajit.org/ +- libffi: https://github.com/libffi/libffi diff --git a/lib/ffi/ffi.c b/lib/ffi/ffi.c new file mode 100644 index 00000000..8e936fc8 --- /dev/null +++ b/lib/ffi/ffi.c @@ -0,0 +1,5013 @@ +/* + * FFI module for ucode - LibFFI-based Foreign Function Interface + * + * Copyright (C) 2005-2025 Mike Pall (LuaJIT FFI implementation) + * Copyright (C) 2023-2026 Jo-Philipp Wich (ucode integration) + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * This module contains derived work from LuaJIT's FFI implementation. + * + * Modifications from LuaJIT: + * - Replaced LuaJIT's own call infrastructure (lj_ccall.c, lj_ccallback.c) with libffi + * - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) + * - Removed JIT-specific code and dependencies + * - Consolidated library loading logic into this module + * + * See NOTICE and ATTRIBUTION.md for complete attribution details. + */ + +/** + * # Foreign Function Interface (FFI) + * + * The `ffi` module provides a foreign function interface for ucode, allowing + * direct interaction with C libraries. It combines a C declaration parser with + * libffi-based function calling to enable seamless interop between ucode and C. + * + * The module can be imported using the wildcard import syntax: + * + * ``` + * import * as ffi from 'ffi'; + * ``` + * + * ## Synopsis + * + * ```javascript + * import * as ffi from 'ffi'; + * + * // 1. Declare C types and functions + * ffi.cdef(` + * struct point { int x; int y; }; + * extern char **environ; + * `); + * + * // 2. Call C functions via the global C namespace + * // Primitive return values are auto-converted to ucode types + * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); + * print(strcmp("hello", "world"), "\n"); // => non-zero (number) + * + * // 3. String return values remain as cdata - use ffi.string() to convert + * let getenv = ffi.C.wrap('char *getenv(char *)'); + * let path_ptr = getenv('PATH'); // Returns char* cdata + * let path_str = ffi.string(path_ptr); // Convert to ucode string + * + * // 4. Create C data instances + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * print(p.get('x'), p.get('y'), "\n"); // => 10 20 + * + * // 5. Access global variables + * print(ffi.C.dlsym('environ').get(0), "\n"); + * + * // 6. Query type information + * print(ffi.sizeof('int'), "\n"); // => 4 + * print(ffi.alignof('double'), "\n"); // => 8 + * print(ffi.offsetof('struct point', 'y'), "\n"); // => 4 + * + * // 7. Load external libraries + * let libz = ffi.dlopen('z'); + * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); + * print(zlibVersion().slice(), "\n"); // => "1.2.11" (or similar) + * + * // Use in callbacks (primitives auto-converted) + * let qsort = ffi.C.wrap('void qsort(void *, size_t, size_t, int (*)(const void *, const void *))'); + * let cmp = ffi.C.wrap('int strcmp(const char *, const char *)'); + * let arr = ffi.ctype('char *[5]', ["zebra", "apple", "banana", "cherry", "date"]); + * // cmp() returns ucode number directly (primitives auto-converted) + * qsort(arr.ptr(), arr.length(), arr.itemsize(), + * (a, b) => cmp(a.deref('const char *'), b.deref('const char *'))); + * ``` + * + * ## Memory Management for char* Return Values + * + * When a wrapped C function returns `char*`, the return value is a **cdata pointer + * object**, not an auto-converted ucode string. This design prevents memory leaks + * and gives you explicit control over memory management. + * + * ### Converting char* to ucode Strings + * + * Use `ffi.string()` or `slice()` to convert a char* cdata to a ucode string: + * + * ```javascript + * let getenv = ffi.C.wrap('char *getenv(char *)'); + * + * let path_ptr = getenv('PATH'); // Returns char* cdata + * let path = ffi.string(path_ptr); // Convert to ucode string + * // or equivalently: + * let path = path_ptr.slice(); // slice() without args = string() + * ``` + * + * **Note**: Both `ffi.string()` and `slice()` create a **copy** of the C string. + * The original C memory remains untouched. + * + * ### Memory Ownership Patterns + * + * #### Pattern 1: C Manages Memory (No Free Required) + * + * Functions like `getenv()`, `strerror()` return pointers to **static/internal + * memory** managed by the C library. Do NOT free these. + * + * ```javascript + * let getenv = ffi.C.wrap('char *getenv(char *)'); + * + * let path_ptr = getenv('PATH'); + * let path = ffi.string(path_ptr); // Copies to ucode string + * + * // path_ptr points to C internal memory - DO NOT free + * // path is a ucode string - managed by ucode GC + * ``` + * + * #### Pattern 2: Caller Must Free (malloc'd Memory) + * + * Functions like `strdup()`, `asprintf()`, `getline()` return **malloc'd memory** + * that you must free to avoid leaks. + * + * ```javascript + * let strdup = ffi.C.wrap('char *strdup(const char *)'); + * let free = ffi.C.wrap('void free(void *)'); + * + * let ptr = strdup("hello"); // malloc'd by strdup + * let str = ffi.string(ptr); // Copies to ucode string + * free(ptr); // NOW you can safely free + * + * // str is safe - it's a ucode string copy + * // ptr memory is freed - no leak + * ``` + * + * **Key**: Keep the cdata pointer until you're done copying, then free it. + * + * #### Pattern 3: Stack-Allocated Buffers + * + * When C writes into a buffer you provide (e.g., `sprintf`), the buffer is + * managed by ucode. + * + * ```javascript + * let sprintf = ffi.C.wrap('int sprintf(char *, const char *, ...)'); + * + * let buf = ffi.ctype('char[256]'); // ucode-managed array + * sprintf(buf, "Hello %s", "World"); + * + * let msg = ffi.string(buf); // Copies to ucode string + * + * // buf is managed by ucode GC - no manual free needed + * ``` + * + * ### Substring Operations with slice() + * + * For char* pointers, `slice()` supports substring extraction: + * + * ```javascript + * let getenv = ffi.C.wrap('char *getenv(char *)'); + * let ptr = getenv('PATH'); + * + * // From start to end (same as ffi.string()) + * let full = ptr.slice(); + * + * // From start index to end + * let rest = ptr.slice(5); + * + * // Specific range + * let part = ptr.slice(0, 10); + * + * // Negative indices (from end) + * let last = ptr.slice(-5); + * ``` + * + * ### Common Functions Reference + * + * | Function | Memory Owner | Pattern | + * |----------|--------------|---------| + * | `getenv()` | C (static) | No free needed | + * | `strerror()` | C (static) | No free needed | + * | `strdup()` | Caller | Must `free()` | + * | `asprintf()` | Caller | Must `free()` | + * | `getline()` | Caller | Must `free()` | + * | `sprintf()` | Caller (buffer) | Buffer managed by you | + * | `strtok()` | C (static) | No free needed | + * + * ### Best Practices + * + * 1. **Always use `ffi.string()` or `slice()`** when you need a ucode string from `char*` + * 2. **Track ownership**: Does C manage the memory or do you? + * 3. **Free after copying**: Call `free(ptr)` only after `ffi.string(ptr)` or `ptr.slice()` + * 4. **Never free static memory**: `getenv()`, `strerror()` return static pointers + * + * ## Limitations + * + * - **No vararg closures**: `wrap()` cannot create closures with variable arguments + * - **Fixed ABI**: Calling convention determined at closure creation time + * - **Platform constraints**: Some architectures have limited support for certain type combinations + * + * ## The `ffi.C` Namespace + * + * `ffi.C` is a special CLib instance representing the process's global symbol table. + * It provides access to standard C library functions without explicit `dlopen()`: + * + * ```javascript + * // These are equivalent: + * let strlen1 = ffi.C.wrap('size_t strlen(const char *)'); + * + * ffi.cdef('size_t strlen(const char *);'); + * let strlen2 = ffi.C.wrap('strlen'); + * ``` + * + * Functions declared via `cdef()` are automatically registered in `ffi.C`'s symbol table. + * + * ## Pointer Arithmetic and Memory Access + * + * C data objects (cdata) provide methods for pointer arithmetic and memory access: + * + * ### Creating Pointers with ptr() + * + * Use `ptr()` to get a pointer to a cdata value: + * + * ```javascript + * let x = ffi.ctype('int', 42); + * let px = x.ptr(); // int* pointer to x + * + * // Pass to C functions expecting pointers + * ffi.cdef('int atoi(const char *)'); + * let num = ffi.ctype('char[4]', "123"); + * let result = atoi(num.ptr()); // => 123 + * ``` + * + * ### Array Indexing with get() and set() + * + * Access array elements using `get(index)` and `set(index, value)`: + * + * ```javascript + * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); + * + * // Read elements + * let first = arr.get(0); // => 10 (ucode number) + * let third = arr.get(2); // => 30 (ucode number) + * + * // Modify elements + * arr.set(0, 100); + * arr.set(4, 200); + * + * // Negative indices work too + * let last = arr.get(-1); // => 200 (ucode number) + * ``` + * + * ### Understanding get() vs index() + * + * **`get()` returns converted ucode values**, while **`index()` returns + * raw cdata references**. This is the key distinction between the two methods. + * + * #### get() - Converted Values + * + * The `get()` method immediately converts C values to ucode types: + * + * ```javascript + * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); + * + * // Returns ucode number directly + * let val1 = arr.get(0); // => 10 (number) + * let val2 = arr.get(2); // => 30 (number) + * + * // Struct field access - returns converted value + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * p.get('x'); // => 10 (number) + * p.get('y'); // => 20 (number) + * ``` + * + * #### index() - Raw cdata References + * + * The `index()` method returns a cdata reference for further manipulation: + * + * ```javascript + * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); + * + * // Returns cdata reference (unconverted) + * let ref1 = arr.index(0); // => cdata (int) + * let ref2 = arr.index(2); // => cdata (int) + * + * // Convert to ucode value explicitly + * ref1.get(); // => 10 (number) + * + * // Or modify through the reference + * arr.index(0).set(100); // Set arr[0] = 100 + * ``` + * + * #### Pointer Arithmetic + * + * Both methods work with pointers, but return different types: + * + * ```javascript + * let ptr = ffi.ctype('int *', arr.ptr()); + * + * // index() returns cdata reference + * ptr.index(0); // => cdata at ptr[0] + * ptr.index(1); // => cdata at ptr[1] + * ptr.index(0).get(); // => 10 (number) + * + * // get() returns converted value + * ptr.get(0); // => 10 (number) + * ptr.get(1); // => 20 (number) + * ``` + * + * #### Path Syntax Support + * + * Both methods support path notation for nested access: + * + * ```javascript + * ffi.cdef('struct rect { struct point min; struct point max; };'); + * let r = ffi.ctype('struct rect', { + * min: {x: 0, y: 0}, + * max: {x: 100, y: 100} + * }); + * + * // get() returns converted value + * r.get('min.x'); // => 0 (number) + * + * // index() returns cdata reference + * r.index('min.x'); // => cdata (int) + * r.index('min.x').get() // => 0 (number) + * ``` + * + * #### Practical Guidance + * + * **Use `get()` when:** + * - You need the value immediately as a ucode type + * - Reading values for computation: `let x = arr.get(i)` + * - Accessing struct fields: `let y = struct.get('field')` + * - Most common use cases + * + * **Use `index()` when:** + * - You need a reference for further manipulation + * - Chaining operations: `arr.index(i).set(val)` + * - Pointer arithmetic with cdata: `ptr.index(n).deref()` + * - Passing references to other C functions + * + * **For writing values:** + * - Use `set()` for both arrays and structs: `arr.set(i, val)`, `struct.set('f', val)` + * + * **For getting pointers (not values):** + * - Use `ptr()` on scalars: `x.ptr()` gives you `int*` + * - Arrays are already pointers: `arr` can be passed to C functions + * + * ### Pointer Arithmetic via get() and index() + * + * Both `get(n)` and `index(n)` work for pointer arithmetic on pointer types: + * + * ```javascript + * ffi.cdef('char *strdup(const char *)'); + * let strdup = ffi.C.wrap('char *strdup(const char *)'); + * + * let ptr = strdup("hello world"); + * + * // get() returns converted value (number for char) + * let first_char = ptr.get(0); // 'h' (number 104) + * let sixth_char = ptr.get(6); // 'w' (number 119) + * + * // index() returns cdata reference + * ptr.index(6); // => cdata (char) + * ptr.index(6).get() // => 119 (number) + * + * // Get substring from offset + * let substring = ffi.string(ptr.get(6)); // "world" + * + * free(ptr); + * ``` + * + * ### Path-Based Access for Nested Structures + * + * Use dot notation and array indexing in paths for complex access: + * + * ```javascript + * ffi.cdef(` + * struct point { int x; int y; }; + * struct rect { struct point min; struct point max; }; + * `); + * + * let r = ffi.ctype('struct rect', { + * min: {x: 0, y: 0}, + * max: {x: 100, y: 100} + * }); + * + * // Nested field access + * r.get('min.x'); // => 0 + * r.set('max.y', 50); + * + * // Array of structs + * ffi.cdef('struct point points[3];'); + * let arr = ffi.ctype('struct point[3]', [ + * {x: 1, y: 2}, + * {x: 3, y: 4}, + * {x: 5, y: 6} + * ]); + * + * arr.get('[1].x'); // => 3 + * arr.set('[2].y', 10); + * ``` + * + * ### Dereferencing Pointers with deref() + * + * Use `deref(type)` to read the value pointed to: + * + * ```javascript + * let x = ffi.ctype('int', 42); + * let px = x.ptr(); + * + * let value = px.deref('int'); // => 42 + * + * // With char* pointers + * ffi.cdef('char *strdup(const char *)'); + * let strdup = ffi.C.wrap('char *strdup(const char *)'); + * + * let ptr = strdup("hello"); + * let first_byte = ptr.deref('char'); // => 'h' (as number 104) + * + * free(ptr); + * ``` + * + * ### Querying Array Properties + * + * Use `length()` and `itemsize()` for array information: + * + * ```javascript + * let arr = ffi.ctype('int[10]'); + * + * arr.length(); // => 10 (number of elements) + * arr.itemsize(); // => 4 (size of each element in bytes) + * + * // Calculate total size + * let total = arr.length() * arr.itemsize(); // => 40 bytes + * ``` + * + * ### Working with Byte Arrays + * + * For `char[]` or `uint8_t[]`, use `slice()` to extract strings: + * + * ```javascript + * let buf = ffi.ctype('char[10]', "hello"); + * + * // Extract as ucode string + * let str = buf.slice(); // => "hello" + * let part = buf.slice(0, 3); // => "hel" + * + * // Or use ffi.string() + * let str2 = ffi.string(buf); // => "hello" + * ``` + * + * ### Complete Example: String Manipulation + * + * ```javascript + * ffi.cdef(` + * char *strdup(const char *); + * void free(void *); + * size_t strlen(const char *); + * `); + * + * let strdup = ffi.C.wrap('char *strdup(const char *)'); + * let free = ffi.C.wrap('void free(void *)'); + * let strlen = ffi.C.wrap('size_t strlen(const char *)'); + * + * // Create a duplicatable string + * let ptr = strdup("hello world"); + * + * // Get length + * let len = strlen(ptr).get(); // => 11 + * + * // Access individual characters via indexing + * let first = ptr.get(0); // 'h' + * let sixth = ptr.get(6); // 'w' + * + * // Extract substrings + * let hello = ptr.slice(0, 5); // "hello" + * let world = ptr.slice(6); // "world" + * + * // Modify in place + * ptr.set(5, 0); // Null-terminate at space + * + * let str = ffi.string(ptr); // => "hello" + * + * // Clean up + * free(ptr); + * ``` + * + * @module ffi + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef HAVE_ULOG +#include +#endif + +#include "uc_cdata.h" +#include "uc_ctype.h" +#include "uc_cparse.h" +#include "uc_cconv.h" + +#define CLNS_INDEX ((1u<= nargs) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type expected, got no value"); + + return 0; + } + + if (ucv_type(arg) == UC_STRING) + { /* Parse an abstract C type declaration. */ + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(arg), + .p = ucv_string_get(arg), + .uv_param = param, + .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT + }; + + if (!uc_cparse(&cp)) + return 0; + + return cp.val.id; + } + else + { + GCcdata *cd = ucv_resource_data(arg, "ffi.ctype"); + + if (!cd) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type expected, got %s", + (narg < nargs) ? ucv_typename(arg) : "no value"); + + return 0; + } + + if (param && param < uc_vector_last(&vm->stack)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "wrong number of type parameters"); + + return 0; + } + //cd = cdataV(o); + return cd->ctypeid == CTID_CTYPEID ? *(CTypeID *)cdataptr(cd) : cd->ctypeid; + } +} + +/* Convert given value to C type. */ +static CTypeID +uv_to_ct(uc_vm_t *vm, uint32_t mode, uc_value_t *uv, GCcdata **cdp) +{ + GCcdata *cd = ucv_resource_data(uv, "ffi.ctype"); + + if (cd && cd->ctypeid == CTID_CTYPEID) { + return *(CTypeID *)cdataptr(cd); + } + else if (cd) { + if (cdp) + *cdp = cd; + + return cd->ctypeid; + } + else if (ucv_type(uv) == UC_STRING) { + /* Parse an abstract C type declaration. */ + CPState cp = { + .uv_vm = vm, + .cts = ctype_cts(vm), + .srcname = ucv_string_get(uv), + .p = ucv_string_get(uv), + .mode = mode + }; + + if (!uc_cparse(&cp)) + return 0; + + return cp.val.id; + } + else { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type or string expected, got %s", + ucv_typename(uv)); + + return 0; + } +} + +/* Convert argument to C pointer. */ +static void *ffi_checkptr(uc_vm_t *vm, size_t nargs, size_t narg, CTypeID id) +{ + uc_value_t *arg = uc_fn_arg(narg); + CTState *cts = ctype_cts(vm); + void *p; + + if (narg >= nargs) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "value expected"); + + return NULL; + } + + uc_cconv_ct_tv(cts, ctype_get(cts, id), (uint8_t *)&p, + arg, CCF_ARG(narg), NULL); + + return p; +} + +/* Get buffer size from cdata object. Returns SIZE_MAX for non-array types. */ +static size_t +ffi_cdata_bufsize(CTState *cts, uc_value_t *uv) +{ + GCcdata *cd = ucv_resource_data(uv, "ffi.ctype"); + CType *ct; + + if (!cd) + return SIZE_MAX; + + ct = ctype_get(cts, cd->ctypeid); + + if (ctype_isptr(ct->info)) + ct = ctype_rawchild(cts, ct); + + if (ctype_isrefarray(ct->info)) + return ct->size; + + return SIZE_MAX; +} + +/* Get redirected or mangled external symbol. */ +static uc_value_t * +clib_extsym(CTState *cts, CType *ct, uc_value_t *name) +{ + if (ct->sib) { + CType *ctf = ctype_get(cts, ct->sib); + + if (ctype_isxattrib(ctf->info, CTA_REDIR)) + return ctf->uv_name; + } + + return name; +} + + +static bool +uc_ctype_requires_ffi_struct(CTState *cts, CTypeID cid) +{ + CType *ct = ctype_get(cts, cid); + CTInfo info = ct->info; + + switch (ctype_type(info)) { + case CT_ARRAY: + switch (cid) { + case CTID_COMPLEX_FLOAT: + case CTID_COMPLEX_DOUBLE: + return false; + } + + /* fall through */ + + case CT_STRUCT: + return true; + } + + return false; +} + +static ffi_type * +uc_ctype_to_ffi_type(CTState *cts, CTypeID cid, ffi_type *st) +{ + CType *ct = ctype_get(cts, cid); + CTInfo info = ct->info; + CTSize size = ct->size; + + switch (ctype_type(info)) { + case CT_NUM: + if (info & CTF_BOOL) + return &ffi_type_uint8; + + if (info & CTF_FP) { + if (size == sizeof(double)) + return &ffi_type_double; + + if (size == sizeof(float)) + return &ffi_type_float; + + return &ffi_type_longdouble; + } + + switch (size) { + case 1: + return (info & CTF_UNSIGNED) ? &ffi_type_uchar : &ffi_type_schar; + + case 2: + return (info & CTF_UNSIGNED) ? &ffi_type_uint16 : &ffi_type_sint16; + + case 4: + return (info & CTF_UNSIGNED) ? &ffi_type_uint32 : &ffi_type_sint32; + + case 8: + return (info & CTF_UNSIGNED) ? &ffi_type_uint64 : &ffi_type_sint64; + } + + assert(0); + return NULL; + + case CT_VOID: + return &ffi_type_void; + + case CT_ENUM: + switch (ctype_cid(info)) { + case CTID_INT32: + return &ffi_type_sint32; + + case CTID_UINT32: + return &ffi_type_uint32; + } + + assert(0); + return NULL; + + case CT_PTR: + return &ffi_type_pointer; + + case CT_ARRAY: + switch (cid) { + case CTID_COMPLEX_FLOAT: + return &ffi_type_complex_float; + + case CTID_COMPLEX_DOUBLE: + return &ffi_type_complex_double; + } + + /* fall through */ + + case CT_STRUCT: + if (!st) + st = xalloc(sizeof(ffi_type)); + + st->type = FFI_TYPE_STRUCT; + st->size = size; + st->alignment = ctype_align(info); + + return st; + } + + return NULL; +} + +static uc_value_t * +clib_dlsym(uc_vm_t *vm, uc_ffi_clib_t *lib, uc_value_t *name) +{ + uc_value_t *sym; + bool exists; + + if (!lib || ucv_type(name) != UC_STRING) + return NULL; + + sym = ucv_object_get(lib->cache, ucv_string_get(name), &exists); + + if (!exists) { + CTState *cts = ctype_cts(vm); + CType *ct; + CTypeID id = uc_ctype_getname(cts, &ct, name, CLNS_INDEX); + + if (!id) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "missing declaration for symbol '%s'", + ucv_string_get(name)); + + return NULL; + } + + if (ctype_isconstval(ct->info)) { + sym = ucv_uint64_new(ct->size); + } + else { + uc_value_t *extname = clib_extsym(cts, ct, name); + + if (!ctype_isfunc(ct->info) && !ctype_isextern(ct->info)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "unexpected ctype %08x for symbol '%s' in clib", + ct->info, ucv_string_get(name)); + + return NULL; + } + +#if UC_TARGET_WINDOWS + DWORD oldwerr = GetLastError(); +#endif + void *p = dlsym(lib->dlh, ucv_string_get(extname)); + +#if UC_TARGET_WINDOWS + SetLastError(oldwerr); +#endif + + if (!p) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "cannot resolve symbol '%s': %s", + ucv_string_get(name), + dlerror()); + + return NULL; + } + + /* dlsym returns a pointer to the symbol (not the value). + * Wrap the symbol's type in a pointer for correct semantics. */ + CTypeID ptr_id = uc_ctype_intern(cts, CTINFO(CT_PTR, CTALIGN_PTR) + id, CTSIZE_PTR); + + sym = uc_cdata_new(vm, ptr_id, CTSIZE_PTR); + *(void **)uc_cdata_dataptr(sym) = p; + } + + ucv_object_add(lib->cache, ucv_string_get(name), sym); + } + + return ucv_get(sym); +} + +static uc_value_t * +uc_ctype_call(uc_vm_t *vm, size_t nargs); + +static uc_value_t * +ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, + uc_value_t *refs); + +/* Path token types */ +typedef enum { + PATH_TOKEN_FIELD, /* Field name: "foo" */ + PATH_TOKEN_INDEX /* Array index: "[0]" */ +} path_token_type; + +typedef struct { + path_token_type type; + union { + char *field; /* Allocated field name (for PATH_TOKEN_FIELD) */ + size_t index; /* For PATH_TOKEN_INDEX */ + }; +} path_token; + +typedef struct { + path_token *entries; + size_t count; +} path_tokens; + +/* Free path tokens */ +static void +path_tokens_free(path_tokens *tokens) +{ + uc_vector_foreach(tokens, tok) + if (tok->type == PATH_TOKEN_FIELD) + free(tok->field); + + uc_vector_clear(tokens); +} + +/* Tokenize a path string like "foo.bar[0].baz" or "foo.0.bar" */ +static bool +path_tokenize(uc_vm_t *vm, uc_value_t *key_uv, path_tokens *tokens) +{ + if (ucv_type(key_uv) != UC_STRING) + return false; + + const char *path = ucv_string_get(key_uv); + size_t len = ucv_string_length(key_uv); + + size_t i = 0; + while (i < len) { + /* Skip dots */ + if (path[i] == '.') { + i++; + continue; + } + + /* Check for array index [n] */ + if (path[i] == '[') { + /* Find closing bracket */ + size_t j = i + 1; + while (j < len && path[j] != ']') + j++; + + if (j >= len) { + uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, + "Invalid path syntax: missing closing bracket"); + path_tokens_free(tokens); + return false; + } + + /* Parse index */ + char *endptr; + size_t idx = strtoul(path + i + 1, &endptr, 10); + if (endptr != path + j) { + uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, + "Invalid path syntax: invalid array index"); + path_tokens_free(tokens); + return false; + } + + /* Add index token */ + uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_INDEX, .index = idx }); + + i = j + 1; + continue; + } + + /* Field name */ + size_t j = i; + while (j < len && path[j] != '.' && path[j] != '[') + j++; + + if (j > i) { + /* Add field token */ + size_t field_len = j - i; + char *field = malloc(field_len + 1); + if (!field) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "Out of memory"); + path_tokens_free(tokens); + return false; + } + memcpy(field, path + i, field_len); + field[field_len] = '\0'; + + uc_vector_push(tokens, (path_token){ .type = PATH_TOKEN_FIELD, .field = field }); + } + + i = j; + } + + return tokens->count > 0; +} + +/* Navigate through a cdata using path tokens. + * Returns final CType, updates pointer to final location. + * Sets *error to true on failure. + */ +static CType * +path_navigate(CTState *cts, GCcdata *start_cd, path_tokens *tokens, + uint8_t **pptr, CType **pct, bool *error) +{ + *error = false; + uint8_t *p = cdataptr(start_cd); + CType *ct = ctype_get(cts, start_cd->ctypeid); + + /* Skip extern and attribute wrappers */ + while (ctype_isextern(ct->info) || ctype_isattrib(ct->info)) + ct = ctype_child(cts, ct); + + /* Handle reference indirection */ + if (ctype_isref(ct->info)) { + p = *(uint8_t **)p; + ct = ctype_child(cts, ct); + } + + uc_vector_foreach(tokens, tok) { + if (tok->type == PATH_TOKEN_FIELD) { + /* String key - struct field access */ + if (!ctype_isstruct(ct->info)) { + *error = true; + return NULL; + } + + CTSize ofs; + CTInfo fqual = 0; + uc_value_t *field_key = ucv_string_new(tok->field); + + CType *fct = uc_ctype_getfieldq(cts, ct, field_key, &ofs, &fqual); + ucv_put(field_key); + + if (!fct) { + *error = true; + return NULL; + } + + p += ofs; + ct = fct; + + /* Get the actual field type */ + ct = ctype_child(cts, ct); + + /* Skip attributes on field */ + while (ctype_isattrib(ct->info)) + ct = ctype_child(cts, ct); + } + else { + /* Integer key - array/pointer access */ + if (!ctype_ispointer(ct->info) && !ctype_isarray(ct->info)) { + *error = true; + return NULL; + } + + CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info)); + if (sz == CTSIZE_INVALID) { + *error = true; + return NULL; + } + + if (ctype_isptr(ct->info)) + p = (uint8_t *)cdata_getptr(p, ct->size); + + /* Check bounds for arrays */ + if (ctype_isarray(ct->info)) { + CTSize arr_len = ct->size / sz; + if (tok->index >= arr_len) { + *error = true; + return NULL; + } + } + + p += tok->index * sz; + ct = ctype_rawchild(cts, ct); + } + } + + *pct = ct; + *pptr = p; + return ct; +} + +static uc_value_t * +clib_wrapped_call(uc_vm_t *vm, size_t nargs) +{ + uc_callframe_t *call = uc_vector_last(&vm->callframes); + uc_cfunction_t *cfn = call->cfunction; + size_t off = ALIGN(sizeof(*cfn) + strlen(cfn->name) + 1); + CTypeID cid = *(CTypeID *)((char *)cfn + off); + void *fp = *(void **)((char *)cfn + off + sizeof(cid)); + + uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR); + *(void **)uc_cdata_dataptr(sym) = fp; + + uc_value_t *ctx = call->ctx; + call->ctx = sym; + + uc_value_t *ret = uc_ctype_call(vm, nargs); + + /* Auto-convert primitive return values for convenience */ + if (ret) { + GCcdata *cd = ucv_resource_data(ret, "ffi.ctype"); + if (cd) { + CTState *cts = ctype_cts(vm); + CType *ct = ctype_get(cts, cd->ctypeid); + + if (ct && !ctype_isfunc(ct->info) && !ctype_isptr(ct->info)) { + /* Primitives: convert to ucode values */ + uc_value_t *converted = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), ct->size, NULL); + ucv_put(ret); + ret = converted; + } + /* Pointers remain as cdata for explicit control: + * - Avoid memory leaks from auto-copying char* + * - Allow explicit ffi.string() conversion when needed + * - Enable pointer arithmetic and dereferencing + */ + } + } + + ucv_put(call->ctx); + call->ctx = ctx; + + return ret; +} + + +/** + * Represents a handle to a loaded shared library. + * + * @class module:ffi.CLib + * @hideconstructor + * + * @see {@link module:ffi#dlopen|dlopen()} + * + * @example + * + * const lib = dlopen(…); + * + * lib.wrap(…); + * lib.dlsym(…); + */ + +/** + * Look up a symbol in the loaded library. + * + * The `dlsym()` method retrieves a symbol (function, variable, or constant) + * from the loaded shared library or global symbol table. + * + * **Input patterns:** + * + * 1. **Bare symbol name**: Look up by symbol name directly. Returns a cdata + * pointer for functions/variables, or a number for constants. + * + * 2. **Full declaration**: Provide a complete declaration string. The symbol + * name is extracted and used for lookup. + * + * @function module:ffi.CLib#dlsym + * + * @param {string} name + * The symbol name or full declaration string. + * + * @returns {?module:ffi.CData|number} + * A cdata pointer for functions/variables, a number for constants, + * or `null` if the symbol cannot be resolved. + * + * @throws {Error} + * Throws an exception if the symbol cannot be found or the declaration + * syntax is invalid. + * + * @example + * // Pattern 1: Bare symbol name + * ffi.cdef('extern char **environ;'); + * let env = ffi.C.dlsym('environ'); + * print(env.get(0), "\n"); + * + * @example + * // Pattern 2: Full declaration + * let getenv = ffi.C.dlsym('char *getenv(char *)'); + * print(ffi.string(getenv.deref('char *'))); + * + * @example + * // Access constant value (returns number) + * ffi.cdef('const int INT_MAX;'); + * let max = ffi.C.dlsym('INT_MAX'); // => number + */ +static uc_value_t * +uc_clib_dlsym(uc_vm_t *vm, size_t nargs) +{ + uc_ffi_clib_t *lib = uc_fn_thisval("ffi.clib"); + uc_value_t *arg = uc_fn_arg(0); + CTState *cts = ctype_cts(vm); + + if (ucv_type(arg) == UC_STRING) { + const char *s = ucv_string_get(arg); + if (strpbrk(s, " \t\n\r")) { + /* Parse the declaration */ + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = s, + .p = s, + .uv_param = NULL, + .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT + }; + + if (!uc_cparse(&cp)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "failed to parse C declaration: '%s'", s); + return NULL; + } + + /* Get the symbol name from the parsed type */ + CType *ct = ctype_raw(cts, cp.val.id); + if (!ct || !ct->uv_name) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "declaration does not define a named symbol"); + return NULL; + } + + /* Use the symbol name for lookup */ + arg = ct->uv_name; + } else { + /* Bare symbol name: first check if there's a declaration */ + CType *ct; + CTypeID id = uc_ctype_getname(cts, &ct, arg, CLNS_INDEX); + if (id) { + /* Declaration exists, use normal clib_dlsym */ + return clib_dlsym(vm, lib, arg); + } else { + /* No declaration: direct dlsym returning void* cdata */ + void *p = dlsym(lib->dlh, s); + if (!p) { + /* Symbol not found */ + return NULL; + } + /* Create a void* cdata */ + CTypeID voidp = CTID_P_VOID; + uc_value_t *cd = uc_cdata_new(vm, voidp, sizeof(void*)); + void **ptr = (void**)uc_cdata_dataptr(cd); + *ptr = p; + return cd; + } + } + } + + return clib_dlsym(vm, lib, arg); +} + +static uc_value_t * +uc_clib_resolve_common(CTState *cts, uc_ffi_clib_t *lib, uc_value_t *cdef, + CType **ctp, GCcdata **cdp) +{ + const char *spec; + size_t spec_len, pos; + GCcdata *cd; + CType *ct; + void *fp; + + if (!lib) + return NULL; + + if (ucv_type(cdef) == UC_STRING) { + spec = ucv_string_get(cdef); + spec_len = ucv_string_length(cdef); + + pos = strcspn(spec, " \t\r\n*[{("); + + if (pos != spec_len) { + CTypeID cid = uv_to_ct(cts->vm, CPARSE_MODE_DIRECT, cdef, NULL); + + if (!cid) + return NULL; + + CType *ct = ctype_raw(cts, cid); + + uc_value_t *sym_name = ct->uv_name; + uc_value_t *sym = clib_dlsym(cts->vm, lib, sym_name); + + if (!sym) { + uc_value_t *repr = uc_ctype_repr(cts->vm, cid, ct->uv_name); + + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "unable to resolve symbol '%s' for declaration '%s'", + ucv_string_get(sym_name), ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + GCcdata *cd = ucv_resource_data(sym, "ffi.ctype"); + assert(cd); + + /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type. + * For function pointers, this gives us the function type which should match the declaration. */ + CType *sym_ct = ctype_get(cts, cd->ctypeid); + if (ctype_isptr(sym_ct->info)) + sym_ct = ctype_rawchild(cts, sym_ct); + + CType *decl_ct = ctype_get(cts, cid); + + if (sym_ct != decl_ct) { + uc_value_t *repr_decl = uc_ctype_repr(cts->vm, cid, ct->uv_name); + uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL); + + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "type mismatch between declaration (%s) and resolved symbol (%s)", + ucv_string_get(repr_decl), + ucv_string_get(repr_sym)); + + ucv_put(repr_decl); + ucv_put(repr_sym); + ucv_put(sym); + + return NULL; + } + + if (ctp) + *ctp = ct; + + if (cdp) + *cdp = cd; + + return sym; + } + else { + CType *ct; + uc_value_t *sym_uv = ucv_string_new(spec); + CTypeID id = uc_ctype_getname(cts, &ct, sym_uv, CLNS_INDEX); + ucv_put(sym_uv); + + if (!id) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "unknown symbol '%s'", spec); + + return NULL; + } + + uc_value_t *sym = clib_dlsym(cts->vm, lib, cdef); + + if (!sym) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "unable to resolve symbol '%s'", spec); + + return NULL; + } + + GCcdata *cd = ucv_resource_data(sym, "ffi.ctype"); + assert(cd); + + /* dlsym returns a pointer to the symbol. Unwrap pointer to get actual type. */ + CType *sym_ct = ctype_get(cts, cd->ctypeid); + if (ctype_isptr(sym_ct->info)) + sym_ct = ctype_rawchild(cts, sym_ct); + + CType *decl_ct = ctype_get(cts, id); + + if (sym_ct != decl_ct) { + uc_value_t *repr_decl = uc_ctype_repr(cts->vm, id, ct->uv_name); + uc_value_t *repr_sym = uc_ctype_repr(cts->vm, cd->ctypeid, NULL); + + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "type mismatch between declared type '%s' and resolved symbol type '%s'", + ucv_string_get(repr_decl), + ucv_string_get(repr_sym)); + + ucv_put(repr_decl); + ucv_put(repr_sym); + ucv_put(sym); + + return NULL; + } + + if (ctp) + *ctp = ct; + + if (cdp) + *cdp = cd; + + return sym; + } + } + + cd = ucv_resource_data(cdef, "ffi.ctype"); + if (!cd) + return NULL; + + /* Handle ctype resources containing function pointers */ + uc_vm_t *vm = cts->vm; + + ct = ctype_get(cts, cd->ctypeid); + + if (!ct) + return NULL; + + /* Unwrap pointer types to get to the actual function type */ + if (ctype_isptr(ct->info)) { + ct = ctype_rawchild(cts, ct); + } + + if (!ct || !ctype_isfunc(ct->info)) { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "attempt to wrap non-function cdata type '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + /* Extract the function pointer from the cdata */ + fp = *(void **)cdataptr(cd); + + if (!fp) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "attempt to wrap NULL function pointer"); + + return NULL; + } + + if (ctp) + *ctp = ct; + + if (cdp) + *cdp = cd; + + /* Create a temporary cdata to hold the function pointer for the caller */ + uc_value_t *sym = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR); + *(void **)uc_cdata_dataptr(sym) = fp; + + return sym; +} + +/** + * Resolve a symbol to a cdata pointer. + * + * The `resolve()` method retrieves a symbol from the loaded library and + * returns a cdata pointer. Unlike `wrap()`, it does not create a callable + * wrapper - it returns the raw pointer for manual handling. + * + * @function module:ffi.CLib#resolve + * + * @param {string|module:ffi.CData} decl + * The function declaration or cdata function pointer. + * + * @returns {?module:ffi.CData} + * A cdata pointer to the symbol, or `null` if resolution fails. + * + * @throws {Error} + * Throws an exception if the symbol cannot be resolved. + * + * @example + * // Resolve function pointer + * ffi.cdef('int strcmp(const char *, const char *)'); + * let ptr = ffi.C.resolve('strcmp'); + * // ptr is a cdata, not callable directly + */ +static uc_value_t * +uc_clib_resolve(uc_vm_t *vm, size_t nargs) +{ + uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib"); + uc_value_t *cdef = uc_fn_arg(0); + CTState *cts = ctype_cts(vm); + + return uc_clib_resolve_common(cts, this, cdef, NULL, NULL); +} + +/** + * Wrap a C function symbol into a callable ucode function. + * + * The `wrap()` method retrieves a function symbol from the library and returns + * a callable wrapper that handles argument marshaling and function invocation + * via libffi. + * + * **Input patterns:** + * + * 1. **Full declaration**: Provide a complete function declaration string. + * The symbol name is extracted automatically. + * + * 2. **Bare symbol**: Provide just the symbol name. Requires that the type + * was previously declared via `cdef()`. + * + * 3. **cdata pointer**: Provide a cdata containing a function pointer + * (e.g., from `dlsym()`). The type must match the cdata's type. + * + * @function module:ffi.CLib#wrap + * + * @param {string|module:ffi.CData} decl + * The function declaration string, bare symbol name, or cdata function pointer. + * + * @returns {?function} + * A callable function wrapper, or `null` if resolution fails. + * + * @throws {Error} + * Throws an exception if the symbol cannot be resolved or is not a function. + * + * @example + * // Pattern 1: Full declaration (no cdef needed) + * let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); + * print(strcmp("hello", "world")); // => number (auto-converted) + * + * @example + * // Pattern 2: Bare symbol (requires cdef) + * ffi.cdef('int strcmp(const char *, const char *)'); + * let strcmp = ffi.C.wrap('strcmp'); + * print(strcmp("hello", "world")); // => number (auto-converted) + * + * @example + * // Pattern 3: cdata function pointer + * ffi.cdef('size_t strlen(const char *)'); + * let strlen_sym = ffi.C.dlsym('strlen'); + * let strlen_fn = ffi.C.wrap(strlen_sym); + * print(strlen_fn("hello")); // => number (auto-converted) + * + * @example + * // Pointer returns remain as cdata for explicit control + * let getenv = ffi.C.wrap('char *getenv(char *)'); + * let path_ptr = getenv('PATH'); // => cdata (char*) + * let path = ffi.string(path_ptr); // Convert to ucode string + */ +static uc_value_t * +uc_clib_wrap(uc_vm_t *vm, size_t nargs) +{ + uc_ffi_clib_t *this = uc_fn_thisval("ffi.clib"); + uc_value_t *cdef = uc_fn_arg(0); + CTState *cts = ctype_cts(vm); + GCcdata *cd; + CType *ct; + + uc_value_t *sym = uc_clib_resolve_common(cts, this, cdef, &ct, &cd); + + if (!sym) + return NULL; + + CTypeID cid = ctype_typeid(cts, ct); + uc_value_t *sym_name = ct->uv_name; + + if (ctype_isptr(ct->info)) + ct = ctype_rawchild(cts, ct); + + if (!ct || !ctype_isfunc(ct->info)) { + uc_value_t *repr_sym = uc_ctype_repr(vm, cd->ctypeid, NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "attempt to wrap non-function value type '%s'", + ucv_string_get(repr_sym)); + + ucv_put(repr_sym); + ucv_put(sym); + + return NULL; + } + + void *fp = *(void **)uc_cdata_dataptr(sym); + uc_cfunction_t *cfn = NULL; + size_t namelen, off; + + namelen = snprintf(NULL, 0, "ffi.%s.%s", + this->name ? this->name : "C", ucv_string_get(sym_name)); + + off = ALIGN(sizeof(*cfn) + namelen + 1); + + cfn = xalloc(off + sizeof(cid) + sizeof(fp)); + cfn->header.type = UC_CFUNCTION; + cfn->cfn = clib_wrapped_call; + + snprintf(cfn->name, namelen + 1, "ffi.%s.%s", + this->name ? this->name : "C", ucv_string_get(sym_name)); + + memcpy((char *)cfn + off, &cid, sizeof(cid)); + memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); + + ucv_put(sym); + + return ucv_get(&cfn->header); +} + + +static size_t +uc_ctype_count_custom_types(CTState *cts, CType *funcspec, CTypeID argtype) +{ + size_t n_custom_types = 0; + + /* check whether return value requires a custom ffi type */ + if (uc_ctype_requires_ffi_struct(cts, ctype_cid(funcspec->info))) + n_custom_types++; + + while (true) { + if (!argtype) + break; + + CType *ctf = ctype_get(cts, argtype); + + assert(ctype_isfield(ctf->info)); + + argtype = ctf->sib; + + if (uc_ctype_requires_ffi_struct(cts, ctype_cid(ctf->info))) + n_custom_types++; + } + + return n_custom_types; +} + +typedef struct { + ffi_closure closure; + ffi_cif cif; + void *codeloc; + uc_vm_t *vm; + uc_value_t *func; + CType *ct; + ffi_type *argtypes[]; +} uc_closure_context_t; + +static void +uc_ctype_closure_cb(ffi_cif *cif, void *ret, void *args[], void *ud) +{ + uc_value_t *uv_arg, *uv_ret = ucv_uint64_new(0); + uc_closure_context_t *context = ud; + uc_exception_type_t ex; + + CTState *cts = ctype_cts(context->vm); + CType *ct_arg, *ct_ret; + CTypeID id_arg; + + uc_vm_stack_push(context->vm, ucv_get(context->func)); + + /* skip attribute entries */ + for (id_arg = context->ct->sib; + id_arg && ctype_isattrib(ctype_get(cts, id_arg)->info); + id_arg = ctype_get(cts, id_arg)->sib) + ; + + for (size_t i = 0; i < cif->nargs; i++) { + uv_arg = NULL; + + assert(id_arg); + ct_arg = ctype_get(cts, id_arg); + + assert(ctype_isfield(ct_arg->info)); + id_arg = ct_arg->sib; + + uc_cconv_tv_ct(cts, ctype_raw(cts, ctype_cid(ct_arg->info)), + ctype_cid(ct_arg->info), &uv_arg, args[i]); + + uc_vm_stack_push(context->vm, uv_arg); + } + + ex = uc_vm_call(context->vm, false, cif->nargs); + + if (ex == EXCEPTION_NONE) + uv_ret = uc_vm_stack_pop(context->vm); + + ct_ret = ctype_get(cts, ctype_cid(context->ct->info)); + + // FIXME: ret value ref + uc_cconv_ct_init(cts, ct_ret, ct_ret->size, ret, &uv_ret, 1, NULL); + ucv_put(uv_ret); +} + +static uc_closure_context_t * +ct_to_closure(uc_vm_t *vm, CTState *cts, CType *ct, uc_value_t *func) +{ + ffi_type *custom_type, **argument_type, *atype, *rtype; + uc_closure_context_t *context; + ffi_abi abi = FFI_DEFAULT_ABI; + size_t context_size; + CTypeID cid_arg; + ffi_status st; + void *codeloc; + + if (!ucv_is_callable(func)) { + char *repr = ucv_to_string(vm, func); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "attempt to bind non-function value '%s'", + repr ? repr : "null"); + + free(repr); + + return NULL; + } + + /* resolve function type */ + if (ct && ctype_isptr(ct->info)) + ct = ctype_rawchild(cts, ct); + + if (!ct || !ctype_isfunc(ct->info)) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_typeid(cts, ct), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "attempt to wrap non-function C type '%s'", + repr ? ucv_string_get(repr) : "NULL"); + + ucv_put(repr); + + return NULL; + } + + /* can't wrap variadic functions */ + if (ct->info & CTF_VARARG) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "wrapping variadic C function types is not supported"); + + return NULL; + } + + /* skip attribute entries */ + for (cid_arg = ct->sib; + cid_arg && ctype_isattrib(ctype_get(cts, cid_arg)->info); + cid_arg = ctype_get(cts, cid_arg)->sib) + ; + + /* compute required size & allocate storage for closure context */ + context_size = sizeof(*context) + + ct->size * sizeof(ffi_type *) + + uc_ctype_count_custom_types(cts, ct, cid_arg) * sizeof(ffi_type); + + context = ffi_closure_alloc(context_size, &codeloc); + + if (!context) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "unable to allocate FFI closure context"); + + return NULL; + } + + context->codeloc = codeloc; + context->func = ucv_get(func); + context->vm = vm; + context->ct = ct; + + argument_type = (ffi_type **)context->argtypes; + custom_type = (ffi_type *)&argument_type[ct->size]; + + /* select ABI */ +#ifdef X86 + switch (ctype_cconv(ct->info)) { + case CTCC_FASTCALL: abi = FFI_FASTCALL; break; + case CTCC_THISCALL: abi = FFI_THISCALL; break; + case CTCC_STDCALL: abi = FFI_STDCALL; break; + case CTCC_CDECL: abi = FFI_MS_CDECL; break; + } +#endif + + if (ctype_isvector(ct->info)) { +#if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64) + if (ct->size != 8 && ct->size != 16) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "vector return type '%s' is not supported", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } +#endif + } + + rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct->info), custom_type); + + if (!rtype) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "don't know how to handle return type '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + goto out; + } + + if (rtype->type == FFI_TYPE_STRUCT) + custom_type++; + + for (size_t i = 0; i < ct->size; i++) { + assert(cid_arg); + + CType *ct_arg = ctype_get(cts, cid_arg); + + assert(ctype_isfield(ct_arg->info)); + + cid_arg = ct_arg->sib; + atype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_arg->info), custom_type); + + if (!atype) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "don't know how to handle argument type '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + goto out; + } + + if (atype->type == FFI_TYPE_STRUCT) + custom_type++; + + *(argument_type++) = atype; + } + + st = ffi_prep_cif(&context->cif, abi, ct->size, rtype, context->argtypes); + + if (st == FFI_OK) { + st = ffi_prep_closure_loc(&context->closure, &context->cif, + uc_ctype_closure_cb, context, + context->codeloc); + } + + switch (st) { + case FFI_BAD_TYPEDEF: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type"); + goto out; + + case FFI_BAD_ABI: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI"); + goto out; + +#ifdef HAVE_FFI_BAD_ARGTYPE + case FFI_BAD_ARGTYPE: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type"); + goto out; +#endif + + case FFI_OK: + return context; + } + +out: + ffi_closure_free(context); + + return NULL; +} + +static uc_value_t * +uc_ctype_call(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + CTState *cts = ctype_cts(vm); + CType *ct = cd ? ctype_get(cts, cd->ctypeid) : NULL; + CTSize sz = CTSIZE_PTR; + + if (ct && ctype_isptr(ct->info)) { + sz = ct->size; + ct = ctype_rawchild(cts, ct); + } + + if (!ct || !ctype_isfunc(ct->info)) { + uc_value_t *repr = cd ? uc_ctype_repr(vm, cd->ctypeid, NULL) : NULL; + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "attempt to call non-function value type '%s'", + repr ? ucv_string_get(repr) : "NULL"); + + ucv_put(repr); + + return NULL; + } + + ffi_cif cif; + ffi_abi abi = FFI_DEFAULT_ABI; + ffi_type *rtype = &ffi_type_void; + + /* select ABI */ +#ifdef X86 + switch (ctype_cconv(ct->info)) { + case CTCC_FASTCALL: abi = FFI_FASTCALL; break; + case CTCC_THISCALL: abi = FFI_THISCALL; break; + case CTCC_STDCALL: abi = FFI_STDCALL; break; + case CTCC_CDECL: abi = FFI_MS_CDECL; break; + } +#endif + + CType *ct_ret = ct; //ctype_child(cts, ct); + + if (ctype_isvector(ct_ret->info)) { +#if defined(X86) || defined(X86_WIN32) || defined(X86_WIN64) + if (ct_ret->size != 8 && ct_ret->size != 16) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "vector return type '%s' is not supported", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } +#endif + } + + rtype = uc_ctype_to_ffi_type(cts, ctype_cid(ct_ret->info), NULL); + + if (!rtype) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "don't know how to handle return type '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + /* skip attribute entries */ + CTypeID fid = ct->sib; + + while (fid) { + CType *ctf = ctype_get(cts, fid); + + if (!ctype_isattrib(ctf->info)) + break; + + fid = ctf->sib; + } + + struct { + size_t count; + ffi_type **entries; + } argtypes = { 0 }; + + struct { + size_t count; + void **entries; + } argvalues = { 0 }; + + struct { + size_t count; + void **entries; + } argmem = { 0 }; + + uc_value_t *rv = NULL; + size_t nfixedargs = 0; + + /* Count fixed arguments from declaration (before ...) */ + CTypeID temp_fid = ct->sib; + while (temp_fid) { + CType *ctf = ctype_get(cts, temp_fid); + if (!ctype_isattrib(ctf->info)) + nfixedargs++; + temp_fid = ctf->sib; + } + + for (size_t i = 0; i < nargs; i++) { + CTypeID did; + bool is_vararg = false; + + if (fid) { + CType *ctf = ctype_get(cts, fid); + + assert(ctype_isfield(ctf->info)); + + fid = ctf->sib; + did = ctype_cid(ctf->info); + } + else if (ct->info & CTF_VARARG) { + is_vararg = true; + /* For variadic args, infer type from ucode value */ + uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i]; + GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype"); + + if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) { + /* cdata argument: use its type directly */ + did = arg_cd->ctypeid; + } + else if (ucv_type(*argp) == UC_STRING) { + /* string -> char* */ + did = CTID_P_CCHAR; + } + else if (ucv_type(*argp) == UC_INTEGER) { + /* integer -> int (promoted from smaller types) */ + did = CTID_INT32; + } + else if (ucv_type(*argp) == UC_DOUBLE) { + /* double stays double (float would be promoted) */ + did = CTID_DOUBLE; + } + else if (ucv_is_callable(*argp)) { + /* callback -> function pointer (void*) */ + did = CTID_P_VOID; + } + else { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "unsupported variadic argument type %s", + ucv_typename(*argp)); + goto out; + } + } + else { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "too many arguments for called function"); + + goto out; + } + + CType *d = ctype_raw(cts, did); + CTSize sz = d->size; + ffi_type *atype = uc_ctype_to_ffi_type(cts, did, NULL); + + /* Apply default argument promotions for variadic arguments */ + if (is_vararg && atype) { + /* Float promotes to double */ + if (atype == &ffi_type_float) { + atype = &ffi_type_double; + sz = sizeof(double); + did = CTID_DOUBLE; + d = ctype_get(cts, did); + } + /* Small integers promote to int */ + else if (atype == &ffi_type_schar || atype == &ffi_type_uchar || + atype == &ffi_type_sint16 || atype == &ffi_type_uint16) { +#if UC_SIZEOF_INT == 4 + atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16) + ? &ffi_type_uint : &ffi_type_sint; + sz = sizeof(int); + did = (atype == &ffi_type_uint) ? CTID_UINT32 : CTID_INT32; +#else + atype = (atype == &ffi_type_uchar || atype == &ffi_type_uint16) + ? &ffi_type_uint64 : &ffi_type_sint64; + sz = sizeof(int64_t); + did = (atype == &ffi_type_uint64) ? CTID_UINT64 : CTID_INT64; +#endif + d = ctype_get(cts, did); + } + } + + if (!atype) { + uc_value_t *repr = uc_ctype_repr(vm, ctype_cid(ct_ret->info), NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "don't know how to handle argument type '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + goto out; + } + + uc_value_t **argp = &vm->stack.entries[vm->stack.count - nargs + i]; + GCcdata *arg_cd = ucv_resource_data(*argp, "ffi.ctype"); + + if (arg_cd && arg_cd->ctypeid != CTID_CTYPEID) { + /* Check if this is an array cdata - if so, wrap pointer in pointer-sized slot */ + CType *arg_ct = ctype_get(cts, arg_cd->ctypeid); + if (ctype_isarray(arg_ct->info)) { + void *memp, *valp; + memp = valp = xalloc(sizeof(void *)); + *(void **)valp = cdataptr(arg_cd); + uc_vector_push(&argmem, memp); + uc_vector_push(&argvalues, valp); + } + else { + uc_vector_push(&argvalues, cdataptr(arg_cd)); + } + } + else if (ctype_isptr(d->info) && ucv_type(*argp) == UC_OBJECT) { + CType *child = ctype_rawchild(cts, d); + if (ctype_isstruct(child->info)) { + void *struct_mem = xalloc(child->size); + uc_cconv_ct_init(cts, child, child->size, struct_mem, argp, 1, NULL); + void *ptr_mem = xalloc(sizeof(void*)); + *(void**)ptr_mem = struct_mem; + uc_vector_push(&argmem, struct_mem); + uc_vector_push(&argmem, ptr_mem); + uc_vector_push(&argvalues, ptr_mem); + } + else { + void *memp, *valp; + memp = valp = xalloc(sz); + uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL); + uc_vector_push(&argmem, memp); + uc_vector_push(&argvalues, valp); + } + } + else { + void *memp, *valp; + + if (ucv_type(*argp) == UC_STRING) { + memp = valp = xalloc(sz); + *(char **)valp = ucv_string_get(*argp); + } + else if (ucv_is_callable(*argp)) { + uc_closure_context_t *cc = ct_to_closure(vm, cts, d, *argp); + + memp = (void *)((uintptr_t)cc | 1u); + valp = &cc->codeloc; + } + else { + memp = valp = xalloc(sz); + uc_cconv_ct_tv(cts, d, valp, *argp, CCF_ARG(i), NULL); + } + + uc_vector_push(&argmem, memp); + uc_vector_push(&argvalues, valp); + } + + uc_vector_push(&argtypes, atype); + } + + if (fid) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "too few arguments for called function"); + + goto out; + } + + ffi_status st; + + if (ct->info & CTF_VARARG) + st = ffi_prep_cif_var(&cif, abi, nfixedargs, argtypes.count, rtype, argtypes.entries); + else + st = ffi_prep_cif(&cif, abi, argtypes.count, rtype, argtypes.entries); + + switch (st) { + case FFI_BAD_TYPEDEF: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI type"); + goto out; + + case FFI_BAD_ABI: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid FFI ABI"); + goto out; + +#ifdef HAVE_FFI_BAD_ARGTYPE + case FFI_BAD_ARGTYPE: + uc_vm_raise_exception(vm, EXCEPTION_TYPE, "invalid variadic argument type"); + goto out; +#endif + + case FFI_OK: + if (rtype != &ffi_type_void) { + CTSize rsz = ctype_get(cts, ctype_cid(ct_ret->info))->size; + + if (rsz < sizeof(ffi_arg) || rsz == CTSIZE_INVALID) + rsz = sizeof(ffi_arg); + + rv = uc_cdata_new(vm, ctype_cid(ct_ret->info), rsz); + } + + ffi_call(&cif, + (void (*)(void))cdata_getptr(cdataptr(cd), sz), + uc_cdata_dataptr(rv), + argvalues.entries); + } + +out: +#ifdef __clang_analyzer__ + /* Clang static analyzer does not understand that rtype is either a static + * ffi_type or a heap-allocated value that is freed here. Pretend to free + * it unconditionally to suppress the false positive memory leak warning. */ + free(rtype); +#else + if (rtype->type == FFI_TYPE_STRUCT) + free(rtype); +#endif + + while (argtypes.count) + if (argtypes.entries[--argtypes.count]->type == FFI_TYPE_STRUCT) + free(argtypes.entries[argtypes.count]); + + while (argmem.count) { + void *ptr = argmem.entries[--argmem.count]; + + if ((uintptr_t)ptr & 1u) { + uc_closure_context_t *cc = + (uc_closure_context_t *)((uintptr_t)ptr & ~(uintptr_t)1u); + + ucv_put(cc->func); + ffi_closure_free(cc); + } + else { + free(ptr); + } + } + + uc_vector_clear(&argvalues); + uc_vector_clear(&argtypes); + uc_vector_clear(&argmem); + + return rv; +} + +static uc_value_t * +uc_ctype_free(uc_vm_t *vm, size_t nargs) +{ + GCcdata **cd = uc_fn_this("ffi.ctype"); + + if (cd) { + if (UC_UNLIKELY(*cd && cdataisv(*cd))) + free(memcdatav(*cd)); + else + free(*cd); + + *cd = NULL; + } + + return NULL; +} + +static uc_value_t * +ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, + uc_value_t *refs); + +static uc_value_t * +ct_to_uv(uc_vm_t *vm, CTState *cts, CTypeID cid, void *cdata, size_t size, + uc_value_t *refs) +{ + CType *ct = ctype_get(cts, cid); + CTInfo info = ct->info; + uc_value_t *s; + + switch (ctype_type(info)) { + case CT_PTR: + switch (ctype_cid(info)) { + case CTID_INT8: + case CTID_UINT8: + if ((info ^ CTF_UCHAR) & CTF_UNSIGNED) + goto generic_ptr; + + /* fall through */ + + case CTID_CCHAR: + /* special optimization case: when retrieving the ucode equivalent value of + a `const char *` pointer, attempt to return a reference to the original + uv string (if any) nstead of constructing a new heap string */ + if (ctype_cid(info) == CTID_CCHAR) { + for (size_t i = 0; i < ucv_array_length(refs); i++) { + uc_string_t *us = (uc_string_t *)ucv_array_get(refs, i); + + if (us->str == *(char **)cdata) { + return ucv_get(&us->header); + } + } + } + + return *(char **)cdata ? ucv_string_new(*(char **)cdata) : NULL; + + default: + generic_ptr: + /* Return pointer value as integer for all pointer types */ + return ucv_uint64_new((uintptr_t)*(void **)cdata); + } + + break; + + case CT_NUM: + if (info & CTF_BOOL) { + return ucv_boolean_new(*(bool *)cdata); + } + else if ((info & CTF_FP)) { + if (size == sizeof(double)) + return ucv_double_new(*(double *)cdata); + else if (size == sizeof(float)) + return ucv_double_new(*(float *)cdata); + } + else if (size == 1) { + if (info & CTF_UNSIGNED) + return ucv_uint64_new(*(uint8_t *)cdata); + else + return ucv_int64_new(*(int8_t *)cdata); + } + else if (size == 2) { + if (info & CTF_UNSIGNED) + return ucv_uint64_new(*(uint16_t *)cdata); + else + return ucv_int64_new(*(int16_t *)cdata); + } + else if (size == 4) { + if (info & CTF_UNSIGNED) + return ucv_uint64_new(*(uint32_t *)cdata); + else + return ucv_int64_new(*(int32_t *)cdata); + } + else if (size == 8) { + if (info & CTF_UNSIGNED) + return ucv_uint64_new(*(uint64_t *)cdata); + else + return ucv_int64_new(*(int64_t *)cdata); + } + + break; + + case CT_ENUM: + /* attempt to return named enum choice name */ + for (CTypeID choice_id = ct->sib; choice_id; ) { + CType *choice_type = ctype_get(cts, choice_id); + + choice_id = choice_type->sib; + + if (!ctype_isconstval(choice_type->info) || !choice_type->uv_name) + continue; + + if (choice_type->size != *(CTSize *)cdata) + continue; + + return ucv_get(choice_type->uv_name); + } + + /* no matching constant name found, return numeric value */ + if (ctype_cid(info) == CTID_UINT32) + return ucv_uint64_new(*(uint32_t *)cdata); + else + return ucv_int64_new(*(int32_t *)cdata); + + break; + + case CT_ARRAY: + if (info & CTF_COMPLEX) { + if (size == 2 * sizeof(float)) { + uc_value_t *a = ucv_array_new_length(vm, 2); + float *f = (float *)cdata; + + ucv_array_set(a, 0, ucv_double_new((double)f[0])); + ucv_array_set(a, 1, ucv_double_new((double)f[1])); + + return a; + } + else if (size == 2 * sizeof(double)) { + uc_value_t *a = ucv_array_new_length(vm, 2); + double *d = (double *)cdata; + + ucv_array_set(a, 0, ucv_double_new(d[0])); + ucv_array_set(a, 1, ucv_double_new(d[1])); + + return a; + } + } + else { + CType *elem_type = ctype_rawchild(cts, ct); + CTSize elem_size = elem_type->size; + uc_value_t *a = ucv_array_new_length(vm, size / elem_size); + + for (size_t off = 0; off < size; off += elem_size) + ucv_array_push(a, + ct_to_uv(vm, cts, ctype_typeid(cts, elem_type), + (char *)cdata + off, elem_size, refs)); + + return a; + } + + break; + + case CT_STRUCT: + s = ucv_object_new(vm); + + for (CTypeID field_id = ct->sib; field_id; ) { + CType *field_type = ctype_get(cts, field_id); + + field_id = field_type->sib; + + if (ctype_isfield(field_type->info) || ctype_isbitfield(field_type->info)) { + if (!field_type->uv_name) + continue; + + ucv_object_add(s, ucv_string_get(field_type->uv_name), + ct_to_uv(vm, cts, ctype_cid(field_type->info), + (char *)cdata + field_type->size, + ctype_rawchild(cts, field_type)->size, refs)); + } + } + + return s; + } + + return NULL; +} + + +/** + * Read a value from a C data object. + * + * The `get()` method reads values from cdata objects and returns them + * converted to ucode types. It supports: + * + * - **Scalar values**: `int.get()` returns the scalar value directly + * - **Array indexing**: `arr.get(n)` returns element at position n + * - **Struct fields**: `struct.get('field')` returns field value + * - **Path notation**: `struct.get('nested.field[0]')` for deep access + * + * For arrays and struct fields, `get()` behaves identically to `index()`. + * Use `get()` as the primary method for reading values due to its + * descriptive name. + * + * @function module:ffi.CData#get + * + * @param {string|number} [key] + * The field name, array index, or path to read. Omit for scalar types + * to get the value directly. + * + * @returns {*} + * The value at the specified location, converted to a ucode type. + * For structs without a key, returns an object with all field values. + * + * @throws {Error} + * Throws an exception if the key is invalid for the type. + * + * @example + * // Read scalar value (no key needed) + * let x = ffi.ctype('int', 42); + * x.get(); // => 42 (number) + * + * @example + * // Read struct field + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * p.get('x'); // => 10 (number) + * p.get('y'); // => 20 (number) + * + * @example + * // Read entire struct as object + * p.get(); // => {x: 10, y: 20} (ucode object) + * + * @example + * // Read array element + * let arr = ffi.ctype('int[5]', [1, 2, 3, 4, 5]); + * arr.get(0); // => 1 (number) + * arr.get(4); // => 5 (number) + * + * @example + * // Path notation for nested access + * ffi.cdef('struct rect { struct point min; struct point max; };'); + * let r = ffi.ctype('struct rect', { + * min: {x: 0, y: 0}, + * max: {x: 100, y: 100} + * }); + * r.get('min.x'); // => 0 + * r.get('max.y'); // => 100 + * + * @see {@link module:ffi.CData#index|index()} - Equivalent for array/field access + * @see {@link module:ffi.CData#set|set()} - Write values to cdata + */ +static uc_value_t * +uc_ctype_get(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + CTState *cts = ctype_cts(vm); + CTInfo qual = 0; + uint8_t *p; + CType *ct; + CTSize sz; + + if (!cd) + return NULL; + + ct = ctype_get(cts, cd->ctypeid); + sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; + p = cdataptr(cd); + + /* Handle reference types: dereference to get actual data pointer */ + if (ctype_isref(ct->info)) { + p = *(uint8_t **)p; + ct = ctype_get(cts, ctype_cid(ct->info)); + if (!ct) + return NULL; + sz = ct->size; + if (sz == CTSIZE_INVALID) + return NULL; + } + + if (nargs == 0 && ctype_isstruct(ct->info)) { + return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz, + cd->refs); + } + + if (nargs) { + uc_value_t *key = uc_fn_arg(0); + + /* Check for path syntax (contains '.' or '[') */ + bool is_path = false; + if (ucv_type(key) == UC_STRING) { + const char *s = ucv_string_get(key); + if (strpbrk(s, ".[")) + is_path = true; + } + + if (is_path) { + /* Use path parsing for nested access */ + path_tokens tokens = {0}; + bool error = false; + + if (!path_tokenize(vm, key, &tokens)) + return NULL; + + ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); + path_tokens_free(&tokens); + + if (error || !ct) { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + char *keystr = ucv_to_string(vm, key); + + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Invalid path '%s' for type '%s'", + keystr, ucv_string_get(repr)); + + ucv_put(repr); + free(keystr); + + return NULL; + } + + /* path_navigate already returns the final type */ + sz = ct->size; + } + else { + /* Use original uc_cdata_index for single-level access */ + ct = uc_cdata_index(cts, cd, key, &p, &qual); + + if (!ct) + return NULL; + + if (qual & 1) { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + char *keystr = ucv_to_string(vm, key); + + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Invalid index '%s' for type '%s' given", + keystr, ucv_string_get(repr)); + + ucv_put(repr); + free(keystr); + + return NULL; + } + + ct = ctype_child(cts, ct); + sz = ct->size; + } + } + + return ct_to_uv(vm, cts, ctype_typeid(cts, ct), p, sz, + cd->refs); +} + +/** + * Write a value to a C data object. + * + * The `set()` method writes a value to a cdata. For structs, it can write + * individual fields by name. For arrays, it can write elements by index. + * + * @function module:ffi.CData#set + * + * @param {string|number} key + * The field name or array index to write. + * + * @param {*} value + * The value to write. Will be converted to the appropriate C type. + * + * @returns {undefined} + * Returns `undefined`. + * + * @throws {Error} + * Throws an exception if the key is invalid or the value cannot be converted. + * + * @example + * // Write scalar value + * let x = ffi.ctype('int'); + * x.set(42); + * + * @example + * // Write struct field + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point'); + * p.set('x', 10); + * p.set('y', 20); + * + * @example + * // Write array element + * let arr = ffi.ctype('int[5]'); + * arr.set(0, 100); + * arr.set(4, 200); + */ +static uc_value_t * +uc_ctype_set(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + CTState *cts = ctype_cts(vm); + CTInfo qual = 0; + uint8_t *p; + CType *ct; + + if (!cd) + return NULL; + + ct = ctype_get(cts, cd->ctypeid); + p = cdataptr(cd); + + /* Handle reference types: dereference to get actual data pointer */ + if (ctype_isref(ct->info)) { + p = *(uint8_t **)p; + ct = ctype_get(cts, ctype_cid(ct->info)); + if (!ct) + return NULL; + } + + if (nargs > 1) { + uc_value_t *key = uc_fn_arg(0); + uc_value_t *val = uc_fn_arg(1); + + /* Check for path syntax (contains '.' or '[') */ + bool is_path = false; + if (ucv_type(key) == UC_STRING) { + const char *s = ucv_string_get(key); + if (strpbrk(s, ".[")) + is_path = true; + } + + if (is_path) { + /* Use path parsing for nested access */ + path_tokens tokens = {0}; + bool error = false; + + if (!path_tokenize(vm, key, &tokens)) + return NULL; + + ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); + path_tokens_free(&tokens); + + if (error || !ct) { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + char *keystr = ucv_to_string(vm, key); + + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Invalid path '%s' for type '%s'", + keystr, ucv_string_get(repr)); + + ucv_put(repr); + free(keystr); + + return NULL; + } + + /* path_navigate already returns the final type, no need to call ctype_child */ + } + else { + /* Use original uc_cdata_index for single-level access */ + ct = uc_cdata_index(cts, cd, key, &p, &qual); + + if (!ct) + return NULL; + + if (qual & 1) { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + char *keystr = ucv_to_string(vm, key); + + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Invalid index '%s' for type '%s' given", + keystr, ucv_string_get(repr)); + + ucv_put(repr); + free(keystr); + + return NULL; + } + + ct = ctype_child(cts, ct); + } + + /* Convert and store the value */ + uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL); + } + else if (nargs == 1) { + /* No key: set the value directly (for reference types or scalar cdata) */ + uc_value_t *val = uc_fn_arg(0); + uc_cconv_ct_tv(cts, ct, p, val, CCF_ARG(0), NULL); + } + + return NULL; +} + +/** + * Get a pointer to a C data object. + * + * The `ptr()` method returns a pointer cdata pointing to the memory of the + * current cdata. This is useful for passing to C functions that expect + * pointers. + * + * @function module:ffi.CData#ptr + * + * @returns {module:ffi.CData} + * A pointer cdata pointing to this cdata's memory. + * + * @example + * // Get pointer to scalar + * let x = ffi.ctype('int', 42); + * let px = x.ptr(); // int* pointer + * + * @example + * // Pass to C function expecting pointer + * ffi.cdef('void memset(void *, int, size_t)'); + * let buf = ffi.ctype('char[10]'); + * ffi.C.wrap('void memset(void *, int, size_t)')(buf.ptr(), 0, 10); + */ +static uc_value_t * +uc_ctype_ptr(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + + if (!cd) + return NULL; + + uc_value_t *pres = uc_cdata_new(vm, CTID_P_VOID, CTSIZE_PTR); + *(void **)uc_cdata_dataptr(pres) = cdataptr(cd); + + return pres; +} + +/** + * Access array elements, struct fields, or perform pointer arithmetic. + * + * The `index()` method returns **raw cdata references** to the accessed + * location, without converting to ucode types. This allows further + * manipulation, pointer arithmetic, or explicit conversion. + * + * Supports: + * + * - **Array indexing**: `arr.index(n)` returns cdata reference to element + * - **Struct fields**: `struct.index('field')` returns cdata reference + * - **Pointer arithmetic**: `ptr.index(n)` returns cdata at *(ptr + n) + * - **Path notation**: `struct.index('nested.field[0]')` for deep access + * + * **Key difference from `get()`**: `index()` returns raw cdata (unconverted), + * while `get()` returns converted ucode values. + * + * @function module:ffi.CData#index + * + * @param {string|number} key + * The array index, field name, or path to access. + * + * @returns {module:ffi.CData} + * A cdata reference to the value at the specified location (unconverted). + * Call `.get()` on the result to convert to a ucode value. + * + * @throws {Error} + * Throws an exception if the key is invalid for the type. + * + * @example + * // Array indexing - returns cdata, not number + * let arr = ffi.ctype('int[5]', [10, 20, 30, 40, 50]); + * arr.index(0); // => cdata (int) + * arr.index(0).get() // => 10 (number) + * + * @example + * // Struct field access - returns cdata reference + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * p.index('x'); // => cdata (int) + * p.index('x').get() // => 10 (number) + * + * @example + * // Pointer arithmetic - returns cdata at offset + * let ptr = ffi.ctype('int *', arr.ptr()); + * ptr.index(0); // => cdata (int) at ptr[0] + * ptr.index(2); // => cdata (int) at ptr[2] + * ptr.index(2).get() // => 30 (number) + * + * @example + * // Chaining - modify through index() + * arr.index(0).set(100); // Set arr[0] = 100 + * + * @see {@link module:ffi.CData#get|get()} - Returns converted ucode values + * @see {@link module:ffi.CData#ptr|ptr()} - Get a pointer, not a value + */ +static uc_value_t * +uc_ctype_index(uc_vm_t *vm, size_t nargs) +{ + CTState *cts = ctype_cts(vm); + CTInfo qual = 0; + uint8_t *p; + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + uc_value_t *key = uc_fn_arg(0); + + if (!cd) + return NULL; + + /* Check for path syntax (contains '.' or '[') */ + bool is_path = false; + if (ucv_type(key) == UC_STRING) { + const char *s = ucv_string_get(key); + if (strpbrk(s, ".[")) + is_path = true; + } + + CType *ct = ctype_get(cts, cd->ctypeid); + + /* Handle reference types: dereference to get actual type */ + if (ctype_isref(ct->info)) { + p = *(uint8_t **)cdataptr(cd); + ct = ctype_get(cts, ctype_cid(ct->info)); + if (!ct) + return NULL; + } + else { + p = cdataptr(cd); + } + + if (is_path) { + /* Use path parsing for nested access */ + path_tokens tokens = {0}; + bool error = false; + + if (!path_tokenize(vm, key, &tokens)) { + path_tokens_free(&tokens); + return NULL; + } + + ct = path_navigate(cts, cd, &tokens, &p, &ct, &error); + path_tokens_free(&tokens); + + if (error || !ct) + return NULL; + } + else { + /* Handle integer key for pointer/array indexing */ + uc_type_t ut = ucv_type(key); + bool is_integer_key = (ut == UC_INTEGER || ut == UC_DOUBLE); + + if (is_integer_key && (ctype_ispointer(ct->info) || ctype_isarray(ct->info))) { + /* Pointer/array indexing: ptr[index] or arr[index] */ + ptrdiff_t idx; + if (ut == UC_INTEGER) + idx = (ptrdiff_t)ucv_int64_get(key); + else + idx = (ptrdiff_t)ucv_double_get(key); + + CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info)); + if (sz == CTSIZE_INVALID) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "size of C type is unknown or too large"); + return NULL; + } + + /* Get the pointer value (for ptr types) or use data directly (for arrays) */ + if (ctype_isptr(ct->info)) + p = (uint8_t *)cdata_getptr(p, ct->size); + + /* Get element type */ + CType *elt = ctype_rawchild(cts, ct); + + /* Calculate offset */ + p = p + idx * (int32_t)sz; + + /* Return raw cdata reference (unconverted) */ + CTypeID elt_id = ctype_typeid(cts, elt); + CType *elt_ct = ctype_get(cts, elt_id); + + /* For pointer element types, we need to store the pointer VALUE, not the address */ + if (ctype_isptr(elt_ct->info)) { + /* Read the pointer value from p and create a cdata containing it */ + void *ptrval = cdata_getptr(p, elt_ct->size); + uc_value_t *res = uc_cdata_new(vm, elt_id, elt_ct->size); + *(void **)uc_cdata_dataptr(res) = ptrval; + return res; + } + + return uc_cdata_newref(vm, p, elt_id); + } + else { + /* Use original uc_cdata_index for single-level access */ + ct = uc_cdata_index(cts, cd, key, &p, &qual); + + if (!ct) + return NULL; + + if (qual & 1) + return NULL; + + /* Get the raw child type to avoid qualifier issues, + but preserve pointer types */ + if (!ctype_ispointer(ct->info)) + ct = ctype_rawchild(cts, ct); + + /* For pointer fields, create a cdata containing the pointer value */ + if (ctype_ispointer(ct->info)) { + void *ptrval = cdata_getptr(p, ct->size); + uc_value_t *res = uc_cdata_new(vm, ctype_typeid(cts, ct), CTSIZE_PTR); + *(void **)uc_cdata_dataptr(res) = ptrval; + return res; + } + } + } + + /* Return raw cdata reference (unconverted) */ + return uc_cdata_newref(vm, p, ctype_typeid(cts, ct)); +} + +/** + * Read the value pointed to by a pointer cdata. + * + * The `deref()` method dereferences a pointer cdata and reads the value + * at the pointed-to address. The target type can be specified explicitly + * or inferred from the pointer type. + * + * @function module:ffi.CData#deref + * + * @param {string} [type] + * The C type to read. If omitted, the pointer's element type is used. + * + * @returns {*} + * The value at the pointer address, converted to a ucode type. + * + * @throws {Error} + * Throws an exception if the pointer is NULL or the type is invalid. + * + * @example + * // Dereference int pointer + * let x = ffi.ctype('int', 42); + * let px = x.ptr(); + * print(px.deref('int')); // => 42 + * + * @example + * // Read first byte of char* + * ffi.cdef('char *strdup(const char *)'); + * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello"); + * print(ptr.deref('char')); // => 104 (ASCII for 'h') + * ptr.deref(); // Also works, uses pointer's element type + */ +static uc_value_t * +uc_ctype_deref(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + + if (!cd) + return NULL; + + CTState *cts = ctype_cts(vm); + CType *ct = ctype_get(cts, cd->ctypeid); + + CTypeID ctid; + uint8_t *p = NULL; + + /* Skip extern and attribute wrappers to get the actual type */ + while (ctype_isextern(ct->info) || ctype_isattrib(ct->info)) + ct = ctype_child(cts, ct); + + if (ctype_isptr(ct->info)) { + ctid = nargs + ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, + uc_fn_arg(0), NULL) + : ctype_cid(ct->info); + + if (!ctid) + return NULL; + + p = *(uint8_t **)cdataptr(cd); + + if (!p) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Attempt to dereference a NULL pointer"); + + return NULL; + } + } + else if (ctype_isref(ct->info)) { + /* Reference: dereference to get actual data pointer */ + ctid = nargs + ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, + uc_fn_arg(0), NULL) + : ctype_cid(ct->info); + + if (!ctid) + return NULL; + + p = *(uint8_t **)cdataptr(cd); + + if (!p) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Attempt to dereference a NULL reference"); + + return NULL; + } + } + else if (ctype_isrefarray(ct->info)) { + /* Array: dereference returns first element */ + ctid = nargs + ? uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, + uc_fn_arg(0), NULL) + : ctype_cid(ct->info); + + if (!ctid) + return NULL; + + p = (uint8_t *)cdataptr(cd); + + if (!p) { + uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, + "Attempt to dereference empty array"); + + return NULL; + } + } + else { + uc_value_t *repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Attempt to dereference non-pointer type %s", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + CType *ctt = ctype_raw(cts, ctid); + + if (ctt->size == CTSIZE_INVALID) { + uc_value_t *repr = uc_ctype_repr(vm, ctid, NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type '%s' has unknown storage size", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + uc_value_t *rv = NULL; + + uc_cconv_tv_ct(cts, ctt, ctid, &rv, p); + + return rv; +} + +static CTSize +uc_ctype_sizeof_common(CTState *cts, uc_value_t *uv, uc_value_t *nelem) +{ + GCcdata *cd = NULL; + CTypeID id; + CTSize sz; + CType *ct; + + id = uv_to_ct(cts->vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, + uv, &cd); + + if (!id) + return CTSIZE_INVALID; + + if (UC_UNLIKELY(cd && cdataisv(cd))) { + ct = uc_ctype_rawref(cts, id); + if (ctype_isarray(ct->info)) { + CType *child = ctype_rawchild(cts, ct); + return cdatavlen(cd) * child->size; + } + return cdatavlen(cd) * ct->size; + } + + ct = uc_ctype_rawref(cts, id); + + if (ctype_isvltype(ct->info)) { + // FIXME: transaprently handle cdata (ffi_checkint()) + if (ucv_type(nelem) != UC_INTEGER) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "integer argument expected, got %s", + ucv_typename(nelem)); + + return CTSIZE_INVALID; + } + + sz = uc_ctype_vlsize(cts, ct, (CTSize)ucv_int64_get(nelem)); + } + else { + sz = ctype_hassize(ct->info) ? ct->size : CTSIZE_INVALID; + } + + return sz; +} + +/** + * Get the size of a C data object in bytes. + * + * The `size()` method returns the total size in bytes of the cdata. For + * arrays, this is the total size including all elements. + * + * @function module:ffi.CData#size + * + * @returns {number} + * The size of the cdata in bytes. + * + * @example + * // Get size of struct + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point'); + * print(p.size()); // => 8 (on typical systems) + * + * @example + * // Get size of array + * let arr = ffi.ctype('int[10]'); + * print(arr.size()); // => 40 (10 * sizeof(int)) + */ +static uc_value_t * +uc_ctype_sizeof(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *this = _uc_fn_this_res(vm); + CTSize sz = uc_ctype_sizeof_common(ctype_cts(vm), this, uc_fn_arg(0)); + + return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL; +} + +/** + * Get the number of elements in an array cdata. + * + * The `length()` method returns the number of elements in an array. + * For non-array types, returns `null`. + * + * @function module:ffi.CData#length + * + * @returns {?number} + * The number of elements in the array, or `null` if not an array. + * + * @example + * // Get array length + * let arr = ffi.ctype('int[10]'); + * print(arr.length()); // => 10 + * + * @example + * // Works with initialized arrays + * let arr2 = ffi.ctype('char[5]', "hello"); + * print(arr2.length()); // => 5 + */ +static uc_value_t * +uc_ctype_length(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *this = _uc_fn_this_res(vm); + CTState *cts = ctype_cts(vm); + CTSize sz = uc_ctype_sizeof_common(cts, this, uc_fn_arg(0)); + + if (sz == CTSIZE_INVALID) + return NULL; + + GCcdata *cd = ucv_resource_data(this, "ffi.ctype"); + CType *ct = ctype_raw(cts, cd->ctypeid); + + if (!ctype_isarray(ct->info)) + return NULL; + + CTSize item_sz = ctype_rawchild(cts, ct)->size; + + return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(sz / item_sz) : NULL; +} + +/** + * Get the size of an array element or struct field in bytes. + * + * The `itemsize()` method returns the size in bytes of each element in an + * array, or the size of a specified struct field. + * + * @function module:ffi.CData#itemsize + * + * @param {string} [fieldname] + * For struct types, the field name to get the size of. + * + * @returns {number} + * The size of each array element or the struct field in bytes. + * + * @example + * // Get array element size + * let arr = ffi.ctype('int[10]'); + * print(arr.itemsize()); // => 4 (sizeof(int)) + * + * @example + * // Get struct field size + * ffi.cdef('struct foo { char a; int b; double c; };'); + * let f = ffi.ctype('struct foo'); + * print(f.itemsize('b')); // => 4 (size of int field) + */ +static uc_value_t * +uc_ctype_itemsize(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *this = _uc_fn_this_res(vm); + GCcdata *cd = NULL; + CTypeID id = uv_to_ct(vm, CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT, + this, &cd); + + if (!id) + return NULL; + + CTState *cts = ctype_cts(vm); + CTInfo info = ctype_raw(cts, id)->info; + CTSize item_sz = CTSIZE_INVALID; + + if (ctype_isstruct(info)) { + uc_value_t *key = uc_fn_arg(0); + + if (ucv_type(key) != UC_STRING) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Expecting field name for struct type, got %s", + nargs ? ucv_typename(key) : "no argument"); + + return NULL; + } + + CTSize ofs; + CType *fct; + + fct = uc_ctype_getfieldq(cts, ctype_raw(cts, id), key, &ofs, NULL); + + if (fct) + item_sz = ctype_rawchild(cts, fct)->size; + } + else if (ctype_isarray(info)) { + item_sz = ctype_rawchild(cts, ctype_get(cts, id))->size; + } + + return (item_sz != CTSIZE_INVALID) ? ucv_uint64_new(item_sz) : NULL; +} + +/** + * Extract a substring from a char* or char[] cdata. + * + * The `slice()` method extracts a substring from a character pointer or + * array. For char* pointers, it reads until the null terminator by default. + * For char[] arrays, it uses the array length. + * + * @function module:ffi.CData#slice + * + * @param {number} [start=0] + * The starting index (0-based). Negative values count from the end. + * + * @param {number} [end] + * The ending index (exclusive). If omitted, uses the end of the string/array. + * + * @returns {string} + * The extracted substring. + * + * @throws {Error} + * Throws an exception if called without arguments on non-char* pointer types. + * + * @example + * // Extract from char* pointer + * ffi.cdef('char *strdup(const char *)'); + * let ptr = ffi.C.wrap('char *strdup(const char *)')("hello world"); + * print(ptr.slice()); // => "hello world" + * print(ptr.slice(6)); // => "world" + * print(ptr.slice(0, 5)); // => "hello" + * + * @example + * // Extract from char[] array + * let buf = ffi.ctype('char[10]', "hello"); + * print(buf.slice()); // => "hello" + * print(buf.slice(0, 3)); // => "hel" + */ +static uc_value_t * +uc_ctype_slice(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + + if (!cd) + return NULL; + + CTState *cts = ctype_cts(vm); + CType *ct = ctype_get(cts, cd->ctypeid); + CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; + uint8_t *p = cdataptr(cd); + + /* Check if this is a char* pointer type */ + bool is_charptr = false; + uint8_t *charptr_data = p; + size_t charptr_len = sz; + + if (ctype_isptr(ct->info)) { + CType *child = ctype_rawchild(cts, ct); + /* Unwrap REF pointers to get the actual pointed-to type */ + if (ctype_isptr(child->info)) { + /* This is a pointer to pointer - check if inner points to char */ + CType *inner = ctype_rawchild(cts, child); + if (ctype_type(inner->info) == CT_NUM && inner->size == 1) { + is_charptr = true; + charptr_data = *(uint8_t **)p; + if (charptr_data) + charptr_len = strlen((char *)charptr_data); + else + charptr_len = 0; + } + } + else if (ctype_type(child->info) == CT_NUM && child->size == 1) { + is_charptr = true; + charptr_data = *(uint8_t **)p; + if (charptr_data) + charptr_len = strlen((char *)charptr_data); + else + charptr_len = 0; + } + } + + /* No arguments: treat as string() for char* pointers */ + if (nargs == 0) { + if (is_charptr) { + /* char* pointer: read null-terminated string */ + if (!charptr_data) + return ucv_string_new(""); + return ucv_string_new((char *)charptr_data); + } + /* For non-char* pointers, require explicit indices */ + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "slice() without arguments only supported for char* pointers"); + return NULL; + } + + int64_t start_i = ucv_int64_get(uc_fn_arg(0)); + size_t start; + size_t end; + + if (start_i < 0) + start = (is_charptr ? charptr_len : sz) + start_i; + else + start = (size_t)start_i; + + if (nargs >= 2) { + int64_t end_i = ucv_int64_get(uc_fn_arg(1)); + if (end_i < 0) + end = (is_charptr ? charptr_len : sz) + end_i; + else + end = (size_t)end_i; + } + else { + end = is_charptr ? charptr_len : sz; + } + + /* Clamp to valid range */ + size_t max_len = is_charptr ? charptr_len : sz; + if (start > max_len) + start = max_len; + if (end > max_len) + end = max_len; + if (start > end) + start = end; + + size_t len = end - start; + + if (len == 0) + return ucv_string_new_length("", 0); + + return ucv_string_new_length((char *)charptr_data + start, len); +} + +static uc_value_t * +uc_ctype_tostring(uc_vm_t *vm, size_t nargs) +{ + GCcdata *cd = uc_fn_thisval("ffi.ctype"); + + if (!cd) + return NULL; + + CTState *cts = ctype_cts(vm); + CType *ct = ctype_get(cts, cd->ctypeid); + uc_value_t *type_repr = uc_ctype_repr(vm, cd->ctypeid, NULL); + uc_stringbuf_t *sb = ucv_stringbuf_new(); + + ucv_stringbuf_addstr(sb, ucv_string_get(type_repr), ucv_string_length(type_repr)); + ucv_put(type_repr); + + /* Skip qualifiers and attributes to get the actual type */ + while (ctype_isattrib(ct->info) || ctype_isref(ct->info)) + ct = ctype_child(cts, ct); + + /* Format value based on type */ + switch (ctype_type(ct->info)) { + case CT_NUM: + case CT_ENUM: + { + uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), + cdataisv(cd) ? cdatavlen(cd) : ct->size, + cd->refs); + if (val) { + char *str = ucv_to_string(vm, val); + if (str) { + ucv_stringbuf_addstr(sb, ": ", 2); + ucv_stringbuf_addstr(sb, str, strlen(str)); + free(str); + } + ucv_put(val); + } + } + break; + + case CT_ARRAY: + { + CTSize clen = ct->size; + CType *ctt = ctype_rawchild(cts, ct); + + /* Complex number: show as re+imI */ + if (ct->info & CTF_COMPLEX) + { + uc_value_t *val = uc_ctype_repr_complex(cdataptr(cd), + cdataisv(cd) ? cdatavlen(cd) : ct->size); + if (val) { + ucv_stringbuf_addstr(sb, ": ", 2); + ucv_stringbuf_addstr(sb, ucv_string_get(val), ucv_string_length(val)); + ucv_put(val); + } + } + /* String array: show contents */ + else if (ctt->size == 1 && (ctt->info & CTF_UNSIGNED) == 0) + { + char *str = (char *)cdataptr(cd); + ucv_stringbuf_addstr(sb, ": \"", 3); + for (char *p = str; *p && (p - str) < 128; p++) { + if (*p == '"') + ucv_stringbuf_addstr(sb, "\\\"", 2); + else if (*p == '\\') + ucv_stringbuf_addstr(sb, "\\\\", 2); + else if (*p == '\n') + ucv_stringbuf_addstr(sb, "\\n", 2); + else if (*p == '\r') + ucv_stringbuf_addstr(sb, "\\r", 2); + else if (*p == '\t') + ucv_stringbuf_addstr(sb, "\\t", 2); + else if (*p >= 32 && *p < 127) + ucv_stringbuf_addstr(sb, p, 1); + else + ucv_stringbuf_printf(sb, "\\x%02x", (unsigned char)*p); + } + ucv_stringbuf_addstr(sb, "\"", 1); + } + else if (clen != CTSIZE_INVALID && ctt->size > 0) + { + ucv_stringbuf_printf(sb, " (len=%zu)", clen / ctt->size); + } + } + break; + + case CT_PTR: + { + void *ptr = *(void **)cdataptr(cd); + if (ptr) + ucv_stringbuf_printf(sb, " @ %p", ptr); + else + ucv_stringbuf_addstr(sb, ": NULL", 6); + } + break; + + case CT_STRUCT: + { + CTSize sz = cdataisv(cd) ? cdatavlen(cd) : ct->size; + uc_value_t *val = ct_to_uv(vm, cts, cd->ctypeid, cdataptr(cd), sz, cd->refs); + if (val) { + char *str = ucv_to_string(vm, val); + if (str) { + ucv_stringbuf_addstr(sb, ": ", 2); + ucv_stringbuf_addstr(sb, str, strlen(str)); + free(str); + } + ucv_put(val); + } + } + break; + + case CT_VOID: + ucv_stringbuf_addstr(sb, ": void", 6); + break; + } + + return ucv_stringbuf_finish(sb); +} + + +/** + * Represents a C data object holding a value of a C type. + * + * @class module:ffi.CData + * @hideconstructor + * + * @see {@link module:ffi#ctype|ctype()} + * + * @example + * + * const val = ctype(…); + * + * val.get(); + * val.set(…); + * val.ptr(); + * val.index(…); + * val.deref(…); + * val.size(); + * val.length(); + * val.itemsize(…); + * val.slice(…); + */ + +/** + * Create a C data instance. + * + * The `ctype()` function creates a new C data object (cdata) of the specified + * type. It can be called with optional initializer values that will be used + * to initialize the object. + * + * **Usage patterns:** + * + * 1. **Without initializer**: Creates an uninitialized cdata of the given type. + * For pointer types, the pointer is set to NULL. + * + * 2. **With initializer**: Creates and initializes a cdata. The initializer + * values depend on the type: + * - Scalar types: single value (number, boolean) + * - Structs: positional arguments for each field or a ucode object + * - Arrays: individual element values or a string for char arrays + * + * ```javascript + * // Primitive type + * let x = ffi.ctype('int', 42); + * print(x.get()); // => 42 + * + * // Struct type with positional arguments + * ffi.cdef('struct point { int x; int y; };'); + * let p1 = ffi.ctype('struct point', 10, 20); + * print(p1.get('x'), p1.get('y')); // => 10 20 + * + * // Struct type with object initializer + * let p2 = ffi.ctype('struct point', { x: 30, y: 40 }); + * print(p2.get('x'), p2.get('y')); // => 30 40 + * + * // Nested struct with object initializer + * ffi.cdef('struct rect { struct point tl; struct point br; };'); + * let r = ffi.ctype('struct rect', { + * tl: { x: 0, y: 0 }, + * br: { x: 100, y: 200 } + * }); + * let tl = r.get('tl'); + * print(tl.get('x'), tl.get('y')); // => 0 0 + * + * // Array type + * let arr = ffi.ctype('int[3]', 1, 2, 3); + * print(arr.get(0), arr.get(1), arr.get(2)); // => 1 2 3 + * + * // Char array from string + * let buf = ffi.ctype('char[10]', 'hello'); + * print(buf.deref()); // => "hello" + * + * // Pointer type (uninitialized) + * let ptr = ffi.ctype('void *'); + * ``` + * + * @function module:ffi#ctype + * + * @param {string} type + * A C type declaration string. Can be a basic type, struct name, array type, + * pointer type, etc. The type must have been declared via `cdef()` first. + * + * @param {...*} [init] + * Optional initializer values. + * + * @returns {?module:ffi.CData} + * A cdata of the specified type, or `null` if the type cannot be + * parsed or has invalid size. For `typeof()` without initializer, + * returns a CTypeID handle cdata. + * + * @throws {Error} + * Throws an exception if the type declaration is invalid or wrong number + * of initializers provided. + * + * @example + * // Create integer + * let x = ffi.ctype('int', 42); + * print(x.get()); + * + * @example + * // Create struct + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * print(p.get('x')); + * + * @example + * // Create array + * let arr = ffi.ctype('double[5]', 1.1, 2.2, 3.3, 4.4, 5.5); + * print(arr.length()); + */ +static uc_value_t * +uc_ffi_ctype(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *spec = uc_fn_arg(0); + CTState *cts = ctype_cts(vm); + uc_value_t *res; + + if (ucv_type(spec) != UC_STRING) + return NULL; + + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(spec), + .p = ucv_string_get(spec), + .uv_param = NULL, + .mode = CPARSE_MODE_ABSTRACT | CPARSE_MODE_NOIMPLICIT + }; + + if (!uc_cparse(&cp)) + return NULL; + + /* initializer values provided... */ + if (nargs > 1) { + size_t init_arg_off = 1; + CTSize sz; + CType *ct = ctype_raw(cts, cp.val.id); + CTInfo info = uc_ctype_info(cts, cp.val.id, &sz); + uc_value_t *refs = NULL; + + if (info & CTF_VLA) { + CTSize vla_sz = ucv_uint64_get(uc_fn_arg(1)); + + if (errno) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "invalid size argument provided"); + + return NULL; + } + + init_arg_off++; + sz = uc_ctype_vlsize(cts, ct, vla_sz); + } + + if (sz == CTSIZE_INVALID) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type has invalid size"); + + return NULL; + } + + res = uc_cdata_newx(vm, cp.val.id, sz, info); + + /* Special handling: char array initialized with a string */ + if (ctype_isarray(info) && ctype_isinteger(ctype_child(cts, ct)->info) && + nargs - init_arg_off == 1 && ucv_type(vm->stack.entries[vm->stack.count - nargs + init_arg_off]) == UC_STRING) { + /* Convert string to array of char values */ + uc_value_t *str = vm->stack.entries[vm->stack.count - nargs + init_arg_off]; + const char *s = ucv_string_get(str); + size_t len = strlen(s); + CType *child = ctype_child(cts, ct); + CTSize elem_sz = child->size; + GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype"); + uint8_t *data = (uint8_t *)cdataptr(cd_tmp); + + /* Copy string including null terminator if array is large enough */ + for (size_t i = 0; i < len && i * elem_sz < sz; i++) { + if (elem_sz == 1) { + data[i] = s[i]; + } else { + /* For wider character types (e.g., char16_t) - truncate for now */ + data[i * elem_sz] = s[i]; + } + } + /* Null-terminate if there's space */ + if (len < sz / elem_sz) { + if (elem_sz == 1) { + data[len] = '\0'; + } else { + data[len * elem_sz] = '\0'; + } + } + } else { + if (nargs - init_arg_off > 0) { + GCcdata *cd_tmp = ucv_resource_data(res, "ffi.ctype"); + uint8_t *data = (uint8_t *)cdataptr(cd_tmp); + uc_cconv_ct_init(cts, ct, sz, data, + &vm->stack.entries[vm->stack.count - nargs + init_arg_off], + nargs - init_arg_off, &refs); + } + } + + GCcdata *cd = ucv_resource_data(res, "ffi.ctype"); + cd->refs = refs; + } + else { + CTSize sz; + CTInfo info = uc_ctype_info(cts, cp.val.id, &sz); + + if (sz == CTSIZE_INVALID) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "C type has invalid size"); + + return NULL; + } + + res = uc_cdata_newx(vm, cp.val.id, sz, info); + } + + return res; +} + +/** + * Declare C types and functions. + * + * The `cdef()` function parses C declaration strings and registers the types + * with the FFI system. This is required before using types with `ctype()`, + * wrapping functions with `wrap()`, or resolving symbols with `dlsym()`. + * + * Multiple declarations can be provided in a single call, separated by + * semicolons. The parser supports most C declaration syntax including: + * + * - Basic types (`int`, `char`, `float`, `double`, etc.) + * - Type modifiers (`const`, `volatile`, `unsigned`, `signed`) + * - Pointers and arrays (`int *`, `char **`, `int[10]`) + * - Structs and unions (`struct foo { ... }`, `union bar { ... }`) + * - Enums (`enum baz { ... }`) + * - Function declarations (`int foo(int, char *)`) + * - Typedefs (`typedef ...`) + * - Extern declarations (`extern int var;`) + * + * ```javascript + * // Declare a struct type + * ffi.cdef('struct point { int x; int y; };'); + * + * // Declare a function + * ffi.cdef('int strcmp(const char *, const char *);'); + * + * // Declare multiple items + * ffi.cdef(` + * typedef unsigned int uint32_t; + * struct sockaddr { + * sa_family_t sa_family; + * char sa_data[14]; + * }; + * extern char **environ; + * `); + * ``` + * + * After declaring types, you can create instances with `ctype()`, wrap + * functions with `wrap()`, or access global variables with `dlsym()`. + * + * @function module:ffi#cdef + * + * @param {string} spec + * A C declaration string or multiple declarations separated by semicolons. + * + * @returns {module:ffi.CData} + * A cdata holding the CTypeID handle for the last declared type. + * + * @throws {Error} + * Throws an exception if the declaration syntax is invalid. + * + * @example + * // Declare struct and create instance + * ffi.cdef('struct point { int x; int y; };'); + * let p = ffi.ctype('struct point', 10, 20); + * print(p.get('x'), p.get('y')); + * + * @example + * // Declare function and wrap it + * ffi.cdef('size_t strlen(const char *);'); + * let strlen = ffi.C.wrap('size_t strlen(const char *)'); + * print(strlen("hello").get()); + */ +static uc_value_t * +uc_ffi_cdef(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *spec = uc_fn_arg(0); + CTState *cts = ctype_cts(vm); + + if (ucv_type(spec) != UC_STRING) + return NULL; + + if (!vm->callframes.count) + return NULL; + + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(spec), + .p = ucv_string_get(spec), + .uv_param = &vm->stack.entries[uc_vector_last(&vm->callframes)->stackframe + 2], + .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT + }; + + if (!uc_cparse(&cp)) + return NULL; + + uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4); + *(CTypeID *)uc_cdata_dataptr(res) = cp.val.id; + + return res; +} + +/** + * Get the CTypeID for a C type. + * + * The `typeof()` function returns a CTypeID handle for the specified type. + * This is useful for storing type references or passing to other FFI functions. + * + * @function module:ffi#typeof + * + * @param {string} type + * The C type declaration. + * + * @returns {module:ffi.CData} + * A cdata holding the CTypeID handle (an integer type ID). + * + * @throws {Error} + * Throws an exception if the type declaration is invalid. + * + * @example + * // Get type ID for struct + * ffi.cdef('struct point { int x; int y; };'); + * let point_type = ffi.typeof('struct point'); + * + * @example + * // Get type ID for function pointer + * ffi.cdef('int callback(int, char *);'); + * let cb_type = ffi.typeof('int (*)(int, char *)'); + */ +static uc_value_t * +uc_ffi_typeof(uc_vm_t *vm, size_t nargs) +{ + CTState *cts = ctype_cts(vm); + CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); + + uc_value_t *res = uc_cdata_new(vm, CTID_CTYPEID, 4); + + *(CTypeID *)uc_cdata_dataptr(res) = id; + + return res; +} + +/** + * Get the size of a C type in bytes. + * + * The `sizeof()` function returns the size in bytes of a C type or cdata + * expression. For variable-length arrays, an element count can be provided. + * + * @function module:ffi#sizeof + * + * @param {string|module:ffi.CData} type + * The C type declaration or cdata expression to measure. + * + * @param {number} [nelem] + * For variable-length arrays, the number of elements. + * + * @returns {?number} + * The size in bytes, or `null` if the size is unknown. + * + * @throws {Error} + * Throws an exception if the type is invalid or nelem is required but missing. + * + * @example + * // Get size of primitive types + * print(ffi.sizeof('int')); // => 4 + * print(ffi.sizeof('double')); // => 8 + * + * @example + * // Get size of struct + * ffi.cdef('struct point { int x; int y; };'); + * print(ffi.sizeof('struct point')); // => 8 + * + * @example + * // Get size of VLA with element count + * ffi.cdef('int vla[];'); + * print(ffi.sizeof('int[]', 10)); // => 40 (10 * sizeof(int)) + */ +static uc_value_t * +uc_ffi_sizeof(uc_vm_t *vm, size_t nargs) +{ + CTSize sz; + + sz = uc_ctype_sizeof_common(ctype_cts(vm), uc_fn_arg(0), uc_fn_arg(1)); + + return (sz != CTSIZE_INVALID) ? ucv_uint64_new(sz) : NULL; +} + +/** + * Get the alignment requirement of a C type in bytes. + * + * The `alignof()` function returns the minimum alignment requirement in bytes + * for a C type. This is useful for understanding structure padding and memory + * layout. + * + * @function module:ffi#alignof + * + * @param {string} type + * The C type declaration. + * + * @returns {number} + * The alignment requirement in bytes (typically a power of 2). + * + * @throws {Error} + * Throws an exception if the type is invalid. + * + * @example + * // Get alignment of primitive types + * print(ffi.alignof('int')); // => 4 + * print(ffi.alignof('double')); // => 8 + * + * @example + * // Get alignment of struct + * ffi.cdef('struct foo { char a; int b; };'); + * print(ffi.alignof('struct foo')); // => 4 (alignment of int member) + */ +static uc_value_t * +uc_ffi_alignof(uc_vm_t *vm, size_t nargs) +{ + CTState *cts = ctype_cts(vm); + CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); + + CTSize sz; + CTInfo info = uc_ctype_info_raw(cts, id, &sz); + + return ucv_uint64_new(1 << ctype_align(info)); +} + +/** + * Get the offset of a struct field in bytes. + * + * The `offsetof()` function returns the byte offset of a field within a struct. + * For bitfields, the bit position and bit size are returned in an array passed + * as the third argument. + * + * @function module:ffi#offsetof + * + * @param {string} type + * The struct type declaration. + * + * @param {string} field + * The field name to get the offset of. + * + * @param {array} [bitpos] + * Optional array to receive [bit_position, bit_size] for bitfield members. + * + * @returns {?number} + * The byte offset of the field, or `null` if the field doesn't exist. + * + * @throws {Error} + * Throws an exception if the type is not a struct or the field is invalid. + * + * @example + * // Get field offset + * ffi.cdef('struct point { int x; int y; };'); + * print(ffi.offsetof('struct point', 'x')); // => 0 + * print(ffi.offsetof('struct point', 'y')); // => 4 + * + * @example + * // Get bitfield info + * ffi.cdef('struct flags { unsigned int a:4; unsigned int b:4; };'); + * let bitpos = []; + * let offset = ffi.offsetof('struct flags', 'b', bitpos); + * print(offset, bitpos[0], bitpos[1]); // => 0 4 4 + */ +static uc_value_t * +uc_ffi_offsetof(uc_vm_t *vm, size_t nargs) +{ + CTState *cts = ctype_cts(vm); + CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); + uc_value_t *name = uc_fn_arg(1); + uc_value_t *bitpos = uc_fn_arg(2); + CType *ct = uc_ctype_rawref(cts, id); + CTSize ofs; + + if (!ctype_isstruct(ct->info) || ct->size == CTSIZE_INVALID) + return NULL; + + if (ucv_type(name) != UC_STRING) + return NULL; + + CType *fct = uc_ctype_getfield(cts, ct, name, &ofs); + + if (ctype_isfield(fct->info)) + return ucv_uint64_new(ofs); + + if (ctype_isbitfield(fct->info)) { + ucv_array_set(bitpos, 0, ucv_uint64_new(ctype_bitpos(fct->info))); + ucv_array_set(bitpos, 1, ucv_uint64_new(ctype_bitbsz(fct->info))); + + return ucv_uint64_new(ofs); + } + + return NULL; +} + +/** + * Get or set the C `errno` value. + * + * The `errno()` function retrieves the current value of the C `errno` + * variable, or sets it to a new value if an argument is provided. + * + * @function module:ffi#errno + * + * @param {number} [value] + * Optional value to set errno to. + * + * @returns {number} + * The current errno value (before any set operation). + * + * @example + * // Get current errno + * let err = ffi.errno(); + * + * @example + * // Set errno + * ffi.errno(0); // Clear errno + */ +static uc_value_t * +uc_ffi_errno(uc_vm_t *vm, size_t nargs) +{ + int err = errno; + + if (nargs) + errno = ucv_int64_get(uc_fn_arg(0)); + + return ucv_int64_new(err); +} + +/** + * Preloaded C types and variables. + * + * The FFI module automatically preloads certain C types and global variables + * that are commonly needed. These are available without explicit `cdef()` declarations. + * + * ### Preloaded Global Variables + * + * The following global variables are automatically available through `ffi.C`: + * + * | Variable | Type | Description | + * |----------|------|-------------| + * | `errno` | `int *` | Thread-local error code pointer | + * | `environ` | `char ***` | Process environment variables | + * + * Access these via `ffi.C.dlsym()`: + * + * ```javascript + * // Get errno pointer + * let errno_ptr = ffi.C.dlsym('errno'); + * let err = errno_ptr.deref('int'); + * + * // Get environment variables + * let env = ffi.C.dlsym('environ'); + * for (let i = 0; i < 10; i++) { + * let var = env.get(i); + * if (!var) break; + * print(ffi.string(var), "\n"); + * } + * ``` + * + * ### Builtin Type Definitions + * + * The following types are pre-declared and available without `cdef()`: + * + * | Type | Description | Typical Size | + * |------|-------------|--------------| + * | `size_t` | Unsigned pointer-sized integer | 4 or 8 bytes | + * | `ssize_t` | Signed pointer-sized integer | 4 or 8 bytes | + * | `intptr_t` | Signed integer with same size as pointer | 4 or 8 bytes | + * | `uintptr_t` | Unsigned integer with same size as pointer | 4 or 8 bytes | + * | `ptrdiff_t` | Signed difference type (pointer subtraction) | 4 or 8 bytes | + * | `wchar_t` | Wide character type | 2 or 4 bytes | + * | `va_list` | Variable argument list (for vararg functions) | Implementation-dependent | + * + * ### Fixed-Width Integer Types + * + * The following types from `` are pre-declared: + * + * | Type | Description | Size | + * |------|-------------|------| + * | `int8_t` | Signed 8-bit integer | 1 byte | + * | `int16_t` | Signed 16-bit integer | 2 bytes | + * | `int32_t` | Signed 32-bit integer | 4 bytes | + * | `int64_t` | Signed 64-bit integer | 8 bytes | + * | `uint8_t` | Unsigned 8-bit integer | 1 byte | + * | `uint16_t` | Unsigned 16-bit integer | 2 bytes | + * | `uint32_t` | Unsigned 32-bit integer | 4 bytes | + * | `uint64_t` | Unsigned 64-bit integer | 8 bytes | + * + * These types can be used directly without prior declaration: + * + * ```javascript + * // Use builtin types directly + * let sz = ffi.sizeof('size_t'); // => 8 (on 64-bit systems) + * let ptr = ffi.ctype('uintptr_t', 0); + * + * // Use fixed-width types + * let i32 = ffi.ctype('int32_t', 42); + * let u64 = ffi.ctype('uint64_t', 0xFFFFFFFFFFFFFFFF); + * + * // Create arrays of builtin types + * let buf = ffi.ctype('uint8_t[256]'); + * let indices = ffi.ctype('size_t[10]'); + * ``` + * + * Note: When wrapping functions that use these types, you still need to + * declare the function prototype via `cdef()` or provide a full declaration + * to `wrap()`: + * + * ```javascript + * // Declare function using builtin types + * ffi.cdef('size_t strlen(const char *);'); + * let strlen = ffi.C.wrap('strlen'); + * + * // Or provide full declaration to wrap() + * let strlen = ffi.C.wrap('size_t strlen(const char *)'); + * ``` + * + * @section Preloaded Types + */ + +/** + * Convert between ucode strings and C char arrays/pointers. + * + * The `string()` function has two modes: + * + * 1. **String to buffer**: Given a ucode string, creates a C char[] buffer + * containing the string plus null terminator. Returns a cdata that can be + * passed to C functions expecting `char*`. + * + * 2. **Pointer to string**: Given a char* cdata pointer, reads the C string + * and returns a ucode string. An optional length parameter can be provided + * to limit the maximum bytes read (reads up to `len` bytes or until null + * terminator, whichever comes first). + * + * @function module:ffi#string + * + * @param {string|module:ffi.CData} arg + * A ucode string to convert to char[], or a char* cdata pointer to read. + * + * @param {number} [len] + * Optional maximum length for reading C strings (reads up to `len` bytes + * or until null terminator). + * + * @returns {string|module:ffi.CData} + * When given a char* pointer: returns a ucode string. + * When given a ucode string: returns a char[] cdata buffer. + * + * @throws {Error} + * Throws an exception if the argument type is invalid. + * + * @example + * // Convert ucode string to char[] buffer + * let buf = ffi.string("hello"); + * // buf is now char[6] cdata (including null terminator) + * // Can be passed to C functions expecting char* + * + * @example + * // Read C string from char* pointer + * ffi.cdef('char *getenv(char *);'); + * let ptr = ffi.C.wrap('char *getenv(char *)')("PATH"); + * let path = ffi.string(ptr); + * print(path); // => "/usr/bin:..." + * + * @example + * // Read fixed-length string (no null terminator) + * ffi.cdef('char *strncpy(char *, const char *, size_t);'); + * let src = ffi.string("hello world"); + * let dst = ffi.ctype('char[5]'); + * ffi.C.wrap('char *strncpy(char *, const char *, size_t)')(dst, src, 5); + * let short_str = ffi.string(dst, 5); // => "hello" (no null terminator) + */ +static uc_value_t * +uc_ffi_string(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *arg = uc_fn_arg(0); + uc_value_t *len_arg = uc_fn_arg(1); + CTState *cts = ctype_cts(vm); + + /* If argument is ucode string, create C char[] buffer */ + if (ucv_type(arg) == UC_STRING) { + size_t len = ucv_string_length(arg) + 1; + + /* Create char[N] array type directly without parser invocation */ + CTypeID elem_type = CTID_CCHAR; /* char element type */ + CTInfo array_info = CTINFO(CT_ARRAY, CTALIGN(0)) + elem_type; + CTSize array_size = len; /* Total size in bytes */ + + /* Intern the array type */ + CTypeID array_typeid = uc_ctype_intern(cts, array_info, array_size); + + /* Create cdata instance */ + uc_value_t *arr = uc_cdata_new(vm, array_typeid, array_size); + + /* Copy string including null terminator */ + const char *src = ucv_string_get(arg); + uint8_t *dst = (uint8_t *)cdataptr((GCcdata *)((uc_resource_t *)arr)->data); + memcpy(dst, src, len); + + return arr; + } + + /* Otherwise, argument is a C pointer - extract address and read as string */ + void *p = NULL; + size_t sz; + + if (nargs > 1) { + /* With explicit max-length: accept any pointer type */ + uc_cconv_ct_tv(cts, ctype_get(cts, CTID_P_VOID), (uint8_t *)&p, arg, + CCF_ARG(1), NULL); + size_t max_len = ucv_uint64_get(len_arg); + /* Read up to max_len bytes or until null terminator (like strncpy) */ + sz = strnlen((const char *)p, max_len); + } + else { + /* Without length: extract pointer address and treat as char* */ + GCcdata *cd = ucv_resource_data(arg, "ffi.ctype"); + if (!cd) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "string or cdata pointer expected, got %s", + ucv_typename(arg)); + + return NULL; + } + + /* Get pointer: arrays contain data directly, pointers contain address */ + CType *cd_ct = ctype_get(cts, cd->ctypeid); + if (ctype_isrefarray(cd_ct->info)) { + /* Array: data is directly in cdata, use array size as limit */ + p = cdataptr(cd); + sz = strnlen((const char *)p, cd_ct->size); + } + else if (ctype_isptr(cd_ct->info)) { + /* Pointer: dereference and read null-terminated string */ + p = *(void **)cdataptr(cd); + if (!p) + return ucv_string_new(""); + sz = strlen((const char *)p); + } + else { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "string or cdata pointer expected, got %s", + ucv_typename(arg)); + + return NULL; + } + } + + return ucv_string_new_length((const char *)p, sz); +} + +/** + * Copy memory between pointers. + * + * The `copy()` function copies memory from a source pointer to a destination + * pointer. If the source is a ucode string, it copies the string including + * its null terminator. Otherwise, an explicit length must be provided. + * + * @function module:ffi#copy + * + * @param {module:ffi.CData} dest + * Destination pointer. + * + * @param {string|module:ffi.CData} src + * Source string or pointer. + * + * @param {number} [len] + * Number of bytes to copy. Required if src is not a string. + * + * @returns {undefined} + * Returns `undefined`. + * + * @example + * // Copy string (includes null terminator) + * let buf = ffi.ctype('char[10]'); + * ffi.copy(buf, "hello"); + * + * @example + * // Copy memory with explicit length + * let src = ffi.ctype('char[5]', [1, 2, 3, 4, 5]); + * let dst = ffi.ctype('char[5]'); + * ffi.copy(dst, src, 5); + */ +static uc_value_t * +uc_ffi_copy(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *dp_arg = uc_fn_arg(0); + void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID); + uc_value_t *sp_arg = uc_fn_arg(1); + void *sp = NULL; + size_t len, dp_size, sp_size = SIZE_MAX; + CTState *cts = ctype_cts(vm); + + /* Get destination buffer size for bounds checking */ + dp_size = ffi_cdata_bufsize(cts, dp_arg); + + /* Handle string source: create temporary buffer */ + if (ucv_type(sp_arg) == UC_STRING) { + const char *src = ucv_string_get(sp_arg); + size_t src_len = ucv_string_length(sp_arg); + + /* Determine length: use explicit len if provided, else string + null */ + if (nargs > 2) { + len = ucv_uint64_get(uc_fn_arg(2)); + } else { + len = src_len + 1; + } + + /* Create temporary char[] buffer */ + CTypeID elem_type = CTID_CCHAR; + CTInfo array_info = CTINFO(CT_ARRAY, CTALIGN(0)) + elem_type; + CTypeID array_typeid = uc_ctype_intern(cts, array_info, len); + uc_value_t *tmp = uc_cdata_new(vm, array_typeid, len); + uint8_t *dst = (uint8_t *)cdataptr((GCcdata *)((uc_resource_t *)tmp)->data); + memcpy(dst, src, len > src_len + 1 ? src_len + 1 : len); + sp = dst; + } else { + sp = ffi_checkptr(vm, nargs, 1, CTID_P_CVOID); + if (!sp) + return NULL; + + /* Get source buffer size for bounds checking */ + sp_size = ffi_cdata_bufsize(cts, sp_arg); + + if (nargs > 2) { + len = ucv_uint64_get(uc_fn_arg(2)); + } else { + len = strnlen((const char *)sp, sp_size); + } + } + + /* Cap length to destination buffer size */ + if (len > dp_size) + len = dp_size; + + /* Cap length to source buffer size */ + if (len > sp_size) + len = sp_size; + + memcpy(dp, sp, len); + + return NULL; +} + +/** + * Fill memory with a byte value. + * + * The `fill()` function sets `len` bytes at the destination pointer to + * the specified fill value. The fill value can be a number, boolean, + * or string (first character used). + * + * @function module:ffi#fill + * + * @param {module:ffi.CData} dest + * Destination pointer. + * + * @param {number} len + * Number of bytes to fill. + * + * @param {number|boolean|string} [value=0] + * Fill value. Numbers/booleans use the value directly; strings use + * the first character's ASCII code. + * + * @returns {undefined} + * Returns `undefined`. + * + * @example + * // Zero-fill a buffer + * let buf = ffi.ctype('char[10]'); + * ffi.fill(buf, 10, 0); + * + * // Fill with specific byte + * ffi.fill(buf, 10, 0xFF); + * + * // Fill with character + * ffi.fill(buf, 10, 'A'); // Fills with 65 (ASCII for 'A') + */ +static uc_value_t * +uc_ffi_fill(uc_vm_t *vm, size_t nargs) +{ + void *dp = ffi_checkptr(vm, nargs, 0, CTID_P_VOID); + size_t len = ucv_int64_get(uc_fn_arg(1)); + uc_value_t *fill = uc_fn_arg(2); + int chr = 0; + + switch (ucv_type(fill)) + { + case UC_INTEGER: + case UC_DOUBLE: + chr = ucv_int64_get(fill); + break; + + case UC_BOOLEAN: + chr = ucv_boolean_get(fill) ? 1 : 0; + break; + + case UC_STRING: + chr = ucv_string_get(fill)[0]; + break; + + default: + chr = 0; + break; + } + + memset(dp, chr, len); + + return NULL; +} + +/** + * Cast a value to a different C type. + * + * The `cast()` function converts a value to a specified C type. It supports + * casts to numbers, enums, and pointers. The cast is performed without + * intermediate ucode type conversions. + * + * @function module:ffi#cast + * + * @param {string} type + * The target C type declaration. + * + * @param {*} value + * The value to cast. Can be a ucode value or cdata. + * + * @returns {module:ffi.CData} + * A cdata of the target type holding the cast value. + * + * @throws {Error} + * Throws an exception if the cast is invalid (e.g., casting to a struct). + * + * @example + * // Cast number to pointer + * let ptr = ffi.cast('void *', 0x1000); + * print(ptr.get()); // => 4096 + * + * @example + * // Cast between pointer types + * ffi.cdef('int x;'); + * let px = ffi.ctype('int *', ffi.ctype('int', 42).ptr()); + * let pv = ffi.cast('void *', px); + * + * @example + * // Cast pointer to integer + * let str = ffi.string("hello"); + * let addr = ffi.cast('uintptr_t', str.ptr()); + * print(addr.get()); // => address as number + * + * @example + * // Cast integer to enum + * ffi.cdef('enum color { RED, GREEN, BLUE };'); + * let c = ffi.cast('enum color', 2); // => BLUE + */ +static uc_value_t * +uc_ffi_cast(uc_vm_t *vm, size_t nargs) +{ + CTState *cts = ctype_cts(vm); + CTypeID id = ffi_checkctype(vm, nargs, 0, cts, NULL); + CType *d = ctype_raw(cts, id); + uc_value_t *init = uc_fn_arg(1); + GCcdata *cd = ucv_resource_data(init, "ffi.ctype"); + + if (!ctype_isnum(d->info) && !ctype_isptr(d->info) && !ctype_isenum(d->info)) { + uc_value_t *repr = uc_ctype_repr(vm, id, NULL); + + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "invalid cast to type '%s', only casts to " + "numbers, enums or pointers are allowed", + ucv_string_get(repr)); + + ucv_put(repr); + + return NULL; + } + + if (cd && cd->ctypeid == id) + return ucv_get(init); + + uc_value_t *res = uc_cdata_new(vm, id, d->size); + uc_value_t *refs = NULL; + + /* when we're casting to pointer, keep references to original memory */ + if (cd && ctype_isptr(d->info)) { + refs = ucv_array_new(vm); + + /* keep reference to original value itself */ + ucv_array_push(refs, ucv_get(init)); + + /* merge original values references */ + uc_value_t *src_refs = cd->refs; + + for (size_t i = 0; i < ucv_array_length(src_refs); i++) + ucv_array_push(refs, ucv_get(ucv_array_get(src_refs, i))); + } + + uc_cconv_ct_tv(cts, d, uc_cdata_dataptr(res), init, CCF_CAST, &refs); + + cd = ucv_resource_data(res, "ffi.ctype"); + cd->refs = refs; + + return res; +} + +#if UC_TARGET_CYGWIN +#define CLIB_SOPREFIX "cyg" +#else +#define CLIB_SOPREFIX "lib" +#endif + +#if defined(__APPLE__) +#define CLIB_SOEXT "%s.dylib" +#elif UC_TARGET_CYGWIN +#define CLIB_SOEXT "%s.dll" +#else +#define CLIB_SOEXT "%s.so" +#endif + +/** + * Load a shared library. + * + * The `dlopen()` function loads a shared library into the process address + * space and returns a CLib object that can be used to access symbols via + * `dlsym()` or `wrap()`. + * + * ```javascript + * // Load zlib compression library + * let libz = ffi.dlopen('z'); + * + * // Load OpenSSL crypto library + * let libcrypto = ffi.dlopen('crypto'); + * + * // Load absolute path + * let custom = ffi.dlopen('/usr/local/lib/mylib.so'); + * + * // Use wrap() to get function pointers + * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); + * print(zlibVersion().slice(), "\n"); // => "1.2.11" + * ``` + * + * On Unix-like systems, the `.so` extension is automatically appended if + * omitted. On macOS, `.dylib` is used. On Windows, `.dll` is used. + * + * When the optional third argument is provided, `dlopen()` will: + * - Parse the C definitions to register types and function prototypes + * - Resolve and wrap all declared functions + * - Attach the wrapped functions as methods on the library object + * + * @function module:ffi#dlopen + * + * @param {string} name + * The library name or path. + * + * @param {boolean} [global=false] + * If `true`, make symbols available to subsequently loaded libraries. + * + * @param {string} [cdefs] + * Optional C declaration string containing types and function prototypes. + * Function declarations will be automatically wrapped and attached to the + * library object as methods. + * + * @returns {?module:ffi.CLib} + * A CLib object representing the loaded library, or `null` on error. + * When `cdefs` is provided, the returned CLib will have wrapped functions + * attached as methods. + * + * @throws {Error} + * Throws an exception if the library cannot be loaded or if C definitions + * cannot be parsed. + * + * @example + * // Load zlib and call functions + * let libz = ffi.dlopen('z'); + * let zlibVersion = libz.wrap('const char *zlibVersion(void)'); + * print(zlibVersion().slice()); + * + * @example + * // Load OpenSSL crypto library + * let libcrypto = ffi.dlopen('crypto'); + * let OpenSSL_version = libcrypto.wrap('const char *OpenSSL_version(int)'); + * print(OpenSSL_version(0).slice()); // => "OpenSSL 3.0.0..." + * + * @example + * // Load library with automatic wrapping + * let libssl = ffi.dlopen('ssl', false, ` + * typedef void SSL_METHOD; + * const SSL_METHOD *TLS_method(void); + * `); + * // TLS_method is now directly callable + * let method = libssl.TLS_method(); + * + * @example + * // Load zlib with pre-wrapped functions + * let libz = ffi.dlopen('z', false, ` + * const char *zlibVersion(void); + * uLong compressBound(uLong sourceLen); + * `); + * print(libz.zlibVersion().slice()); + * print(libz.compressBound(1024)); + */ +static uc_value_t * +uc_ffi_dlopen(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *name = uc_fn_arg(0); + uc_value_t *global = uc_fn_arg(1); + uc_value_t *cdefs = uc_fn_arg(2); + uc_value_t *clibs = uc_vm_registry_get(vm, "ffi.clibs"); + + /* Handle dlopen(null) or dlopen("") case - returns global C library */ + if (!name || (ucv_type(name) == UC_STRING && !ucv_string_length(name))) { + uc_value_t *global_lib = ucv_object_get(clibs, "", NULL); + + /* If cdefs provided, parse them and add wrapped functions to ffi.C prototype */ + if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) { + uc_ffi_clib_t *lib = ucv_resource_data(global_lib, "ffi.clib"); + uc_value_t *methods = ucv_prototype_get(global_lib); + + if (lib) { + CTState *cts = ctype_cts(vm); + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(cdefs), + .p = ucv_string_get(cdefs), + .uv_param = NULL, + .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, + .func_ids = &cp.func_ids_buf, + .func_ids_buf = { .count = 0, .entries = NULL }, + .error = NULL + }; + + if (!uc_cparse(&cp)) + return NULL; + + for (size_t i = 0; i < cp.func_ids_buf.count; i++) { + CTypeID id = cp.func_ids_buf.entries[i]; + CType *ct = ctype_get(cts, id); + if (!ct || !ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) + continue; + + const char *symname = ucv_string_get(ct->uv_name); + void *fp = dlsym(RTLD_DEFAULT, symname); + if (!fp) + continue; + + CTypeID cid = ctype_typeid(cts, ct); + size_t namelen = strlen(symname); + size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1); + + uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); + cfn->header.type = UC_CFUNCTION; + cfn->cfn = clib_wrapped_call; + snprintf(cfn->name, namelen + 1, "ffi.C.%s", symname); + + memcpy((char *)cfn + off, &cid, sizeof(cid)); + memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); + + uc_value_t *wrapped = ucv_get(&cfn->header); + ucv_object_add(methods, symname, wrapped); + } + uc_vector_clear(&cp.func_ids_buf); + } + } + + return ucv_get(global_lib); + } + + if (ucv_type(name) != UC_STRING) + return NULL; + + char *path = ucv_string_get(name); + char *s = path; + + /* relative name provided */ + if (!strchr(path, '/') && !strchr(path, '\\') && !strchr(path, '.')) + xasprintf(&s, CLIB_SOPREFIX CLIB_SOEXT, path); + + int mode = RTLD_LAZY | (ucv_is_truish(global) ? RTLD_GLOBAL : RTLD_LOCAL); + void *dlh = dlopen(s, mode); + + if (!dlh) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "unable to load library '%s' (%s): %s", + path, s, dlerror()); + + if (s != path) + free(s); + + return NULL; + } + + /* Per-name instance caching: check if this library name already exists */ + uc_value_t *cached = ucv_object_get(clibs, s, NULL); + + if (cached) { + dlclose(dlh); + + if (s != path) + free(s); + + return ucv_get(cached); + } + + uc_ffi_clib_t *lib; + + /* Create instance prototype - wrapped functions live here as instance methods */ + uc_value_t *methods = ucv_object_new(vm); + + size_t libnamesize = strlen(s) + 1; + size_t datasize = ((sizeof(uc_ffi_clib_t) + libnamesize + 7) / 8) * 8; + + uc_value_t *lib_obj = ucv_resource_new_with_proto( + vm, ucv_resource_type_lookup(vm, "ffi.clib"), + (void **)&lib, 0, datasize, methods); + + lib->cache = ucv_object_new(vm); + lib->dlh = dlh; + + /* Copy library name into the allocated block right after the struct */ + lib->name = memcpy((char *)lib + sizeof(uc_ffi_clib_t), s, libnamesize); + + if (s != path) + free(s); + + /* If cdefs provided, parse them and wrap functions */ + if (cdefs && ucv_type(cdefs) == UC_STRING && ucv_string_length(cdefs)) { + CTState *cts = ctype_cts(vm); + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(cdefs), + .p = ucv_string_get(cdefs), + .uv_param = NULL, + .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, + .func_ids = &cp.func_ids_buf, + .func_ids_buf = { .count = 0, .entries = NULL }, + .error = NULL + }; + + if (!uc_cparse(&cp)) { + if (lib->dlh != RTLD_DEFAULT) + dlclose(lib->dlh); + + ucv_put(lib_obj); + + return NULL; + } + + for (size_t i = 0; i < cp.func_ids_buf.count; i++) { + CTypeID id = cp.func_ids_buf.entries[i]; + CType *ct = ctype_get(cts, id); + if (!ct) + continue; + + if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) + continue; + + const char *symname = ucv_string_get(ct->uv_name); + + void *fp = dlsym(dlh, symname); + if (!fp) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "unable to resolve symbol '%s' in library '%s'", + symname, lib->name); + + if (lib->dlh != RTLD_DEFAULT) + dlclose(lib->dlh); + + ucv_put(lib_obj); + + return NULL; + } + + CTypeID cid = ctype_typeid(cts, ct); + size_t fnamelen = strlen(symname); + size_t off = ALIGN(sizeof(uc_cfunction_t) + fnamelen + 1); + + uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); + cfn->header.type = UC_CFUNCTION; + cfn->cfn = clib_wrapped_call; + snprintf(cfn->name, fnamelen + 1, "ffi.%s.%s", lib->name, symname); + + memcpy((char *)cfn + off, &cid, sizeof(cid)); + memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); + + uc_value_t *wrapped = ucv_get(&cfn->header); + ucv_object_add(methods, symname, wrapped); + } + + uc_vector_clear(&cp.func_ids_buf); + } + + ucv_object_add(clibs, lib->name, ucv_get(lib_obj)); + + return lib_obj; +} + +/** + * Import a C library with automatic function wrapping. + * + * This is a convenience function that combines library loading, type + * declaration, and function wrapping into a single call. It loads the + * specified library, parses the C definitions, and returns an object + * with all functions pre-wrapped and ready to call. + * + * @function module:ffi#import + * + * @param {string} libname + * The library name or path to load. Can be a bare name (e.g., 'z'), + * a filename (e.g., 'libcrypto.so.3'), or an absolute path. + * + * @param {string} cdefs + * A C declaration string containing function prototypes to import. + * Only function declarations are wrapped; types, structs, and other + * declarations are registered but not added to the result object. + * + * @returns {object|null} + * An object containing wrapped functions keyed by their symbol names. + * Returns null if the library cannot be loaded or if parsing fails. + * + * @throws {Error} + * Throws an exception if: + * - The library cannot be loaded + * - The C declarations are syntactically invalid + * - A declared function cannot be resolved in the library + * + * @example + * // Import sqlite3 with all functions + * let sqlite3 = ffi.import('sqlite3', ` + * const char *sqlite3_libversion(void); + * int sqlite3_libversion_number(void); + * int sqlite3_open(const char *, void **); + * int sqlite3_close(void *); + * `); + * + * print("Version: ", sqlite3.sqlite3_libversion(), "\n"); + * + * @example + * // Import zlib functions + * let zlib = ffi.import('z', ` + * const char *zlibVersion(void); + * uLong compressBound(uLong sourceLen); + * `); + * + * print(zlib.zlibVersion()); + * print(zlib.compressBound(1024)); + */ +static uc_value_t * +uc_ffi_import(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *libname = uc_fn_arg(0); + uc_value_t *cdefs = uc_fn_arg(1); + CTState *cts = ctype_cts(vm); + + if (!libname || ucv_type(libname) != UC_STRING) + return NULL; + + if (!cdefs || ucv_type(cdefs) != UC_STRING) + return NULL; + + /* Load the library */ + char *path = ucv_string_get(libname); + void *dlh = dlopen(path, RTLD_LAZY | RTLD_LOCAL); + + if (!dlh) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "unable to load library '%s' (%s): %s", + ucv_string_get(libname), path, dlerror()); + + return NULL; + } + + /* Parse C definitions to register types */ + CPState cp = { + .uv_vm = vm, + .cts = cts, + .srcname = ucv_string_get(cdefs), + .p = ucv_string_get(cdefs), + .uv_param = NULL, + .mode = CPARSE_MODE_MULTI | CPARSE_MODE_DIRECT, + .func_ids = &cp.func_ids_buf + }; + + if (!uc_cparse(&cp)) { + dlclose(dlh); + return NULL; + } + + /* Create result object */ + uc_value_t *result = ucv_object_new(vm); + + /* Iterate over recorded function type IDs */ + for (size_t i = 0; i < cp.func_ids_buf.count; i++) { + CTypeID id = cp.func_ids_buf.entries[i]; + CType *ct = ctype_get(cts, id); + if (!ct) + continue; + + /* Get the function name */ + if (!ct->uv_name || ucv_type(ct->uv_name) != UC_STRING) + continue; + + const char *symname = ucv_string_get(ct->uv_name); + + /* Resolve the symbol from the library */ + void *fp = dlsym(dlh, symname); + if (!fp) { + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "unable to resolve symbol '%s' in library", symname); + + ucv_put(result); + dlclose(dlh); + return NULL; + } + + /* Wrap the function - create cfunction wrapper */ + CTypeID cid = ctype_typeid(cts, ct); + size_t namelen = strlen(symname); + size_t off = ALIGN(sizeof(uc_cfunction_t) + namelen + 1); + + uc_cfunction_t *cfn = xalloc(off + sizeof(cid) + sizeof(fp)); + cfn->header.type = UC_CFUNCTION; + cfn->cfn = clib_wrapped_call; + snprintf(cfn->name, namelen + 1, "ffi.import.%s", symname); + + /* Store cid and fp after the cfunction struct */ + memcpy((char *)cfn + off, &cid, sizeof(cid)); + memcpy((char *)cfn + off + sizeof(cid), &fp, sizeof(fp)); + + uc_value_t *wrapped = ucv_get(&cfn->header); + ucv_object_add(result, symname, wrapped); + /* ucv_object_add already increments refcount, no need to put */ + } + + uc_vector_clear(&cp.func_ids_buf); + dlclose(dlh); + + return result; +} + + +static const uc_function_list_t clib_fns[] = { + { "dlsym", uc_clib_dlsym }, + { "resolve", uc_clib_resolve }, + { "wrap", uc_clib_wrap }, +}; + +static const uc_function_list_t ctype_fns[] = { + { "call", uc_ctype_call }, + { "free", uc_ctype_free }, + { "get", uc_ctype_get }, + { "set", uc_ctype_set }, + { "ptr", uc_ctype_ptr }, + { "index", uc_ctype_index }, + { "deref", uc_ctype_deref }, + { "size", uc_ctype_sizeof }, + { "length", uc_ctype_length }, + { "itemsize", uc_ctype_itemsize }, + { "slice", uc_ctype_slice }, + { "tostring", uc_ctype_tostring }, +}; + +static const uc_function_list_t global_fns[] = { + { "ctype", uc_ffi_ctype }, + { "cdef", uc_ffi_cdef }, + { "typeof", uc_ffi_typeof }, + { "sizeof", uc_ffi_sizeof }, + { "alignof", uc_ffi_alignof }, + { "offsetof", uc_ffi_offsetof }, + { "errno", uc_ffi_errno }, + { "string", uc_ffi_string }, + { "copy", uc_ffi_copy }, + { "fill", uc_ffi_fill }, + { "cast", uc_ffi_cast }, + { "dlopen", uc_ffi_dlopen }, + { "import", uc_ffi_import }, +}; + + +static void +close_clib(void *ud) +{ + uc_ffi_clib_t *clib = ud; + + ucv_put(clib->cache); + + if (clib->dlh != RTLD_DEFAULT) + dlclose(clib->dlh); +} + +static void +close_ctype(void *ud) +{ + GCcdata *cd = ud; + + /* ucode does not create libffi closure cdata objects; + * closures are created transiently for callback arguments. */ + + if (cd->refs) + ucv_put(cd->refs); +} + + +extern char **environ; + +static void +preload_type(uc_vm_t *vm, uc_ffi_clib_t *lib, const char *cdef, void *val) +{ + CTState *cts = ctype_cts(vm); + uc_value_t *def = ucv_string_new(cdef); + CTypeID cid = uv_to_ct(vm, CPARSE_MODE_DIRECT | CPARSE_MODE_NOIMPLICIT | CPARSE_MODE_MULTI, def, NULL); + + ucv_put(def); + + if (!cid) + return; + + CType *ct = ctype_get(cts, cid); + + if (!ct || ucv_type(ct->uv_name) != UC_STRING) + return; + + uc_value_t *sym = uc_cdata_new(vm, cid, CTSIZE_PTR); + + *(void **)uc_cdata_dataptr(sym) = val; + + ucv_object_add(lib->cache, ucv_string_get(ct->uv_name), sym); +} + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_ctype_init(vm); + + uc_type_declare(vm, "ffi.clib", clib_fns, close_clib); + uc_type_declare(vm, "ffi.ctype", ctype_fns, close_ctype); + + uc_function_list_register(scope, global_fns); + + uc_value_t *clibs = ucv_object_new(vm); + + uc_vm_registry_set(vm, "ffi.clibs", clibs); + + uc_ffi_clib_t *C; + uc_value_t *global_proto = ucv_object_new(vm); + + uc_value_t *stdlib = ucv_resource_new_with_proto( + vm, ucv_resource_type_lookup(vm, "ffi.clib"), + (void **)&C, 0, sizeof(*C), global_proto); + + C->dlh = RTLD_DEFAULT; + C->name = NULL; + C->cache = ucv_object_new(vm); + + ucv_object_add(scope, "C", stdlib); + ucv_object_add(clibs, "", ucv_get(stdlib)); + + /* preload global variables */ + preload_type(vm, C, "int *errno", &errno); + preload_type(vm, C, "char **environ", environ); +} diff --git a/lib/ffi/uc_cconv.c b/lib/ffi/uc_cconv.c new file mode 100644 index 00000000..4af89410 --- /dev/null +++ b/lib/ffi/uc_cconv.c @@ -0,0 +1,1154 @@ +/* +** C type conversions. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This file contains derived work from LuaJIT's FFI conversion machinery (lj_cconv.c). +** +** Modifications: +** - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) +** - Replaced LuaJIT's parallel call infrastructure with libffi +** - Adapted value marshaling between ucode and C types +** - Removed JIT-specific code and dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + + +#include "uc_ctype.h" +#include "uc_cdata.h" +#include "uc_cconv.h" + +#include + +/* -- Conversion errors --------------------------------------------------- */ + +/* Bad conversion. */ +static bool +cconv_err_conv(CTState *cts, CType *d, CType *s, CTInfo flags) +{ + uc_value_t *dst_repr = uc_ctype_repr(cts->vm, ctype_typeid(cts, d), NULL); + uc_value_t *src_repr = NULL; + const char *dst = ucv_string_get(dst_repr); + const char *src; + + if ((flags & CCF_FROMTV)) + { + src = ctype_isnum(s->info) + ? "number" + : ctype_isarray(s->info) + ? "string" + : "null"; + } + else + { + src_repr = uc_ctype_repr(cts->vm, ctype_typeid(cts, s), NULL); + src = ucv_string_get(src_repr); + } + + if (CCF_GETARG(flags)) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "cannot convert argument #%d from '%s' to '%s'", + CCF_GETARG(flags), src, dst); + } + // lj_err_argv(cts->L, CCF_GETARG(flags), LJ_ERR_FFI_BADCONV, src, dst); + else { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "cannot convert from '%s' to '%s'", + src, dst); + } + + // lj_err_callerv(cts->L, LJ_ERR_FFI_BADCONV, src, dst); + ucv_put(dst_repr); + ucv_put(src_repr); + + return false; +} + +/* Bad conversion from TValue. */ +static bool +cconv_err_convtv(CTState *cts, CType *d, uc_value_t *uv, CTInfo flags) +{ + uc_value_t *dst_repr = uc_ctype_repr(cts->vm, ctype_typeid(cts, d), NULL); + const char *dst = ucv_string_get(dst_repr); + const char *src = ucv_typename(uv); + + if (CCF_GETARG(flags)) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "cannot convert argument #%d from '%s' to '%s'", + CCF_GETARG(flags), src, dst); + } + // lj_err_argv(cts->L, CCF_GETARG(flags), LJ_ERR_FFI_BADCONV, src, dst); + else { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "cannot convert from '%s' to '%s'", + src, dst); + } + + // lj_err_callerv(cts->L, LJ_ERR_FFI_BADCONV, src, dst); + ucv_put(dst_repr); + + return false; +} + +/* Initializer overflow. */ +static bool +cconv_err_initov(CTState *cts, CType *d) +{ + uc_value_t *repr = uc_ctype_repr(cts->vm, ctype_typeid(cts, d), NULL); + + uc_vm_raise_exception(cts->vm, EXCEPTION_REFERENCE, + "too many initializers for '%s'", + ucv_string_get(repr)); + + ucv_put(repr); + + return false; +} + +/* -- C type compatibility checks ----------------------------------------- */ + +/* Get raw type and qualifiers for a child type. Resolves enums, too. */ +static CType *cconv_childqual(CTState *cts, CType *ct, CTInfo *qual) +{ + ct = ctype_child(cts, ct); + for (;;) + { + if (ctype_isattrib(ct->info)) + { + if (ctype_attrib(ct->info) == CTA_QUAL) + *qual |= ct->size; + } + else if (!ctype_isenum(ct->info)) + { + break; + } + ct = ctype_child(cts, ct); + } + *qual |= (ct->info & CTF_QUAL); + return ct; +} + +/* Check for compatible types when converting to a pointer. +** Note: these checks are more relaxed than what C99 mandates. +*/ +int uc_cconv_compatptr(CTState *cts, CType *d, CType *s, CTInfo flags) +{ + if (!((flags & CCF_CAST) || d == s)) + { + CTInfo dqual = 0, squal = 0; + d = cconv_childqual(cts, d, &dqual); + if (!ctype_isstruct(s->info)) + s = cconv_childqual(cts, s, &squal); + if ((flags & CCF_SAME)) + { + if (dqual != squal) + return 0; /* Different qualifiers. */ + } + else if (!(flags & CCF_IGNQUAL)) + { + if ((dqual & squal) != squal) + return 0; /* Discarded qualifiers. */ + if (ctype_isvoid(d->info) || ctype_isvoid(s->info)) + return 1; /* Converting to/from void * is always ok. */ + } + if (ctype_type(d->info) != ctype_type(s->info) || + d->size != s->size) + return 0; /* Different type or different size. */ + if (ctype_isnum(d->info)) + { + if (((d->info ^ s->info) & (CTF_BOOL | CTF_FP))) + return 0; /* Different numeric types. */ + } + else if (ctype_ispointer(d->info)) + { + /* Check child types for compatibility. */ + return uc_cconv_compatptr(cts, d, s, flags | CCF_SAME); + } + else if (ctype_isstruct(d->info)) + { + if (d != s) + return 0; /* Must be exact same type for struct/union. */ + } + else if (ctype_isfunc(d->info)) + { + /* NYI: structural equality of functions. */ + } + } + return 1; /* Types are compatible. */ +} + +/* -- C type to C type conversion ----------------------------------------- */ + +/* Convert C type to C type. Caveat: expects to get the raw CType! +** +** Note: This is only used by the interpreter and not optimized at all. +** The JIT compiler will do a much better job specializing for each case. +*/ +bool uc_cconv_ct_ct(CTState *cts, CType *d, CType *s, + uint8_t *dp, uint8_t *sp, CTInfo flags) +{ + CTSize dsize = d->size, ssize = s->size; + CTInfo dinfo = d->info, sinfo = s->info; + void *tmpptr; + + uc_assertCTS(!ctype_isenum(dinfo) && !ctype_isenum(sinfo), + "unresolved enum"); + uc_assertCTS(!ctype_isattrib(dinfo) && !ctype_isattrib(sinfo), + "unstripped attribute"); + + if (ctype_type(dinfo) > CT_MAYCONVERT || ctype_type(sinfo) > CT_MAYCONVERT) + goto err_conv; + + /* Some basic sanity checks. */ + uc_assertCTS(!ctype_isnum(dinfo) || dsize > 0, "bad size for number type"); + uc_assertCTS(!ctype_isnum(sinfo) || ssize > 0, "bad size for number type"); + uc_assertCTS(!ctype_isbool(dinfo) || dsize == 1 || dsize == 4, + "bad size for bool type"); + uc_assertCTS(!ctype_isbool(sinfo) || ssize == 1 || ssize == 4, + "bad size for bool type"); + uc_assertCTS(!ctype_isinteger(dinfo) || (1u << uc_fls(dsize)) == dsize, + "bad size for integer type"); + uc_assertCTS(!ctype_isinteger(sinfo) || (1u << uc_fls(ssize)) == ssize, + "bad size for integer type"); + + switch (cconv_idx2(dinfo, sinfo)) + { + /* Destination is a bool. */ + case CCX(B, B): + /* Source operand is already normalized. */ + if (dsize == 1) + *dp = *sp; + else + *(int *)dp = *sp; + break; + case CCX(B, I): + { + size_t i; + uint8_t b = 0; + for (i = 0; i < ssize; i++) + b |= sp[i]; + b = (b != 0); + if (dsize == 1) + *dp = b; + else + *(int *)dp = b; + break; + } + case CCX(B, F): + { + uint8_t b; + if (ssize == sizeof(double)) + b = (*(double *)sp != 0); + else if (ssize == sizeof(float)) + b = (*(float *)sp != 0); + else + goto err_conv; /* NYI: long double. */ + if (dsize == 1) + *dp = b; + else + *(int *)dp = b; + break; + } + + /* Destination is an integer. */ + case CCX(I, B): + case CCX(I, I): + conv_I_I: + if (dsize > ssize) + { /* Zero-extend or sign-extend LSB. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t fill = (!(sinfo & CTF_UNSIGNED) && (sp[ssize - 1] & 0x80)) ? 0xff : 0; + memcpy(dp, sp, ssize); + memset(dp + ssize, fill, dsize - ssize); +#else + uint8_t fill = (!(sinfo & CTF_UNSIGNED) && (sp[0] & 0x80)) ? 0xff : 0; + memset(dp, fill, dsize - ssize); + memcpy(dp + (dsize - ssize), sp, ssize); +#endif + } + else + { /* Copy LSB. */ +#if __BYTE_ORDER == __LITTLE_ENDIAN + memcpy(dp, sp, dsize); +#else + memcpy(dp, sp + (ssize - dsize), dsize); +#endif + } + break; + case CCX(I, F): + { + double n; /* Always convert via double. */ + conv_I_F: + /* Convert source to double. */ + if (ssize == sizeof(double)) + n = *(double *)sp; + else if (ssize == sizeof(float)) + n = (double)*(float *)sp; + else + goto err_conv; /* NYI: long double. */ + /* Then convert double to integer. */ + /* The conversion must exactly match the semantics of JIT-compiled code! */ + if (dsize < 4 || (dsize == 4 && !(dinfo & CTF_UNSIGNED))) + { + int32_t i = (int32_t)n; + if (dsize == 4) + *(int32_t *)dp = i; + else if (dsize == 2) + *(int16_t *)dp = (int16_t)i; + else + *(int8_t *)dp = (int8_t)i; + } + else if (dsize == 4) + { + *(uint32_t *)dp = (uint32_t)n; + } + else if (dsize == 8) + { + if (!(dinfo & CTF_UNSIGNED)) + *(int64_t *)dp = (int64_t)n; + else + *(uint64_t *)dp = uc_num2u64(n); + } + else + { + goto err_conv; /* NYI: conversion to >64 bit integers. */ + } + break; + } + case CCX(I, C): + s = ctype_child(cts, s); + ssize = s->size; + goto conv_I_F; /* Just convert re. */ + case CCX(I, P): + if (!(flags & CCF_CAST)) + goto err_conv; + sinfo = CTINFO(CT_NUM, CTF_UNSIGNED); + goto conv_I_I; + case CCX(I, A): + if (!(flags & CCF_CAST)) + goto err_conv; + sinfo = CTINFO(CT_NUM, CTF_UNSIGNED); + ssize = CTSIZE_PTR; + tmpptr = sp; + sp = (uint8_t *)&tmpptr; + goto conv_I_I; + + /* Destination is a floating-point number. */ + case CCX(F, B): + case CCX(F, I): + { + double n; /* Always convert via double. */ + conv_F_I: + /* First convert source to double. */ + /* The conversion must exactly match the semantics of JIT-compiled code! */ + if (ssize < 4 || (ssize == 4 && !(sinfo & CTF_UNSIGNED))) + { + int32_t i; + if (ssize == 4) + { + i = *(int32_t *)sp; + } + else if (!(sinfo & CTF_UNSIGNED)) + { + if (ssize == 2) + i = *(int16_t *)sp; + else + i = *(int8_t *)sp; + } + else + { + if (ssize == 2) + i = *(uint16_t *)sp; + else + i = *(uint8_t *)sp; + } + n = (double)i; + } + else if (ssize == 4) + { + n = (double)*(uint32_t *)sp; + } + else if (ssize == 8) + { + if (!(sinfo & CTF_UNSIGNED)) + n = (double)*(int64_t *)sp; + else + n = (double)*(uint64_t *)sp; + } + else + { + goto err_conv; /* NYI: conversion from >64 bit integers. */ + } + /* Convert double to destination. */ + if (dsize == sizeof(double)) + *(double *)dp = n; + else if (dsize == sizeof(float)) + *(float *)dp = (float)n; + else + goto err_conv; /* NYI: long double. */ + break; + } + case CCX(F, F): + { + double n; /* Always convert via double. */ + conv_F_F: + if (ssize == dsize) + goto copyval; + /* Convert source to double. */ + if (ssize == sizeof(double)) + n = *(double *)sp; + else if (ssize == sizeof(float)) + n = (double)*(float *)sp; + else + goto err_conv; /* NYI: long double. */ + /* Convert double to destination. */ + if (dsize == sizeof(double)) + *(double *)dp = n; + else if (dsize == sizeof(float)) + *(float *)dp = (float)n; + else + goto err_conv; /* NYI: long double. */ + break; + } + case CCX(F, C): + s = ctype_child(cts, s); + ssize = s->size; + goto conv_F_F; /* Ignore im, and convert from re. */ + + /* Destination is a complex number. */ + case CCX(C, I): + d = ctype_child(cts, d); + dsize = d->size; + memset(dp + dsize, 0, dsize); /* Clear im. */ + goto conv_F_I; /* Convert to re. */ + case CCX(C, F): + d = ctype_child(cts, d); + dsize = d->size; + memset(dp + dsize, 0, dsize); /* Clear im. */ + goto conv_F_F; /* Convert to re. */ + + case CCX(C, C): + if (dsize != ssize) + { /* Different types: convert re/im separately. */ + CType *dc = ctype_child(cts, d); + CType *sc = ctype_child(cts, s); + return uc_cconv_ct_ct(cts, dc, sc, dp, sp, flags) && + uc_cconv_ct_ct(cts, dc, sc, dp + dc->size, sp + sc->size, flags); + } + goto copyval; /* Otherwise this is easy. */ + + /* Destination is a vector. */ + case CCX(V, I): + case CCX(V, F): + case CCX(V, C): + { + CType *dc = ctype_child(cts, d); + CTSize esize; + /* First convert the scalar to the first element. */ + if (!uc_cconv_ct_ct(cts, dc, s, dp, sp, flags)) + return false; + /* Then replicate it to the other elements (splat). */ + for (sp = dp, esize = dc->size; dsize > esize; dsize -= esize) + { + dp += esize; + memcpy(dp, sp, esize); + } + break; + } + + case CCX(V, V): + /* Copy same-sized vectors, even for different lengths/element-types. */ + if (dsize != ssize) + goto err_conv; + goto copyval; + + /* Destination is a pointer. */ + case CCX(P, I): + if (!(flags & CCF_CAST)) + goto err_conv; + goto conv_I_I; + + case CCX(P, F): + if (!(flags & CCF_CAST) || !(flags & CCF_FROMTV)) + goto err_conv; + /* The signed conversion is cheaper. x64 really has 47 bit pointers. */ + dinfo = CTINFO(CT_NUM, (UC_64 && dsize == 8) ? 0 : CTF_UNSIGNED); + goto conv_I_F; + + case CCX(P, P): + if (!uc_cconv_compatptr(cts, d, s, flags)) + goto err_conv; + cdata_setptr(dp, dsize, cdata_getptr(sp, ssize)); + break; + + case CCX(P, A): + case CCX(P, S): + if (!uc_cconv_compatptr(cts, d, s, flags)) { + goto err_conv; + } + cdata_setptr(dp, dsize, sp); + break; + + /* Destination is an array. */ + case CCX(A, A): + if ((flags & CCF_CAST) || (d->info & CTF_VLA) || dsize != ssize || + d->size == CTSIZE_INVALID || !uc_cconv_compatptr(cts, d, s, flags)) + goto err_conv; + goto copyval; + + /* Destination is a struct/union. */ + case CCX(S, S): + if ((flags & CCF_CAST) || (d->info & CTF_VLA) || d != s) + goto err_conv; /* Must be exact same type. */ + copyval: /* Copy value. */ + uc_assertCTS(dsize == ssize, "value copy with different sizes"); + memcpy(dp, sp, dsize); + break; + + default: + err_conv: + return cconv_err_conv(cts, d, s, flags); + } + + return true; +} + +/* -- C type to TValue conversion ----------------------------------------- */ + +/* Convert C type to TValue. Caveat: expects to get the raw CType! */ +int uc_cconv_tv_ct(CTState *cts, CType *s, CTypeID sid, + uc_value_t **uv, uint8_t *sp) +{ + CTInfo sinfo = s->info; + if (ctype_isnum(sinfo)) + { + ucv_put(*uv); + + if (!ctype_isbool(sinfo)) + { + if (ctype_isinteger(sinfo)) { + if (sinfo & CTF_UNSIGNED) { + uint64_t u; + uc_cconv_ct_ct(cts, ctype_get(cts, CTID_UINT64), s, + (uint8_t *)&u, sp, 0); + *uv = ucv_uint64_new(u); + } + else { + int64_t n; + uc_cconv_ct_ct(cts, ctype_get(cts, CTID_INT64), s, + (uint8_t *)&n, sp, 0); + *uv = ucv_int64_new(n); + } + } + else { + double d; + uc_cconv_ct_ct(cts, ctype_get(cts, CTID_DOUBLE), s, + (uint8_t *)&d, sp, 0); + *uv = ucv_double_new(d); + } + } + else + { + uint32_t b = s->size == 1 ? (*sp != 0) : (*(int *)sp != 0); + *uv = ucv_boolean_new(b); + //setboolV(o, b); + //setboolV(&cts->g->tmptv2, b); /* Remember for trace recorder. */ + } + return 0; + } + else if (ctype_isrefarray(sinfo) || ctype_isstruct(sinfo)) + { + /* Create reference. */ + ucv_put(*uv); + *uv = uc_cdata_newref(cts->vm, sp, sid); + + //setcdataV(cts->L, o, lj_cdata_newref(cts, sp, sid)); + return 1; /* Need GC step. */ + } + else + { + CTSize sz = s->size; + uc_assertCTS(sz != CTSIZE_INVALID, "value copy with invalid size"); + + /* Attributes are stripped, qualifiers are kept (but mostly ignored). */ + uc_value_t *res = uc_cdata_new(cts->vm, ctype_typeid(cts, s), sz); + memcpy(uc_cdata_dataptr(res), sp, sz); + + ucv_put(*uv); + *uv = res; + + return 1; /* Need GC step. */ + } +} + +/* Convert bitfield to TValue. */ +int uc_cconv_tv_bf(CTState *cts, CType *s, uc_value_t **uv, uint8_t *sp) +{ + CTInfo info = s->info; + CTSize pos, bsz; + uint32_t val; + uc_assertCTS(ctype_isbitfield(info), "bitfield expected"); + /* NYI: packed bitfields may cause misaligned reads. */ + switch (ctype_bitcsz(info)) + { + case 4: + val = *(uint32_t *)sp; + break; + case 2: + val = *(uint16_t *)sp; + break; + case 1: + val = *(uint8_t *)sp; + break; + default: + uc_assertCTS(0, "bad bitfield container size %d", ctype_bitcsz(info)); + val = 0; + break; + } + /* Check if a packed bitfield crosses a container boundary. */ + pos = ctype_bitpos(info); + bsz = ctype_bitbsz(info); + uc_assertCTS(pos < 8 * ctype_bitcsz(info), "bad bitfield position"); + uc_assertCTS(bsz > 0 && bsz <= 8 * ctype_bitcsz(info), "bad bitfield size"); + if (pos + bsz > 8 * ctype_bitcsz(info)) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "packed bit fields are not implemented yet"); + return 0; + } + if (!(info & CTF_BOOL)) + { + CTSize shift = 32 - bsz; + if (!(info & CTF_UNSIGNED)) + { + ucv_put(*uv); + *uv = ucv_int64_new((int32_t)(val << (shift - pos)) >> shift); + //setintV(o, (int32_t)(val << (shift - pos)) >> shift); + } + else + { + val = (val << (shift - pos)) >> shift; + ucv_put(*uv); + *uv = ucv_uint64_new(val); + //if (!LJ_DUALNUM || (int32_t)val < 0) + // setnumV(o, (lua_Number)(uint32_t)val); + //else + // setintV(o, (int32_t)val); + } + } + else + { + uint32_t b = (val >> pos) & 1; + uc_assertCTS(bsz == 1, "bad bool bitfield size"); + ucv_put(*uv); + *uv = ucv_boolean_new(b); + //setboolV(o, b); + //setboolV(&cts->g->tmptv2, b); /* Remember for trace recorder. */ + } + return 0; /* No GC step needed. */ +} + +/* -- TValue to C type conversion ----------------------------------------- */ + +/* Convert ucode array to C array. */ +static bool cconv_array_tab(CTState *cts, CType *d, + uint8_t *dp, uc_value_t *arr, CTInfo flags, + uc_value_t **refs) +{ + size_t arrlen = ucv_array_length(arr); + size_t i; + CType *dc = ctype_rawchild(cts, d); /* Array element type. */ + CTSize size = d->size, esize = dc->size, ofs = 0; + for (i = 0; i < arrlen; i++) + { + if (ofs >= size) + cconv_err_initov(cts, d); + uc_cconv_ct_tv(cts, dc, dp + ofs, ucv_array_get(arr, i), flags, refs); + ofs += esize; + } + if (size != CTSIZE_INVALID) + { /* Only fill up arrays with known size. */ + if (ofs == esize) + { /* Replicate a single element. */ + for (; ofs < size; ofs += esize) + memcpy(dp + ofs, dp, esize); + } + else + { /* Otherwise fill the remainder with zero. */ + memset(dp + ofs, 0, size - ofs); + } + } + + return true; +} + +/* Convert ucode array to sub-struct/union. */ +static bool cconv_substruct_tab(CTState *cts, CType *d, uint8_t *dp, + uc_value_t *tab, int32_t *ip, CTInfo flags, + uc_value_t **refs) +{ + CTypeID id = d->sib; + + while (id) + { + CType *df = ctype_get(cts, id); + id = df->sib; + + if (ctype_isfield(df->info) || ctype_isbitfield(df->info)) + { + uc_value_t *uv; + + if (!df->uv_name) + continue; /* Ignore unnamed fields. */ + + if (ucv_type(tab) == UC_ARRAY) + { + int32_t i = *ip; + + uv = ucv_array_get(tab, i); + + if (!uv) + break; /* Stop at first nil. */ + + *ip = i + 1; + } + else if (ucv_type(tab) == UC_OBJECT) + { + uv = ucv_object_get(tab, ucv_string_get(df->uv_name), NULL); + + if (!uv) + continue; + } + else { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "array or object initializer value expected, got %s", + ucv_typename(tab)); + + return false; + } + + if (ctype_isfield(df->info)) + uc_cconv_ct_tv(cts, ctype_rawchild(cts, df), dp + df->size, + uv, flags, refs); + else + uc_cconv_bf_tv(cts, df, dp + df->size, uv, refs); + + if ((d->info & CTF_UNION)) + break; + } + else if (ctype_isxattrib(df->info, CTA_SUBTYPE)) + { + if (!cconv_substruct_tab(cts, ctype_rawchild(cts, df), + dp + df->size, tab, ip, flags, refs)) + return false; + } /* Ignore all other entries in the chain. */ + } + + return true; +} + +/* Convert table to struct/union. */ +static bool cconv_struct_tab(CTState *cts, CType *d, + uint8_t *dp, uc_value_t *tab, CTInfo flags, + uc_value_t **refs) +{ + int32_t i = 0; + + memset(dp, 0, d->size); /* Much simpler to clear the struct first. */ + + return cconv_substruct_tab(cts, d, dp, tab, &i, flags, refs); +} + +/* Convert TValue to C type. Caveat: expects to get the raw CType! */ +bool uc_cconv_ct_tv(CTState *cts, CType *d, + uint8_t *dp, uc_value_t *uv, CTInfo flags, + uc_value_t **refs) +{ + uintptr_t pv = (uintptr_t)uv; + CTypeID sid = CTID_P_VOID; + CType *s; + void *tmpptr; + uint8_t tmpbool, *sp = (uint8_t *)&tmpptr; + GCcdata *cd; + uc_type_t utt = pv & 3; + uc_type_t ut = (!utt && uv) ? uv->type : utt; + + /* optimized case: fast tagged integer read */ + if (UC_LIKELY(utt == UC_INTEGER)) { + /* unsigned */ + if (((pv >> 2) & 1) == 0) { + tmpptr = (void *)(pv >> 3); + sid = (sizeof(tmpptr) == sizeof(uint64_t)) ? CTID_UINT64 : CTID_UINT32; + } + else { + tmpptr = (void *)(uintptr_t)-(pv >> 3); + sid = (sizeof(tmpptr) == sizeof(int64_t)) ? CTID_INT64 : CTID_INT32; + } + + flags |= CCF_FROMTV; + } + /* optimized case: fast bool read */ + else if (UC_LIKELY(utt == UC_BOOLEAN)) + { + tmpbool = (pv >> 2) & 1; + sp = &tmpbool; + sid = CTID_BOOL; + } + else if (UC_LIKELY(ut == UC_INTEGER)) + { + uc_integer_t *ui = (uc_integer_t *)uv; + + /* unsigned */ + if (ui->header.ext_flag) { + sp = (uint8_t *)&ui->i.u64; + sid = CTID_UINT64; + } + else { + sp = (uint8_t *)&ui->i.s64; + sid = CTID_INT64; + } + + flags |= CCF_FROMTV; + } + else if (UC_LIKELY(ut == UC_DOUBLE)) + { + uc_double_t *ud = (uc_double_t *)uv; + + sp = (uint8_t *)&ud->dbl; + sid = CTID_DOUBLE; + flags |= CCF_FROMTV; + } + else if (ut == UC_RESOURCE && (cd = ucv_resource_data(uv, "ffi.ctype")) != NULL) + { + sp = cdataptr(cd); + sid = cd->ctypeid; + s = ctype_get(cts, sid); + if (ctype_isref(s->info)) + { /* Resolve reference for value. */ + uc_assertCTS(s->size == CTSIZE_PTR, "ref is not pointer-sized"); + sp = *(void **)sp; + sid = ctype_cid(s->info); + } + s = ctype_raw(cts, sid); + if (ctype_isfunc(s->info)) + { + CTypeID did = ctype_typeid(cts, d); + sid = uc_ctype_intern(cts, CTINFO(CT_PTR, CTALIGN_PTR | sid), CTSIZE_PTR); + d = ctype_get(cts, did); /* cts->tab may have been reallocated. */ + } + else + { + if (ctype_isenum(s->info)) + s = ctype_child(cts, s); + goto doconv; + } + } + else if (ut == UC_STRING) + { + if (ctype_isenum(d->info)) + { /* Match string against enum constant. */ + CTSize ofs; + CType *cct = uc_ctype_getfield(cts, d, uv, &ofs); + if (!cct || !ctype_isconstval(cct->info)) + goto err_conv; + uc_assertCTS(d->size == 4, "only 32 bit enum supported"); /* NYI */ + sp = (uint8_t *)&cct->size; + sid = ctype_cid(cct->info); + } + else if (ctype_isrefarray(d->info)) + { /* Copy string to array. */ + CType *dc = ctype_rawchild(cts, d); + CTSize sz = ucv_string_length(uv) + 1; + if (!ctype_isinteger(dc->info) || dc->size != 1) + goto err_conv; + if (d->size != 0 && d->size < sz) + sz = d->size; + return memcpy(dp, ucv_string_get(uv), sz); + } + else + { /* Otherwise pass it as a const char[]. */ + uc_value_t *us = uc_cconv_addref(refs, uv); + sp = (uint8_t *)ucv_string_get(us); + sid = CTID_A_CCHAR; + flags |= CCF_FROMTV; + } + } + else if (ut == UC_ARRAY) + { + if (ctype_isarray(d->info)) + { + return cconv_array_tab(cts, d, dp, uv, flags, refs); + } + else if (ctype_isstruct(d->info)) + { + return cconv_struct_tab(cts, d, dp, uv, flags, refs); + } + else + { + goto err_conv; + } + } + else if (ut == UC_OBJECT) + { + if (ctype_isstruct(d->info)) + { + return cconv_struct_tab(cts, d, dp, uv, flags, refs); + } + else + { + goto err_conv; + } + } + else if (ut == UC_NULL) + { + tmpptr = (void *)0; + flags |= CCF_FROMTV; + } + else if (ut == UC_RESOURCE) + { + tmpptr = ucv_resource_data(uv, NULL); + } + else + { + err_conv: + return cconv_err_convtv(cts, d, uv, flags); + } + s = ctype_get(cts, sid); +doconv: + if (ctype_isenum(d->info)) + d = ctype_child(cts, d); + return uc_cconv_ct_ct(cts, d, s, dp, sp, flags); +} + +/* Convert TValue to bitfield. */ +bool uc_cconv_bf_tv(CTState *cts, CType *d, uint8_t *dp, uc_value_t *uv, + uc_value_t **refs) +{ + CTInfo info = d->info; + CTSize pos, bsz; + uint32_t val = 0, mask; + uc_assertCTS(ctype_isbitfield(info), "bitfield expected"); + if ((info & CTF_BOOL)) + { + uint8_t tmpbool = 0; + uc_assertCTS(ctype_bitbsz(info) == 1, "bad bool bitfield size"); + if (!uc_cconv_ct_tv(cts, ctype_get(cts, CTID_BOOL), &tmpbool, uv, 0, refs)) + return false; + val = tmpbool; + } + else + { + CTypeID did = (info & CTF_UNSIGNED) ? CTID_UINT32 : CTID_INT32; + if (!uc_cconv_ct_tv(cts, ctype_get(cts, did), (uint8_t *)&val, uv, 0, refs)) + return false; + } + pos = ctype_bitpos(info); + bsz = ctype_bitbsz(info); + uc_assertCTS(pos < 8 * ctype_bitcsz(info), "bad bitfield position"); + uc_assertCTS(bsz > 0 && bsz <= 8 * ctype_bitcsz(info), "bad bitfield size"); + /* Check if a packed bitfield crosses a container boundary. */ + if (pos + bsz > 8 * ctype_bitcsz(info)) { + //lj_err_caller(cts->L, LJ_ERR_FFI_NYIPACKBIT); + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "packed bit fields are not implemented yet"); + + return false; + } + mask = ((1u << bsz) - 1u) << pos; + val = (val << pos) & mask; + /* NYI: packed bitfields may cause misaligned reads/writes. */ + switch (ctype_bitcsz(info)) + { + case 4: + *(uint32_t *)dp = (*(uint32_t *)dp & ~mask) | (uint32_t)val; + break; + case 2: + *(uint16_t *)dp = (*(uint16_t *)dp & ~mask) | (uint16_t)val; + break; + case 1: + *(uint8_t *)dp = (*(uint8_t *)dp & ~mask) | (uint8_t)val; + break; + default: + uc_assertCTS(0, "bad bitfield container size %d", ctype_bitcsz(info)); + break; + } + + return true; +} + +/* -- Initialize C type with TValues -------------------------------------- */ + +/* Initialize an array with TValues. */ +static bool cconv_array_init(CTState *cts, CType *d, CTSize sz, uint8_t *dp, + uc_value_t **uv, size_t len, uc_value_t **refs) +{ + CType *dc = ctype_rawchild(cts, d); /* Array element type. */ + CTSize ofs, esize = dc->size; + size_t i; + if (len * esize > sz) + return cconv_err_initov(cts, d); + for (i = 0, ofs = 0; i < len; i++, ofs += esize) + if (!uc_cconv_ct_tv(cts, dc, dp + ofs, uv[i], 0, refs)) + return false; + if (ofs == esize) + { /* Replicate a single element. */ + for (; ofs < sz; ofs += esize) + memcpy(dp + ofs, dp, esize); + } + else + { /* Otherwise fill the remainder with zero. */ + memset(dp + ofs, 0, sz - ofs); + } + + return true; +} + +/* Initialize a sub-struct/union with TValues. */ +static bool cconv_substruct_init(CTState *cts, CType *d, uint8_t *dp, + uc_value_t **uv, size_t len, size_t *ip, + uc_value_t **refs) +{ + CTypeID id = d->sib; + while (id) + { + CType *df = ctype_get(cts, id); + id = df->sib; + if (ctype_isfield(df->info) || ctype_isbitfield(df->info)) + { + size_t i = *ip; + if (!df->uv_name) + continue; /* Ignore unnamed fields. */ + if (i >= len) + break; + *ip = i + 1; + + if (ctype_isfield(df->info)) { + if (!uc_cconv_ct_tv(cts, ctype_rawchild(cts, df), + dp + df->size, uv[i], 0, refs)) + return false; + } + else { + if (!uc_cconv_bf_tv(cts, df, dp + df->size, uv[i], refs)) + return false; + } + + if ((d->info & CTF_UNION)) + break; + } + else if (ctype_isxattrib(df->info, CTA_SUBTYPE)) + { + if (!cconv_substruct_init(cts, ctype_rawchild(cts, df), + dp + df->size, uv, len, ip, refs)) + return false; + + if ((d->info & CTF_UNION)) + break; + } /* Ignore all other entries in the chain. */ + } + + return true; +} + +/* Initialize a struct/union with TValues. */ +static bool cconv_struct_init(CTState *cts, CType *d, CTSize sz, uint8_t *dp, + uc_value_t **uv, size_t len, uc_value_t **refs) +{ + size_t i = 0; + + memset(dp, 0, sz); /* Much simpler to clear the struct first. */ + + if (!cconv_substruct_init(cts, d, dp, uv, len, &i, refs)) + return false; + + if (i < len) + return cconv_err_initov(cts, d); + + return true; +} + +/* Initialize a struct/union with a ucode object. */ +static bool cconv_struct_object_init(CTState *cts, CType *d, CTSize sz, uint8_t *dp, + uc_value_t *obj, uc_value_t **refs) +{ + CTypeID id = d->sib; + + memset(dp, 0, sz); + + while (id) + { + CType *df = ctype_get(cts, id); + id = df->sib; + + if (ctype_isfield(df->info) || ctype_isbitfield(df->info)) + { + if (!df->uv_name) + continue; + + uc_value_t *uv = ucv_object_get(obj, ucv_string_get(df->uv_name), NULL); + + if (uv) + { + if (ctype_isfield(df->info)) { + if (!uc_cconv_ct_tv(cts, ctype_rawchild(cts, df), + dp + df->size, uv, 0, refs)) + return false; + } + else { + if (!uc_cconv_bf_tv(cts, df, dp + df->size, uv, refs)) + return false; + } + } + + if ((d->info & CTF_UNION)) + break; + } + else if (ctype_isxattrib(df->info, CTA_SUBTYPE)) + { + CType *child = ctype_rawchild(cts, df); + if (!cconv_struct_object_init(cts, child, child->size, + dp + df->size, obj, refs)) + return false; + + if ((d->info & CTF_UNION)) + break; + } + } + + return true; +} + +/* Check whether to use a multi-value initializer. +** This is true if an aggregate is to be initialized with a value. +** Valarrays are treated as values here so ct_tv handles (V|C, I|F). +*/ +int uc_cconv_multi_init(CTState *cts, CType *d, uc_value_t *uv) +{ + uc_type_t ut = ucv_type(uv); + GCcdata *cd = (ut == UC_RESOURCE) ? ucv_resource_data(uv, "ffi.ctype") : NULL; + + if (!(ctype_isrefarray(d->info) || ctype_isstruct(d->info))) + return 0; /* Destination is not an aggregate. */ + if (ut == UC_ARRAY || ut == UC_OBJECT || (ut == UC_STRING && !ctype_isstruct(d->info))) + return 0; /* Initializer is not a value. */ + if (cd && uc_ctype_rawref(cts, cd->ctypeid) == d) + return 0; /* Source and destination are identical aggregates. */ + return 1; /* Otherwise the initializer is a value. */ +} + +/* Initialize C type with TValues. Caveat: expects to get the raw CType! */ +bool uc_cconv_ct_init(CTState *cts, CType *d, CTSize sz, + uint8_t *dp, uc_value_t **uv, size_t len, uc_value_t **refs) +{ + if (len == 0) + return memset(dp, 0, sz); + else if (len == 1 && !uc_cconv_multi_init(cts, d, *uv)) + return uc_cconv_ct_tv(cts, d, dp, *uv, 0, refs); + else if (ctype_isarray(d->info)) /* Also handles valarray init with len>1. */ + return cconv_array_init(cts, d, sz, dp, uv, len, refs); + else if (ctype_isstruct(d->info)) { + if (len == 1 && ucv_type(*uv) == UC_OBJECT) + return cconv_struct_object_init(cts, d, sz, dp, *uv, refs); + return cconv_struct_init(cts, d, sz, dp, uv, len, refs); + } + else + return cconv_err_initov(cts, d); +} diff --git a/lib/ffi/uc_cconv.h b/lib/ffi/uc_cconv.h new file mode 100644 index 00000000..8d26da57 --- /dev/null +++ b/lib/ffi/uc_cconv.h @@ -0,0 +1,124 @@ +/* +** C type conversions. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This header contains derived work from LuaJIT's FFI conversion machinery. +** +** Modifications: +** - Adapted for ucode VM integration +** - Removed JIT-specific dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#ifndef _UC_CCONV_H +#define _UC_CCONV_H + +#include "uc_ctype.h" + +/* Compressed C type index. ORDER CCX. */ +enum { + CCX_B, /* Bool. */ + CCX_I, /* Integer. */ + CCX_F, /* Floating-point number. */ + CCX_C, /* Complex. */ + CCX_V, /* Vector. */ + CCX_P, /* Pointer. */ + CCX_A, /* Refarray. */ + CCX_S /* Struct/union. */ +}; + +/* Convert C type info to compressed C type index. ORDER CT. ORDER CCX. */ +static UC_AINLINE uint32_t cconv_idx(CTInfo info) +{ + uint32_t idx = ((info >> 26) & 15u); /* Dispatch bits. */ + uc_assertX(ctype_type(info) <= CT_MAYCONVERT, + "cannot convert ctype %08x", info); +#if UC_64 + idx = ((uint32_t)(0xf436fff5fff7f021ULL >> 4*idx) & 15u); +#else + idx = (((idx < 8 ? 0xfff7f021u : 0xf436fff5) >> 4*(idx & 7u)) & 15u); +#endif + uc_assertX(idx < 8, "cannot convert ctype %08x", info); + return idx; +} + +#define cconv_idx2(dinfo, sinfo) \ + ((cconv_idx((dinfo)) << 3) + cconv_idx((sinfo))) + +#define CCX(dst, src) ((CCX_##dst << 3) + CCX_##src) + +/* Conversion flags. */ +#define CCF_CAST 0x00000001u +#define CCF_FROMTV 0x00000002u +#define CCF_SAME 0x00000004u +#define CCF_IGNQUAL 0x00000008u + +#define CCF_ARG_SHIFT 8 +#define CCF_ARG(n) ((n) << CCF_ARG_SHIFT) +#define CCF_GETARG(f) ((f) >> CCF_ARG_SHIFT) + +static inline bool +uc_cconv_needref(uc_value_t *arg) +{ + switch (ucv_type(arg)) { + case UC_STRING: + case UC_ARRAY: + case UC_OBJECT: + case UC_CLOSURE: + case UC_CFUNCTION: + return true; + + default: + return false; + } +} + +static inline uc_value_t ** +uc_cconv_uvref(uc_value_t ***args, uc_value_t ***refs) +{ + uc_value_t **arg = (*args)++; + + return uc_cconv_needref(*arg) ? (*refs)++ : arg; +} + +static inline uc_value_t * +uc_cconv_addref(uc_value_t **refs, uc_value_t *uv) +{ + if (!refs || ucv_type(uv) != UC_STRING) + return uv; + + /* turn tagged pointer short string into heap string */ + if ((uintptr_t)uv & 3) { + uc_string_t *ustr = xalloc(sizeof(uc_string_t) + ucv_string_length(uv) + 1); + + ustr->length = ucv_string_length(uv); + ustr->header.type = UC_STRING; + + memcpy(ustr->str, ucv_string_get(uv), ustr->length); + + uv = &ustr->header; + } + + if (!*refs) + *refs = ucv_array_new(NULL); + + return ucv_array_push(*refs, ucv_get(uv)); +} + +UC_NOAPI int uc_cconv_compatptr(CTState *cts, CType *d, CType *s, CTInfo flags); +UC_NOAPI bool uc_cconv_ct_ct(CTState *cts, CType *d, CType *s, + uint8_t *dp, uint8_t *sp, CTInfo flags); +UC_NOAPI int uc_cconv_tv_ct(CTState *cts, CType *s, CTypeID sid, + uc_value_t **uv, uint8_t *sp); +UC_NOAPI int uc_cconv_tv_bf(CTState *cts, CType *s, + uc_value_t **uv, uint8_t *sp); +UC_NOAPI bool uc_cconv_ct_tv(CTState *cts, CType *d, + uint8_t *dp, uc_value_t *uv, CTInfo flags, uc_value_t **refs); +UC_NOAPI bool uc_cconv_bf_tv(CTState *cts, CType *d, uint8_t *dp, + uc_value_t *uv, uc_value_t **refs); +UC_NOAPI int uc_cconv_multi_init(CTState *cts, CType *d, uc_value_t *uv); +UC_NOAPI bool uc_cconv_ct_init(CTState *cts, CType *d, CTSize sz, + uint8_t *dp, uc_value_t **uv, size_t len, uc_value_t **refs); + +#endif diff --git a/lib/ffi/uc_cdata.c b/lib/ffi/uc_cdata.c new file mode 100644 index 00000000..182cf398 --- /dev/null +++ b/lib/ffi/uc_cdata.c @@ -0,0 +1,326 @@ +/* +** C data management. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This file contains derived work from LuaJIT's FFI C data objects (lj_cdata.c). +** +** Modifications: +** - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) +** - Adapted C data allocation to use ucode resource system (uc_cdata_new, etc.) +** - Removed JIT-specific code and dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + + +#include "uc_ctype.h" +#include "uc_cconv.h" +#include "uc_cdata.h" + +/* -- C data allocation --------------------------------------------------- */ + +/* Allocate a new C data object holding a reference to another object. */ +uc_value_t *uc_cdata_newref(uc_vm_t *vm, const void *p, CTypeID id) +{ + CTypeID refid = uc_ctype_intern(ctype_cts(vm), CTINFO_REF(id), CTSIZE_PTR); + uc_value_t *res = uc_cdata_new(vm, refid, CTSIZE_PTR); + + *(const void **)uc_cdata_dataptr(res) = p; + + return res; +} + +/* Allocate variable-sized or specially aligned C data object. */ +uc_value_t *uc_cdata_newv(uc_vm_t *vm, CTypeID id, CTSize sz, CTSize align) +{ + // global_State *g; + // FIXME: non-power-of-two alignments? + if (align < 1) align = 1; // Ensure minimum alignment + size_t hdrsize = (sizeof(uc_resource_t) + align - 1) & -align; + size_t extra = sizeof(GCcdataVar) + sizeof(GCcdata) + + (align > CT_MEMALIGN ? (1u << align) - (1u << CT_MEMALIGN) : 0); + + uc_resource_t *res = xalloc(hdrsize + sz + extra); + res->header.type = UC_RESOURCE; + res->header.refcount = 1; + res->type = ucv_resource_type_lookup(vm, "ffi.ctype"); + + char *p = (char *)res + hdrsize; + uintptr_t adata = (uintptr_t)p + sizeof(GCcdataVar) + sizeof(GCcdata); + uintptr_t almask = (1u << align) - 1u; + GCcdata *cd = (GCcdata *)(((adata + almask) & ~almask) - sizeof(GCcdata)); + cdatav(cd)->offset = (uint16_t)((char *)cd - p); + cdatav(cd)->extra = extra; + cdatav(cd)->len = sz; + cd->ctypeid = id; + cd->isvla = 1; + cd->refs = NULL; + + res->data = cd; + + return &res->header; +} + +/* Allocate arbitrary C data object. */ +uc_value_t *uc_cdata_newx(uc_vm_t *vm, CTypeID id, CTSize sz, CTInfo info) +{ + if (!(info & CTF_VLA) && ctype_align(info) <= CT_MEMALIGN) + return uc_cdata_new(vm, id, sz); + else + return uc_cdata_newv(vm, id, sz, ctype_align(info)); +} + +/* -- C data indexing ----------------------------------------------------- */ + +/* Index C data by a TValue. Return CType and pointer. */ +CType *uc_cdata_index(CTState *cts, GCcdata *cd, uc_value_t *key, uint8_t **pp, + CTInfo *qual) +{ + uint8_t *p = (uint8_t *)cdataptr(cd); + CType *ct = ctype_get(cts, cd->ctypeid); + ptrdiff_t idx; + uc_type_t ut = ucv_type(key); + GCcdata *cdk = (ut == UC_RESOURCE) ? ucv_resource_data(key, "ffi.ctype") : NULL; + + /* Skip extern and attribute wrappers */ + while (ctype_isextern(ct->info) || ctype_isattrib(ct->info)) + ct = ctype_child(cts, ct); + + /* Resolve reference for cdata object. */ + if (ctype_isref(ct->info)) + { + uc_assertCTS(ct->size == CTSIZE_PTR, "ref is not pointer-sized"); + p = *(uint8_t **)p; + ct = ctype_child(cts, ct); + } + +collect_attrib: + /* Skip any remaining attributes and collect qualifiers. */ + while (ctype_isattrib(ct->info)) + { + if (ctype_attrib(ct->info) == CTA_QUAL) + *qual |= ct->size; + ct = ctype_child(cts, ct); + } + /* Interning rejects refs to refs. */ + uc_assertCTS(!ctype_isref(ct->info), "bad ref of ref"); + + if (ut == UC_INTEGER) + { + idx = (ptrdiff_t)ucv_int64_get(key); + goto integer_key; + } + else if (ut == UC_DOUBLE) + { /* Numeric key. */ + double d = ucv_double_get(key); + idx = UC_64 ? (ptrdiff_t)d : (ptrdiff_t)uc_num2int(d); + integer_key: + if (ctype_ispointer(ct->info) || ctype_isrefarray(ct->info)) + { + CTSize sz = uc_ctype_size(cts, ctype_cid(ct->info)); /* Element size. */ + if (sz == CTSIZE_INVALID) { + uc_vm_raise_exception(cts->vm, EXCEPTION_TYPE, + "size of C type is unknown or too large"); + + return NULL; + } + if (ctype_isptr(ct->info)) + { + p = (uint8_t *)cdata_getptr(p, ct->size); + } + else if ((ct->info & (CTF_VECTOR | CTF_COMPLEX))) + { + if ((ct->info & CTF_COMPLEX)) + idx &= 1; + *qual |= CTF_CONST; /* Valarray elements are constant. */ + } + *pp = p + idx * (int32_t)sz; + return ct; + } + } + else if (cdk) + { /* Integer cdata key. */ + CType *ctk = ctype_raw(cts, cdk->ctypeid); + if (ctype_isenum(ctk->info)) + ctk = ctype_child(cts, ctk); + if (ctype_isinteger(ctk->info)) + { + uc_cconv_ct_ct(cts, ctype_get(cts, CTID_INT_PSZ), ctk, + (uint8_t *)&idx, cdataptr(cdk), 0); + goto integer_key; + } + } + else if (ut == UC_STRING) + { /* String key. */ + if (ctype_isstruct(ct->info)) + { + CTSize ofs; + CType *fct = uc_ctype_getfieldq(cts, ct, key, &ofs, qual); + if (fct) + { + *pp = p + ofs; + return fct; + } + } + else if (ctype_iscomplex(ct->info)) + { + if (ucv_string_length(key) == 2) + { + char *name = ucv_string_get(key); + + *qual |= CTF_CONST; /* Complex fields are constant. */ + if (name[0] == 'r' && name[1] == 'e') + { + *pp = p; + return ct; + } + else if (name[0] == 'i' && name[1] == 'm') + { + *pp = p + (ct->size >> 1); + return ct; + } + } + } + else if (cd->ctypeid == CTID_CTYPEID) + { + /* Allow indexing a (pointer to) struct constructor to get constants. */ + CType *sct = ctype_raw(cts, *(CTypeID *)p); + if (ctype_isptr(sct->info)) + sct = ctype_rawchild(cts, sct); + if (ctype_isstruct(sct->info)) + { + CTSize ofs; + CType *fct = uc_ctype_getfield(cts, sct, key, &ofs); + if (fct && ctype_isconstval(fct->info)) + return fct; + } + ct = sct; /* Allow resolving metamethods for constructors, too. */ + } + } + if (ctype_isptr(ct->info)) + { /* Automatically perform '->'. */ + if (ctype_isstruct(ctype_rawchild(cts, ct)->info)) + { + p = (uint8_t *)cdata_getptr(p, ct->size); + ct = ctype_child(cts, ct); + goto collect_attrib; + } + } + *qual |= 1; /* Lookup failed. */ + return ct; /* But return the resolved raw type. */ +} + +/* -- C data getters ------------------------------------------------------ */ + +/* Get constant value and convert to TValue. */ +static void cdata_getconst(CTState *cts, uc_value_t **uv, CType *ct) +{ + CType *ctt = ctype_child(cts, ct); + uc_assertCTS(ctype_isinteger(ctt->info) && ctt->size <= 4, + "only 32 bit const supported"); /* NYI */ + + ucv_put(*uv); + + /* Constants are already zero-extended/sign-extended to 32 bits. */ + if ((ctt->info & CTF_UNSIGNED) && (int32_t)ct->size < 0) + *uv = ucv_int64_new((int64_t)(uint32_t)ct->size); + else + *uv = ucv_int64_new((int64_t)(int32_t)ct->size); +} + +/* Get C data value and convert to TValue. */ +int uc_cdata_get(CTState *cts, CType *s, uc_value_t **uv, uint8_t *sp) +{ + CTypeID sid; + + if (ctype_isconstval(s->info)) + { + cdata_getconst(cts, uv, s); + return 0; /* No GC step needed. */ + } + else if (ctype_isbitfield(s->info)) + { + return uc_cconv_tv_bf(cts, s, uv, sp); + } + + /* Get child type of pointer/array/field. */ + uc_assertCTS(ctype_ispointer(s->info) || ctype_isfield(s->info), + "pointer or field expected"); + sid = ctype_cid(s->info); + s = ctype_get(cts, sid); + + /* Resolve reference for field. */ + if (ctype_isref(s->info)) + { + uc_assertCTS(s->size == CTSIZE_PTR, "ref is not pointer-sized"); + sp = *(uint8_t **)sp; + sid = ctype_cid(s->info); + s = ctype_get(cts, sid); + } + + /* Skip attributes. */ + while (ctype_isattrib(s->info)) + s = ctype_child(cts, s); + + return uc_cconv_tv_ct(cts, s, sid, uv, sp); +} + +/* -- C data setters ------------------------------------------------------ */ + +/* Convert TValue and set C data value. */ +void uc_cdata_set(CTState *cts, CType *d, uint8_t *dp, uc_value_t *uv, CTInfo qual, uc_value_t **refs) +{ + if (ctype_isconstval(d->info)) + { + goto err_const; + } + else if (ctype_isbitfield(d->info)) + { + if (((d->info | qual) & CTF_CONST)) + goto err_const; + uc_cconv_bf_tv(cts, d, dp, uv, refs); + return; + } + + /* Get child type of pointer/array/field. */ + uc_assertCTS(ctype_ispointer(d->info) || ctype_isfield(d->info), + "pointer or field expected"); + d = ctype_child(cts, d); + + /* Resolve reference for field. */ + if (ctype_isref(d->info)) + { + uc_assertCTS(d->size == CTSIZE_PTR, "ref is not pointer-sized"); + dp = *(uint8_t **)dp; + d = ctype_child(cts, d); + } + + /* Skip attributes and collect qualifiers. */ + for (;;) + { + if (ctype_isattrib(d->info)) + { + if (ctype_attrib(d->info) == CTA_QUAL) + qual |= d->size; + } + else + { + break; + } + d = ctype_child(cts, d); + } + + uc_assertCTS(ctype_hassize(d->info), "store to ctype without size"); + uc_assertCTS(!ctype_isvoid(d->info), "store to void type"); + + if (((d->info | qual) & CTF_CONST)) + { + err_const: + uc_vm_raise_exception(cts->vm, EXCEPTION_REFERENCE, + "attempt to write to constant location"); + + return; + } + + uc_cconv_ct_tv(cts, d, dp, uv, 0, refs); +} diff --git a/lib/ffi/uc_cdata.h b/lib/ffi/uc_cdata.h new file mode 100644 index 00000000..a680fc14 --- /dev/null +++ b/lib/ffi/uc_cdata.h @@ -0,0 +1,117 @@ +/* +** C data management. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This header contains derived work from LuaJIT's FFI C data objects. +** +** Modifications: +** - Adapted for ucode VM integration (uc_cdata_new, uc_cdata_dataptr, etc.) +** - Removed JIT-specific dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#ifndef _UC_CDATA_H +#define _UC_CDATA_H + +#include "uc_ctype.h" + +/* Get C data pointer. */ +static UC_AINLINE void *cdata_getptr(void *p, CTSize sz) +{ + if (UC_64 && sz == 4) + { /* Support 32 bit pointers on 64 bit targets. */ + return ((void *)(uintptr_t) * (uint32_t *)p); + } + else + { + uc_assertX(sz == CTSIZE_PTR, "bad pointer size %d", sz); + return *(void **)p; + } +} + +/* Set C data pointer. */ +static UC_AINLINE void cdata_setptr(void *p, CTSize sz, const void *v) +{ + if (UC_64 && sz == 4) + { /* Support 32 bit pointers on 64 bit targets. */ + *(uint32_t *)p = (uint32_t)(uintptr_t)v; + } + else + { + uc_assertX(sz == CTSIZE_PTR, "bad pointer size %d", sz); + *(void **)p = (void *)v; + } +} + +/* Allocate fixed-size C data object. */ +static inline uc_value_t * +uc_cdata_new(uc_vm_t *vm, CTypeID id, CTSize sz) +{ + CTState *cts = ctype_cts(vm); + uc_resource_t *res; + GCcdata *cd; + +#ifdef LUA_USE_ASSERT + CType *ct = ctype_raw(cts, id); + uc_assertCTS((ctype_hassize(ct->info) ? ct->size : CTSIZE_PTR) == sz, + "inconsistent size of fixed-size cdata alloc"); +#endif + + res = xalloc(ALIGN(sizeof(*res)) + sizeof(*cd) + sz); + res->header.type = UC_RESOURCE; + res->header.refcount = 1; + res->type = ucv_resource_type_lookup(vm, "ffi.ctype"); + res->data = (char *)res + ALIGN(sizeof(*res)); + + cd = res->data; + cd->ctypeid = ctype_check(cts, id); + cd->isvla = 0; + cd->refs = NULL; + + return &res->header; +} + +/* Variant which works without a valid CTState. */ +static UC_AINLINE uc_value_t *uc_cdata_new_(uc_vm_t *vm, CTypeID id, CTSize sz) +{ + GCcdata *cd; + + cd = xalloc(sizeof(*cd) + sz); + cd->ctypeid = id; + cd->isvla = 0; + cd->refs = NULL; + + return ucv_resource_new(ucv_resource_type_lookup(vm, "ffi.ctype"), cd); +} + +UC_NOAPI uc_value_t *uc_cdata_newref(uc_vm_t *vm, const void *pp, CTypeID id); +UC_NOAPI uc_value_t *uc_cdata_newv(uc_vm_t *vm, CTypeID id, CTSize sz, + CTSize align); +UC_NOAPI uc_value_t *uc_cdata_newx(uc_vm_t *vm, CTypeID id, CTSize sz, + CTInfo info); + +static inline void * +uc_cdata_dataptr(uc_value_t *val) +{ + if (ucv_type(val) != UC_RESOURCE) + return NULL; + + uc_resource_t *res = (uc_resource_t *)val; + + if (!res->type || strcmp(res->type->name, "ffi.ctype") != 0) + return NULL; + + return cdataptr((GCcdata *)res->data); +} + +#define uc_cdataptr(res) cdataptr(((uc_resource_t *)res)->data) + + +UC_NOAPI CType *uc_cdata_index(CTState *cts, GCcdata *cd, uc_value_t *key, + uint8_t **pp, CTInfo *qual); +UC_NOAPI int uc_cdata_get(CTState *cts, CType *s, uc_value_t **uv, uint8_t *sp); +UC_NOAPI void uc_cdata_set(CTState *cts, CType *d, uint8_t *dp, uc_value_t *uv, + CTInfo qual, uc_value_t **refs); + +#endif diff --git a/lib/ffi/uc_cparse.c b/lib/ffi/uc_cparse.c new file mode 100644 index 00000000..f6d76ed6 --- /dev/null +++ b/lib/ffi/uc_cparse.c @@ -0,0 +1,3020 @@ +/* +** C declaration parser. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This file contains derived work from LuaJIT's FFI C parser (uc_cparse.c). +** +** Modifications: +** - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) +** - Removed JIT-specific code and dependencies +** - Adapted error handling to use ucode exceptions +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#include +#include +#include "uc_ctype.h" +#include "uc_cparse.h" + +#include "ucode/util.h" + +/* +** Important note: this is NOT a validating C parser! This is a minimal +** C declaration parser, solely for use by the LuaJIT FFI. +** +** It ought to return correct results for properly formed C declarations, +** but it may accept some invalid declarations, too (and return nonsense). +** Also, it shows rather generic error messages to avoid unnecessary bloat. +** If in doubt, please check the input against your favorite C compiler. +*/ + +/* Assertions disabled for production build. */ +#define uc_assertCP(c, ...) ((void)0) + +/* Check if two function types are structurally equivalent. */ +static bool +ctype_func_is_equiv(CTState *cts, CType *ct1, CType *ct2) +{ + CType *p1, *p2; + + if (ct1->info != ct2->info || ct1->size != ct2->size) { + return false; + } + + /* Compare parameter chains. */ + p1 = ctype_get(cts, ct1->sib); + p2 = ctype_get(cts, ct2->sib); + + while (p1 && p2) { + /* Stop at non-field entries (end of parameters or attributes). */ + if (!ctype_isfield(p1->info) || !ctype_isfield(p2->info)) + break; + if (p1->info != p2->info || p1->size != p2->size) { + return false; + } + p1 = ctype_get(cts, p1->sib); + p2 = ctype_get(cts, p2->sib); + } + + return (p1 == p2); +} + +/* -- Miscellaneous ------------------------------------------------------- */ + +/* Match string against a C literal. */ +#define cp_str_is(str, k) \ + (ucv_string_length(str) == sizeof(k) - 1 && !memcmp(ucv_string_get(str), k, sizeof(k) - 1)) + +/* Check string against a linear list of matches. */ +int uc_cparse_case(uc_value_t *str, const char *match) +{ + size_t len; + int n; + for (n = 0; (len = *match++); n++, match += len) + { + if (ucv_string_length(str) == len && !memcmp(match, ucv_string_get(str), len)) + return n; + } + return -1; +} + +/* -- C lexer ------------------------------------------------------------- */ + +/* C lexer token names. */ +static const char *const ctoknames[] = { +#define CTOKSTR(name, str) str, + CTOKDEF(CTOKSTR) +#undef CTOKSTR + NULL}; + +/* Forward declaration. */ +static void cp_err(CPState *cp, const char *em); + +static const char *cp_tok2str(CPState *cp, CPToken tok) +{ + char *e; + uc_assertCP(tok < CTOK_FIRSTDECL, "bad CPToken %d", tok); + if (tok > CTOK_OFS) + return ctoknames[tok - CTOK_OFS - 1]; + else if (!iscntrl(tok)) + { + xasprintf(&e, "%c", tok); + return e; + } + else + { + xasprintf(&e, "char(%d)", tok); + return e; + } +} + +/* End-of-line? */ +static UC_AINLINE int cp_iseol(CPChar c) +{ + return (c == '\n' || c == '\r'); +} + +/* Peek next raw character. */ +static UC_AINLINE CPChar cp_rawpeek(CPState *cp) +{ + return (CPChar)(uint8_t)(*cp->p); +} + +static UC_NOINLINE CPChar cp_get_bs(CPState *cp); + +/* Get next character. */ +static UC_AINLINE CPChar cp_get(CPState *cp) +{ + cp->c = (CPChar)(uint8_t)(*cp->p++); + if (UC_LIKELY(cp->c != '\\')) + return cp->c; + return cp_get_bs(cp); +} + +/* Transparently skip backslash-escaped line breaks. */ +static UC_NOINLINE CPChar cp_get_bs(CPState *cp) +{ + CPChar c2, c = cp_rawpeek(cp); + if (!cp_iseol(c)) + return cp->c; + cp->p++; + c2 = cp_rawpeek(cp); + if (cp_iseol(c2) && c2 != c) + cp->p++; + cp->linenumber++; + return cp_get(cp); +} + +/* Save character in buffer. */ +static UC_AINLINE void cp_save(CPState *cp, CPChar c) +{ + sprintbuf(&cp->pb, "%c", c); +} + +/* Skip line break. Handles "\n", "\r", "\r\n" or "\n\r". */ +static void cp_newline(CPState *cp) +{ + CPChar c = cp_rawpeek(cp); + if (cp_iseol(c) && c != cp->c) + cp->p++; + cp->linenumber++; +} + +static void __attribute__((format(printf, 3, 0))) +cp_errmsg(CPState *cp, CPToken tok, const char *em, ...) +{ + const char *tokstr; + char *s, *msg; + va_list argp; + + if (cp->error) + return; + + if (tok == 0) + { + tokstr = NULL; + } + else if (tok == CTOK_IDENT || tok == CTOK_INTEGER || tok == CTOK_STRING || + tok >= CTOK_FIRSTDECL) + { + if (cp->pb.bpos == 0) + cp_save(cp, '$'); + cp_save(cp, '\0'); + tokstr = cp->pb.buf; + } + else + { + tokstr = cp_tok2str(cp, tok); + } + va_start(argp, em); + xvasprintf(&msg, em, argp); + va_end(argp); + if (tokstr) + { + xasprintf(&s, "%s near '%s'", msg, tokstr); + free(msg); + msg = s; + } + if (cp->linenumber > 1) + { + xasprintf(&s, "%s at line %d", msg, cp->linenumber); + free(msg); + msg = s; + } + + cp->error = msg; +} + +static void cp_err_token(CPState *cp, CPToken tok) +{ + cp_errmsg(cp, cp->tok, "'%s' expected", cp_tok2str(cp, tok)); +} + +static void cp_err_badidx(CPState *cp, CType *ct) +{ + uc_value_t *s = uc_ctype_repr(cp->uv_vm, ctype_typeid(cp->cts, ct), NULL); + cp_errmsg(cp, 0, "'%s' cannot be indexed", ucv_string_get(s)); + ucv_put(s); +} + +static void cp_err(CPState *cp, const char *em) +{ + cp_errmsg(cp, 0, "%s", em); +} + +/* -- Main lexical scanner ------------------------------------------------ */ + +static inline bool is_ident(uint8_t c) +{ + return (c >= 48 && c <= 57) || + (c >= 65 && c <= 90) || + (c == '_') || + (c >= 97 && c <= 122) || + (c >= 128); +} + +/* Parse number literal. Only handles int32_t/uint32_t right now. */ +static CPToken cp_number(CPState *cp) +{ + unsigned long long val; + bool sign = false; + int base = 0; + char *s, *e; + + do + { + cp_save(cp, cp->c); + } while (is_ident(cp_get(cp))); + + cp_save(cp, '\0'); + + s = cp->pb.buf; + + while (isspace(*s)) + s++; + + if (*s == '-') + { + sign = true; + s++; + } + else if (*s == '+') + { + s++; + } + + if (*s == '0') + { + switch (s[1] | 32) + { + case 'x': + base = 16; + s += 2; + break; + + case 'o': + base = 8; + s += 2; + break; + + case 'b': + base = 2; + s += 2; + break; + } + } + + val = strtoull(s, &e, base); + + /* handle potential suffix */ + if (!strcasecmp(e, "ull") || !strcasecmp(e, "llu")) + { + if (sizeof(unsigned long long) > sizeof(uint32_t) && !(cp->mode & CPARSE_MODE_SKIP)) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + cp->val.id = CTID_UINT32; + e += 3; + } + else if (!strcasecmp(e, "ll")) + { + if (sizeof(long long) > sizeof(int32_t) && !(cp->mode & CPARSE_MODE_SKIP)) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + cp->val.id = CTID_INT32; + e += 2; + } + else if (!strcasecmp(e, "ul") || !strcasecmp(e, "lu")) + { + if (sizeof(unsigned long) > sizeof(uint32_t) && !(cp->mode & CPARSE_MODE_SKIP)) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + cp->val.id = CTID_UINT32; + e += 2; + } + else if ((*e | 32) == 'u') + { + cp->val.id = CTID_UINT32; + e++; + } + else if ((*e | 32) == 'l') + { + if (sizeof(long) > sizeof(int32_t) && !(cp->mode & CPARSE_MODE_SKIP)) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + cp->val.id = CTID_INT32; + e++; + } + else if ((*e | 32) == 'i') + { + if (!(cp->mode & CPARSE_MODE_SKIP)) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + e++; + } + else + { + cp->val.id = CTID_INT32; + } + + while (isspace(*e)) + e++; + + if (*e) + cp_errmsg(cp, CTOK_INTEGER, "malformed number"); + + cp->val.u32 = sign ? (uint32_t)-val : (uint32_t)val; + return CTOK_INTEGER; +} + +/* Parse identifier or keyword. */ +static CPToken cp_ident(CPState *cp) +{ + do + { + cp_save(cp, cp->c); + } while (is_ident(cp_get(cp))); + ucv_put(cp->uv_str); + cp->uv_str = ucv_string_new(cp->pb.buf); + // cp->str = lj_buf_str(cp->L, &cp->sb); + cp->val.id = uc_ctype_getname(cp->cts, &cp->ct, cp->uv_str, cp->tmask); + + if (ctype_type(cp->ct->info) == CT_KW) + return ctype_cid(cp->ct->info); + return CTOK_IDENT; +} + +/* Parse parameter. */ +static CPToken cp_param(CPState *cp) +{ + CPChar c = cp_get(cp); + // TValue *o = cp->param; + uc_value_t **uv = cp->uv_param; + if (is_ident(c) || c == '$') /* Reserve $xyz for future extensions. */ { + cp_errmsg(cp, c, "syntax error"); + return CTOK_EOF; + } + if (!uv || uv >= &cp->uv_vm->stack.entries[cp->uv_vm->stack.count]) { + cp_err(cp, "wrong number of type parameters"); + return CTOK_EOF; + } + cp->uv_param = uv + 1; + if (ucv_type(*uv) == UC_STRING) + { + ucv_put(cp->uv_str); + cp->uv_str = ucv_get(*uv); + cp->val.id = 0; + cp->ct = &cp->cts->vtab.entries[0]; + return CTOK_IDENT; + } + else if (ucv_type(*uv) == UC_INTEGER) + { + cp->val.i32 = (int32_t)ucv_int64_get(*uv); + cp->val.id = CTID_INT32; + return CTOK_INTEGER; + } + else + { + void *ctype = ucv_resource_dataptr(*uv, "ffi.ctype"); + if (!ctype) + cp_errmsg(cp, 0, "type parameter expected, got %s", ucv_typename(*uv)); + // lj_err_argtype(cp->L, (int)(o-cp->L->base)+1, "type parameter"); + cp->val.id = (CTypeID)(uintptr_t)ctype; + return '$'; + } +} + +/* Parse string or character constant. */ +static CPToken cp_string(CPState *cp) +{ + CPChar delim = cp->c; + cp_get(cp); + while (cp->c != delim) + { + CPChar c = cp->c; + if (c == '\0') { + cp_errmsg(cp, CTOK_EOF, "unfinished string"); + return CTOK_EOF; + } + if (c == '\\') + { + c = cp_get(cp); + switch (c) + { + case '\0': + cp_errmsg(cp, CTOK_EOF, "unfinished string"); + return CTOK_EOF; + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'v': + c = '\v'; + break; + case 'e': + c = 27; + break; + case 'x': + c = 0; + while (isxdigit(cp_get(cp))) + c = (c << 4) + (isdigit(cp->c) ? cp->c - '0' : (cp->c & 15) + 9); + cp_save(cp, (c & 0xff)); + continue; + default: + if (isdigit(c)) + { + c -= '0'; + if (isdigit(cp_get(cp))) + { + c = c * 8 + (cp->c - '0'); + if (isdigit(cp_get(cp))) + { + c = c * 8 + (cp->c - '0'); + cp_get(cp); + } + } + cp_save(cp, (c & 0xff)); + continue; + } + break; + } + } + cp_save(cp, c); + cp_get(cp); + } + cp_get(cp); + if (delim == '"') + { + // FIXME: consider ucv_stringbuf_new + ucv_put(cp->uv_str); + cp->uv_str = ucv_string_new(cp->pb.buf); + // cp->str = lj_buf_str(cp->L, &cp->sb); + return CTOK_STRING; + } + else + { + if (printbuf_length(&cp->pb) != 1) + cp_err_token(cp, '\''); + cp->val.i32 = (int32_t)(char)*cp->pb.buf; + cp->val.id = CTID_INT32; + return CTOK_INTEGER; + } +} + +/* Skip C comment. */ +static void cp_comment_c(CPState *cp) +{ + do + { + if (cp_get(cp) == '*') + { + do + { + if (cp_get(cp) == '/') + { + cp_get(cp); + return; + } + } while (cp->c == '*'); + } + if (cp_iseol(cp->c)) + cp_newline(cp); + } while (cp->c != '\0'); +} + +/* Skip C++ comment. */ +static void cp_comment_cpp(CPState *cp) +{ + while (!cp_iseol(cp_get(cp)) && cp->c != '\0') + ; +} + +/* Lexical scanner for C. Only a minimal subset is implemented. */ +static CPToken cp_next_(CPState *cp) +{ + //lj_buf_reset(&cp->sb); + if (cp->pb.buf) + printbuf_reset(&cp->pb); + + for (;;) + { + if (is_ident(cp->c)) + return isdigit(cp->c) ? cp_number(cp) : cp_ident(cp); + switch (cp->c) + { + case '\n': + case '\r': + cp_newline(cp); /* fallthrough. */ + case ' ': + case '\t': + case '\v': + case '\f': + cp_get(cp); + break; + case '"': + case '\'': + return cp_string(cp); + case '/': + if (cp_get(cp) == '*') + cp_comment_c(cp); + else if (cp->c == '/') + cp_comment_cpp(cp); + else + return '/'; + break; + case '|': + if (cp_get(cp) != '|') + return '|'; + cp_get(cp); + return CTOK_OROR; + case '&': + if (cp_get(cp) != '&') + return '&'; + cp_get(cp); + return CTOK_ANDAND; + case '=': + if (cp_get(cp) != '=') + return '='; + cp_get(cp); + return CTOK_EQ; + case '!': + if (cp_get(cp) != '=') + return '!'; + cp_get(cp); + return CTOK_NE; + case '<': + if (cp_get(cp) == '=') + { + cp_get(cp); + return CTOK_LE; + } + else if (cp->c == '<') + { + cp_get(cp); + return CTOK_SHL; + } + return '<'; + case '>': + if (cp_get(cp) == '=') + { + cp_get(cp); + return CTOK_GE; + } + else if (cp->c == '>') + { + cp_get(cp); + return CTOK_SHR; + } + return '>'; + case '-': + if (cp_get(cp) != '>') + return '-'; + cp_get(cp); + return CTOK_DEREF; + case '$': + return cp_param(cp); + case '\0': + return CTOK_EOF; + default: + { + CPToken c = cp->c; + cp_get(cp); + return c; + } + } + } +} + +static UC_NOINLINE CPToken cp_next(CPState *cp) +{ + return (cp->tok = cp_next_(cp)); +} + +/* -- C parser ------------------------------------------------------------ */ + +/* Namespaces for resolving identifiers. */ +#define CPNS_DEFAULT \ + ((1u << CT_KW) | (1u << CT_TYPEDEF) | (1u << CT_FUNC) | (1u << CT_EXTERN) | (1u << CT_CONSTVAL)) +#define CPNS_STRUCT ((1u << CT_KW) | (1u << CT_STRUCT) | (1u << CT_ENUM)) + +typedef CTypeID CPDeclIdx; /* Index into declaration stack. */ +typedef uint32_t CPscl; /* Storage class flags. */ + +/* Type declaration context. */ +typedef struct CPDecl +{ + CPDeclIdx top; /* Top of declaration stack. */ + CPDeclIdx pos; /* Insertion position in declaration chain. */ + CPDeclIdx specpos; /* Saved position for declaration specifier. */ + uint32_t mode; /* Declarator mode. */ + CPState *cp; /* C parser state. */ + CTypeID nameid; /* Existing typedef for declared identifier. */ + CTInfo attr; /* Attributes. */ + CTInfo fattr; /* Function attributes. */ + CTInfo specattr; /* Saved attributes. */ + CTInfo specfattr; /* Saved function attributes. */ + CTSize bits; /* Field size in bits (if any). */ + CType stack[CPARSE_MAX_DECLSTACK]; /* Type declaration stack. */ + uc_value_t *uv_name; + uc_value_t *uv_redir; +} CPDecl; + +/* Forward declarations. */ +static CPscl cp_decl_spec(CPState *cp, CPDecl *decl, CPscl scl); +static void cp_declarator(CPState *cp, CPDecl *decl); +static CTypeID cp_decl_abstract(CPState *cp); + +/* Initialize C parser state. Caller must set up: L, p, srcname, mode. */ +static void cp_init(CPState *cp) +{ + cp->error = NULL; + cp->linenumber = 1; + cp->depth = 0; + cp->curpack = 0; + cp->packstack[0] = 255; + cp->pb.bpos = 0; + cp->pb.buf = 0; + cp->pb.size = 0; + cp->uv_str = NULL; + // lj_buf_init(cp->L, &cp->sb); + uc_assertCP(cp->p != NULL, "uninitialized cp->p"); + cp_get(cp); /* Read-ahead first char. */ + cp->tok = 0; + cp->tmask = CPNS_DEFAULT; + cp_next(cp); /* Read-ahead first token. */ +} + +/* Cleanup C parser state. */ +static void cp_cleanup(CPState *cp) +{ + // global_State *g = G(cp->L); + // lj_buf_free(g, &cp->sb); + ucv_put(cp->uv_str); + free(cp->pb.buf); + free(cp->error); +} + +/* Check and consume optional token. */ +static int cp_opt(CPState *cp, CPToken tok) +{ + if (cp->tok == tok) + { + cp_next(cp); + return 1; + } + return 0; +} + +/* Check and consume token. */ +static void cp_check(CPState *cp, CPToken tok) +{ + if (cp->tok != tok) + cp_err_token(cp, tok); + cp_next(cp); +} + +/* Check if the next token may start a type declaration. */ +static int cp_istypedecl(CPState *cp) +{ + if (cp->tok >= CTOK_FIRSTDECL && cp->tok <= CTOK_LASTDECL) + return 1; + if (cp->tok == CTOK_IDENT && ctype_istypedef(cp->ct->info)) + return 1; + if (cp->tok == '$') + return 1; + return 0; +} + +/* -- Constant expression evaluator --------------------------------------- */ + +/* Forward declarations. */ +static void cp_expr_unary(CPState *cp, CPValue *k); +static void cp_expr_sub(CPState *cp, CPValue *k, int pri); + +/* Please note that type handling is very weak here. Most ops simply +** assume integer operands. Accessors are only needed to compute types and +** return synthetic values. The only purpose of the expression evaluator +** is to compute the values of constant expressions one would typically +** find in C header files. And again: this is NOT a validating C parser! +*/ + +/* Parse comma separated expression and return last result. */ +static void cp_expr_comma(CPState *cp, CPValue *k) +{ + do + { + cp_expr_sub(cp, k, 0); + if (cp->error) + return; + } while (cp_opt(cp, ',')); +} + +/* Parse sizeof/alignof operator. */ +static void cp_expr_sizeof(CPState *cp, CPValue *k, int wantsz) +{ + CTSize sz; + CTInfo info; + if (cp_opt(cp, '(')) + { + if (cp_istypedecl(cp)) + k->id = cp_decl_abstract(cp); + else + cp_expr_comma(cp, k); + cp_check(cp, ')'); + } + else + { + cp_expr_unary(cp, k); + } + info = uc_ctype_info_raw(cp->cts, k->id, &sz); + if (wantsz) + { + if (sz != CTSIZE_INVALID) + k->u32 = sz; + else if (k->id != CTID_A_CCHAR) /* Special case for sizeof("string"). */ + { + cp_err(cp, "size of C type is unknown or too large"); + return; + } + } + else + { + k->u32 = 1u << ctype_align(info); + } + k->id = CTID_UINT32; /* Really size_t. */ +} + +/* Parse prefix operators. */ +static void cp_expr_prefix(CPState *cp, CPValue *k) +{ + if (cp->tok == CTOK_INTEGER) + { + *k = cp->val; + cp_next(cp); + } + else if (cp_opt(cp, '+')) + { + cp_expr_unary(cp, k); /* Nothing to do (well, integer promotion). */ + } + else if (cp_opt(cp, '-')) + { + cp_expr_unary(cp, k); + k->i32 = (int32_t)(~(uint32_t)k->i32 + 1); + } + else if (cp_opt(cp, '~')) + { + cp_expr_unary(cp, k); + k->i32 = ~k->i32; + } + else if (cp_opt(cp, '!')) + { + cp_expr_unary(cp, k); + k->i32 = !k->i32; + k->id = CTID_INT32; + } + else if (cp_opt(cp, '(')) + { + if (cp_istypedecl(cp)) + { /* Cast operator. */ + CTypeID id = cp_decl_abstract(cp); + cp_check(cp, ')'); + cp_expr_unary(cp, k); + k->id = id; /* No conversion performed. */ + } + else + { /* Sub-expression. */ + cp_expr_comma(cp, k); + cp_check(cp, ')'); + } + } + else if (cp_opt(cp, '*')) + { /* Indirection. */ + CType *ct; + cp_expr_unary(cp, k); + if (cp->error) + return; + ct = uc_ctype_rawref(cp->cts, k->id); + if (!ctype_ispointer(ct->info)) + { + cp_err_badidx(cp, ct); + return; + } + k->u32 = 0; + k->id = ctype_cid(ct->info); + } + else if (cp_opt(cp, '&')) + { /* Address operator. */ + cp_expr_unary(cp, k); + k->id = uc_ctype_intern(cp->cts, CTINFO(CT_PTR, CTALIGN_PTR + k->id), + CTSIZE_PTR); + } + else if (cp_opt(cp, CTOK_SIZEOF)) + { + cp_expr_sizeof(cp, k, 1); + } + else if (cp_opt(cp, CTOK_ALIGNOF)) + { + cp_expr_sizeof(cp, k, 0); + } + else if (cp->tok == CTOK_IDENT) + { + if (ctype_type(cp->ct->info) == CT_CONSTVAL) + { + k->u32 = cp->ct->size; + k->id = ctype_cid(cp->ct->info); + } + else if (ctype_type(cp->ct->info) == CT_EXTERN) + { + k->u32 = cp->val.id; + k->id = ctype_cid(cp->ct->info); + } + else if (ctype_type(cp->ct->info) == CT_FUNC) + { + k->u32 = cp->val.id; + k->id = cp->val.id; + } + else + { + goto err_expr; + } + cp_next(cp); + } + else if (cp->tok == CTOK_STRING) + { + CTSize sz = ucv_string_length(cp->uv_str); + while (cp_next(cp) == CTOK_STRING) + sz += ucv_string_length(cp->uv_str); + k->u32 = sz + 1; + k->id = CTID_A_CCHAR; + } + else + { + err_expr: + cp_errmsg(cp, cp->tok, "unexpected symbol"); + } +} + +/* Parse postfix operators. */ +static void cp_expr_postfix(CPState *cp, CPValue *k) +{ + for (;;) + { + CType *ct; + if (cp_opt(cp, '[')) + { /* Array/pointer index. */ + CPValue k2; + cp_expr_comma(cp, &k2); + ct = uc_ctype_rawref(cp->cts, k->id); + if (!ctype_ispointer(ct->info)) + { + ct = uc_ctype_rawref(cp->cts, k2.id); + if (!ctype_ispointer(ct->info)) { + cp_err_badidx(cp, ct); + return; + } + } + cp_check(cp, ']'); + k->u32 = 0; + } + else if (cp->tok == '.' || cp->tok == CTOK_DEREF) + { /* Struct deref. */ + CTSize ofs; + CType *fct; + ct = uc_ctype_rawref(cp->cts, k->id); + if (cp->tok == CTOK_DEREF) + { + if (!ctype_ispointer(ct->info)) { + cp_err_badidx(cp, ct); + return; + } + ct = uc_ctype_rawref(cp->cts, ctype_cid(ct->info)); + } + cp_next(cp); + if (cp->tok != CTOK_IDENT) { + cp_err_token(cp, CTOK_IDENT); + return; + } + if (!ctype_isstruct(ct->info) || ct->size == CTSIZE_INVALID || + !(fct = uc_ctype_getfield(cp->cts, ct, cp->uv_str, &ofs)) || + ctype_isbitfield(fct->info)) + { + uc_value_t *s = uc_ctype_repr(cp->uv_vm, ctype_typeid(cp->cts, ct), NULL); + cp_errmsg(cp, 0, "'%s' has no member named '%s'", ucv_string_get(s), ucv_string_get(cp->uv_str)); + ucv_put(s); + + return; + } + ct = fct; + k->u32 = ctype_isconstval(ct->info) ? ct->size : 0; + cp_next(cp); + } + else + { + return; + } + k->id = ctype_cid(ct->info); + } +} + +/* Parse infix operators. */ +static void cp_expr_infix(CPState *cp, CPValue *k, int pri) +{ + CPValue k2; + k2.u32 = 0; + k2.id = 0; /* Silence the compiler. */ + for (;;) + { + switch (pri) + { + case 0: + if (cp_opt(cp, '?')) + { + CPValue k3; + cp_expr_comma(cp, &k2); /* Right-associative. */ + if (cp->error) + return; + cp_check(cp, ':'); + cp_expr_sub(cp, &k3, 0); + if (cp->error) + return; + k->u32 = k->u32 ? k2.u32 : k3.u32; + k->id = k2.id > k3.id ? k2.id : k3.id; + continue; + } + /* fallthrough */ + case 1: + if (cp_opt(cp, CTOK_OROR)) + { + cp_expr_sub(cp, &k2, 2); + if (cp->error) + return; + k->i32 = k->u32 || k2.u32; + k->id = CTID_INT32; + continue; + } + /* fallthrough */ + case 2: + if (cp_opt(cp, CTOK_ANDAND)) + { + cp_expr_sub(cp, &k2, 3); + if (cp->error) + return; + k->i32 = k->u32 && k2.u32; + k->id = CTID_INT32; + continue; + } + /* fallthrough */ + case 3: + if (cp_opt(cp, '|')) + { + cp_expr_sub(cp, &k2, 4); + if (cp->error) + return; + k->u32 = k->u32 | k2.u32; + goto arith_result; + } + /* fallthrough */ + case 4: + if (cp_opt(cp, '^')) + { + cp_expr_sub(cp, &k2, 5); + if (cp->error) + return; + k->u32 = k->u32 ^ k2.u32; + goto arith_result; + } + /* fallthrough */ + case 5: + if (cp_opt(cp, '&')) + { + cp_expr_sub(cp, &k2, 6); + if (cp->error) + return; + k->u32 = k->u32 & k2.u32; + goto arith_result; + } + /* fallthrough */ + case 6: + if (cp_opt(cp, CTOK_EQ)) + { + cp_expr_sub(cp, &k2, 7); + if (cp->error) + return; + k->i32 = k->u32 == k2.u32; + k->id = CTID_INT32; + continue; + } + else if (cp_opt(cp, CTOK_NE)) + { + cp_expr_sub(cp, &k2, 7); + if (cp->error) + return; + k->i32 = k->u32 != k2.u32; + k->id = CTID_INT32; + continue; + } + /* fallthrough */ + case 7: + if (cp_opt(cp, '<')) + { + cp_expr_sub(cp, &k2, 8); + if (cp->error) + return; + if (k->id == CTID_INT32 && k2.id == CTID_INT32) + k->i32 = k->i32 < k2.i32; + else + k->i32 = k->u32 < k2.u32; + k->id = CTID_INT32; + continue; + } + else if (cp_opt(cp, '>')) + { + cp_expr_sub(cp, &k2, 8); + if (cp->error) + return; + if (k->id == CTID_INT32 && k2.id == CTID_INT32) + k->i32 = k->i32 > k2.i32; + else + k->i32 = k->u32 > k2.u32; + k->id = CTID_INT32; + continue; + } + else if (cp_opt(cp, CTOK_LE)) + { + cp_expr_sub(cp, &k2, 8); + if (cp->error) + return; + if (k->id == CTID_INT32 && k2.id == CTID_INT32) + k->i32 = k->i32 <= k2.i32; + else + k->i32 = k->u32 <= k2.u32; + k->id = CTID_INT32; + continue; + } + else if (cp_opt(cp, CTOK_GE)) + { + cp_expr_sub(cp, &k2, 8); + if (cp->error) + return; + if (k->id == CTID_INT32 && k2.id == CTID_INT32) + k->i32 = k->i32 >= k2.i32; + else + k->i32 = k->u32 >= k2.u32; + k->id = CTID_INT32; + continue; + } + /* fallthrough */ + case 8: + if (cp_opt(cp, CTOK_SHL)) + { + cp_expr_sub(cp, &k2, 9); + if (cp->error) + return; + k->u32 = k->u32 << k2.u32; + continue; + } + else if (cp_opt(cp, CTOK_SHR)) + { + cp_expr_sub(cp, &k2, 9); + if (cp->error) + return; + if (k->id == CTID_INT32) + k->i32 = k->i32 >> k2.i32; + else + k->u32 = k->u32 >> k2.u32; + continue; + } + /* fallthrough */ + case 9: + if (cp_opt(cp, '+')) + { + cp_expr_sub(cp, &k2, 10); + if (cp->error) + return; + k->u32 = k->u32 + k2.u32; + arith_result: + if (k2.id > k->id) + k->id = k2.id; /* Trivial promotion to unsigned. */ + continue; + } + else if (cp_opt(cp, '-')) + { + cp_expr_sub(cp, &k2, 10); + if (cp->error) + return; + k->u32 = k->u32 - k2.u32; + goto arith_result; + } + /* fallthrough */ + case 10: + if (cp_opt(cp, '*')) + { + cp_expr_unary(cp, &k2); + if (cp->error) + return; + k->u32 = k->u32 * k2.u32; + goto arith_result; + } + else if (cp_opt(cp, '/')) + { + cp_expr_unary(cp, &k2); + if (cp->error) + return; + if (k2.id > k->id) + k->id = k2.id; /* Trivial promotion to unsigned. */ + if (k2.u32 == 0 || + (k->id == CTID_INT32 && k->u32 == 0x80000000u && k2.i32 == -1)) + { + cp_err(cp, "invalid value"); + return; + } + if (k->id == CTID_INT32) + k->i32 = k->i32 / k2.i32; + else + k->u32 = k->u32 / k2.u32; + continue; + } + else if (cp_opt(cp, '%')) + { + cp_expr_unary(cp, &k2); + if (cp->error) + return; + if (k2.id > k->id) + k->id = k2.id; /* Trivial promotion to unsigned. */ + if (k2.u32 == 0 || + (k->id == CTID_INT32 && k->u32 == 0x80000000u && k2.i32 == -1)) + { + cp_err(cp, "invalid value"); + return; + } + if (k->id == CTID_INT32) + k->i32 = k->i32 % k2.i32; + else + k->u32 = k->u32 % k2.u32; + continue; + } + default: + return; + } + } +} + +/* Parse and evaluate unary expression. */ +static void cp_expr_unary(CPState *cp, CPValue *k) +{ + if (++cp->depth > CPARSE_MAX_DECLDEPTH) + { + cp_err(cp, "chunk has too many syntax levels"); + return; + } + cp_expr_prefix(cp, k); + if (cp->error) + return; + cp_expr_postfix(cp, k); + cp->depth--; +} + +/* Parse and evaluate sub-expression. */ +static void cp_expr_sub(CPState *cp, CPValue *k, int pri) +{ + cp_expr_unary(cp, k); + if (cp->error) + return; + cp_expr_infix(cp, k, pri); +} + +/* Parse constant integer expression. */ +static void cp_expr_kint(CPState *cp, CPValue *k) +{ + CType *ct; + cp_expr_sub(cp, k, 0); + if (cp->error) + return; + ct = ctype_raw(cp->cts, k->id); + if (!ctype_isinteger(ct->info)) + { + cp_err(cp, "invalid value"); + return; + } +} + +/* Parse (non-negative) size expression. */ +static CTSize cp_expr_ksize(CPState *cp) +{ + CPValue k; + cp_expr_kint(cp, &k); + if (cp->error) + return CTSIZE_INVALID; + if (k.u32 >= 0x80000000u) + { + cp_err(cp, "size of C type is unknown or too large"); + return CTSIZE_INVALID; + } + return k.u32; +} + +/* -- Type declaration stack management ----------------------------------- */ + +/* Add declaration element behind the insertion position. */ +static CPDeclIdx cp_add(CPDecl *decl, CTInfo info, CTSize size) +{ + CPDeclIdx top = decl->top; + if (top >= CPARSE_MAX_DECLSTACK) + { + cp_err(decl->cp, "chunk has too many syntax levels"); + return 0; + } + decl->stack[top].info = info; + decl->stack[top].size = size; + decl->stack[top].sib = 0; + decl->stack[top].uv_name = NULL; + // setgcrefnull(decl->stack[top].name); + decl->stack[top].next = decl->stack[decl->pos].next; + decl->stack[decl->pos].next = (CTypeID1)top; + decl->top = top + 1; + return top; +} + +/* Push declaration element before the insertion position. */ +static CPDeclIdx cp_push(CPDecl *decl, CTInfo info, CTSize size) +{ + return (decl->pos = cp_add(decl, info, size)); +} + +/* Push or merge attributes. */ +static void cp_push_attributes(CPDecl *decl) +{ + CType *ct = &decl->stack[decl->pos]; + if (ctype_isfunc(ct->info)) + { /* Ok to modify in-place. */ +#if UC_TARGET_X86 + if ((decl->fattr & CTFP_CCONV)) + ct->info = (ct->info & (CTMASK_NUM | CTF_VARARG | CTMASK_CID)) + + (decl->fattr & ~CTMASK_CID); +#endif + } + else + { + if ((decl->attr & CTFP_ALIGNED) && !(decl->mode & CPARSE_MODE_FIELD)) + cp_push(decl, CTINFO(CT_ATTRIB, CTATTRIB(CTA_ALIGN)), + ctype_align(decl->attr)); + } +} + +/* Push unrolled type to declaration stack and merge qualifiers. */ +static void cp_push_type(CPDecl *decl, CTypeID id) +{ + CType *ct = ctype_get(decl->cp->cts, id); + CTInfo info = ct->info; + CTSize size = ct->size; + switch (ctype_type(info)) + { + case CT_STRUCT: + case CT_ENUM: + cp_push(decl, CTINFO(CT_TYPEDEF, id), 0); /* Don't copy unique types. */ + if ((decl->attr & CTF_QUAL)) + { /* Push unmerged qualifiers. */ + cp_push(decl, CTINFO(CT_ATTRIB, CTATTRIB(CTA_QUAL)), + (decl->attr & CTF_QUAL)); + decl->attr &= ~CTF_QUAL; + } + break; + case CT_ATTRIB: + if (ctype_isxattrib(info, CTA_QUAL)) + decl->attr &= ~size; /* Remove redundant qualifiers. */ + cp_push_type(decl, ctype_cid(info)); /* Unroll. */ + cp_push(decl, info & ~CTMASK_CID, size); /* Copy type. */ + break; + case CT_ARRAY: + if ((ct->info & (CTF_VECTOR | CTF_COMPLEX))) + { + info |= (decl->attr & CTF_QUAL); + decl->attr &= ~CTF_QUAL; + } + cp_push_type(decl, ctype_cid(info)); /* Unroll. */ + cp_push(decl, info & ~CTMASK_CID, size); /* Copy type. */ + decl->stack[decl->pos].sib = 1; /* Mark as already checked and sized. */ + /* Note: this is not copied to the ct->sib in the C type table. */ + break; + case CT_FUNC: + /* Copy type, link parameters (shared). */ + decl->stack[cp_push(decl, info, size)].sib = ct->sib; + break; + default: + /* Copy type, merge common qualifiers. */ + cp_push(decl, info | (decl->attr & CTF_QUAL), size); + decl->attr &= ~CTF_QUAL; + break; + } +} + +/* Consume the declaration element chain and intern the C type. */ +static CTypeID cp_decl_intern(CPState *cp, CPDecl *decl) +{ + CTypeID id = 0; + CPDeclIdx idx = 0; + CTSize csize = CTSIZE_INVALID; + CTSize cinfo = 0; + do + { + CType *ct = &decl->stack[idx]; + CTInfo info = ct->info; + CTInfo size = ct->size; + /* The cid is already part of info for copies of pointers/functions. */ + idx = ct->next; + if (ctype_istypedef(info)) + { + uc_assertCP(id == 0, "typedef not at toplevel"); + id = ctype_cid(info); + /* Always refetch info/size, since struct/enum may have been completed. */ + cinfo = ctype_get(cp->cts, id)->info; + csize = ctype_get(cp->cts, id)->size; + uc_assertCP(ctype_isstruct(cinfo) || ctype_isenum(cinfo), + "typedef of bad type"); + } + else if (ctype_isfunc(info)) + { /* Intern function. */ + CType *fct; + CTypeID fid; + CTypeID sib; + if (id) + { + CType *refct = ctype_raw(cp->cts, id); + /* Reject function or refarray return types. */ + if (ctype_isfunc(refct->info) || ctype_isrefarray(refct->info)) { + cp_err(cp, "invalid C type"); + return 0; + } + } + /* No intervening attributes allowed, skip forward. */ + while (idx) + { + CType *ctn = &decl->stack[idx]; + if (!ctype_isattrib(ctn->info)) + break; + idx = ctn->next; /* Skip attribute. */ + } + sib = ct->sib; /* Next line may reallocate the C type table. */ + fid = uc_ctype_new(cp->cts, &fct); + csize = CTSIZE_INVALID; + fct->info = cinfo = info + id; + fct->size = size; + fct->sib = sib; + id = fid; + } + else if (ctype_isattrib(info)) + { + if (ctype_isxattrib(info, CTA_QUAL)) + cinfo |= size; + else if (ctype_isxattrib(info, CTA_ALIGN)) + CTF_INSERT(cinfo, ALIGN, size); + id = uc_ctype_intern(cp->cts, info + id, size); + /* Inherit csize/cinfo from original type. */ + } + else + { + if (ctype_isnum(info)) + { /* Handle mode/vector-size attributes. */ + uc_assertCP(id == 0, "number not at toplevel"); + if (!(info & CTF_BOOL)) + { + CTSize msize = ctype_msizeP(decl->attr); + CTSize vsize = ctype_vsizeP(decl->attr); + if (msize && (!(info & CTF_FP) || (msize == 4 || msize == 8))) + { + CTSize malign = uc_fls(msize); + if (malign > 4) + malign = 4; /* Limit alignment. */ + CTF_INSERT(info, ALIGN, malign); + size = msize; /* Override size via mode. */ + } + if (vsize) + { /* Vector size set? */ + CTSize esize = uc_fls(size); + if (vsize >= esize) + { + /* Intern the element type first. */ + id = uc_ctype_intern(cp->cts, info, size); + /* Then create a vector (array) with vsize alignment. */ + size = (1u << vsize); + if (vsize > 4) + vsize = 4; /* Limit alignment. */ + if (ctype_align(info) > vsize) + vsize = ctype_align(info); + info = CTINFO(CT_ARRAY, (info & CTF_QUAL) + CTF_VECTOR + + CTALIGN(vsize)); + } + } + } + } + else if (ctype_isptr(info)) + { + /* Reject pointer/ref to ref. */ + if (id && ctype_isref(ctype_raw(cp->cts, id)->info)) { + cp_err(cp, "invalid C type"); + return 0; + } + if (ctype_isref(info)) + { + info &= ~CTF_VOLATILE; /* Refs are always const, never volatile. */ + /* No intervening attributes allowed, skip forward. */ + while (idx) + { + CType *ctn = &decl->stack[idx]; + if (!ctype_isattrib(ctn->info)) + break; + idx = ctn->next; /* Skip attribute. */ + } + } + } + else if (ctype_isarray(info)) + { /* Check for valid array size etc. */ + if (ct->sib == 0) + { /* Only check/size arrays not copied by unroll. */ + if (ctype_isref(cinfo)) /* Reject arrays of refs. */ { + cp_err(cp, "invalid C type"); + return 0; + } + /* Reject VLS or unknown-sized types. */ + if (ctype_isvltype(cinfo) || csize == CTSIZE_INVALID) { + cp_err(cp, "size of C type is unknown or too large"); + return 0; + } + /* a[] and a[?] keep their invalid size. */ + if (size != CTSIZE_INVALID) + { + uint64_t xsz = (uint64_t)size * csize; + if (xsz >= 0x80000000u) { + cp_err(cp, "size of C type is unknown or too large"); + return 0; + } + size = (CTSize)xsz; + } + } + if ((cinfo & CTF_ALIGN) > (info & CTF_ALIGN)) /* Find max. align. */ + info = (info & ~CTF_ALIGN) | (cinfo & CTF_ALIGN); + info |= (cinfo & CTF_QUAL); /* Inherit qual. */ + } + else + { + uc_assertCP(ctype_isvoid(info), "bad ctype %08x", info); + } + csize = size; + cinfo = info + id; + id = uc_ctype_intern(cp->cts, info + id, size); + } + } while (idx); + return id; +} + +/* -- C declaration parser ------------------------------------------------ */ + +/* Reset declaration state to declaration specifier. */ +static void cp_decl_reset(CPDecl *decl) +{ + ucv_put(decl->uv_name); + ucv_put(decl->uv_redir); + + decl->pos = decl->specpos; + decl->top = decl->specpos + 1; + decl->stack[decl->specpos].next = 0; + decl->attr = decl->specattr; + decl->fattr = decl->specfattr; + decl->uv_name = NULL; + decl->uv_redir = NULL; +} + +/* Parse constant initializer. */ +/* NYI: FP constants and strings as initializers. */ +static CTypeID cp_decl_constinit(CPState *cp, CType **ctp, CTypeID ctypeid) +{ + CType *ctt = ctype_get(cp->cts, ctypeid); + CTInfo info; + CTSize size; + CPValue k; + CTypeID constid; + while (ctype_isattrib(ctt->info)) + { /* Skip attributes. */ + ctypeid = ctype_cid(ctt->info); /* Update ID, too. */ + ctt = ctype_get(cp->cts, ctypeid); + } + info = ctt->info; + size = ctt->size; + if (!ctype_isinteger(info) || !(info & CTF_CONST) || size > 4) + { + cp_err(cp, "invalid C type"); + return 0; + } + cp_check(cp, '='); + if (cp->error) + return 0; + cp_expr_sub(cp, &k, 0); + if (cp->error) + return 0; + constid = uc_ctype_new(cp->cts, ctp); + (*ctp)->info = CTINFO(CT_CONSTVAL, CTF_CONST | ctypeid); + k.u32 <<= 8 * (4 - size); + if ((info & CTF_UNSIGNED)) + k.u32 >>= 8 * (4 - size); + else + k.u32 = (uint32_t)((int32_t)k.u32 >> 8 * (4 - size)); + (*ctp)->size = k.u32; + return constid; +} + +/* Parse size in parentheses as part of attribute. */ +static CTSize cp_decl_sizeattr(CPState *cp) +{ + CTSize sz; + uint32_t oldtmask = cp->tmask; + cp->tmask = CPNS_DEFAULT; /* Required for expression evaluator. */ + cp_check(cp, '('); + if (cp->error) + return 0; + sz = cp_expr_ksize(cp); + if (cp->error) + return 0; + cp->tmask = oldtmask; + cp_check(cp, ')'); + if (cp->error) + return 0; + return sz; +} + +/* Parse alignment attribute. */ +static void cp_decl_align(CPState *cp, CPDecl *decl) +{ + CTSize al = 4; /* Unspecified alignment is 16 bytes. */ + if (cp->tok == '(') + { + al = cp_decl_sizeattr(cp); + if (cp->error) + return; + al = al ? uc_fls(al) : 0; + } + CTF_INSERT(decl->attr, ALIGN, al); + decl->attr |= CTFP_ALIGNED; +} + +/* Parse GCC asm("name") redirect. */ +static void cp_decl_asm(CPState *cp, unused CPDecl *decl) +{ + cp_next(cp); + if (cp->error) + return; + cp_check(cp, '('); + if (cp->error) + return; + if (cp->tok == CTOK_STRING) + { + uc_stringbuf_t *buf = ucv_stringbuf_new(); + // GCstr *str = cp->str; + ucv_stringbuf_addstr(buf, ucv_string_get(cp->uv_str), ucv_string_length(cp->uv_str)); + while (cp_next(cp) == CTOK_STRING) + { + if (cp->error) + return; + ucv_stringbuf_addstr(buf, ucv_string_get(cp->uv_str), ucv_string_length(cp->uv_str)); + } + decl->uv_redir = ucv_stringbuf_finish(buf); + } + cp_check(cp, ')'); + if (cp->error) + return; +} + +/* Parse GCC __attribute__((mode(...))). */ +static void cp_decl_mode(CPState *cp, CPDecl *decl) +{ + cp_check(cp, '('); + if (cp->tok == CTOK_IDENT) + { + const char *s = ucv_string_get(cp->uv_str); + CTSize sz = 0, vlen = 0; + if (s[0] == '_' && s[1] == '_') + s += 2; + if (*s == 'V') + { + s++; + vlen = *s++ - '0'; + if (*s >= '0' && *s <= '9') + vlen = vlen * 10 + (*s++ - '0'); + } + switch (*s++) + { + case 'Q': + sz = 1; + break; + case 'H': + sz = 2; + break; + case 'S': + sz = 4; + break; + case 'D': + sz = 8; + break; + case 'T': + sz = 16; + break; + case 'O': + sz = 32; + break; + default: + goto bad_size; + } + if (*s == 'I' || *s == 'F') + { + CTF_INSERT(decl->attr, MSIZEP, sz); + if (vlen) + CTF_INSERT(decl->attr, VSIZEP, uc_fls(vlen * sz)); + } + bad_size: + cp_next(cp); + } + cp_check(cp, ')'); +} + +/* Parse GCC __attribute__((...)). */ +static void cp_decl_gccattribute(CPState *cp, CPDecl *decl) +{ + cp_next(cp); + if (cp->error) + return; + cp_check(cp, '('); + if (cp->error) + return; + cp_check(cp, '('); + if (cp->error) + return; + while (cp->tok != ')') + { + if (cp->tok == CTOK_IDENT) + { + uc_value_t *attrstr = ucv_get(cp->uv_str); + cp_next(cp); + if (cp->error) + return; + switch (uc_cparse_case(attrstr, + "\007aligned" + "\013__aligned__" + "\006packed" + "\012__packed__" + "\004mode" + "\010__mode__" + "\013vector_size" + "\017__vector_size__" +#if UC_TARGET_X86 + "\007regparm" + "\013__regparm__" + "\005cdecl" + "\011__cdecl__" + "\010thiscall" + "\014__thiscall__" + "\010fastcall" + "\014__fastcall__" + "\007stdcall" + "\013__stdcall__" + "\012sseregparm" + "\016__sseregparm__" +#endif + )) + { + case 0: + case 1: /* aligned */ + cp_decl_align(cp, decl); + break; + case 2: + case 3: /* packed */ + decl->attr |= CTFP_PACKED; + break; + case 4: + case 5: /* mode */ + cp_decl_mode(cp, decl); + break; + case 6: + case 7: /* vector_size */ + { + CTSize vsize = cp_decl_sizeattr(cp); + if (vsize) + CTF_INSERT(decl->attr, VSIZEP, uc_fls(vsize)); + } + break; +#if UC_TARGET_X86 + case 8: + case 9: /* regparm */ + CTF_INSERT(decl->fattr, REGPARM, cp_decl_sizeattr(cp)); + decl->fattr |= CTFP_CCONV; + break; + case 10: + case 11: /* cdecl */ + CTF_INSERT(decl->fattr, CCONV, CTCC_CDECL); + decl->fattr |= CTFP_CCONV; + break; + case 12: + case 13: /* thiscall */ + CTF_INSERT(decl->fattr, CCONV, CTCC_THISCALL); + decl->fattr |= CTFP_CCONV; + break; + case 14: + case 15: /* fastcall */ + CTF_INSERT(decl->fattr, CCONV, CTCC_FASTCALL); + decl->fattr |= CTFP_CCONV; + break; + case 16: + case 17: /* stdcall */ + CTF_INSERT(decl->fattr, CCONV, CTCC_STDCALL); + decl->fattr |= CTFP_CCONV; + break; + case 18: + case 19: /* sseregparm */ + decl->fattr |= CTF_SSEREGPARM; + decl->fattr |= CTFP_CCONV; + break; +#endif + default: /* Skip all other attributes. */ + ucv_put(attrstr); + goto skip_attr; + } + ucv_put(attrstr); + } + else if (cp->tok >= CTOK_FIRSTDECL) + { /* For __attribute((const)) etc. */ + cp_next(cp); + skip_attr: + if (cp_opt(cp, '(')) + { + while (cp->tok != ')' && cp->tok != CTOK_EOF) + { + cp_next(cp); + if (cp->error) + return; + } + cp_check(cp, ')'); + if (cp->error) + return; + } + } + else + { + break; + } + if (!cp_opt(cp, ',')) + break; + } + cp_check(cp, ')'); + if (cp->error) + return; + cp_check(cp, ')'); + if (cp->error) + return; +} + +/* Parse MSVC __declspec(...). */ +static void cp_decl_msvcattribute(CPState *cp, CPDecl *decl) +{ + cp_next(cp); + if (cp->error) + return; + cp_check(cp, '('); + if (cp->error) + return; + while (cp->tok == CTOK_IDENT) + { + uc_value_t *attrstr = ucv_get(cp->uv_str); + cp_next(cp); + if (cp->error) + return; + if (cp_str_is(attrstr, "align")) + { + cp_decl_align(cp, decl); + if (cp->error) + return; + } + else + { /* Ignore all other attributes. */ + if (cp_opt(cp, '(')) + { + while (cp->tok != ')' && cp->tok != CTOK_EOF) + { + cp_next(cp); + if (cp->error) + return; + } + cp_check(cp, ')'); + if (cp->error) + return; + } + } + ucv_put(attrstr); + } + cp_check(cp, ')'); + if (cp->error) + return; +} + +/* Parse declaration attributes (and common qualifiers). */ +static void cp_decl_attributes(CPState *cp, CPDecl *decl) +{ + for (;;) + { + switch (cp->tok) + { + case CTOK_CONST: + decl->attr |= CTF_CONST; + break; + case CTOK_VOLATILE: + decl->attr |= CTF_VOLATILE; + break; + case CTOK_RESTRICT: + break; /* Ignore. */ + case CTOK_EXTENSION: + break; /* Ignore. */ + case CTOK_ATTRIBUTE: + cp_decl_gccattribute(cp, decl); + if (cp->error) + return; + continue; + case CTOK_ASM: + cp_decl_asm(cp, decl); + if (cp->error) + return; + continue; + case CTOK_DECLSPEC: + cp_decl_msvcattribute(cp, decl); + if (cp->error) + return; + continue; + case CTOK_CCDECL: +#if UC_TARGET_X86 + CTF_INSERT(decl->fattr, CCONV, cp->ct->size); + decl->fattr |= CTFP_CCONV; +#endif + break; + case CTOK_PTRSZ: +#if UC_64 + CTF_INSERT(decl->attr, MSIZEP, cp->ct->size); +#endif + break; + default: + return; + } + cp_next(cp); + } +} + +/* Parse struct/union/enum name. */ +static CTypeID cp_struct_name(CPState *cp, CPDecl *sdecl, CTInfo info) +{ + CTypeID sid; + CType *ct; + cp->tmask = CPNS_STRUCT; + cp_next(cp); + cp_decl_attributes(cp, sdecl); + cp->tmask = CPNS_DEFAULT; + if (cp->tok != '{') + { + if (cp->tok != CTOK_IDENT) + cp_err_token(cp, CTOK_IDENT); + if (cp->val.id) + { /* Name of existing struct/union/enum. */ + sid = cp->val.id; + ct = cp->ct; + if ((ct->info ^ info) & (CTMASK_NUM | CTF_UNION)) /* Wrong type. */ + cp_errmsg(cp, 0, "attempt to redefine '%s'", ucv_string_get(ct->uv_name)); + } + else + { /* Create named, incomplete struct/union/enum. */ + if ((cp->mode & CPARSE_MODE_NOIMPLICIT)) + cp_errmsg(cp, 0, "undeclared or implicit tag '%s'", ucv_string_get(cp->uv_str)); + sid = uc_ctype_new(cp->cts, &ct); + ct->info = info; + ct->size = CTSIZE_INVALID; + ctype_setname(ct, cp->uv_str); + uc_ctype_addname(cp->cts, ct, sid); + } + cp_next(cp); + } + else + { /* Create anonymous, incomplete struct/union/enum. */ + sid = uc_ctype_new(cp->cts, &ct); + ct->info = info; + ct->size = CTSIZE_INVALID; + } + if (cp->tok == '{') + { + if (ct->size != CTSIZE_INVALID || ct->sib) + cp_errmsg(cp, 0, "attempt to redefine '%s'", ucv_string_get(ct->uv_name)); + ct->sib = 1; /* Indicate the type is currently being defined. */ + } + return sid; +} + +/* Determine field alignment. */ +static CTSize cp_field_align(unused CPState *cp, unused CType *ct, CTInfo info) +{ + CTSize align = ctype_align(info); +#if (UC_TARGET_X86 && !UC_ABI_WIN) || (UC_TARGET_ARM && __APPLE__) + /* The SYSV i386 and iOS ABIs limit alignment of non-vector fields to 2^2. */ + if (align > 2 && !(info & CTFP_ALIGNED)) + { + if (ctype_isarray(info) && !(info & CTF_VECTOR)) + { + do + { + ct = ctype_rawchild(cp->cts, ct); + info = ct->info; + } while (ctype_isarray(info) && !(info & CTF_VECTOR)); + } + if (ctype_isnum(info) || ctype_isenum(info)) + align = 2; + } +#endif + return align; +} + +/* Layout struct/union fields. */ +static void cp_struct_layout(CPState *cp, CTypeID sid, CTInfo sattr) +{ + CTSize bofs = 0, bmaxofs = 0; /* Bit offset and max. bit offset. */ + CTSize maxalign = ctype_align(sattr); + CType *sct = ctype_get(cp->cts, sid); + CTInfo sinfo = sct->info; + CTypeID fieldid = sct->sib; + while (fieldid) + { + CType *ct = ctype_get(cp->cts, fieldid); + CTInfo attr = ct->size; /* Field declaration attributes (temp.). */ + + if (ctype_isfield(ct->info) || + (ctype_isxattrib(ct->info, CTA_SUBTYPE) && attr)) + { + CTSize align, amask; /* Alignment (pow2) and alignment mask (bits). */ + CTSize sz; + CTInfo info = uc_ctype_info(cp->cts, ctype_cid(ct->info), &sz); + CTSize bsz, csz = 8 * sz; /* Field size and container size (in bits). */ + sinfo |= (info & (CTF_QUAL | CTF_VLA)); /* Merge pseudo-qualifiers. */ + + /* Check for size overflow and determine alignment. */ + if (sz >= 0x20000000u || bofs + csz < bofs || (info & CTF_VLA)) + { + if (!(sz == CTSIZE_INVALID && ctype_isarray(info) && + !(sinfo & CTF_UNION))) { + cp_err(cp, "size of C type is unknown or too large"); + return; + } + csz = sz = 0; /* Treat a[] and a[?] as zero-sized. */ + } + align = cp_field_align(cp, ct, info); + if (((attr | sattr) & CTFP_PACKED) || + ((attr & CTFP_ALIGNED) && ctype_align(attr) > align)) + align = ctype_align(attr); + if (cp->packstack[cp->curpack] < align) + align = cp->packstack[cp->curpack]; + if (align > maxalign) + maxalign = align; + amask = (8u << align) - 1; + + bsz = ctype_bitcsz(ct->info); /* Bitfield size (temp.). */ + if (bsz == CTBSZ_FIELD || !ctype_isfield(ct->info)) + { + bsz = csz; /* Regular fields or subtypes always fill the container. */ + bofs = (bofs + amask) & ~amask; /* Start new aligned field. */ + ct->size = (bofs >> 3); /* Store field offset. */ + } + else + { /* Bitfield. */ + if (bsz == 0 || (attr & CTFP_ALIGNED) || + (!((attr | sattr) & CTFP_PACKED) && (bofs & amask) + bsz > csz)) + bofs = (bofs + amask) & ~amask; /* Start new aligned field. */ + + /* Prefer regular field over bitfield. */ + if (bsz == csz && (bofs & amask) == 0) + { + ct->info = CTINFO(CT_FIELD, ctype_cid(ct->info)); + ct->size = (bofs >> 3); /* Store field offset. */ + } + else + { + ct->info = CTINFO(CT_BITFIELD, + (info & (CTF_QUAL | CTF_UNSIGNED | CTF_BOOL)) + + (csz << (CTSHIFT_BITCSZ - 3)) + (bsz << CTSHIFT_BITBSZ)); +#if UC_BE + ct->info += ((csz - (bofs & (csz - 1)) - bsz) << CTSHIFT_BITPOS); +#else + ct->info += ((bofs & (csz - 1)) << CTSHIFT_BITPOS); +#endif + ct->size = ((bofs & ~(csz - 1)) >> 3); /* Store container offset. */ + } + } + + /* Determine next offset or max. offset. */ + if ((sinfo & CTF_UNION)) + { + if (bsz > bmaxofs) + bmaxofs = bsz; + } + else + { + bofs += bsz; + } + } /* All other fields in the chain are already set up. */ + + fieldid = ct->sib; + } + + /* Complete struct/union. */ + sct->info = sinfo + CTALIGN(maxalign); + bofs = (sinfo & CTF_UNION) ? bmaxofs : bofs; + maxalign = (8u << maxalign) - 1; + sct->size = (((bofs + maxalign) & ~maxalign) >> 3); +} + +/* Parse struct/union declaration. */ +static CTypeID cp_decl_struct(CPState *cp, CPDecl *sdecl, CTInfo sinfo) +{ + CTypeID sid = cp_struct_name(cp, sdecl, sinfo); + if (cp->error) + return 0; + if (cp_opt(cp, '{')) + { /* Struct/union definition. */ + CTypeID lastid = sid; + int lastdecl = 0; + while (cp->tok != '}') + { + CPDecl decl = { 0 }; + CPscl scl = cp_decl_spec(cp, &decl, CDF_STATIC); + if (cp->error) + return 0; + decl.mode = scl ? CPARSE_MODE_DIRECT : CPARSE_MODE_DIRECT | CPARSE_MODE_ABSTRACT | CPARSE_MODE_FIELD; + + for (;;) + { + CTypeID ctypeid; + + if (lastdecl) + { + cp_err_token(cp, '}'); + return 0; + } + + /* Parse field declarator. */ + decl.bits = CTSIZE_INVALID; + cp_declarator(cp, &decl); + if (cp->error) + return 0; + ctypeid = cp_decl_intern(cp, &decl); + if (cp->error) + return 0; + + if ((scl & CDF_STATIC)) + { /* Static constant in struct namespace. */ + CType *ct; + CTypeID fieldid = cp_decl_constinit(cp, &ct, ctypeid); + if (cp->error) + return 0; + ctype_get(cp->cts, lastid)->sib = fieldid; + lastid = fieldid; + ctype_setname(ct, decl.uv_name); + } + else + { + CTSize bsz = CTBSZ_FIELD; /* Temp. for layout phase. */ + CType *ct; + CTypeID fieldid = uc_ctype_new(cp->cts, &ct); /* Do this first. */ + CType *tct = ctype_raw(cp->cts, ctypeid); + + if (decl.bits == CTSIZE_INVALID) + { /* Regular field. */ + if (ctype_isarray(tct->info) && tct->size == CTSIZE_INVALID) + lastdecl = 1; /* a[] or a[?] must be the last declared field. */ + + /* Accept transparent struct/union/enum. */ + if (!decl.uv_name) + { + if (!((ctype_isstruct(tct->info) && !(tct->info & CTF_VLA)) || + ctype_isenum(tct->info))) + { + cp_err_token(cp, CTOK_IDENT); + return 0; + } + ct->info = CTINFO(CT_ATTRIB, CTATTRIB(CTA_SUBTYPE) + ctypeid); + ct->size = ctype_isstruct(tct->info) ? (decl.attr | 0x80000000u) : 0; /* For layout phase. */ + goto add_field; + } + } + else + { /* Bitfield. */ + bsz = decl.bits; + if (!ctype_isinteger_or_bool(tct->info) || + (bsz == 0 && decl.uv_name) || 8 * tct->size > CTBSZ_MAX || + bsz > ((tct->info & CTF_BOOL) ? 1 : 8 * tct->size)) + { + cp_errmsg(cp, ':', "invalid value"); + return 0; + } + } + + /* Create temporary field for layout phase. */ + ct->info = CTINFO(CT_FIELD, ctypeid + (bsz << CTSHIFT_BITCSZ)); + ct->size = decl.attr; + if (decl.uv_name) + ctype_setname(ct, decl.uv_name); + + add_field: + ctype_get(cp->cts, lastid)->sib = fieldid; + lastid = fieldid; + } + cp_decl_reset(&decl); + if (!cp_opt(cp, ',')) + break; + } + cp_check(cp, ';'); + if (cp->error) + return 0; + } + cp_check(cp, '}'); + if (cp->error) + return 0; + ctype_get(cp->cts, lastid)->sib = 0; /* Drop sib = 1 for empty structs. */ + cp_decl_attributes(cp, sdecl); /* Layout phase needs postfix attributes. */ + if (cp->error) + return 0; + cp_struct_layout(cp, sid, sdecl->attr); + } + return sid; +} + +/* Parse enum declaration. */ +static CTypeID cp_decl_enum(CPState *cp, CPDecl *sdecl) +{ + CTypeID eid = cp_struct_name(cp, sdecl, CTINFO(CT_ENUM, CTID_VOID)); + if (cp->error) + return 0; + CTInfo einfo = CTINFO(CT_ENUM, CTALIGN(2) + CTID_UINT32); + CTSize esize = 4; /* Only 32 bit enums are supported. */ + if (cp_opt(cp, '{')) + { /* Enum definition. */ + CPValue k; + CTypeID lastid = eid; + k.u32 = 0; + k.id = CTID_INT32; + do + { + uc_value_t *name = ucv_get(cp->uv_str); + if (cp->tok != CTOK_IDENT) + { + cp_err_token(cp, CTOK_IDENT); + return 0; + } + if (cp->val.id) + { + cp_errmsg(cp, 0, "attempt to redefine '%s'", ucv_string_get(name)); + return 0; + } + cp_next(cp); + if (cp_opt(cp, '=')) + { + cp_expr_kint(cp, &k); + if (cp->error) + return 0; + if (k.id == CTID_UINT32) + { + /* C99 says that enum constants are always (signed) integers. + ** But since unsigned constants like 0x80000000 are quite common, + ** those are left as uint32_t. + */ + if (k.i32 >= 0) + k.id = CTID_INT32; + } + else + { + /* OTOH it's common practice and even mandated by some ABIs + ** that the enum type itself is unsigned, unless there are any + ** negative constants. + */ + k.id = CTID_INT32; + if (k.i32 < 0) + einfo = CTINFO(CT_ENUM, CTALIGN(2) + CTID_INT32); + } + } + /* Add named enum constant. */ + { + CType *ct; + CTypeID constid = uc_ctype_new(cp->cts, &ct); + ctype_get(cp->cts, lastid)->sib = constid; + lastid = constid; + ctype_setname(ct, name); + ct->info = CTINFO(CT_CONSTVAL, CTF_CONST | k.id); + ct->size = k.u32++; + if (k.u32 == 0x80000000u) + k.id = CTID_UINT32; + uc_ctype_addname(cp->cts, ct, constid); + } + ucv_put(name); + if (!cp_opt(cp, ',')) + break; + } while (cp->tok != '}'); /* Trailing ',' is ok. */ + cp_check(cp, '}'); + if (cp->error) + return 0; + /* Complete enum. */ + ctype_get(cp->cts, eid)->info = einfo; + ctype_get(cp->cts, eid)->size = esize; + } + return eid; +} + +/* Parse declaration specifiers. */ +static CPscl cp_decl_spec(CPState *cp, CPDecl *decl, CPscl scl) +{ + uint32_t cds = 0, sz = 0; + CTypeID tdef = 0; + + decl->cp = cp; + decl->mode = cp->mode; + decl->uv_name = NULL; + decl->uv_redir = NULL; + decl->attr = 0; + decl->fattr = 0; + decl->pos = decl->top = 0; + decl->stack[0].next = 0; + + for (;;) + { /* Parse basic types. */ + cp_decl_attributes(cp, decl); + if (cp->error) + return 0; + if (cp->tok >= CTOK_FIRSTDECL && cp->tok <= CTOK_LASTDECLFLAG) + { + uint32_t cbit; + if (cp->ct->size) + { + if (sz) + goto end_decl; + sz = cp->ct->size; + } + cbit = (1u << (cp->tok - CTOK_FIRSTDECL)); + cds = cds | cbit | ((cbit & cds & CDF_LONG) << 1); + if (cp->tok >= CTOK_FIRSTSCL) + { + if (!(scl & cbit)) + { + cp_errmsg(cp, cp->tok, "bad storage class"); + return 0; + } + } + else if (tdef) + { + goto end_decl; + } + cp_next(cp); + continue; + } + if (sz || tdef || + (cds & (CDF_SHORT | CDF_LONG | CDF_SIGNED | CDF_UNSIGNED | CDF_COMPLEX))) + break; + switch (cp->tok) + { + case CTOK_STRUCT: + tdef = cp_decl_struct(cp, decl, CTINFO(CT_STRUCT, 0)); + if (cp->error) + return 0; + continue; + case CTOK_UNION: + tdef = cp_decl_struct(cp, decl, CTINFO(CT_STRUCT, CTF_UNION)); + if (cp->error) + return 0; + continue; + case CTOK_ENUM: + tdef = cp_decl_enum(cp, decl); + if (cp->error) + return 0; + continue; + case CTOK_IDENT: + if (ctype_istypedef(cp->ct->info)) + { + tdef = ctype_cid(cp->ct->info); /* Get typedef. */ + cp_next(cp); + continue; + } + break; + case '$': + tdef = cp->val.id; + cp_next(cp); + continue; + default: + break; + } + break; + } +end_decl: + + if ((cds & CDF_COMPLEX)) /* Use predefined complex types. */ + tdef = sz == 4 ? CTID_COMPLEX_FLOAT : CTID_COMPLEX_DOUBLE; + + if (tdef) + { + cp_push_type(decl, tdef); + } + else if ((cds & CDF_VOID)) + { + cp_push(decl, CTINFO(CT_VOID, (decl->attr & CTF_QUAL)), CTSIZE_INVALID); + decl->attr &= ~CTF_QUAL; + } + else + { + /* Determine type info and size. */ + CTInfo info = CTINFO(CT_NUM, (cds & CDF_UNSIGNED) ? CTF_UNSIGNED : 0); + if ((cds & CDF_BOOL)) + { + if ((cds & ~(CDF_SCL | CDF_BOOL | CDF_INT | CDF_SIGNED | CDF_UNSIGNED))) + { + cp_errmsg(cp, 0, "invalid C type"); + return 0; + } + info |= CTF_BOOL; + if (!(cds & CDF_SIGNED)) + info |= CTF_UNSIGNED; + if (!sz) + { + sz = 1; + } + } + else if ((cds & CDF_FP)) + { + info = CTINFO(CT_NUM, CTF_FP); + if ((cds & CDF_LONG)) + sz = sizeof(long double); + } + else if ((cds & CDF_CHAR)) + { + if ((cds & (CDF_CHAR | CDF_SIGNED | CDF_UNSIGNED)) == CDF_CHAR) + info |= CTF_UCHAR; /* Handle platforms where char is unsigned. */ + } + else if ((cds & CDF_SHORT)) + { + sz = sizeof(short); + } + else if ((cds & CDF_LONGLONG)) + { + sz = 8; + } + else if ((cds & CDF_LONG)) + { + info |= CTF_LONG; + sz = sizeof(long); + } + else if (!sz) + { + if (!(cds & (CDF_SIGNED | CDF_UNSIGNED))) + { + cp_errmsg(cp, cp->tok, "declaration specifier expected"); + return 0; + } + sz = sizeof(int); + } + uc_assertCP(sz != 0, "basic ctype with zero size"); + info += CTALIGN(uc_fls(sz)); /* Use natural alignment. */ + info += (decl->attr & CTF_QUAL); /* Merge qualifiers. */ + cp_push(decl, info, sz); + decl->attr &= ~CTF_QUAL; + } + decl->specpos = decl->pos; + decl->specattr = decl->attr; + decl->specfattr = decl->fattr; + return (cds & CDF_SCL); /* Return storage class. */ +} + +/* Parse array declaration. */ +static void cp_decl_array(CPState *cp, CPDecl *decl) +{ + CTInfo info = CTINFO(CT_ARRAY, 0); + CTSize nelem = CTSIZE_INVALID; /* Default size for a[] or a[?]. */ + cp_decl_attributes(cp, decl); + if (cp->error) + return; + if (cp_opt(cp, '?')) + info |= CTF_VLA; /* Create variable-length array a[?]. */ + else if (cp->tok != ']') + { + nelem = cp_expr_ksize(cp); + if (cp->error) + return; + } + cp_check(cp, ']'); + cp_add(decl, info, nelem); +} + +/* Parse function declaration. */ +static void cp_decl_func(CPState *cp, CPDecl *fdecl) +{ + CTSize nargs = 0; + CTInfo info = CTINFO(CT_FUNC, 0); + CTypeID lastid = 0, anchor = 0; + if (cp->tok != ')') + { + do + { + CPDecl decl = {0}; + CTypeID ctypeid, fieldid; + CType *ct; + if (cp_opt(cp, '.')) + { /* Vararg function. */ + cp_check(cp, '.'); /* Workaround for the minimalistic lexer. */ + cp_check(cp, '.'); + info |= CTF_VARARG; + break; + } + cp_decl_spec(cp, &decl, CDF_REGISTER); + if (cp->error) + return; + decl.mode = CPARSE_MODE_DIRECT | CPARSE_MODE_ABSTRACT; + cp_declarator(cp, &decl); + if (cp->error) + return; + ctypeid = cp_decl_intern(cp, &decl); + if (cp->error) + return; + ct = ctype_raw(cp->cts, ctypeid); + if (ctype_isvoid(ct->info)) + break; + else if (ctype_isrefarray(ct->info)) + ctypeid = uc_ctype_intern(cp->cts, + CTINFO(CT_PTR, CTALIGN_PTR | ctype_cid(ct->info)), CTSIZE_PTR); + else if (ctype_isfunc(ct->info)) + ctypeid = uc_ctype_intern(cp->cts, + CTINFO(CT_PTR, CTALIGN_PTR | ctypeid), CTSIZE_PTR); + /* Add new parameter. */ + fieldid = uc_ctype_new(cp->cts, &ct); + if (anchor) + ctype_get(cp->cts, lastid)->sib = fieldid; + else + anchor = fieldid; + lastid = fieldid; + if (decl.uv_name) + ctype_setname(ct, decl.uv_name); + ct->info = CTINFO(CT_FIELD, ctypeid); + ct->size = nargs++; + cp_decl_reset(&decl); + } while (cp_opt(cp, ',')); + } + cp_check(cp, ')'); + if (cp->error) + return; + if (cp_opt(cp, '{')) + { /* Skip function definition. */ + int level = 1; + cp->mode |= CPARSE_MODE_SKIP; + for (;;) + { + if (cp->tok == '{') + level++; + else if (cp->tok == '}' && --level == 0) + break; + else if (cp->tok == CTOK_EOF) { + cp_err_token(cp, '}'); + return; + } + cp_next(cp); + } + cp->mode &= ~CPARSE_MODE_SKIP; + cp->tok = ';'; /* Ok for cp_decl_multi(), error in cp_decl_single(). */ + } + info |= (fdecl->fattr & ~CTMASK_CID); + fdecl->fattr = 0; + fdecl->stack[cp_add(fdecl, info, nargs)].sib = anchor; +} + +/* Parse declarator. */ +static void cp_declarator(CPState *cp, CPDecl *decl) +{ + if (++cp->depth > CPARSE_MAX_DECLDEPTH) + { + cp_err(cp, "chunk has too many syntax levels"); + return; + } + + for (;;) + { /* Head of declarator. */ + if (cp_opt(cp, '*')) + { /* Pointer. */ + CTSize sz; + CTInfo info; + cp_decl_attributes(cp, decl); + if (cp->error) + return; + sz = CTSIZE_PTR; + info = CTINFO(CT_PTR, CTALIGN_PTR); +#if UC_64 + if (ctype_msizeP(decl->attr) == 4) + { + sz = 4; + info = CTINFO(CT_PTR, CTALIGN(2)); + } +#endif + info += (decl->attr & (CTF_QUAL | CTF_REF)); + decl->attr &= ~(CTF_QUAL | (CTMASK_MSIZEP << CTSHIFT_MSIZEP)); + cp_push(decl, info, sz); + } + else if (cp_opt(cp, '&') || cp_opt(cp, CTOK_ANDAND)) + { /* Reference. */ + decl->attr &= ~(CTF_QUAL | (CTMASK_MSIZEP << CTSHIFT_MSIZEP)); + cp_push(decl, CTINFO_REF(0), CTSIZE_PTR); + } + else + { + break; + } + } + + if (cp_opt(cp, '(')) + { /* Inner declarator. */ + CPDeclIdx pos; + cp_decl_attributes(cp, decl); + if (cp->error) + return; + /* Resolve ambiguity between inner declarator and 1st function parameter. */ + if ((decl->mode & CPARSE_MODE_ABSTRACT) && + (cp->tok == ')' || cp_istypedecl(cp))) + goto func_decl; + pos = decl->pos; + cp_declarator(cp, decl); + if (cp->error) + return; + cp_check(cp, ')'); + if (cp->error) + return; + decl->pos = pos; + } + else if (cp->tok == CTOK_IDENT) + { /* Direct declarator. */ + if (!(decl->mode & CPARSE_MODE_DIRECT)) + { + cp_err_token(cp, CTOK_EOF); + return; + } + decl->uv_name = ucv_get(cp->uv_str); + decl->nameid = cp->val.id; + cp_next(cp); + } + else + { /* Abstract declarator. */ + if (!(decl->mode & CPARSE_MODE_ABSTRACT)) + { + cp_err_token(cp, CTOK_IDENT); + return; + } + } + + for (;;) + { /* Tail of declarator. */ + if (cp_opt(cp, '[')) + { /* Array. */ + cp_decl_array(cp, decl); + if (cp->error) + return; + } + else if (cp_opt(cp, '(')) + { /* Function. */ + func_decl: + cp_decl_func(cp, decl); + if (cp->error) + return; + } + else + { + break; + } + } + + if ((decl->mode & CPARSE_MODE_FIELD) && cp_opt(cp, ':')) /* Field width. */ + { + decl->bits = cp_expr_ksize(cp); + if (cp->error) + return; + } + + /* Process postfix attributes. */ + cp_decl_attributes(cp, decl); + if (cp->error) + return; + cp_push_attributes(decl); + + cp->depth--; +} + +/* Parse an abstract type declaration and return it's C type ID. */ +static CTypeID cp_decl_abstract(CPState *cp) +{ + CPDecl decl = {0}; + cp_decl_spec(cp, &decl, 0); + if (cp->error) + return 0; + decl.mode = CPARSE_MODE_ABSTRACT; + cp_declarator(cp, &decl); + if (cp->error) + return 0; + CTypeID rv = cp_decl_intern(cp, &decl); + if (cp->error) + return 0; + cp_decl_reset(&decl); + return rv; +} + +/* Handle pragmas. */ +static void cp_pragma(CPState *cp, size_t pragmaline) +{ + cp_next(cp); + if (cp->error) + return; + if (cp->tok == CTOK_IDENT && cp_str_is(cp->uv_str, "pack")) + { + cp_next(cp); + if (cp->error) + return; + cp_check(cp, '('); + if (cp->error) + return; + if (cp->tok == CTOK_IDENT) + { + if (cp_str_is(cp->uv_str, "push")) + { + if (cp->curpack < CPARSE_MAX_PACKSTACK - 1) + { + cp->packstack[cp->curpack + 1] = cp->packstack[cp->curpack]; + cp->curpack++; + } + else + { + cp_errmsg(cp, cp->tok, "chunk has too many syntax levels"); + return; + } + } + else if (cp_str_is(cp->uv_str, "pop")) + { + if (cp->curpack > 0) + cp->curpack--; + } + else + { + cp_errmsg(cp, cp->tok, "unexpected symbol"); + return; + } + cp_next(cp); + if (cp->error) + return; + if (!cp_opt(cp, ',')) + goto end_pack; + } + if (cp->tok == CTOK_INTEGER) + { + cp->packstack[cp->curpack] = cp->val.u32 ? uc_fls(cp->val.u32) : 0; + cp_next(cp); + if (cp->error) + return; + } + else + { + cp->packstack[cp->curpack] = 255; + } + end_pack: + cp_check(cp, ')'); + if (cp->error) + return; + } + else + { /* Ignore all other pragmas. */ + while (cp->tok != CTOK_EOF && cp->linenumber == pragmaline) + cp_next(cp); + } +} + +/* Handle line number. */ +static void cp_line(CPState *cp, size_t hashline) +{ + size_t newline = cp->val.u32; + /* TODO: Handle file name and include it in error messages. */ + while (cp->tok != CTOK_EOF && cp->linenumber == hashline) + { + cp_next(cp); + if (cp->error) + return; + } + cp->linenumber = newline; +} + +/* Parse multiple C declarations of types or extern identifiers. */ +static void cp_decl_multi(CPState *cp) +{ + int first = 1; + while (cp->tok != CTOK_EOF) + { + CPDecl decl = {0}; + CPscl scl; + if (cp_opt(cp, ';')) + { /* Skip empty statements. */ + first = 0; + continue; + } + if (cp->tok == '#') + { /* Workaround, since we have no preprocessor, yet. */ + size_t hashline = cp->linenumber; + CPToken tok = cp_next(cp); + if (cp->error) + return; + if (tok == CTOK_INTEGER) + { + cp_line(cp, hashline); + if (cp->error) + return; + continue; + } + else if (tok == CTOK_IDENT && cp_str_is(cp->uv_str, "line")) + { + if (cp_next(cp) != CTOK_INTEGER) { + cp_err_token(cp, tok); + return; + } + cp_line(cp, hashline); + if (cp->error) + return; + continue; + } + else if (tok == CTOK_IDENT && cp_str_is(cp->uv_str, "pragma")) + { + cp_pragma(cp, hashline); + if (cp->error) + return; + continue; + } + else + { + cp_errmsg(cp, cp->tok, "unexpected symbol"); + return; + } + } + scl = cp_decl_spec(cp, &decl, CDF_TYPEDEF | CDF_EXTERN | CDF_STATIC); + if (cp->error) + return; + if ((cp->tok == ';' || cp->tok == CTOK_EOF) && + ctype_istypedef(decl.stack[0].info)) + { + CTInfo info = ctype_rawchild(cp->cts, &decl.stack[0])->info; + if (ctype_isstruct(info) || ctype_isenum(info)) + goto decl_end; /* Accept empty declaration of struct/union/enum. */ + } + for (;;) + { + CTypeID ctypeid; + cp_declarator(cp, &decl); + if (cp->error) + return; + ctypeid = cp_decl_intern(cp, &decl); + if (cp->error) + return; + if (decl.uv_name) + { + if (decl.nameid) + { /* Redeclaration detected - check compatibility. */ + CType *existing_ct = ctype_get(cp->cts, decl.nameid); + CType *new_ct = ctype_get(cp->cts, ctypeid); + + /* Skip extern and attributes to get the actual function type. */ + CType *existing_func = existing_ct; + while (ctype_isextern(existing_func->info) || ctype_isattrib(existing_func->info)) + existing_func = ctype_rawchild(cp->cts, existing_func); + + /* Unwrap CT_FUNC if needed. */ + CType *new_func = new_ct; + if (ctype_isptr(new_ct->info)) + new_func = ctype_rawchild(cp->cts, new_ct); + + if (ctype_isfunc(existing_func->info) && ctype_isfunc(new_func->info) && + ctype_func_is_equiv(cp->cts, existing_func, new_func)) + { /* Compatible: reuse existing type. */ + if (!existing_ct->uv_name) + ctype_setname(existing_ct, decl.uv_name); + ctypeid = decl.nameid; /* Use existing ID */ + cp->val.id = ctypeid; /* Update parser state */ + /* Add to hash table with the name. */ + uc_ctype_addname(cp->cts, existing_ct, ctypeid); + /* Record function type ID if func_ids vector is provided */ + if (cp->func_ids && (ctype_isfunc(existing_ct->info) || + (ctype_isextern(existing_ct->info) && ctype_isfunc(ctype_get(cp->cts, ctype_cid(existing_ct->info))->info)))) { + uc_vector_push(cp->func_ids, ctypeid); + } + } + else + { /* Incompatible redeclaration. */ + cp_errmsg(cp, 0, "redeclaration of '%s' as different type", + ucv_string_get(decl.uv_name)); + } + } + else + { /* New declaration. */ + CType *ct; + CTypeID id; + if ((scl & CDF_TYPEDEF)) + { /* Create new typedef. */ + id = uc_ctype_new(cp->cts, &ct); + ct->info = CTINFO(CT_TYPEDEF, ctypeid); + goto noredir; + } + else if (ctype_isfunc(ctype_get(cp->cts, ctypeid)->info)) + { + /* Treat both static and extern function declarations as extern. */ + ct = ctype_get(cp->cts, ctypeid); + /* We always get new anonymous functions (typedefs are copied). */ + uc_assertCP(ct->uv_name == NULL, "unexpected named function"); + id = ctypeid; /* Just name it. */ + } + else if ((scl & CDF_STATIC)) + { /* Accept static constants. */ + id = cp_decl_constinit(cp, &ct, ctypeid); + goto noredir; + } + else + { /* External references have extern or no storage class. */ + id = uc_ctype_new(cp->cts, &ct); + ct->info = CTINFO(CT_EXTERN, ctypeid); + } + if (decl.uv_redir) + { /* Add attribute for redirected symbol name. */ + CType *cta; + CTypeID aid = uc_ctype_new(cp->cts, &cta); + ct = ctype_get(cp->cts, id); /* Table may have been reallocated. */ + cta->info = CTINFO(CT_ATTRIB, CTATTRIB(CTA_REDIR)); + cta->sib = ct->sib; + ct->sib = aid; + ctype_setname(cta, decl.uv_redir); + } + noredir: + ctype_setname(ct, decl.uv_name); + uc_ctype_addname(cp->cts, ct, id); + + /* Record function type ID if func_ids vector is provided */ + if (cp->func_ids && (ctype_isfunc(ct->info) || + (ctype_isextern(ct->info) && ctype_isfunc(ctype_get(cp->cts, ctype_cid(ct->info))->info)))) { + uc_vector_push(cp->func_ids, id); + } + } + } + cp_decl_reset(&decl); + if (!cp_opt(cp, ',')) + break; + } + decl_end: + if (cp->tok == CTOK_EOF && first) + break; /* May omit ';' for 1 decl. */ + first = 0; + cp_check(cp, ';'); + } +} + +/* Parse a single C type declaration. */ +static void cp_decl_single(CPState *cp) +{ + CPDecl decl = {0}; + cp_decl_spec(cp, &decl, 0); + if (cp->error) + return; + cp_declarator(cp, &decl); + if (cp->error) + return; + cp->val.id = cp_decl_intern(cp, &decl); + if (cp->error) + return; + + CTypeID ctypeid = cp->val.id; + + if (decl.uv_name) + { + if (decl.nameid) + { /* Redeclaration detected - check compatibility. */ + CType *existing_ct = ctype_get(cp->cts, decl.nameid); + CType *new_ct = ctype_get(cp->cts, ctypeid); + + /* Skip extern and attributes to get the actual function type. */ + CType *existing_func = existing_ct; + while (ctype_isextern(existing_func->info) || ctype_isattrib(existing_func->info)) + existing_func = ctype_rawchild(cp->cts, existing_func); + + /* Unwrap CT_PTR if needed. */ + CType *new_func = new_ct; + if (ctype_isptr(new_ct->info)) + new_func = ctype_rawchild(cp->cts, new_ct); + + if (ctype_isfunc(existing_func->info) && ctype_isfunc(new_func->info) && + ctype_func_is_equiv(cp->cts, existing_func, new_func)) + { /* Compatible: reuse existing type. */ + if (!existing_ct->uv_name) + ctype_setname(existing_ct, decl.uv_name); + ctypeid = decl.nameid; /* Use existing ID */ + cp->val.id = ctypeid; /* Update parser state */ + /* Add to hash table with the name. */ + uc_ctype_addname(cp->cts, existing_ct, ctypeid); + } + else + { /* Incompatible redeclaration. */ + cp_errmsg(cp, 0, "redeclaration of '%s' as different type", + ucv_string_get(decl.uv_name)); + } + } + else + { /* New declaration. */ + CType *ct; + CTypeID id; + + /* Treat both static and extern function declarations as extern. */ + ct = ctype_get(cp->cts, ctypeid); + /* We always get new anonymous functions (typedefs are copied). */ + uc_assertCP(ct->uv_name == NULL, "unexpected named function"); + id = ctypeid; /* Just name it. */ + + if (ctype_isfunc(ct->info) && decl.uv_redir) + { /* Add attribute for redirected symbol name. */ + CType *cta; + CTypeID aid = uc_ctype_new(cp->cts, &cta); + ct = ctype_get(cp->cts, id); /* Table may have been reallocated. */ + cta->info = CTINFO(CT_ATTRIB, CTATTRIB(CTA_REDIR)); + cta->sib = ct->sib; + ct->sib = aid; + ctype_setname(cta, decl.uv_redir); + } + + ctype_setname(ct, decl.uv_name); + uc_ctype_addname(cp->cts, ct, id); + } + } + + cp_decl_reset(&decl); + + if (cp->tok != CTOK_EOF) + cp_err_token(cp, CTOK_EOF); +} + +/* ------------------------------------------------------------------------ */ + +/* C parser. */ +bool uc_cparse(CPState *cp) +{ + bool rv = true; + + cp_init(cp); + + if ((cp->mode & CPARSE_MODE_MULTI)) + cp_decl_multi(cp); + else + cp_decl_single(cp); + + if (cp->uv_param && cp->uv_param != &cp->uv_vm->stack.entries[cp->uv_vm->stack.count]) + cp_err(cp, "wrong number of type parameters"); + + uc_assertCP(cp->depth == 0, "unbalanced cparser declaration depth"); + + if (cp->error) { + uc_vm_raise_exception(cp->cts->vm, EXCEPTION_SYNTAX, + "invalid C type: %s", cp->error); + + rv = false; + } + + cp_cleanup(cp); + + return rv; +} diff --git a/lib/ffi/uc_cparse.h b/lib/ffi/uc_cparse.h new file mode 100644 index 00000000..43cd7c9e --- /dev/null +++ b/lib/ffi/uc_cparse.h @@ -0,0 +1,77 @@ +/* +** C declaration parser. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This header contains derived work from LuaJIT's FFI C parser. +** +** Modifications: +** - Adapted for ucode VM integration +** - Removed JIT-specific dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#ifndef _UC_CPARSE_H +#define _UC_CPARSE_H + +#include +#include + +#include "uc_def.h" +#include "uc_ctype.h" + +/* C parser limits. */ +#define CPARSE_MAX_BUF 32768 /* Max. token buffer size. */ +#define CPARSE_MAX_DECLSTACK 100 /* Max. declaration stack depth. */ +#define CPARSE_MAX_DECLDEPTH 20 /* Max. recursive declaration depth. */ +#define CPARSE_MAX_PACKSTACK 7 /* Max. pack pragma stack depth. */ + +/* Flags for C parser mode. */ +#define CPARSE_MODE_MULTI 1 /* Process multiple declarations. */ +#define CPARSE_MODE_ABSTRACT 2 /* Accept abstract declarators. */ +#define CPARSE_MODE_DIRECT 4 /* Accept direct declarators. */ +#define CPARSE_MODE_FIELD 8 /* Accept field width in bits, too. */ +#define CPARSE_MODE_NOIMPLICIT 16 /* Reject implicit declarations. */ +#define CPARSE_MODE_SKIP 32 /* Skip definitions, ignore errors. */ + +typedef int CPChar; /* C parser character. Unsigned ext. from char. */ +typedef int CPToken; /* C parser token. */ + +/* C parser internal value representation. */ +typedef struct CPValue { + union { + int32_t i32; /* Value for CTID_INT32. */ + uint32_t u32; /* Value for CTID_UINT32. */ + }; + CTypeID id; /* C Type ID of the value. */ +} CPValue; + +/* C parser state. */ +typedef struct CPState { + CPChar c; /* Current character. */ + CPToken tok; /* Current token. */ + CPValue val; /* Token value. */ + CType *ct; /* C type table entry. */ + const char *p; /* Current position in input buffer. */ + CTState *cts; /* C type state. */ + const char *srcname; /* Current source name. */ + size_t linenumber; /* Input line counter. */ + int depth; /* Recursive declaration depth. */ + uint32_t tmask; /* Type mask for next identifier. */ + uint32_t mode; /* C parser mode. */ + uint8_t packstack[CPARSE_MAX_PACKSTACK]; /* Stack for pack pragmas. */ + uint8_t curpack; /* Current position in pack pragma stack. */ + uc_vm_t *uv_vm; + struct printbuf pb; + uc_value_t *uv_str; + uc_value_t **uv_param; + char *error; + struct cp_func_ids_buf { size_t count; CTypeID *entries; } func_ids_buf; /* Optional buffer to collect function type IDs */ + struct cp_func_ids_buf *func_ids; /* Pointer to func_ids_buf or external buffer */ +} CPState; + +UC_NOAPI bool uc_cparse(CPState *cp); + +UC_NOAPI int uc_cparse_case(uc_value_t *str, const char *match); + +#endif diff --git a/lib/ffi/uc_ctype.c b/lib/ffi/uc_ctype.c new file mode 100644 index 00000000..110b1775 --- /dev/null +++ b/lib/ffi/uc_ctype.c @@ -0,0 +1,802 @@ +/* +** C type management. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This file contains derived work from LuaJIT's FFI type system (lj_ctype.c). +** +** Modifications: +** - Adapted VM interactions to use ucode's API (uc_vm_t, uc_value_t, etc.) +** - Adapted type registration and lookup for ucode resource system +** - Removed JIT-specific code and dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + + +#include +#include + +#include "ucode/util.h" + +#include "uc_ctype.h" + +/* Hash constants from lj_tab.h - inlined since lj_tab.h removed */ +#define HASH_ROT1 14 +#define HASH_ROT2 5 +#define HASH_ROT3 13 + +static UC_AINLINE uint32_t hashrot(uint32_t lo, uint32_t hi) +{ +#if UC_TARGET_X86ORX64 + lo ^= hi; hi = uc_rol(hi, HASH_ROT1); + lo -= hi; hi = uc_rol(hi, HASH_ROT2); + hi ^= lo; hi -= uc_rol(lo, HASH_ROT3); +#else + lo ^= hi; + lo = lo - uc_rol(hi, HASH_ROT1); + hi = lo ^ uc_rol(hi, HASH_ROT1 + HASH_ROT2); + hi = hi - uc_rol(lo, HASH_ROT3); +#endif + return hi; +} + +/* -- C type definitions -------------------------------------------------- */ + +/* Predefined typedefs. */ +#define CTTDDEF(_) \ + /* Vararg handling. */ \ + _("va_list", P_VOID) \ + _("__builtin_va_list", P_VOID) \ + _("__gnuc_va_list", P_VOID) \ + /* From stddef.h. */ \ + _("ptrdiff_t", INT_PSZ) \ + _("size_t", UINT_PSZ) \ + _("wchar_t", WCHAR) \ + /* Subset of stdint.h. */ \ + _("int8_t", INT8) \ + _("int16_t", INT16) \ + _("int32_t", INT32) \ + _("int64_t", INT64) \ + _("uint8_t", UINT8) \ + _("uint16_t", UINT16) \ + _("uint32_t", UINT32) \ + _("uint64_t", UINT64) \ + _("intptr_t", INT_PSZ) \ + _("uintptr_t", UINT_PSZ) \ + /* From POSIX. */ \ + _("ssize_t", INT_PSZ) \ + /* End of typedef list. */ + +/* Keywords (only the ones we actually care for). */ +#define CTKWDEF(_) \ + /* Type specifiers. */ \ + _("void", -1, CTOK_VOID) \ + _("_Bool", 0, CTOK_BOOL) \ + _("bool", 1, CTOK_BOOL) \ + _("char", 1, CTOK_CHAR) \ + _("int", 4, CTOK_INT) \ + _("__int8", 1, CTOK_INT) \ + _("__int16", 2, CTOK_INT) \ + _("__int32", 4, CTOK_INT) \ + _("__int64", 8, CTOK_INT) \ + _("float", 4, CTOK_FP) \ + _("double", 8, CTOK_FP) \ + _("long", 0, CTOK_LONG) \ + _("short", 0, CTOK_SHORT) \ + _("_Complex", 0, CTOK_COMPLEX) \ + _("complex", 0, CTOK_COMPLEX) \ + _("__complex", 0, CTOK_COMPLEX) \ + _("__complex__", 0, CTOK_COMPLEX) \ + _("signed", 0, CTOK_SIGNED) \ + _("__signed", 0, CTOK_SIGNED) \ + _("__signed__", 0, CTOK_SIGNED) \ + _("unsigned", 0, CTOK_UNSIGNED) \ + /* Type qualifiers. */ \ + _("const", 0, CTOK_CONST) \ + _("__const", 0, CTOK_CONST) \ + _("__const__", 0, CTOK_CONST) \ + _("volatile", 0, CTOK_VOLATILE) \ + _("__volatile", 0, CTOK_VOLATILE) \ + _("__volatile__", 0, CTOK_VOLATILE) \ + _("restrict", 0, CTOK_RESTRICT) \ + _("__restrict", 0, CTOK_RESTRICT) \ + _("__restrict__", 0, CTOK_RESTRICT) \ + _("inline", 0, CTOK_INLINE) \ + _("__inline", 0, CTOK_INLINE) \ + _("__inline__", 0, CTOK_INLINE) \ + /* Storage class specifiers. */ \ + _("typedef", 0, CTOK_TYPEDEF) \ + _("extern", 0, CTOK_EXTERN) \ + _("static", 0, CTOK_STATIC) \ + _("auto", 0, CTOK_AUTO) \ + _("register", 0, CTOK_REGISTER) \ + /* GCC Attributes. */ \ + _("__extension__", 0, CTOK_EXTENSION) \ + _("__attribute", 0, CTOK_ATTRIBUTE) \ + _("__attribute__", 0, CTOK_ATTRIBUTE) \ + _("asm", 0, CTOK_ASM) \ + _("__asm", 0, CTOK_ASM) \ + _("__asm__", 0, CTOK_ASM) \ + /* MSVC Attributes. */ \ + _("__declspec", 0, CTOK_DECLSPEC) \ + _("__cdecl", CTCC_CDECL, CTOK_CCDECL) \ + _("__thiscall", CTCC_THISCALL, CTOK_CCDECL) \ + _("__fastcall", CTCC_FASTCALL, CTOK_CCDECL) \ + _("__stdcall", CTCC_STDCALL, CTOK_CCDECL) \ + _("__ptr32", 4, CTOK_PTRSZ) \ + _("__ptr64", 8, CTOK_PTRSZ) \ + /* Other type specifiers. */ \ + _("struct", 0, CTOK_STRUCT) \ + _("union", 0, CTOK_UNION) \ + _("enum", 0, CTOK_ENUM) \ + /* Operators. */ \ + _("sizeof", 0, CTOK_SIZEOF) \ + _("__alignof", 0, CTOK_ALIGNOF) \ + _("__alignof__", 0, CTOK_ALIGNOF) \ + /* End of keyword list. */ + +/* Type info for predefined types. Size merged in. */ +static CTInfo uc_ctype_typeinfo[] = { +#define CTTYINFODEF(id, sz, ct, info) CTINFO((ct), (((sz) & 0x3fu) << 10) + (info)), +#define CTTDINFODEF(name, id) CTINFO(CT_TYPEDEF, CTID_##id), +#define CTKWINFODEF(name, sz, kw) CTINFO(CT_KW, (((sz) & 0x3fu) << 10) + (kw)), + CTTYDEF(CTTYINFODEF) + CTTDDEF(CTTDINFODEF) + CTKWDEF(CTKWINFODEF) +#undef CTTYINFODEF +#undef CTTDINFODEF +#undef CTKWINFODEF + 0}; + +/* Predefined type names collected in a single string. */ +static const char *const uc_ctype_typenames = +#define CTTDNAMEDEF(name, id) name "\0" +#define CTKWNAMEDEF(name, sz, cds) name "\0" + CTTDDEF(CTTDNAMEDEF) + CTKWDEF(CTKWNAMEDEF) +#undef CTTDNAMEDEF +#undef CTKWNAMEDEF + ; + +#define CTTYPEINFO_NUM (sizeof(uc_ctype_typeinfo) / sizeof(CTInfo) - 1) +#ifdef LUAJIT_CTYPE_CHECK_ANCHOR +#define CTTYPETAB_MIN CTTYPEINFO_NUM +#else +#define CTTYPETAB_MIN 128 +#endif + +/* -- C type interning ---------------------------------------------------- */ + +#define ct_hashtype(info, size) (hashrot(info, size) & CTHASH_MASK) + +static inline uint32_t +ct_hashname(uc_value_t *uv) +{ + uint8_t *u8 = NULL; + size_t len = 0; + uint32_t h; + + h = ucv_type(uv); + + switch (h) + { + case UC_STRING: + u8 = (uint8_t *)ucv_string_get(uv); + len = ucv_string_length(uv); + break; + + default: + assert(0); + } + + while (len > 0) + { + h = h * 129 + (*u8++) + LH_PRIME; + len--; + } + + return h & CTHASH_MASK; +} + +/* Create new type element. */ +CTypeID uc_ctype_new(CTState *cts, CType **ctp) +{ + CTypeID id = cts->vtab.count; + CType *ct; + if (UC_UNLIKELY(id >= CTID_MAX)) + { + uc_vm_raise_exception(cts->vm, EXCEPTION_RUNTIME, + "FFI type table overflow"); + return 0; + } + + uc_vector_grow(&cts->vtab); + *ctp = ct = &cts->vtab.entries[cts->vtab.count++]; + ct->info = 0; + ct->size = 0; + ct->sib = 0; + ct->next = 0; + ct->uv_name = NULL; + return id; +} + +/* Intern a type element. */ +CTypeID uc_ctype_intern(CTState *cts, CTInfo info, CTSize size) +{ + uint32_t h = ct_hashtype(info, size); + CTypeID id = cts->hash[h]; + uc_assertCTS(cts->L, "uninitialized cts->L"); + while (id) + { + CType *ct = ctype_get(cts, id); + if (ct->info == info && ct->size == size) + return id; + id = ct->next; + } + uc_vector_grow(&cts->vtab); + id = cts->vtab.count++; + cts->vtab.entries[id].info = info; + cts->vtab.entries[id].size = size; + cts->vtab.entries[id].sib = 0; + cts->vtab.entries[id].next = cts->hash[h]; + cts->vtab.entries[id].uv_name = NULL; + cts->hash[h] = (CTypeID1)id; + return id; +} + +/* Add type element to hash table. */ +static void ctype_addtype(CTState *cts, CType *ct, CTypeID id) +{ + uint32_t h = ct_hashtype(ct->info, ct->size); + ct->next = cts->hash[h]; + cts->hash[h] = (CTypeID1)id; +} + +/* Add named element to hash table. */ +void uc_ctype_addname(CTState *cts, CType *ct, CTypeID id) +{ + uint32_t h = ct_hashname(ct->uv_name); + ct->next = cts->hash[h]; + cts->hash[h] = (CTypeID1)id; +} + +/* Get a C type by name, matching the type mask. */ +CTypeID uc_ctype_getname(CTState *cts, CType **ctp, uc_value_t *name, uint32_t tmask) +{ + CTypeID id = cts->hash[ct_hashname(name)]; + while (id) + { + CType *ct = ctype_get(cts, id); + + if (ucv_is_equal(ct->uv_name, name) && ((tmask >> ctype_type(ct->info)) & 1)) + { + *ctp = ct; + return id; + } + id = ct->next; + } + *ctp = &cts->vtab.entries[0]; /* Simplify caller logic. ctype_get() would assert. */ + return 0; +} + +/* Get a struct/union/enum/function field by name. */ +CType *uc_ctype_getfieldq(CTState *cts, CType *ct, uc_value_t *name, CTSize *ofs, + CTInfo *qual) +{ + while (ct->sib) + { + ct = ctype_get(cts, ct->sib); + if (ucv_is_equal(ct->uv_name, name)) + { + *ofs = ct->size; + return ct; + } + if (ctype_isxattrib(ct->info, CTA_SUBTYPE)) + { + CType *fct, *cct = ctype_child(cts, ct); + CTInfo q = 0; + while (ctype_isattrib(cct->info)) + { + if (ctype_attrib(cct->info) == CTA_QUAL) + q |= cct->size; + cct = ctype_child(cts, cct); + } + fct = uc_ctype_getfieldq(cts, cct, name, ofs, qual); + if (fct) + { + if (qual) + *qual |= q; + *ofs += ct->size; + return fct; + } + } + } + return NULL; /* Not found. */ +} + +/* -- C type information -------------------------------------------------- */ + +/* Follow references and get raw type for a C type ID. */ +CType *uc_ctype_rawref(CTState *cts, CTypeID id) +{ + CType *ct = ctype_get(cts, id); + while (ctype_isattrib(ct->info) || ctype_isref(ct->info)) + ct = ctype_child(cts, ct); + return ct; +} + +/* Get size for a C type ID. Does NOT support VLA/VLS. */ +CTSize uc_ctype_size(CTState *cts, CTypeID id) +{ + CType *ct = ctype_raw(cts, id); + return ctype_hassize(ct->info) ? ct->size : CTSIZE_INVALID; +} + +/* Get size for a variable-length C type. Does NOT support other C types. */ +CTSize uc_ctype_vlsize(CTState *cts, CType *ct, CTSize nelem) +{ + uint64_t xsz = 0; + if (ctype_isstruct(ct->info)) + { + CTypeID arrid = 0, fid = ct->sib; + xsz = ct->size; /* Add the struct size. */ + while (fid) + { + CType *ctf = ctype_get(cts, fid); + if (ctype_type(ctf->info) == CT_FIELD) + arrid = ctype_cid(ctf->info); /* Remember last field of VLS. */ + fid = ctf->sib; + } + ct = ctype_raw(cts, arrid); + } + uc_assertCTS(ctype_isvlarray(ct->info), "VLA expected"); + ct = ctype_rawchild(cts, ct); /* Get array element. */ + uc_assertCTS(ctype_hassize(ct->info), "bad VLA without size"); + /* Calculate actual size of VLA and check for overflow. */ + xsz += (uint64_t)ct->size * nelem; + return xsz < 0x80000000u ? (CTSize)xsz : CTSIZE_INVALID; +} + +/* Get type, qualifiers, size and alignment for a C type ID. */ +CTInfo uc_ctype_info(CTState *cts, CTypeID id, CTSize *szp) +{ + CTInfo qual = 0; + CType *ct = ctype_get(cts, id); + for (;;) + { + CTInfo info = ct->info; + if (ctype_isenum(info)) + { + /* Follow child. Need to look at its attributes, too. */ + } + else if (ctype_isattrib(info)) + { + if (ctype_isxattrib(info, CTA_QUAL)) + qual |= ct->size; + else if (ctype_isxattrib(info, CTA_ALIGN) && !(qual & CTFP_ALIGNED)) + qual |= CTFP_ALIGNED + CTALIGN(ct->size); + } + else + { + if (!(qual & CTFP_ALIGNED)) + qual |= (info & CTF_ALIGN); + qual |= (info & ~(CTF_ALIGN | CTMASK_CID)); + uc_assertCTS(ctype_hassize(info) || ctype_isfunc(info), + "ctype without size"); + *szp = ctype_isfunc(info) ? CTSIZE_INVALID : ct->size; + break; + } + ct = ctype_get(cts, ctype_cid(info)); + } + return qual; +} + +/* Ditto, but follow a reference. */ +CTInfo uc_ctype_info_raw(CTState *cts, CTypeID id, CTSize *szp) +{ + CType *ct = ctype_get(cts, id); + if (ctype_isref(ct->info)) + id = ctype_cid(ct->info); + return uc_ctype_info(cts, id, szp); +} + +/* -- C type representation ----------------------------------------------- */ + +/* Fixed max. length of a C type representation. */ +#define CTREPR_MAX 512 + +typedef struct CTRepr +{ + char *pb, *pe; + CTState *cts; + int needsp; + int ok; + char buf[CTREPR_MAX]; +} CTRepr; + +/* Prepend string. */ +static void ctype_prepstr(CTRepr *ctr, const char *str, size_t len) +{ + char *p = ctr->pb; + if (ctr->buf + len + 1 > p) + { + ctr->ok = 0; + return; + } + if (ctr->needsp) + *--p = ' '; + ctr->needsp = 1; + p -= len; + while (len-- > 0) + p[len] = str[len]; + ctr->pb = p; +} + +#define ctype_preplit(ctr, str) ctype_prepstr((ctr), "" str, sizeof(str) - 1) + +/* Prepend char. */ +static void ctype_prepc(CTRepr *ctr, int c) +{ + if (ctr->buf >= ctr->pb) + { + ctr->ok = 0; + return; + } + *--ctr->pb = c; +} + +/* Prepend number. */ +static void ctype_prepnum(CTRepr *ctr, uint32_t n) +{ + char *p = ctr->pb; + if (ctr->buf + 10 + 1 > p) + { + ctr->ok = 0; + return; + } + do + { + *--p = (char)('0' + n % 10); + } while (n /= 10); + ctr->pb = p; + ctr->needsp = 0; +} + +/* Append char. */ +static void ctype_appc(CTRepr *ctr, int c) +{ + if (ctr->pe >= ctr->buf + CTREPR_MAX) + { + ctr->ok = 0; + return; + } + *ctr->pe++ = c; +} + +/* Append number. */ +static void ctype_appnum(CTRepr *ctr, uint32_t n) +{ + char buf[10]; + char *p = buf + sizeof(buf); + char *q = ctr->pe; + if (q > ctr->buf + CTREPR_MAX - 10) + { + ctr->ok = 0; + return; + } + do + { + *--p = (char)('0' + n % 10); + } while (n /= 10); + do + { + *q++ = *p++; + } while (p < buf + sizeof(buf)); + ctr->pe = q; +} + +/* Prepend qualifiers. */ +static void ctype_prepqual(CTRepr *ctr, CTInfo info) +{ + if ((info & CTF_VOLATILE)) + ctype_preplit(ctr, "volatile"); + if ((info & CTF_CONST)) + ctype_preplit(ctr, "const"); +} + +/* Prepend named type. */ +static void ctype_preptype(CTRepr *ctr, CType *ct, CTInfo qual, const char *t) +{ + if (ct->uv_name) + { + ctype_prepstr(ctr, ucv_string_get(ct->uv_name), ucv_string_length(ct->uv_name)); + } + else + { + if (ctr->needsp) + ctype_prepc(ctr, ' '); + ctype_prepnum(ctr, ctype_typeid(ctr->cts, ct)); + ctr->needsp = 1; + } + ctype_prepstr(ctr, t, strlen(t)); + ctype_prepqual(ctr, qual); +} + +static void ctype_repr(CTRepr *ctr, CTypeID id) +{ + CType *ct = ctype_get(ctr->cts, id); + CTInfo qual = 0; + int ptrto = 0; + for (;;) + { + CTInfo info = ct->info; + CTSize size = ct->size; + switch (ctype_type(info)) + { + case CT_NUM: + if ((info & CTF_BOOL)) + { + ctype_preplit(ctr, "bool"); + } + else if ((info & CTF_FP)) + { + if (size == sizeof(double)) + ctype_preplit(ctr, "double"); + else if (size == sizeof(float)) + ctype_preplit(ctr, "float"); + else + ctype_preplit(ctr, "long double"); + } + else if (size == 1) + { + if (!((info ^ CTF_UCHAR) & CTF_UNSIGNED)) + ctype_preplit(ctr, "char"); + else if (CTF_UCHAR) + ctype_preplit(ctr, "signed char"); + else + ctype_preplit(ctr, "unsigned char"); + } + else if (size < 8) + { + if (size == 4) + ctype_preplit(ctr, "int"); + else + ctype_preplit(ctr, "short"); + if ((info & CTF_UNSIGNED)) + ctype_preplit(ctr, "unsigned"); + } + else + { + ctype_preplit(ctr, "_t"); + ctype_prepnum(ctr, size * 8); + ctype_preplit(ctr, "int"); + if ((info & CTF_UNSIGNED)) + ctype_prepc(ctr, 'u'); + } + ctype_prepqual(ctr, (qual | info)); + return; + case CT_VOID: + ctype_preplit(ctr, "void"); + ctype_prepqual(ctr, (qual | info)); + return; + case CT_STRUCT: + ctype_preptype(ctr, ct, qual, (info & CTF_UNION) ? "union" : "struct"); + return; + case CT_ENUM: + if (id == CTID_CTYPEID) + { + ctype_preplit(ctr, "ctype"); + return; + } + ctype_preptype(ctr, ct, qual, "enum"); + return; + case CT_ATTRIB: + if (ctype_attrib(info) == CTA_QUAL) + qual |= size; + break; + case CT_PTR: + if ((info & CTF_REF)) + { + CType *ct_child = ctype_get(ctr->cts, ctype_cid(info)); + if (ctype_type(ct_child->info) != CT_PTR) + ctype_prepc(ctr, '&'); + } + else + { + ctype_prepqual(ctr, (qual | info)); + if (UC_64 && size == 4) + ctype_preplit(ctr, "__ptr32"); + ctype_prepc(ctr, '*'); + } + qual = 0; + ptrto = 1; + ctr->needsp = 1; + break; + case CT_ARRAY: + if (ctype_isrefarray(info)) + { + ctr->needsp = 1; + if (ptrto) + { + ptrto = 0; + ctype_prepc(ctr, '('); + ctype_appc(ctr, ')'); + } + ctype_appc(ctr, '['); + if (size != CTSIZE_INVALID) + { + CTSize csize = ctype_child(ctr->cts, ct)->size; + ctype_appnum(ctr, csize ? size / csize : 0); + } + else if ((info & CTF_VLA)) + { + ctype_appc(ctr, '?'); + } + ctype_appc(ctr, ']'); + } + else if ((info & CTF_COMPLEX)) + { + if (size == 2 * sizeof(float)) + ctype_preplit(ctr, "float"); + ctype_preplit(ctr, "complex"); + return; + } + else + { + ctype_preplit(ctr, ")))"); + ctype_prepnum(ctr, size); + ctype_preplit(ctr, "__attribute__((vector_size("); + } + break; + case CT_FUNC: + ctr->needsp = 1; + if (ptrto) + { + ptrto = 0; + ctype_prepc(ctr, '('); + ctype_appc(ctr, ')'); + } + ctype_appc(ctr, '('); + ctype_appc(ctr, ')'); + break; + default: + uc_assertG_(ctr->cts->g, 0, "bad ctype %08x", info); + break; + } + ct = ctype_get(ctr->cts, ctype_cid(info)); + } +} + +/* Return a printable representation of a C type. */ +uc_value_t *uc_ctype_repr(uc_vm_t *vm, CTypeID id, uc_value_t *name) +{ + CTRepr ctr; + ctr.pb = ctr.pe = &ctr.buf[CTREPR_MAX / 2]; + ctr.cts = ctype_cts(vm); + ctr.ok = 1; + ctr.needsp = 0; + if (name) + ctype_prepstr(&ctr, ucv_string_get(name), ucv_string_length(name)); + ctype_repr(&ctr, id); + if (UC_UNLIKELY(!ctr.ok)) + return ucv_string_new("?"); + return ucv_string_new_length(ctr.pb, ctr.pe - ctr.pb); +} + +/* Convert int64_t/uint64_t to string with 'LL' or 'ULL' suffix. */ +uc_value_t *uc_ctype_repr_int64(uint64_t n, int isunsigned) +{ + char buf[1 + 20 + 3]; + char *p = buf + sizeof(buf); + int sign = 0; + *--p = 'L'; + *--p = 'L'; + if (isunsigned) + { + *--p = 'U'; + } + else if ((int64_t)n < 0) + { + n = ~n + 1u; + sign = 1; + } + do + { + *--p = (char)('0' + n % 10); + } while (n /= 10); + if (sign) + *--p = '-'; + return ucv_string_new_length(p, (size_t)(buf + sizeof(buf) - p)); +} + +/* Convert complex to string with 'i' or 'I' suffix. */ +uc_value_t *uc_ctype_repr_complex(void *sp, CTSize size) +{ + uc_stringbuf_t *sb = ucv_stringbuf_new(); + TValue re, im; + if (size == 2 * sizeof(double)) + { + re.n = *(double *)sp; + im.n = ((double *)sp)[1]; + } + else + { + re.n = (double)*(float *)sp; + im.n = (double)((float *)sp)[1]; + } + ucv_stringbuf_printf(sb, "%.14g", re.n); + if (!(im.u32.hi & 0x80000000u) || im.n != im.n) + ucv_stringbuf_append(sb, "+"); + ucv_stringbuf_printf(sb, "%.14g", im.n); + if (sb->buf[sb->bpos - 1] >= 'a') + ucv_stringbuf_append(sb, "I"); + else + ucv_stringbuf_append(sb, "i"); + return ucv_stringbuf_finish(sb); +} + +/* -- C type state -------------------------------------------------------- */ + +static void free_ctypes(void *ud) +{ + CTState *cts = ud; + + while (cts->vtab.count) + ucv_put(cts->vtab.entries[--cts->vtab.count].uv_name); + + free(cts->vtab.entries); + free(cts->vcbid.entries); + free(cts); +} + +/* Initialize C type table and state. */ +CTState *uc_ctype_init(uc_vm_t *vm) +{ + CTState *cts = xalloc(sizeof(CTState)); + CType *ct; // = lj_mem_newvec(L, CTTYPETAB_MIN, CType); + const char *name = uc_ctype_typenames; + CTypeID id; + memset(cts, 0, sizeof(CTState)); + // cts->tab = ct; + // cts->sizetab = CTTYPETAB_MIN; + // cts->top = CTTYPEINFO_NUM; + //cts->L = NULL; + //cts->g = G(L); + cts->vm = vm; + + for (id = 0; id < CTTYPEINFO_NUM; id++, ct++) + { + CTInfo info = uc_ctype_typeinfo[id]; + uc_vector_grow(&cts->vtab); + ct = &cts->vtab.entries[cts->vtab.count++]; + ct->size = (CTSize)((int32_t)(info << 16) >> 26); + ct->info = info & 0xffff03ffu; + ct->sib = 0; + if (ctype_type(info) == CT_KW || ctype_istypedef(info)) + { + size_t len = strlen(name); + uc_value_t *str = ucv_string_new_length(name, len); + ctype_setname(ct, str); + ucv_put(str); + name += len + 1; + uc_ctype_addname(cts, ct, id); + } + else + { + ct->uv_name = NULL; + ct->next = 0; + if (!ctype_isenum(info)) + ctype_addtype(cts, ct, id); + } + } + + //setmref(G(L)->ctype_state, cts); + + uc_resource_type_t *cts_res; + + cts_res = ucv_resource_type_add(vm, "ffi.types", NULL, free_ctypes); + + uc_vm_registry_set(vm, "ffi.types", ucv_resource_new(cts_res, cts)); + + return cts; +} diff --git a/lib/ffi/uc_ctype.h b/lib/ffi/uc_ctype.h new file mode 100644 index 00000000..81cb6c50 --- /dev/null +++ b/lib/ffi/uc_ctype.h @@ -0,0 +1,477 @@ +/* +** C type management. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice below. +** +** This header contains derived work from LuaJIT's FFI type system. +** +** Modifications: +** - Adapted for ucode VM integration (uc_vm_t, uc_value_t, etc.) +** - Adapted type registry for ucode resource system +** - Removed JIT-specific dependencies +** +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#ifndef _UC_CTYPE_H +#define _UC_CTYPE_H + +#include +#include + +#include "uc_def.h" + +/* C data object - FFI specific */ +typedef struct GCcdata { + uint16_t ctypeid; + uint8_t isvla; + uc_value_t *refs; +} GCcdata; + +typedef struct GCcdataVar { + uint16_t offset; + uint16_t extra; + size_t len; +} GCcdataVar; + +#define cdataptr(cd) ((void *)((cd)+1)) +#define cdataisv(cd) ((cd)->isvla) +#define cdatav(cd) ((GCcdataVar *)((char *)(cd) - sizeof(GCcdataVar))) +#define cdatavlen(cd) check_exp(cdataisv(cd), cdatav(cd)->len) +#define sizecdatav(cd) (cdatavlen(cd) + cdatav(cd)->extra) +#define memcdatav(cd) ((void *)((char *)(cd) - cdatav(cd)->offset)) + +/* -- C type definitions -------------------------------------------------- */ + +/* C type numbers. Highest 4 bits of C type info. ORDER CT. */ +enum { + /* Externally visible types. */ + CT_NUM, /* Integer or floating-point numbers. */ + CT_STRUCT, /* Struct or union. */ + CT_PTR, /* Pointer or reference. */ + CT_ARRAY, /* Array or complex type. */ + CT_MAYCONVERT = CT_ARRAY, + CT_VOID, /* Void type. */ + CT_ENUM, /* Enumeration. */ + CT_HASSIZE = CT_ENUM, /* Last type where ct->size holds the actual size. */ + CT_FUNC, /* Function. */ + CT_TYPEDEF, /* Typedef. */ + CT_ATTRIB, /* Miscellaneous attributes. */ + /* Internal element types. */ + CT_FIELD, /* Struct/union field or function parameter. */ + CT_BITFIELD, /* Struct/union bitfield. */ + CT_CONSTVAL, /* Constant value. */ + CT_EXTERN, /* External reference. */ + CT_KW /* Keyword. */ +}; + +UC_STATIC_ASSERT(((int)CT_PTR & (int)CT_ARRAY) == CT_PTR); +UC_STATIC_ASSERT(((int)CT_STRUCT & (int)CT_ARRAY) == CT_STRUCT); + +/* +** ---------- info ------------ +** |type flags... A cid | size | sib | next | name | +** +----------------------------+--------+-------+-------+-------+-- +** |NUM BFcvUL.. A | size | | type | | +** |STRUCT ..cvU..V A | size | field | name? | name? | +** |PTR ..cvR... A cid | size | | type | | +** |ARRAY VCcv...V A cid | size | | type | | +** |VOID ..cv.... A | size | | type | | +** |ENUM A cid | size | const | name? | name? | +** |FUNC ....VS.. cc cid | nargs | field | name? | name? | +** |TYPEDEF cid | | | name | name | +** |ATTRIB attrnum cid | attr | sib? | type? | | +** |FIELD cid | offset | field | | name? | +** |BITFIELD B.cvU csz bsz pos | offset | field | | name? | +** |CONSTVAL c cid | value | const | name | name | +** |EXTERN cid | | sib? | name | name | +** |KW tok | size | | name | name | +** +----------------------------+--------+-------+-------+-------+-- +** ^^ ^^--- bits used for C type conversion dispatch +*/ + +/* C type info flags. TFFArrrr */ +#define CTF_BOOL 0x08000000u /* Boolean: NUM, BITFIELD. */ +#define CTF_FP 0x04000000u /* Floating-point: NUM. */ +#define CTF_CONST 0x02000000u /* Const qualifier. */ +#define CTF_VOLATILE 0x01000000u /* Volatile qualifier. */ +#define CTF_UNSIGNED 0x00800000u /* Unsigned: NUM, BITFIELD. */ +#define CTF_LONG 0x00400000u /* Long: NUM. */ +#define CTF_VLA 0x00100000u /* Variable-length: ARRAY, STRUCT. */ +#define CTF_REF 0x00800000u /* Reference: PTR. */ +#define CTF_VECTOR 0x08000000u /* Vector: ARRAY. */ +#define CTF_COMPLEX 0x04000000u /* Complex: ARRAY. */ +#define CTF_UNION 0x00800000u /* Union: STRUCT. */ +#define CTF_VARARG 0x00800000u /* Vararg: FUNC. */ +#define CTF_SSEREGPARM 0x00400000u /* SSE register parameters: FUNC. */ + +#define CTF_QUAL (CTF_CONST|CTF_VOLATILE) +#define CTF_ALIGN (CTMASK_ALIGN< 0 ? CTF_UNSIGNED : 0) + +/* Flags used in parser. .F.Ammvf cp->attr */ +#define CTFP_ALIGNED 0x00000001u /* cp->attr + ALIGN */ +#define CTFP_PACKED 0x00000002u /* cp->attr */ +/* ...C...f cp->fattr */ +#define CTFP_CCONV 0x00000001u /* cp->fattr + CCONV/[SSE]REGPARM */ + +/* C type info bitfields. */ +#define CTMASK_CID 0x0000ffffu /* Max. 65536 type IDs. */ +#define CTMASK_NUM 0xf0000000u /* Max. 16 type numbers. */ +#define CTSHIFT_NUM 28 +#define CTMASK_ALIGN 15 /* Max. alignment is 2^15. */ +#define CTSHIFT_ALIGN 16 +#define CTMASK_ATTRIB 255 /* Max. 256 attributes. */ +#define CTSHIFT_ATTRIB 16 +#define CTMASK_CCONV 3 /* Max. 4 calling conventions. */ +#define CTSHIFT_CCONV 16 +#define CTMASK_REGPARM 3 /* Max. 0-3 regparms. */ +#define CTSHIFT_REGPARM 18 +/* Bitfields only used in parser. */ +#define CTMASK_VSIZEP 15 /* Max. vector size is 2^15. */ +#define CTSHIFT_VSIZEP 4 +#define CTMASK_MSIZEP 255 /* Max. type size (via mode) is 128. */ +#define CTSHIFT_MSIZEP 8 + +/* Info bits for BITFIELD. Max. size of bitfield is 64 bits. */ +#define CTBSZ_MAX 32 /* Max. size of bitfield is 32 bit. */ +#define CTBSZ_FIELD 127 /* Temp. marker for regular field. */ +#define CTMASK_BITPOS 127 +#define CTMASK_BITBSZ 127 +#define CTMASK_BITCSZ 127 +#define CTSHIFT_BITPOS 0 +#define CTSHIFT_BITBSZ 8 +#define CTSHIFT_BITCSZ 16 + +#define CTF_INSERT(info, field, val) \ + info = (info & ~(CTMASK_##field<> CTSHIFT_NUM) +#define ctype_cid(info) ((CTypeID)((info) & CTMASK_CID)) +#define ctype_align(info) (((info) >> CTSHIFT_ALIGN) & CTMASK_ALIGN) +#define ctype_attrib(info) (((info) >> CTSHIFT_ATTRIB) & CTMASK_ATTRIB) +#define ctype_bitpos(info) (((info) >> CTSHIFT_BITPOS) & CTMASK_BITPOS) +#define ctype_bitbsz(info) (((info) >> CTSHIFT_BITBSZ) & CTMASK_BITBSZ) +#define ctype_bitcsz(info) (((info) >> CTSHIFT_BITCSZ) & CTMASK_BITCSZ) +#define ctype_vsizeP(info) (((info) >> CTSHIFT_VSIZEP) & CTMASK_VSIZEP) +#define ctype_msizeP(info) (((info) >> CTSHIFT_MSIZEP) & CTMASK_MSIZEP) +#define ctype_cconv(info) (((info) >> CTSHIFT_CCONV) & CTMASK_CCONV) + +/* Simple type checks. */ +#define ctype_isnum(info) (ctype_type((info)) == CT_NUM) +#define ctype_isvoid(info) (ctype_type((info)) == CT_VOID) +#define ctype_isptr(info) (ctype_type((info)) == CT_PTR) +#define ctype_isarray(info) (ctype_type((info)) == CT_ARRAY) +#define ctype_isstruct(info) (ctype_type((info)) == CT_STRUCT) +#define ctype_isfunc(info) (ctype_type((info)) == CT_FUNC) +#define ctype_isenum(info) (ctype_type((info)) == CT_ENUM) +#define ctype_istypedef(info) (ctype_type((info)) == CT_TYPEDEF) +#define ctype_isattrib(info) (ctype_type((info)) == CT_ATTRIB) +#define ctype_isfield(info) (ctype_type((info)) == CT_FIELD) +#define ctype_isbitfield(info) (ctype_type((info)) == CT_BITFIELD) +#define ctype_isconstval(info) (ctype_type((info)) == CT_CONSTVAL) +#define ctype_isextern(info) (ctype_type((info)) == CT_EXTERN) +#define ctype_hassize(info) (ctype_type((info)) <= CT_HASSIZE) + +/* Combined type and flag checks. */ +#define ctype_isinteger(info) \ + (((info) & (CTMASK_NUM|CTF_BOOL|CTF_FP)) == CTINFO(CT_NUM, 0)) +#define ctype_isinteger_or_bool(info) \ + (((info) & (CTMASK_NUM|CTF_FP)) == CTINFO(CT_NUM, 0)) +#define ctype_isbool(info) \ + (((info) & (CTMASK_NUM|CTF_BOOL)) == CTINFO(CT_NUM, CTF_BOOL)) +#define ctype_isfp(info) \ + (((info) & (CTMASK_NUM|CTF_FP)) == CTINFO(CT_NUM, CTF_FP)) + +#define ctype_ispointer(info) \ + ((ctype_type(info) >> 1) == (CT_PTR >> 1)) /* Pointer or array. */ +#define ctype_isref(info) \ + (((info) & (CTMASK_NUM|CTF_REF)) == CTINFO(CT_PTR, CTF_REF)) + +#define ctype_isrefarray(info) \ + (((info) & (CTMASK_NUM|CTF_VECTOR|CTF_COMPLEX)) == CTINFO(CT_ARRAY, 0)) +#define ctype_isvector(info) \ + (((info) & (CTMASK_NUM|CTF_VECTOR)) == CTINFO(CT_ARRAY, CTF_VECTOR)) +#define ctype_iscomplex(info) \ + (((info) & (CTMASK_NUM|CTF_COMPLEX)) == CTINFO(CT_ARRAY, CTF_COMPLEX)) + +#define ctype_isvltype(info) \ + (((info) & ((CTMASK_NUM|CTF_VLA) - (2u<g, (c), __VA_ARGS__)) +#else +#define uc_assertCTS(c, ...) ((void)cts) +#endif + +/* -- Predefined types ---------------------------------------------------- */ + +/* Target-dependent types. */ +#if UC_TARGET_PPC +#define CTTYDEFP(_) \ + _(LINT32, 4, CT_NUM, CTF_LONG|CTALIGN(2)) +#else +#define CTTYDEFP(_) +#endif + +#define CTF_LONG_IF8 (CTF_LONG * (sizeof(long) == 8)) + +/* Common types. */ +#define CTTYDEF(_) \ + _(NONE, 0, CT_ATTRIB, CTATTRIB(CTA_BAD)) \ + _(VOID, -1, CT_VOID, CTALIGN(0)) \ + _(CVOID, -1, CT_VOID, CTF_CONST|CTALIGN(0)) \ + _(BOOL, 1, CT_NUM, CTF_BOOL|CTF_UNSIGNED|CTALIGN(0)) \ + _(CCHAR, 1, CT_NUM, CTF_CONST|CTF_UCHAR|CTALIGN(0)) \ + _(INT8, 1, CT_NUM, CTALIGN(0)) \ + _(UINT8, 1, CT_NUM, CTF_UNSIGNED|CTALIGN(0)) \ + _(INT16, 2, CT_NUM, CTALIGN(1)) \ + _(UINT16, 2, CT_NUM, CTF_UNSIGNED|CTALIGN(1)) \ + _(INT32, 4, CT_NUM, CTALIGN(2)) \ + _(UINT32, 4, CT_NUM, CTF_UNSIGNED|CTALIGN(2)) \ + _(INT64, 8, CT_NUM, CTF_LONG_IF8|CTALIGN(3)) \ + _(UINT64, 8, CT_NUM, CTF_UNSIGNED|CTF_LONG_IF8|CTALIGN(3)) \ + _(FLOAT, 4, CT_NUM, CTF_FP|CTALIGN(2)) \ + _(DOUBLE, 8, CT_NUM, CTF_FP|CTALIGN(3)) \ + _(COMPLEX_FLOAT, 8, CT_ARRAY, CTF_COMPLEX|CTALIGN(2)|CTID_FLOAT) \ + _(COMPLEX_DOUBLE, 16, CT_ARRAY, CTF_COMPLEX|CTALIGN(3)|CTID_DOUBLE) \ + _(P_VOID, CTSIZE_PTR, CT_PTR, CTALIGN_PTR|CTID_VOID) \ + _(P_CVOID, CTSIZE_PTR, CT_PTR, CTALIGN_PTR|CTID_CVOID) \ + _(P_CCHAR, CTSIZE_PTR, CT_PTR, CTALIGN_PTR|CTID_CCHAR) \ + _(P_UINT8, CTSIZE_PTR, CT_PTR, CTALIGN_PTR|CTID_UINT8) \ + _(A_CCHAR, -1, CT_ARRAY, CTF_CONST|CTALIGN(0)|CTID_CCHAR) \ + _(CTYPEID, 4, CT_ENUM, CTALIGN(2)|CTID_INT32) \ + CTTYDEFP(_) \ + /* End of type list. */ + +/* Public predefined type IDs. */ +enum { +#define CTTYIDDEF(id, sz, ct, info) CTID_##id, +CTTYDEF(CTTYIDDEF) +#undef CTTYIDDEF + /* Predefined typedefs and keywords follow. */ + CTID_MAX = 65536 +}; + +/* Target-dependent type IDs. */ +#if UC_64 +#define CTID_INT_PSZ CTID_INT64 +#define CTID_UINT_PSZ CTID_UINT64 +#else +#define CTID_INT_PSZ CTID_INT32 +#define CTID_UINT_PSZ CTID_UINT32 +#endif + +#if UC_ABI_WIN +#define CTID_WCHAR CTID_UINT16 +#elif UC_TARGET_PPC +#define CTID_WCHAR CTID_LINT32 +#else +#define CTID_WCHAR CTID_INT32 +#endif + +/* -- C tokens and keywords ----------------------------------------------- */ + +/* C lexer keywords. */ +#define CTOKDEF(_) \ + _(IDENT, "") _(STRING, "") \ + _(INTEGER, "") _(EOF, "") \ + _(OROR, "||") _(ANDAND, "&&") _(EQ, "==") _(NE, "!=") \ + _(LE, "<=") _(GE, ">=") _(SHL, "<<") _(SHR, ">>") _(DEREF, "->") + +/* Simple declaration specifiers. */ +#define CDSDEF(_) \ + _(VOID) _(BOOL) _(CHAR) _(INT) _(FP) \ + _(LONG) _(LONGLONG) _(SHORT) _(COMPLEX) _(SIGNED) _(UNSIGNED) \ + _(CONST) _(VOLATILE) _(RESTRICT) _(INLINE) \ + _(TYPEDEF) _(EXTERN) _(STATIC) _(AUTO) _(REGISTER) + +/* C keywords. */ +#define CKWDEF(_) \ + CDSDEF(_) _(EXTENSION) _(ASM) _(ATTRIBUTE) \ + _(DECLSPEC) _(CCDECL) _(PTRSZ) \ + _(STRUCT) _(UNION) _(ENUM) \ + _(SIZEOF) _(ALIGNOF) + +/* C token numbers. */ +enum { + CTOK_OFS = 255, +#define CTOKNUM(name, sym) CTOK_##name, +#define CKWNUM(name) CTOK_##name, +CTOKDEF(CTOKNUM) +CKWDEF(CKWNUM) +#undef CTOKNUM +#undef CKWNUM + CTOK_FIRSTDECL = CTOK_VOID, + CTOK_FIRSTSCL = CTOK_TYPEDEF, + CTOK_LASTDECLFLAG = CTOK_REGISTER, + CTOK_LASTDECL = CTOK_ENUM +}; + +/* Declaration specifier flags. */ +enum { +#define CDSFLAG(name) CDF_##name = (1u << (CTOK_##name - CTOK_FIRSTDECL)), +CDSDEF(CDSFLAG) +#undef CDSFLAG + CDF__END +}; + +#define CDF_SCL (CDF_TYPEDEF|CDF_EXTERN|CDF_STATIC|CDF_AUTO|CDF_REGISTER) + +/* -- C type management --------------------------------------------------- */ + +/* Get C type state. */ +static UC_AINLINE CTState *ctype_cts(uc_vm_t *vm) +{ + return ucv_resource_data(uc_vm_registry_get(vm, "ffi.types"), NULL); +} + +/* Save and restore state of C type table. */ +#define UC_CTYPE_SAVE(cts) CTState savects_ = *(cts) +#define UC_CTYPE_RESTORE(cts) \ + ((cts)->top = savects_.top, \ + memcpy((cts)->hash, savects_.hash, sizeof(savects_.hash))) + +/* Check C type ID for validity when assertions are enabled. */ +static UC_AINLINE CTypeID ctype_check(unused CTState *cts, CTypeID id) +{ + uc_assertCTS(id > 0 && id < cts->top, "bad CTID %d", id); + return id; +} + +/* Get C type for C type ID. */ +static UC_AINLINE CType *ctype_get(CTState *cts, CTypeID id) +{ + return &cts->vtab.entries[ctype_check(cts, id)]; +} + +/* Get C type ID for a C type. */ +#define ctype_typeid(cts, ct) ((CTypeID)((ct) - (cts)->vtab.entries)) + +/* Get child C type. */ +static UC_AINLINE CType *ctype_child(CTState *cts, CType *ct) +{ + uc_assertCTS(!(ctype_isvoid(ct->info) || ctype_isstruct(ct->info) || + ctype_isbitfield(ct->info)), + "ctype %08x has no children", ct->info); + return ctype_get(cts, ctype_cid(ct->info)); +} + +/* Get raw type for a C type ID. */ +static UC_AINLINE CType *ctype_raw(CTState *cts, CTypeID id) +{ + CType *ct = ctype_get(cts, id); + while (ctype_isattrib(ct->info)) ct = ctype_child(cts, ct); + return ct; +} + +/* Get raw type of the child of a C type. */ +static UC_AINLINE CType *ctype_rawchild(CTState *cts, CType *ct) +{ + do { ct = ctype_child(cts, ct); } while (ctype_isattrib(ct->info)); + return ct; +} + +/* Set the name of a C type table element. */ +static UC_AINLINE void ctype_setname(CType *ct, uc_value_t *s) +{ + ucv_put(ct->uv_name); + ct->uv_name = ucv_get(s); +} + +UC_NOAPI CTypeID uc_ctype_new(CTState *cts, CType **ctp); +UC_NOAPI CTypeID uc_ctype_intern(CTState *cts, CTInfo info, CTSize size); +UC_NOAPI void uc_ctype_addname(CTState *cts, CType *ct, CTypeID id); +UC_NOAPI CTypeID uc_ctype_getname(CTState *cts, CType **ctp, uc_value_t *name, + uint32_t tmask); +UC_NOAPI CType *uc_ctype_getfieldq(CTState *cts, CType *ct, uc_value_t *name, + CTSize *ofs, CTInfo *qual); +#define uc_ctype_getfield(cts, ct, name, ofs) \ + uc_ctype_getfieldq((cts), (ct), (name), (ofs), NULL) +UC_NOAPI CType *uc_ctype_rawref(CTState *cts, CTypeID id); +UC_NOAPI CTSize uc_ctype_size(CTState *cts, CTypeID id); +UC_NOAPI CTSize uc_ctype_vlsize(CTState *cts, CType *ct, CTSize nelem); +UC_NOAPI CTInfo uc_ctype_info(CTState *cts, CTypeID id, CTSize *szp); +UC_NOAPI CTInfo uc_ctype_info_raw(CTState *cts, CTypeID id, CTSize *szp); +UC_NOAPI uc_value_t *uc_ctype_repr(uc_vm_t *vm, CTypeID id, uc_value_t *name); +UC_NOAPI uc_value_t *uc_ctype_repr_int64(uint64_t n, int isunsigned); +UC_NOAPI uc_value_t *uc_ctype_repr_complex(void *sp, CTSize size); +UC_NOAPI CTState *uc_ctype_init(uc_vm_t *vm); + +#endif diff --git a/lib/ffi/uc_def.h b/lib/ffi/uc_def.h new file mode 100644 index 00000000..70395370 --- /dev/null +++ b/lib/ffi/uc_def.h @@ -0,0 +1,121 @@ +/* +** ucode common internal definitions. +** Copyright (C) 2005-2025 Mike Pall. See Copyright Notice in NOTICE. +** +** Derived from LuaJIT's internal definitions. +** See NOTICE and ATTRIBUTION.md for complete attribution details. +*/ + +#ifndef _UC_DEF_H +#define _UC_DEF_H + +#include +#include +#include + +/* -- Target detection ---------------------------------------------------- */ + +/* -- Arch-specific settings ---------------------------------------------- */ + +#if defined(__i386__) || defined(__i386) || defined(__x86_64__) || defined(__x86_64) + +#define UC_TARGET_X86 1 +#define UC_TARGET_X86ORX64 1 +#if defined(__x86_64__) || defined(__x86_64) +#define UC_TARGET_X64 1 +#endif + +#elif defined(__ppc__) || defined(__ppc) || defined(__PPC__) || defined(__PPC) || defined(__powerpc__) || defined(__powerpc) || defined(__POWERPC__) || defined(__POWERPC) + +#define UC_TARGET_PPC 1 + +#endif + +/* -- Derived defines ----------------------------------------------------- */ + +#if UINTPTR_MAX == 0xffffffffffffffffULL +#define UC_64 1 +#else +#define UC_64 0 +#endif + +#pragma pack(push, 8) +typedef union TValue { + uint64_t u64; + double n; + struct { + uint32_t lo; + uint32_t hi; + } u32; + int32_t i; +} TValue; +#pragma pack(pop) +typedef const TValue cTValue; + +#define uc_num2int(n) ((int32_t)(n)) + +#define UC_STATIC_ASSERT(cond) \ + extern void uc_static_assert(int STATIC_ASSERTION_FAILED[(cond)?1:-1]) + +#define uc_rol(x, n) (((x)<<(n)) | ((x)>>(-(int)(n)&(8*sizeof(x)-1)))) + +#if defined(__GNUC__) || defined(__clang__) + +#define UC_NORET __attribute__((noreturn)) +#define UC_AINLINE inline __attribute__((always_inline)) +#define UC_NOINLINE __attribute__((noinline)) + +#if defined(__ELF__) || defined(__MACH__) +#define UC_NOAPI extern __attribute__((visibility("hidden"))) +#endif + +#if defined(__i386__) +#define UC_FASTCALL __attribute__((fastcall)) +#endif + +#define UC_LIKELY(x) __builtin_expect(!!(x), 1) +#define UC_UNLIKELY(x) __builtin_expect(!!(x), 0) + +#define uc_fls(x) ((uint32_t)(__builtin_clz(x)^31)) + +static UC_AINLINE uint64_t uc_num2u64(double n) +{ +#if defined(__i386__) || defined(__x86_64__) + int64_t i = (int64_t)n; + if (i < 0) i = (int64_t)(n - 18446744073709551616.0); + return (uint64_t)i; +#else + return (uint64_t)n; +#endif +} + +#ifndef UC_FASTCALL +#define UC_FASTCALL +#endif +#ifndef UC_NORET +#define UC_NORET +#endif +#ifndef UC_NOAPI +#define UC_NOAPI extern +#endif +#ifndef UC_LIKELY +#define UC_LIKELY(x) (x) +#define UC_UNLIKELY(x) (x) +#endif + +#ifdef LUA_USE_ASSERT +#define uc_assertG_(g, c, ...) \ + ((c) ? (void)0 : uc_assert_fail((g), __FILE__, __LINE__, __func__, __VA_ARGS__)) +#define uc_assertG(c, ...) uc_assertG_(g, (c), __VA_ARGS__) +#define uc_assertX(c, ...) uc_assertG_(NULL, (c), __VA_ARGS__) +#define check_exp(c, e) (uc_assertX((c), #c), (e)) +#else +#define uc_assertG_(g, c, ...) ((void)0) +#define uc_assertG(c, ...) ((void)g) +#define uc_assertX(c, ...) ((void)0) +#define check_exp(c, e) (e) +#endif + +#endif + +#endif diff --git a/tests/custom/17_lib_ffi/01_cdef b/tests/custom/17_lib_ffi/01_cdef new file mode 100644 index 00000000..facdfa0f --- /dev/null +++ b/tests/custom/17_lib_ffi/01_cdef @@ -0,0 +1,20 @@ +The `ffi.cdef()` function parses C declaration strings and registers types +and function prototypes with the FFI subsystem. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef(` + typedef int myint; + typedef struct { int x; } mystruct; + int global_var; + int func(int a, const char *b); +`); +print("cdef OK\n"); +-- End -- + +-- Expect stdout -- +cdef OK +-- End -- + diff --git a/tests/custom/17_lib_ffi/02_ctype b/tests/custom/17_lib_ffi/02_ctype new file mode 100644 index 00000000..80c91256 --- /dev/null +++ b/tests/custom/17_lib_ffi/02_ctype @@ -0,0 +1,25 @@ +The `ffi.ctype()` function creates typed C data instances. When initializing +with a numeric value, implicit type conversion occurs (e.g., float to int). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int myint;'); + +let a = ffi.ctype('myint', 42); +print(a.get(), "\n"); + +let b = ffi.ctype('myint', 20.5); +print(b.get(), "\n"); + +let c = ffi.ctype('myint', 0); +print(c.get(), "\n"); +-- End -- + +-- Expect stdout -- +42 +20 +0 +-- End -- + diff --git a/tests/custom/17_lib_ffi/03_cast b/tests/custom/17_lib_ffi/03_cast new file mode 100644 index 00000000..66667f33 --- /dev/null +++ b/tests/custom/17_lib_ffi/03_cast @@ -0,0 +1,26 @@ +The `ffi.cast()` function converts values between types. Numeric casts +truncate floating point values to integers. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int myint; typedef void *ptr;'); + +let n = ffi.cast('myint', 3.14); +print("cast int: ", n.get(), "\n"); + +let p = ffi.cast('ptr', ffi.ctype('int', 42)); +print("cast pointer: OK\n"); + +let i = ffi.ctype('myint', 100); +let c = ffi.cast('myint', i); +print("cast same: ", c.get(), "\n"); +-- End -- + +-- Expect stdout -- +cast int: 3 +cast pointer: OK +cast same: 100 +-- End -- + diff --git a/tests/custom/17_lib_ffi/04_sizeof b/tests/custom/17_lib_ffi/04_sizeof new file mode 100644 index 00000000..5578542c --- /dev/null +++ b/tests/custom/17_lib_ffi/04_sizeof @@ -0,0 +1,24 @@ +The `ffi.sizeof()` function queries the size of types or cdata instances +in bytes. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; };'); + +print("int: ", ffi.sizeof('int'), "\n"); +print("double: ", ffi.sizeof('double'), "\n"); +print("point: ", ffi.sizeof('struct point'), "\n"); + +let arr = ffi.ctype('int[10]'); +print("int[10]: ", ffi.sizeof(arr), "\n"); +-- End -- + +-- Expect stdout -- +int: 4 +double: 8 +point: 8 +int[10]: 40 +-- End -- + diff --git a/tests/custom/17_lib_ffi/05_alignof b/tests/custom/17_lib_ffi/05_alignof new file mode 100644 index 00000000..e1301b78 --- /dev/null +++ b/tests/custom/17_lib_ffi/05_alignof @@ -0,0 +1,22 @@ +The `ffi.alignof()` function queries the alignment requirement of types +in bytes. Struct alignment is determined by its most strictly aligned member. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct aligned { char c; int i; double d; };'); + +print("char: ", ffi.alignof('char'), "\n"); +print("int: ", ffi.alignof('int'), "\n"); +print("double: ", ffi.alignof('double'), "\n"); +print("struct aligned: ", ffi.alignof('struct aligned'), "\n"); +-- End -- + +-- Expect stdout -- +char: 1 +int: 4 +double: 8 +struct aligned: 4 +-- End -- + diff --git a/tests/custom/17_lib_ffi/06_callbacks b/tests/custom/17_lib_ffi/06_callbacks new file mode 100644 index 00000000..75161b6a --- /dev/null +++ b/tests/custom/17_lib_ffi/06_callbacks @@ -0,0 +1,57 @@ +Callbacks allow C function pointers to invoke ucode closures. The `qsort` function +demonstrates callback usage with integer and string array sorting. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Method 1: Predeclare with cdef(), then wrap bare name +ffi.cdef(` + void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); + int strcmp(const char *, const char *); +`); + +let narr = ffi.ctype('int[5]', [56, 4, 12, 1, 5]); +let qsort_fn = ffi.C.wrap('qsort'); + +qsort_fn(narr.ptr(), narr.length(), narr.itemsize(), + (a, b) => a.deref('int') - b.deref('int')); + +print("sorted: "); +for (let i = 0; i < 5; i++) { + print(narr.get(i), " "); +} +print("\n"); +-- End -- + +-- Expect stdout -- +sorted: 1 4 5 12 56 +-- End -- + + +String array sorting using strcmp callback. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Method 2: No cdef(), use full declaration in wrap() +let sarr = ffi.ctype('const char *[3]', ['foo', 'bar', 'qrx']); +let qsort_fn = ffi.C.wrap('void qsort(void *, size_t, size_t, int (*)(const void *, const void *))'); +let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); + +qsort_fn(sarr.ptr(), sarr.length(), sarr.itemsize(), + (a, b) => strcmp(a.deref('const char *'), b.deref('const char *'))); + +print("sorted: "); +for (let i = 0; i < 3; i++) { + print(sarr.get(i), " "); +} +print("\n"); +-- End -- + +-- Expect stdout -- +sorted: bar foo qrx +-- End -- + diff --git a/tests/custom/17_lib_ffi/07_ctype b/tests/custom/17_lib_ffi/07_ctype new file mode 100644 index 00000000..e5fda2b1 --- /dev/null +++ b/tests/custom/17_lib_ffi/07_ctype @@ -0,0 +1,35 @@ +The `ffi.ctype()` function creates C data instances with various initializers. + +Struct initialization with named fields. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef struct { int x; float y; } point;'); + +let p = ffi.ctype('point', {x: 10, y: 3.14}); +print("x: ", p.get('x'), " y: ", p.get('y'), "\n"); +-- End -- + +-- Expect stdout -- +x: 10 y: 3.1400001049042 +-- End -- + + +Array initialization with values. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[5];'); + +let a = ffi.ctype('intarr', 1, 2, 3, 4, 5); +print("len: ", a.length(), "\n"); +-- End -- + +-- Expect stdout -- +len: 5 +-- End -- + diff --git a/tests/custom/17_lib_ffi/08_ptr_deref b/tests/custom/17_lib_ffi/08_ptr_deref new file mode 100644 index 00000000..ae3ffadd --- /dev/null +++ b/tests/custom/17_lib_ffi/08_ptr_deref @@ -0,0 +1,42 @@ +The `cdata.ptr()` method returns a pointer to the cdata. The `cdata.deref()` method +dereferences a pointer to read the value. + +Pointer and dereference basic usage. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let i = ffi.ctype('int', 42); +let p = i.ptr(); + +let addr = p.get(); +print("address is valid: ", addr != null && addr > 0, "\n"); +print("value: ", p.deref('int'), "\n"); +-- End -- + +-- Expect stdout -- +address is valid: true +value: 42 +-- End -- + + +Dereference returns primitive value directly. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let i = ffi.ctype('int', 123); +let p = i.ptr(); +let v = p.deref('int'); + +print("type of deref: ", type(v), "\n"); +print("value: ", v, "\n"); +-- End -- + +-- Expect stdout -- +type of deref: int +value: 123 +-- End -- + diff --git a/tests/custom/17_lib_ffi/09_get_set b/tests/custom/17_lib_ffi/09_get_set new file mode 100644 index 00000000..a37403c8 --- /dev/null +++ b/tests/custom/17_lib_ffi/09_get_set @@ -0,0 +1,67 @@ +The `cdata.get()` and `cdata.set()` methods read and write struct fields and array elements. + +Struct field access with get/set. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef struct { int x; float y; } point;'); +let p = ffi.ctype('point', 0, 0.0); + +p.set('x', 100); +p.set('y', 2.71); +print("x: ", p.get('x'), " y: ", p.get('y'), "\n"); +-- End -- + +-- Expect stdout -- +x: 100 y: 2.710000038147 +-- End -- + + +Array element access. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 0, 0, 0); + +a.set(0, 10); +a.set(1, 20); +a.set(2, 30); +print("array: ", a.get(0), " ", a.get(1), " ", a.get(2), "\n"); +-- End -- + +-- Expect stdout -- +array: 10 20 30 +-- End -- + + +index() returns cdata reference for further manipulation. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 10, 20, 30); + +// index() returns cdata, get() converts to value +let ref = a.index(0); +print("type: ", type(ref), "\n"); +print("value: ", ref.get(), "\n"); + +// Chain index().set() for modification +a.index(1).set(99); +print("after set: ", a.get(1), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +value: 10 +after set: 99 +-- End -- + + diff --git a/tests/custom/17_lib_ffi/10_sizeof_alignof b/tests/custom/17_lib_ffi/10_sizeof_alignof new file mode 100644 index 00000000..09465fb4 --- /dev/null +++ b/tests/custom/17_lib_ffi/10_sizeof_alignof @@ -0,0 +1,47 @@ +The `ffi.sizeof()` and `ffi.alignof()` functions query type sizes and alignments. + +Size of basic types and structs. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef(` + typedef char c; + typedef short s; + typedef int i; + typedef struct { int x; } st; +`); + +print("sizeof(char): ", ffi.sizeof('c'), "\n"); +print("sizeof(int): ", ffi.sizeof('i'), "\n"); +print("sizeof(struct): ", ffi.sizeof('st'), "\n"); +-- End -- + +-- Expect stdout -- +sizeof(char): 1 +sizeof(int): 4 +sizeof(struct): 4 +-- End -- + + +Size from expression and alignment query. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef(`typedef char c; typedef int i;`); + +let arr = ffi.ctype('int[10]'); +print("sizeof(array): ", ffi.sizeof(arr), "\n"); +print("alignof(int): ", ffi.alignof('i'), "\n"); +print("alignof(struct with char+int): ", ffi.alignof('struct { char c; int i; }'), "\n"); +-- End -- + +-- Expect stdout -- +sizeof(array): 40 +alignof(int): 4 +alignof(struct with char+int): 4 +-- End -- + diff --git a/tests/custom/17_lib_ffi/11_offsetof b/tests/custom/17_lib_ffi/11_offsetof new file mode 100644 index 00000000..e8a73df8 --- /dev/null +++ b/tests/custom/17_lib_ffi/11_offsetof @@ -0,0 +1,21 @@ +The `ffi.offsetof()` function returns the byte offset of a struct field. + +Field offsets in a struct with padding. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef struct { char a; int b; short c; } st;'); + +print("offsetof(a): ", ffi.offsetof('st', 'a'), "\n"); +print("offsetof(b): ", ffi.offsetof('st', 'b'), "\n"); +print("offsetof(c): ", ffi.offsetof('st', 'c'), "\n"); +-- End -- + +-- Expect stdout -- +offsetof(a): 0 +offsetof(b): 4 +offsetof(c): 8 +-- End -- + diff --git a/tests/custom/17_lib_ffi/12_length_itemsize b/tests/custom/17_lib_ffi/12_length_itemsize new file mode 100644 index 00000000..79ac6ba1 --- /dev/null +++ b/tests/custom/17_lib_ffi/12_length_itemsize @@ -0,0 +1,38 @@ +The `cdata.length()` and `cdata.itemsize()` methods query array properties. + +Array length query. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[5];'); +let a = ffi.ctype('intarr', 1, 2, 3, 4, 5); + +print("length: ", a.length(), "\n"); +-- End -- + +-- Expect stdout -- +length: 5 +-- End -- + + +Item size for different array types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[5]; typedef char chararr[10];'); +let a = ffi.ctype('intarr', 1, 2, 3, 4, 5); +let c = ffi.ctype('chararr', [1, 2, 3, 4, 5]); + +print("int itemsize: ", a.itemsize(), "\n"); +print("char itemsize: ", c.itemsize(), "\n"); +-- End -- + +-- Expect stdout -- +int itemsize: 4 +char itemsize: 1 +-- End -- + diff --git a/tests/custom/17_lib_ffi/13_string b/tests/custom/17_lib_ffi/13_string new file mode 100644 index 00000000..9896909d --- /dev/null +++ b/tests/custom/17_lib_ffi/13_string @@ -0,0 +1,37 @@ +The `ffi.string()` function converts between C strings and ucode strings. It's bidirectional: +- `ffi.string(char_ptr)` - converts C char* to ucode string +- `ffi.string(ucode_string)` - creates char[] buffer from ucode string + +Char* to ucode string conversion. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let ptr = ffi.ctype('const char *', "hello"); +let s = ffi.string(ptr); + +print("type: ", type(s), " value: ", s, "\n"); +-- End -- + +-- Expect stdout -- +type: string value: hello +-- End -- + + +Creating char[] buffer from ucode string. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let buf = ffi.string("world"); +print("type: ", type(buf), "\n"); +print("deref: ", buf.deref('char'), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +deref: 119 +-- End -- + diff --git a/tests/custom/17_lib_ffi/14_wrap b/tests/custom/17_lib_ffi/14_wrap new file mode 100644 index 00000000..3dfb48fc --- /dev/null +++ b/tests/custom/17_lib_ffi/14_wrap @@ -0,0 +1,35 @@ +The `ffi.C.wrap()` function creates callable wrappers around C function pointers. + +Wrap a C function and call it. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let strcmp = ffi.C.wrap('int strcmp(const char *, const char *)'); +let result = strcmp("hello", "hello"); + +print("strcmp result: ", result, "\n"); +-- End -- + +-- Expect stdout -- +strcmp result: 0 +-- End -- + + +Wrap getpid function. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let getpid = ffi.C.wrap('int getpid(void)'); +let pid = getpid(); + +print("pid type: ", type(pid), "\n"); +-- End -- + +-- Expect stdout -- +pid type: int +-- End -- + diff --git a/tests/custom/17_lib_ffi/15_dlopen_dlsym b/tests/custom/17_lib_ffi/15_dlopen_dlsym new file mode 100644 index 00000000..3a6cfaf4 --- /dev/null +++ b/tests/custom/17_lib_ffi/15_dlopen_dlsym @@ -0,0 +1,52 @@ +The `ffi.dlopen()` and `ffi.C.dlsym()` functions load dynamic libraries and resolve symbols. + +Load a dynamic library (libz). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +try { + let libz = ffi.dlopen('z'); + print("dlopen result: ", libz ? "OK" : "NULL", "\n"); +} catch (e) { + print("dlopen error: ", e, "\n"); +} +-- End -- + +-- Expect stdout -- +dlopen result: OK +-- End -- + + +Resolve symbol from global scope. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let strlen_sym = ffi.C.dlsym('strlen'); +print("dlsym result: ", strlen_sym ? "found" : "NULL", "\n"); +-- End -- + +-- Expect stdout -- +dlsym result: found +-- End -- + + +Wrap and call dlsym'd function. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let strlen_sym = ffi.C.dlsym('strlen'); +let strlen_fn = ffi.C.wrap('size_t strlen(const char *)'); +let len = strlen_fn("hello"); + +print("strlen: ", len, "\n"); +-- End -- + +-- Expect stdout -- +strlen: 5 +-- End -- diff --git a/tests/custom/17_lib_ffi/16_errno b/tests/custom/17_lib_ffi/16_errno new file mode 100644 index 00000000..5c0207e6 --- /dev/null +++ b/tests/custom/17_lib_ffi/16_errno @@ -0,0 +1,27 @@ +The `ffi.errno()` function gets and sets the C errno value. + +Get and set errno. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let err1 = ffi.errno(); +ffi.errno(22); +let err2 = ffi.errno(); +ffi.errno(0); + +print("errno before: ", err1, " after: ", err2, "\n"); + +// Access errno via dlsym without prior cdef declaration +ffi.errno(42); +let errno_sym = ffi.C.dlsym("errno"); +print("errno via dlsym: ", errno_sym?.deref("int"), "\n"); +ffi.errno(0); +-- End -- + +-- Expect stdout -- +errno before: 0 after: 22 +errno via dlsym: 42 +-- End -- + diff --git a/tests/custom/17_lib_ffi/17_copy b/tests/custom/17_lib_ffi/17_copy new file mode 100644 index 00000000..736cfc58 --- /dev/null +++ b/tests/custom/17_lib_ffi/17_copy @@ -0,0 +1,20 @@ +The `ffi.copy()` function copies memory between C data objects. + +Copy memory between buffers. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef char chararr[5];'); +let src = ffi.ctype('chararr', [104, 101, 108, 108, 111]); // "hello" +let dst = ffi.ctype('chararr'); + +ffi.copy(dst.ptr(), src.ptr(), 5); +print("copied: ", ffi.string(dst.ptr(), 5), "\n"); +-- End -- + +-- Expect stdout -- +copied: hello +-- End -- + diff --git a/tests/custom/17_lib_ffi/18_fill b/tests/custom/17_lib_ffi/18_fill new file mode 100644 index 00000000..e764d7da --- /dev/null +++ b/tests/custom/17_lib_ffi/18_fill @@ -0,0 +1,18 @@ +The `ffi.fill()` function fills memory with a byte value. + +Fill buffer with a byte value. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let buf = ffi.ctype('char[10]'); +ffi.fill(buf.ptr(), 10, 0x41); // Fill with 'A' + +print("filled: ", ffi.string(buf.ptr(), 10), "\n"); +-- End -- + +-- Expect stdout -- +filled: AAAAAAAAAA +-- End -- + diff --git a/tests/custom/17_lib_ffi/19_typeof b/tests/custom/17_lib_ffi/19_typeof new file mode 100644 index 00000000..921f9f43 --- /dev/null +++ b/tests/custom/17_lib_ffi/19_typeof @@ -0,0 +1,18 @@ +The `ffi.typeof()` function returns the CTypeID of a cdata value. + +Get type ID from cdata. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let i = ffi.ctype('int', 42); +let tid = ffi.typeof(i); + +print("type of int cdata: ", tid.get(), "\n"); +-- End -- + +-- Expect stdout -- +type of int cdata: 9 +-- End -- + diff --git a/tests/custom/17_lib_ffi/20_complex_structs b/tests/custom/17_lib_ffi/20_complex_structs new file mode 100644 index 00000000..4deeeb52 --- /dev/null +++ b/tests/custom/17_lib_ffi/20_complex_structs @@ -0,0 +1,46 @@ +Complex nested structs with path-based access. + +Nested struct initialization. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; }; struct rect { struct point min; struct point max; };'); +let r = ffi.ctype('struct rect', { + min: {x: 1, y: 2}, + max: {x: 3, y: 4} +}); + +print("min.x: ", r.get('min.x'), " max.y: ", r.get('max.y'), "\n"); +-- End -- + +-- Expect stdout -- +min.x: 1 max.y: 4 +-- End -- + + +Struct with array of structs. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; }; struct polyline { struct point pts[3]; int count; };'); +let poly = ffi.ctype('struct polyline', { + pts: [ + {x: 1, y: 2}, + {x: 3, y: 4}, + {x: 5, y: 6} + ], + count: 3 +}); + +let pts = poly.get('pts'); +print("pts[0]: ", pts[0].x, " ", pts[0].y, " count: ", poly.get('count'), "\n"); +-- End -- + +-- Expect stdout -- +pts[0]: 1 2 count: 3 +-- End -- + diff --git a/tests/custom/17_lib_ffi/21_path_access b/tests/custom/17_lib_ffi/21_path_access new file mode 100644 index 00000000..300eadcf --- /dev/null +++ b/tests/custom/17_lib_ffi/21_path_access @@ -0,0 +1,90 @@ +Path-based access allows dot notation and array indexing in get/set calls. + +Nested struct field access with paths. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; }; struct rect { struct point min; struct point max; };'); +let r = ffi.ctype('struct rect', { + min: {x: 1, y: 2}, + max: {x: 3, y: 4} +}); + +print("min.x: ", r.get('min.x'), "\n"); +print("max.y: ", r.get('max.y'), "\n"); +-- End -- + +-- Expect stdout -- +min.x: 1 +max.y: 4 +-- End -- + + +Array element access with paths. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct arr { int data[5]; };'); +let a = ffi.ctype('struct arr', {data: [1, 2, 3, 4, 5]}); + +print("data[0]: ", a.get('data[0]'), "\n"); +print("data[2]: ", a.get('data[2]'), "\n"); +-- End -- + +-- Expect stdout -- +data[0]: 1 +data[2]: 3 +-- End -- + + +Set with path syntax. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct arr { int data[5]; };'); +let a = ffi.ctype('struct arr', {data: [1, 2, 3, 4, 5]}); + +a.set('data[1]', 99); +print("data[1] after set: ", a.get('data[1]'), "\n"); +-- End -- + +-- Expect stdout -- +data[1] after set: 99 +-- End -- + + +index() with path notation returns cdata reference. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; }; struct rect { struct point min; struct point max; };'); +let r = ffi.ctype('struct rect', { + min: {x: 1, y: 2}, + max: {x: 3, y: 4} +}); + +// index() with path returns cdata reference +let ref = r.index('min.x'); +print("type: ", type(ref), "\n"); +print("value: ", ref.get(), "\n"); + +// Modify through path reference +r.index('max.y').set(999); +print("max.y after set: ", r.get('max.y'), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +value: 1 +max.y after set: 999 +-- End -- + + diff --git a/tests/custom/17_lib_ffi/22_index_ref b/tests/custom/17_lib_ffi/22_index_ref new file mode 100644 index 00000000..6918793a --- /dev/null +++ b/tests/custom/17_lib_ffi/22_index_ref @@ -0,0 +1,242 @@ +The `cdata.index()` method returns raw cdata references for further manipulation, +unlike `get()` which returns converted ucode values. + +Basic index() returns cdata reference. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 10, 20, 30); + +// index() returns cdata reference +let ref = a.index(0); +print("type of index: ", type(ref), "\n"); + +// Can call get() on the reference to convert +print("value: ", ref.get(), "\n"); +-- End -- + +-- Expect stdout -- +type of index: resource +value: 10 +-- End -- + + +index() supports chaining with set(). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 10, 20, 30); + +// Chain index().set() to modify through reference +a.index(1).set(99); +print("array: ", a.get(0), " ", a.get(1), " ", a.get(2), "\n"); +-- End -- + +-- Expect stdout -- +array: 10 99 30 +-- End -- + + +index() works with struct fields. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef struct { int x; int y; } point;'); +let p = ffi.ctype('point', 100, 200); + +// index() returns cdata reference to field +let xref = p.index('x'); +print("type: ", type(xref), "\n"); +print("value: ", xref.get(), "\n"); + +// Modify through reference +p.index('y').set(999); +print("y after set: ", p.get('y'), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +value: 100 +y after set: 999 +-- End -- + + +index() supports path notation. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('struct point { int x; int y; }; struct rect { struct point min; struct point max; };'); +let r = ffi.ctype('struct rect', { + min: {x: 1, y: 2}, + max: {x: 3, y: 4} +}); + +// index() with path returns cdata reference +let ref = r.index('min.x'); +print("type: ", type(ref), "\n"); +print("value: ", ref.get(), "\n"); + +// Modify through path reference +r.index('max.y').set(999); +print("max.y after set: ", r.get('max.y'), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +value: 1 +max.y after set: 999 +-- End -- + + +index() works with pointer arithmetic. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[5];'); +let a = ffi.ctype('intarr', 10, 20, 30, 40, 50); +let ptr = ffi.ctype('int *', a.ptr()); + +// index() on pointer for arithmetic +print("ptr[0]: ", ptr.index(0).get(), "\n"); +print("ptr[2]: ", ptr.index(2).get(), "\n"); +print("ptr[4]: ", ptr.index(4).get(), "\n"); + +// Modify through pointer index +ptr.index(1).set(99); +print("after set: ", a.get(1), "\n"); +-- End -- + +-- Expect stdout -- +ptr[0]: 10 +ptr[2]: 30 +ptr[4]: 50 +after set: 99 +-- End -- + + +index() with char* for byte-level access. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('char *strdup(const char *); void free(void *);'); +let strdup = ffi.C.wrap('char *strdup(const char *)'); +let free = ffi.C.wrap('void free(void *)'); + +let ptr = strdup("hello"); + +// index() for byte access +print("ptr[0]: ", ptr.index(0).get(), "\n"); +print("ptr[1]: ", ptr.index(1).get(), "\n"); +print("ptr[4]: ", ptr.index(4).get(), "\n"); + +// Modify through index +ptr.index(0).set(65); // 'A' +print("after mod: ", ffi.string(ptr), "\n"); + +free(ptr); +-- End -- + +-- Expect stdout -- +ptr[0]: 104 +ptr[1]: 101 +ptr[4]: 111 +after mod: Aello +-- End -- + + +Compare get() vs index() return types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 10, 20, 30); + +// get() returns converted value +let v1 = a.get(0); +print("get() type: ", type(v1), " value: ", v1, "\n"); + +// index() returns cdata reference +let v2 = a.index(0); +print("index() type: ", type(v2), "\n"); + +// Must call get() on index() result +print("index().get() type: ", type(v2.get()), " value: ", v2.get(), "\n"); +-- End -- + +-- Expect stdout -- +get() type: int value: 10 +index() type: resource +index().get() type: int value: 10 +-- End -- + + +index() on nested arrays. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr2d[2][3];'); +let m = ffi.ctype('intarr2d', [1, 2, 3], [4, 5, 6]); + +// First index returns row cdata +let row0 = m.index(0); +print("row0 type: ", type(row0), "\n"); + +// Second index on row returns element cdata +let elem = row0.index(1); +print("elem type: ", type(elem), "\n"); +print("value: ", elem.get(), "\n"); + +// Chain for modification +m.index(1).index(2).set(999); +print("m[1][2]: ", m.get('[1][2]'), "\n"); +-- End -- + +-- Expect stdout -- +row0 type: resource +elem type: resource +value: 2 +m[1][2]: 999 +-- End -- + + +index() with const types still returns reference. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef int intarr[3];'); +let a = ffi.ctype('intarr', 10, 20, 30); + +// Get const pointer +let const_ptr = ffi.ctype('const int *', a.ptr()); + +// index() on const pointer +let ref = const_ptr.index(0); +print("type: ", type(ref), "\n"); +print("value: ", ref.get(), "\n"); +-- End -- + +-- Expect stdout -- +type: resource +value: 10 +-- End -- + diff --git a/tests/custom/17_lib_ffi/23_builtin_types b/tests/custom/17_lib_ffi/23_builtin_types new file mode 100644 index 00000000..71c0e23c --- /dev/null +++ b/tests/custom/17_lib_ffi/23_builtin_types @@ -0,0 +1,268 @@ +Preloaded types and variables are available without explicit cdef() declarations. + +Predeclared errno variable. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + + // Resolve cached errno + let e = ffi.C.dlsym('errno'); + // e is accessible (value may vary) + print("errno type: ", replace(e?.tostring(), / @ .+/, ''), "\n"); +-- End -- + +-- Expect stdout -- +errno type: int ** +-- End -- + + +Predeclared environ variable. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Resolve cached environ +let env = ffi.C.dlsym('environ'); +print("environ type: ", replace(env?.tostring(), / @ .+/, ''), "\n"); + +// Access first few environment variables +let count = 0; +for (let i = 0; i < 5; i++) { + let var = env.deref("char **").get(i); + if (!var) break; + count++; +} +print("found ", count ? "some" : "no", " vars\n"); +-- End -- + +-- Expect stdout -- +environ type: char *** +found some vars +-- End -- + + +Builtin size_t type. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// size_t is predeclared - no cdef needed +let sz = ffi.sizeof('size_t'); +print("size_t size: ", sz, "\n"); + +let st = ffi.ctype('size_t', 12345); +print("size_t value: ", st.get(), "\n"); +-- End -- + +-- Expect stdout -- +size_t size: 8 +size_t value: 12345 +-- End -- + + +Builtin ssize_t type. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// ssize_t is predeclared - no cdef needed +let sz = ffi.sizeof('ssize_t'); +print("ssize_t size: ", sz, "\n"); + +let sst = ffi.ctype('ssize_t', -42); +print("ssize_t value: ", sst.get(), "\n"); +-- End -- + +-- Expect stdout -- +ssize_t size: 8 +ssize_t value: -42 +-- End -- + + +Builtin intptr_t and uintptr_t types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// intptr_t/uintptr_t are predeclared +let ip = ffi.sizeof('intptr_t'); +let up = ffi.sizeof('uintptr_t'); +print("intptr_t size: ", ip, " uintptr_t size: ", up, "\n"); + +let ptr = ffi.ctype('uintptr_t', 0xDEADBEEF); +print("uintptr_t value: ", ptr.get(), "\n"); +-- End -- + +-- Expect stdout -- +intptr_t size: 8 uintptr_t size: 8 +uintptr_t value: 3735928559 +-- End -- + + +Builtin ptrdiff_t type. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// ptrdiff_t is predeclared +let pd = ffi.sizeof('ptrdiff_t'); +print("ptrdiff_t size: ", pd, "\n"); + +let ptd = ffi.ctype('ptrdiff_t', -100); +print("ptrdiff_t value: ", ptd.get(), "\n"); +-- End -- + +-- Expect stdout -- +ptrdiff_t size: 8 +ptrdiff_t value: -100 +-- End -- + + +Builtin wchar_t type. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// wchar_t is predeclared +let wc = ffi.sizeof('wchar_t'); +print("wchar_t size: ", wc, "\n"); +-- End -- + +-- Expect stdout -- +wchar_t size: 4 +-- End -- + + +Fixed-width int8_t and uint8_t types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// int8_t/uint8_t are predeclared +let i8 = ffi.sizeof('int8_t'); +let u8 = ffi.sizeof('uint8_t'); +print("int8_t size: ", i8, " uint8_t size: ", u8, "\n"); + +let si8 = ffi.ctype('int8_t', -128); +let ui8 = ffi.ctype('uint8_t', 255); +print("int8_t: ", si8.get(), " uint8_t: ", ui8.get(), "\n"); +-- End -- + +-- Expect stdout -- +int8_t size: 1 uint8_t size: 1 +int8_t: -128 uint8_t: 255 +-- End -- + + +Fixed-width int16_t and uint16_t types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// int16_t/uint16_t are predeclared +let i16 = ffi.sizeof('int16_t'); +let u16 = ffi.sizeof('uint16_t'); +print("int16_t size: ", i16, " uint16_t size: ", u16, "\n"); + +let si16 = ffi.ctype('int16_t', -32768); +let ui16 = ffi.ctype('uint16_t', 65535); +print("int16_t: ", si16.get(), " uint16_t: ", ui16.get(), "\n"); +-- End -- + +-- Expect stdout -- +int16_t size: 2 uint16_t size: 2 +int16_t: -32768 uint16_t: 65535 +-- End -- + + +Fixed-width int32_t and uint32_t types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// int32_t/uint32_t are predeclared +let i32 = ffi.sizeof('int32_t'); +let u32 = ffi.sizeof('uint32_t'); +print("int32_t size: ", i32, " uint32_t size: ", u32, "\n"); + +let si32 = ffi.ctype('int32_t', -2147483648); +let ui32 = ffi.ctype('uint32_t', 4294967295); +print("int32_t: ", si32.get(), " uint32_t: ", ui32.get(), "\n"); +-- End -- + +-- Expect stdout -- +int32_t size: 4 uint32_t size: 4 +int32_t: -2147483648 uint32_t: 4294967295 +-- End -- + + +Fixed-width int64_t and uint64_t types. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// int64_t/uint64_t are predeclared +let i64 = ffi.sizeof('int64_t'); +let u64 = ffi.sizeof('uint64_t'); +print("int64_t size: ", i64, " uint64_t size: ", u64, "\n"); + +let si64 = ffi.ctype('int64_t', -9223372036854775808); +let ui64 = ffi.ctype('uint64_t', 18446744073709551615); +print("int64_t: ", si64.get(), " uint64_t: ", ui64.get(), "\n"); +-- End -- + +-- Expect stdout -- +int64_t size: 8 uint64_t size: 8 +int64_t: -9223372036854775808 uint64_t: 18446744073709551615 +-- End -- + + +Using builtin types in function declarations. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Use builtin types in cdef +ffi.cdef('size_t strlen(const char *);'); +let strlen = ffi.C.wrap('strlen'); + +let len = strlen("hello"); +print("strlen result: ", len, "\n"); +-- End -- + +-- Expect stdout -- +strlen result: 5 +-- End -- + + +Using builtin types in arrays. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Create arrays of builtin types +let buf = ffi.ctype('uint8_t[4]', [1, 2, 3, 4]); +let idx = ffi.ctype('size_t[3]', [10, 20, 30]); + +print("buf[0]: ", buf.get(0), " buf[3]: ", buf.get(3), "\n"); +print("idx[1]: ", idx.get(1), "\n"); +-- End -- + +-- Expect stdout -- +buf[0]: 1 buf[3]: 4 +idx[1]: 20 +-- End -- + diff --git a/tests/custom/17_lib_ffi/24_ptr_array_args b/tests/custom/17_lib_ffi/24_ptr_array_args new file mode 100644 index 00000000..f59391a6 --- /dev/null +++ b/tests/custom/17_lib_ffi/24_ptr_array_args @@ -0,0 +1,121 @@ +The `ffi.string()` function correctly handles array cdata objects returned from C library functions like `strcpy()` and `memcpy()`. + +strcpy() with char[] buffer argument. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let strcpy = libc.wrap('char *strcpy(char *, const char *)'); + +let buf = ffi.ctype('char[256]'); +let result = strcpy(buf, "hello world"); + +print("result: ", ffi.string(result), "\n"); +-- End -- + +-- Expect stdout -- +result: hello world +-- End -- + + +memcpy() with char[] buffer arguments. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let memcpy = libc.wrap('void *memcpy(void *, const void *, size_t)'); + +let src = ffi.ctype('char[256]', "test data"); +let dst = ffi.ctype('char[256]'); + +memcpy(dst, src, 9); +print("copied: ", ffi.string(dst), "\n"); +-- End -- + +-- Expect stdout -- +copied: test data +-- End -- + + +strcmp() with multiple char[] buffers. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let strcmp = libc.wrap('int strcmp(const char *, const char *)'); + +let s1 = ffi.ctype('char[256]', "hello"); +let s2 = ffi.ctype('char[256]', "hello"); +let s3 = ffi.ctype('char[256]', "world"); + +let r1 = strcmp(s1, s2); +let r2 = strcmp(s1, s3); +print("strcmp(s1, s2): ", r1 == 0 ? "equal" : "not equal", "\n"); +print("strcmp(s1, s3): ", r2 < 0 ? "less" : "greater", "\n"); +-- End -- + +-- Expect stdout -- +strcmp(s1, s2): equal +strcmp(s1, s3): less +-- End -- + + +strncpy() with length parameter. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let strncpy = libc.wrap('char *strncpy(char *, const char *, size_t)'); + +let buf = ffi.ctype('char[256]'); +strncpy(buf, "short", 5); + +print("strncpy result: ", ffi.string(buf), "\n"); +-- End -- + +-- Expect stdout -- +strncpy result: short +-- End -- + + +strcpy() with empty string. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let strcpy = libc.wrap('char *strcpy(char *, const char *)'); + +let buf = ffi.ctype('char[256]'); +strcpy(buf, ""); + +print("empty string: ", ffi.string(buf), "\n"); +-- End -- + +-- Expect stdout -- +empty string: +-- End -- + + +sprintf() with array buffer. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let sprintf = libc.wrap('int sprintf(char *, const char *, ...)'); + +let buf = ffi.ctype('char[256]'); +sprintf(buf, "value=%d", 42); + +print("sprintf result: ", ffi.string(buf), "\n"); +-- End -- + +-- Expect stdout -- +sprintf result: value=42 +-- End -- + diff --git a/tests/custom/17_lib_ffi/25_calling_conventions b/tests/custom/17_lib_ffi/25_calling_conventions new file mode 100644 index 00000000..1eff337e --- /dev/null +++ b/tests/custom/17_lib_ffi/25_calling_conventions @@ -0,0 +1,23 @@ +Calling conventions specify how functions receive arguments and return values. +On x86 platforms, ucode supports `__attribute__((stdcall))`, `__attribute__((fastcall))`, +and `__attribute__((cdecl))` syntax. The keywords `__stdcall`, `__fastcall`, etc. also work. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Test all calling convention syntaxes in a single multi-line cdef +ffi.cdef(` +__attribute__((stdcall)) int atoi(const char *); +__attribute__((fastcall)) long strtol(const char *, char **, int); +__attribute__((cdecl)) size_t strlen(const char *); +__stdcall int abs(int); +`); + +// All declarations parsed successfully +print("Calling conventions: OK\n"); +-- End -- + +-- Expect stdout -- +Calling conventions: OK +-- End -- diff --git a/tests/custom/17_lib_ffi/26_variadic_args b/tests/custom/17_lib_ffi/26_variadic_args new file mode 100644 index 00000000..933af276 --- /dev/null +++ b/tests/custom/17_lib_ffi/26_variadic_args @@ -0,0 +1,51 @@ +Test calling C functions with variadic arguments (printf-style functions). + +printf() with integer argument. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let printf = libc.wrap('int printf(const char *, ...)'); + +printf("value=%d\n", 42); +-- End -- + +-- Expect stdout -- +value=42 +-- End -- + + +printf() with multiple variadic parameters. +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let printf = libc.wrap('int printf(const char *, ...)'); + +printf("%s: %d %f\n", "test", 10, 3.14); +-- End -- + +-- Expect stdout -- +test: 10 3.140000 +-- End -- + + +sprintf() with array buffer (requires char array to pointer conversion). +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null); +let sprintf = libc.wrap('int sprintf(char *, const char *, ...)'); + +let buf = ffi.ctype('char[256]'); +sprintf(buf, "value=%d", 42); + +print("sprintf result: ", ffi.string(buf), "\n"); +-- End -- + +-- Expect stdout -- +sprintf result: value=42 +-- End -- diff --git a/tests/custom/17_lib_ffi/27_tostring b/tests/custom/17_lib_ffi/27_tostring new file mode 100644 index 00000000..77d883e8 --- /dev/null +++ b/tests/custom/17_lib_ffi/27_tostring @@ -0,0 +1,59 @@ +The `ffi.ctype` cdata objects provide a `tostring()` method that serializes +the C type along with its value for debugging purposes. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Scalar types +let i = ffi.ctype('int', 42); +print(i.tostring(), "\n"); + +let f = ffi.ctype('float', 3.14); +print(f.tostring(), "\n"); + +let d = ffi.ctype('double', 2.71828); +print(d.tostring(), "\n"); + +// Character arrays with string content +let buf = ffi.ctype('char[6]', "hello"); +print(buf.tostring(), "\n"); + +// Pointer types +let pn = ffi.ctype('void *'); +print(pn.tostring(), "\n"); + +// Struct types +ffi.cdef('struct point { int x; int y; };'); +let p = ffi.ctype('struct point', 10, 20); +print(p.tostring(), "\n"); + +// Array types +let arr = ffi.ctype('int[5]', [1, 2, 3, 4, 5]); +print(arr.tostring(), "\n"); + +// Complex numbers +let c = ffi.ctype('float complex', 1.0, 2.0); +print(c.tostring(), "\n"); + +// Unsigned types +let u = ffi.ctype('uint32_t', 4294967295); +print(u.tostring(), "\n"); + +// Signed types +let s = ffi.ctype('int8_t', -128); +print(s.tostring(), "\n"); +-- End -- + +-- Expect stdout -- +int: 42 +float: 3.1400001049042 +double: 2.71828 +char [6]: "hello" +void *: NULL +struct point: { "x": 10, "y": 20 } +int [5] (len=5) +complex float: 1+2i +unsigned int: 4294967295 +char: -128 +-- End -- diff --git a/tests/custom/17_lib_ffi/28_dlopen_cdefs b/tests/custom/17_lib_ffi/28_dlopen_cdefs new file mode 100644 index 00000000..9311295a --- /dev/null +++ b/tests/custom/17_lib_ffi/28_dlopen_cdefs @@ -0,0 +1,232 @@ +The `ffi.dlopen()` function now accepts an optional third argument containing C definitions that are automatically parsed and wrapped. + +Basic function wrapping with zlib. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z', false, 'const char *zlibVersion(void);'); +print('zlibVersion = ', replace(ffi.string(libz.zlibVersion()), /^([0-9]+)\..+$/, '$1.*'), '\n'); +-- End -- + +-- Expect stdout -- +zlibVersion = 1.* +-- End -- + + +Global C library with single function. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null, false, 'int getpid(void);'); +let pid = libc.getpid(); +print('getpid works\n'); +-- End -- + +-- Expect stdout -- +getpid works +-- End -- + + +Multiple functions in single cdef. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null, false, 'int getpid(void);\nint getppid(void);'); +let pid = libc.getpid(); +let ppid = libc.getppid(); +print('getpid and getppid work\n'); +-- End -- + +-- Expect stdout -- +getpid and getppid work +-- End -- + + +Backward compatibility - dlopen without cdefs. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z'); +print('dlopen without cdefs works\n'); +-- End -- + +-- Expect stdout -- +dlopen without cdefs works +-- End -- + + +Error handling - invalid cdefs syntax. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +try { + ffi.dlopen(null, false, 'invalid syntax here'); + print('ERROR - should have thrown\n'); +} catch (e) { + print('Correctly caught error\n'); +} +-- End -- + +-- Expect stdout -- +Correctly caught error +-- End -- + + +Functions with integer parameters. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z', false, 'int compressBound(long srcLen);'); +let result = libz.compressBound(1024); +print('compressBound(1024) =', result, '\n'); +-- End -- + +-- Expect stdout -- +compressBound(1024) =1037 +-- End -- + + +Functions with pointer parameters (using ffi.string). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null, false, 'char *strerror(int errnum);'); +let msg = libc.strerror(0); +print('strerror(0) works\n'); +-- End -- + +-- Expect stdout -- +strerror(0) works +-- End -- + + +Empty cdefs string (should work, no functions added). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z', false, ''); +print('empty cdefs works\n'); +-- End -- + +-- Expect stdout -- +empty cdefs works +-- End -- + + +Null library name with cdefs (global library). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc = ffi.dlopen(null, false, 'int abs(int);'); +let result = libc.abs(-42); +print('abs(-42) =', result, '\n'); +-- End -- + +-- Expect stdout -- +abs(-42) =42 +-- End -- + + +Mix of type definitions and function wrapping. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +ffi.cdef('typedef long mylong;'); +let libz = ffi.dlopen('z', false, 'const char *zlibVersion(void);'); +let ver = ffi.string(libz.zlibVersion()); +print('zlibVersion with prior cdef = ', replace(ver, /^([0-9]+)\..+$/, '$1.*'), '\n'); +-- End -- + +-- Expect stdout -- +zlibVersion with prior cdef = 1.* +-- End -- + + +Cdefs with struct (types registered). + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +// Structs are registered as types +let libc = ffi.dlopen(null, false, 'typedef struct { int x; int y; } point_t;'); +let Point = ffi.ctype('point_t'); +let p = ffi.cast('point_t*', ffi.cast('void*', 0)); +print('struct type registered\n'); +-- End -- + +-- Expect stdout -- +struct type registered +-- End -- + + +Direct property access returns callable function. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z', false, 'const char *zlibVersion(void);'); +let fn = libz.zlibVersion; +let ver = ffi.string(fn()); +print('Direct property access returns callable function\n'); +-- End -- + +-- Expect stdout -- +Direct property access returns callable function +-- End -- + + +Repeated dlopen with cdefs adds to same global library. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libc1 = ffi.dlopen(null, false, 'int getpid(void);'); +let libc2 = ffi.dlopen(null, false, 'int getppid(void);'); +let pid = libc1.getpid(); +let ppid = libc2.getppid(); +print('global lib cdefs accumulate\n'); +-- End -- + +-- Expect stdout -- +global lib cdefs accumulate +-- End -- + + +Function with const char* return and void parameter. + +-- Testcase -- +{% +import * as ffi from 'ffi'; + +let libz = ffi.dlopen('z', false, 'const char *zlibVersion(void);'); +let ver_ptr = libz.zlibVersion(); +let ver_str = ffi.string(ver_ptr); +print('ver = ', replace(ver_str, /^([0-9]+)\..+$/, '$1.*'), '\n'); +-- End -- + +-- Expect stdout -- +ver = 1.* +-- End -- diff --git a/types.c b/types.c index 239ef5fe..8ab82b38 100644 --- a/types.c +++ b/types.c @@ -2538,14 +2538,44 @@ ucv_key_get(uc_vm_t *vm, uc_value_t *scope, uc_value_t *key) if (!found) { k = ucv_key_to_string(vm, key); - for (o = scope; o; o = ucv_prototype_get(o)) { - if (ucv_type(o) != UC_OBJECT) - continue; + /* Check resource type prototype first */ + if (ucv_type(scope) == UC_RESOURCE) { + uc_value_t *uv = scope; + + if (uv->ext_flag) { + uc_resource_ext_t *ext = (uc_resource_ext_t *)scope; + + /* Check instance-specific proto first (set via ucv_resource_new_prototyped) */ + if (ext->hasproto) { + uc_value_t *inst_proto = *(uc_value_t **)(ext + 1); + if (inst_proto) { + v = ucv_object_get(inst_proto, k ? k : ucv_string_get(key), &found); + } + } + + /* Then fall back to type prototype */ + if (!found && ext->type && ext->type->proto) { + v = ucv_object_get(ext->type->proto, k ? k : ucv_string_get(key), &found); + } + } else { + uc_resource_t *res = (uc_resource_t *)scope; + if (res->type && res->type->proto) { + v = ucv_object_get(res->type->proto, k ? k : ucv_string_get(key), &found); + } + } + } + + /* Then check object prototype chain */ + if (!found) { + for (o = scope; o; o = ucv_prototype_get(o)) { + if (ucv_type(o) != UC_OBJECT) + continue; - v = ucv_object_get(o, k ? k : ucv_string_get(key), &found); + v = ucv_object_get(o, k ? k : ucv_string_get(key), &found); - if (found) - break; + if (found) + break; + } } free(k);