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
21 changes: 21 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 @@ -16,7 +17,11 @@
#include "headers-handwritten.h"

static_assert(static_cast<uint8_t>(JSC::FFI::Type::Char) == 0, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Int64) == 7, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Uint64) == 8, "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::Int64Fast) == 15, "FFI::Type tag drift");
static_assert(static_cast<uint8_t>(JSC::FFI::Type::Uint64Fast) == 16, "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 +124,19 @@ extern "C" void Bun__JSCFFICallbackClose(JSC::EncodedJSValue callbackValue)
if (auto* callback = dynamicDowncast<JSC::JSFFICallback>(JSC::JSValue::decode(callbackValue)))
callback->close();
}

// JSVALUE_TO_SLOT_SLOW in src/runtime/ffi/FFI.h: the dlopen() argument conversion for cc() wrappers.
extern "C" uint64_t Bun__FFI__jsValueToSlotSlow(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 could free a transcoded `cstring` after the call, so JS strings throw.
JSC::FFI::writeSlotFromJSValue(globalObject, globalObject->ffiContext(), static_cast<JSC::FFI::Type>(abiType), JSC::JSValue::decode(encodedValue), slot, nullptr);
if (scope.exception()) [[unlikely]] {
*threw = true;
return 0;
}
return slot;
}
19 changes: 11 additions & 8 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 All @@ -142,10 +144,10 @@ 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(EncodedJSValue value) __attribute__((__always_inline__));
static int64_t JSVALUE_TO_INT64(EncodedJSValue value) __attribute__((__always_inline__));
uint64_t JSVALUE_TO_UINT64_SLOW(EncodedJSValue value);
int64_t JSVALUE_TO_INT64_SLOW(EncodedJSValue value);
// The dlopen() argument conversion for the ABI_TYPE_* type `abiType`; when it throws, `*threw` is set and the wrapper must return.
uint64_t JSVALUE_TO_SLOT_SLOW(void* jsGlobalObject, int32_t abiType, bool* threw, int64_t value);
static uint64_t JSVALUE_TO_UINT64(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue value) __attribute__((__always_inline__));
static int64_t JSVALUE_TO_INT64(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue value) __attribute__((__always_inline__));

EncodedJSValue UINT64_TO_JSVALUE_SLOW(void* jsGlobalObject, uint64_t val);
EncodedJSValue INT64_TO_JSVALUE_SLOW(void* jsGlobalObject, int64_t val);
Expand Down Expand Up @@ -311,7 +313,7 @@ static bool JSVALUE_TO_BOOL(EncodedJSValue val) {
}


static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
static uint64_t JSVALUE_TO_UINT64(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue value) {
if (JSVALUE_IS_INT32(value)) {
return (uint64_t)JSVALUE_TO_INT32(value);
}
Expand All @@ -324,9 +326,10 @@ static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
return (uint64_t)JSVALUE_TO_TYPED_ARRAY_LENGTH(value);
}

return JSVALUE_TO_UINT64_SLOW(value);
// A BigInt, or not a number at all (the slow path throws for those).
return JSVALUE_TO_SLOT_SLOW(jsGlobalObject, abiType, threw, value.asInt64);
}
static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
static int64_t JSVALUE_TO_INT64(void* jsGlobalObject, int32_t abiType, bool* threw, EncodedJSValue value) {
if (JSVALUE_IS_INT32(value)) {
return (int64_t)JSVALUE_TO_INT32(value);
}
Expand All @@ -335,7 +338,7 @@ static int64_t JSVALUE_TO_INT64(EncodedJSValue value) {
return (int64_t)JSVALUE_TO_DOUBLE(value);
}

return JSVALUE_TO_INT64_SLOW(value);
return (int64_t)JSVALUE_TO_SLOT_SLOW(jsGlobalObject, abiType, threw, value.asInt64);
}

static EncodedJSValue UINT64_TO_JSVALUE(void* jsGlobalObject, uint64_t val) {
Expand Down
98 changes: 64 additions & 34 deletions src/runtime/ffi/abi_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,44 +107,60 @@ bun_core::comptime_string_map! {
// ToCFormatter / ToJSFormatter. Indexed by `self as usize`.
// ─────────────────────────────────────────────────────────────────────────────

/// The `FFI.h` function a generated wrapper converts an argument with.
enum ToC {
/// Written out by [`ToCFormatter`] itself, or not an argument type.
Special,
/// `f(arg)`; cannot fail.
Infallible(&'static str),
/// `f(JS_GLOBAL_OBJECT, ABI_TYPE_*, &threw, arg)`; may throw via `JSVALUE_TO_SLOT_SLOW`.
Fallible(&'static str),
}

struct AbiRow {
c_type: &'static [u8],
to_c_macro: Option<&'static str>,
/// `#define`d to the discriminant (also the engine's type tag) by `Function::compile`.
tag_define: &'static str,
to_c: ToC,
to_js: Option<(&'static str, &'static str)>,
}

const ABI_TYPE_COUNT: usize = 22;

#[rustfmt::skip]
static ABI_TABLE: [AbiRow; 22] = {
static ABI_TABLE: [AbiRow; ABI_TYPE_COUNT] = {
use ToC::*;
const fn r(
c_type: &'static [u8],
to_c_macro: Option<&'static str>,
tag_define: &'static str,
to_c: ToC,
to_js: Option<(&'static str, &'static str)>,
) -> AbiRow {
AbiRow { c_type, to_c_macro, to_js }
AbiRow { c_type, tag_define, to_c, 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", "ABI_TYPE_CHAR", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int8T */ r(b"int8_t", "ABI_TYPE_I8", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint8T */ r(b"uint8_t", "ABI_TYPE_U8", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int16T */ r(b"int16_t", "ABI_TYPE_I16", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint16T */ r(b"uint16_t", "ABI_TYPE_U16", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Int32T */ r(b"int32_t", "ABI_TYPE_I32", Infallible("JSVALUE_TO_INT32"), Some(("INT32_TO_JSVALUE((int32_t)", ")"))),
/* Uint32T */ r(b"uint32_t", "ABI_TYPE_U32", Infallible("JSVALUE_TO_INT32"), Some(("UINT32_TO_JSVALUE(", ")"))),
/* Int64T */ r(b"int64_t", "ABI_TYPE_I64", Fallible("JSVALUE_TO_INT64"), Some(("INT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Uint64T */ r(b"uint64_t", "ABI_TYPE_U64", Fallible("JSVALUE_TO_UINT64"), Some(("UINT64_TO_JSVALUE_SLOW(JS_GLOBAL_OBJECT, ", ")"))),
/* Double */ r(b"double", "ABI_TYPE_F64", Infallible("JSVALUE_TO_DOUBLE"), Some(("DOUBLE_TO_JSVALUE(", ")"))),
/* Float */ r(b"float", "ABI_TYPE_F32", Infallible("JSVALUE_TO_FLOAT"), Some(("FLOAT_TO_JSVALUE(", ")"))),
/* Bool */ r(b"bool", "ABI_TYPE_BOOL", Infallible("JSVALUE_TO_BOOL"), Some(("BOOLEAN_TO_JSVALUE(", ")"))),
/* Ptr */ r(b"void*", "ABI_TYPE_PTR", Infallible("JSVALUE_TO_PTR"), Some(("PTR_TO_JSVALUE(", ")"))),
/* Void */ r(b"void", "ABI_TYPE_VOID", Special, None),
/* CString */ r(b"void*", "ABI_TYPE_CSTRING", Infallible("JSVALUE_TO_PTR"), Some(("PTR_TO_JSVALUE(", ")"))),
/* I64Fast */ r(b"int64_t", "ABI_TYPE_I64_FAST", Fallible("JSVALUE_TO_INT64"), Some(("INT64_TO_JSVALUE(JS_GLOBAL_OBJECT, (int64_t)", ")"))),
/* U64Fast */ r(b"uint64_t", "ABI_TYPE_U64_FAST", Fallible("JSVALUE_TO_UINT64"), Some(("UINT64_TO_JSVALUE(JS_GLOBAL_OBJECT, ", ")"))),
/* Function */ r(b"void*", "ABI_TYPE_FUNCTION", Infallible("JSVALUE_TO_PTR"), Some(("PTR_TO_JSVALUE(", ")"))),
/* NapiEnv */ r(b"napi_env", "ABI_TYPE_NAPI_ENV", Special, None),
/* NapiValue */ r(b"napi_value", "ABI_TYPE_NAPI_VALUE", Special, Some(("((EncodedJSValue) {.asNapiValue = ", " } )"))),
/* Buffer */ r(b"void*", "ABI_TYPE_BUFFER", Infallible("JSVALUE_TO_TYPED_ARRAY_VECTOR"), None),
/* BufferLen */ r(b"uint64_t", "ABI_TYPE_BUFFER_LENGTH", Special, None),
Comment thread
robobun marked this conversation as resolved.
]
};

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

/// One [`AbiRow::tag_define`] per variant, for `define_symbols`.
pub(crate) fn tag_defines() -> [(&'static str, i64); ABI_TYPE_COUNT] {
core::array::from_fn(|i| (ABI_TABLE[i].tag_define, i 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 +230,11 @@ impl ABIType {
matches!(self, ABIType::Double | ABIType::Float)
}

/// See [`ToC::Fallible`].
pub(crate) fn arg_conversion_can_throw(self) -> bool {
matches!(self.row().to_c, ToC::Fallible(_))
}

pub(crate) fn to_c(self, symbol: &[u8]) -> ToCFormatter<'_> {
ToCFormatter { tag: self, symbol }
}
Expand Down Expand Up @@ -243,17 +269,21 @@ pub struct ToCFormatter<'a> {
impl fmt::Display for ToCFormatter<'_> {
fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
let row = self.tag.row();
let Some(macro_) = row.to_c_macro else {
return match self.tag {
let symbol = BStr::new(self.symbol);
match row.to_c {
ToC::Infallible(function) => write!(writer, "{function}({symbol})"),
ToC::Fallible(function) => write!(
writer,
"{function}(JS_GLOBAL_OBJECT, {}, &threw, {symbol})",
row.tag_define
),
ToC::Special => match self.tag {
ABIType::Void => Ok(()),
ABIType::NapiEnv => writer.write_str("((napi_env)&Bun__thisFFIModuleNapiEnv)"),
ABIType::NapiValue => write!(writer, "{}.asNapiValue", BStr::new(self.symbol)),
ABIType::NapiValue => write!(writer, "{symbol}.asNapiValue"),
_ => unreachable!(),
};
};
writer.write_str(macro_)?;
fmt::Display::fmt(BStr::new(self.symbol), writer)?;
writer.write_str(")")
},
}
}
}

Expand Down
68 changes: 44 additions & 24 deletions src/runtime/ffi/ffi_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,14 @@ fn create_jsc_ffi_function(
mod exposed_to_ffi {
use super::{JSGlobalObject, JSValue};
unsafe extern "C" {
#[link_name = "JSC__JSValue__toInt64"]
pub(super) fn JSVALUE_TO_INT64(value: JSValue) -> i64;
#[link_name = "JSC__JSValue__toUInt64NoTruncate"]
pub(super) fn JSVALUE_TO_UINT64(value: JSValue) -> u64;
/// `JSCFFIBridge.cpp`; what the `ToC::Fallible` conversions in `FFI.h` fall back to.
#[link_name = "Bun__FFI__jsValueToSlotSlow"]
pub(super) fn JSVALUE_TO_SLOT_SLOW(
global: *mut JSGlobalObject,
abi_type: i32,
threw: *mut bool,
value: JSValue,
) -> u64;
#[link_name = "JSC__JSValue__fromInt64NoTruncate"]
pub(super) fn INT64_TO_JSVALUE(global: *mut JSGlobalObject, i: i64) -> JSValue;
#[link_name = "JSC__JSValue__fromUInt64NoTruncate"]
Expand Down Expand Up @@ -2062,6 +2066,8 @@ impl Function {
}

CompilerRT::define(state);
// Wrapper-only: the user's C (`CompileC::compile`) is compiled without these.
state.define_symbols(&ABIType::tag_defines());

// SAFETY: source_code was NUL-terminated above
if state
Expand Down Expand Up @@ -2145,12 +2151,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 +2195,35 @@ impl Function {
// );

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

// Converted first: a throw has to return before the native call and the handle scope.
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];
writer.write_all(b" ")?;
arg.param_typename(writer)?;
write!(
writer,
" converted{} = {};\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 +2232,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 +2241,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, "converted{}", 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 @@ -2599,14 +2629,8 @@ impl CompilerRT {

state
.add_symbol(
zstr!("JSVALUE_TO_INT64_SLOW"),
WORKAROUND.jsvalue_to_int64 as *const c_void,
)
.expect("unreachable");
state
.add_symbol(
zstr!("JSVALUE_TO_UINT64_SLOW"),
WORKAROUND.jsvalue_to_uint64 as *const c_void,
zstr!("JSVALUE_TO_SLOT_SLOW"),
exposed_to_ffi::JSVALUE_TO_SLOT_SLOW as *const c_void,
)
.expect("unreachable");
state
Expand All @@ -2625,15 +2649,11 @@ impl CompilerRT {
}

struct MyFunctionSStructWorkAround {
jsvalue_to_int64: unsafe extern "C" fn(JSValue) -> i64,
jsvalue_to_uint64: unsafe extern "C" fn(JSValue) -> u64,
int64_to_jsvalue: unsafe extern "C" fn(*mut JSGlobalObject, i64) -> JSValue,
uint64_to_jsvalue: unsafe extern "C" fn(*mut JSGlobalObject, u64) -> JSValue,
}

static WORKAROUND: MyFunctionSStructWorkAround = MyFunctionSStructWorkAround {
jsvalue_to_int64: exposed_to_ffi::JSVALUE_TO_INT64,
jsvalue_to_uint64: exposed_to_ffi::JSVALUE_TO_UINT64,
int64_to_jsvalue: exposed_to_ffi::INT64_TO_JSVALUE,
uint64_to_jsvalue: exposed_to_ffi::UINT64_TO_JSVALUE,
};
Expand Down
Loading
Loading