mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 22:01:47 +00:00
Compare commits
13 Commits
claude/fix
...
ben/fix-ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43b7015da1 | ||
|
|
3b3d906807 | ||
|
|
9162208179 | ||
|
|
a08f000383 | ||
|
|
5c63f95ef6 | ||
|
|
4bde4bcdb5 | ||
|
|
ae514ce0b5 | ||
|
|
7aa37cb155 | ||
|
|
099d2dfe80 | ||
|
|
d4ede69799 | ||
|
|
f8ce2bfa42 | ||
|
|
c4d3ef5e53 | ||
|
|
e49d5828bc |
4
Makefile
4
Makefile
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
3
test/js/bun/ffi/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
ffi-test.dylib
|
||||
ffi-test.so
|
||||
ffi-test.dll
|
||||
1
test/js/bun/ffi/cc-compile-error-fixture.c
generated
Normal file
1
test/js/bun/ffi/cc-compile-error-fixture.c
generated
Normal file
@@ -0,0 +1 @@
|
||||
int foo() { return does_not_exist; }
|
||||
38
test/js/bun/ffi/cc-fixture.c
generated
38
test/js/bun/ffi/cc-fixture.c
generated
@@ -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;
|
||||
}
|
||||
|
||||
22
test/js/bun/ffi/cc-fixture.js
generated
22
test/js/bun/ffi/cc-fixture.js
generated
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); }
|
||||
224
test/js/bun/ffi/ffi-test.zig
Normal file
224
test/js/bun/ffi/ffi-test.zig
Normal 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user