fluffos/testsuite/include/ffi.h
Yucong Sun 9be761aa00 package_ffi: foreign function interface for LPC (libffi), with callbacks
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>
2026-07-09 20:48:48 -04:00

40 lines
1.2 KiB
C

/*
* ffi.h -- type codes for the FFI package (package_ffi).
*
* Copy this file to your mudlib include dir. The codes describe the C
* type of an argument or return value to ffi_prepare()/ffi_call() and
* the scalar type for ffi_read()/ffi_write(). See docs/driver/ffi-plan.md.
*
* The boundary is BYTES: LPC strings are UTF-8-native and are NEVER
* marshalled implicitly to C char*. All pointer/data crosses as a
* `buffer`; encode with string_encode()/decode with string_decode().
*/
#ifndef _FFI_H_
#define _FFI_H_
#define FFI_VOID 0
#define FFI_INT8 1
#define FFI_UINT8 2
#define FFI_INT16 3
#define FFI_UINT16 4
#define FFI_INT32 5
#define FFI_UINT32 6
#define FFI_INT64 7
#define FFI_UINT64 8
#define FFI_FLOAT 9 /* C float <-> LPC float */
#define FFI_DOUBLE 10 /* C double <-> LPC float */
/*
* A pointer. As an ARGUMENT: pass a buffer; the native pointer is the
* buffer's bytes (whatever encoding you put there). As a RETURN: an
* integer holding the raw address -- copy from it with ffi_peek().
*/
#define FFI_POINTER 11
/* Native C int/long widths (resolved to fixed sizes at build time). */
#define FFI_INT FFI_INT32
#define FFI_UINT FFI_UINT32
#define FFI_LONG FFI_INT64
#define FFI_ULONG FFI_UINT64
#endif /* _FFI_H_ */