or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

database.mdffi.mdfile-system.mdglobals.mdhtml-rewriter.mdhttp-server.mdindex.mdprocess-management.mdredis.mds3.mdshell.mdtesting.mdutilities.md
tile.json

ffi.mddocs/

Foreign Function Interface

Native library integration with C function binding, pointer manipulation, type-safe FFI declarations, and seamless interoperability between JavaScript and native code.

Core Imports

import { dlopen, FFIType, ptr, CString, toArrayBuffer, toBuffer } from "bun:ffi";

Capabilities

Library Loading

Load native libraries and bind C functions with automatic type marshalling and memory management.

/**
 * Load a native library and bind its functions
 * @param name - Path to shared library file (.so, .dll, .dylib) or library name
 * @param symbols - Object mapping function names to their FFI signatures
 * @returns Library object with bound functions
 */
function dlopen<Fns extends Record<string, FFIFunction>>(
  name: string | import("bun").BunFile | URL,
  symbols: Fns
): Library<Fns>;

/**
 * Compile C source code and create FFI bindings
 * @param options - Compilation options
 * @returns Library with compiled symbols
 */
function cc<Fns extends Record<string, FFIFunction>>(options: {
  source: string | import("bun").BunFile | URL;
  library?: string[] | string;
  include?: string[] | string;
  symbols: Fns;
  define?: Record<string, string>;
  flags?: string | string[];
}): Library<Fns>;

/**
 * Create a single C function from a pointer
 * @param fn - FFI function declaration with pointer
 * @returns Callable function with close method
 */
function CFunction(fn: FFIFunction & { ptr: Pointer }): CallableFunction & {
  close(): void;
};

/**
 * Link symbols that are already loaded in memory
 * @param symbols - Map of symbols with function pointers
 * @returns Library with linked symbols
 */
function linkSymbols<Fns extends Record<string, FFIFunction>>(symbols: Fns): Library<Fns>;

interface FFIFunction {
  /** Parameter types (in order) */
  readonly args?: readonly FFITypeOrString[];
  /** Return type */
  readonly returns?: FFITypeOrString;
  /** Function pointer (for pre-loaded functions) */
  readonly ptr?: Pointer | bigint;
  /** Whether function can be called from other threads */
  readonly threadsafe?: boolean;
}

type Symbols = Readonly<Record<string, FFIFunction>>;

interface Library<Fns extends Symbols> {
  /** Bound symbols as callable functions */
  symbols: ConvertFns<Fns>;
  /** Close the library and free resources */
  close(): void;
}

type FFITypeOrString = FFIType | keyof FFITypeStringToType;

Usage Examples:

import { dlopen, FFIType } from "bun:ffi";

// Load math library
const mathLib = dlopen("libm.so.6", {
  sin: {
    args: ["double"],
    returns: "double"
  },
  cos: {
    args: ["double"], 
    returns: "double"
  },
  pow: {
    args: ["double", "double"],
    returns: "double"
  }
});

// Use bound functions
const result = mathLib.symbols.sin(Math.PI / 2); // 1.0
const power = mathLib.symbols.pow(2, 8); // 256.0

// Load custom library
const myLib = dlopen("./libmycode.so", {
  add_numbers: {
    args: ["int32_t", "int32_t"],
    returns: "int32_t"
  },
  process_string: {
    args: ["cstring"],
    returns: "cstring"
  },
  allocate_buffer: {
    args: ["uint32_t"],
    returns: "ptr"
  }
});

// Call custom functions
const sum = myLib.symbols.add_numbers(42, 58); // 100
const processed = myLib.symbols.process_string("hello");

// Clean up
mathLib.close();
myLib.close();

FFI Type System

Comprehensive type mapping between JavaScript and C types with automatic marshalling.

/**
 * FFI type enumeration for C type mapping
 */
enum FFIType {
  /** 8-bit character */
  char = 0,
  /** 8-bit signed integer */
  int8_t = 1,
  /** 8-bit signed integer (alias) */
  i8 = 1,
  /** 8-bit unsigned integer */
  uint8_t = 2,
  /** 8-bit unsigned integer (alias) */
  u8 = 2,
  /** 16-bit signed integer */
  int16_t = 3,
  /** 16-bit signed integer (alias) */
  i16 = 3,
  /** 16-bit unsigned integer */
  uint16_t = 4,
  /** 16-bit unsigned integer (alias) */
  u16 = 4,
  /** 32-bit signed integer */
  int32_t = 5,
  /** 32-bit signed integer (alias) */
  i32 = 5,
  /** 32-bit signed integer (C int) */
  int = 5,
  /** 32-bit unsigned integer */
  uint32_t = 6,
  /** 32-bit unsigned integer (alias) */
  u32 = 6,
  /** 64-bit signed integer */
  int64_t = 7,
  /** 64-bit signed integer (alias) */
  i64 = 7,
  /** 64-bit unsigned integer */
  uint64_t = 8,
  /** 64-bit unsigned integer (alias) */
  u64 = 8,
  /** 64-bit floating point */
  double = 9,
  /** 64-bit floating point (alias) */
  f64 = 9,
  /** 32-bit floating point */
  float = 10,
  /** 32-bit floating point (alias) */
  f32 = 10,
  /** Boolean value */
  bool = 11,
  /** Pointer value */
  ptr = 12,
  /** Pointer value (alias) */
  pointer = 12,
  /** No return value */
  void = 13,
  /** C string (null-terminated) */
  cstring = 14,
  /** Fast 64-bit signed integer (may be number or bigint) */
  i64_fast = 15,
  /** Fast 64-bit unsigned integer (may be number or bigint) */
  u64_fast = 16,
  /** Function pointer */
  function = 17,
  /** Node-API environment */
  napi_env = 18,
  /** Node-API value */
  napi_value = 19,
  /** Buffer type */
  buffer = 20
}

type Pointer = number & { __pointer__: null };

/**
 * FFI type to JavaScript argument type mapping
 */
interface FFITypeToArgsType {
  [FFIType.char]: number;
  [FFIType.int8_t]: number;
  [FFIType.i8]: number;
  [FFIType.uint8_t]: number;
  [FFIType.u8]: number;
  [FFIType.int16_t]: number;
  [FFIType.i16]: number;
  [FFIType.uint16_t]: number;
  [FFIType.u16]: number;
  [FFIType.int32_t]: number;
  [FFIType.i32]: number;
  [FFIType.int]: number;
  [FFIType.uint32_t]: number;
  [FFIType.u32]: number;
  [FFIType.int64_t]: number | bigint;
  [FFIType.i64]: number | bigint;
  [FFIType.uint64_t]: number | bigint;
  [FFIType.u64]: number | bigint;
  [FFIType.double]: number;
  [FFIType.f64]: number;
  [FFIType.float]: number;
  [FFIType.f32]: number;
  [FFIType.bool]: boolean;
  [FFIType.ptr]: NodeJS.TypedArray | Pointer | CString | null;
  [FFIType.pointer]: NodeJS.TypedArray | Pointer | CString | null;
  [FFIType.void]: undefined;
  [FFIType.cstring]: NodeJS.TypedArray | Pointer | CString | null;
  [FFIType.i64_fast]: number | bigint;
  [FFIType.u64_fast]: number | bigint;
  [FFIType.function]: Pointer | JSCallback;
  [FFIType.napi_env]: unknown;
  [FFIType.napi_value]: unknown;
  [FFIType.buffer]: NodeJS.TypedArray | DataView;
}

/**
 * FFI type to JavaScript return type mapping
 */
interface FFITypeToReturnsType {
  [FFIType.char]: number;
  [FFIType.int8_t]: number;
  [FFIType.i8]: number;
  [FFIType.uint8_t]: number;
  [FFIType.u8]: number;
  [FFIType.int16_t]: number;
  [FFIType.i16]: number;
  [FFIType.uint16_t]: number;
  [FFIType.u16]: number;
  [FFIType.int32_t]: number;
  [FFIType.i32]: number;
  [FFIType.int]: number;
  [FFIType.uint32_t]: number;
  [FFIType.u32]: number;
  [FFIType.int64_t]: bigint;
  [FFIType.i64]: bigint;
  [FFIType.uint64_t]: bigint;
  [FFIType.u64]: bigint;
  [FFIType.double]: number;
  [FFIType.f64]: number;
  [FFIType.float]: number;
  [FFIType.f32]: number;
  [FFIType.bool]: boolean;
  [FFIType.ptr]: Pointer | null;
  [FFIType.pointer]: Pointer | null;
  [FFIType.void]: undefined;
  [FFIType.cstring]: CString;
  [FFIType.i64_fast]: number | bigint;
  [FFIType.u64_fast]: number | bigint;
  [FFIType.function]: Pointer | null;
  [FFIType.napi_env]: unknown;
  [FFIType.napi_value]: unknown;
  [FFIType.buffer]: NodeJS.TypedArray | DataView;
}

/**
 * String names to FFI type mapping
 */
interface FFITypeStringToType {
  ["char"]: FFIType.char;
  ["int8_t"]: FFIType.int8_t;
  ["i8"]: FFIType.i8;
  ["uint8_t"]: FFIType.uint8_t;
  ["u8"]: FFIType.u8;
  ["int16_t"]: FFIType.int16_t;
  ["i16"]: FFIType.i16;
  ["uint16_t"]: FFIType.uint16_t;
  ["u16"]: FFIType.u16;
  ["int32_t"]: FFIType.int32_t;
  ["i32"]: FFIType.i32;
  ["int"]: FFIType.int;
  ["uint32_t"]: FFIType.uint32_t;
  ["u32"]: FFIType.u32;
  ["int64_t"]: FFIType.int64_t;
  ["i64"]: FFIType.i64;
  ["uint64_t"]: FFIType.uint64_t;
  ["u64"]: FFIType.u64;
  ["double"]: FFIType.double;
  ["f64"]: FFIType.f64;
  ["float"]: FFIType.float;
  ["f32"]: FFIType.f32;
  ["bool"]: FFIType.bool;
  ["ptr"]: FFIType.ptr;
  ["pointer"]: FFIType.pointer;
  ["void"]: FFIType.void;
  ["cstring"]: FFIType.cstring;
  ["function"]: FFIType.pointer;
  ["usize"]: FFIType.uint64_t;
  ["callback"]: FFIType.pointer;
  ["napi_env"]: FFIType.napi_env;
  ["napi_value"]: FFIType.napi_value;
  ["buffer"]: FFIType.buffer;
}

type ToFFIType<T extends FFITypeOrString> = T extends FFIType ? T : T extends string ? FFITypeStringToType[T] : never;

type ConvertFns<Fns extends Symbols> = {
  [K in keyof Fns]: {
    (
      ...args: Fns[K]["args"] extends infer A extends readonly FFITypeOrString[]
        ? { [L in keyof A]: FFITypeToArgsType[ToFFIType<A[L]>] }
        : [unknown] extends [Fns[K]["args"]]
          ? []
          : never
    ): [unknown] extends [Fns[K]["returns"]]
      ? undefined
      : FFITypeToReturnsType[ToFFIType<NonNullable<Fns[K]["returns"]>>];
  };
};

Pointer Operations

Low-level pointer manipulation and memory management utilities.

/**
 * Get pointer address of a typed array or buffer
 * @param view - Typed array, ArrayBuffer, or DataView
 * @param byteOffset - Optional byte offset
 * @returns Pointer address
 */
function ptr(view: NodeJS.TypedArray | ArrayBufferLike | DataView, byteOffset?: number): Pointer;

/**
 * Read a pointer as a Buffer
 * @param ptr - Memory address to read
 * @param byteOffset - Bytes to skip before reading
 * @param byteLength - Bytes to read (0-terminated if not provided)
 * @returns Buffer containing data
 */
function toBuffer(ptr: Pointer, byteOffset?: number, byteLength?: number): Buffer;

/**
 * Read a pointer as an ArrayBuffer
 * @param ptr - Memory address to read
 * @param byteOffset - Bytes to skip before reading
 * @param byteLength - Bytes to read (0-terminated if not provided)
 * @returns ArrayBuffer containing data
 */
function toArrayBuffer(ptr: Pointer, byteOffset?: number, byteLength?: number): ArrayBuffer;

/**
 * Low-level memory reading functions
 */
namespace read {
  function u8(ptr: Pointer, byteOffset?: number): number;
  function i8(ptr: Pointer, byteOffset?: number): number;
  function u16(ptr: Pointer, byteOffset?: number): number;
  function i16(ptr: Pointer, byteOffset?: number): number;
  function u32(ptr: Pointer, byteOffset?: number): number;
  function i32(ptr: Pointer, byteOffset?: number): number;
  function f32(ptr: Pointer, byteOffset?: number): number;
  function u64(ptr: Pointer, byteOffset?: number): bigint;
  function i64(ptr: Pointer, byteOffset?: number): bigint;
  function f64(ptr: Pointer, byteOffset?: number): number;
  function ptr(ptr: Pointer, byteOffset?: number): number;
  function intptr(ptr: Pointer, byteOffset?: number): number;
}

Usage Examples:

import { ptr, CString, read } from "bun:ffi";

// Get pointer from typed array
const buffer = new ArrayBuffer(16);
const bufferPtr = ptr(buffer);

// Working with C strings from pointers
const lib = dlopen("./mylib.so", {
  get_version: {
    returns: "cstring"
  }
});

const versionPtr = lib.symbols.get_version();
const version = new CString(versionPtr);
console.log("Version:", version.toString());

// Working with typed arrays
const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const arrayPtr = ptr(uint8Array);

// Pass pointer to native function
const lib = dlopen("./mylib.so", {
  process_bytes: {
    args: ["ptr", "uint32_t"],
    returns: "int32_t"
  }
});

const uint8Array = new Uint8Array([1, 2, 3, 4, 5]);
const arrayPtr = ptr(uint8Array);
const result = lib.symbols.process_bytes(arrayPtr, uint8Array.length);

Callback Functions

Create JavaScript callbacks callable from native code with proper type marshalling.

/**
 * C string class for working with null-terminated strings
 */
class CString extends String {
  /**
   * Create a C string from a pointer
   * @param ptr - Pointer to C string data
   * @param byteOffset - Byte offset to start reading
   * @param byteLength - Number of bytes to read
   */
  constructor(ptr: Pointer, byteOffset?: number, byteLength?: number);
  
  /** Pointer to the C string */
  ptr: Pointer;
  /** Byte offset */
  byteOffset?: number;
  /** Byte length */
  byteLength?: number;
  
  /** Get the pointer as an ArrayBuffer */
  get arrayBuffer(): ArrayBuffer;
}

/**
 * JavaScript callback that can be called from native code
 */
class JSCallback {
  /**
   * Create a JavaScript callback for FFI
   * @param callback - JavaScript function to call
   * @param definition - C function signature
   */
  constructor(callback: (...args: any[]) => any, definition: FFIFunction);
  
  /** Pointer to the callback function (null if closed) */
  readonly ptr: Pointer | null;
  /** Whether callback is thread-safe */
  readonly threadsafe: boolean;
  
  /** Free the callback resources */
  close(): void;
}

Usage Examples:

import { dlopen, JSCallback } from "bun:ffi";

// Create a JavaScript callback
const myCallback = new JSCallback((a: number, b: number) => {
  console.log(`Native code called with: ${a}, ${b}`);
  return a + b;
}, {
  args: ["int32_t", "int32_t"],
  returns: "int32_t"
});

// Load library that accepts callbacks
const lib = dlopen("./callback_lib.so", {
  register_callback: {
    args: ["ptr"],
    returns: "void"
  },
  call_callback: {
    args: ["int32_t", "int32_t"],
    returns: "int32_t"
  }
});

// Register the callback
lib.symbols.register_callback(myCallback.ptr);

// Native code can now call our JavaScript function
const result = lib.symbols.call_callback(10, 20);
console.log("Result:", result); // 30

// Clean up callback
myCallback.close();

Structure Handling

Work with C structures using JavaScript objects and automatic layout management.

/**
 * View the generated C code for FFI bindings
 * @param symbols - Symbol definitions to view
 * @param is_callback - Whether viewing callback code
 * @returns Generated C source code
 */
function viewSource(symbols: Symbols, is_callback?: false): string[];
function viewSource(callback: FFIFunction, is_callback: true): string;

/**
 * Platform-specific dynamic library file extension
 */
const suffix: string;

Usage Examples:

import { dlopen, ptr, read, toBuffer } from "bun:ffi";

// Working with structures manually using pointers and read functions
const lib = dlopen("./struct_lib.so", {
  get_point: {
    returns: "ptr"
  },
  process_point: {
    args: ["ptr"],
    returns: "double"
  }
});

// Get a pointer to a structure from native code
const pointPtr = lib.symbols.get_point();

// Read structure fields manually
const x = read.f64(pointPtr, 0);     // x at offset 0
const y = read.f64(pointPtr, 8);     // y at offset 8
const z = read.f64(pointPtr, 16);    // z at offset 16

console.log(`Point: (${x}, ${y}, ${z})`);

// Process the point
const distance = lib.symbols.process_point(pointPtr);
console.log("Distance:", distance);

Memory Management

Advanced memory management utilities for working with native code.

Usage Examples:

import { ptr, read, toBuffer, toArrayBuffer } from "bun:ffi";

// Working with typed arrays (automatic memory management)
const array = new Int32Array(10);

// Initialize array with values
for (let i = 0; i < 10; i++) {
  array[i] = i * i;
}

// Get pointer for passing to native functions
const arrayPtr = ptr(array);

// Read values using FFI read functions
for (let i = 0; i < 10; i++) {
  const value = read.i32(arrayPtr, i * 4);
  console.log(`array[${i}] = ${value}`);
}

// Convert pointer back to different views
const buffer = toBuffer(arrayPtr, 0, array.byteLength);
const arrayBuffer = toArrayBuffer(arrayPtr, 0, array.byteLength);

console.log("Buffer length:", buffer.length);
console.log("ArrayBuffer length:", arrayBuffer.byteLength);

Type Definitions

/** FFI function callable symbol marker */
const FFIFunctionCallableSymbol: unique symbol;

/** Native module handle (platform-specific) */
interface NativeLibrary {
  handle: number;
  symbols: Record<string, any>;
}

/** FFI compilation and linking options */
interface CompilerOptions {
  source: string | import("bun").BunFile | URL;
  library?: string[] | string;
  include?: string[] | string;
  define?: Record<string, string>;
  flags?: string | string[];
}