Skip to content

Commit

Permalink
Experiment: Add buffer type and inline pointer (#14036)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jarred-Sumner committed Sep 20, 2024
1 parent e938791 commit f263436
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 17 deletions.
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);

/// 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 @@ -3679,6 +3679,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

0 comments on commit f263436

Please sign in to comment.