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.function — native→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.

Activity

  1. added
    rfcProposal that needs design agreement before implementation
    and removed
    enhancementNew capability or improvement
    on Jul 26, 2026
  2. proggeramlug commented on Aug 21, 2026

    @proggeramlug
    ContributorAuthor

    The new full-source OpenCode target is tracked by #8506. This remains its P0 native boundary: complete callbacks/closure rooting, external memory views, and node:ffi compatibility so the unmodified OpenTUI/Yoga and FFF wrappers can compile and use their upstream dylibs.

  3. proggeramlug commented on Aug 24, 2026

    @proggeramlug
    ContributorAuthor

    Closed by #8704, landed on main via #8721 (squash 3f655301e).

    That PR was landed through a batch wrapper PR rather than merged directly, so GitHub's close keyword never fired at merge time — its mergedAt is null even though the work is on main. Verified before closing: crates/perry-runtime/src/bun_ffi/ (call, callback, dlopen, memory, read) is present on main, changelog.d/8704-complete-c-abi-ffi.md landed, and its 29 bun_ffi tests pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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