Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
25 changes: 25 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,23 @@ extern "C" void Bun__JSCFFICallbackClose(JSC::EncodedJSValue callbackValue)
if (auto* callback = dynamicDowncast<JSC::JSFFICallback>(JSC::JSValue::decode(callbackValue)))
callback->close();
}

// JSVALUE_TO_SLOT_SLOW for the wrappers cc() compiles (src/runtime/ffi/FFI.h): the conversion a
// dlopen()'d symbol's argument of the same type gets, for the values the wrapper does not decode
// inline. `abiType` is an ABIType discriminant, which is also the engine's tag (static_asserts
// above). A rejected value leaves the engine's TypeError pending and reports it through `threw`.
// No string arena is passed: nothing would free a transcoded string once the native call is over,
// so a type that would need one (`cstring` given a JS string) throws instead.
Comment thread
robobun marked this conversation as resolved.
Outdated
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;
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;
}
25 changes: 17 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,15 @@ 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 engine's argument conversion for the type tagged `abiType` (one of the ABI_TYPE_* tags the
// runtime defines when it compiles this file), i.e. what dlopen()'d symbols run for every argument.
// The conversions below decode the common encodings inline and come here for the rest. Returns the
// 64-bit argument slot, which the caller casts to the C type. A value the type does not accept
// throws a TypeError and sets `*threw`; the generated wrapper then returns without calling the
// native function.
Comment thread
robobun marked this conversation as resolved.
Outdated
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 +318,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 +331,11 @@ static uint64_t JSVALUE_TO_UINT64(EncodedJSValue value) {
return (uint64_t)JSVALUE_TO_TYPED_ARRAY_LENGTH(value);
}

return JSVALUE_TO_UINT64_SLOW(value);
// BigInt, or not a number at all (undefined, null, a boolean, an object, ...): the slow path
// converts the former and throws for the latter.
Comment thread
robobun marked this conversation as resolved.
Outdated
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 +344,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
105 changes: 71 additions & 34 deletions src/runtime/ffi/abi_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,44 +107,66 @@ bun_core::comptime_string_map! {
// ToCFormatter / ToJSFormatter. Indexed by `self as usize`.
// ─────────────────────────────────────────────────────────────────────────────

/// How a generated wrapper turns an argument's JSValue into the C parameter; the functions named
/// here are defined in `FFI.h`.
Comment thread
robobun marked this conversation as resolved.
Outdated
enum ToC {
/// Written out by [`ToCFormatter`] itself, or not an argument type.
Special,
/// `f(arg)`: decodes the JSValue's bits and cannot fail.
Infallible(&'static str),
/// `f(JS_GLOBAL_OBJECT, ABI_TYPE_*, &threw, arg)`: decodes the common encodings inline and
/// hands everything else to the engine's converter (`JSVALUE_TO_SLOT_SLOW`), which throws for
/// values the type does not accept. The wrapper runs these conversions before the native call
/// and returns as soon as one of them threw.
Comment thread
robobun marked this conversation as resolved.
Outdated
Fallible(&'static str),
}

struct AbiRow {
c_type: &'static [u8],
to_c_macro: Option<&'static str>,
/// `#define`d to the variant's discriminant, which is also the engine's tag for the type, when
/// a wrapper is compiled (`Function::compile`), so `FFI.h` and the generated code can name the
/// type they ask the engine's converter for.
Comment thread
robobun marked this conversation as resolved.
Outdated
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 +183,12 @@ impl ABIType {
/// See [`ABI_TYPE_LABEL`].
pub(crate) const LABEL: &'static __ComptimeStringMap_ABI_TYPE_LABEL = &ABI_TYPE_LABEL;

/// The `ABI_TYPE_*` preprocessor definitions every generated wrapper is compiled with, one per
/// variant; see [`AbiRow::tag_define`].
Comment thread
robobun marked this conversation as resolved.
Outdated
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 +237,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 +276,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
Loading