diff options
author | 2022-05-02 20:26:18 -0700 | |
---|---|---|
committer | 2022-05-02 20:26:18 -0700 | |
commit | c6d732eee2721cd6191672cbe2c57fb17c3fffe4 (patch) | |
tree | da2716453ab256ea0b389b077ac7c98513abbfe7 | |
parent | 21ab47d9fe1085b7a1a959df810386367ebd5c23 (diff) | |
download | bun-c6d732eee2721cd6191672cbe2c57fb17c3fffe4.tar.gz bun-c6d732eee2721cd6191672cbe2c57fb17c3fffe4.tar.zst bun-c6d732eee2721cd6191672cbe2c57fb17c3fffe4.zip |
[bun:ffi] Improve `uint64_t` and `int64_t` performance
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.fixture.callback.c | 120 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.fixture.receiver.c | 125 | ||||
-rw-r--r-- | integration/bunjs-only-snippets/ffi.test.js | 181 | ||||
-rw-r--r-- | src/javascript/jsc/api/FFI.h | 114 | ||||
-rw-r--r-- | src/javascript/jsc/api/ffi.zig | 178 | ||||
-rw-r--r-- | src/javascript/jsc/ffi.exports.js | 21 |
6 files changed, 512 insertions, 227 deletions
diff --git a/integration/bunjs-only-snippets/ffi.test.fixture.callback.c b/integration/bunjs-only-snippets/ffi.test.fixture.callback.c index bc31d8b1c..97932e255 100644 --- a/integration/bunjs-only-snippets/ffi.test.fixture.callback.c +++ b/integration/bunjs-only-snippets/ffi.test.fixture.callback.c @@ -8,20 +8,15 @@ // It must be kept in sync with JSCJSValue.h // https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 #ifdef IS_CALLBACK -// #define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction); +#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call #endif #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 -// #include <stdint.h> -#ifdef INJECT_BEFORE -// #include <stdio.h> -#endif -// #include <tcclib.h> -// // /* 7.18.1.1 Exact-width integer types */ +// /* 7.18.1.1 Exact-width integer types */ typedef unsigned char uint8_t; typedef signed char int8_t; typedef short int16_t; @@ -30,7 +25,7 @@ typedef int int32_t; typedef unsigned int uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; -typedef uint64_t size_t; +typedef unsigned long long size_t; typedef long intptr_t; typedef uint64_t uintptr_t; typedef _Bool bool; @@ -39,6 +34,13 @@ typedef _Bool bool; #define false 0 +#ifdef INJECT_BEFORE +// #include <stdint.h> +#endif +// #include <tcclib.h> + + + // This value is 2^49, used to encode doubles such that the encoded value will // begin with a 15-bit pattern within the range 0x0002..0xFFFC. #define DoubleEncodeOffsetBit 49 @@ -50,6 +52,10 @@ typedef _Bool bool; #define TagValueTrue (OtherTag | BoolTag | true) #define TagValueUndefined (OtherTag | UndefinedTag) #define TagValueNull (OtherTag) +#define NotCellMask NumberTag | OtherTag + +#define MAX_INT32 2147483648 +#define MAX_INT52 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. @@ -59,8 +65,8 @@ typedef void* JSCell; typedef union EncodedJSValue { int64_t asInt64; -#if USE_JSVALUE32_64 -#elif USE_JSVALUE64 + +#if USE_JSVALUE64 JSCell *ptr; #endif @@ -85,7 +91,13 @@ EncodedJSValue ValueTrue = { TagValueTrue }; typedef void* JSContext; -#define LOAD_ARGUMENTS_FROM_CALL_FRAME EncodedJSValue* args = (EncodedJSValue*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList); +// Bun_FFI_PointerOffsetToArgumentsList is injected into the build +// The value is generated in `make sizegen` +// The value is 6. +// On ARM64_32, the value is something else but it really doesn't matter for our case +// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore +#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ + int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) #ifdef IS_CALLBACK @@ -94,11 +106,20 @@ JSContext cachedJSContext; void* cachedCallbackFunction; #endif -uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value); -int64_t JSVALUE_TO_INT64(EncodedJSValue value); +static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__)); +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); +uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value); +int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); + +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); -EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); @@ -112,6 +133,19 @@ 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 bool JSVALUE_IS_CELL(EncodedJSValue val) { + return !(val.asInt64 & NotCellMask); +} + +static bool JSVALUE_IS_INT32(EncodedJSValue val) { + return (val.asInt64 & NumberTag) == NumberTag; +} + +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { + return val.asInt64 & NumberTag; +} + + static void* JSVALUE_TO_PTR(EncodedJSValue val) { // must be a double return (void*)(val.asInt64 - DoubleEncodeOffset); @@ -165,9 +199,57 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) { return val.asInt64 == TagValueTrue; } -#define arg(i) ((EncodedJSValue*)args)[i] + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (uint64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (uint64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_UINT64_SLOW(globalObject, value); +} +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (int64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (int64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_INT64_SLOW(value); +} + +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { + if (val < MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val < MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return UINT64_TO_JSVALUE_SLOW(globalObject, val); +} + +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { + if (val >= -MAX_INT32 && val <= MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val >= -MAX_INT52 && val <= MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return INT64_TO_JSVALUE_SLOW(globalObject, val); +} + #ifndef IS_CALLBACK void* JSFunctionCall(void* globalObject, void* callFrame); + #endif @@ -178,14 +260,14 @@ void* JSFunctionCall(void* globalObject, void* callFrame); /* --- The Callback Function */ bool my_callback_function(void* arg0); -bool my_callback_function(, void* arg0) { +bool my_callback_function(void* arg0) { #ifdef INJECT_BEFORE INJECT_BEFORE; #endif EncodedJSValue arguments[1] = { -void* PTR_TO_JSVALUE(arg0) + PTR_TO_JSVALUE(arg0) }; - EncodedJSValue return_value = {bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, 1, arguments, 0)}; + EncodedJSValue return_value = {bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, 1, &arguments[0], (void*)0)}; return JSVALUE_TO_BOOL(return_value); } diff --git a/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c b/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c index 121f2d3a0..07d44c3a6 100644 --- a/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c +++ b/integration/bunjs-only-snippets/ffi.test.fixture.receiver.c @@ -1,3 +1,4 @@ +#define HAS_ARGUMENTS #define USES_FLOAT 1 // This file is part of Bun! // You can find the original source: @@ -8,20 +9,15 @@ // It must be kept in sync with JSCJSValue.h // https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 #ifdef IS_CALLBACK -// #define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction); +#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call #endif #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 -// #include <stdint.h> -#ifdef INJECT_BEFORE -// #include <stdio.h> -#endif -// #include <tcclib.h> -// // /* 7.18.1.1 Exact-width integer types */ +// /* 7.18.1.1 Exact-width integer types */ typedef unsigned char uint8_t; typedef signed char int8_t; typedef short int16_t; @@ -30,7 +26,7 @@ typedef int int32_t; typedef unsigned int uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; -typedef uint64_t size_t; +typedef unsigned long long size_t; typedef long intptr_t; typedef uint64_t uintptr_t; typedef _Bool bool; @@ -39,6 +35,13 @@ typedef _Bool bool; #define false 0 +#ifdef INJECT_BEFORE +// #include <stdint.h> +#endif +// #include <tcclib.h> + + + // This value is 2^49, used to encode doubles such that the encoded value will // begin with a 15-bit pattern within the range 0x0002..0xFFFC. #define DoubleEncodeOffsetBit 49 @@ -50,6 +53,10 @@ typedef _Bool bool; #define TagValueTrue (OtherTag | BoolTag | true) #define TagValueUndefined (OtherTag | UndefinedTag) #define TagValueNull (OtherTag) +#define NotCellMask NumberTag | OtherTag + +#define MAX_INT32 2147483648 +#define MAX_INT52 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. @@ -59,8 +66,8 @@ typedef void* JSCell; typedef union EncodedJSValue { int64_t asInt64; -#if USE_JSVALUE32_64 -#elif USE_JSVALUE64 + +#if USE_JSVALUE64 JSCell *ptr; #endif @@ -85,7 +92,13 @@ EncodedJSValue ValueTrue = { TagValueTrue }; typedef void* JSContext; -#define LOAD_ARGUMENTS_FROM_CALL_FRAME EncodedJSValue* args = (EncodedJSValue*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList); +// Bun_FFI_PointerOffsetToArgumentsList is injected into the build +// The value is generated in `make sizegen` +// The value is 6. +// On ARM64_32, the value is something else but it really doesn't matter for our case +// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore +#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ + int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) #ifdef IS_CALLBACK @@ -94,11 +107,20 @@ JSContext cachedJSContext; void* cachedCallbackFunction; #endif -uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value); -int64_t JSVALUE_TO_INT64(EncodedJSValue value); +static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__)); +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); +uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value); +int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); + +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); -EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); @@ -112,6 +134,19 @@ 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 bool JSVALUE_IS_CELL(EncodedJSValue val) { + return !(val.asInt64 & NotCellMask); +} + +static bool JSVALUE_IS_INT32(EncodedJSValue val) { + return (val.asInt64 & NumberTag) == NumberTag; +} + +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { + return val.asInt64 & NumberTag; +} + + static void* JSVALUE_TO_PTR(EncodedJSValue val) { // must be a double return (void*)(val.asInt64 - DoubleEncodeOffset); @@ -165,22 +200,72 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) { return val.asInt64 == TagValueTrue; } -#define arg(i) ((EncodedJSValue*)args)[i] + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (uint64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (uint64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_UINT64_SLOW(globalObject, value); +} +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (int64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (int64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_INT64_SLOW(value); +} + +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { + if (val < MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val < MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return UINT64_TO_JSVALUE_SLOW(globalObject, val); +} + +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { + if (val >= -MAX_INT32 && val <= MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val >= -MAX_INT52 && val <= MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return INT64_TO_JSVALUE_SLOW(globalObject, val); +} + #ifndef IS_CALLBACK void* JSFunctionCall(void* globalObject, void* callFrame); + #endif // --- Generated Code --- /* --- The Function To Call */ -float not_a_callback(); +float not_a_callback(float arg0); + /* ---- Your Wrapper Function ---- */ void* JSFunctionCall(void* globalObject, void* callFrame) { -#ifdef HAS_ARGUMENTS LOAD_ARGUMENTS_FROM_CALL_FRAME; -#endif - float return_value = not_a_callback(); + EncodedJSValue arg0; + arg0.asInt64 = *argsPtr; + float return_value = not_a_callback( JSVALUE_TO_FLOAT(arg0)); + return FLOAT_TO_JSVALUE(return_value).asPtr; } diff --git a/integration/bunjs-only-snippets/ffi.test.js b/integration/bunjs-only-snippets/ffi.test.js index b95bcfa8e..56e36d6e0 100644 --- a/integration/bunjs-only-snippets/ffi.test.js +++ b/integration/bunjs-only-snippets/ffi.test.js @@ -2,6 +2,7 @@ import { describe, it, expect } from "bun:test"; import { unsafe } from "bun"; // import { + native, viewSource, dlopen, CString, @@ -29,7 +30,7 @@ it("ffi print", async () => { { not_a_callback: { return_type: "float", - args: [], + args: ["float"], }, }, false @@ -338,12 +339,13 @@ it("ffi run", () => { } = dlopen("/tmp/bun-ffi-test.dylib", types); expect(returns_true()).toBe(true); + expect(returns_false()).toBe(false); + expect(returns_42_char()).toBe(42); expect(returns_42_uint64_t().valueOf()).toBe(42); 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); @@ -354,11 +356,16 @@ it("ffi run", () => { expect(returns_neg_42_int32_t()).toBe(-42); expect(identity_int32_t(10)).toBe(10); expect(returns_neg_42_int64_t()).toBe(-42); + expect(identity_char(10)).toBe(10); + expect(identity_float(10.199999809265137)).toBe(10.199999809265137); + expect(identity_bool(true)).toBe(true); + expect(identity_bool(false)).toBe(false); expect(identity_double(10.100000000000364)).toBe(10.100000000000364); + expect(identity_int8_t(10)).toBe(10); expect(identity_int16_t(10)).toBe(10); expect(identity_int64_t(10)).toBe(10); @@ -366,6 +373,7 @@ it("ffi run", () => { expect(identity_uint16_t(10)).toBe(10); expect(identity_uint32_t(10)).toBe(10); expect(identity_uint64_t(10)).toBe(10); + var bigArray = new BigUint64Array(8); new Uint8Array(bigArray.buffer).fill(255); var bigIntArray = new BigInt64Array(bigArray.buffer); @@ -411,100 +419,99 @@ it("ffi run", () => { expect(identity_ptr(cptr)).toBe(cptr); const second_ptr = ptr(new Buffer(8)); expect(identity_ptr(second_ptr)).toBe(second_ptr); - function identityBool() { - return true; - } - globalThis.identityBool = identityBool; + // function identityBool() { + // return true; + // } + // globalThis.identityBool = identityBool; - const first = callback( - { - return_type: "bool", - }, - identityBool - ); + // const first = native.callback( + // { + // return_type: "bool", + // }, + // identityBool + // ); + // expect( + // cb_identity_true(return_a_function_ptr_to_function_that_returns_true()) + // ).toBe(true); - expect( - cb_identity_true(return_a_function_ptr_to_function_that_returns_true()) - ).toBe(true); + // expect(cb_identity_true(first)).toBe(true); - expect(cb_identity_true(first)).toBe(true); + // expect( + // cb_identity_false( + // callback( + // { + // return_type: "bool", + // }, + // () => false + // ) + // ) + // ).toBe(false); - expect( - cb_identity_false( - callback( - { - return_type: "bool", - }, - () => false - ) - ) - ).toBe(false); + // expect( + // cb_identity_42_char( + // callback( + // { + // return_type: "char", + // }, + // () => 42 + // ) + // ) + // ).toBe(42); + // expect( + // cb_identity_42_uint8_t( + // callback( + // { + // return_type: "uint8_t", + // }, + // () => 42 + // ) + // ) + // ).toBe(42); - expect( - cb_identity_42_char( - callback( - { - return_type: "char", - }, - () => 42 - ) - ) - ).toBe(42); - expect( - cb_identity_42_uint8_t( - callback( - { - return_type: "uint8_t", - }, - () => 42 - ) - ) - ).toBe(42); - - cb_identity_neg_42_int8_t( - callback( - { - return_type: "int8_t", - }, - () => -42 - ) - ).toBe(-42); + // cb_identity_neg_42_int8_t( + // callback( + // { + // return_type: "int8_t", + // }, + // () => -42 + // ) + // ).toBe(-42); - cb_identity_42_uint16_t( - callback( - { - return_type: "uint16_t", - }, - () => 42 - ) - ).toBe(42); + // cb_identity_42_uint16_t( + // callback( + // { + // return_type: "uint16_t", + // }, + // () => 42 + // ) + // ).toBe(42); - cb_identity_42_uint32_t( - callback( - { - return_type: "uint32_t", - }, - () => 42 - ) - ).toBe(42); + // cb_identity_42_uint32_t( + // callback( + // { + // return_type: "uint32_t", + // }, + // () => 42 + // ) + // ).toBe(42); - cb_identity_neg_42_int16_t( - callback( - { - return_type: "int16_t", - }, - () => -42 - ) - ).toBe(-42); + // cb_identity_neg_42_int16_t( + // callback( + // { + // return_type: "int16_t", + // }, + // () => -42 + // ) + // ).toBe(-42); - cb_identity_neg_42_int32_t( - callback( - { - return_type: "int32_t", - }, - () => -42 - ) - ).toBe(-42); + // cb_identity_neg_42_int32_t( + // callback( + // { + // return_type: "int32_t", + // }, + // () => -42 + // ) + // ).toBe(-42); close(); }); diff --git a/src/javascript/jsc/api/FFI.h b/src/javascript/jsc/api/FFI.h index 69af59dc6..399448cae 100644 --- a/src/javascript/jsc/api/FFI.h +++ b/src/javascript/jsc/api/FFI.h @@ -7,20 +7,15 @@ // It must be kept in sync with JSCJSValue.h // https://github.com/Jarred-Sumner/WebKit/blob/72c2052b781cbfd4af867ae79ac9de460e392fba/Source/JavaScriptCore/runtime/JSCJSValue.h#L455-L458 #ifdef IS_CALLBACK -// #define INJECT_BEFORE printf("bun_call %p cachedJSContext %p cachedCallbackFunction %p\n", &bun_call, cachedJSContext, cachedCallbackFunction); +#define INJECT_BEFORE int c = 500; // This is a callback, so we need to inject code before the call #endif #define IS_BIG_ENDIAN 0 #define USE_JSVALUE64 1 #define USE_JSVALUE32_64 0 -// #include <stdint.h> -#ifdef INJECT_BEFORE -// #include <stdio.h> -#endif -// #include <tcclib.h> -// // /* 7.18.1.1 Exact-width integer types */ +// /* 7.18.1.1 Exact-width integer types */ typedef unsigned char uint8_t; typedef signed char int8_t; typedef short int16_t; @@ -29,7 +24,7 @@ typedef int int32_t; typedef unsigned int uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; -typedef uint64_t size_t; +typedef unsigned long long size_t; typedef long intptr_t; typedef uint64_t uintptr_t; typedef _Bool bool; @@ -38,6 +33,13 @@ typedef _Bool bool; #define false 0 +#ifdef INJECT_BEFORE +// #include <stdint.h> +#endif +// #include <tcclib.h> + + + // This value is 2^49, used to encode doubles such that the encoded value will // begin with a 15-bit pattern within the range 0x0002..0xFFFC. #define DoubleEncodeOffsetBit 49 @@ -49,6 +51,10 @@ typedef _Bool bool; #define TagValueTrue (OtherTag | BoolTag | true) #define TagValueUndefined (OtherTag | UndefinedTag) #define TagValueNull (OtherTag) +#define NotCellMask NumberTag | OtherTag + +#define MAX_INT32 2147483648 +#define MAX_INT52 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. @@ -58,8 +64,8 @@ typedef void* JSCell; typedef union EncodedJSValue { int64_t asInt64; -#if USE_JSVALUE32_64 -#elif USE_JSVALUE64 + +#if USE_JSVALUE64 JSCell *ptr; #endif @@ -84,7 +90,13 @@ EncodedJSValue ValueTrue = { TagValueTrue }; typedef void* JSContext; -#define LOAD_ARGUMENTS_FROM_CALL_FRAME EncodedJSValue* args = (EncodedJSValue*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList); +// Bun_FFI_PointerOffsetToArgumentsList is injected into the build +// The value is generated in `make sizegen` +// The value is 6. +// On ARM64_32, the value is something else but it really doesn't matter for our case +// However, I don't want this to subtly break amidst future upgrades to JavaScriptCore +#define LOAD_ARGUMENTS_FROM_CALL_FRAME \ + int64_t *argsPtr = (int64_t*)((size_t*)callFrame + Bun_FFI_PointerOffsetToArgumentsList) #ifdef IS_CALLBACK @@ -93,11 +105,20 @@ JSContext cachedJSContext; void* cachedCallbackFunction; #endif -uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value); -int64_t JSVALUE_TO_INT64(EncodedJSValue value); +static bool JSVALUE_IS_CELL(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_INT32(EncodedJSValue val) __attribute__((__always_inline__)); +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) __attribute__((__always_inline__)); + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) __attribute__((__always_inline__)); +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__)); +uint64_t JSVALUE_TO_UINT64_SLOW(void* globalObject, EncodedJSValue value); +int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value); + +EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* globalObject, uint64_t val); +EncodedJSValue INT64_TO_JSVALUE_SLOW(void* globalObject, int64_t val); +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) __attribute__((__always_inline__)); +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) __attribute__((__always_inline__)); -EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val); -EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val); static EncodedJSValue INT32_TO_JSVALUE(int32_t val) __attribute__((__always_inline__)); static EncodedJSValue DOUBLE_TO_JSVALUE(double val) __attribute__((__always_inline__)); @@ -111,6 +132,19 @@ 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 bool JSVALUE_IS_CELL(EncodedJSValue val) { + return !(val.asInt64 & NotCellMask); +} + +static bool JSVALUE_IS_INT32(EncodedJSValue val) { + return (val.asInt64 & NumberTag) == NumberTag; +} + +static bool JSVALUE_IS_NUMBER(EncodedJSValue val) { + return val.asInt64 & NumberTag; +} + + static void* JSVALUE_TO_PTR(EncodedJSValue val) { // must be a double return (void*)(val.asInt64 - DoubleEncodeOffset); @@ -164,9 +198,57 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) { return val.asInt64 == TagValueTrue; } -#define arg(i) ((EncodedJSValue*)args)[i] + +static uint64_t JSVALUE_TO_UINT64(void* globalObject, EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (uint64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (uint64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_UINT64_SLOW(globalObject, value); +} +static int64_t JSVALUE_TO_INT64(EncodedJSValue value) { + if (JSVALUE_IS_INT32(value)) { + return (int64_t)JSVALUE_TO_INT32(value); + } + + if (JSVALUE_IS_NUMBER(value)) { + return (int64_t)JSVALUE_TO_DOUBLE(value); + } + + return JSVALUE_TO_INT64_SLOW(value); +} + +static EncodedJSValue UINT64_TO_JSVALUE(void* globalObject, uint64_t val) { + if (val < MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val < MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return UINT64_TO_JSVALUE_SLOW(globalObject, val); +} + +static EncodedJSValue INT64_TO_JSVALUE(void* globalObject, int64_t val) { + if (val >= -MAX_INT32 && val <= MAX_INT32) { + return INT32_TO_JSVALUE((int32_t)val); + } + + if (val >= -MAX_INT52 && val <= MAX_INT52) { + return DOUBLE_TO_JSVALUE((double)val); + } + + return INT64_TO_JSVALUE_SLOW(globalObject, val); +} + #ifndef IS_CALLBACK void* JSFunctionCall(void* globalObject, void* callFrame); + #endif diff --git a/src/javascript/jsc/api/ffi.zig b/src/javascript/jsc/api/ffi.zig index 702d0d6d6..ce162f890 100644 --- a/src/javascript/jsc/api/ffi.zig +++ b/src/javascript/jsc/api/ffi.zig @@ -79,28 +79,6 @@ const ComptimeStringMap = @import("../../../comptime_string_map.zig").ComptimeSt const TCC = @import("../../../../tcc.zig"); -/// This is the entry point for generated FFI callback functions -/// We want to avoid potentially causing LLVM to not inline our regular calls to JSC.C.JSObjectCallAsFunction -/// to do that, we use a different pointer for the callback function -/// which is this noinline wrapper -noinline fn bun_call( - ctx: JSC.C.JSContextRef, - function: JSC.C.JSObjectRef, - count: usize, - argv: [*c]const JSC.C.JSValueRef, -) callconv(.C) JSC.C.JSObjectRef { - var exception = [1]JSC.C.JSValueRef{null}; - Output.debug("[bun_call] {d} args\n", .{count}); - return JSC.C.JSObjectCallAsFunction(ctx, function, JSC.JSValue.jsUndefined().asObjectRef(), count, argv, &exception); -} - -comptime { - if (!JSC.is_bindgen) { - _ = bun_call; - @export(bun_call, .{ .name = "bun_call" }); - } -} - pub const FFI = struct { dylib: std.DynLib, functions: std.StringArrayHashMapUnmanaged(Function) = .{}, @@ -596,6 +574,7 @@ pub const FFI = struct { JSVALUE_TO_UINT64: fn (JSValue0: JSC.JSValue) callconv(.C) u64, INT64_TO_JSVALUE: fn (arg0: [*c]JSC.JSGlobalObject, arg1: i64) callconv(.C) JSC.JSValue, UINT64_TO_JSVALUE: fn (arg0: [*c]JSC.JSGlobalObject, arg1: u64) callconv(.C) JSC.JSValue, + bun_call: *const @TypeOf(JSC.C.JSObjectCallAsFunction), }; const headers = @import("../bindings/headers.zig"); @@ -604,9 +583,10 @@ pub const FFI = struct { .JSVALUE_TO_UINT64 = headers.JSC__JSValue__toUInt64NoTruncate, .INT64_TO_JSVALUE = headers.JSC__JSValue__fromInt64NoTruncate, .UINT64_TO_JSVALUE = headers.JSC__JSValue__fromUInt64NoTruncate, + .bun_call = &JSC.C.JSObjectCallAsFunction, }; - const tcc_options = "-std=c11 -nostdlib -Wl,--export-all-symbols"; + const tcc_options = "-std=c11 -nostdlib -Wl,--export-all-symbols" ++ if (Environment.isDebug) " -g" else ""; pub fn compile( this: *Function, @@ -663,31 +643,7 @@ pub const FFI = struct { } CompilerRT.inject(state); _ = TCC.tcc_add_symbol(state, this.base_name, this.symbol_from_dynamic_library.?); - _ = TCC.tcc_add_symbol( - state, - "JSVALUE_TO_INT64", - workaround.JSVALUE_TO_INT64, - ); - _ = TCC.tcc_add_symbol( - state, - "JSVALUE_TO_UINT64", - workaround.JSVALUE_TO_UINT64, - ); - std.mem.doNotOptimizeAway(headers.JSC__JSValue__toUInt64NoTruncate); - std.mem.doNotOptimizeAway(headers.JSC__JSValue__toInt64); - std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromInt64NoTruncate); - std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromUInt64NoTruncate); - _ = TCC.tcc_add_symbol( - state, - "INT64_TO_JSVALUE", - workaround.INT64_TO_JSVALUE, - ); - _ = TCC.tcc_add_symbol( - state, - "UINT64_TO_JSVALUE", - workaround.UINT64_TO_JSVALUE, - ); if (this.step == .failed) { return; } @@ -731,7 +687,6 @@ pub const FFI = struct { }; return; } - const CompilerRT = struct { noinline fn memset( dest: [*]u8, @@ -752,6 +707,31 @@ pub const FFI = struct { pub fn inject(state: *TCC.TCCState) void { _ = TCC.tcc_add_symbol(state, "memset", &memset); _ = TCC.tcc_add_symbol(state, "memcpy", &memcpy); + + _ = TCC.tcc_add_symbol( + state, + "JSVALUE_TO_INT64_SLOW", + workaround.JSVALUE_TO_INT64, + ); + _ = TCC.tcc_add_symbol( + state, + "JSVALUE_TO_UINT64_SLOW", + workaround.JSVALUE_TO_UINT64, + ); + std.mem.doNotOptimizeAway(headers.JSC__JSValue__toUInt64NoTruncate); + std.mem.doNotOptimizeAway(headers.JSC__JSValue__toInt64); + std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromInt64NoTruncate); + std.mem.doNotOptimizeAway(headers.JSC__JSValue__fromUInt64NoTruncate); + _ = TCC.tcc_add_symbol( + state, + "INT64_TO_JSVALUE_SLOW", + workaround.INT64_TO_JSVALUE, + ); + _ = TCC.tcc_add_symbol( + state, + "UINT64_TO_JSVALUE_SLOW", + workaround.UINT64_TO_JSVALUE, + ); } }; @@ -797,12 +777,12 @@ pub const FFI = struct { return; } - Output.debug("here", .{}); + CompilerRT.inject(state); - _ = TCC.tcc_add_symbol(state, "bun_call", JSC.C.JSObjectCallAsFunction); + Output.debug("here", .{}); + _ = TCC.tcc_add_symbol(state, "bun_call", workaround.bun_call.*); _ = TCC.tcc_add_symbol(state, "cachedJSContext", js_context); _ = TCC.tcc_add_symbol(state, "cachedCallbackFunction", js_function); - CompilerRT.inject(state); var relocation_size = TCC.tcc_relocate(state, null); if (relocation_size == 0) return; @@ -826,14 +806,11 @@ pub const FFI = struct { return; }; - Output.debug("symbol: {*}", .{symbol}); - Output.debug("bun_call: {*}", .{&bun_call}); - Output.debug("js_function: {*}", .{js_function}); this.step = .{ .compiled = .{ .ptr = symbol, - .buf = &[_]u8{}, + .buf = bytes, .js_function = js_function, .js_context = js_context, }, @@ -884,18 +861,69 @@ pub const FFI = struct { try arg.typename(writer); try writer.print(" arg{d}", .{i}); } - try writer.writeAll(");\n\n"); - - // -- Generate JavaScriptCore's C wrapper function try writer.writeAll( + \\); + \\ + \\ \\/* ---- Your Wrapper Function ---- */ \\void* JSFunctionCall(void* globalObject, void* callFrame) { - \\#ifdef HAS_ARGUMENTS - \\ LOAD_ARGUMENTS_FROM_CALL_FRAME; - \\#endif \\ ); + if (this.arg_types.items.len > 0) { + try writer.writeAll( + \\ LOAD_ARGUMENTS_FROM_CALL_FRAME; + \\ + ); + for (this.arg_types.items) |arg, i| { + if (arg.needsACastInC()) { + if (i < this.arg_types.items.len - 1) { + try writer.print( + \\ EncodedJSValue arg{d}; + \\ arg{d}.asInt64 = *argsPtr++; + \\ + , + .{ + i, + i, + }, + ); + } else { + try writer.print( + \\ EncodedJSValue arg{d}; + \\ arg{d}.asInt64 = *argsPtr; + \\ + , + .{ + i, + i, + }, + ); + } + } else { + if (i < this.arg_types.items.len - 1) { + try writer.print( + \\ int64_t arg{d} = *argsPtr++; + \\ + , + .{ + i, + }, + ); + } else { + try writer.print( + \\ int64_t arg{d} = *argsPtr; + \\ + , + .{ + i, + }, + ); + } + } + } + } + // try writer.writeAll( // "(JSContext ctx, void* function, void* thisObject, size_t argumentCount, const EncodedJSValue arguments[], void* exception);\n\n", // ); @@ -909,7 +937,7 @@ pub const FFI = struct { } try writer.print("{s}(", .{std.mem.span(this.base_name)}); first = true; - arg_buf[0..4].* = "arg(".*; + arg_buf[0..3].* = "arg".*; for (this.arg_types.items) |arg, i| { if (!first) { try writer.writeAll(", "); @@ -917,9 +945,13 @@ pub const FFI = struct { first = false; try writer.writeAll(" "); - _ = std.fmt.bufPrintIntToSlice(arg_buf["arg(".len..], i, 10, .lower, .{}); - arg_buf["arg(N".len] = ')'; - try writer.print("{}", .{arg.toC(arg_buf[0..6])}); + const lengthBuf = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{}); + const argName = arg_buf[0 .. 3 + lengthBuf.len]; + if (arg.needsACastInC()) { + try writer.print("{}", .{arg.toC(argName)}); + } else { + try writer.writeAll(argName); + } } try writer.writeAll(");\n"); @@ -982,6 +1014,7 @@ pub const FFI = struct { } try writer.writeAll(");\n\n"); + first = true; try this.return_type.typename(writer); try writer.writeAll(" my_callback_function"); @@ -1010,7 +1043,6 @@ pub const FFI = struct { var arg_buf: [512]u8 = undefined; arg_buf[0.."arg".len].* = "arg".*; for (this.arg_types.items) |arg, i| { - try arg.typename(writer); const printed = std.fmt.bufPrintIntToSlice(arg_buf["arg".len..], i, 10, .lower, .{}); const arg_name = arg_buf[0 .. "arg".len + printed.len]; try writer.print(" {}", .{arg.toJS(arg_name)}); @@ -1025,7 +1057,7 @@ pub const FFI = struct { try writer.writeAll(" "); if (!(this.return_type == .void)) { - try writer.writeAll(" EncodedJSValue return_value = {"); + try writer.writeAll("EncodedJSValue return_value = {"); } // JSC.C.JSObjectCallAsFunction( // ctx, @@ -1037,9 +1069,9 @@ pub const FFI = struct { // ); try writer.writeAll("bun_call(cachedJSContext, cachedCallbackFunction, (void*)0, "); if (this.arg_types.items.len > 0) { - try writer.print("{d}, arguments, 0)", .{this.arg_types.items.len}); + try writer.print("{d}, &arguments[0], (void*)0)", .{this.arg_types.items.len}); } else { - try writer.writeAll("0, arguments, (void*)0)"); + try writer.writeAll("0, &arguments[0], (void*)0)"); } if (this.return_type != .void) { @@ -1076,6 +1108,14 @@ pub const FFI = struct { cstring = 14, + /// Types that we can directly pass through as an `int64_t` + pub fn needsACastInC(this: ABIType) bool { + return switch (this) { + .char, .int8_t, .uint8_t, .int16_t, .uint16_t, .int32_t, .uint32_t => false, + else => true, + }; + } + const map = .{ .{ "bool", ABIType.bool }, .{ "c_int", ABIType.int32_t }, diff --git a/src/javascript/jsc/ffi.exports.js b/src/javascript/jsc/ffi.exports.js index 8f6c47e41..949226436 100644 --- a/src/javascript/jsc/ffi.exports.js +++ b/src/javascript/jsc/ffi.exports.js @@ -72,7 +72,7 @@ ffiWrappers[FFIType.uint32_t] = function uint32(val) { }; ffiWrappers[FFIType.int64_t] = function int64(val) { if (typeof val === "bigint") { - if (val < Number.MAX_VALUE) { + if (val < BigInt(Number.MAX_VALUE)) { return Number(val).valueOf(); } } @@ -86,7 +86,7 @@ ffiWrappers[FFIType.int64_t] = function int64(val) { ffiWrappers[FFIType.uint64_t] = function int64(val) { if (typeof val === "bigint") { - if (val < Number.MAX_VALUE && val > 0) { + if (val < BigInt(Number.MAX_VALUE) && val > 0) { return Number(val).valueOf(); } } @@ -100,7 +100,7 @@ ffiWrappers[FFIType.uint64_t] = function int64(val) { ffiWrappers[FFIType.uint16_t] = function uint64(val) { if (typeof val === "bigint") { - if (val < Number.MAX_VALUE) { + if (val < BigInt(Number.MAX_VALUE)) { return Math.abs(Number(val).valueOf()); } } @@ -238,17 +238,6 @@ export function dlopen(path, options) { return result; } -export function callback(options) { - const result = nativeCallback(options); - - if (options.args || options.return_type) { - return FFIBuilder( - options.args ?? [], - options.return_type ?? FFIType.void, - result, - "callback" - ); - } - - return result; +export function callback(options, cb) { + return nativeCallback(options, cb); } |