Compare commits

...

13 Commits

Author SHA1 Message Date
Ben Grant
43b7015da1 Fix typo 2024-11-22 12:35:30 -08:00
Ben Grant
3b3d906807 Add comment for debugtrap FFI function 2024-11-22 11:29:57 -08:00
Ben Grant
9162208179 Merge branch 'main' into ben/fix-ffi-test 2024-11-22 11:03:32 -08:00
Ben Grant
a08f000383 Work on testing threadsafe 2024-10-07 17:36:20 -07:00
Ben Grant
5c63f95ef6 Work on porting FFI test fixture to Zig 2024-10-07 14:44:03 -07:00
Ben Grant
4bde4bcdb5 Merge branch 'main' into ben/fix-ffi-test 2024-10-07 10:33:42 -07:00
Ben Grant
ae514ce0b5 Rewrite much of the ffi test 2024-10-03 17:16:39 -07:00
Ben Grant
7aa37cb155 Add test for compile error with bun cc 2024-10-03 17:10:32 -07:00
Ben Grant
099d2dfe80 Fix numeric limits in FFI.h 2024-10-03 17:09:25 -07:00
Ben Grant
d4ede69799 Fix memset/memcpy return value and test new implementations from bun cc 2024-10-03 17:08:53 -07:00
Ben Grant
f8ce2bfa42 Add ability to put breakpoints in FFI.h 2024-10-03 17:04:08 -07:00
Ben Grant
c4d3ef5e53 Add missing types to FFI.h 2024-10-03 17:02:03 -07:00
Ben Grant
e49d5828bc Document u64_fast/i64_fast 2024-10-03 16:58:32 -07:00
13 changed files with 670 additions and 346 deletions

View File

@@ -630,10 +630,6 @@ boringssl: boringssl-build boringssl-copy
.PHONY: boringssl-debug
boringssl-debug: boringssl-build-debug boringssl-copy
.PHONY: compile-ffi-test
compile-ffi-test:
clang $(OPTIMIZATION_LEVEL) -shared -undefined dynamic_lookup -o /tmp/bun-ffi-test.dylib -fPIC ./test/js/bun/ffi/ffi-test.c
sqlite:
.PHONY: zstd

View File

@@ -108,31 +108,33 @@ $ zig build-lib add.cpp -dynamic -lc -lc++
The following `FFIType` values are supported.
| `FFIType` | C Type | Aliases |
| ---------- | -------------- | --------------------------- |
| buffer | `char*` | |
| 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` | |
| `FFIType` | C Type | Aliases |
| ---------- | ------------ | --------------------------- |
| buffer | `char*` | |
| 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` | |
Note: `buffer` arguments must be a `TypedArray` or `DataView`.
`i64_fast` and `u64_fast` types are passed and returned as either a `Number`, or a `BigInt` when the numeric value is outside of the safe integer range (`Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`, or -(2^53 - 1) to 2^53 - 1 inclusive). `i64` and `u64` always use `BigInt`, which may be easier to work with (since it's a consistent type) but adds overhead in cases where the numeric value doesn't actually need `BigInt` to be represented.
## Strings
JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.

View File

@@ -25,8 +25,9 @@ typedef int int32_t;
typedef unsigned int uint32_t;
typedef long long int64_t;
typedef unsigned long long uint64_t;
typedef unsigned long long size_t;
typedef long intptr_t;
typedef int64_t ssize_t;
typedef uint64_t size_t;
typedef int64_t intptr_t;
typedef uint64_t uintptr_t;
typedef _Bool bool;
@@ -64,6 +65,8 @@ void* NapiHandleScope__open(void* jsGlobalObject, bool detached);
void NapiHandleScope__close(void* jsGlobalObject, void* handleScope);
#endif
// call this to cause a breakpoint, in case you need to debug functions in FFI.h
void __builtin_debugtrap(void);
#ifdef INJECT_BEFORE
// #include <stdint.h>
@@ -83,8 +86,12 @@ void NapiHandleScope__close(void* jsGlobalObject, void* handleScope);
#define TagValueNull (OtherTag)
#define NotCellMask (int64_t)(NumberTag | OtherTag)
#define MAX_INT32 2147483648
#define MAX_INT52 9007199254740991
// Smallest value that can be represented as a signed 32-bit integer
#define MIN_INT32 -2147483648
// Highest value that can be represented as a signed 32-bit integer
#define MAX_INT32 2147483647
// Same as Number.MAX_SAFE_INTEGER. Highest integer that is not the approximation of any other integer.
#define MAX_SAFE_INTEGER 9007199254740991
// If all bits in the mask are set, this indicates an integer number,
// if any but not all are set this value is a double precision number.
@@ -323,11 +330,11 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
}
static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
if (val < MAX_INT32) {
if (val <= MAX_INT32) {
return INT32_TO_JSVALUE((int32_t)val);
}
if (val < MAX_INT52) {
if (val <= MAX_SAFE_INTEGER) {
return DOUBLE_TO_JSVALUE((double)val);
}
@@ -335,11 +342,11 @@ static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
}
static EncodedJSValue INT64_TO_JSVALUE(void* jsGlobalObject, int64_t val) {
if (val >= -MAX_INT32 && val <= MAX_INT32) {
if (val >= MIN_INT32 && val <= MAX_INT32) {
return INT32_TO_JSVALUE((int32_t)val);
}
if (val >= -MAX_INT52 && val <= MAX_INT52) {
if (val >= -MAX_SAFE_INTEGER && val <= MAX_SAFE_INTEGER) {
return DOUBLE_TO_JSVALUE((double)val);
}

View File

@@ -2440,20 +2440,26 @@ const CompilerRT = struct {
.bun_call = &JSC.C.JSObjectCallAsFunction,
} else undefined;
noinline fn memset(
fn memset(
dest: [*]u8,
c: u8,
byte_count: usize,
) callconv(.C) void {
) callconv(.C) [*]u8 {
@memset(dest[0..byte_count], c);
return dest;
}
noinline fn memcpy(
fn memcpy(
noalias dest: [*]u8,
noalias source: [*]const u8,
byte_count: usize,
) callconv(.C) void {
) callconv(.C) [*]u8 {
@memcpy(dest[0..byte_count], source[0..byte_count]);
return dest;
}
fn breakpoint() callconv(.C) void {
@breakpoint();
}
pub fn define(state: *TCC.TCCState) void {
@@ -2503,6 +2509,12 @@ const CompilerRT = struct {
_ = TCC.tcc_add_symbol(state, "memcpy", &memcpy);
_ = TCC.tcc_add_symbol(state, "NapiHandleScope__open", &bun.JSC.napi.NapiHandleScope.NapiHandleScope__open);
_ = TCC.tcc_add_symbol(state, "NapiHandleScope__close", &bun.JSC.napi.NapiHandleScope.NapiHandleScope__close);
// If you need to debug the generated wrapper functions, you can insert calls to this
// function in src/bun.js/api/FFI.h or in the code generated here to have it stop in a
// debugger. You'll still be looking at a disassembly view, though, since TCC doesn't create
// debug info. Be absolutely sure to not leave those calls in when you're done, since they
// will just crash Bun if no debugger is running.
_ = TCC.tcc_add_symbol(state, "__builtin_debugtrap", &breakpoint);
_ = TCC.tcc_add_symbol(
state,

3
test/js/bun/ffi/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
ffi-test.dylib
ffi-test.so
ffi-test.dll

View File

@@ -0,0 +1 @@
int foo() { return does_not_exist; }

View File

@@ -9,6 +9,7 @@
#endif
#include <stdint.h>
#include <string.h>
#if __has_include(<node/node_api.h>)
@@ -50,4 +51,39 @@ int main() {
#endif
return 42;
}
}
bool memset_and_memcpy_work(void) {
char dst[10] = {0};
char src[10] = {0};
if (memset(src, 5, 9) != src) {
return false;
}
for (int i = 0; i < 9; i++) {
if (src[i] != 5) {
return false;
}
}
if (src[9] != 0) {
return false;
}
for (int i = 0; i < 10; i++) {
src[i] = i + 1;
}
if (memcpy(dst, src, 9) != dst) {
return false;
}
for (int i = 0; i < 9; i++) {
if (dst[i] != src[i]) {
return false;
}
}
if (dst[9] != 0) {
return false;
}
return true;
}

View File

@@ -1,10 +1,11 @@
import { cc } from "bun:ffi";
import fixture from "./cc-fixture.c" with { type: "file" };
import errorFixture from "./cc-compile-error-fixture.c" with { type: "file" };
let bytes = new Uint8Array(64);
bytes[bytes.length - 1] = 42;
const {
symbols: { napi_main, main, lastByte },
symbols: { napi_main, main, lastByte, memset_and_memcpy_work },
} = cc({
source: fixture,
define: {
@@ -24,6 +25,10 @@ const {
args: [],
returns: "int",
},
"memset_and_memcpy_work": {
args: [],
returns: "bool",
},
},
});
@@ -38,3 +43,18 @@ if (napi_main(null) !== "Hello, Napi!") {
if (lastByte(bytes, bytes.byteLength) !== 42) {
throw new Error("lastByte(bytes, bytes.length) !== 42");
}
if (!memset_and_memcpy_work()) {
throw new Error("memset/memcpy test detected error");
}
let threw = undefined;
try {
cc({ source: errorFixture, symbols: { foo: { args: [], returns: "i32" } } });
} catch (e) {
threw = e;
} finally {
if (threw === undefined) {
throw new Error("cc invalid C code did not throw an error");
}
}

View File

@@ -2,6 +2,7 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#define FFI_EXPORT __declspec(dllexport)
@@ -9,110 +10,6 @@
#define FFI_EXPORT __attribute__((visibility("default")))
#endif
FFI_EXPORT bool returns_true();
FFI_EXPORT bool returns_false();
FFI_EXPORT char returns_42_char();
FFI_EXPORT float returns_42_float();
FFI_EXPORT double returns_42_double();
FFI_EXPORT uint8_t returns_42_uint8_t();
FFI_EXPORT int8_t returns_neg_42_int8_t();
FFI_EXPORT uint16_t returns_42_uint16_t();
FFI_EXPORT uint32_t returns_42_uint32_t();
FFI_EXPORT uint64_t returns_42_uint64_t();
FFI_EXPORT int16_t returns_neg_42_int16_t();
FFI_EXPORT int32_t returns_neg_42_int32_t();
FFI_EXPORT int64_t returns_neg_42_int64_t();
FFI_EXPORT bool cb_identity_true(bool (*cb)());
FFI_EXPORT bool cb_identity_false(bool (*cb)());
FFI_EXPORT char cb_identity_42_char(char (*cb)());
FFI_EXPORT float cb_identity_42_float(float (*cb)());
FFI_EXPORT double cb_identity_42_double(double (*cb)());
FFI_EXPORT uint8_t cb_identity_42_uint8_t(uint8_t (*cb)());
FFI_EXPORT int8_t cb_identity_neg_42_int8_t(int8_t (*cb)());
FFI_EXPORT uint16_t cb_identity_42_uint16_t(uint16_t (*cb)());
FFI_EXPORT uint32_t cb_identity_42_uint32_t(uint32_t (*cb)());
FFI_EXPORT uint64_t cb_identity_42_uint64_t(uint64_t (*cb)());
FFI_EXPORT int16_t cb_identity_neg_42_int16_t(int16_t (*cb)());
FFI_EXPORT int32_t cb_identity_neg_42_int32_t(int32_t (*cb)());
FFI_EXPORT int64_t cb_identity_neg_42_int64_t(int64_t (*cb)());
FFI_EXPORT bool identity_bool_true();
FFI_EXPORT bool identity_bool_false();
FFI_EXPORT char identity_char(char a);
FFI_EXPORT float identity_float(float a);
FFI_EXPORT bool identity_bool(bool ident);
FFI_EXPORT double identity_double(double a);
FFI_EXPORT int8_t identity_int8_t(int8_t a);
FFI_EXPORT int16_t identity_int16_t(int16_t a);
FFI_EXPORT int32_t identity_int32_t(int32_t a);
FFI_EXPORT int64_t identity_int64_t(int64_t a);
FFI_EXPORT uint8_t identity_uint8_t(uint8_t a);
FFI_EXPORT uint16_t identity_uint16_t(uint16_t a);
FFI_EXPORT uint32_t identity_uint32_t(uint32_t a);
FFI_EXPORT uint64_t identity_uint64_t(uint64_t a);
FFI_EXPORT void *identity_ptr(void *ident);
FFI_EXPORT char add_char(char a, char b);
FFI_EXPORT float add_float(float a, float b);
FFI_EXPORT double add_double(double a, double b);
FFI_EXPORT int8_t add_int8_t(int8_t a, int8_t b);
FFI_EXPORT int16_t add_int16_t(int16_t a, int16_t b);
FFI_EXPORT int32_t add_int32_t(int32_t a, int32_t b);
FFI_EXPORT int64_t add_int64_t(int64_t a, int64_t b);
FFI_EXPORT uint8_t add_uint8_t(uint8_t a, uint8_t b);
FFI_EXPORT uint16_t add_uint16_t(uint16_t a, uint16_t b);
FFI_EXPORT uint32_t add_uint32_t(uint32_t a, uint32_t b);
FFI_EXPORT uint64_t add_uint64_t(uint64_t a, uint64_t b);
bool returns_false() { return false; }
bool returns_true() { return true; }
char returns_42_char() { return '*'; }
double returns_42_double() { return (double)42.42; }
float returns_42_float() { return 42.42f; }
int16_t returns_neg_42_int16_t() { return -42; }
int32_t returns_neg_42_int32_t() { return -42; }
int64_t returns_neg_42_int64_t() { return -42; }
int8_t returns_neg_42_int8_t() { return -42; }
uint16_t returns_42_uint16_t() { return 42; }
uint32_t returns_42_uint32_t() { return 42; }
uint64_t returns_42_uint64_t() { return 42; }
uint8_t returns_42_uint8_t() { return (uint8_t)42; }
char identity_char(char a) { return a; }
float identity_float(float a) { return a; }
double identity_double(double a) { return a; }
int8_t identity_int8_t(int8_t a) { return a; }
int16_t identity_int16_t(int16_t a) { return a; }
int32_t identity_int32_t(int32_t a) { return a; }
int64_t identity_int64_t(int64_t a) { return a; }
uint8_t identity_uint8_t(uint8_t a) { return a; }
uint16_t identity_uint16_t(uint16_t a) { return a; }
uint32_t identity_uint32_t(uint32_t a) { return a; }
uint64_t identity_uint64_t(uint64_t a) { return a; }
bool identity_bool(bool ident) { return ident; }
void *identity_ptr(void *ident) { return ident; }
char add_char(char a, char b) { return a + b; }
float add_float(float a, float b) { return a + b; }
double add_double(double a, double b) { return a + b; }
int8_t add_int8_t(int8_t a, int8_t b) { return a + b; }
int16_t add_int16_t(int16_t a, int16_t b) { return a + b; }
int32_t add_int32_t(int32_t a, int32_t b) { return a + b; }
int64_t add_int64_t(int64_t a, int64_t b) { return a + b; }
uint8_t add_uint8_t(uint8_t a, uint8_t b) { return a + b; }
uint16_t add_uint16_t(uint16_t a, uint16_t b) { return a + b; }
uint32_t add_uint32_t(uint32_t a, uint32_t b) { return a + b; }
uint64_t add_uint64_t(uint64_t a, uint64_t b) { return a + b; }
FFI_EXPORT void *ptr_should_point_to_42_as_int32_t();
void *ptr_should_point_to_42_as_int32_t() {
int32_t *ptr = malloc(sizeof(int32_t));
*ptr = 42;
return ptr;
}
static uint8_t buffer_with_deallocator[128];
static int deallocatorCalled;
FFI_EXPORT void deallocator(void *ptr, void *userData) { deallocatorCalled++; }
@@ -125,27 +22,3 @@ FFI_EXPORT void *getDeallocatorBuffer() {
return &buffer_with_deallocator;
}
FFI_EXPORT int getDeallocatorCalledCount() { return deallocatorCalled; }
FFI_EXPORT bool is_null(int32_t *ptr) { return ptr == NULL; }
FFI_EXPORT bool does_pointer_equal_42_as_int32_t(int32_t *ptr);
bool does_pointer_equal_42_as_int32_t(int32_t *ptr) { return *ptr == 42; }
FFI_EXPORT void *return_a_function_ptr_to_function_that_returns_true();
void *return_a_function_ptr_to_function_that_returns_true() {
return (void *)&returns_true;
}
FFI_EXPORT bool cb_identity_true(bool (*cb)()) { return cb(); }
FFI_EXPORT bool cb_identity_false(bool (*cb)()) { return cb(); }
FFI_EXPORT char cb_identity_42_char(char (*cb)()) { return cb(); }
FFI_EXPORT float cb_identity_42_float(float (*cb)()) { return cb(); }
FFI_EXPORT double cb_identity_42_double(double (*cb)()) { return cb(); }
FFI_EXPORT uint8_t cb_identity_42_uint8_t(uint8_t (*cb)()) { return cb(); }
FFI_EXPORT int8_t cb_identity_neg_42_int8_t(int8_t (*cb)()) { return cb(); }
FFI_EXPORT uint16_t cb_identity_42_uint16_t(uint16_t (*cb)()) { return cb(); }
FFI_EXPORT uint32_t cb_identity_42_uint32_t(uint32_t (*cb)()) { return cb(); }
FFI_EXPORT uint64_t cb_identity_42_uint64_t(uint64_t (*cb)()) { return cb(); }
FFI_EXPORT int16_t cb_identity_neg_42_int16_t(int16_t (*cb)()) { return cb(); }
FFI_EXPORT int32_t cb_identity_neg_42_int32_t(int32_t (*cb)()) { return cb(); }
FFI_EXPORT int64_t cb_identity_neg_42_int64_t(int64_t (*cb)()) { return cb(); }

View File

@@ -0,0 +1,224 @@
const std = @import("std");
const c = @cImport(@cInclude("string.h"));
fn exportAndTrack(
comptime function_names: *[]const []const u8,
comptime name: []const u8,
comptime function: anytype,
) void {
comptime std.debug.assert(@typeInfo(@TypeOf(function)) == .Fn);
function_names.* = function_names.* ++ &[_][]const u8{name};
@export(function, .{ .name = name });
}
/// if number is a floating-point type and NaN, change it to the canonical NaN for that type.
/// otherwise return number unchanged.
fn purifyNan(number: anytype) @TypeOf(number) {
const T = @TypeOf(number);
if (@typeInfo(T) == .Float and std.math.isNan(number)) {
return std.math.nan(T);
} else {
return number;
}
}
fn markAsCalled(name: []const u8) void {
for (functions_not_called, 0..) |uncalled_name, i| {
if (std.mem.eql(u8, name, uncalled_name)) {
std.mem.swap([]const u8, &functions_not_called[i], &functions_not_called[functions_not_called.len - 1]);
functions_not_called = functions_not_called[0 .. functions_not_called.len - 1];
break;
}
}
}
/// Export a function named `name` which records that it has been called and then returns `value`
fn exportReturns(
comptime function_names: *[]const []const u8,
comptime name: []const u8,
comptime T: type,
comptime value: T,
) void {
exportAndTrack(function_names, name, struct {
fn returns() callconv(.C) T {
markAsCalled(name);
return value;
}
}.returns);
}
/// Export a function named `name` which takes one parameter of type T and returns it. If T is a
/// floating-point type, NaN arguments are purified.
fn exportIdentity(
comptime function_names: *[]const []const u8,
comptime name: []const u8,
comptime T: type,
) void {
exportAndTrack(function_names, name, struct {
fn identity(x: T) callconv(.C) T {
markAsCalled(name);
return purifyNan(x);
}
}.identity);
}
/// Export a function named `name` which takes two parameters of type T and returns their sum.
fn exportAdd(
comptime function_names: *[]const []const u8,
comptime name: []const u8,
comptime T: type,
) void {
exportAndTrack(function_names, name, struct {
fn add(a: T, b: T) callconv(.C) T {
markAsCalled(name);
return a + b;
}
}.add);
}
/// Export a function named `name` which accepts a callback returning T, and returns what that
/// callback returns
fn exportCallbackIdentity(
comptime function_names: *[]const []const u8,
comptime name: []const u8,
comptime T: type,
) void {
exportAndTrack(function_names, name, struct {
fn entrypoint(cb: *const fn () callconv(.C) T, result: *T) void {
result.* = purifyNan(cb());
}
fn callbackIdentity(cb: *const fn () callconv(.C) T) callconv(.C) T {
markAsCalled(name);
var result: T = undefined;
var thread = std.Thread.spawn(.{}, entrypoint, .{ cb, &result }) catch unreachable;
thread.join();
return result;
}
}.callbackIdentity);
}
fn cName(comptime T: type) []const u8 {
return switch (T) {
f32 => "float",
f64 => "double",
c_char => "char",
else => {
const int = @typeInfo(T).Int;
return switch (int.signedness) {
.unsigned => std.fmt.comptimePrint("uint{}_t", .{int.bits}),
.signed => std.fmt.comptimePrint("int{}_t", .{int.bits}),
};
},
};
}
var @"42": i32 = 42;
fn returnPointerTo42() callconv(.C) *i32 {
markAsCalled("ptr_should_point_to_42_as_int32_t");
return &@"42";
}
fn memsetAndMemcpyWork() callconv(.C) bool {
markAsCalled("memset_and_memcpy_work");
var dst = [1]u8{0} ** 10;
var src = [1]u8{0} ** 10;
const dst_opaque: *anyopaque = @ptrCast(&dst);
const src_opaque: *anyopaque = @ptrCast(&src);
if (c.memset(&src, 5, 9) != src_opaque) {
return false;
}
// should set 9 items to 5 and leave the 10th unchanged
for (0..9) |i| {
if (src[i] != 5) return false;
}
if (src[9] != 0) return false;
// prepare some values
for (&src, 0..) |*s, i| {
s.* = @intCast(i + 1);
}
if (c.memcpy(&dst, &src, 9) != dst_opaque) {
return false;
}
// first 9 items of dst should match src
for (dst[0..9], src[0..9]) |d, s| {
if (d != s) return false;
}
// 10th item of dst should be unchanged
if (dst[9] != 0) return false;
return true;
}
fn returnTrueCallback() callconv(.C) bool {
markAsCalled("return_true_callback");
return true;
}
fn returnFunctionReturningTrue() callconv(.C) *const fn () callconv(.C) bool {
markAsCalled("return_a_function_ptr_to_function_that_returns_true");
return &returnTrueCallback;
}
fn isNull(ptr: ?*i32) callconv(.C) bool {
markAsCalled("is_null");
return ptr == null;
}
fn pointsTo42(ptr: ?*const i32) callconv(.C) bool {
markAsCalled("does_pointer_equal_42_as_int32_t");
return ptr.?.* == 42;
}
var all_function_names = blk: {
var function_names: []const []const u8 = &.{};
for (.{
u8, u16, u32, u64,
i8, i16, i32, i64,
f32, f64, c_char,
}) |T| {
if (T != c_char and @typeInfo(T) == .Int and @typeInfo(T).Int.signedness == .signed) {
exportReturns(&function_names, "returns_neg_42_" ++ cName(T), T, -42);
} else {
exportReturns(&function_names, "returns_42_" ++ cName(T), T, 42);
}
exportIdentity(&function_names, "identity_" ++ cName(T), T);
exportAdd(&function_names, "add_" ++ cName(T), T);
exportCallbackIdentity(&function_names, "cb_identity_" ++ cName(T), T);
}
exportReturns(&function_names, "returns_true", bool, true);
exportReturns(&function_names, "returns_false", bool, false);
exportIdentity(&function_names, "identity_bool", bool);
exportIdentity(&function_names, "identity_ptr", *anyopaque);
exportCallbackIdentity(&function_names, "cb_identity_bool", bool);
exportAndTrack(&function_names, "ptr_should_point_to_42_as_int32_t", returnPointerTo42);
exportAndTrack(&function_names, "memset_and_memcpy_work", memsetAndMemcpyWork);
exportAndTrack(&function_names, "return_true_callback", returnTrueCallback);
exportAndTrack(&function_names, "return_a_function_ptr_to_function_that_returns_true", returnFunctionReturningTrue);
exportAndTrack(&function_names, "does_pointer_equal_42_as_int32_t", pointsTo42);
break :blk function_names[0..].*;
};
var functions_not_called: [][]const u8 = &all_function_names;
export fn logUncalled() void {
if (functions_not_called.len > 0) {
std.debug.print("these functions were not called:\n", .{});
for (functions_not_called) |name| {
std.debug.print(" {s}\n", .{name});
}
}
}

View File

@@ -35,6 +35,37 @@ typedef _Bool bool;
#define true 1
#define false 0
#ifndef SRC_JS_NATIVE_API_TYPES_H_
typedef struct napi_env__ *napi_env;
typedef int64_t 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>
@@ -45,14 +76,14 @@ typedef _Bool bool;
// 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
@@ -70,6 +101,8 @@ typedef union EncodedJSValue {
JSCell *ptr;
#endif
napi_value asNapiValue;
#if IS_BIG_ENDIAN
struct {
int32_t tag;
@@ -140,6 +173,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);
@@ -153,6 +191,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
@@ -162,6 +219,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;
@@ -244,6 +306,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) {

View File

@@ -35,6 +35,37 @@ typedef _Bool bool;
#define true 1
#define false 0
#ifndef SRC_JS_NATIVE_API_TYPES_H_
typedef struct napi_env__ *napi_env;
typedef int64_t 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>
@@ -45,14 +76,14 @@ typedef _Bool bool;
// 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
@@ -70,6 +101,8 @@ typedef union EncodedJSValue {
JSCell *ptr;
#endif
napi_value asNapiValue;
#if IS_BIG_ENDIAN
struct {
int32_t tag;
@@ -140,6 +173,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);
@@ -153,6 +191,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
@@ -162,6 +219,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;
@@ -244,6 +306,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) {

View File

@@ -1,10 +1,11 @@
import { afterAll, describe, expect, it } from "bun:test";
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import { existsSync } from "fs";
import { isGlibcVersionAtLeast } from "harness";
import { platform } from "os";
import { spawnSync } from "bun";
import {
dlopen as _dlopen,
dlopen,
CFunction,
CString,
JSCallback,
@@ -16,15 +17,19 @@ import {
viewSource,
} from "bun:ffi";
const dlopen = (...args) => {
try {
return _dlopen(...args);
} catch (err) {
console.error("To enable this test, run `make compile-ffi-test`.");
throw err;
beforeAll(() => {
const platformFlags = platform() == "win32" ? [] : ["-fPIC", "-undefined", "dynamic_lookup"];
const compile = spawnSync({
cmd: ["clang", "-g3", "-O0", "-shared", ...platformFlags, "-o", `ffi-test.${suffix}`, "ffi-test.c"],
cwd: import.meta.dir,
stderr: "inherit",
stdout: "inherit",
stdin: "inherit",
});
if (!compile.success) {
throw new Error("failed to compile FFI tests");
}
};
const ok = existsSync("/tmp/bun-ffi-test." + suffix);
}, 5000);
it("ffi print", async () => {
await Bun.write(
@@ -243,55 +248,55 @@ function getTypes(fast) {
cb_identity_true: {
returns: "bool",
args: ["ptr"],
args: ["callback"],
},
cb_identity_false: {
returns: "bool",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_char: {
returns: "char",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_float: {
returns: "float",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_double: {
returns: "double",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_uint8_t: {
returns: "uint8_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_neg_42_int8_t: {
returns: "int8_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_uint16_t: {
returns: "uint16_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_uint32_t: {
returns: "uint32_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_42_uint64_t: {
returns: uint64_t,
args: ["ptr"],
args: ["callback"],
},
cb_identity_neg_42_int16_t: {
returns: "int16_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_neg_42_int32_t: {
returns: "int32_t",
args: ["ptr"],
args: ["callback"],
},
cb_identity_neg_42_int64_t: {
returns: int64_t,
args: ["ptr"],
args: ["callback"],
},
return_a_function_ptr_to_function_that_returns_true: {
@@ -311,178 +316,173 @@ function getTypes(fast) {
returns: "ptr",
args: [],
},
memset_and_memcpy_work: {
returns: "bool",
args: [],
},
};
}
function passCallbackThroughCIdentityFunction(cFunction, value, type) {
const cb = new JSCallback(() => value, { returns: type, args: [] });
const returned = cFunction(cb);
cb.close();
return returned;
}
function ffiRunner(fast) {
describe("FFI runner" + (fast ? " (fast int)" : ""), () => {
const types = getTypes(fast);
const {
symbols: {
returns_true,
returns_false,
return_a_function_ptr_to_function_that_returns_true,
returns_42_char,
returns_42_float,
returns_42_double,
returns_42_uint8_t,
returns_neg_42_int8_t,
returns_42_uint16_t,
returns_42_uint32_t,
returns_42_uint64_t,
returns_neg_42_int16_t,
returns_neg_42_int32_t,
returns_neg_42_int64_t,
identity_char,
identity_float,
identity_bool,
identity_double,
identity_int8_t,
identity_int16_t,
identity_int32_t,
identity_int64_t,
identity_uint8_t,
identity_uint16_t,
identity_uint32_t,
identity_uint64_t,
add_char,
add_float,
add_double,
add_int8_t,
add_int16_t,
add_int32_t,
add_int64_t,
add_uint8_t,
add_uint16_t,
identity_ptr,
add_uint32_t,
add_uint64_t,
is_null,
does_pointer_equal_42_as_int32_t,
ptr_should_point_to_42_as_int32_t,
cb_identity_true,
cb_identity_false,
cb_identity_42_char,
cb_identity_42_float,
cb_identity_42_double,
cb_identity_42_uint8_t,
cb_identity_neg_42_int8_t,
cb_identity_42_uint16_t,
cb_identity_42_uint32_t,
cb_identity_42_uint64_t,
cb_identity_neg_42_int16_t,
cb_identity_neg_42_int32_t,
cb_identity_neg_42_int64_t,
getDeallocatorCalledCount,
getDeallocatorCallback,
getDeallocatorBuffer,
},
let c = {},
close,
} = dlopen("/tmp/bun-ffi-test.dylib", types);
uncalledFunctions = [];
beforeAll(() => {
const lib = dlopen(import.meta.dir + `/ffi-test.${suffix}`, types);
uncalledFunctions.push(...Object.keys(lib.symbols));
for (const [name, fn] of Object.entries(lib.symbols)) {
c[name] = (...args) => {
if (uncalledFunctions.includes(name)) {
uncalledFunctions.splice(uncalledFunctions.indexOf(name), 1);
}
return fn(...args);
};
}
close = lib.close;
});
it("provides working builtin implementations", () => {
expect(c.memset_and_memcpy_work()).toBeTrue();
});
it("primitives", () => {
Bun.gc(true);
expect(returns_true()).toBe(true);
expect(c.returns_true()).toBe(true);
Bun.gc(true);
expect(returns_false()).toBe(false);
expect(c.returns_false()).toBe(false);
expect(returns_42_char()).toBe(42);
if (fast) expect(returns_42_uint64_t().valueOf()).toBe(42);
else expect(returns_42_uint64_t().valueOf()).toBe(42n);
expect(c.returns_42_char()).toBe(42);
if (fast) expect(c.returns_42_uint64_t()).toBe(42);
else expect(c.returns_42_uint64_t()).toBe(42n);
Bun.gc(true);
expect(Math.fround(returns_42_float())).toBe(Math.fround(42.41999804973602));
expect(returns_42_double()).toBe(42.42);
expect(returns_42_uint8_t()).toBe(42);
expect(returns_neg_42_int8_t()).toBe(-42);
expect(returns_42_uint16_t()).toBe(42);
expect(returns_42_uint32_t()).toBe(42);
if (fast) expect(returns_42_uint64_t()).toBe(42);
else expect(returns_42_uint64_t()).toBe(42n);
expect(returns_neg_42_int16_t()).toBe(-42);
expect(returns_neg_42_int32_t()).toBe(-42);
expect(identity_int32_t(10)).toBe(10);
expect(Math.fround(c.returns_42_float())).toBe(Math.fround(42.41999804973602));
expect(c.returns_42_double()).toBe(42.42);
expect(c.returns_42_uint8_t()).toBe(42);
expect(c.returns_neg_42_int8_t()).toBe(-42);
expect(c.returns_42_uint16_t()).toBe(42);
expect(c.returns_42_uint32_t()).toBe(42);
if (fast) expect(c.returns_42_uint64_t()).toBe(42);
else expect(c.returns_42_uint64_t()).toBe(42n);
expect(c.returns_neg_42_int16_t()).toBe(-42);
expect(c.returns_neg_42_int32_t()).toBe(-42);
expect(c.identity_int32_t(10)).toBe(10);
Bun.gc(true);
if (fast) expect(returns_neg_42_int64_t()).toBe(-42);
else expect(returns_neg_42_int64_t()).toBe(-42n);
if (fast) expect(c.returns_neg_42_int64_t()).toBe(-42);
else expect(c.returns_neg_42_int64_t()).toBe(-42n);
expect(identity_char(10)).toBe(10);
expect(c.identity_char(10)).toBe(10);
expect(identity_float(10.199999809265137)).toBe(10.199999809265137);
expect(c.identity_float(10.199999809265137)).toBe(10.199999809265137);
expect(c.identity_float(42)).toBe(42);
expect(identity_bool(true)).toBe(true);
expect(c.identity_bool(true)).toBe(true);
expect(identity_bool(false)).toBe(false);
expect(identity_double(10.100000000000364)).toBe(10.100000000000364);
expect(c.identity_bool(false)).toBe(false);
expect(c.identity_double(10.100000000000364)).toBe(10.100000000000364);
expect(c.identity_double(42)).toBe(42);
expect(identity_int8_t(10)).toBe(10);
expect(identity_int16_t(10)).toBe(10);
expect(c.identity_int8_t(10)).toBe(10);
expect(c.identity_int16_t(10)).toBe(10);
if (fast) expect(identity_int64_t(10)).toBe(10);
else expect(identity_int64_t(10)).toBe(10n);
expect(identity_uint8_t(10)).toBe(10);
expect(identity_uint16_t(10)).toBe(10);
expect(identity_uint32_t(10)).toBe(10);
if (fast) expect(identity_uint64_t(10)).toBe(10);
else expect(identity_uint64_t(10)).toBe(10n);
if (fast) expect(c.identity_int64_t(10)).toBe(10);
else expect(c.identity_int64_t(10)).toBe(10n);
expect(c.identity_uint8_t(10)).toBe(10);
expect(c.identity_uint16_t(10)).toBe(10);
expect(c.identity_uint32_t(10)).toBe(10);
if (fast) expect(c.identity_uint64_t(10)).toBe(10);
else expect(c.identity_uint64_t(10)).toBe(10n);
Bun.gc(true);
var bigArray = new BigUint64Array(8);
new Uint8Array(bigArray.buffer).fill(255);
var bigIntArray = new BigInt64Array(bigArray.buffer);
expect(identity_uint64_t(bigArray[0])).toBe(bigArray[0]);
expect(identity_uint64_t(bigArray[0] - BigInt(1))).toBe(bigArray[0] - BigInt(1));
expect(c.identity_uint64_t(bigArray[0])).toBe(bigArray[0]);
expect(c.identity_uint64_t(bigArray[0] - BigInt(1))).toBe(bigArray[0] - BigInt(1));
if (fast) {
expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0);
expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10);
expect(c.add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0);
expect(c.add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10);
} else {
expect(add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0n);
expect(add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10n);
expect(c.add_uint64_t(BigInt(-1) * bigArray[0], bigArray[0])).toBe(0n);
expect(c.add_uint64_t(BigInt(-1) * bigArray[0] + BigInt(10), bigArray[0])).toBe(10n);
}
if (fast) {
expect(identity_uint64_t(0)).toBe(0);
expect(identity_uint64_t(100)).toBe(100);
expect(identity_uint64_t(BigInt(100))).toBe(100);
expect(c.identity_uint64_t(0)).toBe(0);
expect(c.identity_uint64_t(100)).toBe(100);
expect(c.identity_uint64_t(BigInt(100))).toBe(100);
expect(identity_int64_t(bigIntArray[0])).toBe(-1);
expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(-2);
expect(c.identity_int64_t(bigIntArray[0])).toBe(-1);
expect(c.identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(-2);
} else {
expect(identity_uint64_t(0)).toBe(0n);
expect(identity_uint64_t(100)).toBe(100n);
expect(identity_uint64_t(BigInt(100))).toBe(100n);
expect(c.identity_uint64_t(0)).toBe(0n);
expect(c.identity_uint64_t(100)).toBe(100n);
expect(c.identity_uint64_t(BigInt(100))).toBe(100n);
expect(identity_int64_t(bigIntArray[0])).toBe(bigIntArray[0]);
expect(identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(bigIntArray[0] - BigInt(1));
expect(c.identity_int64_t(bigIntArray[0])).toBe(bigIntArray[0]);
expect(c.identity_int64_t(bigIntArray[0] - BigInt(1))).toBe(bigIntArray[0] - BigInt(1));
}
Bun.gc(true);
expect(add_char.native(1, 1)).toBe(2);
expect(c.add_char(1, 1)).toBe(2);
expect(add_float(2.4, 2.8)).toBe(Math.fround(5.2));
expect(add_double(4.2, 0.1)).toBe(4.3);
expect(add_int8_t(1, 1)).toBe(2);
expect(add_int16_t(1, 1)).toBe(2);
expect(add_int32_t(1, 1)).toBe(2);
if (fast) expect(add_int64_t(1, 1)).toBe(2);
else expect(add_int64_t(1n, 1n)).toBe(2n);
expect(add_uint8_t(1, 1)).toBe(2);
expect(add_uint16_t(1, 1)).toBe(2);
expect(add_uint32_t(1, 1)).toBe(2);
expect(c.add_float(2.4, 2.8)).toBe(Math.fround(5.2));
expect(c.add_double(4.2, 0.1)).toBe(4.3);
expect(c.add_int8_t(1, 1)).toBe(2);
expect(c.add_int16_t(1, 1)).toBe(2);
expect(c.add_int32_t(1, 1)).toBe(2);
if (fast) expect(c.add_int64_t(1, 1)).toBe(2);
else expect(c.add_int64_t(1n, 1n)).toBe(2n);
expect(c.add_uint8_t(1, 1)).toBe(2);
expect(c.add_uint16_t(1, 1)).toBe(2);
expect(c.add_uint32_t(1, 1)).toBe(2);
Bun.gc(true);
expect(is_null(null)).toBe(true);
const cptr = ptr_should_point_to_42_as_int32_t();
expect(c.is_null(null)).toBe(true);
const cptr = c.ptr_should_point_to_42_as_int32_t();
expect(cptr != 0).toBe(true);
expect(typeof cptr === "number").toBe(true);
expect(does_pointer_equal_42_as_int32_t(cptr)).toBe(true);
expect(c.does_pointer_equal_42_as_int32_t(cptr)).toBe(true);
const buffer = toBuffer(cptr, 0, 4);
expect(buffer.readInt32(0)).toBe(42);
expect(new DataView(toArrayBuffer(cptr, 0, 4), 0, 4).getInt32(0, true)).toBe(42);
expect(ptr(buffer)).toBe(cptr);
expect(new CString(cptr, 0, 1).toString()).toBe("*");
expect(identity_ptr(cptr)).toBe(cptr);
const second_ptr = ptr(new Buffer(8));
expect(identity_ptr(second_ptr)).toBe(second_ptr);
expect(new CString(cptr, 0, 1).toString()).toBe(String.fromCharCode(42));
expect(c.identity_ptr(cptr)).toBe(cptr);
const second_ptr = ptr(Buffer.alloc(8));
expect(c.identity_ptr(second_ptr)).toBe(second_ptr);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_true, true, "bool")).toBe(true);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_false, false, "bool")).toBe(false);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_char, 42, "char")).toBe(42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_float, 42, "f32")).toBe(42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_double, 42, "f64")).toBe(42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_uint8_t, 42, "u8")).toBe(42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_neg_42_int8_t, -42, "i8")).toBe(-42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_uint16_t, 42, "u16")).toBe(42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_42_uint32_t, 42, "u32")).toBe(42);
expect(
passCallbackThroughCIdentityFunction(c.cb_identity_42_uint64_t, fast ? 42 : 42n, fast ? "u64_fast" : "u64"),
).toBe(fast ? 42 : 42n);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_neg_42_int16_t, -42, "i16")).toBe(-42);
expect(passCallbackThroughCIdentityFunction(c.cb_identity_neg_42_int32_t, -42, "i32")).toBe(-42);
expect(
passCallbackThroughCIdentityFunction(
c.cb_identity_neg_42_int64_t,
fast ? -42 : -42n,
fast ? "i64_fast" : "i64",
),
).toBe(fast ? -42 : -42n);
});
it("CFunction", () => {
var myCFunction = new CFunction({
ptr: return_a_function_ptr_to_function_that_returns_true(),
ptr: c.return_a_function_ptr_to_function_that_returns_true(),
returns: "bool",
});
expect(myCFunction()).toBe(true);
@@ -577,22 +577,41 @@ function ffiRunner(fast) {
});
describe("integer identities work for all possible values", () => {
function limits(bits, signed) {
// 2^n
const twoN = 1n << BigInt(bits);
// 2^(n-1)
const twoNMinus1 = 1n << BigInt(bits - 1);
if (signed) {
if (bits > 32) {
return { min: -twoNMinus1, max: twoNMinus1 - 1n };
} else {
return { min: Number(-twoNMinus1), max: Number(twoNMinus1) - 1 };
}
} else {
if (bits > 32) {
return { min: 0n, max: twoN - 1n };
} else {
return { min: 0, max: Number(twoN) - 1 };
}
}
}
const cases = [
{ type: "int8_t", min: -128, max: 127, fn: identity_int8_t },
{ type: "int16_t", min: -32768, max: 32767, fn: identity_int16_t },
{ type: "int32_t", min: -2147483648, max: 2147483647, fn: identity_int32_t },
{ type: "int64_t", min: -9223372036854775808n, max: 9223372036854775807n, fn: identity_int64_t },
{ type: "uint8_t", min: 0, max: 255, fn: identity_uint8_t },
{ type: "uint16_t", min: 0, max: 65535, fn: identity_uint16_t },
{ type: "uint32_t", min: 0, max: 4294967295, fn: identity_uint32_t },
{ type: "uint64_t", min: 0n, max: 18446744073709551615n, fn: identity_uint64_t },
{ type: "int8_t", ...limits(8, true), fn: c.identity_int8_t },
{ type: "int16_t", ...limits(16, true), fn: c.identity_int16_t },
{ type: "int32_t", ...limits(32, true), fn: c.identity_int32_t },
{ type: fast ? "i64_fast" : "int64_t", ...limits(64, true), fn: c.identity_int64_t },
{ type: "uint8_t", ...limits(8, false), fn: c.identity_uint8_t },
{ type: "uint16_t", ...limits(16, false), fn: c.identity_uint16_t },
{ type: "uint32_t", ...limits(32, false), fn: c.identity_uint32_t },
{ type: fast ? "u64_fast" : "uint64_t", ...limits(64, false), fn: c.identity_uint64_t },
];
for (const { type, min, max, fn } of cases) {
const bigint = typeof min === "bigint";
const inc = bigint
? //
(max - min) / 32768n
const inc = bigint //
? (max - min) / 32768n
: Math.ceil((max - min) / 32768);
it(type, () => {
expect(bigint ? BigInt(fn(min)) : fn(min)).toBe(min);
@@ -608,6 +627,9 @@ function ffiRunner(fast) {
afterAll(() => {
close();
if (uncalledFunctions.length > 0) {
throw new Error(`these FFI functions were not tested:\n ${uncalledFunctions.join("\n ")}`);
}
});
});
}
@@ -645,14 +667,10 @@ it("read", () => {
delete globalThis.buffer;
});
if (ok) {
describe("run ffi", () => {
ffiRunner(false);
ffiRunner(true);
});
} else {
it.skip("run ffi", () => {});
}
describe("run ffi", () => {
ffiRunner(false);
ffiRunner(true);
});
it("dlopen throws an error instead of returning it", () => {
let err;