new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Bun · Runtime · all subjects

bun apis/ffi

48 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

FFI module: bun:ffi

Bun provides Foreign Function Interface (FFI) support through the built-in module bun:ffi for calling C/native code.

cc symbols option - expose C functions to JavaScript

The symbols option is an object that specifies which C functions and variables to expose to JavaScript. Each symbol is a key with an object value containing args (array of FFIType values) and returns (FFIType value). Example: symbols: { hello: { args: [], returns: 'int' } }

cc function - compile and run C from JavaScript

The cc function from 'bun:ffi' compiles and runs C code from JavaScript with low overhead. It uses TinyCC to compile the C code, then links it with the JavaScript runtime, converting types in-place. The basic usage involves passing source code, specifying symbols (functions) to expose with their argument types and return types.

cc FFIType primitive types table

cc supports the following FFIType values for primitive types: cstring (char*), function/fn/callback (void*(*)()), ptr/pointer/void*/char* (void*), i8/int8_t (int8_t), i16/int16_t (int16_t), i32/int32_t/int (int32_t), i64/int64_t (int64_t), i64_fast (int64_t), u8/uint8_t (uint8_t), u16/uint16_t (uint16_t), u32/uint32_t (uint32_t), u64/uint64_t (uint64_t), u64_fast (uint64_t), f32/float (float), f64/double (double), bool (bool), char (char), napi_env (napi_env), napi_value (napi_value).

cc basic example - compile and call C function

JavaScript example using cc to compile and call a C function: import { cc } from 'bun:ffi'; import source from './hello.c' with { type: 'file' }; const { symbols: { hello } } = cc({ source, symbols: { hello: { args: [], returns: 'int' } } }); console.log('What is the answer to the universe?', hello());. C source: int hello() { return 42; }. Running 'bun hello.ts' prints: What is the answer to the universe? 42

cc library option - link C libraries

The library option in cc is an optional array of strings specifying libraries to link with the C code. Example: cc({ source: 'hello.c', library: ['sqlite3'] });

cc source option - C source file

The source option specifies the C code to compile. It can be a string (file path), URL, or BunFile object. Example: cc({ source: 'hello.c', symbols: { hello: { args: [], returns: 'int' } } })

cc flags option - TinyCC compiler flags

The flags option is optional and accepts a string or array of strings containing flags passed to the TinyCC compiler. Examples include -I for include directories and -D for preprocessor definitions.

cc define option - preprocessor definitions

The define option is an optional object of preprocessor definitions passed to the TinyCC compiler as key-value pairs. Example: cc({ source: 'hello.c', define: { NDEBUG: '1' } })

cc napi_value and napi_env - pass JavaScript values to C

For strings, objects, and other non-primitive types, cc supports N-API. Use napi_value to pass or receive JavaScript values from a C function without type conversions. Use napi_env to receive the N-API environment used to call the JavaScript function. This allows returning strings, objects, arrays and other complex types from C to JavaScript.

cc example - return C string to JavaScript using N-API

JavaScript: import { cc } from 'bun:ffi'; import source from './hello.c' with { type: 'file' }; const { symbols: { hello } } = cc({ source, symbols: { hello: { args: ['napi_env'], returns: 'napi_value' } } }); const result = hello();. C source: #include <node/node_api.h> napi_value hello(napi_env env) { napi_value result; napi_create_string_utf8(env, 'Hello, Napi!', NAPI_AUTO_LENGTH, &result); return result; }

cc example - return C object to JavaScript using N-API

C source code to return an object from C to JavaScript using N-API: #include <node/node_api.h> napi_value hello(napi_env env) { napi_value result; napi_create_object(env, &result); return result; }

JSCallback for creating callbacks passed to C functions

JSCallback creates JavaScript callback functions that can be passed to C/FFI functions, allowing native code to call back into JavaScript. Constructor takes a function and a descriptor object with 'returns', 'args', and optional 'threadsafe' properties. Example: const callback = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), { returns: 'bool', args: ['ptr', 'usize'] }); Call close() on the callback when done to free memory.

JSCallback threadsafe parameter for multi-threaded callbacks

JSCallback has optional 'threadsafe' parameter (defaults to false) for thread-safe callbacks. When enabled, callbacks can be invoked from any thread including threads spawned by native libraries. The engine copies C arguments on the calling thread and marshals invocation onto the JavaScript thread where arguments are converted (64-bit integers and pointers arrive as exact BigInts). Because invocation is asynchronous from C's perspective, return value to C caller is unspecified; declare non-void returns but C side must treat thread-safe callbacks as returning void and ignore return value.

JSCallback performance tip using .ptr property

For slight performance boost with JSCallback, pass JSCallback.prototype.ptr directly instead of the JSCallback object itself when calling native functions expecting a function pointer. Example: setOnResolve(onResolve.ptr) instead of setOnResolve(onResolve).

Pointer representation in JavaScript

Bun represents pointers as numbers in JavaScript. 64-bit processors support up to 52 bits of addressable space, and JavaScript numbers support 53 bits of usable space, leaving about 11 bits extra. BigInt is not used because it is slower and engines allocate BigInts separately. If BigInt is passed to a function, it is converted to a number. On Windows, the API type HANDLE does not represent a virtual address; use u64 instead of ptr for HANDLE values.

ptr function for converting TypedArray to pointer

ptr(typedArray) converts from a TypedArray to a pointer (number). Example: import { ptr } from 'bun:ffi'; let myTypedArray = new Uint8Array(32); const myPtr = ptr(myTypedArray);

toArrayBuffer function for converting pointer to ArrayBuffer

toArrayBuffer(ptr, byteOffset, byteLength) converts from a pointer to an ArrayBuffer. If byteLength is not provided, it is assumed to be a null-terminated pointer. Example: import { ptr, toArrayBuffer } from 'bun:ffi'; let myTypedArray = new Uint8Array(32); const myPtr = ptr(myTypedArray); myTypedArray = new Uint8Array(toArrayBuffer(myPtr, 0, 32), 0, 32);

read function for reading from pointers

read function reads data from a pointer. For short-lived pointers, use read with methods: read.ptr, read.i8, read.i16, read.i32, read.i64, read.u8, read.u16, read.u32, read.u64, read.f32, read.f64. Each takes (ptr, byteOffset). read is usually faster than DataView because it does not create a DataView or ArrayBuffer. Example: read.u8(myPtr, 0)

DataView for reading long-lived pointers

For long-lived pointers, convert to DataView using toArrayBuffer. Example: import { toArrayBuffer } from 'bun:ffi'; let myDataView = new DataView(toArrayBuffer(myPtr, 0, 32)); console.log(myDataView.getUint8(0, true), myDataView.getUint8(1, true));

Manual memory management in bun:ffi

bun:ffi does not manage memory automatically. You must free memory when done. Close JSCallback instances with callback.close(). Track when TypedArray is no longer used from JavaScript with FinalizationRegistry. Track from C/FFI side by passing deallocator callback and optional context pointer to toArrayBuffer or toBuffer.

toArrayBuffer deallocator callback signature

toArrayBuffer accepts optional deallocator callback and context pointer for memory tracking from C side. Callback signature: typedef void (*JSTypedArrayBytesDeallocator)(void *bytes, void *deallocatorContext);. Can be called with deallocatorContext: toArrayBuffer(bytes, byteOffset, byteLength, deallocatorContext, jsTypedArrayBytesDeallocator) or without: toArrayBuffer(bytes, byteOffset, byteLength, jsTypedArrayBytesDeallocator).

Memory safety with raw pointers in bun:ffi

Do not use raw pointers outside of FFI. A future version of Bun may add a CLI flag to disable bun:ffi.

Pointer alignment requirements

If an API expects a pointer sized to something other than char or u8, make sure the TypedArray is also that size. A u64* is not exactly the same as [8]u8* due to alignment.

Passing TypedArray as pointer to FFI functions

Where FFI functions expect a pointer, pass a TypedArray of equivalent size. TypedArray is automatically converted to pointer. Example: const pixels = new Uint8ClampedArray(128 * 128 * 4); const out = encode_png(pixels, 128, 128);

Using ptr function for direct pointer access

If automatic TypedArray to pointer conversion is not desired, or a pointer to a specific byte offset within TypedArray is needed, use ptr() directly to get the pointer: import { ptr } from 'bun:ffi'; const myPtr = ptr(pixels); Returns a number, not BigInt.

Reading pointer data and saving to disk example

Example of reading pointer data and saving to disk: const out = encode_png(pixels, 128, 128); let png = new Uint8Array(toArrayBuffer(out)); await Bun.write('out.png', png);

dlopen sqlite3 example

Example using dlopen to import sqlite3_libversion from libsqlite3: import { dlopen, FFIType, suffix } from 'bun:ffi'; const path = `libsqlite3.${suffix}`; const { symbols: { sqlite3_libversion } } = dlopen(path, { sqlite3_libversion: { args: [], returns: FFIType.cstring } }); console.log(`SQLite 3 version: ${sqlite3_libversion()}`);

Zig FFI example with add function

Example Zig FFI: Zig source (add.zig): pub export fn add(a: i32, b: i32) i32 { return a + b; }. Compile: zig build-lib add.zig -dynamic -OReleaseFast. JavaScript: import { dlopen, FFIType } from 'bun:ffi'; const { i32 } = FFIType; const lib = dlopen(`libadd.${suffix}`, { add: { args: [i32, i32], returns: i32 } }); console.log(lib.symbols.add(1, 2));

Rust FFI example with add function

Example Rust FFI: #[no_mangle] pub extern "C" fn add(a: i32, b: i32) -> i32 { a + b }. Compile: rustc --crate-type cdylib add.rs

bun:ffi module overview

The bun:ffi module allows efficiently calling native libraries from JavaScript. It works with any language that supports the C ABI, including Zig, Rust, C/C++, C#, Nim, and Kotlin. According to benchmarks, bun:ffi is roughly 2-6x faster than Node.js FFI through Node-API. The module is experimental with known bugs and limitations and should not be relied on in production; the most stable way to interact with native code is to write a Node-API module.

JSCallback example with search function

Example JSCallback passed to native search function: import { dlopen, JSCallback, ptr, CString } from 'bun:ffi'; const { symbols: { search }, close } = dlopen('libmylib', { search: { returns: 'usize', args: ['cstring', 'callback'] } }); const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), { returns: 'bool', args: ['ptr', 'usize'] }); const str = Buffer.from('wwutwutwutwutwutwutwutwutwutwutut\0', 'utf8'); if (search(ptr(str), searchIterator)) { /* found match */ } setTimeout(() => { searchIterator.close(); close(); }, 5000);

CFunction example with function pointer

Example CFunction from native function pointer: import { CFunction } from 'bun:ffi'; let myNativeLibraryGetVersion = /* pointer */; const getVersion = new CFunction({ returns: 'cstring', args: [], ptr: myNativeLibraryGetVersion }); getVersion();

linkSymbols example with multiple function pointers

Example linkSymbols with multiple function pointers: import { linkSymbols } from 'bun:ffi'; const [majorPtr, minorPtr, patchPtr] = getVersionPtrs(); const lib = linkSymbols({ getMajor: { returns: 'cstring', args: [], ptr: majorPtr }, getMinor: { returns: 'cstring', args: [], ptr: minorPtr }, getPatch: { returns: 'cstring', args: [], ptr: patchPtr } }); const [major, minor, patch] = [lib.symbols.getMajor(), lib.symbols.getMinor(), lib.symbols.getPatch()];

Async functions not supported in bun:ffi

Async functions are not supported in bun:ffi.

buffer_length parameter code example

Example using buffer_length parameter: const { symbols: { write_all } } = dlopen(path, { write_all: { args: ['i32', 'buffer', 'buffer_length'], returns: 'u64' } }); const chunk = new TextEncoder().encode('hello'); write_all(1, chunk, chunk);

C++ FFI example with add function

Example C++ FFI: #include <cstdint> extern "C" int32_t add(int32_t a, int32_t b) { return a + b; }. Linux compile: clang++ -shared -fPIC add.cpp -o libadd.so. macOS compile: clang++ -dynamiclib add.cpp -o libadd.dylib

dlopen function signature and usage

dlopen(path, symbolMap) opens a native library and imports symbols. The first argument is a library name or file path. The second argument is an object mapping symbol names to descriptors with 'args' (array of FFIType or strings) and 'returns' (FFIType or string). Returns an object with a 'symbols' property containing callable functions for each imported symbol.

suffix export for platform-specific library extensions

The bun:ffi module exports 'suffix' which is either 'dylib', 'so', or 'dll' depending on the platform. This can be used to construct platform-independent paths to native libraries, for example: const path = `libsqlite3.${suffix}`

FFIType values and C type mappings

Supported FFIType values: buffer (char*), cstring (char*), function/fn/callback (void*)()), ptr/pointer/void*/char* (void*), i8/int8_t (int8_t), i16/int16_t (int16_t), i32/int32_t/int (int32_t), i64/int64_t (int64_t), i64_fast (int64_t), u8/uint8_t (uint8_t), u16/uint16_t (uint16_t), u32/uint32_t (uint32_t), u64/uint64_t (uint64_t), u64_fast (uint64_t), f32/float (float), f64/double (double), bool (bool), char (char), napi_env (napi_env, cc() only), napi_value (napi_value, cc() only), buffer_length (uint64_t/size_t, engine-native only, not cc()).

buffer FFIType requirements

buffer arguments must be a TypedArray or DataView. When buffer is used as a parameter, it accepts TypedArray or DataView objects.

buffer_length FFIType twin parameter

buffer_length is buffer's length twin: pass the same TypedArray/DataView passed for the buffer parameter, and the callee receives that view's byte length as an unsigned 64-bit integer. The engine reads the pointer and length off the same object at the moment of call, creating an atomic snapshot. It is argument-only and not available inside cc(). Example: dlopen(path, { write_all: { args: ['i32', 'buffer', 'buffer_length'], returns: 'u64' } }); write_all(1, chunk, chunk);

napi_env and napi_value FFIType restrictions

napi_env and napi_value are only valid in cc() source, where napi_env parameter is filled by the compiled trampoline and napi_value passes the JavaScript value through unchanged. Using either type in dlopen, linkSymbols, JSCallback, or CFunction descriptor throws a TypeError.

CString conversion from pointer to JavaScript string

CString(ptr: number, byteOffset?: number, byteLength?: number): string converts a UTF-8 C string at a pointer and returns a plain JavaScript string. new CString(ptr) converts from a null-terminated string pointer. new CString(ptr, 0, byteLength) converts from a pointer with known length. The result is a normal string (typeof === 'string') that is a clone of the C string, safe to use after the original pointer is freed.

cstring FFIType behavior in return and args

When used in 'returns', FFIType.cstring coerces the pointer to a JavaScript string. When used in 'args', FFIType.cstring accepts everything 'ptr' does plus additionally accepts a JavaScript string directly. The engine transcodes JavaScript strings to null-terminated UTF-8 buffers that live for the call duration, so no manual encoding is needed. Example: symbols.puts('Hello, world!');

cstring return value lifetime and aliasing

When a cstring is returned, the pointer is whatever the C function returned (owned by native side). The engine copies nothing on return; the JavaScript string is cloned from it. The one aliasing case is when a C function returns a pointer derived from a cstring argument passed as JavaScript string: that argument was transcoded into the engine's call-scoped buffer, so treat returned pointers as valid only until the next FFI call reuses that buffer. Clone via the returned string or new CString rather than holding the raw address.

CFunction for calling function pointers

CFunction creates a callable function from a native function pointer. Constructor takes an object with 'returns', 'args', and 'ptr' properties. 'ptr' is the native function pointer (number or bigint). Example: const getVersion = new CFunction({ returns: 'cstring', args: [], ptr: myNativeLibraryGetVersion }); getVersion();

linkSymbols for multiple function pointers

linkSymbols defines multiple function pointers at once. Takes an object mapping names (can be anything, unlike dlopen) to descriptors with 'returns', 'args', and 'ptr' properties. Since it does not use dlsym(), a valid ptr must be provided for each. Invalid pointers will crash the program. Returns an object with a 'symbols' property containing the callable functions.

Give your agent this brain