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')));