aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--integration/bunjs-only-snippets/buffer.test.js4
-rw-r--r--integration/bunjs-only-snippets/ffi.test.js35
-rw-r--r--packages/bun-types/package.json2
-rw-r--r--packages/bun-types/types.d.ts342
-rw-r--r--src/javascript/jsc/api/FFI.h1
-rw-r--r--src/javascript/jsc/api/bun.zig371
-rw-r--r--src/javascript/jsc/api/ffi.zig136
-rw-r--r--src/javascript/jsc/base.zig51
-rw-r--r--src/javascript/jsc/ffi.exports.js7
-rw-r--r--src/javascript/jsc/javascript.zig15
-rw-r--r--src/linker.zig7
-rw-r--r--types/bun/bun.d.ts2
-rw-r--r--types/bun/ffi.d.ts523
-rw-r--r--types/bun/index.d.ts1
-rw-r--r--types/bun/paths.txt1
15 files changed, 1277 insertions, 221 deletions
diff --git a/integration/bunjs-only-snippets/buffer.test.js b/integration/bunjs-only-snippets/buffer.test.js
index 0d5821fdc..d1a5c7ff3 100644
--- a/integration/bunjs-only-snippets/buffer.test.js
+++ b/integration/bunjs-only-snippets/buffer.test.js
@@ -31,10 +31,6 @@ it("Buffer", () => {
expect(new Buffer(input).toString("utf8")).toBe(inputs[i]);
expect(Array.from(new Buffer(input)).join(",")).toBe(good[i].join(","));
}
- for (let i = 0; i < inputs.length; i++) {
- var input = inputs[i];
- expect(new Buffer(input, "ucs2").toString("utf8")).toBe(inputs[i]);
- }
});
it("Buffer.toBuffer throws", () => {
diff --git a/integration/bunjs-only-snippets/ffi.test.js b/integration/bunjs-only-snippets/ffi.test.js
index 16967fe51..2337a44c8 100644
--- a/integration/bunjs-only-snippets/ffi.test.js
+++ b/integration/bunjs-only-snippets/ffi.test.js
@@ -1,10 +1,20 @@
import { describe, it, expect } from "bun:test";
import { unsafe } from "bun";
+//
+import {
+ viewSource,
+ dlopen,
+ CString,
+ ptr,
+ toBuffer,
+ toArrayBuffer,
+ FFIType,
+} from "bun:ffi";
it("ffi print", () => {
- Bun.dlprint({
+ viewSource({
add: {
- params: ["int32_t", "int32_t"],
+ args: [FFIType.int],
return_type: "int32_t",
},
})[0];
@@ -211,7 +221,7 @@ it("ffi run", () => {
ptr_should_point_to_42_as_int32_t,
},
close,
- } = Bun.dlopen("/tmp/bun-ffi-test.dylib", types);
+ } = dlopen("/tmp/bun-ffi-test.dylib", types);
expect(returns_true()).toBe(true);
expect(returns_false()).toBe(false);
@@ -249,16 +259,17 @@ it("ffi run", () => {
expect(add_uint16_t(1, 1)).toBe(2);
expect(add_uint32_t(1, 1)).toBe(2);
- const ptr = ptr_should_point_to_42_as_int32_t();
- expect(ptr != 0).toBe(true);
- expect(typeof ptr === "number").toBe(true);
- expect(does_pointer_equal_42_as_int32_t(ptr)).toBe(true);
- const buffer = unsafe.bufferFromPtr(ptr, 4);
+ const cptr = ptr_should_point_to_42_as_int32_t();
+ expect(cptr != 0).toBe(true);
+ expect(typeof cptr === "number").toBe(true);
+ expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true);
+ const buffer = toBuffer(cptr, 0, 4);
expect(buffer.readInt32(0)).toBe(42);
- expect(
- new DataView(unsafe.arrayBufferFromPtr(ptr, 4), 0, 4).getInt32(0, true)
- ).toBe(42);
- expect(unsafe.arrayBufferToPtr(buffer)).toBe(ptr);
+ expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe(
+ 42
+ );
+ expect(ptr(buffer)).toBe(cptr);
+ expect(new CString(cptr, 0, 1)).toBe("*");
close();
});
``;
diff --git a/packages/bun-types/package.json b/packages/bun-types/package.json
index 8d6c8e90d..5b6b10ace 100644
--- a/packages/bun-types/package.json
+++ b/packages/bun-types/package.json
@@ -1,6 +1,6 @@
{
"name": "bun-types",
- "version": "0.0.78",
+ "version": "0.0.79",
"description": "Type definitions for bun.js",
"types": "types.d.ts",
"files": [
diff --git a/packages/bun-types/types.d.ts b/packages/bun-types/types.d.ts
index ebe8e2e5f..b3dfbc605 100644
--- a/packages/bun-types/types.d.ts
+++ b/packages/bun-types/types.d.ts
@@ -572,6 +572,22 @@ declare module "bun" {
serverNames: Record<string, SSLOptions & SSLAdvancedOptions>;
};
+ /**
+ * HTTP & HTTPS Server
+ *
+ * To start the server, see {@link serve}
+ *
+ * Often, you don't need to interact with this object directly. It exists to help you with the following tasks:
+ * - Stop the server
+ * - How many requests are currently being handled?
+ *
+ * For performance, Bun pre-allocates most of the data for 2048 concurrent requests.
+ * That means starting a new server allocates about 500 KB of memory. Try to
+ * avoid starting and stopping the server often (unless it's a new instance of bun).
+ *
+ * Powered by a fork of [uWebSockets](https://github.com/uNetworking/uWebSockets). Thank you @alexhultman.
+ *
+ */
interface Server {
/**
* Stop listening to prevent new connections from being accepted.
@@ -877,7 +893,7 @@ declare module "bun" {
*
* This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
*
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
*
* The equivalent `openssl` command is:
*
@@ -897,7 +913,7 @@ declare module "bun" {
*
* This hashing function balances speed with cryptographic strength. This does not encrypt or decrypt data.
*
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
+ * The implementation uses [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
*
* The equivalent `openssl` command is:
*
@@ -909,14 +925,9 @@ declare module "bun" {
export function sha(input: StringOrBuffer, encoding: DigestEncoding): string;
/**
+ * This is not the default because it's not cryptographically secure and it's slower than {@link SHA512}
*
- * Hashing functions for SHA-1
- *
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
- *
- * SHA-1 is no longer cryptographically secure and it's slower than {@link SHA512}.
- *
- * Consider using the {@link SHA512_256} instead.
+ * Consider using the ugly-named {@link SHA512_256} instead
*/
export class SHA1 extends CryptoHashInterface<SHA1> {
constructor();
@@ -926,18 +937,6 @@ declare module "bun" {
*/
static readonly byteLength: 20;
}
-
- /**
- *
- * Hashing functions for MD5
- *
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
- *
- * If you're looking for a fast hashing function, consider {@link hash}
- * instead. This is not cryptographically secure and it's slower than
- * {@link hash}. The best reason to use it is compatibility.
- *
- */
export class MD5 extends CryptoHashInterface<MD5> {
constructor();
@@ -946,18 +945,6 @@ declare module "bun" {
*/
static readonly byteLength: 16;
}
-
- /**
- *
- * Ancient hashing function. Bun maybe shouldn't have included this.
- *
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
- *
- * If you're looking for a fast hashing function, consider {@link hash}
- * instead. This is not cryptographically secure and it's slower than
- * {@link hash}. The best reason to use it is compatibility.
- *
- */
export class MD4 extends CryptoHashInterface<MD4> {
constructor();
@@ -966,11 +953,6 @@ declare module "bun" {
*/
static readonly byteLength: 16;
}
- /**
- * Smaller variant of SHA-2
- *
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
- */
export class SHA224 extends CryptoHashInterface<SHA224> {
constructor();
@@ -979,11 +961,6 @@ declare module "bun" {
*/
static readonly byteLength: 28;
}
- /**
- * Faster variant of SHA-2, but with bigger output (64 bytes)
- *
- * Powered by [BoringSSL](https://boringssl.googlesource.com/boringssl) (used in Chromium & Go)
- */
export class SHA512 extends CryptoHashInterface<SHA512> {
constructor();
@@ -1000,13 +977,6 @@ declare module "bun" {
*/
static readonly byteLength: 48;
}
- /**
- * SHA-2 (Secure Hash Algorithm 2) is a set of cryptographic hash functions
- * designed by the United States National Security Agency (NSA) and first
- * published in 2001
- *
- * {@link https://en.wikipedia.org/wiki/SHA-2}
- */
export class SHA256 extends CryptoHashInterface<SHA256> {
constructor();
@@ -1016,9 +986,6 @@ declare module "bun" {
static readonly byteLength: 32;
}
/**
- *
- * Fast variant of SHA-512, but with smaller output (32 bytes)
- *
* See also {@link sha}
*/
export class SHA512_256 extends CryptoHashInterface<SHA512_256> {
@@ -1063,6 +1030,268 @@ interface BufferEncodingOption {
declare var Bun: typeof import("bun");
+
+// ./ffi.d.ts
+
+/**
+ * `bun:ffi` lets you efficiently call C functions & FFI functions from JavaScript
+ * without writing any C code yourself.
+ *
+ * ```js
+ * import {dlopen, CString, ptr} from 'bun:ffi';
+ *
+ * const lib = dlopen('libsqlite3', {});
+ *
+ *
+ * ```
+ *
+ * This is powered by just-in-time compiling C wrappers
+ * that convert JavaScript types to C types and back. Internally,
+ * bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
+ * goes to Fabrice Bellard and TinyCC maintainers for making this possible.
+ *
+ */
+declare module "bun:ffi" {
+ export enum FFIType {
+ char = 0,
+
+ int8_t = 1,
+ i8 = 1,
+
+ uint8_t = 2,
+ u8 = 2,
+
+ int16_t = 3,
+ i16 = 3,
+
+ uint16_t = 4,
+ u16 = 4,
+
+ /**
+ * 32-bit signed integer
+ *
+ */
+ int32_t = 5,
+
+ /**
+ * 32-bit signed integer
+ *
+ * Alias of {@link FFIType.int32_t}
+ */
+ i32 = 5,
+ /**
+ * 32-bit signed integer
+ *
+ * The same as `int` in C
+ */
+ int = 5,
+
+ uint32_t = 6,
+ u32 = 6,
+
+ int64_t = 7,
+ i64 = 7,
+
+ uint64_t = 8,
+ u64 = 8,
+
+ double = 9,
+ f64 = 9,
+
+ float = 10,
+ f32 = 10,
+
+ bool = 11,
+
+ ptr = 12,
+ pointer = 12,
+
+ void = 13,
+ }
+
+ type Symbols = Record<
+ string,
+ {
+ /**
+ * Arguments to a C function
+ *
+ * Defaults to an empty array, which means no arguments.
+ *
+ * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
+ *
+ * @example
+ * From JavaScript:
+ * ```js
+ * const lib = dlopen('add', {
+ * // FFIType can be used or you can pass string labels.
+ * args: [FFIType.i32, "i32"],
+ * return_type: "i32",
+ * });
+ * lib.symbols.add(1, 2)
+ * ```
+ * In C:
+ * ```c
+ * int add(int a, int b) {
+ * return a + b;
+ * }
+ * ```
+ */
+ args?: FFIType[];
+ return_type?: FFIType;
+ }
+ >;
+
+ export interface Library {
+ symbols: Record<string, CallableFunction>;
+
+ /**
+ * `dlclose` the library, unloading the symbols and freeing memory allocated.
+ *
+ * Once called, the library is no longer usable.
+ *
+ * Calling a function from a library that has been closed is undefined behavior.
+ */
+ close(): void;
+ }
+
+ export function dlopen(libraryName: string, symbols: Symbols): Library<T>;
+
+ /**
+ * Read a pointer as a {@link Buffer}
+ *
+ * If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
+ *
+ * @param ptr The memory address to read
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ *
+ */
+ export function toBuffer(
+ ptr: number,
+ byteOffset?: number,
+ byteLength?: number
+ ): Buffer;
+
+ /**
+ * Read a pointer as an {@link ArrayBuffer}
+ *
+ * If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
+ *
+ * @param ptr The memory address to read
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+ export function toArrayBuffer(
+ ptr: number,
+ byteOffset?: number,
+ byteLength?: number
+ ): ArrayBuffer;
+
+ /**
+ * Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer}
+ *
+ * Use this to pass {@link TypedArray} or {@link ArrayBuffer} to C functions.
+ *
+ * This is for use with FFI functions. For performance reasons, FFI will
+ * not automatically convert typed arrays to C pointers.
+ *
+ * @param {TypedArray|ArrayBuffer|DataView} view the typed array or array buffer to get the pointer for
+ * @param {number} byteOffset optional offset into the view in bytes
+ *
+ * @example
+ *
+ * From JavaScript:
+ * ```js
+ * const array = new Uint8Array(10);
+ * const rawPtr = ptr(array);
+ * myCFunction(rawPtr);
+ * ```
+ * To C:
+ * ```c
+ * void myCFunction(char* rawPtr) {
+ * // Do something with rawPtr
+ * }
+ * ```
+ *
+ */
+ export function ptr(
+ view: TypedArray | ArrayBufferLike | DataView,
+ byteOffset?: number
+ ): number;
+
+ /**
+ * Get a string from a UTF-8 encoded C string
+ * If `byteLength` is not provided, the string is assumed to be null-terminated.
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * console.log(new CString(ptr));
+ * ```
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * // print the first 4 characters
+ * console.log(new CString(ptr, 0, 4));
+ * ```
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+ export interface CString {
+ /**
+ * Get a string from a UTF-8 encoded C string
+ * If `byteLength` is not provided, the string is assumed to be null-terminated.
+ *
+ * @param ptr The pointer to the C string
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * console.log(new CString(ptr));
+ * ```
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * // print the first 4 characters
+ * console.log(new CString(ptr, 0, 4));
+ * ```
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+ new (ptr: number, byteOffset?: number, byteLength?: number): string;
+ }
+
+ /**
+ * View the generated C code for FFI bindings
+ *
+ * You probably won't need this unless there's a bug in the FFI bindings
+ * generator or you're just curious.
+ */
+ export function viewSource(symbols: Symbols): string[];
+}
+
+
// ./fs.d.ts
/**
@@ -4664,6 +4893,7 @@ declare module "node:fs" {
export = fs;
}
+
// ./html-rewriter.d.ts
declare namespace HTMLRewriterTypes {
@@ -4780,9 +5010,10 @@ declare class HTMLRewriter {
transform(input: Response): Response;
}
+
// ./globals.d.ts
-type Encoding = "utf8" | "utf-8" | "windows-1252" | "utf-16";
+type Encoding = "utf-8" | "windows-1252" | "utf-16";
interface console {
assert(condition?: boolean, ...data: any[]): void;
@@ -6208,6 +6439,7 @@ declare var Loader: {
resolveSync: (specifier: string, from: string) => string;
};
+
// ./path.d.ts
/**
@@ -6415,6 +6647,7 @@ declare module "node:path/win32" {
export * from "path/win32";
}
+
// ./bun-test.d.ts
/**
@@ -6453,3 +6686,4 @@ declare module "test" {
import BunTestModule = require("bun:test");
export = BunTestModule;
}
+
diff --git a/src/javascript/jsc/api/FFI.h b/src/javascript/jsc/api/FFI.h
index d5ccf3152..10552b604 100644
--- a/src/javascript/jsc/api/FFI.h
+++ b/src/javascript/jsc/api/FFI.h
@@ -18,6 +18,7 @@
/* 7.18.1.1 Exact-width integer types */
typedef signed char int8_t;
typedef unsigned char uint8_t;
+typedef char int8_t;
typedef short int16_t;
typedef unsigned short uint16_t;
typedef int int32_t;
diff --git a/src/javascript/jsc/api/bun.zig b/src/javascript/jsc/api/bun.zig
index 34e9b7645..1481bb689 100644
--- a/src/javascript/jsc/api/bun.zig
+++ b/src/javascript/jsc/api/bun.zig
@@ -77,6 +77,7 @@ const VirtualMachine = @import("../javascript.zig").VirtualMachine;
const IOTask = JSC.IOTask;
const is_bindgen = JSC.is_bindgen;
+const max_addressible_memory = std.math.maxInt(u56);
threadlocal var css_imports_list_strings: [512]ZigString = undefined;
threadlocal var css_imports_list: [512]Api.StringPointer = undefined;
@@ -1121,12 +1122,6 @@ pub const Class = NewClass(
.sha = .{
.rfn = JSC.wrapWithHasContainer(Crypto.SHA512_256, "hash", false, false),
},
- .dlprint = .{
- .rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
- },
- .dlopen = .{
- .rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
- },
},
.{
.main = .{
@@ -1208,6 +1203,9 @@ pub const Class = NewClass(
.SHA512_256 = .{
.get = Crypto.SHA512_256.getter,
},
+ .FFI = .{
+ .get = FFI.getter,
+ },
},
);
@@ -1862,112 +1860,10 @@ pub const Unsafe = struct {
.arrayBufferToString = .{
.rfn = arrayBufferToString,
},
- .arrayBufferToPtr = .{
- .rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferToPtr", false, false),
- },
- .arrayBufferFromPtr = .{
- .rfn = JSC.wrapWithHasContainer(Unsafe, "arrayBufferFromPtr", false, false),
- },
- .bufferFromPtr = .{
- .rfn = JSC.wrapWithHasContainer(Unsafe, "bufferFromPtr", false, false),
- },
},
.{},
);
- const ValueOrError = union(enum) {
- err: JSValue,
- slice: []u8,
- };
-
- pub fn arrayBufferToPtr(globalThis: *JSGlobalObject, value: JSValue) JSValue {
- if (value.isEmpty()) {
- return JSC.JSValue.jsNull();
- }
-
- const array_buffer = value.asArrayBuffer(globalThis) orelse {
- return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
- };
-
- if (array_buffer.len == 0) {
- return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
- }
-
- return JSC.JSValue.jsNumber(@bitCast(f64, @ptrToInt(array_buffer.ptr)));
- }
-
- fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, valueLength: JSValue) ValueOrError {
- if (!value.isNumber()) {
- return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
- }
-
- const num = value.asNumber();
- if (num == 0) {
- return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
- }
-
- if (!std.math.isFinite(num)) {
- return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
- }
-
- const addr = @bitCast(usize, num);
-
- if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
- return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
- }
-
- if (!valueLength.isNumber()) {
- return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
- }
-
- if (valueLength.asNumber() == 0.0) {
- return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
- }
-
- const length_i = valueLength.toInt64();
- if (length_i < 0) {
- return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
- }
-
- if (length_i > std.math.maxInt(u48)) {
- return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
- }
-
- const length = @intCast(usize, length_i);
-
- return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
- }
-
- pub fn arrayBufferFromPtr(
- globalThis: *JSGlobalObject,
- value: JSValue,
- valueLength: JSValue,
- ) JSC.JSValue {
- switch (getPtrSlice(globalThis, value, valueLength)) {
- .err => |erro| {
- return erro;
- },
- .slice => |slice| {
- return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
- },
- }
- }
-
- pub fn bufferFromPtr(
- globalThis: *JSGlobalObject,
- value: JSValue,
- valueLength: JSValue,
- ) JSC.JSValue {
- switch (getPtrSlice(globalThis, value, valueLength)) {
- .err => |erro| {
- return erro;
- },
- .slice => |slice| {
- return JSC.JSValue.createBuffer(globalThis, slice, null);
- },
- }
- }
-
// For testing the segfault handler
pub fn __debug__doSegfault(
_: void,
@@ -2353,6 +2249,265 @@ pub const Timer = struct {
}
};
+pub const FFI = struct {
+ pub const Class = NewClass(
+ void,
+ .{
+ .name = "FFI",
+ },
+ .{
+ .viewSource = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "print", false, false),
+ },
+ .dlopen = .{
+ .rfn = JSC.wrapWithHasContainer(JSC.FFI, "open", false, false),
+ },
+ .ptr = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "ptr", false, false),
+ },
+ .toBuffer = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "toBuffer", false, false),
+ },
+ .toArrayBuffer = .{
+ .rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false),
+ },
+ },
+ .{
+ .CString = .{
+ .get = UnsafeCString.getter,
+ },
+ },
+ );
+
+ pub fn ptr(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ ) JSValue {
+ if (value.isEmpty()) {
+ return JSC.JSValue.jsNull();
+ }
+
+ const array_buffer = value.asArrayBuffer(globalThis) orelse {
+ return JSC.toInvalidArguments("Expected ArrayBufferView", .{}, globalThis.ref());
+ };
+
+ if (array_buffer.len == 0) {
+ return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis.ref());
+ }
+
+ var addr: usize = @ptrToInt(array_buffer.ptr);
+
+ if (byteOffset) |off| {
+ if (!off.isEmptyOrUndefinedOrNull()) {
+ if (!off.isNumber()) {
+ return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref());
+ }
+ }
+
+ const bytei64 = off.toInt64();
+ if (bytei64 < 0) {
+ addr -|= @intCast(usize, bytei64 * -1);
+ } else {
+ addr += @intCast(usize, bytei64);
+ }
+
+ if (addr > @ptrToInt(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
+ return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis.ref());
+ }
+ }
+
+ if (addr > max_addressible_memory) {
+ return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis.ref());
+ }
+
+ if (addr == 0) {
+ return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis.ref());
+ }
+
+ if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
+ return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref());
+ }
+
+ // truncate to 56 bits to clear any pointer tags
+ return JSC.JSValue.jsNumber(@bitCast(f64, @as(usize, @truncate(u56, addr))));
+ }
+
+ const ValueOrError = union(enum) {
+ err: JSValue,
+ slice: []u8,
+ };
+
+ pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
+ if (!value.isNumber()) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis.ref()) };
+ }
+
+ const num = value.asNumber();
+ if (num == 0) {
+ return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (!std.math.isFinite(num)) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
+ }
+
+ var addr = @bitCast(usize, num);
+
+ if (byteOffset) |byte_off| {
+ if (byte_off.isNumber()) {
+ const off = byte_off.toInt64();
+ if (off < 0) {
+ addr -|= @intCast(usize, off * -1);
+ } else {
+ addr +|= @intCast(usize, off);
+ }
+
+ if (addr == 0) {
+ return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (!std.math.isFinite(byte_off.asNumber())) {
+ return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis.ref()) };
+ }
+ } else if (!byte_off.isEmptyOrUndefinedOrNull()) {
+ // do nothing
+ } else {
+ return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis.ref()) };
+ }
+ }
+
+ if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
+ return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis.ref()) };
+ }
+
+ if (byteLength) |valueLength| {
+ if (!valueLength.isEmptyOrUndefinedOrNull()) {
+ if (!valueLength.isNumber()) {
+ return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis.ref()) };
+ }
+
+ if (valueLength.asNumber() == 0.0) {
+ return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ const length_i = valueLength.toInt64();
+ if (length_i < 0) {
+ return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ if (length_i > max_addressible_memory) {
+ return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis.ref()) };
+ }
+
+ const length = @intCast(usize, length_i);
+ return .{ .slice = @intToPtr([*]u8, addr)[0..length] };
+ }
+ }
+
+ return .{ .slice = std.mem.sliceTo(@intToPtr([*:0]u8, addr), 0) };
+ }
+
+ pub fn toArrayBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis.ref(), null, null, null);
+ },
+ }
+ }
+
+ pub fn toBuffer(
+ globalThis: *JSGlobalObject,
+ value: JSValue,
+ byteOffset: ?JSValue,
+ valueLength: ?JSValue,
+ ) JSC.JSValue {
+ switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
+ .err => |erro| {
+ return erro;
+ },
+ .slice => |slice| {
+ return JSC.JSValue.createBuffer(globalThis, slice, null);
+ },
+ }
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("FFI"));
+ if (existing.isEmpty()) {
+ var prototype = JSC.C.JSObjectMake(ctx, FFI.Class.get().?[0], null);
+ var base = JSC.C.JSObjectMake(ctx, null, null);
+ JSC.C.JSObjectSetPrototype(ctx, base, prototype);
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("FFI"),
+ JSValue.fromRef(base),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+};
+
+pub const UnsafeCString = struct {
+ pub fn constructor(
+ ctx: js.JSContextRef,
+ _: js.JSObjectRef,
+ len: usize,
+ args: [*c]const js.JSValueRef,
+ exception: js.ExceptionRef,
+ ) callconv(.C) js.JSObjectRef {
+ if (len == 0) {
+ JSC.throwInvalidArguments("Expected a ptr", .{}, ctx, exception);
+ return null;
+ }
+
+ return newCString(ctx.ptr(), JSC.JSValue.fromRef(args[0]), if (len > 1) JSC.JSValue.fromRef(args[1]) else null, if (len > 2) JSC.JSValue.fromRef(args[2]) else null).asObjectRef();
+ }
+
+ pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
+ switch (FFI.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
+ .err => |err| {
+ return err;
+ },
+ .slice => |slice| {
+ return WebCore.Encoder.toString(slice.ptr, slice.len, globalThis, .utf8);
+ },
+ }
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ var existing = ctx.ptr().getCachedObject(&ZigString.init("UnsafeCString"));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init("UnsafeCString"),
+ JSValue.fromRef(JSC.C.JSObjectMakeConstructor(ctx, null, constructor)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+};
+
/// EnvironmentVariables is runtime defined.
/// Also, you can't iterate over process.env normally since it only exists at build-time otherwise
// This is aliased to Bun.env
diff --git a/src/javascript/jsc/api/ffi.zig b/src/javascript/jsc/api/ffi.zig
index ab866232b..45893eb7d 100644
--- a/src/javascript/jsc/api/ffi.zig
+++ b/src/javascript/jsc/api/ffi.zig
@@ -98,12 +98,14 @@ pub const FFI = struct {
this.closed = true;
this.dylib.close();
+ const allocator = VirtualMachine.vm.allocator;
+
for (this.functions.values()) |*val| {
- VirtualMachine.vm.allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
+ allocator.free(bun.constStrToU8(std.mem.span(val.base_name)));
- val.arg_types.deinit(VirtualMachine.vm.allocator);
+ val.arg_types.deinit(allocator);
}
- this.functions.deinit(VirtualMachine.vm.allocator);
+ this.functions.deinit(allocator);
return JSC.JSValue.jsUndefined();
}
@@ -312,9 +314,28 @@ pub const FFI = struct {
try abi_types.ensureTotalCapacityPrecise(allocator, array.len);
while (array.next()) |val| {
- if (val.isEmptyOrUndefinedOrNull() or !val.jsType().isStringLike()) {
+ if (val.isEmptyOrUndefinedOrNull()) {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
+ }
+
+ if (val.isAnyInt()) {
+ const int = val.toInt32();
+ switch (int) {
+ 0...13 => {
+ abi_types.appendAssumeCapacity(@intToEnum(ABIType, int));
+ continue;
+ },
+ else => {
+ abi_types.clearAndFree(allocator);
+ return ZigString.init("invalid ABI type").toErrorInstance(global);
+ },
+ }
+ }
+
+ if (!val.jsType().isStringLike()) {
abi_types.clearAndFree(allocator);
- return ZigString.init("param must be a string (type name)").toErrorInstance(global);
+ return ZigString.init("param must be a string (type name) or number").toErrorInstance(global);
}
var type_name = val.toSlice(global, allocator);
@@ -581,39 +602,78 @@ pub const FFI = struct {
@"void" = 13,
- pub const label = ComptimeStringMap(ABIType, .{
- .{ "bool", .bool },
- .{ "c_int", .int32_t },
- .{ "c_uint", .uint32_t },
- .{ "char", .char },
- .{ "char*", .ptr },
- .{ "double", .double },
- .{ "f32", .float },
- .{ "f64", .double },
- .{ "float", .float },
- .{ "i16", .int16_t },
- .{ "i32", .int32_t },
- .{ "i64", .int64_t },
- .{ "i8", .int8_t },
- .{ "int", .int32_t },
- .{ "int16_t", .int16_t },
- .{ "int32_t", .int32_t },
- .{ "int64_t", .int64_t },
- .{ "int8_t", .int8_t },
- .{ "isize", .int64_t },
- .{ "u16", .uint16_t },
- .{ "u32", .uint32_t },
- .{ "u64", .uint64_t },
- .{ "u8", .uint8_t },
- .{ "uint16_t", .uint16_t },
- .{ "uint32_t", .uint32_t },
- .{ "uint64_t", .uint64_t },
- .{ "uint8_t", .uint8_t },
- .{ "usize", .uint64_t },
- .{ "void*", .ptr },
- .{ "ptr", .ptr },
- .{ "pointer", .ptr },
- });
+ const map = .{
+ .{ "bool", ABIType.bool },
+ .{ "c_int", ABIType.int32_t },
+ .{ "c_uint", ABIType.uint32_t },
+ .{ "char", ABIType.char },
+ .{ "char*", ABIType.ptr },
+ .{ "double", ABIType.double },
+ .{ "f32", ABIType.float },
+ .{ "f64", ABIType.double },
+ .{ "float", ABIType.float },
+ .{ "i16", ABIType.int16_t },
+ .{ "i32", ABIType.int32_t },
+ .{ "i64", ABIType.int64_t },
+ .{ "i8", ABIType.int8_t },
+ .{ "int", ABIType.int32_t },
+ .{ "int16_t", ABIType.int16_t },
+ .{ "int32_t", ABIType.int32_t },
+ .{ "int64_t", ABIType.int64_t },
+ .{ "int8_t", ABIType.int8_t },
+ .{ "isize", ABIType.int64_t },
+ .{ "u16", ABIType.uint16_t },
+ .{ "u32", ABIType.uint32_t },
+ .{ "u64", ABIType.uint64_t },
+ .{ "u8", ABIType.uint8_t },
+ .{ "uint16_t", ABIType.uint16_t },
+ .{ "uint32_t", ABIType.uint32_t },
+ .{ "uint64_t", ABIType.uint64_t },
+ .{ "uint8_t", ABIType.uint8_t },
+ .{ "usize", ABIType.uint64_t },
+ .{ "void*", ABIType.ptr },
+ .{ "ptr", ABIType.ptr },
+ .{ "pointer", ABIType.ptr },
+ };
+ pub const label = ComptimeStringMap(ABIType, map);
+ const EnumMapFormatter = struct {
+ name: []const u8,
+ entry: ABIType,
+ pub fn format(self: EnumMapFormatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
+ try writer.writeAll("['");
+ // these are not all valid identifiers
+ try writer.writeAll(self.name);
+ try writer.writeAll("']:");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ try writer.writeAll(",'");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ try writer.writeAll("':");
+ try std.fmt.formatInt(@enumToInt(self.entry), 10, .lower, .{}, writer);
+ }
+ };
+ pub const map_to_js_object = brk: {
+ var count: usize = 2;
+ for (map) |item, i| {
+ var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
+ count += std.fmt.count("{}", .{fmt});
+ count += @boolToInt(i > 0);
+ }
+
+ var buf: [count]u8 = undefined;
+ buf[0] = '{';
+ buf[buf.len - 1] = '}';
+ var end: usize = 1;
+ for (map) |item, i| {
+ var fmt = EnumMapFormatter{ .name = item.@"0", .entry = item.@"1" };
+ if (i > 0) {
+ buf[end] = ',';
+ end += 1;
+ }
+ end += (std.fmt.bufPrint(buf[end..], "{}", .{fmt}) catch unreachable).len;
+ }
+
+ break :brk buf;
+ };
pub fn isFloatingPoint(this: ABIType) bool {
return switch (this) {
diff --git a/src/javascript/jsc/base.zig b/src/javascript/jsc/base.zig
index e2d57b9ca..430d60253 100644
--- a/src/javascript/jsc/base.zig
+++ b/src/javascript/jsc/base.zig
@@ -2882,6 +2882,9 @@ pub fn wrapWithHasContainer(
};
args[i] = val;
},
+ ?JSValue => {
+ args[i] = iter.protectEatNext();
+ },
else => @compileError("Unexpected Type " ++ @typeName(ArgType)),
}
}
@@ -2921,3 +2924,51 @@ pub fn wrapWithHasContainer(
}
}.callback;
}
+
+pub fn cachedBoundFunction(comptime name: [:0]const u8, comptime callback: anytype) (fn (
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+) js.JSValueRef) {
+ return struct {
+ const name_ = name;
+ pub fn call(
+ arg2: js.JSContextRef,
+ arg3: js.JSObjectRef,
+ arg4: js.JSObjectRef,
+ arg5: usize,
+ arg6: [*c]const js.JSValueRef,
+ arg7: js.ExceptionRef,
+ ) callconv(.C) js.JSObjectRef {
+ return callback(
+ {},
+ arg2,
+ arg3,
+ arg4,
+ arg6[0..arg5],
+ arg7,
+ );
+ }
+
+ pub fn getter(
+ _: void,
+ ctx: js.JSContextRef,
+ _: js.JSValueRef,
+ _: js.JSStringRef,
+ _: js.ExceptionRef,
+ ) js.JSValueRef {
+ const name_slice = std.mem.span(name_);
+ var existing = ctx.ptr().getCachedObject(&ZigString.init(name_slice));
+ if (existing.isEmpty()) {
+ return ctx.ptr().putCachedObject(
+ &ZigString.init(name_slice),
+ JSValue.fromRef(JSC.C.JSObjectMakeFunctionWithCallback(ctx, JSC.C.JSStringCreateStatic(name_slice.ptr, name_slice.len), call)),
+ ).asObjectRef();
+ }
+
+ return existing.asObjectRef();
+ }
+ }.getter;
+}
diff --git a/src/javascript/jsc/ffi.exports.js b/src/javascript/jsc/ffi.exports.js
new file mode 100644
index 000000000..59af47d4f
--- /dev/null
+++ b/src/javascript/jsc/ffi.exports.js
@@ -0,0 +1,7 @@
+export const ptr = globalThis.Bun.FFI.ptr;
+export const toBuffer = globalThis.Bun.FFI.toBuffer;
+export const toArrayBuffer = globalThis.Bun.FFI.toArrayBuffer;
+export const CString = globalThis.Bun.FFI.CString;
+export const dlopen = globalThis.Bun.FFI.dlopen;
+export const viewSource = globalThis.Bun.FFI.viewSource;
+// --- FFIType ---
diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig
index 009ef160d..5996f5808 100644
--- a/src/javascript/jsc/javascript.zig
+++ b/src/javascript/jsc/javascript.zig
@@ -1078,6 +1078,14 @@ pub const VirtualMachine = struct {
.source_url = ZigString.init("node:path"),
.hash = 0,
};
+ } else if (strings.eqlComptime(_specifier, "bun:ffi")) {
+ return ResolvedSource{
+ .allocator = null,
+ .source_code = ZigString.init(@embedFile("ffi.exports.js") ++ "export const FFIType = " ++ JSC.FFI.ABIType.map_to_js_object ++ ";\n"),
+ .specifier = ZigString.init("bun:ffi"),
+ .source_url = ZigString.init("bun:ffi"),
+ .hash = 0,
+ };
}
const specifier = normalizeSpecifier(_specifier);
@@ -1303,11 +1311,14 @@ pub const VirtualMachine = struct {
ret.result = null;
ret.path = "node:fs";
return;
- }
- if (strings.eqlComptime(specifier, "node:path")) {
+ } else if (strings.eqlComptime(specifier, "node:path")) {
ret.result = null;
ret.path = "node:path";
return;
+ } else if (strings.eqlComptime(specifier, "bun:ffi")) {
+ ret.result = null;
+ ret.path = "bun:ffi";
+ return;
}
const is_special_source = strings.eqlComptime(source, main_file_name) or js_ast.Macro.isMacroPath(source);
diff --git a/src/linker.zig b/src/linker.zig
index 9adccff33..55e367dcb 100644
--- a/src/linker.zig
+++ b/src/linker.zig
@@ -318,12 +318,15 @@ pub const Linker = struct {
}
}
- if (import_record.path.text.len > 4 and strings.eqlComptime(import_record.path.text[0.."bun:".len], "bun:")) {
+ if (import_record.path.text.len > 4 and strings.eqlComptimeIgnoreLen(import_record.path.text[0.."bun:".len], "bun:")) {
import_record.path = Fs.Path.init(import_record.path.text["bun:".len..]);
import_record.path.namespace = "bun";
- if (strings.eqlComptime(import_record.path.text, "test")) {
+ if (strings.eqlComptime(import_record.path.text, "ffi")) {
+ import_record.path.text = "bun:ffi";
+ } else if (strings.eqlComptime(import_record.path.text, "test")) {
import_record.tag = .bun_test;
}
+
// don't link bun
continue;
}
diff --git a/types/bun/bun.d.ts b/types/bun/bun.d.ts
index da4aa796e..4f68a34f6 100644
--- a/types/bun/bun.d.ts
+++ b/types/bun/bun.d.ts
@@ -986,6 +986,8 @@ declare module "bun" {
*/
static readonly byteLength: 32;
}
+
+ export const FFI: typeof import("bun:ffi");
}
type TypedArray =
diff --git a/types/bun/ffi.d.ts b/types/bun/ffi.d.ts
new file mode 100644
index 000000000..16a197643
--- /dev/null
+++ b/types/bun/ffi.d.ts
@@ -0,0 +1,523 @@
+/**
+ * `bun:ffi` lets you efficiently call C functions & FFI functions from JavaScript
+ * without writing any C code yourself.
+ *
+ * ```js
+ * import {dlopen, CString, ptr} from 'bun:ffi';
+ *
+ * const lib = dlopen('libsqlite3', {});
+ *
+ *
+ * ```
+ *
+ * This is powered by just-in-time compiling C wrappers
+ * that convert JavaScript types to C types and back. Internally,
+ * bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
+ * goes to Fabrice Bellard and TinyCC maintainers for making this possible.
+ *
+ */
+declare module "bun:ffi" {
+ export enum FFIType {
+ char = 0,
+ /**
+ * 8-bit signed integer
+ *
+ * Must be a value between -127 and 127
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * signed char
+ * char // on x64 & aarch64 macOS
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ int8_t = 1,
+ /**
+ * 8-bit signed integer
+ *
+ * Must be a value between -127 and 127
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * signed char
+ * char // on x64 & aarch64 macOS
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ i8 = 1,
+
+ /**
+ * 8-bit unsigned integer
+ *
+ * Must be a value between 0 and 255
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * unsigned char
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ uint8_t = 2,
+ /**
+ * 8-bit unsigned integer
+ *
+ * Must be a value between 0 and 255
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * unsigned char
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ u8 = 2,
+
+ /**
+ * 16-bit signed integer
+ *
+ * Must be a value between -32768 and 32767
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * in16_t
+ * short // on arm64 & x64
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ int16_t = 3,
+ /**
+ * 16-bit signed integer
+ *
+ * Must be a value between -32768 and 32767
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * in16_t
+ * short // on arm64 & x64
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ i16 = 3,
+
+ /**
+ * 16-bit unsigned integer
+ *
+ * Must be a value between 0 and 65535, inclusive.
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * uint16_t
+ * unsigned short // on arm64 & x64
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ uint16_t = 4,
+ /**
+ * 16-bit unsigned integer
+ *
+ * Must be a value between 0 and 65535, inclusive.
+ *
+ * When passing to a FFI function (C ABI), type coercsion is not performed.
+ *
+ * In C:
+ * ```c
+ * uint16_t
+ * unsigned short // on arm64 & x64
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * var num = 0;
+ * ```
+ */
+ u16 = 4,
+
+ /**
+ * 32-bit signed integer
+ *
+ */
+ int32_t = 5,
+
+ /**
+ * 32-bit signed integer
+ *
+ * Alias of {@link FFIType.int32_t}
+ */
+ i32 = 5,
+ /**
+ * 32-bit signed integer
+ *
+ * The same as `int` in C
+ *
+ * ```c
+ * int
+ * ```
+ */
+ int = 5,
+
+ /**
+ * 32-bit unsigned integer
+ *
+ * The same as `unsigned int` in C (on x64 & arm64)
+ *
+ * C:
+ * ```c
+ * unsigned int
+ * ```
+ * JavaScript:
+ * ```js
+ * ptr(new Uint32Array(1))
+ * ```
+ */
+ uint32_t = 6,
+ /**
+ * 32-bit unsigned integer
+ *
+ * Alias of {@link FFIType.uint32_t}
+ */
+ u32 = 6,
+
+ /**
+ * int64 is a 64-bit signed integer
+ *
+ * This is not implemented yet!
+ */
+ int64_t = 7,
+ /**
+ * i64 is a 64-bit signed integer
+ *
+ * This is not implemented yet!
+ */
+ i64 = 7,
+
+ /**
+ * 64-bit unsigned integer
+ *
+ * This is not implemented yet!
+ */
+ uint64_t = 8,
+ /**
+ * 64-bit unsigned integer
+ *
+ * This is not implemented yet!
+ */
+ u64 = 8,
+
+ /**
+ * Doubles are not supported yet!
+ */
+ double = 9,
+ /**
+ * Doubles are not supported yet!
+ */
+ f64 = 9,
+ /**
+ * Floats are not supported yet!
+ */
+ float = 10,
+ /**
+ * Floats are not supported yet!
+ */
+ f32 = 10,
+
+ /**
+ * Booelan value
+ *
+ * Must be `true` or `false`. `0` and `1` type coercion is not supported.
+ *
+ * In C, this corresponds to:
+ * ```c
+ * bool
+ * _Bool
+ * ```
+ *
+ *
+ */
+ bool = 11,
+
+ /**
+ * Pointer value
+ *
+ * See {@link Bun.FFI.ptr} for more information
+ *
+ * In C:
+ * ```c
+ * void*
+ * ```
+ *
+ * In JavaScript:
+ * ```js
+ * ptr(new Uint8Array(1))
+ * ```
+ */
+ ptr = 12,
+ /**
+ * Pointer value
+ *
+ * alias of {@link FFIType.ptr}
+ */
+ pointer = 12,
+
+ /**
+ * void value
+ *
+ * void arguments are not supported
+ *
+ * void return type is the default return type
+ *
+ * In C:
+ * ```c
+ * void
+ * ```
+ *
+ */
+ void = 13,
+ }
+
+ type Symbols = Record<
+ string,
+ {
+ /**
+ * Arguments to a FFI function (C ABI)
+ *
+ * Defaults to an empty array, which means no arguments.
+ *
+ * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
+ *
+ * @example
+ * From JavaScript:
+ * ```js
+ * const lib = dlopen('add', {
+ * // FFIType can be used or you can pass string labels.
+ * args: [FFIType.i32, "i32"],
+ * return_type: "i32",
+ * });
+ * lib.symbols.add(1, 2)
+ * ```
+ * In C:
+ * ```c
+ * int add(int a, int b) {
+ * return a + b;
+ * }
+ * ```
+ */
+ args?: FFIType[];
+ /**
+ * Return type to a FFI function (C ABI)
+ *
+ * Defaults to an empty array, which means no arguments.
+ *
+ * To pass a pointer, use "ptr" or "pointer" as the type name. To get a pointer, see {@link ptr}.
+ *
+ * @example
+ * From JavaScript:
+ * ```js
+ * const lib = dlopen('add', {
+ * // FFIType can be used or you can pass string labels.
+ * args: [FFIType.i32, "i32"],
+ * return_type: "i32",
+ * });
+ * lib.symbols.add(1, 2)
+ * ```
+ * In C:
+ * ```c
+ * int add(int a, int b) {
+ * return a + b;
+ * }
+ * ```
+ */
+ return_type?: FFIType;
+ }
+ >;
+
+ export interface Library {
+ symbols: Record<string, CallableFunction>;
+
+ /**
+ * `dlclose` the library, unloading the symbols and freeing memory allocated.
+ *
+ * Once called, the library is no longer usable.
+ *
+ * Calling a function from a library that has been closed is undefined behavior.
+ */
+ close(): void;
+ }
+
+ export function dlopen(libraryName: string, symbols: Symbols): Library<T>;
+
+ /**
+ * Read a pointer as a {@link Buffer}
+ *
+ * If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
+ *
+ * @param ptr The memory address to read
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ *
+ */
+ export function toBuffer(
+ ptr: number,
+ byteOffset?: number,
+ byteLength?: number
+ ): Buffer;
+
+ /**
+ * Read a pointer as an {@link ArrayBuffer}
+ *
+ * If `byteLength` is not provided, the pointer is assumed to be 0-terminated.
+ *
+ * @param ptr The memory address to read
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+ export function toArrayBuffer(
+ ptr: number,
+ byteOffset?: number,
+ byteLength?: number
+ ): ArrayBuffer;
+
+ /**
+ * Get the pointer backing a {@link TypedArray} or {@link ArrayBuffer}
+ *
+ * Use this to pass {@link TypedArray} or {@link ArrayBuffer} to C functions.
+ *
+ * This is for use with FFI functions. For performance reasons, FFI will
+ * not automatically convert typed arrays to C pointers.
+ *
+ * @param {TypedArray|ArrayBuffer|DataView} view the typed array or array buffer to get the pointer for
+ * @param {number} byteOffset optional offset into the view in bytes
+ *
+ * @example
+ *
+ * From JavaScript:
+ * ```js
+ * const array = new Uint8Array(10);
+ * const rawPtr = ptr(array);
+ * myCFunction(rawPtr);
+ * ```
+ * To C:
+ * ```c
+ * void myCFunction(char* rawPtr) {
+ * // Do something with rawPtr
+ * }
+ * ```
+ *
+ */
+ export function ptr(
+ view: TypedArray | ArrayBufferLike | DataView,
+ byteOffset?: number
+ ): number;
+
+ /**
+ * Get a string from a UTF-8 encoded C string
+ * If `byteLength` is not provided, the string is assumed to be null-terminated.
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * console.log(new CString(ptr));
+ * ```
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * // print the first 4 characters
+ * console.log(new CString(ptr, 0, 4));
+ * ```
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+
+ export class CString extends String {
+ /**
+ * Get a string from a UTF-8 encoded C string
+ * If `byteLength` is not provided, the string is assumed to be null-terminated.
+ *
+ * @param ptr The pointer to the C string
+ * @param byteOffset bytes to skip before reading
+ * @param byteLength bytes to read
+ *
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * console.log(new CString(ptr));
+ * ```
+ *
+ * @example
+ * ```js
+ * var ptr = lib.symbols.getVersion();
+ * // print the first 4 characters
+ * console.log(new CString(ptr, 0, 4));
+ * ```
+ *
+ * While there are some checks to catch invalid pointers, this is a difficult
+ * thing to do safely. Passing an invalid pointer can crash the program and
+ * reading beyond the bounds of the pointer will crash the program or cause
+ * undefined behavior. Use with care!
+ */
+ constructor(ptr: number, byteOffset?: number, byteLength?: number): string;
+ }
+
+ /**
+ * View the generated C code for FFI bindings
+ *
+ * You probably won't need this unless there's a bug in the FFI bindings
+ * generator or you're just curious.
+ */
+ export function viewSource(symbols: Symbols): string[];
+}
diff --git a/types/bun/index.d.ts b/types/bun/index.d.ts
index 032c360f4..7cb0c4934 100644
--- a/types/bun/index.d.ts
+++ b/types/bun/index.d.ts
@@ -5,6 +5,7 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference path="./bun.d.ts" />
+/// <reference path="./ffi.d.ts" />
/// <reference path="./fs.d.ts" />
/// <reference path="./html-rewriter.d.ts" />
/// <reference path="./globals.d.ts" />
diff --git a/types/bun/paths.txt b/types/bun/paths.txt
index ffc59bdc7..a7647de9e 100644
--- a/types/bun/paths.txt
+++ b/types/bun/paths.txt
@@ -1,4 +1,5 @@
./bun.d.ts
+./ffi.d.ts
./fs.d.ts
./html-rewriter.d.ts
./globals.d.ts