Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: Add buffer type and inline pointer #14036

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api/ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ The following `FFIType` values are supported.

| `FFIType` | C Type | Aliases |
| ---------- | -------------- | --------------------------- |
| buffer | `char*` | |
| cstring | `char*` | |
| function | `(void*)(*)()` | `fn`, `callback` |
| ptr | `void*` | `pointer`, `void*`, `char*` |
Expand All @@ -130,6 +131,8 @@ The following `FFIType` values are supported.
| napi_env | `napi_env` | |
| napi_value | `napi_value` | |

Note: `buffer` arguments must be a `TypedArray` or `DataView`.

## Strings

JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.
Expand Down
4 changes: 4 additions & 0 deletions packages/bun-types/ffi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ declare module "bun:ffi" {

napi_env = 18,
napi_value = 19,
buffer = 20,
}

type Pointer = number & { __pointer__: null };
Expand Down Expand Up @@ -377,6 +378,7 @@ declare module "bun:ffi" {
[FFIType.function]: Pointer | JSCallback; // cannot be null
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
[FFIType.buffer]: NodeJS.TypedArray | DataView;
}
interface FFITypeToReturnsType {
[FFIType.char]: number;
Expand Down Expand Up @@ -411,6 +413,7 @@ declare module "bun:ffi" {
[FFIType.function]: Pointer | null;
[FFIType.napi_env]: unknown;
[FFIType.napi_value]: unknown;
[FFIType.buffer]: NodeJS.TypedArray | DataView;
}
interface FFITypeStringToType {
["char"]: FFIType.char;
Expand Down Expand Up @@ -445,6 +448,7 @@ declare module "bun:ffi" {
["callback"]: FFIType.pointer; // for now
["napi_env"]: never;
["napi_value"]: unknown;
["buffer"]: FFIType.buffer;
}

type FFITypeOrString = FFIType | keyof FFITypeStringToType;
Expand Down
43 changes: 38 additions & 5 deletions src/bun.js/api/FFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef _Bool bool;

#ifndef SRC_JS_NATIVE_API_TYPES_H_
typedef struct napi_env__ *napi_env;
typedef struct napi_value__ *napi_value;
typedef int64_t napi_value;
typedef enum {
napi_ok,
napi_invalid_arg,
Expand Down Expand Up @@ -74,14 +74,14 @@ void NapiHandleScope__pop(void* jsGlobalObject, void* handleScope);
// begin with a 15-bit pattern within the range 0x0002..0xFFFC.
#define DoubleEncodeOffsetBit 49
#define DoubleEncodeOffset (1ll << DoubleEncodeOffsetBit)
#define OtherTag 0x2
#define BoolTag 0x4
#define UndefinedTag 0x8
#define OtherTag 0x2ll
#define BoolTag 0x4ll
#define UndefinedTag 0x8ll
#define TagValueFalse (OtherTag | BoolTag | false)
#define TagValueTrue (OtherTag | BoolTag | true)
#define TagValueUndefined (OtherTag | UndefinedTag)
#define TagValueNull (OtherTag)
#define NotCellMask NumberTag | OtherTag
#define NotCellMask (int64_t)(NumberTag | OtherTag)

#define MAX_INT32 2147483648
#define MAX_INT52 9007199254740991
Expand Down Expand Up @@ -171,6 +171,11 @@ static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inli
static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
static bool JSVALUE_TO_BOOL(EncodedJSValue val) __attribute__((__always_inline__));
static uint8_t GET_JSTYPE(EncodedJSValue val) __attribute__((__always_inline__));
static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) __attribute__((__always_inline__));
static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) __attribute__((__always_inline__));
static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) __attribute__((__always_inline__));
static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) __attribute__((__always_inline__));

static bool JSVALUE_IS_CELL(EncodedJSValue val) {
return !(val.asInt64 & NotCellMask);
Expand All @@ -184,6 +189,25 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) {
return val.asInt64 & NumberTag;
}

static uint8_t GET_JSTYPE(EncodedJSValue val) {
return *(uint8_t*)((uint8_t*)val.asPtr + JSCell__offsetOfType);
}

static bool JSTYPE_IS_TYPED_ARRAY(uint8_t type) {
return type >= JSTypeArrayBufferViewMin && type <= JSTypeArrayBufferViewMax;
}

static bool JSCELL_IS_TYPED_ARRAY(EncodedJSValue val) {
return JSVALUE_IS_CELL(val) && JSTYPE_IS_TYPED_ARRAY(GET_JSTYPE(val));
}

static void* JSVALUE_TO_TYPED_ARRAY_VECTOR(EncodedJSValue val) {
return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector);
}

static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) {
return *(uint64_t*)((char*)val.asPtr + JSArrayBufferView__offsetOfLength);
}

// JSValue numbers-as-pointers are represented as a 52-bit integer
// Previously, the pointer was stored at the end of the 64-bit value
Expand All @@ -193,6 +217,11 @@ static bool JSVALUE_IS_NUMBER(EncodedJSValue val) {
static void* JSVALUE_TO_PTR(EncodedJSValue val) {
if (val.asInt64 == TagValueNull)
return 0;

if (JSCELL_IS_TYPED_ARRAY(val)) {
return JSVALUE_TO_TYPED_ARRAY_VECTOR(val);
}

val.asInt64 -= DoubleEncodeOffset;
size_t ptr = (size_t)val.asDouble;
return (void*)ptr;
Expand Down Expand Up @@ -275,6 +304,10 @@ static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
return (uint64_t)JSVALUE_TO_DOUBLE(value);
}

if (JSCELL_IS_TYPED_ARRAY(value)) {
return (uint64_t)JSVALUE_TO_TYPED_ARRAY_LENGTH(value);
}

return JSVALUE_TO_UINT64_SLOW(value);
}
static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
Expand Down
77 changes: 68 additions & 9 deletions src/bun.js/api/ffi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,24 @@ const IOTask = JSC.IOTask;
const TCC = @import("../../tcc.zig");
extern fn pthread_jit_write_protect_np(enable: bool) callconv(.C) void;

const Offsets = extern struct {
JSArrayBufferView__offsetOfLength: u32,
JSArrayBufferView__offsetOfByteOffset: u32,
JSArrayBufferView__offsetOfVector: u32,
JSCell__offsetOfType: u32,

extern "C" var Bun__FFI__offsets: Offsets;
extern "C" fn Bun__FFI__ensureOffsetsAreLoaded() void;
fn loadOnce() void {
Bun__FFI__ensureOffsetsAreLoaded();
}
var once = std.once(loadOnce);
pub fn get() *const Offsets {
once.call();
return &Bun__FFI__offsets;
}
};

pub const FFI = struct {
dylib: ?std.DynLib = null,
relocated_bytes_to_free: ?[]u8 = null,
Expand Down Expand Up @@ -1361,6 +1379,11 @@ pub const FFI = struct {
return ZigString.static("Cannot return napi_env to JavaScript").toErrorInstance(global);
}

if (return_type == .buffer) {
abi_types.clearAndFree(allocator);
return ZigString.static("Cannot return a buffer to JavaScript (since byteLength and byteOffset are unknown)").toErrorInstance(global);
}

if (function.threadsafe and return_type != ABIType.void) {
abi_types.clearAndFree(allocator);
return ZigString.static("Threadsafe functions must return void").toErrorInstance(global);
Expand Down Expand Up @@ -1542,14 +1565,7 @@ pub const FFI = struct {
}

_ = TCC.tcc_set_output_type(state, TCC.TCC_OUTPUT_MEMORY);
const Sizes = @import("../bindings/sizes.zig");

var symbol_buf: [256]u8 = undefined;
TCC.tcc_define_symbol(
state,
"Bun_FFI_PointerOffsetToArgumentsList",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{Sizes.Bun_FFI_PointerOffsetToArgumentsList}) catch unreachable,
);
CompilerRT.define(state);

// TCC.tcc_define_symbol(
Expand Down Expand Up @@ -2064,7 +2080,7 @@ pub const FFI = struct {
function = 17,
napi_env = 18,
napi_value = 19,

buffer = 20,
pub const max = @intFromEnum(ABIType.napi_value);
Copy link
Contributor

@190n 190n Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should at least change to ABIType.buffer, or we can calculate the max:

Suggested change
pub const max = @intFromEnum(ABIType.napi_value);
pub const max = max_abi_type: {
var current_max = std.math.minInt(i32);
for (@typeInfo(ABIType).Enum.fields) |f| {
current_max = @max(current_max, f.value);
}
break :max_abi_type current_max;
};

or, could we @cImport these?


/// Types that we can directly pass through as an `int64_t`
Expand Down Expand Up @@ -2104,6 +2120,8 @@ pub const FFI = struct {
.{ "uint64_t", ABIType.uint64_t },
.{ "uint8_t", ABIType.uint8_t },
.{ "usize", ABIType.uint64_t },
.{ "size_t", ABIType.uint64_t },
.{ "buffer", ABIType.buffer },
.{ "void*", ABIType.ptr },
.{ "ptr", ABIType.ptr },
.{ "pointer", ABIType.ptr },
Expand Down Expand Up @@ -2219,6 +2237,9 @@ pub const FFI = struct {
try writer.writeAll(".asNapiValue");
return;
},
.buffer => {
try writer.writeAll("JSVALUE_TO_TYPED_ARRAY_VECTOR(");
},
}
// if (self.fromi64) {
// try writer.writeAll("EncodedJSValue{ ");
Expand Down Expand Up @@ -2274,6 +2295,9 @@ pub const FFI = struct {
.napi_value => {
try writer.print("((EncodedJSValue) {{.asNapiValue = {s} }} )", .{self.symbol});
},
.buffer => {
try writer.writeAll("0");
},
}
}
};
Expand Down Expand Up @@ -2302,7 +2326,7 @@ pub const FFI = struct {

pub fn typenameLabel(this: ABIType) []const u8 {
return switch (this) {
.function, .cstring, .ptr => "void*",
.buffer, .function, .cstring, .ptr => "void*",
.bool => "bool",
.int8_t => "int8_t",
.uint8_t => "uint8_t",
Expand Down Expand Up @@ -2345,6 +2369,7 @@ pub const FFI = struct {
.void => "void",
.napi_env => "napi_env",
.napi_value => "napi_value",
.buffer => "buffer",
};
}
};
Expand Down Expand Up @@ -2423,6 +2448,40 @@ const CompilerRT = struct {
// there
_ = TCC.tcc_compile_string(state, @embedFile(("libtcc1.c")));
}

const Sizes = @import("../bindings/sizes.zig");
var symbol_buf: [256]u8 = undefined;
TCC.tcc_define_symbol(
state,
"Bun_FFI_PointerOffsetToArgumentsList",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{Sizes.Bun_FFI_PointerOffsetToArgumentsList}) catch unreachable,
);
const offsets = Offsets.get();
TCC.tcc_define_symbol(
state,
"JSArrayBufferView__offsetOfLength",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{offsets.JSArrayBufferView__offsetOfLength}) catch unreachable,
);
TCC.tcc_define_symbol(
state,
"JSArrayBufferView__offsetOfVector",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{offsets.JSArrayBufferView__offsetOfVector}) catch unreachable,
);
TCC.tcc_define_symbol(
state,
"JSCell__offsetOfType",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{offsets.JSCell__offsetOfType}) catch unreachable,
);
TCC.tcc_define_symbol(
state,
"JSTypeArrayBufferViewMin",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{@intFromEnum(JSC.JSValue.JSType.min_typed_array)}) catch unreachable,
);
TCC.tcc_define_symbol(
state,
"JSTypeArrayBufferViewMax",
std.fmt.bufPrintZ(&symbol_buf, "{d}", .{@intFromEnum(JSC.JSValue.JSType.max_typed_array)}) catch unreachable,
);
}

pub fn inject(state: *TCC.TCCState) void {
Expand Down
3 changes: 3 additions & 0 deletions src/bun.js/bindings/bindings.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3676,6 +3676,9 @@ pub const JSValue = enum(JSValueReprInt) {
JSAsJSONType = 0b11110000 | 1,
_,

pub const min_typed_array: JSType = .Int8Array;
pub const max_typed_array: JSType = .DataView;

pub fn canGet(this: JSType) bool {
return switch (this) {
.Array,
Expand Down
17 changes: 17 additions & 0 deletions src/bun.js/bindings/ffi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "root.h"

typedef struct FFIFields {
uint32_t JSArrayBufferView__offsetOfLength;
uint32_t JSArrayBufferView__offsetOfByteOffset;
uint32_t JSArrayBufferView__offsetOfVector;
uint32_t JSCell__offsetOfType;
} FFIFields;
extern "C" FFIFields Bun__FFI__offsets = { 0 };

extern "C" void Bun__FFI__ensureOffsetsAreLoaded()
{
Bun__FFI__offsets.JSArrayBufferView__offsetOfLength = JSC::JSArrayBufferView::offsetOfLength();
Bun__FFI__offsets.JSArrayBufferView__offsetOfByteOffset = JSC::JSArrayBufferView::offsetOfByteOffset();
Bun__FFI__offsets.JSArrayBufferView__offsetOfVector = JSC::JSArrayBufferView::offsetOfVector();
Bun__FFI__offsets.JSCell__offsetOfType = JSC::JSCell::typeInfoTypeOffset();
}
24 changes: 22 additions & 2 deletions src/js/bun/ffi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const FFIType = {
fn: 17,
napi_env: 18,
napi_value: 19,
buffer: 20,
};

const suffix = process.platform === "win32" ? "dll" : process.platform === "darwin" ? "dylib" : "so";
Expand Down Expand Up @@ -155,7 +156,7 @@ Object.defineProperty(globalThis, "__GlobalBunCString", {
configurable: false,
});

const ffiWrappers = new Array(20);
const ffiWrappers = new Array(21);

var char = "val|0";
ffiWrappers.fill(char);
Expand Down Expand Up @@ -281,14 +282,25 @@ Object.defineProperty(globalThis, "__GlobalBunFFIPtrFunctionForWrapper", {
enumerable: false,
configurable: true,
});
Object.defineProperty(globalThis, "__GlobalBunFFIPtrArrayBufferViewFn", {
value: function isTypedArrayView(val) {
return $isTypedArrayView(val);
},
enumerable: false,
configurable: true,
});

ffiWrappers[FFIType.cstring] = ffiWrappers[FFIType.pointer] = `{
if (typeof val === "number") return val;
if (!val) {
return null;
}

if (ArrayBuffer.isView(val) || val instanceof ArrayBuffer) {
if (__GlobalBunFFIPtrArrayBufferViewFn(val)) {
return val;
}

if (val instanceof ArrayBuffer) {
return __GlobalBunFFIPtrFunctionForWrapper(val);
}

Expand All @@ -299,6 +311,14 @@ ffiWrappers[FFIType.cstring] = ffiWrappers[FFIType.pointer] = `{
throw new TypeError(\`Unable to convert \${ val } to a pointer\`);
}`;

ffiWrappers[FFIType.buffer] = `{
if (!__GlobalBunFFIPtrArrayBufferViewFn(val)) {
throw new TypeError("Expected a TypedArray");
}

return val;
}`;

ffiWrappers[FFIType.function] = `{
if (typeof val === "number") {
return val;
Expand Down
4 changes: 4 additions & 0 deletions test/js/bun/ffi/cc-fixture.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading