ffi

runtime-builtins.d.ts

fino:ffi - Rust-backed native library binding.

This synthetic module exposes Fino's low-level C ABI surface. It is intended for built-in runtime modules and advanced applications that need to load system libraries, call native functions, pass raw pointers, describe by-value structs, or expose JavaScript callbacks to native code.

Prefer a higher-level fino:* module when one exists. FFI signatures are not checked against native headers, and mistakes in parameter types, return types, pointer lifetimes, struct layout, callback lifetimes, or ownership rules can corrupt memory or crash the process.

Native functions use the platform C calling convention. Buffers passed to native code must stay alive until native code has finished reading or writing them, and memory returned by native libraries must be released with the matching native API.

import { dlopen } from 'fino:ffi';

const libc = dlopen(null, {
  getpid: { parameters: [], result: 'i32' },
});

console.log(libc.symbols.getpid());

Types

type NativeTypeName = | 'void' | 'bool' | 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'usize' | 'isize' | 'usizeBig' | 'isizeBig' | 'f32' | 'f64' | 'pointer' | 'ignoredPointer' | 'buffer'

String descriptor for a scalar or pointer-shaped C ABI type.

Integer descriptors use the matching C width. usize and isize marshal results as JavaScript number; use usizeBig or isizeBig when the full pointer-sized range must round-trip as bigint.

pointer represents an opaque void* and crosses JS as NativePointer. buffer passes an ArrayBuffer or typed-array backing-store address as a void* parameter. ignoredPointer is for callbacks that receive a pointer parameter but intentionally do not materialize it in JavaScript.

type NativeTypeDescriptor = NativeTypeName | StructType

Native type descriptor accepted in parameter and result positions.

structType() values may be used by dlopen() for by-value struct parameters and return values. FfiCallback does not currently support struct parameters or struct returns.

type NativeSymbolMap = Record<string, NativeSymbolSpec>

Map of C symbol names to native call signatures.

type NativeBindings<TSymbols extends NativeSymbolMap> = { [K in keyof TSymbols]: (...args: any[]) => any; }

Callable JavaScript wrappers for symbols loaded from a dynamic library.

The runtime validates and marshals arguments according to the symbol's descriptor on each call. Synchronous symbols return their native result directly; symbols marked async: true return a Promise.

type NativeSymbolPointers<TSymbols extends NativeSymbolMap> = { [K in keyof TSymbols]: ArrayBuffer; }

Raw native code pointers for symbols loaded from a dynamic library.

These pointers are useful when a C API needs another function pointer, but they should not be called or dereferenced manually.

type NativePointer = ArrayBuffer | ArrayBufferView | null

A native pointer value: an 8-byte ArrayBuffer holding the address as a little-endian u64, or null for the C null pointer.

ArrayBufferView values may be passed where a pointer to the view's first byte is required. The view's byteOffset is included in the address.

type StructFieldTuple = readonly [name: string, type: NativeTypeDescriptor]

Tuple-form field descriptor accepted by structType().

type StructField = StructFieldTuple | StructFieldDescriptor

Field descriptor accepted by structType().

Interfaces

interface NativeSymbolSpec {

Native function signature metadata passed to dlopen().

A symbol descriptor maps the JavaScript call boundary to the native ABI. parameters defaults to an empty list only when an empty array is supplied; each entry must be a native type descriptor. result describes the return type and should be 'void' for functions that do not return a value.

Properties

parameters?: readonly NativeTypeDescriptor[]

Positional native parameter descriptors in C call order.

result?: NativeTypeDescriptor

Native return descriptor.

nonblocking?: boolean

Legacy compatibility flag. Use async for new bindings.

async?: boolean

Run the call on Fino's native blocking pool and return a Promise.

Use this for long-running calls or functions that may block on I/O. Do not pass pointers to short-lived stack-like JS buffers unless those buffers are retained until the promise settles.

fast?: boolean

Enable V8 Fast API dispatch when the signature supports it.

Defaults to true. Set this to false for native functions that may synchronously call back into JavaScript through an FfiCallback, because those calls need the normal V8 handle-scope path.

variadic?: number

Number of fixed parameters before variadic arguments.

Variadic C functions require a variadic libffi call interface on some platforms. For example, fcntl(fd, cmd, ...) has two fixed parameters.

interface DynamicLibrary<TSymbols extends NativeSymbolMap = NativeSymbolMap> {

Handle returned by dlopen().

The handle keeps the native library open for as long as any bound function may be called. symbols contains callable wrappers, while pointers contains raw pointer-sized buffers for the resolved symbol addresses.

Properties

symbols: NativeBindings<TSymbols>

Bound native symbols keyed by their C symbol name.

pointers: NativeSymbolPointers<TSymbols>

Raw symbol addresses keyed by their C symbol name.

interface StructFieldInfo {

Metadata for one field in a StructType.

Properties

name: string

Field name used by get(), set(), and offsetOf().

offset: number

Byte offset from the start of the struct.

size: number

Field width in bytes.

interface StructType {

By-value C struct descriptor returned by structType().

Struct values are represented as ArrayBuffers containing the native byte layout. The descriptor can allocate a correctly sized buffer, read and write fields, report offsets, and be used as a dlopen() parameter or result descriptor.

Readonly Properties

readonly size: number

Total struct size in bytes, including trailing padding.

readonly align: number

Struct alignment in bytes.

readonly fields: readonly StructFieldInfo[]

Field metadata in declaration order.

Methods

alloc(): ArrayBuffer

Allocate a zero-filled ArrayBuffer with this struct's size.

get(buffer: ArrayBuffer | ArrayBufferView, field: string): unknown

Read a field value from a struct buffer.

Nested struct fields are returned as copied ArrayBuffers. Pointer fields are returned as NativePointer values.

set(buffer: ArrayBuffer | ArrayBufferView, field: string, value: unknown): void

Write a field value into a struct buffer.

The buffer must be large enough to contain the target field at its native offset. Nested struct fields accept a buffer with the nested layout.

offsetOf(field: string): number

Return the byte offset of a named field.

interface StructFieldDescriptor {

Object-form field descriptor accepted by structType().

Properties

name: string

Field name.

type: NativeTypeDescriptor | 'bytes'

Field type, another StructType, or 'bytes' for explicit padding.

offset?: number

Explicit byte offset. When omitted, the field is naturally aligned after the previous field.

size?: number

Padding byte count when type is 'bytes'.

interface FfiCallbackHandle {

Callback object returned by new FfiCallback().

Readonly Properties

readonly pointer: ArrayBuffer

Native function pointer to pass to C APIs.

Methods

close(): void

Release the native callback trampoline.

The method is idempotent. Do not close the callback while native code may still call pointer.

interface PointerApi {

Pointer and raw-memory helper API exposed as Pointer.

Pointer helpers dereference raw addresses. They do not validate that the address is allocated, correctly aligned, or large enough for the requested access.

Methods

null(): null

The C null pointer.

addr(source: ArrayBuffer | ArrayBufferView): bigint

Backing-store address of a buffer as a bigint. For views, byteOffset is applied so the address points at element 0.

offset(ptr: NativePointer, bytes: number): NativePointer

Pointer arithmetic: a new pointer advanced by bytes.

of( source: ArrayBuffer | ArrayBufferView, arena?: ArrayBuffer | ArrayBufferView, byteOffset?: number, ): ArrayBuffer | undefined

Take the address of a buffer's backing store as a pointer value. With an arena, the address is written into arena at byteOffset with zero allocation and undefined is returned.

readU8(ptr: NativePointer, offset?: number): number

Read an unsigned 8-bit integer at ptr + offset.

readI8(ptr: NativePointer, offset?: number): number

Read a signed 8-bit integer at ptr + offset.

readU16(ptr: NativePointer, offset?: number): number

Read an unsigned 16-bit little-endian integer at ptr + offset.

readI16(ptr: NativePointer, offset?: number): number

Read a signed 16-bit little-endian integer at ptr + offset.

readU32(ptr: NativePointer, offset?: number): number

Read an unsigned 32-bit little-endian integer at ptr + offset.

readI32(ptr: NativePointer, offset?: number): number

Read a signed 32-bit little-endian integer at ptr + offset.

readU64(ptr: NativePointer, offset?: number): bigint

Read an unsigned 64-bit little-endian integer at ptr + offset.

readI64(ptr: NativePointer, offset?: number): bigint

Read a signed 64-bit little-endian integer at ptr + offset.

readF32(ptr: NativePointer, offset?: number): number

Read a 32-bit little-endian float at ptr + offset.

readF64(ptr: NativePointer, offset?: number): number

Read a 64-bit little-endian float at ptr + offset.

readPointer(ptr: NativePointer, offset?: number): NativePointer

Dereference a pointer-sized field at ptr + offset.

writeU8(ptr: NativePointer, offset: number, value: number | bigint): void

Write an unsigned 8-bit integer at ptr + offset.

writeI8(ptr: NativePointer, offset: number, value: number | bigint): void

Write a signed 8-bit integer at ptr + offset.

writeU16(ptr: NativePointer, offset: number, value: number | bigint): void

Write an unsigned 16-bit little-endian integer at ptr + offset.

writeI16(ptr: NativePointer, offset: number, value: number | bigint): void

Write a signed 16-bit little-endian integer at ptr + offset.

writeU32(ptr: NativePointer, offset: number, value: number | bigint): void

Write an unsigned 32-bit little-endian integer at ptr + offset.

writeI32(ptr: NativePointer, offset: number, value: number | bigint): void

Write a signed 32-bit little-endian integer at ptr + offset.

writeU64(ptr: NativePointer, offset: number, value: number | bigint): void

Write an unsigned 64-bit little-endian integer at ptr + offset.

writeI64(ptr: NativePointer, offset: number, value: number | bigint): void

Write a signed 64-bit little-endian integer at ptr + offset.

writeF32(ptr: NativePointer, offset: number, value: number): void

Write a 32-bit little-endian float at ptr + offset.

writeF64(ptr: NativePointer, offset: number, value: number): void

Write a 64-bit little-endian float at ptr + offset.

writePointer(ptr: NativePointer, offset: number, value: NativePointer): void

Write a pointer-sized address at ptr + offset.

copyFrom(ptr: NativePointer, len: number): Uint8Array

Copy len bytes from ptr into a new Uint8Array.

copyFromInto(dest: ArrayBuffer | ArrayBufferView, ptr: NativePointer, len?: number): void

Copy bytes from ptr into an existing buffer. len defaults to the destination's byte length.

copyTo(ptr: NativePointer, src: Uint8Array | ArrayBuffer): void

Copy the bytes of src to the native buffer at ptr.

view(ptr: NativePointer, len: number, opts?: { onRelease?: () => void }): ArrayBuffer

Create an ArrayBuffer that aliases the native memory at [ptr, ptr + len) without copying.

The native allocation must outlive the buffer unless onRelease owns freeing it: it fires exactly once, on the JS thread, after V8 frees the backing store (GC of the buffer, or transfer/detach). Structured clone copies the bytes; transferring detaches the buffer and triggers release.

Constants

const Pointer: PointerApi

Namespace of pointer and raw-memory helpers.

const FfiCallback: { new ( spec: Pick<NativeSymbolSpec, 'parameters' | 'result'>, callback: (...args: any[]) => unknown, ): FfiCallbackHandle; }

Native callback constructor.

Constructed callbacks expose a native function pointer and must be retained for as long as native code may call them. The callback may be closed explicitly or with a using declaration.

import { FfiCallback } from 'fino:ffi';

using cmp = new FfiCallback(
  { parameters: ['pointer', 'pointer'], result: 'i32' },
  (left, right) => 0,
);

nativeApi.symbols.registerComparator(cmp.pointer);

Functions

function structType( fields: readonly StructField[], options?: { size?: number; align?: number }, ): StructType

Build a by-value struct descriptor from field layout metadata.

Fields may be declared as [name, type] tuples or as objects with explicit offset and size metadata. Normal fields are naturally aligned by default. Object fields with type: 'bytes' add explicit padding and require size.

import { structType } from 'fino:ffi';

const Point = structType([
  ['x', 'i32'],
  ['y', 'i32'],
]);

const point = Point.alloc();
Point.set(point, 'x', 12);
Point.set(point, 'y', -3);

function dlopen<TSymbols extends NativeSymbolMap = NativeSymbolMap>( path: string | null, symbols: TSymbols, ): DynamicLibrary<TSymbols>

Open a dynamic library and bind the requested symbols.

path may be null to resolve symbols from the current process. The returned handle exposes callable wrappers in symbols and raw symbol addresses in pointers.

import { dlopen } from 'fino:ffi';

const libc = dlopen('/usr/lib/libSystem.B.dylib', {
  strlen: { parameters: ['buffer'], result: 'usize' },
});

const input = new TextEncoder().encode('hello\0');
console.log(libc.symbols.strlen(input));

function ffiFunction( pointer: NativePointer | bigint, definition: NativeSymbolSpec, ): (...args: any[]) => any

Bind a function pointer obtained at runtime.

dlopen() can only reach symbols by name. Function pointers handed back by native code have no name to look up: vkGetInstanceProcAddr results, callback fields read out of a C struct, and FfiCallback.pointer all need this instead.

pointer accepts a pointer buffer, an ArrayBufferView over one, or a BigInt address. The definition is the same shape dlopen() takes for a single symbol, including async, fast, and variadic.

The returned function keeps nothing alive. Whatever provides the code — a DynamicLibrary handle, an FfiCallback — must be retained for as long as the function may be called, or the call will jump into freed memory.

import { dlopen, ffiFunction } from 'fino:ffi';

const libc = dlopen('/usr/lib/libSystem.B.dylib', {
  strlen: { parameters: ['buffer'], result: 'usize' },
});

const strlen = ffiFunction(libc.pointers.strlen, {
  parameters: ['buffer'],
  result: 'usizeBig',
});

console.log(strlen(new TextEncoder().encode('hello\0')));