Basic Usage
import { cc } from "bun:ffi";
const lib = cc({
source: `
int add(int a, int b) {
return a + b;
}
`,
symbols: {
add: {
args: ["i32", "i32"],
returns: "i32",
},
},
});
const result = lib.symbols.add(5, 3);
console.log(result); // 8
Source Options
Inline Source
const lib = cc({
source: `
#include <stdio.h>
void greet(const char* name) {
printf("Hello, %s!\\n", name);
}
`,
symbols: {
greet: {
args: ["cstring"],
returns: "void",
},
},
});
lib.symbols.greet("World");
Source Files
const lib = cc({
source: "math_utils.c",
symbols: {
factorial: {
args: ["i32"],
returns: "i64",
},
},
});
Multiple Files
const lib = cc({
source: [
"helpers.c",
"algorithms.c",
"main.c",
],
symbols: { /* ... */ },
});
Headers and Includes
Standard Headers
TinyCC includes standard C library headers:const lib = cc({
source: `
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
double calculate(double x) {
return sqrt(x * x + sin(x));
}
`,
symbols: {
calculate: {
args: ["f64"],
returns: "f64",
},
},
});
Custom Include Paths
const lib = cc({
source: `
#include "my_header.h"
#include "utils/helpers.h"
int process() {
return helper_function();
}
`,
include: [
"./include",
"./vendor/lib/include",
],
symbols: { /* ... */ },
});
Linking Libraries
System Libraries
const lib = cc({
source: `
#include <sqlite3.h>
const char* get_version() {
return sqlite3_libversion();
}
`,
library: ["sqlite3"],
symbols: {
get_version: {
args: [],
returns: "cstring",
},
},
});
console.log(lib.symbols.get_version()); // "3.43.0"
Multiple Libraries
const lib = cc({
source: `/* ... */`,
library: ["m", "pthread", "dl"],
symbols: { /* ... */ },
});
Library Search Paths
const lib = cc({
source: `/* ... */`,
library: ["mylib"],
libraryPath: [
"/usr/local/lib",
"./build/lib",
],
symbols: { /* ... */ },
});
Preprocessor Defines
const lib = cc({
source: `
#ifdef DEBUG
#define LOG(x) printf("%s\\n", x)
#else
#define LOG(x)
#endif
void process() {
LOG("Processing...");
// do work
}
`,
define: {
DEBUG: "1",
VERSION: "\"1.0.0\"",
MAX_SIZE: "1024",
},
symbols: { /* ... */ },
});
Compiler Flags
Optimization
const lib = cc({
source: `/* ... */`,
flags: "-O2", // Optimize
symbols: { /* ... */ },
});
Warnings
const lib = cc({
source: `/* ... */`,
flags: "-Wall -Wextra",
symbols: { /* ... */ },
});
Multiple Flags
const lib = cc({
source: `/* ... */`,
flags: [
"-O2",
"-Wall",
"-Wextra",
"-std=c11",
],
symbols: { /* ... */ },
});
Symbol Types
See the FFI documentation for a complete list of supported types.Common Patterns
const lib = cc({
source: `
// Integers
int add_ints(int a, int b) { return a + b; }
// Floats
double add_floats(double a, double b) { return a + b; }
// Strings
const char* concat(const char* a, const char* b) {
static char result[256];
sprintf(result, "%s%s", a, b);
return result;
}
// Pointers
void fill_array(int* arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] = i * i;
}
}
`,
symbols: {
add_ints: {
args: ["i32", "i32"],
returns: "i32",
},
add_floats: {
args: ["f64", "f64"],
returns: "f64",
},
concat: {
args: ["cstring", "cstring"],
returns: "cstring",
},
fill_array: {
args: ["ptr", "i32"],
returns: "void",
},
},
});
Working with Arrays
Passing Arrays
import { ptr } from "bun:ffi";
const lib = cc({
source: `
int sum_array(int* arr, int length) {
int sum = 0;
for (int i = 0; i < length; i++) {
sum += arr[i];
}
return sum;
}
`,
symbols: {
sum_array: {
args: ["ptr", "i32"],
returns: "i32",
},
},
});
const arr = new Int32Array([1, 2, 3, 4, 5]);
const total = lib.symbols.sum_array(ptr(arr), arr.length);
console.log(total); // 15
Returning Arrays
const lib = cc({
source: `
#include <stdlib.h>
int* create_range(int size) {
int* arr = malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
arr[i] = i;
}
return arr;
}
void free_array(int* arr) {
free(arr);
}
`,
symbols: {
create_range: {
args: ["i32"],
returns: "ptr",
},
free_array: {
args: ["ptr"],
returns: "void",
},
},
});
// Get pointer from C
const arrPtr = lib.symbols.create_range(10);
// Read from pointer
const arr = new Int32Array(new ArrayBuffer(40), arrPtr);
console.log(arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// Free C memory
lib.symbols.free_array(arrPtr);
Memory Management
Stack Allocation
Fast for small, short-lived data:const lib = cc({
source: `
typedef struct {
int x;
int y;
} Point;
Point create_point(int x, int y) {
Point p = { x, y };
return p; // Returned by value
}
`,
symbols: { /* ... */ },
});
Heap Allocation
Required for large or long-lived data:const lib = cc({
source: `
#include <stdlib.h>
typedef struct {
int* data;
int size;
} Buffer;
Buffer* create_buffer(int size) {
Buffer* buf = malloc(sizeof(Buffer));
buf->data = malloc(size * sizeof(int));
buf->size = size;
return buf;
}
void free_buffer(Buffer* buf) {
free(buf->data);
free(buf);
}
`,
symbols: {
create_buffer: {
args: ["i32"],
returns: "ptr",
},
free_buffer: {
args: ["ptr"],
returns: "void",
},
},
});
const buf = lib.symbols.create_buffer(1024);
// Use buffer...
lib.symbols.free_buffer(buf);
Real-World Examples
Fast Math
const math = cc({
source: `
#include <math.h>
void vector_add(double* a, double* b, double* result, int n) {
for (int i = 0; i < n; i++) {
result[i] = a[i] + b[i];
}
}
double vector_dot(double* a, double* b, int n) {
double sum = 0.0;
for (int i = 0; i < n; i++) {
sum += a[i] * b[i];
}
return sum;
}
`,
library: ["m"],
symbols: {
vector_add: {
args: ["ptr", "ptr", "ptr", "i32"],
returns: "void",
},
vector_dot: {
args: ["ptr", "ptr", "i32"],
returns: "f64",
},
},
});
const a = new Float64Array([1, 2, 3]);
const b = new Float64Array([4, 5, 6]);
const result = new Float64Array(3);
math.symbols.vector_add(ptr(a), ptr(b), ptr(result), 3);
console.log(result); // [5, 7, 9]
const dot = math.symbols.vector_dot(ptr(a), ptr(b), 3);
console.log(dot); // 32
Image Processing
const image = cc({
source: `
void grayscale(unsigned char* pixels, int width, int height) {
for (int i = 0; i < width * height; i++) {
int offset = i * 4;
unsigned char r = pixels[offset];
unsigned char g = pixels[offset + 1];
unsigned char b = pixels[offset + 2];
unsigned char gray = (r + g + b) / 3;
pixels[offset] = gray;
pixels[offset + 1] = gray;
pixels[offset + 2] = gray;
}
}
`,
symbols: {
grayscale: {
args: ["ptr", "i32", "i32"],
returns: "void",
},
},
});
const width = 1920;
const height = 1080;
const pixels = new Uint8Array(width * height * 4);
// ... load image data ...
image.symbols.grayscale(ptr(pixels), width, height);
Sorting
const sort = cc({
source: `
#include <stdlib.h>
int compare_ints(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
void sort_array(int* arr, int n) {
qsort(arr, n, sizeof(int), compare_ints);
}
`,
symbols: {
sort_array: {
args: ["ptr", "i32"],
returns: "void",
},
},
});
const arr = new Int32Array([5, 2, 8, 1, 9]);
sort.symbols.sort_array(ptr(arr), arr.length);
console.log(arr); // [1, 2, 5, 8, 9]
Error Handling
Compile Errors
try {
const lib = cc({
source: `
int broken() {
return "not a number"; // Type error
}
`,
symbols: { /* ... */ },
});
} catch (err) {
console.error("Compilation failed:");
console.error(err.message);
}
Runtime Errors
const lib = cc({
source: `
#include <stdio.h>
#include <errno.h>
int read_file(const char* path) {
FILE* f = fopen(path, "r");
if (!f) return -errno;
fclose(f);
return 0;
}
`,
symbols: {
read_file: {
args: ["cstring"],
returns: "i32",
},
},
});
const result = lib.symbols.read_file("/nonexistent");
if (result !== 0) {
console.error(`Error code: ${-result}`);
}
Performance
Compilation Speed
TinyCC is extremely fast:const start = performance.now();
const lib = cc({
source: `/* 1000 lines of C */`,
symbols: { /* ... */ },
});
console.log(`Compiled in ${performance.now() - start}ms`);
// Typically < 50ms
Runtime Performance
Compiled code runs at native speed:const lib = cc({
source: `
long fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}
`,
symbols: {
fibonacci: {
args: ["i32"],
returns: "i64",
},
},
});
console.time("C");
const result = lib.symbols.fibonacci(40);
console.timeEnd("C"); // ~500ms
// vs pure JavaScript (much slower)
Limitations
TinyCC Constraints
- No C++ support (C only)
- Limited optimization (no -O3)
- No inline assembly on some platforms
- Smaller subset of GNU C extensions
Platform Support
- macOS: x86_64, ARM64
- Linux: x86_64, ARM64
- Windows: x86_64 (limited)
Best Practices
-
Check return values
const result = lib.symbols.do_something(); if (result !== 0) { throw new Error(`Failed with code ${result}`); } -
Free allocated memory
const ptr = lib.symbols.allocate(1024); try { // Use pointer } finally { lib.symbols.free(ptr); } -
Use const for read-only data
source: ` const int MAX_SIZE = 1024; void process(const char* input) { } ` -
Document ABIs
symbols: { // C: int add(int a, int b) add: { args: ["i32", "i32"], returns: "i32", }, }
Debugging
// Enable debug output
process.env.BUN_DEBUG_FFI = "1";
const lib = cc({
source: `
#include <stdio.h>
void debug_info() {
printf("Debug: called from C\\n");
}
`,
flags: "-Wall -Wextra", // Enable warnings
symbols: { /* ... */ },
});