Skip to content

Commit

Permalink
Support napi inside of bun:ffi (#14028)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner committed Sep 19, 2024
1 parent 866a6d9 commit 6415296
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 41 deletions.
197 changes: 197 additions & 0 deletions docs/api/cc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
`bun:ffi` has experimental support for compiling and running C from JavaScript with low overhead.

## Usage (cc in `bun:ffi`)

See the [introduction blog post](https://bun.sh/blog/compile-and-run-c-in-js) for more information.

JavaScript:

```ts#hello.js
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };

const {
symbols: { hello },
} = cc({
source,
symbols: {
hello: {
args: [],
returns: "int",
},
},
});

console.log("What is the answer to the universe?", hello());
```

C source:

```c#hello.c
int hello() {
return 42;
}
```

When you run `hello.js`, it will print:

```sh
$ bun hello.js
What is the answer to the universe? 42
```

Under the hood, `cc` uses [TinyCC](https://bellard.org/tcc/) to compile the C code and then link it with the JavaScript runtime, efficiently converting types in-place.

### Primitive types

The same `FFIType` values in [`dlopen`](/docs/api/ffi) are supported in `cc`.

| `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` | |
| napi_env | `napi_env` | |
| napi_value | `napi_value` | |

### Strings, objects, and non-primitive types

To make it easier to work with strings, objects, and other non-primitive types that don't map 1:1 to C types, `cc` supports N-API.

To pass or receive a JavaScript values without any type conversions from a C function, you can use `napi_value`.

You can also pass a `napi_env` to receive the N-API environment used to call the JavaScript function.

#### Returning a C string to JavaScript

For example, if you have a string in C, you can return it to JavaScript like this:

```ts#hello.js
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };

const {
symbols: { hello },
} = cc({
source,
symbols: {
hello: {
args: ["napi_env"],
returns: "napi_value",
},
},
});

const result = hello();
```

And in C:

```c#hello.c
#include <node/node_api.h>

napi_value hello(napi_env env) {
napi_value result;
napi_create_string_utf8(env, "Hello, Napi!", NAPI_AUTO_LENGTH, &result);
return result;
}
```
You can also use this to return other types like objects and arrays:
```c#hello.c
#include <node/node_api.h>
napi_value hello(napi_env env) {
napi_value result;
napi_create_object(env, &result);
return result;
}
```

### `cc` Reference

#### `library: string[]`

The `library` array is used to specify the libraries that should be linked with the C code.

```ts
type Library = string[];

cc({
source: "hello.c",
library: ["sqlite3"],
});
```

#### `symbols`

The `symbols` object is used to specify the functions and variables that should be exposed to JavaScript.

```ts
type Symbols = {
[key: string]: {
args: FFIType[];
returns: FFIType;
};
};
```

#### `source`

The `source` is a file path to the C code that should be compiled and linked with the JavaScript runtime.

```ts
type Source = string | URL | BunFile;

cc({
source: "hello.c",
symbols: {
hello: {
args: [],
returns: "int",
},
},
});
```

#### `flags: string | string[]`

The `flags` is an optional array of strings that should be passed to the TinyCC compiler.

```ts
type Flags = string | string[];
```

These are flags like `-I` for include directories and `-D` for preprocessor definitions.

#### `defines: Record<string, string>`

The `defines` is an optional object that should be passed to the TinyCC compiler.

```ts
type Defines = Record<string, string>;

cc({
source: "hello.c",
defines: {
"NDEBUG": "1",
},
});
```

These are preprocessor definitions passed to the TinyCC compiler.
42 changes: 22 additions & 20 deletions docs/api/ffi.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Use the built-in `bun:ffi` module to efficiently call native libraries from JavaScript. It works with languages that support the C ABI (Zig, Rust, C/C++, C#, Nim, Kotlin, etc).

## Usage (`bun:ffi`)
## dlopen usage (`bun:ffi`)

To print the version number of `sqlite3`:

Expand Down Expand Up @@ -108,25 +108,27 @@ $ zig build-lib add.cpp -dynamic -lc -lc++

The following `FFIType` values are supported.

| `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` | |
| `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` | |
| napi_env | `napi_env` | |
| napi_value | `napi_value` | |

## Strings

Expand Down
9 changes: 9 additions & 0 deletions packages/bun-types/ffi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ declare module "bun:ffi" {
*/
u64_fast = 16,
function = 17,

napi_env = 18,
napi_value = 19,
}

type Pointer = number & { __pointer__: null };
Expand Down Expand Up @@ -372,6 +375,8 @@ declare module "bun:ffi" {
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | JSCallback; // cannot be null
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
}
interface FFITypeToReturnsType {
[FFIType.char]: number;
Expand Down Expand Up @@ -404,6 +409,8 @@ declare module "bun:ffi" {
[FFIType.i64_fast]: number | bigint;
[FFIType.u64_fast]: number | bigint;
[FFIType.function]: Pointer | null;
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
}
interface FFITypeStringToType {
["char"]: FFIType.char;
Expand Down Expand Up @@ -436,6 +443,8 @@ declare module "bun:ffi" {
["function"]: FFIType.pointer; // for now
["usize"]: FFIType.uint64_t; // for now
["callback"]: FFIType.pointer; // for now
["napi_env"]: never;
["napi_value"]: unknown;
}

type FFITypeOrString = FFIType | keyof FFITypeStringToType;
Expand Down
33 changes: 33 additions & 0 deletions src/bun.js/api/FFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,37 @@ typedef _Bool bool;
#define true 1
#define false 0

#ifndef SRC_JS_NATIVE_API_TYPES_H_
typedef struct napi_env__ *napi_env;
typedef struct napi_value__ *napi_value;
typedef enum {
napi_ok,
napi_invalid_arg,
napi_object_expected,
napi_string_expected,
napi_name_expected,
napi_function_expected,
napi_number_expected,
napi_boolean_expected,
napi_array_expected,
napi_generic_failure,
napi_pending_exception,
napi_cancelled,
napi_escape_called_twice,
napi_handle_scope_mismatch,
napi_callback_scope_mismatch,
napi_queue_full,
napi_closing,
napi_bigint_expected,
napi_date_expected,
napi_arraybuffer_expected,
napi_detachable_arraybuffer_expected,
napi_would_deadlock // unused
} napi_status;
void* NapiHandleScope__push(void* jsGlobalObject, bool detached);
void NapiHandleScope__pop(void* jsGlobalObject, void* handleScope);
#endif


#ifdef INJECT_BEFORE
// #include <stdint.h>
Expand Down Expand Up @@ -68,6 +99,8 @@ typedef union EncodedJSValue {
JSCell *ptr;
#endif

napi_value asNapiValue;

#if IS_BIG_ENDIAN
struct {
int32_t tag;
Expand Down
Loading

0 comments on commit 6415296

Please sign in to comment.