Skip to content

Commit

Permalink
[bun:ffi] Implement JSCallback so C can call into JS
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner committed Nov 2, 2022
1 parent 86639fe commit 81033c5
Show file tree
Hide file tree
Showing 9 changed files with 659 additions and 231 deletions.
108 changes: 90 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3682,24 +3682,25 @@ 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` | `int64_t` |
| i64_fast | `int64_t` | |
| u8 | `uint8_t` | `uint8_t` |
| u16 | `uint16_t` | `uint16_t` |
| u32 | `uint32_t` | `uint32_t` |
| u64 | `uint64_t` | `uint64_t` |
| u64_fast | `uint64_t` | |
| f32 | `float` | `float` |
| f64 | `double` | `double` |
| bool | `bool` | |
| char | `char` | |
| `FFIType` | C Type | Aliases |
| --------- | -------------- | --------------------------- |
| cstring | `char*` | |
| function | `(void*)(*)()` | `fn`, `callback` |
| ptr | `void*` | `pointer`, `void*`, `char*` |
| i8 | `int8_t` | `int8_t` |
| i16 | `int16_t` | `int16_t` |
| i32 | `int32_t` | `int32_t`, `int` |
| i64 | `int64_t` | `int64_t` |
| i64_fast | `int64_t` | |
| u8 | `uint8_t` | `uint8_t` |
| u16 | `uint16_t` | `uint16_t` |
| u32 | `uint32_t` | `uint32_t` |
| u64 | `uint64_t` | `uint64_t` |
| u64_fast | `uint64_t` | |
| f32 | `float` | `float` |
| f64 | `double` | `double` |
| bool | `bool` | |
| char | `char` | |

#### Strings (`CString`)

Expand Down Expand Up @@ -3826,6 +3827,77 @@ const [major, minor, patch] = [
];
```
#### Callbacks (`JSCallback`)
Bun v0.2.3 added `JSCallback` which lets you create JavaScript callback functions that you can pass to C/FFI functions. The C/FFI function can call into the JavaScript/TypeScript code. This is useful for asynchronous code or otherwise when you want to call into JavaScript code from C.
```ts
import { dlopen, JSCallback } from "bun:ffi";

const {
symbols: { setOnResolve, setOnReject },
} = dlopen("libmylib", {
setOnResolve: {
returns: "bool",
args: ["function"],
},
setOnReject: {
returns: "bool",
args: ["function"],
},
});

const onResolve = new JSCallback(
{
returns: "bool",
args: ["i32"],
},
(arg) => arg === 42
);

const onReject = new JSCallback(
{
returns: "bool",
args: ["i32"],
},
(arg) => arg > 42
);

setOnResolve(onResolve);
setOnReject(onReject);

// Sometime later:
setTimeout(() => {
onResolve.close();
onReject.close();
}, 5000);
```
When you're done with a JSCallback, you should call `close()` to free the memory.
For a slight performance boost, directly pass `JSCallback.prototype.ptr` instead of the `JSCallback` object:
```ts
const onResolve = new JSCallback(
{
returns: "bool",
args: ["i32"],
},
(arg) => arg === 42
);
const setOnResolve = new CFunction({
returns: "bool",
args: ["function"],
ptr: myNativeLibrarySetOnResolve,
});

// This code runs slightly faster:
setOnResolve(onResolve.ptr);

// Compared to this:
setOnResolve(onResolve);
```
#### Pointers
Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript.
Expand Down
31 changes: 19 additions & 12 deletions src/bun.js/api/FFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,16 @@ typedef void* JSContext;


#ifdef IS_CALLBACK
extern int64_t bun_call(JSContext, void* func, void* thisValue, size_t len, const EncodedJSValue args[], void* exception);
JSContext cachedJSContext;
void* cachedCallbackFunction;
ZIG_REPR_TYPE FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args);
// We wrap
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) __attribute__((__always_inline__));
static EncodedJSValue _FFI_Callback_call(void* ctx, size_t argCount, ZIG_REPR_TYPE* args) {
EncodedJSValue return_value;
return_value.asZigRepr = FFI_Callback_call(ctx, argCount, args);
return return_value;
}
static ZIG_REPR_TYPE arguments[100];

#endif

static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__));
Expand All @@ -115,10 +122,10 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_i
uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value);
int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value);

EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val);
EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val);
static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__));
static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__));
EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val);
EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val);
static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) __attribute__((__always_inline__));
static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) __attribute__((__always_inline__));


static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__));
Expand Down Expand Up @@ -240,7 +247,7 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
return JSVALUE_TO_INT64_SLOW(value);
}

static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) {
static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
if (val < MAX_INT32) {
return INT32_TO_JSVALUE((int32_t)val);
}
Expand All @@ -249,10 +256,10 @@ static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) {
return DOUBLE_TO_JSVALUE((double)val);
}

return UINT64_TO_JSVALUE_SLOW(globalObject, val);
return UINT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
}

static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) {
static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
if (val >= -MAX_INT32 && val <= MAX_INT32) {
return INT32_TO_JSVALUE((int32_t)val);
}
Expand All @@ -261,11 +268,11 @@ static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) {
return DOUBLE_TO_JSVALUE((double)val);
}

return INT64_TO_JSVALUE_SLOW(globalObject, val);
return INT64_TO_JSVALUE_SLOW(jsGlobalObject, val);
}

#ifndef IS_CALLBACK
ZIG_REPR_TYPE JSFunctionCall(void* globalObject, void* callFrame);
ZIG_REPR_TYPE JSFunctionCall(void* jsGlobalObject, void* callFrame);

#endif

Expand Down
3 changes: 3 additions & 0 deletions src/bun.js/api/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2603,6 +2603,9 @@ pub const FFI = struct {
.toArrayBuffer = .{
.rfn = JSC.wrapWithHasContainer(@This(), "toArrayBuffer", false, false, true),
},
.closeCallback = .{
.rfn = JSC.wrapWithHasContainer(JSC.FFI, "closeCallback", false, false, false),
},
},
.{
.read = .{
Expand Down
Loading

0 comments on commit 81033c5

Please sign in to comment.