Fully implements the docs/driver/ffi-plan.md design. LPC can now load native shared libraries, call C functions whose signatures are described at runtime, manage native memory, pass in/out parameters, and expose LPC function pointers to C as callbacks. Package (src/packages/ffi, option PACKAGE_FFI ON, libffi via pkg-config): - ffi_load/unload/symbol; ffi_prepare/ffi_call (ffi_prep_cif + ffi_call); ffi_alloc/free/sizeof/peek/address; ffi_read/write; ffi_struct_layout; ffi_callback/ffi_callback_addr/ffi_callback_free (libffi closures that re-enter the VM via safe_call_function_pointer); ffi_error/ffi_status. - Buffers are the currency for all pointer/byte data; raw pointer VALUES (returned pointers, buffer/callback addresses) are ints. LPC strings are UTF-8-native and never implicitly marshalled -- a char* is a buffer the caller encoded (pinned by ffi_string.lpc). - Native allocations are LPC buffers (GC-tracked); handle tables freed at shutdown (ffi_cleanup) and marked for DEBUGMALLOC (mark_ffi). Security: master apply valid_ffi(op, arg, caller) gates every load/symbol/prepare/callback (VALID_FFI added to the applies table); a missing apply denies by default. Optional "ffi allowed libraries" config allow-list (rc.cc + runtime_config.h + regenerated config.md, new Security category). __PACKAGE_FFI__ predefine added. tools/ffi/generate.py: turns a C header into LPC bindings (buffer params for char*, optional --string-convenience UTF-8 overloads) plus a struct layout include; reports+skips unsupported forms; --emit-json contract. Dependency-free test.py. Tests: 20 testsuite/single/tests/efuns/ffi_*.lpc (every efun, the qsort callback round trip, the generated-bindings end-to-end path), guarded by __PACKAGE_FFI__ with a libc-reachability probe. The efuns are VM-stack- based, so the LPC testsuite is the surface -- libffi's call/closure paths run there under ASan/UBSan and the per-file check_memory leak gate. The clang RelWithDebInfo sanitizer caught an error()-unwind leak: both ffi_prepare and ffi_callback allocated before a code_to_type() that can error() -- now unique_ptr/custom-deleter owned (AGENTS.md section 4). Verified: testsuite x3 (ASan Debug) + ctest 297, RelWithDebInfo suite x3 + ctest 298, clang RelWithDebInfo sanitizer (leak-clean), tools/ffi test.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| generate.py | ||
| README.md | ||
| test.py | ||
| test_sample.h | ||
tools/ffi — LPC bindings generator for package_ffi
Generates ready-to-use LPC bindings for a native library from its C
header, targeting the FFI package (src/packages/ffi, efuns declared in
<ffi.h>). See the full design in
docs/driver/ffi-plan.md.
Usage
tools/ffi/generate.py <header.h> --lib <libpath> --out <basename> \
[--string-convenience] [--emit-json]
Emits next to <basename>:
| File | Contents |
|---|---|
<basename>.lpc |
One LPC wrapper per exported C function. Handles are prepared lazily and cached; the library is ffi_loaded on first use. |
<basename>_structs.h |
ffi_struct_layout field-type arrays + symbolic field-offset #defines for each struct in the header. |
Example:
tools/ffi/generate.py /usr/include/math.h --lib libm.so.6 --out mudlib/std/libm
The buffer / byte boundary
A C char* (or any data pointer) is emitted as an LPC buffer
parameter, never string — LPC strings are UTF-8-native and the byte
encoding must be explicit (string_encode/string_decode). With
--string-convenience the generator also emits a <name>_s(string ...)
overload that string_encodes to a NUL-terminated UTF-8 buffer for the
common case, clearly named so the encoding is never hidden by default.
Supported C subset
Scalar types (char…long long, the intN_t/uintN_t family,
size_t, float, double), any pointer (→ buffer), void returns,
and plain struct { ... } bodies of those field types. Anything else —
function-pointer parameters, unions, bitfields, varargs — is reported
to stderr and skipped, never mis-bound. --emit-json dumps the parsed
signature table (the shared contract for the bindings and tests).
Tests
python3 tools/ffi/test.py
Dependency-free; generates from test_sample.h and checks the emitted
LPC/struct output and the JSON contract. The end-to-end path (generated
bindings compiled and calling real native functions) is pinned by the
driver testsuite: testsuite/single/tests/efuns/ffi_generated.lpc.