Skip to content

runtime: C-ABI FFI layer (bun:ffi shape) — dlopen, typed calls, pinned buffers, toArrayBuffer, JSCallback #6562

Description

@proggeramlug

The single biggest platform feature for compiling Bun-specific apps (driver: opencode, but this is what the Bun-native ecosystem standardizes on): a C-ABI FFI layer with the bun:ffi API shape (and optionally Node's experimental node:ffi shape — bun-ffi-structs already bridges both, so one implementation can serve either import).

Evidence from opencode

Everything native in the TUI product funnels through plain C-ABI dylibs — no N-API anywhere (verified: libopentui.dylib from @opentui/core-darwin-arm64@0.4.5 exports 1,775 C symbols, zero napi_* entries; the "node backend" is the same dylib driven via experimental node:ffi):

consumer lib FFI decl size notes
@opentui/core (the TUI renderer) libopentui.dylib per platform ~348 typed symbol decls renderer + buffers + embedded yoga (YG*) + embedded tree-sitter with bundled grammars (js/ts/markdown/zig)
bun-pty librust_pty.{dylib,so,dll} (bundled in tarball) 17 symbols pty spawn/read/write/resize/kill
@ff-labs/fff-bun @ff-labs/fff-bin-* platform pkgs dlopen in src/ffi.ts fuzzy finder; opencode's node condition stubs it gracefully — deferrable

Primitives required (from actual usage, not the full spec)

  • dlopen(path, symbolTable) with typed args/returns — FFIType.{void, ptr, pointer, cstring, bool, i8..i64, u8..u64, usize, f32, f64, function, callback} (observed set in @opentui/core dist).
  • ptr(arrayBufferOrView) — hand JS buffer memory to native. GC design constraint: perry's GC relocates objects (cf. the forwarding-stub/array-growth work, Default GC: array from a direct function call, stored in a variable, is lost when its backing store grows past 32MiB; object arrays get corrupted length #6228/fix(gc): minor sweep reclaimed array-growth forwarding stubs still referenced from old-gen parents #6471) — FFI-exposed buffers must be pinned or allocated in non-moving/off-heap space for their native lifetime.
  • toArrayBuffer(ptr, byteOffset, byteLength) — 28 uses in opentui — zero-copy JS views over native-owned memory; needs external backing-store support in perry's ArrayBuffer (no GC ownership, explicit lifetime).
  • CString (read NUL-terminated, encode).
  • JSCallback / FFIType.functionnative→JS callbacks (opentui registers them). Hardest piece: trampoline into perry's JS execution, closure rooting, and a decision on threading (reject cross-thread invocation initially unless opentui proves to need it — verify which callbacks it actually registers before building).
  • bun-ffi-structs is pure JS over these primitives — free once they exist.

For a native-code runtime, this is natural territory: direct dlopen + call-stub generation (libffi or generated thunks), no JIT trampolines needed.

Suggested bring-up

  1. dlopen + typed calls + ptr on pinned buffers + CString, proven against bun-pty's 17-symbol dylib (spawn a shell, echo round-trip) — small, observable, exercises strings/ints/pointers/read-loop.
  2. toArrayBuffer external backing stores.
  3. JSCallback trampolines.
  4. opentui's 348-symbol table + renderer bring-up (the opencode target tracker owns the app-level milestone).

Acceptance per stage; stage 1 acceptance: a perry-compiled script drives bun-pty end-to-end on macOS-arm64 + linux-x64.

Metadata

Metadata

Assignees

No one assigned

    Labels

    rfcProposal that needs design agreement before implementation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions