diff --git a/changelog.d/8464-closure-unwind-family.md b/changelog.d/8464-closure-unwind-family.md deleted file mode 100644 index 3f649fbace..0000000000 --- a/changelog.d/8464-closure-unwind-family.md +++ /dev/null @@ -1 +0,0 @@ -fix(runtime): the whole closure dispatch family is `extern "C-unwind"` now (#8463). #8416 let dynamic Function errors unwind but converted only `js_closure_call1`; every other edge (`call0`/`call3+`, `call2`'s inner transmute, `dispatch_with_arity`'s per-arity transmutes, `js_closure_call_array`/`_apply_with_spread`, the direct-call-site cache, `js_function_bind`) kept the abort-on-unwind shim, turning a deferred Function error into "panic in a function that cannot unwind" on Linux — the nightly `bun_ffi_stage1` abort. Registration entry points stay `extern "C"`. diff --git a/changelog.d/8482-revert-c-unwind-family.md b/changelog.d/8482-revert-c-unwind-family.md new file mode 100644 index 0000000000..5db4441914 --- /dev/null +++ b/changelog.d/8482-revert-c-unwind-family.md @@ -0,0 +1 @@ +revert: back out #8464's blanket `extern "C-unwind"` conversion of the closure dispatch family. Measured on three sweeps: main carried **16** gap regressions both before the batch (`526e0b502`) and with the string PRs but without it (`3627657c7`), and **36** with it (`15a30d7f6`) — plus a newly-red `gc-stress`. It also did not fix the abort it was written for (#8479 still reproduces on Linux with the change in place). Converting a frame to `C-unwind` turns LLVM `call` into `invoke` and lets unwinding actually run through frames that were never designed to be unwound through — which holds locks, raw pointers and GC state — so the conversion cost stability without buying the fix. diff --git a/crates/perry-runtime/src/closure/dispatch/bound.rs b/crates/perry-runtime/src/closure/dispatch/bound.rs index 9066f86a1d..69664b7c2c 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound.rs @@ -360,7 +360,7 @@ unsafe fn read_function_name_property(closure_ptr: usize) -> Option { /// `.name` is set to `"bound " + target.name` and `.length` to /// `max(0, target.length - boundArgs.length)`, matching Node. Refs #2840. #[no_mangle] -pub unsafe extern "C-unwind" fn js_function_bind( +pub unsafe extern "C" fn js_function_bind( target_value: f64, args_ptr: *const f64, args_len: usize, @@ -489,7 +489,7 @@ pub unsafe extern "C-unwind" fn js_function_bind( /// survives the bitcode pipeline. See project_auto_optimize_keepalive_3320. #[cfg(feature = "keepalive-anchors")] #[used] -static KEEP_JS_FUNCTION_BIND: unsafe extern "C-unwind" fn(f64, *const f64, usize) -> f64 = +static KEEP_JS_FUNCTION_BIND: unsafe extern "C" fn(f64, *const f64, usize) -> f64 = js_function_bind; /// Reify a `Function.prototype.{bind,call,apply}` (or any function method) diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index b8032eb938..22a50b8f6d 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -11,7 +11,7 @@ use super::*; /// Call a closure with 0 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call0(closure: *const ClosureHeader) -> f64 { +pub extern "C" fn js_closure_call0(closure: *const ClosureHeader) -> f64 { let func_ptr = get_valid_func_ptr(closure); if func_ptr.is_null() { return dispatch_proxy_callee_or_throw(closure, &[]); @@ -26,7 +26,7 @@ pub extern "C-unwind" fn js_closure_call0(closure: *const ClosureHeader) -> f64 dispatch_with_arity(closure, func_ptr, &[], declared) }, _ => { - let func: extern "C-unwind" fn(*const ClosureHeader) -> f64 = + let func: extern "C" fn(*const ClosureHeader) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure) } @@ -84,7 +84,7 @@ pub extern "C-unwind" fn js_closure_call2( dispatch_with_arity(closure, func_ptr, &[arg0, arg1], declared) }, _ => { - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1) } @@ -93,7 +93,7 @@ pub extern "C-unwind" fn js_closure_call2( /// Call a closure with 3 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call3( +pub extern "C" fn js_closure_call3( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -117,7 +117,7 @@ pub extern "C-unwind" fn js_closure_call3( dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2], declared) }, _ => { - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2) } @@ -126,7 +126,7 @@ pub extern "C-unwind" fn js_closure_call3( /// Call a closure with 4 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call4( +pub extern "C" fn js_closure_call4( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -157,7 +157,7 @@ pub extern "C-unwind" fn js_closure_call4( dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2, arg3], declared) }, _ => { - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2, arg3) } @@ -166,7 +166,7 @@ pub extern "C-unwind" fn js_closure_call4( /// Call a closure with 5 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call5( +pub extern "C" fn js_closure_call5( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -202,14 +202,14 @@ pub extern "C-unwind" fn js_closure_call5( }; } } - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64, f64, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2, arg3, arg4) } /// Call a closure with 6 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call6( +pub extern "C" fn js_closure_call6( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -251,7 +251,7 @@ pub extern "C-unwind" fn js_closure_call6( }; } } - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2, arg3, arg4, arg5) } @@ -291,7 +291,7 @@ pub(crate) fn dispatch_rest_or_declared_arity( /// Call a closure with 7 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call7( +pub extern "C" fn js_closure_call7( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -316,14 +316,14 @@ pub extern "C-unwind" fn js_closure_call7( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 7) { return result; } - let func: extern "C-unwind" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64, f64) -> f64 = + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6) } /// Call a closure with 8 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call8( +pub extern "C" fn js_closure_call8( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -349,23 +349,14 @@ pub extern "C-unwind" fn js_closure_call8( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 8) { return result; } - let func: extern "C-unwind" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; + let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 = + unsafe { std::mem::transmute(func_ptr) }; func(closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) } /// Call a closure with 9 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call9( +pub extern "C" fn js_closure_call9( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -392,7 +383,7 @@ pub extern "C-unwind" fn js_closure_call9( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 9) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -411,7 +402,7 @@ pub extern "C-unwind" fn js_closure_call9( /// Call a closure with 10 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call10( +pub extern "C" fn js_closure_call10( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -439,7 +430,7 @@ pub extern "C-unwind" fn js_closure_call10( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 10) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -459,7 +450,7 @@ pub extern "C-unwind" fn js_closure_call10( /// Call a closure with 11 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call11( +pub extern "C" fn js_closure_call11( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -494,7 +485,7 @@ pub extern "C-unwind" fn js_closure_call11( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 11) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -515,7 +506,7 @@ pub extern "C-unwind" fn js_closure_call11( /// Call a closure with 12 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call12( +pub extern "C" fn js_closure_call12( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -551,7 +542,7 @@ pub extern "C-unwind" fn js_closure_call12( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 12) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -573,7 +564,7 @@ pub extern "C-unwind" fn js_closure_call12( /// Call a closure with 13 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call13( +pub extern "C" fn js_closure_call13( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -610,7 +601,7 @@ pub extern "C-unwind" fn js_closure_call13( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 13) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -633,7 +624,7 @@ pub extern "C-unwind" fn js_closure_call13( /// Call a closure with 14 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call14( +pub extern "C" fn js_closure_call14( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -672,7 +663,7 @@ pub extern "C-unwind" fn js_closure_call14( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 14) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -697,7 +688,7 @@ pub extern "C-unwind" fn js_closure_call14( /// Call a closure with 15 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call15( +pub extern "C" fn js_closure_call15( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -739,7 +730,7 @@ pub extern "C-unwind" fn js_closure_call15( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 15) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, @@ -765,7 +756,7 @@ pub extern "C-unwind" fn js_closure_call15( /// Call a closure with 16 arguments, returning f64 #[no_mangle] -pub extern "C-unwind" fn js_closure_call16( +pub extern "C" fn js_closure_call16( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -808,7 +799,7 @@ pub extern "C-unwind" fn js_closure_call16( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 16) { return result; } - let func: extern "C-unwind" fn( + let func: extern "C" fn( *const ClosureHeader, f64, f64, diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index 3370905f6e..bffbef9a3d 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -89,7 +89,7 @@ macro_rules! define_direct_call_site { ) => { $(#[$meta])* #[derive(Clone, Copy)] - pub struct $site(Option f64>); + pub struct $site(Option f64>); impl $site { /// Resolve `closure` once, before the loop. @@ -98,7 +98,7 @@ macro_rules! define_direct_call_site { $site(resolve_direct_func_ptr(closure, $arity).map(|func_ptr| unsafe { std::mem::transmute::< *const u8, - extern "C-unwind" fn(*const ClosureHeader, $(define_direct_call_site!(@f64 $arg)),+) -> f64, + extern "C" fn(*const ClosureHeader, $(define_direct_call_site!(@f64 $arg)),+) -> f64, >(func_ptr) })) } @@ -174,11 +174,11 @@ mod tests { // A capture-less body behind a real `ClosureHeader`, the same way // `array/tests.rs` and `array/typed_array_receiver_tests.rs` build theirs. - extern "C-unwind" fn add3(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { + extern "C" fn add3(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { a * 100.0 + b * 10.0 + c } - extern "C-unwind" fn sum2(_c: *const ClosureHeader, a: f64, b: f64) -> f64 { + extern "C" fn sum2(_c: *const ClosureHeader, a: f64, b: f64) -> f64 { a + b } diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index f5ad0bc112..111450546b 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -390,7 +390,7 @@ pub unsafe extern "C-unwind" fn js_native_call_value( /// rather than xmm0 — matching the trampoline's `extern "C"` int-arg /// expectation. #[no_mangle] -pub unsafe extern "C-unwind" fn js_closure_call_array( +pub unsafe extern "C" fn js_closure_call_array( closure_env: i64, args_ptr: *const f64, args_len: i64, @@ -661,7 +661,7 @@ pub unsafe extern "C-unwind" fn js_closure_call_array( /// `lower_expr` produces for a closure-typed expression). A null/undefined /// box returns TAG_UNDEFINED. #[no_mangle] -pub unsafe extern "C-unwind" fn js_closure_call_apply_with_spread( +pub unsafe extern "C" fn js_closure_call_apply_with_spread( closure_box: f64, regular_args: *const f64, regular_count: i64, diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index 935a98c586..1f2a44c229 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -954,15 +954,14 @@ pub unsafe fn dispatch_with_arity( macro_rules! arm { (@ty $i:tt) => { f64 }; ($($i:tt),* $(,)?) => {{ - let f: extern "C-unwind" fn(*const ClosureHeader $(, arm!(@ty $i))*) -> f64 = + let f: extern "C" fn(*const ClosureHeader $(, arm!(@ty $i))*) -> f64 = std::mem::transmute(func_ptr); f(closure $(, a!($i))*) }}; } match k { 0 => { - let f: extern "C-unwind" fn(*const ClosureHeader) -> f64 = - std::mem::transmute(func_ptr); + let f: extern "C" fn(*const ClosureHeader) -> f64 = std::mem::transmute(func_ptr); f(closure) } 1 => arm!(0), diff --git a/crates/perry/tests/bun_ffi_stage1.rs b/crates/perry/tests/bun_ffi_stage1.rs index 9d0e9a072b..489145985b 100644 --- a/crates/perry/tests/bun_ffi_stage1.rs +++ b/crates/perry/tests/bun_ffi_stage1.rs @@ -1,15 +1,13 @@ -// #8463 / #8478: these fixtures exercise JS exceptions unwinding out of the -// FFI call path. Two layers must BOTH be `extern "C-unwind"`: -// * the closure dispatch family (`js_closure_call*`, `dispatch_with_arity`) -// — the callers, converted in #8464; -// * `bun_ffi::dlopen`'s `sym_thunk_*` / `close_thunk` — the callees, which -// are where `tier1_every_ffi_type_against_test_dylib`'s use-after-close -// throw actually originates (#8478). -// A single plain `extern "C"` edge anywhere on that path turns the throw into -// `panic in a function that cannot unwind` and aborts the binary on Linux. -// macOS does not reproduce it, so the Linux `e2e-scoped` run of this suite is -// the only real verdict — naming this file in a runtime diff is what opts the -// suite into that job. Do not merge a fix here before it has reported. +// #8479: `tier1_every_ffi_type_against_test_dylib` aborts on Linux with +// "panic in a function that cannot unwind" at the use-after-close throw +// (`lib.close()` then calling a symbol). The frame carrying the nounwind +// guard is STILL UNIDENTIFIED: converting the closure dispatch family +// (#8464) did not fix it and measurably regressed main, and converting +// `bun_ffi::dlopen`'s thunks (#8480) did not fix it either. macOS does not +// reproduce it at all, so the Linux `e2e-scoped` run of this suite is the +// only verdict that counts — naming this file in a runtime diff is what +// opts the suite into that job, and a candidate fix must not be merged +// before that job has reported green. //! bun:ffi stages 1-2 (#6562) — e2e: compile TS that dlopens real C-ABI //! dylibs and drive them through the typed call stubs. //! @@ -53,6 +51,12 @@ fn compile_and_run(dir: &Path, entry: &Path, envs: &[(&str, &str)]) -> (bool, St String::from_utf8_lossy(&compile.stderr) ); let mut run = Command::new(&output); + // #8479 diagnostic: the use-after-close abort reproduces only on Linux + // and the panic message alone does not name the frame carrying the + // nounwind guard — two candidate fixes (#8464, #8480) both left it + // aborting. Ask the child for a backtrace so CI names the frame instead + // of us guessing a third time. + run.env("RUST_BACKTRACE", "full"); for (k, v) in envs { run.env(k, v); }