mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Add docs for FFI
This commit is contained in:
9
Makefile
9
Makefile
@@ -12,7 +12,7 @@ endif
|
||||
MIN_MACOS_VERSION = 10.14
|
||||
|
||||
|
||||
MARCH_NATIVE =
|
||||
MARCH_NATIVE = -mtune=native
|
||||
|
||||
ARCH_NAME :=
|
||||
DOCKER_BUILDARCH =
|
||||
@@ -21,11 +21,12 @@ ifeq ($(ARCH_NAME_RAW),arm64)
|
||||
DOCKER_BUILDARCH = arm64
|
||||
BREW_PREFIX_PATH = /opt/homebrew
|
||||
MIN_MACOS_VERSION = 11.0
|
||||
MARCH_NATIVE = -mtune=native
|
||||
else
|
||||
ARCH_NAME = x64
|
||||
DOCKER_BUILDARCH = amd64
|
||||
BREW_PREFIX_PATH = /usr/local
|
||||
MARCH_NATIVE = -march=native
|
||||
MARCH_NATIVE = -march=native -mtune=native
|
||||
endif
|
||||
|
||||
AR=
|
||||
@@ -102,7 +103,7 @@ LIBICONV_PATH =
|
||||
AR=llvm-ar-13
|
||||
endif
|
||||
|
||||
OPTIMIZATION_LEVEL=-O3
|
||||
OPTIMIZATION_LEVEL=-O3 $(MARCH_NATIVE)
|
||||
CFLAGS = $(MACOS_MIN_FLAG) $(MARCH_NATIVE) $(BITCODE_OR_SECTIONS) -g $(OPTIMIZATION_LEVEL) -fno-exceptions -fvisibility=hidden -fvisibility-inlines-hidden
|
||||
BUN_TMP_DIR := /tmp/make-bun
|
||||
BUN_DEPLOY_DIR = /tmp/bun-v$(PACKAGE_JSON_VERSION)/$(PACKAGE_NAME)
|
||||
@@ -416,7 +417,7 @@ boringssl: boringssl-build boringssl-copy
|
||||
boringssl-debug: boringssl-build-debug boringssl-copy
|
||||
|
||||
compile-ffi-test:
|
||||
clang -O3 -shared -undefined dynamic_lookup -o /tmp/bun-ffi-test$(SHARED_LIB_EXTENSION) ./integration/bunjs-only-snippets/ffi-test.c
|
||||
clang $(OPTIMIZATION_LEVEL) -shared -undefined dynamic_lookup -o /tmp/bun-ffi-test$(SHARED_LIB_EXTENSION) ./integration/bunjs-only-snippets/ffi-test.c
|
||||
|
||||
libbacktrace:
|
||||
cd $(BUN_DEPS_DIR)/libbacktrace && \
|
||||
|
||||
335
README.md
335
README.md
@@ -1549,6 +1549,341 @@ await Bun.write(Bun.file("index.html"), await fetch("http://example.com"));
|
||||
await Bun.write("output.txt", Bun.file("input.txt"));
|
||||
```
|
||||
|
||||
### `bun:ffi` (Foreign Functions Interface)
|
||||
|
||||
`bun:ffi` lets you efficiently call native libraries from JavaScript. It works with languages that support the C ABI (Zig, Rust, C/C++, C#, Nim, Kotlin, etc).
|
||||
|
||||
Note: this is available in the next version of Bun (v0.0.79), which is not released yet.
|
||||
|
||||
This snippet prints sqlite3's version number:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, suffix } from "bun:ffi";
|
||||
|
||||
// `suffix` is either "dylib", "so", or "dll" depending on the platform
|
||||
// you don't have to use "suffix", it's just there for convenience
|
||||
const path = `libsqlite3.${suffix}`;
|
||||
|
||||
const {
|
||||
symbols: {
|
||||
// sqlite3_libversion is the function we will call
|
||||
sqlite3_libversion,
|
||||
},
|
||||
} =
|
||||
// dlopen() expects:
|
||||
// 1. a library name or file path
|
||||
// 2. a map of symbols
|
||||
dlopen(path, {
|
||||
// `sqlite3_libversion` is a function that returns a string
|
||||
sqlite3_libversion: {
|
||||
// sqlite3_libversion takes no arguments
|
||||
args: [],
|
||||
// sqlite3_libversion returns a pointer to a string
|
||||
returns: FFIType.cstring,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`SQLite 3 version: ${sqlite3_libversion()}`);
|
||||
```
|
||||
|
||||
#### Low-overhead FFI
|
||||
|
||||
7ns to go from JavaScript <> native code with `bun:ffi` (on my machine, an M1X)
|
||||
|
||||
- 2x faster than napi (Node v17.7.1)
|
||||
- 75x faster than Deno v1.21.1
|
||||
|
||||
As measured in [this simple benchmark](./bench/ffi/plus100)
|
||||
|
||||
<img width="699" alt="image" src="https://user-images.githubusercontent.com/709451/166412310-df3df42c-68af-40f0-aa7f-fb72895df72d.png">
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Why is bun:ffi fast?</summary>
|
||||
|
||||
Bun generates & just-in-time compiles C bindings that efficiently convert values between JavaScript types and native types.
|
||||
|
||||
To compile C, Bun embeds [TinyCC](https://github.com/TinyCC/tinycc) a small and fast C compiler.
|
||||
|
||||
</details>
|
||||
|
||||
#### Usage
|
||||
|
||||
With Zig:
|
||||
|
||||
```zig
|
||||
// add.zig
|
||||
pub export fn add(a: i32, b: i32) i32 {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
|
||||
To compile:
|
||||
|
||||
```bash
|
||||
zig build-lib add.zig -dynamic -OReleaseFast
|
||||
```
|
||||
|
||||
Pass `dlopen` the path to the shared library and the list of symbols you want to import.
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, suffix } from "bun:ffi";
|
||||
|
||||
const path = `libadd.${suffix}`;
|
||||
|
||||
const lib = dlopen(path, {
|
||||
add: {
|
||||
args: [FFIType.i32, FFIType.i32],
|
||||
returns: FFIType.i32,
|
||||
},
|
||||
});
|
||||
|
||||
lib.symbols.add(1, 2);
|
||||
```
|
||||
|
||||
With Rust:
|
||||
|
||||
```rust
|
||||
// add.rs
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add(a: isize, b: isize) -> isize {
|
||||
a + b
|
||||
}
|
||||
```
|
||||
|
||||
To compile:
|
||||
|
||||
```bash
|
||||
rustc --crate-type cdylib add.rs
|
||||
```
|
||||
|
||||
#### Supported FFI types (`FFIType`)
|
||||
|
||||
| `FFIType` | C Type | Aliases |
|
||||
| --------- | ---------- | --------------------------- |
|
||||
| cstring | `char*` | |
|
||||
| ptr | `void*` | `pointer`, `void*`, `char*` |
|
||||
| i8 | `int8_t` | `int8_t` |
|
||||
| i16 | `int16_t` | `int16_t` |
|
||||
| i32 | `int32_t` | `int32_t`, `int` |
|
||||
| i64 | `int64_t` | `int32_t` |
|
||||
| u8 | `uint8_t` | `uint8_t` |
|
||||
| u16 | `uint16_t` | `uint16_t` |
|
||||
| u32 | `uint32_t` | `uint32_t` |
|
||||
| u64 | `uint64_t` | `uint32_t` |
|
||||
| f32 | `float` | `float` |
|
||||
| f64 | `double` | `double` |
|
||||
| bool | `bool` | |
|
||||
| char | `char` | |
|
||||
|
||||
#### Strings (`CString`)
|
||||
|
||||
JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.
|
||||
|
||||
<details>
|
||||
<summary>How are JavaScript strings and C strings different?</summary>
|
||||
|
||||
JavaScript strings:
|
||||
|
||||
- UTF16 (2 bytes per letter) or potentially latin1, depending on the JavaScript engine & what characters are used
|
||||
- `length` stored separately
|
||||
- Immutable
|
||||
|
||||
C strings:
|
||||
|
||||
- UTF8 (1 byte per letter), usually
|
||||
- The length is not stored. Instead, the string is null-terminated which means the length is the index of the first `\0` it finds
|
||||
- Mutable
|
||||
|
||||
</details>
|
||||
|
||||
To help with that, `bun:ffi` exports `CString` which extends JavaScript's builtin `String` with a few extras:
|
||||
|
||||
```ts
|
||||
class CString extends String {
|
||||
/**
|
||||
* Given a `ptr`, this will automatically search for the closing `\0` character and transcode from UTF-8 to UTF-16 if necessary.
|
||||
*/
|
||||
constructor(ptr: number, byteOffset?: number, byteLength?: number): string;
|
||||
|
||||
/**
|
||||
* The ptr to the C string
|
||||
*
|
||||
* This `CString` instance is a clone of the string, so it
|
||||
* is safe to continue using this instance after the `ptr` has been
|
||||
* freed.
|
||||
*/
|
||||
ptr: number;
|
||||
byteOffset?: number;
|
||||
byteLength?: number;
|
||||
}
|
||||
```
|
||||
|
||||
To convert from a 0-terminated pointer to a JavaScript string:
|
||||
|
||||
```ts
|
||||
const myString = new CString(ptr);
|
||||
```
|
||||
|
||||
To convert from a pointer with a known length to a JavaScript string:
|
||||
|
||||
```ts
|
||||
const myString = new CString(ptr, 0, byteLength);
|
||||
```
|
||||
|
||||
`new CString` clones the C string, so it is safe to continue using `myString` after `ptr` has been freed.
|
||||
|
||||
```ts
|
||||
my_library_free(myString.ptr);
|
||||
|
||||
// this is safe because myString is a clone
|
||||
console.log(myString);
|
||||
```
|
||||
|
||||
##### Returning a string
|
||||
|
||||
When used in `returns`, `FFIType.cstring` coerces the pointer to a JavaScript `string`. When used in `args`, `cstring` is identical to `ptr`.
|
||||
|
||||
#### Pointers
|
||||
|
||||
Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>How does a 64 bit pointer fit in a JavaScript number?</summary>
|
||||
|
||||
64-bit processors support up to [52 bits of addressible space](https://en.wikipedia.org/wiki/64-bit_computing#Limits_of_processors).
|
||||
|
||||
[JavaScript numbers](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) support 63 bits of usable space, so that leaves us with about 11 bits of extra space.
|
||||
|
||||
Why not `BigInt`?
|
||||
|
||||
`BigInt` is slower. JavaScript engines allocate a separate `BigInt` which means they can't just fit in a regular javascript value.
|
||||
|
||||
If you pass a `BigInt` to a function, it will be converted to a `number`
|
||||
|
||||
</details>
|
||||
|
||||
**To convert from a TypedArray to a pointer**:
|
||||
|
||||
```ts
|
||||
import { ptr } from "bun:ffi";
|
||||
var myTypedArray = new Uint8Array(32);
|
||||
const myPtr = ptr(myTypedArray);
|
||||
```
|
||||
|
||||
**To convert from a pointer to an ArrayBuffer**:
|
||||
|
||||
```ts
|
||||
import { ptr, toArrayBuffer } from "bun:ffi";
|
||||
var myTypedArray = new Uint8Array(32);
|
||||
const myPtr = ptr(myTypedArray);
|
||||
|
||||
// toTypedArray accepts a `byteOffset` and `byteLength`
|
||||
// if `byteLength` is not provided, it is assumed to be a null-terminated pointer
|
||||
myTypedArray = new Uint8Array(toArrayBuffer(myPtr, 0, 32), 0, 32);
|
||||
```
|
||||
|
||||
**Pointers & memory safety**
|
||||
|
||||
Using raw pointers outside of FFI is extremely not recommended.
|
||||
|
||||
A future version of bun may add a CLI flag to disable `bun:ffi` (or potentially a separate build of bun).
|
||||
|
||||
**Pointer alignment**
|
||||
|
||||
If an API expects a pointer sized to something other than `char` or `u8`, make sure the typed array is also that size.
|
||||
|
||||
A `u64*` is not exactly the same as `[8]u8*` due to alignment
|
||||
|
||||
##### Passing a pointer
|
||||
|
||||
Where FFI functions expect a pointer, pass a TypedArray of equivalent size
|
||||
|
||||
Easymode:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { encode_png },
|
||||
} = dlopen(myLibraryPath, {
|
||||
encode_png: {
|
||||
// FFIType's can be specified as strings too
|
||||
args: ["ptr", "uint32_t"],
|
||||
returns: FFIType.ptr,
|
||||
},
|
||||
});
|
||||
|
||||
const pixels = new Uint8ClampedArray(128 * 128 * 4);
|
||||
pixels.fill(254);
|
||||
pixels.subarray(0, 32 * 32 * 2).fill(0);
|
||||
|
||||
const out = encode_png(
|
||||
// pixels will be passed as a pointer
|
||||
pixels,
|
||||
|
||||
pixels.byteLength
|
||||
);
|
||||
```
|
||||
|
||||
The [generated wrapper](https://github.com/Jarred-Sumner/bun/blob/c6d732eee2721cd6191672cbe2c57fb17c3fffe4/src/javascript/jsc/ffi.exports.js#L146-L148) will automatically convert the pointer to a TypedArray.
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Hardmode</summary>
|
||||
|
||||
If you don't want the automatic conversion or you want a pointer to a specific byte offset within the TypedArray, you can also directly get the pointer to the TypedArray:
|
||||
|
||||
```ts
|
||||
import { dlopen, FFIType, ptr } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { encode_png },
|
||||
} = dlopen(myLibraryPath, {
|
||||
encode_png: {
|
||||
// FFIType's can be specified as strings too
|
||||
args: ["ptr", "u32", "u32"],
|
||||
returns: FFIType.ptr,
|
||||
},
|
||||
});
|
||||
|
||||
const pixels = new Uint8ClampedArray(128 * 128 * 4);
|
||||
pixels.fill(254);
|
||||
|
||||
// this returns a number! not a BigInt!
|
||||
const myPtr = ptr(pixels);
|
||||
|
||||
const out = encode_png(
|
||||
myPtr,
|
||||
|
||||
// dimensions:
|
||||
128,
|
||||
128
|
||||
);
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
##### Reading pointers
|
||||
|
||||
```ts
|
||||
const out = encode_png(
|
||||
// pixels will be passed as a pointer
|
||||
pixels,
|
||||
|
||||
// dimensions:
|
||||
128,
|
||||
128
|
||||
);
|
||||
|
||||
// assuming it is 0-terminated, it can be read like this:
|
||||
var png = new Uint8Array(toArrayBuffer(out));
|
||||
|
||||
// save it to disk:
|
||||
await Bun.write("out.png", png);
|
||||
```
|
||||
|
||||
### `Bun.Transpiler`
|
||||
|
||||
`Bun.Transpiler` lets you use Bun's transpiler from JavaScript (available in Bun.js)
|
||||
|
||||
@@ -12,221 +12,221 @@ import { bench, group, run } from "mitata";
|
||||
|
||||
const types = {
|
||||
returns_true: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: [],
|
||||
},
|
||||
returns_false: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: [],
|
||||
},
|
||||
returns_42_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: [],
|
||||
},
|
||||
// returns_42_float: {
|
||||
// return_type: "float",
|
||||
// returns: "float",
|
||||
// args: [],
|
||||
// },
|
||||
// returns_42_double: {
|
||||
// return_type: "double",
|
||||
// returns: "double",
|
||||
// args: [],
|
||||
// },
|
||||
returns_42_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: [],
|
||||
},
|
||||
// // returns_42_uint64_t: {
|
||||
// // return_type: "uint64_t",
|
||||
// // returns: "uint64_t",
|
||||
// // args: [],
|
||||
// // },
|
||||
returns_neg_42_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: [],
|
||||
},
|
||||
// returns_neg_42_int64_t: {
|
||||
// return_type: "int64_t",
|
||||
// returns: "int64_t",
|
||||
// args: [],
|
||||
// },
|
||||
|
||||
identity_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["char"],
|
||||
},
|
||||
// identity_float: {
|
||||
// return_type: "float",
|
||||
// returns: "float",
|
||||
// args: ["float"],
|
||||
// },
|
||||
identity_bool: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["bool"],
|
||||
},
|
||||
// identity_double: {
|
||||
// return_type: "double",
|
||||
// returns: "double",
|
||||
// args: ["double"],
|
||||
// },
|
||||
identity_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["int8_t"],
|
||||
},
|
||||
identity_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["int16_t"],
|
||||
},
|
||||
identity_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["int32_t"],
|
||||
},
|
||||
// identity_int64_t: {
|
||||
// return_type: "int64_t",
|
||||
// returns: "int64_t",
|
||||
// args: ["int64_t"],
|
||||
// },
|
||||
identity_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["uint8_t"],
|
||||
},
|
||||
identity_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["uint16_t"],
|
||||
},
|
||||
identity_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["uint32_t"],
|
||||
},
|
||||
// identity_uint64_t: {
|
||||
// return_type: "uint64_t",
|
||||
// returns: "uint64_t",
|
||||
// args: ["uint64_t"],
|
||||
// },
|
||||
|
||||
add_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["char", "char"],
|
||||
},
|
||||
add_float: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: ["float", "float"],
|
||||
},
|
||||
add_double: {
|
||||
return_type: "double",
|
||||
returns: "double",
|
||||
args: ["double", "double"],
|
||||
},
|
||||
add_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["int8_t", "int8_t"],
|
||||
},
|
||||
add_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["int16_t", "int16_t"],
|
||||
},
|
||||
add_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["int32_t", "int32_t"],
|
||||
},
|
||||
// add_int64_t: {
|
||||
// return_type: "int64_t",
|
||||
// returns: "int64_t",
|
||||
// args: ["int64_t", "int64_t"],
|
||||
// },
|
||||
add_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["uint8_t", "uint8_t"],
|
||||
},
|
||||
add_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["uint16_t", "uint16_t"],
|
||||
},
|
||||
add_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["uint32_t", "uint32_t"],
|
||||
},
|
||||
|
||||
does_pointer_equal_42_as_int32_t: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
|
||||
ptr_should_point_to_42_as_int32_t: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: [],
|
||||
},
|
||||
identity_ptr: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: ["ptr"],
|
||||
},
|
||||
// add_uint64_t: {
|
||||
// return_type: "uint64_t",
|
||||
// returns: "uint64_t",
|
||||
// args: ["uint64_t", "uint64_t"],
|
||||
// },
|
||||
|
||||
cb_identity_true: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_false: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["ptr"],
|
||||
},
|
||||
// cb_identity_42_float: {
|
||||
// return_type: "float",
|
||||
// returns: "float",
|
||||
// args: ["ptr"],
|
||||
// },
|
||||
// cb_identity_42_double: {
|
||||
// return_type: "double",
|
||||
// returns: "double",
|
||||
// args: ["ptr"],
|
||||
// },
|
||||
cb_identity_42_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
// cb_identity_42_uint64_t: {
|
||||
// return_type: "uint64_t",
|
||||
// returns: "uint64_t",
|
||||
// args: ["ptr"],
|
||||
// },
|
||||
cb_identity_neg_42_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
// cb_identity_neg_42_int64_t: {
|
||||
// return_type: "int64_t",
|
||||
// returns: "int64_t",
|
||||
// args: ["ptr"],
|
||||
// },
|
||||
|
||||
return_a_function_ptr_to_function_that_returns_true: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: [],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// clang -O3 -shared -undefined dynamic_lookup ./noop.c -o noop.dylib
|
||||
// clang -O3 -shared -mtune=native ./noop.c -o noop.dylib
|
||||
|
||||
int noop();
|
||||
void noop();
|
||||
|
||||
int noop() { return 1; }
|
||||
void noop() {}
|
||||
Binary file not shown.
@@ -6,11 +6,10 @@ const {
|
||||
} = dlopen("./noop.dylib", {
|
||||
noop: {
|
||||
args: [],
|
||||
return_type: "i32",
|
||||
returns: "void",
|
||||
},
|
||||
});
|
||||
var raw = Object.keys(noop);
|
||||
bench("noop", () => {
|
||||
raw();
|
||||
noop();
|
||||
});
|
||||
run({ collect: false, percentiles: true });
|
||||
|
||||
1
bench/ffi/plus100/.gitignore
vendored
Normal file
1
bench/ffi/plus100/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
./napi-plus100
|
||||
27
bench/ffi/plus100/README.md
Normal file
27
bench/ffi/plus100/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## FFI overhead comparison
|
||||
|
||||
This compares the cost of a simple function call going from JavaScript to native code and back in:
|
||||
|
||||
- Bun v0.0.79
|
||||
- napi.rs (Node v17.7.1)
|
||||
- Deno v1.21.1
|
||||
|
||||
To set up:
|
||||
|
||||
```bash
|
||||
bun setup
|
||||
```
|
||||
|
||||
To run the benchmark:
|
||||
|
||||
```bash
|
||||
bun bench
|
||||
```
|
||||
|
||||
| Overhead | Using | Version | Platform |
|
||||
| -------- | ------- | ------- | --------------- |
|
||||
| 7ns | bun:ffi | 0.0.79 | macOS (aarch64) |
|
||||
| 18ns | napi.rs | 17.7.1 | macOS (aarch64) |
|
||||
| 580ns | Deno | 1.21.1 | macOS (aarch64) |
|
||||
|
||||
The native [function](./plus100.c) called in Deno & Bun are the same. The function called with napi.rs is from napi's official [package-template](https://github.com/napi-rs/package-template)
|
||||
7
bench/ffi/plus100/download-napi-plus100.sh
Normal file
7
bench/ffi/plus100/download-napi-plus100.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf plus100-napi
|
||||
git clone https://github.com/napi-rs/package-template plus100-napi --depth=1
|
||||
cd plus100-napi
|
||||
npm install
|
||||
npm run build
|
||||
BIN
bench/ffi/plus100/libadd.dylib
Executable file
BIN
bench/ffi/plus100/libadd.dylib
Executable file
Binary file not shown.
12
bench/ffi/plus100/package.json
Normal file
12
bench/ffi/plus100/package.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "plus100",
|
||||
"scripts": {
|
||||
"setup": "bun run napi-setup && bun run compile",
|
||||
"bench-deno": "deno run --allow-ffi --unstable -A plus100.deno.js",
|
||||
"napi-setup": "bash download-napi-plus100.sh",
|
||||
"bench-napi": "node plus100.napi.mjs",
|
||||
"bench-bun": "bun run ./plus100.bun.js",
|
||||
"compile": "clang -mtune=native -O3 -shared ./plus100.c -o plus100.dylib",
|
||||
"bench": "echo -e '\n--- Bun:\n' && bun run bench-bun && echo -e '\n--- Node:\n' && bun run bench-napi && echo -e '\n--- Deno:\n' && bun run bench-deno"
|
||||
}
|
||||
}
|
||||
19
bench/ffi/plus100/plus100.bun.js
Normal file
19
bench/ffi/plus100/plus100.bun.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { run, bench, group, baseline } from "mitata";
|
||||
import { dlopen } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { plus100: plus100 },
|
||||
close,
|
||||
} = dlopen("./plus100.dylib", {
|
||||
plus100: {
|
||||
params: ["int32_t"],
|
||||
returns: "int32_t",
|
||||
},
|
||||
});
|
||||
bench("plus100(1) (Bun FFI)", () => {
|
||||
plus100(1);
|
||||
});
|
||||
|
||||
// collect option collects benchmark returned values into array
|
||||
// prevents gc and can help with jit optimizing out functions
|
||||
run({ collect: false, percentiles: true });
|
||||
6
bench/ffi/plus100/plus100.c
Normal file
6
bench/ffi/plus100/plus100.c
Normal file
@@ -0,0 +1,6 @@
|
||||
// clang -mtune=native -O3 -shared ./plus100.c -o plus100.dylib
|
||||
#include <stdint.h>
|
||||
|
||||
int32_t plus100(int32_t a);
|
||||
|
||||
int32_t plus100(int32_t a) { return a + 100; }
|
||||
18
bench/ffi/plus100/plus100.deno.js
Normal file
18
bench/ffi/plus100/plus100.deno.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { run, bench, group, baseline } from "https://esm.sh/mitata";
|
||||
|
||||
const {
|
||||
symbols: { plus100: plus100 },
|
||||
close,
|
||||
} = Deno.dlopen("./plus100.dylib", {
|
||||
plus100: {
|
||||
parameters: ["i32"],
|
||||
result: "i32",
|
||||
},
|
||||
});
|
||||
bench("plus100(1) (Deno FFI)", () => {
|
||||
plus100(1);
|
||||
});
|
||||
|
||||
// collect option collects benchmark returned values into array
|
||||
// prevents gc and can help with jit optimizing out functions
|
||||
run({ collect: false, percentiles: true });
|
||||
BIN
bench/ffi/plus100/plus100.dylib
Executable file
BIN
bench/ffi/plus100/plus100.dylib
Executable file
Binary file not shown.
10
bench/ffi/plus100/plus100.napi.mjs
Normal file
10
bench/ffi/plus100/plus100.napi.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
import { bench, run } from "mitata";
|
||||
|
||||
import module from "module";
|
||||
|
||||
const { plus100 } = module.createRequire(import.meta.url)("./plus100-napi");
|
||||
|
||||
bench("plus100(1) (napi.rs)", () => {
|
||||
plus100(1);
|
||||
});
|
||||
run({ collect: false, percentiles: true });
|
||||
7
examples/add.rs
Normal file
7
examples/add.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[no_mangle]
|
||||
pub extern "C" fn add(a: isize, b: isize) -> isize {
|
||||
a + b
|
||||
}
|
||||
|
||||
// to compile:
|
||||
// rustc --crate-type cdylib add.rs
|
||||
12
examples/add.ts
Normal file
12
examples/add.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { dlopen, suffix } from "bun:ffi";
|
||||
|
||||
const {
|
||||
symbols: { add },
|
||||
} = dlopen(`./libadd.${suffix}`, {
|
||||
add: {
|
||||
args: ["i32", "i32"],
|
||||
returns: "i32",
|
||||
},
|
||||
});
|
||||
|
||||
console.log(add(1, 2));
|
||||
6
examples/add.zig
Normal file
6
examples/add.zig
Normal file
@@ -0,0 +1,6 @@
|
||||
pub export fn add(a: i32, b: i32) i32 {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
// to compile:
|
||||
// zig build-lib -OReleaseFast ./add.zig -dynamic --name add
|
||||
@@ -18,7 +18,7 @@ it("ffi print", async () => {
|
||||
import.meta.dir + "/ffi.test.fixture.callback.c",
|
||||
viewSource(
|
||||
{
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
true
|
||||
@@ -29,7 +29,7 @@ it("ffi print", async () => {
|
||||
viewSource(
|
||||
{
|
||||
not_a_callback: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: ["float"],
|
||||
},
|
||||
},
|
||||
@@ -39,7 +39,7 @@ it("ffi print", async () => {
|
||||
expect(
|
||||
viewSource(
|
||||
{
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: [],
|
||||
},
|
||||
true
|
||||
@@ -49,7 +49,7 @@ it("ffi print", async () => {
|
||||
viewSource(
|
||||
{
|
||||
a: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: [],
|
||||
},
|
||||
},
|
||||
@@ -61,221 +61,221 @@ it("ffi print", async () => {
|
||||
it("ffi run", () => {
|
||||
const types = {
|
||||
returns_true: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: [],
|
||||
},
|
||||
returns_false: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: [],
|
||||
},
|
||||
returns_42_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: [],
|
||||
},
|
||||
returns_42_float: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: [],
|
||||
},
|
||||
returns_42_double: {
|
||||
return_type: "double",
|
||||
returns: "double",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: [],
|
||||
},
|
||||
returns_42_uint64_t: {
|
||||
return_type: "uint64_t",
|
||||
returns: "uint64_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: [],
|
||||
},
|
||||
returns_neg_42_int64_t: {
|
||||
return_type: "int64_t",
|
||||
returns: "int64_t",
|
||||
args: [],
|
||||
},
|
||||
|
||||
identity_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["char"],
|
||||
},
|
||||
identity_float: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: ["float"],
|
||||
},
|
||||
identity_bool: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["bool"],
|
||||
},
|
||||
identity_double: {
|
||||
return_type: "double",
|
||||
returns: "double",
|
||||
args: ["double"],
|
||||
},
|
||||
identity_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["int8_t"],
|
||||
},
|
||||
identity_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["int16_t"],
|
||||
},
|
||||
identity_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["int32_t"],
|
||||
},
|
||||
identity_int64_t: {
|
||||
return_type: "int64_t",
|
||||
returns: "int64_t",
|
||||
args: ["int64_t"],
|
||||
},
|
||||
identity_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["uint8_t"],
|
||||
},
|
||||
identity_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["uint16_t"],
|
||||
},
|
||||
identity_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["uint32_t"],
|
||||
},
|
||||
identity_uint64_t: {
|
||||
return_type: "uint64_t",
|
||||
returns: "uint64_t",
|
||||
args: ["uint64_t"],
|
||||
},
|
||||
|
||||
add_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["char", "char"],
|
||||
},
|
||||
add_float: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: ["float", "float"],
|
||||
},
|
||||
add_double: {
|
||||
return_type: "double",
|
||||
returns: "double",
|
||||
args: ["double", "double"],
|
||||
},
|
||||
add_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["int8_t", "int8_t"],
|
||||
},
|
||||
add_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["int16_t", "int16_t"],
|
||||
},
|
||||
add_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["int32_t", "int32_t"],
|
||||
},
|
||||
add_int64_t: {
|
||||
return_type: "int64_t",
|
||||
returns: "int64_t",
|
||||
args: ["int64_t", "int64_t"],
|
||||
},
|
||||
add_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["uint8_t", "uint8_t"],
|
||||
},
|
||||
add_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["uint16_t", "uint16_t"],
|
||||
},
|
||||
add_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["uint32_t", "uint32_t"],
|
||||
},
|
||||
|
||||
does_pointer_equal_42_as_int32_t: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
|
||||
ptr_should_point_to_42_as_int32_t: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: [],
|
||||
},
|
||||
identity_ptr: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: ["ptr"],
|
||||
},
|
||||
add_uint64_t: {
|
||||
return_type: "uint64_t",
|
||||
returns: "uint64_t",
|
||||
args: ["uint64_t", "uint64_t"],
|
||||
},
|
||||
|
||||
cb_identity_true: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_false: {
|
||||
return_type: "bool",
|
||||
returns: "bool",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_char: {
|
||||
return_type: "char",
|
||||
returns: "char",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_float: {
|
||||
return_type: "float",
|
||||
returns: "float",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_double: {
|
||||
return_type: "double",
|
||||
returns: "double",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint8_t: {
|
||||
return_type: "uint8_t",
|
||||
returns: "uint8_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int8_t: {
|
||||
return_type: "int8_t",
|
||||
returns: "int8_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint16_t: {
|
||||
return_type: "uint16_t",
|
||||
returns: "uint16_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint32_t: {
|
||||
return_type: "uint32_t",
|
||||
returns: "uint32_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_42_uint64_t: {
|
||||
return_type: "uint64_t",
|
||||
returns: "uint64_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int16_t: {
|
||||
return_type: "int16_t",
|
||||
returns: "int16_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int32_t: {
|
||||
return_type: "int32_t",
|
||||
returns: "int32_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
cb_identity_neg_42_int64_t: {
|
||||
return_type: "int64_t",
|
||||
returns: "int64_t",
|
||||
args: ["ptr"],
|
||||
},
|
||||
|
||||
return_a_function_ptr_to_function_that_returns_true: {
|
||||
return_type: "ptr",
|
||||
returns: "ptr",
|
||||
args: [],
|
||||
},
|
||||
};
|
||||
@@ -426,7 +426,7 @@ it("ffi run", () => {
|
||||
|
||||
// const first = native.callback(
|
||||
// {
|
||||
// return_type: "bool",
|
||||
// returns: "bool",
|
||||
// },
|
||||
// identityBool
|
||||
// );
|
||||
@@ -440,7 +440,7 @@ it("ffi run", () => {
|
||||
// cb_identity_false(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "bool",
|
||||
// returns: "bool",
|
||||
// },
|
||||
// () => false
|
||||
// )
|
||||
@@ -451,7 +451,7 @@ it("ffi run", () => {
|
||||
// cb_identity_42_char(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "char",
|
||||
// returns: "char",
|
||||
// },
|
||||
// () => 42
|
||||
// )
|
||||
@@ -461,7 +461,7 @@ it("ffi run", () => {
|
||||
// cb_identity_42_uint8_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "uint8_t",
|
||||
// returns: "uint8_t",
|
||||
// },
|
||||
// () => 42
|
||||
// )
|
||||
@@ -471,7 +471,7 @@ it("ffi run", () => {
|
||||
// cb_identity_neg_42_int8_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "int8_t",
|
||||
// returns: "int8_t",
|
||||
// },
|
||||
// () => -42
|
||||
// )
|
||||
@@ -480,7 +480,7 @@ it("ffi run", () => {
|
||||
// cb_identity_42_uint16_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "uint16_t",
|
||||
// returns: "uint16_t",
|
||||
// },
|
||||
// () => 42
|
||||
// )
|
||||
@@ -489,7 +489,7 @@ it("ffi run", () => {
|
||||
// cb_identity_42_uint32_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "uint32_t",
|
||||
// returns: "uint32_t",
|
||||
// },
|
||||
// () => 42
|
||||
// )
|
||||
@@ -498,7 +498,7 @@ it("ffi run", () => {
|
||||
// cb_identity_neg_42_int16_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "int16_t",
|
||||
// returns: "int16_t",
|
||||
// },
|
||||
// () => -42
|
||||
// )
|
||||
@@ -507,7 +507,7 @@ it("ffi run", () => {
|
||||
// cb_identity_neg_42_int32_t(
|
||||
// callback(
|
||||
// {
|
||||
// return_type: "int32_t",
|
||||
// returns: "int32_t",
|
||||
// },
|
||||
// () => -42
|
||||
// )
|
||||
|
||||
@@ -214,11 +214,11 @@ export function dlopen(path, options) {
|
||||
var symbol = result.symbols[key];
|
||||
if (
|
||||
options[key]?.args?.length ||
|
||||
FFIType[options[key]?.return_type] === FFIType.cstring
|
||||
FFIType[options[key]?.returns] === FFIType.cstring
|
||||
) {
|
||||
result.symbols[key] = FFIBuilder(
|
||||
options[key].args ?? [],
|
||||
options[key].return_type ?? FFIType.void,
|
||||
options[key].returns ?? FFIType.void,
|
||||
symbol,
|
||||
// in stacktraces:
|
||||
// instead of
|
||||
|
||||
@@ -937,6 +937,8 @@ pub const VirtualMachine = struct {
|
||||
this.resolved_count = 0;
|
||||
}
|
||||
|
||||
const shared_library_suffix = if (Environment.isMac) "dylib" else if (Environment.isLinux) "so" else "";
|
||||
|
||||
inline fn _fetch(
|
||||
_: *JSGlobalObject,
|
||||
_specifier: string,
|
||||
@@ -1081,7 +1083,14 @@ pub const VirtualMachine = struct {
|
||||
} else if (strings.eqlComptime(_specifier, "bun:ffi")) {
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = ZigString.init("export const FFIType = " ++ JSC.FFI.ABIType.map_to_js_object ++ ";\n\n" ++ @embedFile("ffi.exports.js") ++ "\n"),
|
||||
.source_code = ZigString.init(
|
||||
"export const FFIType = " ++
|
||||
JSC.FFI.ABIType.map_to_js_object ++
|
||||
";\n\n" ++
|
||||
"export const suffix = '" ++ shared_library_suffix ++ "';\n\n" ++
|
||||
@embedFile("ffi.exports.js") ++
|
||||
"\n",
|
||||
),
|
||||
.specifier = ZigString.init("bun:ffi"),
|
||||
.source_url = ZigString.init("bun:ffi"),
|
||||
.hash = 0,
|
||||
|
||||
39
types/bun/ffi.d.ts
vendored
39
types/bun/ffi.d.ts
vendored
@@ -314,7 +314,7 @@ declare module "bun:ffi" {
|
||||
void = 13,
|
||||
|
||||
/**
|
||||
* When used as a `return_type`, this will automatically become a {@link CString}.
|
||||
* When used as a `returns`, this will automatically become a {@link CString}.
|
||||
*
|
||||
* When used in `args` it is equivalent to {@link FFIType.pointer}
|
||||
*
|
||||
@@ -365,7 +365,7 @@ declare module "bun:ffi" {
|
||||
* const lib = dlopen('add', {
|
||||
* // FFIType can be used or you can pass string labels.
|
||||
* args: [FFIType.i32, "i32"],
|
||||
* return_type: "i32",
|
||||
* returns: "i32",
|
||||
* });
|
||||
* lib.symbols.add(1, 2)
|
||||
* ```
|
||||
@@ -389,7 +389,7 @@ declare module "bun:ffi" {
|
||||
* ```js
|
||||
* const lib = dlopen('z', {
|
||||
* version: {
|
||||
* return_type: "ptr",
|
||||
* returns: "ptr",
|
||||
* }
|
||||
* });
|
||||
* console.log(new CString(lib.symbols.version()));
|
||||
@@ -402,18 +402,18 @@ declare module "bun:ffi" {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
return_type?: FFITypeOrString;
|
||||
returns?: FFITypeOrString;
|
||||
}
|
||||
|
||||
type Symbols = Record<string, FFIFunction>;
|
||||
|
||||
/**
|
||||
* Compile a callback function
|
||||
*
|
||||
* Returns a function pointer
|
||||
*
|
||||
*/
|
||||
export function callback(ffi: FFIFunction, cb: Function): number;
|
||||
// /**
|
||||
// * Compile a callback function
|
||||
// *
|
||||
// * Returns a function pointer
|
||||
// *
|
||||
// */
|
||||
// export function callback(ffi: FFIFunction, cb: Function): number;
|
||||
|
||||
export interface Library {
|
||||
symbols: Record<string, CallableFunction>;
|
||||
@@ -583,4 +583,21 @@ declare module "bun:ffi" {
|
||||
*/
|
||||
export function viewSource(symbols: Symbols, is_callback?: false): string[];
|
||||
export function viewSource(callback: FFIFunction, is_callback: true): string;
|
||||
|
||||
/**
|
||||
* Platform-specific file extension name for dynamic libraries
|
||||
*
|
||||
* "." is not included
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* "dylib" // macOS
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* "so" // linux
|
||||
* ```
|
||||
*/
|
||||
export const suffix: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user