Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/jsc/bindings/JSCFFIBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "root.h"

#include <JavaScriptCore/BunFFI.h>
#include <JavaScriptCore/FFIConversions.h>
#include <JavaScriptCore/FFISignature.h>
#include <JavaScriptCore/FFIType.h>
#include <JavaScriptCore/FFIContext.h>
Expand All @@ -17,6 +18,8 @@

static_assert(static_cast<uint8_t>(JSC::FFI::Type::Char) == 0, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Pointer) == 12, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::CString) == 14, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Function) == 17, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::JSValue) == 19, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Buffer) == 20, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::BufferLength) == 21, "FFI::Type tag drift");
Expand Down Expand Up @@ -119,3 +122,19 @@ extern "C" void Bun__JSCFFICallbackClose(JSC::EncodedJSValue callbackValue)
if (auto* callback = dynamicDowncast<JSC::JSFFICallback>(JSC::JSValue::decode(callbackValue)))
callback->close();
}

// JSVALUE_TO_PTR_SLOW in the wrappers cc() compiles (src/runtime/ffi/FFI.h).
extern "C" void* Bun__FFI__jsValueToPointerSlow(JSC::JSGlobalObject* globalObject, int32_t abiType, bool* threw, JSC::EncodedJSValue encodedValue)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);

uint64_t slot = 0;
// No string arena: nothing would free a transcoded cstring after the native call, so JS strings throw.
JSC::FFI::writeSlotFromJSValue(globalObject, globalObject->ffiContext(), static_cast<JSC::FFI::Type>(abiType), JSC::JSValue::decode(encodedValue), slot, /* arena */ nullptr);
if (scope.exception()) [[unlikely]] {
*threw = true;
return nullptr;
}
return reinterpret_cast<void*>(static_cast<uintptr_t>(slot));
}
41 changes: 27 additions & 14 deletions src/runtime/ffi/FFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ napi_value asNapiValue;

EncodedJSValue ValueUndefined = { TagValueUndefined };
EncodedJSValue ValueTrue = { TagValueTrue };
// What a host function returns after throwing; JSC unwinds to the pending exception and ignores it.
EncodedJSValue ValueEmpty = { 0 };

typedef void* JSContext;

Expand Down Expand Up @@ -159,7 +161,9 @@ static EncodedJSValue FLOAT_TO_JSVALUE(float val) __attribute__((__always_inline
static EncodedJSValue BOOLEAN_TO_JSVALUE(bool val) __attribute__((__always_inline__));
static EncodedJSValue PTR_TO_JSVALUE(void* ptr) __attribute__((__always_inline__));

static void* JSVALUE_TO_PTR(EncodedJSValue val) __attribute__((__always_inline__));
// The engine's conversion (the one dlopen()'d symbols use); on a non-pointer it throws and sets *threw.
void* JSVALUE_TO_PTR_SLOW(void* jsGlobalObject, int32_t abiType, bool* threw, int64_t val);
static void* JSVALUE_TO_PTR(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue val) __attribute__((__always_inline__));
static int32_t JSVALUE_TO_INT32(EncodedJSValue val) __attribute__((__always_inline__));
static float JSVALUE_TO_FLOAT(EncodedJSValue val) __attribute__((__always_inline__));
static double JSVALUE_TO_DOUBLE(EncodedJSValue val) __attribute__((__always_inline__));
Expand Down Expand Up @@ -207,21 +211,30 @@ static uint64_t JSVALUE_TO_TYPED_ARRAY_LENGTH(EncodedJSValue val) {
// Now, they're stored at the beginning of the 64-bit value
// This behavior change enables the JIT to handle it better
// It also is better readability when console.log(myPtr)
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);
static void* JSVALUE_TO_PTR(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue val) {
// TinyCC does not inline, so the tag tests are spelled out instead of calling the helpers above.
int64_t bits = val.asInt64;

if (!(bits & NotCellMask)) {
uint8_t type = *(uint8_t*)((char*)val.asPtr + JSCell__offsetOfType);
if (type >= JSTypeArrayBufferViewMin && type <= JSTypeArrayBufferViewMax)
return *(void**)((char*)val.asPtr + JSArrayBufferView__offsetOfVector);
} else if (abiType != ABI_TYPE_BUFFER) {
if ((bits & NumberTag) == NumberTag)
return (void*)(uintptr_t)(int32_t)bits;

if (bits & NumberTag) {
val.asInt64 = bits - DoubleEncodeOffset;
return (void*)(uintptr_t)(int64_t)val.asDouble;
}

// null and undefined are NULL, except as a callback, where the slow path throws like dlopen() does.
if ((bits & ~UndefinedTag) == TagValueNull && abiType != ABI_TYPE_FUNCTION)
return 0;
}

if (JSVALUE_IS_INT32(val)) {
return (void*)(uintptr_t)JSVALUE_TO_INT32(val);
}

// Assume the JSValue is a double
val.asInt64 -= DoubleEncodeOffset;
return (void*)(uintptr_t)val.asDouble;
// Other cells (JSCallback, ArrayBuffer, BigInt, objects with a `ptr`, strings), non-views for 'buffer', junk.
return JSVALUE_TO_PTR_SLOW(jsGlobalObject, abiType, threw, bits);
}

static EncodedJSValue PTR_TO_JSVALUE(void* ptr) {
Expand Down
60 changes: 38 additions & 22 deletions src/runtime/ffi/abi_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,28 +123,28 @@ static ABI_TABLE: [AbiRow; 22] = {
AbiRow { c_type, to_c_macro, to_js }
}
[
/* Char */ r(b"char", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int8T */ r(b"int8_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint8T */ r(b"uint8_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int16T */ r(b"int16_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint16T */ r(b"uint16_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int32T */ r(b"int32_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint32T */ r(b"uint32_t", Some("JSVALUE_TO_INT32("), Some(("UINT32_TO_JSVALUE(", ")"))),
/* Int64T */ r(b"int64_t", Some("JSVALUE_TO_INT64("), Some(("INT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Uint64T */ r(b"uint64_t", Some("JSVALUE_TO_UINT64("), Some(("UINT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Double */ r(b"double", Some("JSVALUE_TO_DOUBLE("), Some(("DOUBLE_TO_JSVALUE(", ")"))),
/* Float */ r(b"float", Some("JSVALUE_TO_FLOAT("), Some(("FLOAT_TO_JSVALUE(", ")"))),
/* Bool */ r(b"bool", Some("JSVALUE_TO_BOOL("), Some(("BOOLEAN_TO_JSVALUE(", ")"))),
/* Ptr */ r(b"void*", Some("JSVALUE_TO_PTR("), Some(("PTR_TO_JSVALUE(", ")"))),
/* Void */ r(b"void", None, None),
/* CString */ r(b"void*", Some("JSVALUE_TO_PTR("), Some(("PTR_TO_JSVALUE(", ")"))),
/* I64Fast */ r(b"int64_t", Some("JSVALUE_TO_INT64("), Some(("INT64_TO_JSVALUE(JS_GLOBAL_OBJECT, (int64_t)", ")"))),
/* U64Fast */ r(b"uint64_t", Some("JSVALUE_TO_UINT64("), Some(("UINT64_TO_JSVALUE(JS_GLOBAL_OBJECT, ", ")"))),
/* Function */ r(b"void*", Some("JSVALUE_TO_PTR("), Some(("PTR_TO_JSVALUE(", ")"))),
/* NapiEnv */ r(b"napi_env", None, None),
/* NapiValue */ r(b"napi_value", None, Some(("((EncodedJSValue) {.asNapiValue = ", " } )"))),
/* Buffer */ r(b"void*", Some("JSVALUE_TO_TYPED_ARRAY_VECTOR("), None),
/* BufferLen */ r(b"uint64_t", None, None),
/* Char */ r(b"char", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int8T */ r(b"int8_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint8T */ r(b"uint8_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int16T */ r(b"int16_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint16T */ r(b"uint16_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int32T */ r(b"int32_t", Some("JSVALUE_TO_INT32("), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint32T */ r(b"uint32_t", Some("JSVALUE_TO_INT32("), Some(("UINT32_TO_JSVALUE(", ")"))),
/* Int64T */ r(b"int64_t", Some("JSVALUE_TO_INT64("), Some(("INT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Uint64T */ r(b"uint64_t", Some("JSVALUE_TO_UINT64("), Some(("UINT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Double */ r(b"double", Some("JSVALUE_TO_DOUBLE("), Some(("DOUBLE_TO_JSVALUE(", ")"))),
/* Float */ r(b"float", Some("JSVALUE_TO_FLOAT("), Some(("FLOAT_TO_JSVALUE(", ")"))),
/* Bool */ r(b"bool", Some("JSVALUE_TO_BOOL("), Some(("BOOLEAN_TO_JSVALUE(", ")"))),
/* Ptr */ r(b"void*", Some("JSVALUE_TO_PTR(JS_GLOBAL_OBJECT, ABI_TYPE_PTR, &threw, "), Some(("PTR_TO_JSVALUE(", ")"))),
/* Void */ r(b"void", None, None),
/* CString */ r(b"void*", Some("JSVALUE_TO_PTR(JS_GLOBAL_OBJECT, ABI_TYPE_CSTRING, &threw, "), Some(("PTR_TO_JSVALUE(", ")"))),
/* I64Fast */ r(b"int64_t", Some("JSVALUE_TO_INT64("), Some(("INT64_TO_JSVALUE(JS_GLOBAL_OBJECT, (int64_t)", ")"))),
/* U64Fast */ r(b"uint64_t", Some("JSVALUE_TO_UINT64("), Some(("UINT64_TO_JSVALUE(JS_GLOBAL_OBJECT, ", ")"))),
/* Function */ r(b"void*", Some("JSVALUE_TO_PTR(JS_GLOBAL_OBJECT, ABI_TYPE_FUNCTION, &threw, "), Some(("PTR_TO_JSVALUE(", ")"))),
/* NapiEnv */ r(b"napi_env", None, None),
/* NapiValue */ r(b"napi_value", None, Some(("((EncodedJSValue) {.asNapiValue = ", " } )"))),
/* Buffer */ r(b"void*", Some("JSVALUE_TO_PTR(JS_GLOBAL_OBJECT, ABI_TYPE_BUFFER, &threw, "), None),
/* BufferLen */ r(b"uint64_t", None, None),
Comment thread
robobun marked this conversation as resolved.
Comment thread
robobun marked this conversation as resolved.
]
};

Expand All @@ -161,6 +161,14 @@ impl ABIType {
/// See [`ABI_TYPE_LABEL`].
pub(crate) const LABEL: &'static __ComptimeStringMap_ABI_TYPE_LABEL = &ABI_TYPE_LABEL;

/// `#define`d into every generated wrapper; `FFI.h` passes them to `JSVALUE_TO_PTR_SLOW`.
pub(crate) const POINTER_TAG_DEFINES: &'static [(&'static str, i64)] = &[
("ABI_TYPE_PTR", ABIType::Ptr as i64),
("ABI_TYPE_CSTRING", ABIType::CString as i64),
("ABI_TYPE_FUNCTION", ABIType::Function as i64),
("ABI_TYPE_BUFFER", ABIType::Buffer as i64),
];

/// Returns `None` for out-of-range discriminants.
#[inline]
pub(crate) const fn from_int(n: i32) -> Option<Self> {
Expand Down Expand Up @@ -209,6 +217,14 @@ impl ABIType {
matches!(self, ABIType::Double | ABIType::Float)
}

/// Argument conversions that go through `JSVALUE_TO_PTR_SLOW` (`FFI.h`) and so can throw.
pub(crate) fn arg_conversion_can_throw(self) -> bool {
matches!(
self,
ABIType::Ptr | ABIType::CString | ABIType::Function | ABIType::Buffer
)
}

pub(crate) fn to_c(self, symbol: &[u8]) -> ToCFormatter<'_> {
ToCFormatter { tag: self, symbol }
}
Expand Down
56 changes: 47 additions & 9 deletions src/runtime/ffi/ffi_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ fn create_jsc_ffi_function(

/// Raw extern fn pointers fed to the TCC-JIT'd C trampolines via `add_symbol`.
mod exposed_to_ffi {
use super::{JSGlobalObject, JSValue};
use super::{JSGlobalObject, JSValue, c_void};
unsafe extern "C" {
#[link_name = "JSC__JSValue__toInt64"]
pub(super) fn JSVALUE_TO_INT64(value: JSValue) -> i64;
Expand All @@ -161,6 +161,14 @@ mod exposed_to_ffi {
pub(super) fn INT64_TO_JSVALUE(global: *mut JSGlobalObject, i: i64) -> JSValue;
#[link_name = "JSC__JSValue__fromUInt64NoTruncate"]
pub(super) fn UINT64_TO_JSVALUE(global: *mut JSGlobalObject, i: u64) -> JSValue;
/// Slow path of `JSVALUE_TO_PTR` in `FFI.h`; defined in `JSCFFIBridge.cpp`.
#[link_name = "Bun__FFI__jsValueToPointerSlow"]
pub(super) fn JSVALUE_TO_PTR_SLOW(
global: *mut JSGlobalObject,
abi_type: i32,
threw: *mut bool,
value: JSValue,
) -> *mut c_void;
}
}

Expand Down Expand Up @@ -2062,6 +2070,8 @@ impl Function {
}

CompilerRT::define(state);
// Only the wrapper uses these; `CompilerRT::define` is also run for the user's own C.
state.define_symbols(ABIType::POINTER_TAG_DEFINES);

// SAFETY: source_code was NUL-terminated above
if state
Expand Down Expand Up @@ -2145,12 +2155,6 @@ impl Function {
ZIG_REPR_TYPE JSFunctionCall(void* JS_GLOBAL_OBJECT, void* callFrame) {\n",
)?;

if self.needs_handle_scope() {
writer.write_all(
b" void* handleScope = NapiHandleScope__open(&Bun__thisFFIModuleNapiEnv, false);\n",
)?;
}

if !self.arg_types.is_empty() {
writer.write_all(b" LOAD_ARGUMENTS_FROM_CALL_FRAME;\n")?;
for (i, arg) in self.arg_types.iter().enumerate() {
Expand Down Expand Up @@ -2195,6 +2199,33 @@ impl Function {
// );

let mut arg_buf = [0u8; 512];
arg_buf[0..3].copy_from_slice(b"arg");

// Pointer conversions can run JS (a `ptr` getter) and throw, so they come before the call.
let mut declared_threw = false;
for (i, arg) in self.arg_types.iter().enumerate() {
if !arg.arg_conversion_can_throw() {
continue;
}
if !declared_threw {
declared_threw = true;
writer.write_all(b" bool threw = false;\n")?;
}
let length_buf = bun_core::fmt::print_int(&mut arg_buf[3..], i);
let arg_name = &arg_buf[0..3 + length_buf];
write!(
writer,
" void* ptr{} = {};\n if (threw) return ValueEmpty.asZigRepr;\n",
i,
arg.to_c(arg_name)
)?;
}

if self.needs_handle_scope() {
writer.write_all(
b" void* handleScope = NapiHandleScope__open(&Bun__thisFFIModuleNapiEnv, false);\n",
)?;
}

writer.write_all(b" ")?;
if self.return_type != ABIType::Void {
Expand All @@ -2203,7 +2234,6 @@ impl Function {
}
write!(writer, "{}(", BStr::new(self.base_name.as_bytes()))?;
first = true;
arg_buf[0..3].copy_from_slice(b"arg");
for (i, arg) in self.arg_types.iter().enumerate() {
if !first {
writer.write_all(b", ")?;
Expand All @@ -2213,7 +2243,9 @@ impl Function {

let length_buf = bun_core::fmt::print_int(&mut arg_buf[3..], i);
let arg_name = &arg_buf[0..3 + length_buf];
if arg.needs_a_cast_in_c() {
if arg.arg_conversion_can_throw() {
write!(writer, "ptr{}", i)?;
} else if arg.needs_a_cast_in_c() {
write!(writer, "{}", arg.to_c(arg_name))?;
} else {
writer.write_all(arg_name)?;
Expand Down Expand Up @@ -2621,6 +2653,12 @@ impl CompilerRT {
WORKAROUND.uint64_to_jsvalue as *const c_void,
)
.expect("unreachable");
state
.add_symbol(
zstr!("JSVALUE_TO_PTR_SLOW"),
exposed_to_ffi::JSVALUE_TO_PTR_SLOW as *const c_void,
)
.expect("unreachable");
}
}

Expand Down
Loading