diff --git a/changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md b/changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md index 4941f32a1c..ff896d7570 100644 --- a/changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md +++ b/changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md @@ -3,13 +3,15 @@ function object runs is `body(callee, this, a0, ...)`; its Rust type is defined once, in perry-abi (`js_body_fn_ty!`, `JsBody0`..`JsBody16`, `JsThis`), and shared by the runtime and perry-ffi. -- `perry_ffi::alloc_closure`, `register_closure_arity` and the new - `register_closure_rest` take a typed body (`F: JsBody`) - instead of a `*const u8`. A body with the wrong signature — no receiver, a - wrong parameter type, a bare pointer — is a compile error on every - platform. Migrate with a cast to the body's type: - `alloc_closure(my_body as perry_ffi::JsBody1, captures)`, where `my_body` - is `extern "C" fn(*const RawClosureHeader, JsThis, f64) -> f64`. +- `perry_ffi::alloc_closure(info, captures)` takes the body's + `&'static JsFunctionInfo`, built by `perry_ffi::js_function_info!(body, n)` + from the body's typed pointer (`JsBody0`..`JsBody16`) instead of a + `*const u8`. A body with the wrong signature — no receiver, a wrong + parameter type, a bare pointer — is a compile error on every platform. + `register_closure_arity` is removed: the parameter count comes from the + body's type, and other facts are builder calls on the info + (`js_function_info!(body, 1; with_rest(0))`). `my_body` is + `extern "C" fn(*const RawClosureHeader, JsThis, f64) -> f64`. - Calls into JS take the receiver after the function, `JsThis::UNDEFINED` for a plain call: `JsClosure::call0..4(this, ...)`, the new `JsClosure::call_slice(this, &args)` and `perry_ffi::call_value(func, @@ -21,8 +23,8 @@ defined once, in perry-abi (`js_body_fn_ty!`, `JsBody0`..`JsBody16`, which is published before it (`scripts/publish_perry_ffi.sh`). - `scripts/check_js_body_call_funnel.py` refuses an `extern` declaration of a call entry without the receiver (a stale declaration compiles and passes - garbage as `this`), a closure registrar declared outside the runtime, - stdlib and perry-ffi, and a perry-ffi registration function taking a + garbage as `this`), a closure allocator declared outside the runtime, + stdlib and perry-ffi, and a perry-ffi `alloc_closure` taking a `*const u8`. It found stale entry declarations in perry-stdlib, the UI crates and the runtime's own geisterhand registry. - Every in-tree perry-ffi user (the `perry-ext-*` crates, the UI crates, diff --git a/changelog.d/11654-fn-info-closure-body-registry.md b/changelog.d/11654-fn-info-closure-body-registry.md new file mode 100644 index 0000000000..12020f2077 --- /dev/null +++ b/changelog.d/11654-fn-info-closure-body-registry.md @@ -0,0 +1,37 @@ +**A function object points to its body's static `JsFunctionInfo`; the +closure-body registry is gone.** Every fact a caller needs about a body — +its code address, the parameter count a caller pads to, the rest kind and +its fixed prefix, `.length` and its declared fallback, the arrow / strict / +async / generator / built-in bits, and the compiler-private trusted-direct +and versioned-loop clones — is one immutable record per body (perry-abi +`JsFunctionInfo`), and the function object's header word at +8 points to it. +A fact is one load from the object; nothing is looked up by code address. + +- Codegen emits each body's info as a constant next to the body + (`@$info`), and every allocation names the info. A module declares + another module's info `external` instead of copying it, so a body has one + info and one singleton function object. +- Runtime natives and perry-ffi addons define the info as a `static` from + the body's typed pointer: `fn_info!(body, n; ...)` in the runtime and + stdlib, `perry_ffi::js_function_info!(body, n; ...)` in addons. The + parameter count comes from the body's type; a body of any other signature + does not compile. +- Deleted: `CLOSURE_BODY_REGISTRY`, `TRUSTED_TARGETS`, `DISPATCH_RECENT`, the + twelve `js_register_closure_*` entries (arity, rest, rest-and-arguments, + synthetic arguments, length, arrow, strict, async, generator, async + generator, trusted direct, versioned loop) with their ~885 references, and + every module-init registration call codegen emitted for them. A class + constructor's rest position travels with its class constructor flags. +- perry-ffi 0.6: `alloc_closure(info, captures)` takes + `&'static JsFunctionInfo`; `register_closure_arity` and + `register_closure_rest` are removed (the facts are builder calls on the + info: `with_rest(n)`, `with_length(n)`, ...). +- `ClosureHeader::code()` proves the cell is a live function object before it + loads through the info, so comparing an arbitrary receiver's code against a + known body stays a compare (it used to read the raw word at +8). +- `scripts/check_js_body_call_funnel.py` refuses a closure allocator handed + a raw code pointer, an allocator declaration whose first parameter is not + `*const JsFunctionInfo`, and any `js_register_closure_*` name or deleted + code-keyed table anywhere in the crates. +- `test-files/test_gap_fn_info_facts.ts` reads each info field through + generic calls (the function travels as a value), against node. diff --git a/crates/perry-abi/src/lib.rs b/crates/perry-abi/src/lib.rs index 076bf95638..5c358a16c8 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -31,12 +31,12 @@ pub const AGENT_PTR_STACK_LIMIT: usize = 2; pub const HOT_TLS_AGENT_PTRS_OFFSET: usize = 128; /// `closure::ClosureHeader` (LP64): the u32 capture count at 0, the ShapeId -/// at 4 (the same word as `ObjectHeader`), the code pointer at 8, the shaped -/// own-property record at 16, captures from 24. ILP32 targets shrink the two -/// pointers: code pointer at 8, props at 12, captures from 16 (derived in -/// `perry-codegen/src/target_layout.rs`). +/// at 4 (the same word as `ObjectHeader`), the function's +/// [`JsFunctionInfo`] pointer at 8, the shaped own-property record at 16, +/// captures from 24. ILP32 targets shrink the two pointers: info at 8, props +/// at 12, captures from 16 (derived in `perry-codegen/src/target_layout.rs`). pub const CLOSURE_SHAPE_OFFSET: usize = 4; -pub const CLOSURE_FUNC_PTR_OFFSET: usize = 8; +pub const CLOSURE_INFO_OFFSET: usize = 8; pub const CLOSURE_PROPS_OFFSET: usize = 16; pub const CLOSURE_HEADER_SIZE: usize = 24; @@ -128,7 +128,7 @@ macro_rules! js_body_fn_ty { } /// A JS body with a statically known JS arity: implemented for exactly the -/// [`js_body_fn_ty!`] pointer types (`JsBody0` .. `JsBody16`), so an API +/// [`js_body_fn_ty!`] pointer types (`JsBody0` .. `JsBody32`), so an API /// taking `impl JsBody` refuses any other signature — a bare `*const u8`, /// a body without the receiver, a wrong argument type — at compile time. /// @@ -142,6 +142,21 @@ pub unsafe trait JsBody: Copy { fn code(self) -> *const u8; } +/// [`js_body_fn_ty!`] with the `C-unwind` calling convention, for the few +/// runtime bodies a JS exception must be able to unwind through (the same +/// register ABI). +#[macro_export] +macro_rules! js_body_unwind_fn_ty { + (@f64 $x:tt) => { f64 }; + ($callee:ty; $($x:tt),* $(,)?) => { + unsafe extern "C-unwind" fn( + *const $callee, + $crate::JsThis + $(, $crate::js_body_unwind_fn_ty!(@f64 $x))* + ) -> f64 + }; +} + macro_rules! js_body_types { ($($alias:ident = $n:literal [$($x:tt),*];)*) => {$( #[doc = concat!("A JS body declaring ", stringify!($n), " JS parameters.")] @@ -157,6 +172,41 @@ macro_rules! js_body_types { )*}; } +macro_rules! js_body_unwind_types { + ($($alias:ident = $n:literal [$($x:tt),*];)*) => {$( + #[doc = concat!("A `C-unwind` JS body declaring ", stringify!($n), " JS parameters.")] + pub type $alias = js_body_unwind_fn_ty!(C; $($x),*); + // SAFETY: the pointer type is a JS body type by construction. + unsafe impl JsBody for $alias { + const ARITY: u32 = $n; + #[inline(always)] + fn code(self) -> *const u8 { + self as *const u8 + } + } + )*}; +} + +js_body_unwind_types! { + JsBodyUnwind0 = 0 []; + JsBodyUnwind1 = 1 [a]; + JsBodyUnwind2 = 2 [a, a]; + JsBodyUnwind3 = 3 [a, a, a]; + JsBodyUnwind4 = 4 [a, a, a, a]; + JsBodyUnwind5 = 5 [a, a, a, a, a]; + JsBodyUnwind6 = 6 [a, a, a, a, a, a]; + JsBodyUnwind7 = 7 [a, a, a, a, a, a, a]; + JsBodyUnwind8 = 8 [a, a, a, a, a, a, a, a]; + JsBodyUnwind9 = 9 [a, a, a, a, a, a, a, a, a]; + JsBodyUnwind10 = 10 [a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind11 = 11 [a, a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind12 = 12 [a, a, a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind13 = 13 [a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind14 = 14 [a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind15 = 15 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBodyUnwind16 = 16 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; +} + js_body_types! { JsBody0 = 0 []; JsBody1 = 1 [a]; @@ -175,6 +225,215 @@ js_body_types! { JsBody14 = 14 [a, a, a, a, a, a, a, a, a, a, a, a, a, a]; JsBody15 = 15 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; JsBody16 = 16 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody17 = 17 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody18 = 18 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody19 = 19 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody20 = 20 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody21 = 21 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody22 = 22 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody23 = 23 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody24 = 24 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody25 = 25 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody26 = 26 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody27 = 27 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody28 = 28 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody29 = 29 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody30 = 30 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody31 = 31 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody32 = 32 [a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a]; +} + +/// Everything a function object's BODY determines, in one static, immutable +/// record per body (the SharedFunctionInfo model): the code address and the +/// facts every caller of the body needs. A function object points to its +/// body's info from its header ([`CLOSURE_INFO_OFFSET`]); nothing is ever +/// looked up by code address. Codegen emits one as a constant next to each +/// body; runtime natives and perry-ffi addons declare one as a `static` +/// ([`JsFunctionInfo::of`]), so the parameter count comes from the body's +/// type. +/// +/// Layout (LP64, pinned by `JS_FUNCTION_INFO_*` below; codegen emits it as +/// `{ ptr, i16, i16, i32, i32, i32, ptr, i64, ptr, i32, i16, i16, i64 }`). +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct JsFunctionInfo { + /// The body's code address (a function-table index on wasm32). + pub code: *const u8, + /// JS `f64` parameters the body declares after the callee and receiver — + /// a rest body's rest array included. A caller passing fewer pads with + /// `undefined` to this count. + pub params: u16, + /// Fixed parameters before the rest parameter (valid with `FN_REST_*`). + pub rest_fixed: u16, + /// `FN_*` bits. + pub flags: u32, + /// ECMAScript `.length` (valid with [`FN_HAS_LENGTH`]). + pub length: u32, + /// Captures the trusted direct-call clone was compiled for. + pub trusted_captures: u32, + /// A compiler-private direct-call clone of an arrow body that may use + /// compiler-proven capture invariants (null when none). + pub trusted_code: *const u8, + /// The trusted clone's boxed-capture mask. + pub trusted_boxed_mask: u64, + /// A compiler-private versioned-loop callback clone (null when none). + pub versioned_code: *const u8, + /// Captures the versioned-loop clone was compiled for. + pub versioned_captures: u32, + /// The JS-visible declared parameter count (valid with + /// [`FN_HAS_DECLARED`]): what `.length` falls back to. It can differ + /// from `params`, the ABI width a caller pads to. + pub declared: u16, + /// Padding (zero). Private, so an info can only be built through + /// [`JsFunctionInfo::of`] (typed) or the `unsafe` + /// [`JsFunctionInfo::from_code`]. + reserved: u16, + /// The versioned-loop clone's boxed-capture mask. + pub versioned_boxed_mask: u64, +} + +// SAFETY: immutable after construction; the pointers are code addresses. +unsafe impl Sync for JsFunctionInfo {} + +/// `JsFunctionInfo::flags`: the body has a rest parameter bundling every JS +/// argument from `rest_fixed` on (`...rest`). +pub const FN_REST_USER: u32 = 1 << 0; +/// The body takes a synthetic `arguments` array of every argument. +pub const FN_REST_SYNTHETIC_ARGUMENTS: u32 = 1 << 1; +/// The body takes both a `...rest` array and a synthetic `arguments` array. +pub const FN_REST_USER_AND_ARGUMENTS: u32 = 1 << 2; +/// Any rest kind. +pub const FN_REST_MASK: u32 = + FN_REST_USER | FN_REST_SYNTHETIC_ARGUMENTS | FN_REST_USER_AND_ARGUMENTS; +/// `length` is valid. +pub const FN_HAS_LENGTH: u32 = 1 << 3; +/// An arrow function: lexical `this`, not constructable. +pub const FN_ARROW: u32 = 1 << 4; +/// Strict-mode code (a primitive receiver is not boxed). +pub const FN_STRICT: u32 = 1 << 5; +/// An async function (async generators set it too). +pub const FN_ASYNC: u32 = 1 << 6; +/// A generator function (async generators set it too). +pub const FN_GENERATOR: u32 = 1 << 7; +/// An `async function*`. +pub const FN_ASYNC_GENERATOR: u32 = 1 << 8; +/// A built-in function kind without `[[Construct]]`. +pub const FN_NON_CONSTRUCTOR: u32 = 1 << 9; +/// A runtime-native built-in (its `[[Call]]` skips OrdinaryCallBindThis). +pub const FN_BUILTIN: u32 = 1 << 10; +/// `declared` is valid. +pub const FN_HAS_DECLARED: u32 = 1 << 11; + +/// Byte offsets of the fields codegen emits and emitted code reads. +pub const JS_FUNCTION_INFO_CODE_OFFSET: usize = 0; +pub const JS_FUNCTION_INFO_PARAMS_OFFSET: usize = 8; +pub const JS_FUNCTION_INFO_FLAGS_OFFSET: usize = 12; +pub const JS_FUNCTION_INFO_SIZE: usize = 64; + +impl JsFunctionInfo { + /// The info of the body at `code` declaring `params` JS parameters, with + /// no other facts. + /// + /// # Safety + /// `code` is a JS body (`js_body_fn_ty!`) declaring exactly `params` JS + /// parameters — or one of the runtime's bound-value sentinels. Prefer + /// [`JsFunctionInfo::of`], which takes both from the body's type. + pub const unsafe fn from_code(code: *const u8, params: u16) -> Self { + JsFunctionInfo { + code, + params, + rest_fixed: 0, + flags: 0, + length: 0, + trusted_captures: 0, + trusted_code: core::ptr::null(), + trusted_boxed_mask: 0, + versioned_code: core::ptr::null(), + versioned_captures: 0, + declared: 0, + reserved: 0, + versioned_boxed_mask: 0, + } + } + + /// The info of the typed body `body`: its code address and parameter + /// count both come from `body` itself — + /// `static INFO: JsFunctionInfo = JsFunctionInfo::of(body as JsBody2);` + /// A body of any other signature does not compile. + pub const fn of>(body: F) -> Self { + // A `JsBody` is a function pointer: reinterpret it as its address. + union Code { + body: F, + code: *const u8, + } + // SAFETY: `F` is a fn-pointer type (the trait is implemented for + // nothing else), the same size and bits as a code pointer. + let code = unsafe { Code { body }.code }; + // SAFETY: `code` is `body`, a JS body of `F::ARITY` parameters. + unsafe { Self::from_code(code, F::ARITY as u16) } + } + + /// With `FN_*` bits set. + pub const fn with_flags(mut self, flags: u32) -> Self { + self.flags |= flags; + self + } + + /// With a JS-visible declared parameter count (`.length`'s fallback). + pub const fn with_declared(mut self, declared: u16) -> Self { + self.declared = declared; + self.flags |= FN_HAS_DECLARED; + self + } + + /// With an ECMAScript `.length`. + pub const fn with_length(mut self, length: u32) -> Self { + self.length = length; + self.flags |= FN_HAS_LENGTH; + self + } + + /// A `...rest` body with `fixed` parameters before the rest array. + pub const fn with_rest(mut self, fixed: u16) -> Self { + self.rest_fixed = fixed; + self.flags = (self.flags & !FN_REST_MASK) | FN_REST_USER; + self + } + + /// A body of rest `kind` (one `FN_REST_*` bit) with `fixed` parameters + /// before its rest / `arguments` array. + pub const fn with_rest_kind(mut self, fixed: u16, kind: u32) -> Self { + self.rest_fixed = fixed; + self.flags = (self.flags & !FN_REST_MASK) | kind; + self + } + + /// With a trusted direct-call clone of this arrow body, compiled for + /// `captures` captures with `boxed_mask` boxed ones. + pub const fn with_trusted_direct( + mut self, + code: *const u8, + captures: u32, + boxed_mask: u64, + ) -> Self { + self.trusted_code = code; + self.trusted_captures = captures; + self.trusted_boxed_mask = boxed_mask; + self + } + + /// With a versioned-loop callback clone of this arrow body. + pub const fn with_versioned_loop( + mut self, + code: *const u8, + captures: u32, + boxed_mask: u64, + ) -> Self { + self.versioned_code = code; + self.versioned_captures = captures; + self.versioned_boxed_mask = boxed_mask; + self + } } /// `js_closure_call{N}(callee, this, a0..aN-1)` calls a function object with @@ -235,12 +494,16 @@ pub const JS_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 1 + 4] = [ /// reads (`perry-codegen/src/expr/method_site.rs`). pub const METHOD_SITE_WORD_OFFSET: usize = 0; pub const METHOD_SITE_SLOT_OFFSET: usize = 8; -pub const METHOD_SITE_FUNC_OFFSET: usize = 16; +/// The memoized body's `JsFunctionInfo` (identity: a closure hits when its +/// info word equals this). +pub const METHOD_SITE_INFO_OFFSET: usize = 16; pub const METHOD_SITE_CLOSURE_OFFSET: usize = 24; pub const METHOD_SITE_GEN_OFFSET: usize = 32; +/// The memoized body's code address, the hit's call target. +pub const METHOD_SITE_CODE_OFFSET: usize = 40; /// Entries per method site, and one entry's size. pub const METHOD_SITE_WAYS: usize = 2; -pub const METHOD_SITE_ENTRY_SIZE: usize = 40; +pub const METHOD_SITE_ENTRY_SIZE: usize = 48; /// A method site calls a body with its argument count padded by `undefined` /// up to this many extra arguments (never past 16), and admits bodies that /// declare up to that many parameters. diff --git a/crates/perry-codegen/src/block.rs b/crates/perry-codegen/src/block.rs index 54964bf97c..edb188783e 100644 --- a/crates/perry-codegen/src/block.rs +++ b/crates/perry-codegen/src/block.rs @@ -96,6 +96,12 @@ pub struct RegCounter { /// callee is UB, so the registry, not the emitting code, is the single /// source of truth. `None` for functions built outside a module (tests). preserve_none_fns: RefCell>>>>, + /// The module's `JsFunctionInfo` requests and facts (`crate::fn_info`), + /// injected by `LlModule::define_function` so every allocation site can + /// name its body's info without per-site threading. `None` for + /// functions built outside a module (tests): the info is then named but + /// never emitted. + fn_infos: RefCell>>>, /// `@`-prefixed symbol of the module's null-guard global (the zeroed /// `i32` that [`LlBlock::safe_load_i32_from_ptr`] dereferences in place /// of a bad handle). Injected by `LlModule::define_function` so the name @@ -126,6 +132,7 @@ impl RegCounter { eh_unwind_labels: RefCell::new(Vec::new()), shadow_slot_allocas: RefCell::new(HashSet::new()), preserve_none_fns: RefCell::new(None), + fn_infos: RefCell::new(None), null_guard_symbol: RefCell::new(None), stable_packed_revalidation_slots: RefCell::new(Vec::new()), } @@ -171,6 +178,30 @@ impl RegCounter { *self.preserve_none_fns.borrow_mut() = Some(fns); } + /// Install the module's `JsFunctionInfo` state (`crate::fn_info`). + pub(crate) fn set_fn_infos(&self, infos: Rc>) { + *self.fn_infos.borrow_mut() = Some(infos); + } + + /// The `@`-prefixed info symbol of `body`, recorded as requested. + pub(crate) fn fn_info_ref(&self, body: &str) -> String { + match &*self.fn_infos.borrow() { + Some(infos) => infos.borrow_mut().request(body), + None => format!("@{}", crate::fn_info::info_symbol(body)), + } + } + + /// Record a fact about `body`, a body this module defines. + pub(crate) fn note_fn_info( + &self, + body: &str, + note: impl FnOnce(&mut crate::fn_info::FnInfoFacts), + ) { + if let Some(infos) = &*self.fn_infos.borrow() { + note(infos.borrow_mut().facts_mut(body)); + } + } + /// Whether `callee` must be called with the `preserve_nonecc` convention. /// Cheap for the overwhelmingly common miss: only generated clone symbols /// contain `$`, so ordinary runtime helpers never reach the set lookup. @@ -1395,6 +1426,23 @@ impl LlBlock { Some((cont, lpad)) } + /// The `@`-prefixed `JsFunctionInfo` of the JS body `body`, the operand + /// every function-object allocation passes (`crate::fn_info`). Recorded, + /// so the module emits (or declares) it. + pub fn fn_info_ref(&self, body: &str) -> String { + self.counter.fn_info_ref(body) + } + + /// Record a fact about `body`, a JS body this module defines, in its + /// `JsFunctionInfo`. + pub(crate) fn note_fn_info( + &self, + body: &str, + note: impl FnOnce(&mut crate::fn_info::FnInfoFacts), + ) { + self.counter.note_fn_info(body, note); + } + pub fn call(&mut self, ret_ty: LlvmType, func_name: &str, args: &[(LlvmType, &str)]) -> String { self.call_with_gc_leaf(ret_ty, func_name, args, false) } diff --git a/crates/perry-codegen/src/codegen/arguments.rs b/crates/perry-codegen/src/codegen/arguments.rs index 2751a0b921..3fca36bffc 100644 --- a/crates/perry-codegen/src/codegen/arguments.rs +++ b/crates/perry-codegen/src/codegen/arguments.rs @@ -158,10 +158,10 @@ pub(crate) fn materialize_arguments_object( double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)) } ArgumentsCallee::FunctionWrapper(wrapper) => { - let wrap_ref = format!("@{}", wrapper); + let wrap_info = ctx.block().fn_info_ref(wrapper); let closure_ptr = ctx.block() - .call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ref)]); + .call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); nanbox_pointer_inline(ctx.block(), &closure_ptr) } ArgumentsCallee::CurrentClosure => { @@ -437,10 +437,11 @@ pub(crate) fn try_lower_elided_arguments_index_get( } else { match &elided.callee { ElidedArgumentsCallee::Undefined => (undefined, "null".to_string()), - // The runtime materializes the singleton after rooting its - // operands, so no allocation sits between `key` and the call. + // The runtime materializes the singleton (from the wrapper's + // `JsFunctionInfo`) after rooting its operands, so no + // allocation sits between `key` and the call. ElidedArgumentsCallee::FunctionWrapper(wrapper) => { - (undefined, format!("@{wrapper}")) + (undefined, ctx.block().fn_info_ref(wrapper)) } ElidedArgumentsCallee::CurrentClosure => { let ptr = crate::expr::try_current_closure_ptr_value(ctx) diff --git a/crates/perry-codegen/src/codegen/artifacts.rs b/crates/perry-codegen/src/codegen/artifacts.rs index 0976ede7ab..b2e1551c8f 100644 --- a/crates/perry-codegen/src/codegen/artifacts.rs +++ b/crates/perry-codegen/src/codegen/artifacts.rs @@ -693,11 +693,11 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { // the renamed export as a VALUE and called it via `.apply`/`.call` // (`compose`'s `pipe.apply(this, reverse(arguments))` in ramda — `pipe` is // `export default function pipe()` with a synthetic `arguments`) reached - // `js_native_call_value` with an unregistered wrapper func_ptr, so - // `lookup_closure_rest_full` missed and the args were dispatched positionally - // instead of bundled — the variadic function saw `arguments.length === 0`. - // Register the exported-alias wrapper symbol with the same metadata as the - // local one so the runtime bundles correctly through the rename. + // `js_native_call_value` with a wrapper that recorded no rest fact, so the + // args were dispatched positionally instead of bundled — the variadic + // function saw `arguments.length === 0`. Give the exported-alias wrapper's + // `JsFunctionInfo` the same facts as the local one so the runtime bundles + // correctly through the rename. { let func_by_local_name: HashMap<&str, &perry_hir::Function> = hir.functions.iter().map(|f| (f.name.as_str(), f)).collect(); @@ -716,9 +716,9 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { module_prefix, sanitize(exported) ); - // The registration loop (`string_pool.rs`) iterates + // The fact recorder (`string_pool.rs`) iterates // `user_fn_wrapper_rest`; the synthetic/rest_and_arguments sets only - // *refine* which runtime fn each entry uses. So the alias must be + // *refine* which rest kind each entry gets. So the alias must be // added to `user_fn_wrapper_rest` (keyed on the rest param index) in // EVERY case, plus the matching refinement set. let Some(rest_idx) = f.params.iter().position(|p| p.is_rest) else { diff --git a/crates/perry-codegen/src/codegen/closure_collect.rs b/crates/perry-codegen/src/codegen/closure_collect.rs index ec6a84fa4c..20f0c295e3 100644 --- a/crates/perry-codegen/src/codegen/closure_collect.rs +++ b/crates/perry-codegen/src/codegen/closure_collect.rs @@ -730,9 +730,8 @@ pub(crate) fn collect_module_closures(hir: &HirModule) -> ModuleClosures { // HIR-synthesized `arguments` need to bundle ALL passed args into // the rest slot at dispatch time — JS spec semantics for // `arguments.length` count every passed arg, not just the trailing - // tail after the fixed params. The runtime side reads this through - // `js_register_closure_synthetic_arguments` (vs the regular - // `js_register_closure_rest`). + // tail after the fixed params. The runtime reads this rest kind from the + // body's `JsFunctionInfo` (`crate::fn_info`). let closure_synthetic_arguments: std::collections::HashSet = closures .iter() .filter_map(|(fid, expr)| { @@ -777,9 +776,10 @@ pub(crate) fn collect_module_closures(hir: &HirModule) -> ModuleClosures { }) .collect(); - // Refs #421: declared param count for every non-rest closure. Used by - // `emit_string_pool` to register each closure's ABI arity so the runtime - // can pad missing args with TAG_UNDEFINED in the dynamic-dispatch path. + // Refs #421: declared param count for every non-rest closure: the + // `declared` fact of its `JsFunctionInfo` (`fn.length`'s fallback). The + // runtime pads a short call to the body's parameter count, which the info + // takes from the body's definition. let closure_arities: HashMap = closures .iter() .filter_map(|(fid, expr)| { diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index d802639ff0..09bd2bdd93 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -34,13 +34,14 @@ fn emit_script_global_function_decls(ctx: &mut FnCtx<'_>, hir: &HirModule) { Some(n) => n.clone(), None => continue, }; - let wrap_ptr = format!("@__perry_wrap_{}", func_name); + let wrap_body = format!("__perry_wrap_{}", func_name); let key_idx = ctx.strings.intern(name); let key_handle_global = format!("@{}", ctx.strings.entry(key_idx).handle_global); let blk = ctx.block(); let global_box = blk.call(DOUBLE, "js_get_global_this", &[]); let obj_raw = crate::expr::unbox_to_i64(blk, &global_box); - let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + let wrap_info = blk.fn_info_ref(&wrap_body); + let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); let closure_box = crate::expr::nanbox_pointer_inline(blk, &closure_handle); let key_box = blk.load(DOUBLE, &key_handle_global); let key_raw = crate::expr::unbox_to_i64(blk, &key_box); diff --git a/crates/perry-codegen/src/codegen/helpers.rs b/crates/perry-codegen/src/codegen/helpers.rs index 9c9aa5e30c..8a4026aaaf 100644 --- a/crates/perry-codegen/src/codegen/helpers.rs +++ b/crates/perry-codegen/src/codegen/helpers.rs @@ -1532,20 +1532,14 @@ pub(super) fn emit_namespace_populator( NamespaceEntryKind::LocalVar { .. } | NamespaceEntryKind::ForeignVar { .. } => { let wrapper = namespace_live_getter_wrapper_symbol(module_prefix, i); let blk = ctx.block(); - let handle = blk.call( - I64, - "js_closure_alloc_singleton", - &[(PTR, &format!("@{}", wrapper))], - ); + let info = blk.fn_info_ref(&wrapper); + let handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &info)]); crate::expr::nanbox_pointer_inline(blk, &handle) } NamespaceEntryKind::LocalFunction { wrap_symbol } => { let blk = ctx.block(); - let handle = blk.call( - I64, - "js_closure_alloc_singleton", - &[(PTR, &format!("@{}", wrap_symbol))], - ); + let info = blk.fn_info_ref(wrap_symbol); + let handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &info)]); crate::expr::nanbox_pointer_inline(blk, &handle) } NamespaceEntryKind::LocalClass { class_id } => { @@ -1576,21 +1570,15 @@ pub(super) fn emit_namespace_populator( ctx.pending_declares .push((wrapper_name.clone(), DOUBLE, wrapper_params)); let blk = ctx.block(); - let handle = blk.call( - I64, - "js_closure_alloc_singleton", - &[(PTR, &format!("@{}", wrapper_name))], - ); + let info = blk.fn_info_ref(&wrapper_name); + let handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &info)]); crate::expr::nanbox_pointer_inline(blk, &handle) } NamespaceEntryKind::NestedNamespace { .. } if is_self_namespace => { let wrapper = namespace_live_getter_wrapper_symbol(module_prefix, i); let blk = ctx.block(); - let handle = blk.call( - I64, - "js_closure_alloc_singleton", - &[(PTR, &format!("@{}", wrapper))], - ); + let info = blk.fn_info_ref(&wrapper); + let handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &info)]); crate::expr::nanbox_pointer_inline(blk, &handle) } NamespaceEntryKind::NestedNamespace { source_prefix } => ctx diff --git a/crates/perry-codegen/src/codegen/mod.rs b/crates/perry-codegen/src/codegen/mod.rs index 29198a2ed3..103c50e280 100644 --- a/crates/perry-codegen/src/codegen/mod.rs +++ b/crates/perry-codegen/src/codegen/mod.rs @@ -3798,6 +3798,10 @@ fn compile_module_impl( cross_module: &cross_module, })?; + // One `JsFunctionInfo` per body a function object runs (`crate::fn_info`), + // after every function — and so every allocation site — exists. + llmod.emit_fn_infos(); + // Emit the buffer alias-scope metadata once per module, covering every // scope id allocated across compile_function / compile_closure / // compile_method / compile_static_method / compile_module_entry. Must diff --git a/crates/perry-codegen/src/codegen/string_pool.rs b/crates/perry-codegen/src/codegen/string_pool.rs index 203c1972f7..3a8ebb4f32 100644 --- a/crates/perry-codegen/src/codegen/string_pool.rs +++ b/crates/perry-codegen/src/codegen/string_pool.rs @@ -154,17 +154,17 @@ pub(super) fn emit_string_pool( versioned_loop_callbacks: &std::collections::HashSet, // Issue #653: wrappers (`__perry_wrap_`) for top-level user functions // that declare a rest param. Each entry is `(wrapper_symbol, fixed_arity)` - // — the runtime side-table is keyed on the wrapper's func_ptr, NOT the - // underlying user function, because that's what `js_closure_alloc_singleton` - // stores in the ClosureHeader. Without this registration, calling a user + // — a fact of the WRAPPER's `JsFunctionInfo`, NOT the underlying user + // function's, because the wrapper is what a function value runs. Without + // it, calling a user // function as a value through `js_closure_call_apply_with_spread` fed the // raw spread elements into the wrapper's flat `(this, a0, a1)` signature // instead of bundling args[fixed_arity..] into a real array — the rest // param then read a single element's bits as if it were the rest array. user_fn_wrapper_rest: &[(String, usize)], // Refs #915 (gap 1 from #899): subset of `closure_rest_params` whose - // rest param is the HIR-synthesized `arguments` array. These need - // `js_register_closure_synthetic_arguments` so the runtime bundles + // rest param is the HIR-synthesized `arguments` array. Their infos say so, + // so the runtime bundles // ALL passed args (not just the trailing tail) into the rest slot — // matching JS spec semantics for `arguments.length`. closure_synthetic_arguments: &std::collections::HashSet, @@ -177,30 +177,28 @@ pub(super) fn emit_string_pool( closure_rest_and_arguments: &std::collections::HashSet, user_fn_wrapper_rest_and_arguments: &std::collections::HashSet, // ABI param count for every top-level user-function wrapper - // (`__perry_wrap_`) — used to register the wrapper's - // declared arity in the runtime's closure body registry so dynamic - // dispatch can pad missing trailing args before invoking the wrapper. - // Entries for wrappers also present in `user_fn_wrapper_rest` are skipped - // (those go through the rest registry which already controls dispatch). + // (`__perry_wrap_`): the declared arity in the wrapper's + // `JsFunctionInfo` (`.length`'s fallback). Entries for wrappers also + // present in `user_fn_wrapper_rest` are skipped (their rest fact rules). user_fn_wrapper_arity: &[(String, u32)], // ECMAScript-visible `.length` for every top-level user-function wrapper. user_fn_wrapper_length: &[(String, u32)], - // Wrapper symbols for top-level async functions. Registered by function - // pointer so `util.types.isAsyncFunction` keeps working when the value is + // Wrapper symbols for top-level async functions. Async in their infos so + // `util.types.isAsyncFunction` keeps working when the value is // observed through a runtime alias instead of direct HIR. user_fn_wrapper_async: &std::collections::HashSet, // Wrapper/closure symbols whose original source form was a generator - // function. Registered so util.types.isGeneratorFunction can distinguish + // function. Marked in their infos so util.types.isGeneratorFunction can distinguish // lowered generator state-machine closures from ordinary functions. user_fn_wrapper_generator: &std::collections::HashSet, // #3664: wrapper/closure symbols whose source form was `async function*`. - // Registered in the runtime's async-generator registry so the + // Marked in their infos so the // `%AsyncGeneratorFunction%`/`%AsyncGenerator%` intrinsic chain (and // `util.types.isAsyncFunction`) resolve correctly for them. user_fn_wrapper_async_generator: &std::collections::HashSet, // Strict-mode user functions (wrapper or inline-closure symbols). - // Each entry produces one `js_register_closure_strict_function` call so - // call/apply/bind can apply spec OrdinaryCallBindThis (#4850). + // Strict in their infos so call/apply/bind can apply spec + // OrdinaryCallBindThis (#4850). user_fn_wrapper_strict: &std::collections::HashSet, // `(wrapper_symbol, display_name)` for every top-level user function // we want `console.log` / `util.inspect` to label with the original @@ -880,20 +878,18 @@ pub(super) fn emit_string_pool( // user/cap boundary from this (signature truth), not the decl-site snapshot // length, which mis-split dynamic-parent (capless-sig-with-snapshot) ctors. let mut ctor_triples: Vec<(u32, String, u32, u32)> = Vec::new(); - // #wall3: class ctors with a rest param (`constructor(...args)`) need their - // standalone `_constructor` func_ptr registered as rest-bearing in the closure body registry so - // a member-new (`new ns.Sub(opts)` → js_new_function_construct → - // js_native_call_value) BUNDLES trailing args into the rest array. Without - // this the rest param binds to the first arg as a scalar (a=opts, not - // [opts]) and `super(...args)` spreads a bare object → 0x400000000 mis-box → - // crash (Next.js `new c.AppPageRouteModule({...})`). Mirrors the - // closure-rest registration but keyed by the `_constructor` symbol. - let mut ctor_rest_regs: Vec<(String, usize)> = Vec::new(); - // Per-class-id ctor synth/rest flags (has_synthetic_arguments, has_rest) so - // the `super(...spread)` runtime apply path packs a pass-through parent + // Per-class-id ctor synth/rest flags (has_synthetic_arguments, has_rest) + // so the `super(...spread)` runtime apply path packs a pass-through parent // ctor's `arguments` / rest slot correctly (a zero-declared-param parent - // that reads `arguments`, e.g. tsc's emitted pass-through ctor). - let mut ctor_flag_regs: Vec<(u32, bool, bool)> = Vec::new(); + // that reads `arguments`, e.g. tsc's emitted pass-through ctor), plus the + // rest param's position (in USER params, -1 when none) so a member-new + // (`new ns.Sub(opts)` → js_new_function_construct → js_native_call_value) + // BUNDLES trailing args into the rest array (#wall3). Without that the + // rest param binds to the first arg as a scalar (a=opts, not [opts]) and + // `super(...args)` spreads a bare object → 0x400000000 mis-box → crash + // (Next.js `new c.AppPageRouteModule({...})`). A fact of the class's + // constructor table, keyed by class id like every other one. + let mut ctor_flag_regs: Vec<(u32, bool, bool, i64)> = Vec::new(); for (class_name, class) in classes.iter() { // Refs #486: skip alias keys (class_table now contains both the // canonical name and self-binding aliases like `_X` from @@ -1027,13 +1023,11 @@ pub(super) fn emit_string_pool( // to that ctor unchanged, so it takes the same layout (#10484: a dynamic // `new Sub(x)` must put all args in the ancestor's `arguments` slot). let shape_params = constructor_layout_params(class, classes, ctor_params); - // #wall3: record the rest-param position (in USER params) so the runtime - // bundles trailing args at the dynamic member-new dispatch path. - if let Some(rest_idx) = - shape_params.and_then(|params| params.iter().position(|p| p.is_rest)) - { - ctor_rest_regs.push((ctor_symbol.clone(), rest_idx)); - } + // #wall3: the rest-param position (in USER params), registered with + // the flags below. + let ctor_rest_fixed = shape_params + .and_then(|params| params.iter().position(|p| p.is_rest)) + .map_or(-1, |rest_idx| rest_idx as i64); // Record the ctor's trailing-param shape so the `super(...spread)` // apply path forwards the flat spread args and packs the trailing slot: // a synthesized `arguments` slot receives ALL args (from index 0), a @@ -1054,8 +1048,8 @@ pub(super) fn emit_string_pool( .any(|p| p.is_rest && p.arguments_object.is_none()) }) .unwrap_or(false); - if ctor_has_synth || ctor_has_rest { - ctor_flag_regs.push((cid, ctor_has_synth, ctor_has_rest)); + if ctor_has_synth || ctor_has_rest || ctor_rest_fixed >= 0 { + ctor_flag_regs.push((cid, ctor_has_synth, ctor_has_rest, ctor_rest_fixed)); } } // #5957: count the ctor's trailing `__perry_cap_*` signature params. @@ -1213,24 +1207,10 @@ pub(super) fn emit_string_pool( ], ); } - // #wall3: register rest-bearing class ctors' func_ptrs in the closure-rest - // side table so the dynamic member-new dispatch (js_native_call_value via - // js_new_function_construct) bundles trailing args into the rest array, - // matching the static `new` path. See `ctor_rest_regs` above. - ctor_rest_regs.sort_unstable(); - for (ctor_symbol, rest_idx) in ctor_rest_regs { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", ctor_symbol); - blk.call_void( - "js_register_closure_rest", - &[(PTR, &func_ref), (I32, &rest_idx.to_string())], - ); - } // Register ctor synth/rest flags so `super(...spread)` packs the parent // ctor's trailing `arguments` / rest slot correctly. ctor_flag_regs.sort_unstable(); - for (cid, has_synth, has_rest) in ctor_flag_regs { + for (cid, has_synth, has_rest, rest_fixed) in ctor_flag_regs { chunker.roll_if_full(); let blk = chunker.current_block(); blk.call_void( @@ -1239,6 +1219,7 @@ pub(super) fn emit_string_pool( (I64, &cid.to_string()), (I64, if has_synth { "1" } else { "0" }), (I64, if has_rest { "1" } else { "0" }), + (I64, &rest_fixed.to_string()), ], ); } @@ -1541,15 +1522,9 @@ pub(super) fn emit_string_pool( let func_ref = format!("@{}", llvm_name); let func_i64 = blk.ptrtoint(&func_ref, I64); let bytes_i64 = blk.ptrtoint(&bytes_global, I64); - // Register the setter's spec `.length` keyed by its func_ptr so + // The setter's spec `.length` rides with its registration, so // `Object.getOwnPropertyDescriptor(proto, prop).set.length` reports - // the default-aware count instead of the raw ABI arity. Static - // accessors are emitted under a no-`this` `perry_static_…` symbol, so - // the same func_ptr is what a value-read of `.set` binds. - blk.call_void( - "js_register_closure_length", - &[(PTR, &func_ref), (I32, &spec_length.to_string())], - ); + // the default-aware count instead of the raw ABI arity. let register_fn = if is_static { "js_register_class_static_setter" } else { @@ -1562,6 +1537,7 @@ pub(super) fn emit_string_pool( (I64, &bytes_i64), (I64, &len_str), (I64, &func_i64), + (I32, &spec_length.to_string()), ], ); blk.call_void( @@ -1576,247 +1552,159 @@ pub(super) fn emit_string_pool( ); } - // Issue #493: register each rest-bearing closure body's func_ptr -> - // fixed_arity in the runtime's closure-rest side table. `js_closure_callN` - // consults it to bundle trailing args at call sites where codegen - // doesn't know the closure's arity statically (e.g. `obj.cb(a, b, c)` - // where `cb` is a class field holding `(...args) => …`). Without this - // entry the closure body sees the first arg as the rest param itself, - // not the bundled array — `args.length` reads `1` against the string - // value, and trailing args are dropped. Static call sites (named fns, - // `Expr::FuncRef`, `let f = (...args)=>…; f(a,b,c)`) keep their - // existing call-site bundling and never enter this dispatch path. - let mut sorted_rest: Vec<(u32, usize)> = closure_rest_params - .iter() - .map(|(fid, ri)| (*fid, *ri)) - .collect(); - sorted_rest.sort_unstable(); - for (fid, fixed_arity) in sorted_rest { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let closure_sym = format!("perry_closure_{}__{}", module_prefix, fid); - let func_ref = format!("@{}", closure_sym); - // Refs #915 (gap 1 from #899): closures whose rest param is the - // synthesized `arguments` use the synthetic-arguments registration - // so the runtime bundles ALL args into the rest slot. - let runtime_fn = if closure_rest_and_arguments.contains(&fid) { - "js_register_closure_rest_and_arguments" - } else if closure_synthetic_arguments.contains(&fid) { - "js_register_closure_synthetic_arguments" + let chunk_names = chunker.finish(); + record_fn_info_facts( + llmod, + module_prefix, + FnInfoFactSources { + closure_rest_params, + closure_arities, + closure_lengths, + closure_arrow_functions, + trusted_box_closures, + versioned_loop_callbacks, + user_fn_wrapper_rest, + closure_synthetic_arguments, + user_fn_wrapper_synthetic_arguments, + closure_rest_and_arguments, + user_fn_wrapper_rest_and_arguments, + user_fn_wrapper_arity, + user_fn_wrapper_length, + user_fn_wrapper_async, + user_fn_wrapper_generator, + user_fn_wrapper_async_generator, + user_fn_wrapper_strict, + }, + ); + let init_name = format!("__perry_init_strings_{}", module_prefix); + let init_fn = llmod.define_function(&init_name, VOID, vec![]); + let _ = init_fn.create_block("entry"); + let blk = init_fn.block_mut(0).unwrap(); + for cname in &chunk_names { + blk.call_void(cname, &[]); + } + blk.ret_void(); +} + +/// The codegen metadata a module's `JsFunctionInfo` facts come from +/// (`crate::fn_info`), keyed by closure func-id or wrapper symbol. +struct FnInfoFactSources<'a> { + closure_rest_params: &'a HashMap, + closure_arities: &'a HashMap, + closure_lengths: &'a HashMap, + closure_arrow_functions: &'a std::collections::HashSet, + trusted_box_closures: + &'a std::collections::HashMap, + versioned_loop_callbacks: &'a std::collections::HashSet, + user_fn_wrapper_rest: &'a [(String, usize)], + closure_synthetic_arguments: &'a std::collections::HashSet, + user_fn_wrapper_synthetic_arguments: &'a std::collections::HashSet, + closure_rest_and_arguments: &'a std::collections::HashSet, + user_fn_wrapper_rest_and_arguments: &'a std::collections::HashSet, + user_fn_wrapper_arity: &'a [(String, u32)], + user_fn_wrapper_length: &'a [(String, u32)], + user_fn_wrapper_async: &'a std::collections::HashSet, + user_fn_wrapper_generator: &'a std::collections::HashSet, + user_fn_wrapper_async_generator: &'a std::collections::HashSet, + user_fn_wrapper_strict: &'a std::collections::HashSet, +} + +/// Record every fact the module knows about its own closure bodies and +/// value wrappers in their `JsFunctionInfo`s (`crate::fn_info`): +/// +/// * rest kind + fixed arity (#493, #653; #915's synthesized `arguments` +/// bundles ALL args; rest-and-arguments passes both arrays) — dynamic +/// dispatch bundles trailing args for call sites that cannot see the +/// body's signature (`obj.cb(a, b, c)` with `cb` a `(...args) => …` field); +/// * declared arity of non-rest bodies (#421) — `fn.length`'s fallback; +/// * ECMAScript `.length` (default-aware; Ramda's `converge` / `juxt` read +/// it from function values); +/// * arrow, strict (#4850 OrdinaryCallBindThis), async, generator and +/// async-generator (#3664) bits; +/// * the trusted direct-call and versioned-loop clones of eligible arrows. +fn record_fn_info_facts(llmod: &LlModule, module_prefix: &str, src: FnInfoFactSources<'_>) { + use crate::fn_info::RestKind; + let closure = |fid: u32| format!("perry_closure_{}__{}", module_prefix, fid); + for (&fid, &fixed) in src.closure_rest_params { + let kind = if src.closure_rest_and_arguments.contains(&fid) { + RestKind::UserAndArguments + } else if src.closure_synthetic_arguments.contains(&fid) { + RestKind::SyntheticArguments } else { - "js_register_closure_rest" + RestKind::User }; - blk.call_void( - runtime_fn, - &[(PTR, &func_ref), (I32, &fixed_arity.to_string())], - ); + llmod.note_fn_info(&closure(fid), |f| f.set_rest(fixed, kind)); } - - // Refs #421: register every non-rest closure's declared param count so - // `js_native_call_value` can pad missing trailing args with TAG_UNDEFINED - // when a closure stored as a class field is invoked method-style on an - // any-typed receiver with fewer args than declared. Rest-bearing closures - // are already handled by the closure-rest registry above (which pads - // internally via `dispatch_rest_bundled`). - let mut sorted_arities: Vec<(u32, u32)> = closure_arities - .iter() - .map(|(fid, arity)| (*fid, *arity)) - .collect(); - sorted_arities.sort_unstable(); - for (fid, arity) in sorted_arities { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let closure_sym = format!("perry_closure_{}__{}", module_prefix, fid); - let func_ref = format!("@{}", closure_sym); - blk.call_void( - "js_register_closure_arity", - &[(PTR, &func_ref), (I32, &arity.to_string())], - ); + for (&fid, &arity) in src.closure_arities { + llmod.note_fn_info(&closure(fid), |f| f.set_declared(arity)); } - - // Register ECMAScript-visible `.length` for all closures. This is - // intentionally separate from declared arity: default parameters lower - // into body prologues, so dispatch still needs the full ABI arity, while - // `fn.length` stops at the first default/rest parameter. - let mut sorted_lengths: Vec<(u32, u32)> = closure_lengths - .iter() - .map(|(fid, length)| (*fid, *length)) - .collect(); - sorted_lengths.sort_unstable(); - for (fid, length) in sorted_lengths { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let closure_sym = format!("perry_closure_{}__{}", module_prefix, fid); - let func_ref = format!("@{}", closure_sym); - blk.call_void( - "js_register_closure_length", - &[(PTR, &func_ref), (I32, &length.to_string())], - ); + for (&fid, &length) in src.closure_lengths { + llmod.note_fn_info(&closure(fid), |f| f.set_length(length)); } - - let mut sorted_arrows: Vec = closure_arrow_functions.iter().copied().collect(); - sorted_arrows.sort_unstable(); - for fid in sorted_arrows { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let closure_sym = format!("perry_closure_{}__{}", module_prefix, fid); - let func_ref = format!("@{}", closure_sym); - blk.call_void("js_register_closure_arrow_function", &[(PTR, &func_ref)]); + for &fid in src.closure_arrow_functions { + llmod.note_fn_info(&closure(fid), |f| f.set_arrow()); } - - let mut sorted_trusted: Vec<(u32, super::closure_collect::TrustedBoxClosure)> = - trusted_box_closures - .iter() - .map(|(func_id, plan)| (*func_id, *plan)) - .collect(); - sorted_trusted.sort_unstable_by_key(|(func_id, _)| *func_id); - for (fid, plan) in sorted_trusted { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let public_ref = format!("@perry_closure_{}__{}", module_prefix, fid); - let trusted_ref = format!("{}$trusted_boxes", public_ref); - blk.call_void( - "js_register_closure_trusted_direct", - &[ - (PTR, &public_ref), - (PTR, &trusted_ref), - (I32, &plan.capture_count.to_string()), - (I64, &plan.boxed_capture_mask.to_string()), - ], - ); - if versioned_loop_callbacks.contains(&fid) { - let versioned_ref = format!("{}$trusted_boxes$versioned_loop", public_ref); - blk.call_void( - "js_register_closure_versioned_loop_direct", - &[ - (PTR, &public_ref), - (PTR, &versioned_ref), - (I32, &plan.capture_count.to_string()), - (I64, &plan.boxed_capture_mask.to_string()), - ], - ); + // The clones hang off arrow bodies only (the runtime's resolvers take + // them from an arrow's info). + for (&fid, plan) in src.trusted_box_closures { + if !src.closure_arrow_functions.contains(&fid) { + continue; } + let public = closure(fid); + let trusted = format!("{public}$trusted_boxes"); + let versioned = src + .versioned_loop_callbacks + .contains(&fid) + .then(|| format!("{trusted}$versioned_loop")); + llmod.note_fn_info(&public, |f| { + f.trusted = Some(crate::fn_info::CloneTarget { + symbol: trusted, + captures: plan.capture_count, + boxed_mask: plan.boxed_capture_mask, + }); + f.versioned = versioned.map(|symbol| crate::fn_info::CloneTarget { + symbol, + captures: plan.capture_count, + boxed_mask: plan.boxed_capture_mask, + }); + }); } - - // Issue #653: register `__perry_wrap_` wrappers for top-level user - // functions whose source signature includes a rest param. Mirrors the - // closure-rest loop above but keyed on the wrapper's symbol rather than - // the closure body. See `user_fn_wrapper_rest` doc on this fn's signature. - let mut sorted_wrappers: Vec<(String, usize)> = user_fn_wrapper_rest.to_vec(); - sorted_wrappers.sort(); - let rest_wrapper_names: std::collections::HashSet = - sorted_wrappers.iter().map(|(s, _)| s.clone()).collect(); - for (wrap_sym, fixed_arity) in sorted_wrappers { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - // Refs #915 (gap 1 from #899): wrappers whose underlying function - // declared a synthesized `arguments` rest param need the - // synthetic-arguments registration. - let runtime_fn = if user_fn_wrapper_rest_and_arguments.contains(&wrap_sym) { - "js_register_closure_rest_and_arguments" - } else if user_fn_wrapper_synthetic_arguments.contains(&wrap_sym) { - "js_register_closure_synthetic_arguments" - } else { - "js_register_closure_rest" - }; - blk.call_void( - runtime_fn, - &[(PTR, &func_ref), (I32, &fixed_arity.to_string())], - ); - } - - // Register declared ABI param count for `__perry_wrap_` wrappers of - // every non-rest top-level user function. Mirrors the closure-arity loop - // above; `.length` is registered separately below. - let mut sorted_wrapper_arities: Vec<(String, u32)> = user_fn_wrapper_arity + let rest_wrappers: std::collections::HashSet<&str> = src + .user_fn_wrapper_rest .iter() - .filter(|(name, _)| !rest_wrapper_names.contains(name)) - .cloned() + .map(|(s, _)| s.as_str()) .collect(); - sorted_wrapper_arities.sort(); - for (wrap_sym, arity) in sorted_wrapper_arities { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void( - "js_register_closure_arity", - &[(PTR, &func_ref), (I32, &arity.to_string())], - ); + for (wrap_sym, fixed) in src.user_fn_wrapper_rest { + let kind = if src.user_fn_wrapper_rest_and_arguments.contains(wrap_sym) { + RestKind::UserAndArguments + } else if src.user_fn_wrapper_synthetic_arguments.contains(wrap_sym) { + RestKind::SyntheticArguments + } else { + RestKind::User + }; + llmod.note_fn_info(wrap_sym, |f| f.set_rest(*fixed, kind)); } - - // Register spec `.length` for all top-level user-function wrappers, - // including rest wrappers. Ramda's `converge` / `juxt` / `useWith` - // chains read `fn.length` from function values to compute curry arities. - let mut sorted_wrapper_lengths: Vec<(String, u32)> = user_fn_wrapper_length.to_vec(); - sorted_wrapper_lengths.sort(); - for (wrap_sym, length) in sorted_wrapper_lengths { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void( - "js_register_closure_length", - &[(PTR, &func_ref), (I32, &length.to_string())], - ); + for (wrap_sym, arity) in src.user_fn_wrapper_arity { + if rest_wrappers.contains(wrap_sym.as_str()) { + continue; + } + llmod.note_fn_info(wrap_sym, |f| f.set_declared(*arity)); } - - let mut sorted_async_wrappers: Vec = user_fn_wrapper_async.iter().cloned().collect(); - sorted_async_wrappers.sort(); - for wrap_sym in sorted_async_wrappers { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void("js_register_closure_async_function", &[(PTR, &func_ref)]); + for (wrap_sym, length) in src.user_fn_wrapper_length { + llmod.note_fn_info(wrap_sym, |f| f.set_length(*length)); } - - let mut sorted_generator_wrappers: Vec = - user_fn_wrapper_generator.iter().cloned().collect(); - sorted_generator_wrappers.sort(); - for wrap_sym in sorted_generator_wrappers { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void( - "js_register_closure_generator_function", - &[(PTR, &func_ref)], - ); + for wrap_sym in src.user_fn_wrapper_async { + llmod.note_fn_info(wrap_sym, |f| f.set_async()); } - - // #3664: async-generator wrappers. These are ALSO in - // `user_fn_wrapper_generator` above (they share the sync generator - // lowering); this extra registration is what lets the runtime tell an - // `async function*` apart from a `function*`. - let mut sorted_async_generator_wrappers: Vec = - user_fn_wrapper_async_generator.iter().cloned().collect(); - sorted_async_generator_wrappers.sort(); - for wrap_sym in sorted_async_generator_wrappers { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void( - "js_register_closure_async_generator_function", - &[(PTR, &func_ref)], - ); + for wrap_sym in src.user_fn_wrapper_generator { + llmod.note_fn_info(wrap_sym, |f| f.set_generator()); } - - let mut sorted_strict_wrappers: Vec = user_fn_wrapper_strict.iter().cloned().collect(); - sorted_strict_wrappers.sort(); - for wrap_sym in sorted_strict_wrappers { - chunker.roll_if_full(); - let blk = chunker.current_block(); - let func_ref = format!("@{}", wrap_sym); - blk.call_void("js_register_closure_strict_function", &[(PTR, &func_ref)]); + for wrap_sym in src.user_fn_wrapper_async_generator { + llmod.note_fn_info(wrap_sym, |f| f.set_async_generator()); } - - let chunk_names = chunker.finish(); - let init_name = format!("__perry_init_strings_{}", module_prefix); - let init_fn = llmod.define_function(&init_name, VOID, vec![]); - let _ = init_fn.create_block("entry"); - let blk = init_fn.block_mut(0).unwrap(); - for cname in &chunk_names { - blk.call_void(cname, &[]); + for wrap_sym in src.user_fn_wrapper_strict { + llmod.note_fn_info(wrap_sym, |f| f.set_strict()); } - blk.ret_void(); } #[cfg(test)] diff --git a/crates/perry-codegen/src/codegen/trusted_box_callback_tests.rs b/crates/perry-codegen/src/codegen/trusted_box_callback_tests.rs index e86b6f58b6..895ae5396f 100644 --- a/crates/perry-codegen/src/codegen/trusted_box_callback_tests.rs +++ b/crates/perry-codegen/src/codegen/trusted_box_callback_tests.rs @@ -334,9 +334,23 @@ fn direct_arrow_gets_a_private_body_but_keeps_the_public_validation_path() { assert!(!trusted.contains("@js_box_set_bits(")); assert!(trusted.contains("@js_write_barrier(")); - assert!(ir.contains( - "@js_register_closure_trusted_direct(ptr @perry_closure_trusted_box_callback_ts__99, ptr @perry_closure_trusted_box_callback_ts__99$trusted_boxes, i32 1, i64 1)" - )); + // The body's `JsFunctionInfo` names the trusted clone and its exact + // one-box capture layout (`crate::fn_info`). + let info = info_line(&ir, "perry_closure_trusted_box_callback_ts__99"); + assert!( + info.contains( + "i32 1, ptr @perry_closure_trusted_box_callback_ts__99$trusted_boxes, i64 1, ptr null" + ), + "{info}" + ); +} + +/// The `@$info = ...` definition line of `body`. +fn info_line<'a>(ir: &'a str, body: &str) -> &'a str { + let prefix = format!("@{body}$info = "); + ir.lines() + .find(|line| line.starts_with(&prefix)) + .unwrap_or_else(|| panic!("no info for {body} in IR:\n{ir}")) } #[test] @@ -361,9 +375,13 @@ fn additive_property_callback_gets_a_cold_deopting_private_body() { && special.contains("versioned_callback.deopt.mark"), "both observable cold arms must poison the loop before fallback:\n{special}" ); - assert!(ir.contains( - "@js_register_closure_versioned_loop_direct(ptr @perry_closure_versioned_loop_callback_ts__100, ptr @perry_closure_versioned_loop_callback_ts__100$trusted_boxes$versioned_loop, i32 1, i64 1)" - )); + let info = info_line(&ir, "perry_closure_versioned_loop_callback_ts__100"); + assert!( + info.contains( + "ptr @perry_closure_versioned_loop_callback_ts__100$trusted_boxes$versioned_loop, i32 1, i16" + ) && info.ends_with("i64 1 }"), + "{info}" + ); } #[test] @@ -465,7 +483,10 @@ fn versioned_callback_selector_rejects_calls_and_heap_writes() { fn closure_first_stored_as_a_value_does_not_get_a_trusted_body() { let ir = emit(false); assert!(!ir.contains("$trusted_boxes")); - assert!(!ir.contains("call void @js_register_closure_trusted_direct(")); + assert!( + !ir.contains("$trusted_boxes"), + "no info may name a trusted clone" + ); } #[test] diff --git a/crates/perry-codegen/src/expr/arrays_finds.rs b/crates/perry-codegen/src/expr/arrays_finds.rs index 20f5c31b98..13e66e5f8b 100644 --- a/crates/perry-codegen/src/expr/arrays_finds.rs +++ b/crates/perry-codegen/src/expr/arrays_finds.rs @@ -682,13 +682,13 @@ pub(crate) fn lower( |func_name| format!("__perry_wrap_{func_name}"), ); let blk = ctx.block(); - let wrap_ptr = format!("@{}", wrap_name); + let wrap_info = blk.fn_info_ref(&wrap_name); // FuncRef wrappers always have 0 captures, so we can route - // through the singleton-cached allocator: same func_ptr always + // through the singleton-cached allocator: the same body info always // yields the same ClosureHeader. Eliminates the per-evaluation // gc_malloc + gc_check_trigger that was the dominant cost in // tight loops which pass a function as a callback. - let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); Ok(nanbox_pointer_inline(blk, &closure_handle)) } diff --git a/crates/perry-codegen/src/expr/closure.rs b/crates/perry-codegen/src/expr/closure.rs index b82be07315..b0e2eb5318 100644 --- a/crates/perry-codegen/src/expr/closure.rs +++ b/crates/perry-codegen/src/expr/closure.rs @@ -224,7 +224,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { + usize::from(*captures_new_target) + usize::from(*captures_this); - let func_ref = format!("@{}", func_name); + // The body's `JsFunctionInfo` (`crate::fn_info`): what every + // allocation below names instead of the body. + let info_ref = ctx.block().fn_info_ref(&func_name); // Issue #450: when `captures_this`, OR in the runtime's // `CAPTURES_THIS_FLAG` (0x8000_0000) so the runtime can detect // closures whose last capture slot is the reserved `this` slot. @@ -242,14 +244,14 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // Closures with NO captures (and no `this` to patch) are // observationally identical across every call site that // produces them, so route through `js_closure_alloc_singleton` - // to share a single ClosureHeader cached by func_ptr. + // to share a single ClosureHeader cached by body info. // // Closures WITH captures route through // `js_closure_alloc_with_captures_singleton` whenever none of // those captures are mutated by the body (the common case // for ECS callbacks like `(eid, arch, compId) => { ... // changeset ... }` capturing `this._changeset`). The cache - // keys on (func_ptr, capture_bits…) so distinct capture + // keys on (body info, capture_bits…) so distinct capture // values still produce distinct closures; identical // (func, captures) at a hot call site re-uses the cached // ClosureHeader and skips gc_malloc + gc_check_trigger. @@ -276,7 +278,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // IDENTITY CAVEAT (#4831 follow-up — Stripe `protoExtend`): // the singleton-sharing paths (`js_closure_alloc_singleton` / // `js_closure_alloc_with_captures_singleton`) return ONE cached - // `ClosureHeader` for a given (func_ptr[, capture-bits]) key, so two + // `ClosureHeader` for a given (body info[, capture-bits]) key, so two // evaluations of the same closure literal observe the SAME function // object — and therefore the SAME `.prototype`. A non-arrow // `function` expression that is used as a CONSTRUCTOR must instead @@ -376,22 +378,20 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { && auto_captures.iter().all(|cap_id| { !ctx.boxed_vars.contains(cap_id) || uncounted_box_capture(cap_id) }); - // Register an `async function(){}` *expression* closure (one with - // no `await` — bodies that await are rewritten to a state machine - // upstream and arrive with `is_async: false`) in the async-function - // registry so `IsConstructor`/`util.types.isAsyncFunction` recognize - // it. Generators are hoisted to top-level and registered elsewhere. - // (Test262 subclass/superclass-async-function.) BEFORE the - // allocation: the closure's ShapeId names its [[Prototype]] - // (%AsyncFunction.prototype%), which the runtime reads off the - // body registry at birth. + // An `async function(){}` *expression* closure (one with no + // `await` — bodies that await are rewritten to a state machine + // upstream and arrive with `is_async: false`) is async in its + // info, so `IsConstructor`/`util.types.isAsyncFunction` recognize + // it and its ShapeId names %AsyncFunction.prototype% at birth. + // Generators are hoisted to top-level and described elsewhere. + // (Test262 subclass/superclass-async-function.) if *is_async { ctx.block() - .call_void("js_register_closure_async_function", &[(PTR, &func_ref)]); + .note_fn_info(&func_name, |facts| facts.set_async()); } let closure_handle = if no_capture_singleton { let blk = ctx.block(); - blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &func_ref)]) + blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &info_ref)]) } else if captured_singleton { // Stack-allocate a `[u64; total_caps]` capture buffer // (auto captures, plus `this` at the reserved slot if @@ -421,7 +421,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { blk.call( I64, "js_closure_alloc_with_captures_singleton", - &[(PTR, &func_ref), (I32, &cap_count), (PTR, &buf)], + &[(PTR, &info_ref), (I32, &cap_count), (PTR, &buf)], ) } else if bulk_fresh_init { // Fresh (identity-carrying) closure with plain-bits captures: @@ -447,14 +447,14 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { blk.call( I64, "js_closure_alloc_init", - &[(PTR, &func_ref), (I32, &cap_count), (PTR, &buf)], + &[(PTR, &info_ref), (I32, &cap_count), (PTR, &buf)], ) } else { let blk = ctx.block(); blk.call( I64, "js_closure_alloc", - &[(PTR, &func_ref), (I32, &cap_count)], + &[(PTR, &info_ref), (I32, &cap_count)], ) }; diff --git a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs index 4c0a8608d3..e496c5df75 100644 --- a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs +++ b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs @@ -482,11 +482,10 @@ fn materialize_compiled_namespace(ctx: &mut FnCtx<'_>, name: &str) -> Result, expr: &Expr) -> Result { // const-bound closures already use: ONE module-level // singleton observed via stable address from every // module. `js_closure_alloc_singleton` keys its - // pool by func_ptr, so calling it with the source's - // wrapper symbol from any module returns the same + // pool by the body's `JsFunctionInfo`, which only the + // source module defines (every other module declares it + // `external`, `crate::fn_info`), so calling it with the + // source wrapper's info from any module returns the same // ClosureHeader the source returned for its // `Expr::FuncRef(id)` value-reads. The pre-fix // `__perry_extern_closure___` global is no @@ -1000,9 +1001,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { ctx.pending_declares .push((wrap_name.clone(), DOUBLE, wrap_param_types)); let blk = ctx.block(); - let wrap_ptr = format!("@{}", wrap_name); + let wrap_info = blk.fn_info_ref(&wrap_name); let closure_handle = - blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); return Ok(nanbox_pointer_inline(blk, &closure_handle)); } // Issue #841 companion: namespace imports for the same five diff --git a/crates/perry-codegen/src/expr/logical_collections.rs b/crates/perry-codegen/src/expr/logical_collections.rs index 02ec353a50..891aae37bf 100644 --- a/crates/perry-codegen/src/expr/logical_collections.rs +++ b/crates/perry-codegen/src/expr/logical_collections.rs @@ -369,10 +369,14 @@ fn lower_captureless_some_inline( let fallback_value = { let blk = ctx.block(); let arr_handle = unbox_to_i64(blk, &arr_box0); + // The runtime takes the body's static info (it may have to + // materialize the function object for a TypedArray / Buffer + // receiver), never its code address. + let callback_info = blk.fn_info_ref(callback_func.trim_start_matches('@')); let value = blk.call( DOUBLE, "js_array_some_captureless", - &[(I64, &arr_handle), (PTR, callback_func)], + &[(I64, &arr_handle), (PTR, &callback_info)], ); blk.br(&merge_l); value diff --git a/crates/perry-codegen/src/expr/method_site.rs b/crates/perry-codegen/src/expr/method_site.rs index c904238cb6..9601bd1036 100644 --- a/crates/perry-codegen/src/expr/method_site.rs +++ b/crates/perry-codegen/src/expr/method_site.rs @@ -13,13 +13,13 @@ //! w == site.word else MISS //! s = site.slot //! s < 0 (inherited): PERRY_PROTO_VALIDITY == site.gen else MISS -//! h = site.closure ; f = site.func +//! h = site.closure ; f = site.code //! own: v = load [recv + HDR + 8*s] ; v is a heap pointer else MISS //! fn: v = load [[recv + PROPS] + HDR + 8*s] (bit 61: a function's //! own-property object; same checks as own) -//! [v-8] & 0x80FF == CLOSURE ; [v+8] == site.func else NEXT WAY -//! h = handle(v) ; f = site.func -//! CALL: this = recv ; r = f(h, args...) ; restore this +//! [v-8] & 0x80FF == CLOSURE ; [v+8] == site.info else NEXT WAY +//! h = handle(v) ; f = site.code +//! CALL: r = f(h, recv, args...) (the receiver is the `this` parameter) //! MISS: js_method_site_miss(slot, feedback_site, recv, method_id, args) //! PRIMITIVE: js_typed_feedback_native_call_method_by_id(feedback_site, recv, method_id, args) //! ``` @@ -75,7 +75,11 @@ pub(crate) fn emit_method_site( }; let abi_word = crate::runtime_abi::METHOD_SITE_WORD_OFFSET.to_string(); let abi_slot = crate::runtime_abi::METHOD_SITE_SLOT_OFFSET.to_string(); - let abi_func = crate::runtime_abi::METHOD_SITE_FUNC_OFFSET.to_string(); + // An entry memoizes a body as its `JsFunctionInfo` (the identity a + // closure's info word is compared with) and its code address (the call + // target), so a hit costs no load through the info. + let abi_info = crate::runtime_abi::METHOD_SITE_INFO_OFFSET.to_string(); + let abi_code = crate::runtime_abi::METHOD_SITE_CODE_OFFSET.to_string(); let abi_closure = crate::runtime_abi::METHOD_SITE_CLOSURE_OFFSET.to_string(); let abi_gen = crate::runtime_abi::METHOD_SITE_GEN_OFFSET.to_string(); let ways = crate::runtime_abi::METHOD_SITE_WAYS; @@ -85,11 +89,11 @@ pub(crate) fn emit_method_site( let index_mask = crate::runtime_abi::METHOD_SITE_INDEX_MASK.to_string(); let entry_size = crate::runtime_abi::METHOD_SITE_ENTRY_SIZE; let header = crate::target_layout::object_header_size_bytes(ctx.target_triple) as i64; - // `ClosureHeader` (64-bit only here): the code pointer, and the GcHeader + // `ClosureHeader` (64-bit only here): the info pointer, and the GcHeader // (type, flags) half-word in front of the payload that makes a cell a // live function object: type `GC_TYPE_CLOSURE` and not a forwarded stub // (`closure::is_closure_ptr`'s kind term; there is no payload magic). - let func_offset = crate::runtime_abi::CLOSURE_FUNC_PTR_OFFSET as i64; + let info_offset = crate::runtime_abi::CLOSURE_INFO_OFFSET as i64; let props_offset = crate::runtime_abi::CLOSURE_PROPS_OFFSET as i64; let kind_offset = -(crate::runtime_abi::GC_HEADER_SIZE as i64); let kind_mask = (0xFFu16 | (u16::from(crate::runtime_abi::GC_FLAG_FORWARDED) << 8)).to_string(); @@ -338,11 +342,13 @@ pub(crate) fn emit_method_site( let kind = blk.load(crate::types::I16, &kp); let kind = blk.and(crate::types::I16, &kind, &kind_mask); let is_closure = blk.icmp_eq(crate::types::I16, &kind, &closure_kind); - let fpp = emit_field_ptr(blk, &own_ub, func_offset); - let fp = blk.load(I64, &fpp); - let mf_p = blk.gep(crate::types::I8, &entry, &[(I64, &abi_func)]); + let ip = emit_field_ptr(blk, &own_ub, info_offset); + let info = blk.load(I64, &ip); + let mi_p = blk.gep(crate::types::I8, &entry, &[(I64, &abi_info)]); + let mi = blk.load(I64, &mi_p); + let same = blk.icmp_eq(I64, &info, &mi); + let mf_p = blk.gep(crate::types::I8, &entry, &[(I64, &abi_code)]); let mf = blk.load(I64, &mf_p); - let same = blk.icmp_eq(I64, &fp, &mf); let hit = blk.and(I1, &is_closure, &same); let h = emit_handle(blk, &own_ub); let end = blk.label.clone(); @@ -391,7 +397,7 @@ pub(crate) fn emit_method_site( let valid = blk.icmp_eq(I64, &g, &mg); let hp = blk.gep(crate::types::I8, &entry, &[(I64, &abi_closure)]); let h = blk.load(I64, &hp); - let fp_p = blk.gep(crate::types::I8, &entry, &[(I64, &abi_func)]); + let fp_p = blk.gep(crate::types::I8, &entry, &[(I64, &abi_code)]); let f = blk.load(I64, &fp_p); let end = blk.label.clone(); blk.cond_br(&valid, &call_l, &miss_l); diff --git a/crates/perry-codegen/src/expr/property_get.rs b/crates/perry-codegen/src/expr/property_get.rs index 6a5c762d65..95d704dafd 100644 --- a/crates/perry-codegen/src/expr/property_get.rs +++ b/crates/perry-codegen/src/expr/property_get.rs @@ -1250,9 +1250,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { ctx.pending_declares .push((wrap_name.clone(), DOUBLE, wrap_param_types)); let blk = ctx.block(); - let wrap_ptr = format!("@{}", wrap_name); + let wrap_info = blk.fn_info_ref(&wrap_name); let closure_handle = - blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); return Ok(nanbox_pointer_inline(blk, &closure_handle)); } } diff --git a/crates/perry-codegen/src/expr/super_method.rs b/crates/perry-codegen/src/expr/super_method.rs index e2643c5489..2cc5462910 100644 --- a/crates/perry-codegen/src/expr/super_method.rs +++ b/crates/perry-codegen/src/expr/super_method.rs @@ -334,8 +334,8 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // the symbol. Push into `pending_declares` — `declare_function` // dedupes against any later same-TU `define` (`module.rs:67-69` // comment) so this is safe for the same-module case too. The - // signature is informational only (runtime dispatches via - // ClosureHeader's func_ptr); use the JS body ABI with 0 doubles, + // signature is informational only (the runtime dispatches by the + // body's `JsFunctionInfo`); use the JS body ABI with 0 doubles, // the shape the imported-function-ref sites use for // unknown-arity imports. let wrap_name = format!("__perry_wrap_{}", fn_name); @@ -345,8 +345,8 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { crate::expr::body_call::js_body_param_types(0), )); let blk = ctx.block(); - let wrap_ptr = format!("@{}", wrap_name); - let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + let wrap_info = blk.fn_info_ref(&wrap_name); + let closure_handle = blk.call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); Ok(nanbox_pointer_inline(blk, &closure_handle)) } diff --git a/crates/perry-codegen/src/fn_info.rs b/crates/perry-codegen/src/fn_info.rs new file mode 100644 index 0000000000..fe022e3d78 --- /dev/null +++ b/crates/perry-codegen/src/fn_info.rs @@ -0,0 +1,306 @@ +//! One static `JsFunctionInfo` (perry-abi) per JS body a function object runs. +//! +//! A function object's header points at its body's info +//! (`perry_abi::CLOSURE_INFO_OFFSET`); every fact a caller needs about the +//! body — its parameter count, rest kind, `.length`, arrow / strict / async / +//! generator bits, the compiler-private direct-call clones — is read from it. +//! Codegen emits the info as a constant next to the body it describes, +//! `@$info`, and every allocation of a function object names that +//! constant instead of the body. +//! +//! Emission is two-sided and module-scoped: +//! * every allocation site asks its block for the info of the body it +//! allocates (`LlBlock::fn_info_ref`), which records the request; +//! * the module records the facts it knows about its own bodies +//! ([`FnInfoState::facts_mut`]) — the same metadata that used to become +//! module-init registration calls; +//! * after all functions exist, [`FnInfoState::render_globals`] defines one +//! info per body this module DEFINES that is requested here or visible to +//! other modules (an external-linkage value wrapper another module may +//! allocate), and declares `external` the info of every requested body +//! another module defines. A foreign body's info is never copied: the +//! runtime keys a function's singleton object by its info, so every module +//! must name the one the defining module emits. + +use std::collections::{BTreeMap, BTreeSet}; + +use crate::runtime_abi::{ + FN_ARROW, FN_ASYNC, FN_ASYNC_GENERATOR, FN_GENERATOR, FN_HAS_DECLARED, FN_HAS_LENGTH, + FN_REST_SYNTHETIC_ARGUMENTS, FN_REST_USER, FN_REST_USER_AND_ARGUMENTS, FN_STRICT, +}; + +/// The LLVM type of a `JsFunctionInfo`, field for field (perry-abi's +/// `#[repr(C)]` layout; LLVM lays it out for the target exactly as the +/// runtime's C layout does, 32-bit pointers included). +pub(crate) const INFO_TYPE: &str = + "{ ptr, i16, i16, i32, i32, i32, ptr, i64, ptr, i32, i16, i16, i64 }"; + +/// The info symbol of `body` (no `@`). +pub(crate) fn info_symbol(body: &str) -> String { + format!("{body}$info") +} + +/// A compiler-private direct-call clone of a body. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct CloneTarget { + pub symbol: String, + pub captures: u32, + pub boxed_mask: u64, +} + +/// What the module knows about one of its bodies beyond its parameter +/// count, which comes from the body's definition. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub(crate) struct FnInfoFacts { + pub flags: u32, + pub rest_fixed: u16, + pub length: u32, + pub declared: u16, + pub trusted: Option, + pub versioned: Option, +} + +/// The rest kind of a body with a rest / `arguments` array. +#[derive(Clone, Copy, Debug)] +pub(crate) enum RestKind { + User, + SyntheticArguments, + UserAndArguments, +} + +impl FnInfoFacts { + /// A body whose array parameter bundles every argument from `fixed` on. + pub(crate) fn set_rest(&mut self, fixed: usize, kind: RestKind) { + let bit = match kind { + RestKind::User => FN_REST_USER, + RestKind::SyntheticArguments => FN_REST_SYNTHETIC_ARGUMENTS, + RestKind::UserAndArguments => FN_REST_USER_AND_ARGUMENTS, + }; + self.flags = (self.flags + & !(FN_REST_USER | FN_REST_SYNTHETIC_ARGUMENTS | FN_REST_USER_AND_ARGUMENTS)) + | bit; + self.rest_fixed = saturate_u16(fixed as u64); + } + + /// The JS-visible declared parameter count (`.length`'s fallback). + pub(crate) fn set_declared(&mut self, declared: u32) { + self.declared = saturate_u16(u64::from(declared)); + self.flags |= FN_HAS_DECLARED; + } + + /// The ECMAScript `.length`. + pub(crate) fn set_length(&mut self, length: u32) { + self.length = length; + self.flags |= FN_HAS_LENGTH; + } + + pub(crate) fn set_arrow(&mut self) { + self.flags |= FN_ARROW; + } + + pub(crate) fn set_strict(&mut self) { + self.flags |= FN_STRICT; + } + + pub(crate) fn set_async(&mut self) { + self.flags |= FN_ASYNC; + } + + pub(crate) fn set_generator(&mut self) { + self.flags |= FN_GENERATOR; + } + + /// `async function*`: async and async-generator both. + pub(crate) fn set_async_generator(&mut self) { + self.flags |= FN_ASYNC_GENERATOR | FN_ASYNC; + } +} + +fn saturate_u16(value: u64) -> u16 { + u16::try_from(value).unwrap_or(u16::MAX) +} + +/// A body this module defines, as the info renderer sees it. +pub(crate) struct DefinedBody { + /// JS `f64` parameters after the callee and the receiver. + pub params: usize, + /// The body's LLVM linkage (empty = external). + pub linkage: String, +} + +/// The module's info requests and facts. +#[derive(Default)] +pub(crate) struct FnInfoState { + requested: BTreeSet, + facts: BTreeMap, +} + +impl FnInfoState { + /// Record that code in this module names `body`'s info; returns the + /// `@`-prefixed info symbol. + pub(crate) fn request(&mut self, body: &str) -> String { + if !self.requested.contains(body) { + self.requested.insert(body.to_string()); + } + format!("@{}", info_symbol(body)) + } + + /// The facts of a body this module defines. + pub(crate) fn facts_mut(&mut self, body: &str) -> &mut FnInfoFacts { + self.facts.entry(body.to_string()).or_default() + } + + /// The `@...$info` global lines: a definition for every body this module + /// defines that is requested or has recorded facts or is `exported` + /// (an external value wrapper another module may allocate), and an + /// `external` declaration for every requested body defined elsewhere. + /// `defined(body)` answers for this module's definitions. + pub(crate) fn render_globals( + &self, + defined: impl Fn(&str) -> Option, + exported: impl IntoIterator, + ) -> Vec { + let mut bodies: BTreeSet<&str> = self.requested.iter().map(String::as_str).collect(); + bodies.extend(self.facts.keys().map(String::as_str)); + let exported: Vec = exported.into_iter().collect(); + bodies.extend(exported.iter().map(String::as_str)); + let mut out = Vec::with_capacity(bodies.len()); + for body in bodies { + match defined(body) { + Some(def) => out.push(render_definition( + body, + &def, + self.facts.get(body).cloned().unwrap_or_default(), + )), + None if self.requested.contains(body) => out.push(format!( + "@{} = external constant {}", + info_symbol(body), + INFO_TYPE + )), + // Facts about a body this module does not define (a closure + // the module never emitted) describe nothing to emit. + None => {} + } + } + out + } +} + +fn render_definition(body: &str, def: &DefinedBody, facts: FnInfoFacts) -> String { + let linkage = match def.linkage.as_str() { + "" => String::new(), + other => format!("{other} "), + }; + let clone = |target: &Option| match target { + Some(t) => (format!("@{}", t.symbol), t.captures, t.boxed_mask), + None => ("null".to_string(), 0, 0), + }; + let (trusted_code, trusted_captures, trusted_mask) = clone(&facts.trusted); + let (versioned_code, versioned_captures, versioned_mask) = clone(&facts.versioned); + format!( + "@{info} = {linkage}constant {ty} {{ ptr @{body}, i16 {params}, i16 {rest}, i32 {flags}, \ + i32 {length}, i32 {tcap}, ptr {tcode}, i64 {tmask}, ptr {vcode}, i32 {vcap}, \ + i16 {declared}, i16 0, i64 {vmask} }}", + info = info_symbol(body), + ty = INFO_TYPE, + params = saturate_u16(def.params as u64), + rest = facts.rest_fixed, + flags = facts.flags, + length = facts.length, + tcap = trusted_captures, + tcode = trusted_code, + tmask = trusted_mask as i64, + vcode = versioned_code, + vcap = versioned_captures, + declared = facts.declared, + vmask = versioned_mask as i64, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn defined(params: usize, linkage: &str) -> Option { + Some(DefinedBody { + params, + linkage: linkage.to_string(), + }) + } + + #[test] + fn a_requested_local_body_gets_a_definition_with_its_facts() { + let mut state = FnInfoState::default(); + assert_eq!( + state.request("perry_closure_m__3"), + "@perry_closure_m__3$info" + ); + let facts = state.facts_mut("perry_closure_m__3"); + facts.set_rest(1, RestKind::User); + facts.set_length(1); + facts.set_arrow(); + let lines = state.render_globals( + |b| { + (b == "perry_closure_m__3") + .then(|| defined(2, "internal")) + .flatten() + }, + [], + ); + assert_eq!( + lines, + vec![format!( + "@perry_closure_m__3$info = internal constant {INFO_TYPE} {{ ptr @perry_closure_m__3, \ + i16 2, i16 1, i32 {}, i32 1, i32 0, ptr null, i64 0, ptr null, i32 0, i16 0, i16 0, i64 0 }}", + FN_REST_USER | FN_HAS_LENGTH | FN_ARROW + )] + ); + } + + #[test] + fn a_foreign_body_is_declared_never_copied() { + let mut state = FnInfoState::default(); + state.request("__perry_wrap_perry_fn_other__f"); + let lines = state.render_globals(|_| None, []); + assert_eq!( + lines, + vec![format!( + "@__perry_wrap_perry_fn_other__f$info = external constant {INFO_TYPE}" + )] + ); + } + + #[test] + fn an_exported_wrapper_is_defined_even_when_nothing_here_allocates_it() { + let state = FnInfoState::default(); + let lines = state.render_globals( + |b| { + (b == "__perry_wrap_perry_fn_m__g") + .then(|| defined(1, "")) + .flatten() + }, + ["__perry_wrap_perry_fn_m__g".to_string()], + ); + assert_eq!(lines.len(), 1); + assert!(lines[0].starts_with(&format!( + "@__perry_wrap_perry_fn_m__g$info = constant {INFO_TYPE} {{ ptr @__perry_wrap_perry_fn_m__g, i16 1," + ))); + } + + #[test] + fn clone_targets_are_named() { + let mut state = FnInfoState::default(); + state.request("perry_closure_m__9"); + state.facts_mut("perry_closure_m__9").trusted = Some(CloneTarget { + symbol: "perry_closure_m__9$trusted_boxes".to_string(), + captures: 2, + boxed_mask: 0b10, + }); + let lines = state.render_globals(|_| defined(0, "internal"), []); + assert!( + lines[0].contains("i32 2, ptr @perry_closure_m__9$trusted_boxes, i64 2, ptr null"), + "{}", + lines[0] + ); + } +} diff --git a/crates/perry-codegen/src/function.rs b/crates/perry-codegen/src/function.rs index 4a3b7335ef..80a8165276 100644 --- a/crates/perry-codegen/src/function.rs +++ b/crates/perry-codegen/src/function.rs @@ -870,6 +870,11 @@ impl LlFunction { self.reg_counter.set_preserve_none_fns(fns); } + /// Install the module's `JsFunctionInfo` state (`crate::fn_info`). + pub(crate) fn set_fn_infos(&self, infos: Rc>) { + self.reg_counter.set_fn_infos(infos); + } + /// Point this function's null-guard loads at the module's (possibly /// module-prefixed) null-guard global. See `RegCounter::null_guard_symbol`. pub(crate) fn set_null_guard_global(&self, global: &str) { diff --git a/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv b/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv index b8169c8d43..914db12021 100644 --- a/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv @@ -2749,18 +2749,6 @@ js_register_class_static_method_bind_length Leaf js_register_class_static_setter Reenters js_register_class_string_member_order Leaf js_register_class_to_string_tag Leaf -js_register_closure_arity Leaf -js_register_closure_arrow_function Leaf -js_register_closure_async_function Leaf -js_register_closure_async_generator_function Leaf -js_register_closure_generator_function Leaf -js_register_closure_length Leaf -js_register_closure_rest Leaf -js_register_closure_rest_and_arguments Leaf -js_register_closure_strict_function Leaf -js_register_closure_synthetic_arguments Leaf -js_register_closure_trusted_direct Leaf -js_register_closure_versioned_loop_direct Leaf js_register_embedded_asset Leaf js_register_embedded_text_asset Leaf js_register_embedded_zstd_asset Reenters diff --git a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv index 2910e150eb..24ca620ba2 100644 --- a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv +++ b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv @@ -2746,18 +2746,6 @@ js_register_class_static_method_bind_length Reenters js_register_class_static_setter Reenters js_register_class_string_member_order Leaf js_register_class_to_string_tag Reenters -js_register_closure_arity Leaf -js_register_closure_arrow_function Leaf -js_register_closure_async_function Leaf -js_register_closure_async_generator_function Leaf -js_register_closure_generator_function Leaf -js_register_closure_length Leaf -js_register_closure_rest Leaf -js_register_closure_rest_and_arguments Leaf -js_register_closure_strict_function Leaf -js_register_closure_synthetic_arguments Leaf -js_register_closure_trusted_direct Leaf -js_register_closure_versioned_loop_direct Leaf js_register_embedded_asset Leaf js_register_embedded_text_asset Leaf js_register_embedded_zstd_asset Reenters diff --git a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv index 6db7fc1fa6..e611d78064 100644 --- a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv @@ -2746,18 +2746,6 @@ js_register_class_static_method_bind_length Leaf js_register_class_static_setter Reenters js_register_class_string_member_order Leaf js_register_class_to_string_tag Leaf -js_register_closure_arity Leaf -js_register_closure_arrow_function Leaf -js_register_closure_async_function Leaf -js_register_closure_async_generator_function Leaf -js_register_closure_generator_function Leaf -js_register_closure_length Leaf -js_register_closure_rest Leaf -js_register_closure_rest_and_arguments Leaf -js_register_closure_strict_function Leaf -js_register_closure_synthetic_arguments Leaf -js_register_closure_trusted_direct Leaf -js_register_closure_versioned_loop_direct Leaf js_register_embedded_asset Leaf js_register_embedded_text_asset Leaf js_register_embedded_zstd_asset Reenters diff --git a/crates/perry-codegen/src/lib.rs b/crates/perry-codegen/src/lib.rs index 7fa575d719..912749b500 100644 --- a/crates/perry-codegen/src/lib.rs +++ b/crates/perry-codegen/src/lib.rs @@ -16,6 +16,7 @@ pub(crate) mod dialect; pub(crate) mod eh_mode; pub mod expr; pub mod ext_registry; +pub(crate) mod fn_info; pub mod function; pub(crate) mod gc_call_effects; pub mod gc_map; diff --git a/crates/perry-codegen/src/lower_call/early_branches.rs b/crates/perry-codegen/src/lower_call/early_branches.rs index 7e4c72875d..20ef67eb04 100644 --- a/crates/perry-codegen/src/lower_call/early_branches.rs +++ b/crates/perry-codegen/src/lower_call/early_branches.rs @@ -424,11 +424,10 @@ pub fn try_lower_closure_typed_local_call( lowered_args.push(lower_expr(ctx, a)?); } - // Issue #493: rest-bundling is now handled inside js_closure_callN - // via the runtime closure-rest registry — see - // `js_register_closure_rest` (registered for every closure body - // with `...rest` at module init) and `dispatch_rest_bundled` in - // `crates/perry-runtime/src/closure.rs`. Bundling at the static + // Issue #493: rest-bundling is handled inside js_closure_callN from + // the body's `JsFunctionInfo` (every body with `...rest` records + // it, `crate::fn_info`) — see `dispatch_rest_bundled` in + // `crates/perry-runtime/src/closure/registry.rs`. Bundling at the static // call site here would double-wrap (the runtime would re-bundle // the already-bundled array into `[[a,b,c]]`), so the call site // now passes the raw args through and lets the runtime @@ -581,14 +580,15 @@ pub fn try_lower_closure_typed_local_call( // dynamic question is "is the value still the closure // whose body is `@closure_fn`", and two compare-only loads // answer it: the GcHeader's (type, flags) halfword is exactly - // (CLOSURE, not FORWARDED) and `func_ptr == @closure_fn`. A + // (CLOSURE, not FORWARDED) and its info word is + // `@closure_fn$info` (`crate::fn_info`). A // forwarded (moved) closure fails the header compare and // takes the guard, which resolves forwarding as it always // did. A non-closure heap cell fails the header compare — // the kind is the GC type byte, not payload bytes; the // runtime's volatile-ordering // ceremony guards a transmute-and-call of an ARBITRARY - // func_ptr, which this compare-only probe never does. + // code pointer, which this compare-only probe never does. // #7170 R1 single-binding fact: identity holds with // FuncRef strength, so the runtime guard AND the probe // are both unnecessary — the value cannot be anything but @@ -618,10 +618,9 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = probe_idx; { - let fp_offset = crate::target_layout::closure_func_ptr_offset_bytes( - ctx.target_triple, - ) - .to_string(); + let info_offset = + crate::target_layout::closure_info_offset_bytes(ctx.target_triple) + .to_string(); let blk = ctx.block(); let bits = blk.bitcast_double_to_i64(&recv_box); let handle = blk.and(I64, &bits, crate::nanbox::POINTER_MASK_I64); @@ -643,24 +642,27 @@ pub fn try_lower_closure_typed_local_call( &masked, &crate::runtime_abi::GC_TYPE_CLOSURE.to_string(), ); - let fp_addr = blk.add(I64, &handle, &fp_offset); - let fp_ptr = blk.inttoptr(I64, &fp_addr); - let fp = blk.load(I64, &fp_ptr); - let expected_fp = blk.ptrtoint(&format!("@{}", closure_fn), I64); - let fp_ok = blk.icmp_eq(I64, &fp, &expected_fp); - let hit = blk.and(I1, &kind_ok, &fp_ok); + let info_addr = blk.add(I64, &handle, &info_offset); + let info_ptr = blk.inttoptr(I64, &info_addr); + let info = blk.load(I64, &info_ptr); + let expected_info = blk.fn_info_ref(&closure_fn); + let expected_info = blk.ptrtoint(&expected_info, I64); + let info_ok = blk.icmp_eq(I64, &info, &expected_info); + let hit = blk.and(I1, &kind_ok, &info_ok); blk.cond_br(&hit, &fast_label, &guard_call_label); } ctx.current_block = guard_call_idx; } if !guard_free { + // The guard compares the value's info with the body's. + let expected_info = ctx.block().fn_info_ref(&closure_fn); let guard_ok = ctx.block().call( I32, "js_typed_feedback_closure_direct_call_guard", &[ (I64, &site_id), (DOUBLE, &recv_box), - (crate::types::PTR, &format!("@{}", closure_fn)), + (crate::types::PTR, &expected_info), (I32, &expected_arity), (I32, &call_arity), ], diff --git a/crates/perry-codegen/src/lower_call/func_ref.rs b/crates/perry-codegen/src/lower_call/func_ref.rs index d896321a32..9227bd4d35 100644 --- a/crates/perry-codegen/src/lower_call/func_ref.rs +++ b/crates/perry-codegen/src/lower_call/func_ref.rs @@ -1457,10 +1457,10 @@ pub fn try_lower_func_ref_call( // arg_group.release(ctx); if ctx.local_generator_funcs.contains(fid) { - let wrap_ptr = format!("@__perry_wrap_{}", fname); + let wrap_info = ctx.block().fn_info_ref(&format!("__perry_wrap_{}", fname)); let closure_handle = ctx.block() - .call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_ptr)]); + .call(I64, "js_closure_alloc_singleton", &[(PTR, &wrap_info)]); return Ok(Some(ctx.block().call( DOUBLE, "js_generator_attach_closure_prototype", diff --git a/crates/perry-codegen/src/lower_call/native_table/thread_lodash.rs b/crates/perry-codegen/src/lower_call/native_table/thread_lodash.rs index a57c0d2908..dd4e4da2cf 100644 --- a/crates/perry-codegen/src/lower_call/native_table/thread_lodash.rs +++ b/crates/perry-codegen/src/lower_call/native_table/thread_lodash.rs @@ -28,7 +28,7 @@ pub(super) const THREAD_LODASH_ROWS: &[NativeModSig] = &[ // ========== perry/thread (parallelMap, parallelFilter, spawn) ========== // Runtime expects both args as NaN-boxed f64 values and returns the same // — no unboxing/reboxing needed on either side. Closure is a POINTER_TAG'd - // ClosureHeader; the runtime reads `func_ptr` and calls it per element. + // ClosureHeader; the runtime calls its body per element. NativeModSig { module: "perry/thread", has_receiver: false, diff --git a/crates/perry-codegen/src/lower_call/property_get/imported_object.rs b/crates/perry-codegen/src/lower_call/property_get/imported_object.rs index dc10cb8b13..1af87f664b 100644 --- a/crates/perry-codegen/src/lower_call/property_get/imported_object.rs +++ b/crates/perry-codegen/src/lower_call/property_get/imported_object.rs @@ -121,13 +121,12 @@ fn emit_cached_own_method_guard( .block() .gep(DOUBLE, &fields, &[(I64, &field_index.to_string())]); let closure_value = ctx.block().load(DOUBLE, &slot); + // The guard compares the value's `JsFunctionInfo` with the body's. + let expected_info = ctx.block().fn_info_ref(closure_symbol); let fast_handle = ctx.block().call( I64, "js_closure_exact_func_guard", - &[ - (DOUBLE, &closure_value), - (PTR, &format!("@{closure_symbol}")), - ], + &[(DOUBLE, &closure_value), (PTR, &expected_info)], ); let guard_passes = ctx.block().icmp_ne(I64, &fast_handle, "0"); let fast_end = ctx.block().label.clone(); @@ -144,7 +143,7 @@ fn emit_cached_own_method_guard( (I32, &field_index.to_string()), (PTR, &bytes_global), (I64, &name_len), - (PTR, &format!("@{closure_symbol}")), + (PTR, &expected_info), (PTR, &ic_slot.slot_ref), ], ); diff --git a/crates/perry-codegen/src/module.rs b/crates/perry-codegen/src/module.rs index cdfd59aa3e..1a902a43a9 100644 --- a/crates/perry-codegen/src/module.rs +++ b/crates/perry-codegen/src/module.rs @@ -99,6 +99,10 @@ pub struct LlModule { /// the specialization plan is final (`codegen/mod.rs`), and reads happen /// at call-emission/render time, so define order never matters. preserve_none_fns: Rc>>, + /// The module's `JsFunctionInfo` requests and facts (`crate::fn_info`), + /// shared with every function's register counter; emitted by + /// [`Self::emit_fn_infos`] once every function exists. + fn_infos: Rc>, } /// The `source_filename` every Perry-emitted module records. @@ -157,6 +161,7 @@ impl LlModule { native_rep_records: Vec::new(), fp_flags, preserve_none_fns: Rc::new(RefCell::new(HashSet::new())), + fn_infos: Rc::new(RefCell::new(crate::fn_info::FnInfoState::default())), } } @@ -302,11 +307,52 @@ impl LlModule { // #8175: every function shares the module's preserve_nonecc registry, // so its call sites and its own define header agree on the convention. func.set_preserve_none_fns(Rc::clone(&self.preserve_none_fns)); + func.set_fn_infos(Rc::clone(&self.fn_infos)); func.set_null_guard_global(&self.null_guard_global()); self.functions.push(func); self.functions.last_mut().unwrap() } + /// Record a fact about `body`, a JS body this module defines, in its + /// `JsFunctionInfo` (`crate::fn_info`). + pub(crate) fn note_fn_info( + &self, + body: &str, + note: impl FnOnce(&mut crate::fn_info::FnInfoFacts), + ) { + note(self.fn_infos.borrow_mut().facts_mut(body)); + } + + /// Emit the module's `JsFunctionInfo` globals (`crate::fn_info`): one + /// definition per body this module defines that is allocated here, has + /// recorded facts, or is an external-linkage value wrapper another module + /// may allocate; an `external` declaration for every allocated body + /// another module defines. Runs once, after every function exists. + pub(crate) fn emit_fn_infos(&mut self) { + let lines = { + let functions = &self.functions; + let by_name: std::collections::HashMap<&str, &LlFunction> = + functions.iter().map(|f| (f.name.as_str(), f)).collect(); + let exported = functions + .iter() + .filter(|f| { + f.linkage.is_empty() && f.name.starts_with("__perry_wrap_") && is_js_body(f) + }) + .map(|f| f.name.clone()); + self.fn_infos.borrow().render_globals( + |body| { + let f = by_name.get(body)?; + Some(crate::fn_info::DefinedBody { + params: f.params.len().saturating_sub(2), + linkage: f.linkage.clone(), + }) + }, + exported, + ) + }; + self.globals.extend(lines); + } + /// A defined function by symbol name. pub(crate) fn function_named(&self, name: &str) -> Option<&LlFunction> { self.functions.iter().find(|f| f.name == name) @@ -1184,3 +1230,11 @@ pub(crate) struct OwnedCodegenUnitPart { #[cfg(test)] mod tests; + +/// Whether `f` is a JS body (`double body(i64 %this_closure, i64 %js_this, ...)`, +/// `perry_abi::JS_BODY_*`). +fn is_js_body(f: &LlFunction) -> bool { + f.params.len() >= 2 + && f.params[0].1 == crate::expr::body_call::JS_BODY_CALLEE + && f.params[1].1 == crate::expr::body_call::JS_BODY_THIS +} diff --git a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/language_core.rs b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/language_core.rs index 5f2d720156..ce7be62902 100644 --- a/crates/perry-codegen/src/runtime_decls/stdlib_ffi/language_core.rs +++ b/crates/perry-codegen/src/runtime_decls/stdlib_ffi/language_core.rs @@ -348,7 +348,8 @@ pub(crate) fn declare_core(module: &mut LlModule) { // ========== Class registration ========== module.declare_function("js_register_class_getter", VOID, &[I64, I64, I64, I64]); // Refs #486: per-class setter dispatch — see object.rs::js_register_class_setter. - module.declare_function("js_register_class_setter", VOID, &[I64, I64, I64, I64]); + // The last argument is the setter's default-aware spec `.length`. + module.declare_function("js_register_class_setter", VOID, &[I64, I64, I64, I64, I32]); module.declare_function( "js_register_class_string_member_order", VOID, @@ -374,7 +375,7 @@ pub(crate) fn declare_core(module: &mut LlModule) { module.declare_function( "js_register_class_static_setter", VOID, - &[I64, I64, I64, I64], + &[I64, I64, I64, I64, I32], ); module.declare_function( "js_register_class_method", @@ -385,12 +386,14 @@ pub(crate) fn declare_core(module: &mut LlModule) { // ()` can replay it on a dynamically-allocated instance. module.declare_function("js_register_class_constructor", VOID, &[I64, I64, I64, I64]); // Constructor synth/rest flags: (class_id, has_synthetic_arguments, - // has_rest) — consulted by the `super(...spread)` apply path so it packs a - // pass-through parent ctor's `arguments` / rest slot correctly. + // has_rest, rest_fixed) — consulted by the `super(...spread)` apply path + // so it packs a pass-through parent ctor's `arguments` / rest slot + // correctly, and by the dynamic construct path, which bundles every + // argument from `rest_fixed` on into the rest array (-1: no rest bundle). module.declare_function( "js_register_class_constructor_flags", VOID, - &[I64, I64, I64], + &[I64, I64, I64, I64], ); // #1788: register a class STATIC method + dispatch an inherited static // method on a class value (subclass extends a class-expression value). diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index 3a5a1f70a4..b40a98be0d 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -196,9 +196,10 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { // Closure / function-as-value primitives (Phase D). // - // - js_closure_alloc(func_ptr, capture_count) -> *mut ClosureHeader - // Allocates a closure object pointing at the given function with - // space for `capture_count` captured-value slots. + // - js_closure_alloc(info, capture_count) -> *mut ClosureHeader + // Allocates a function object running the body the given + // `JsFunctionInfo` (`crate::fn_info`) describes, with space for + // `capture_count` captured-value slots. // - js_closure_set/get_capture_bits(closure, idx, bits) // Read/write a captured value's raw JSValueBits at slot `idx`. // - js_closure_set/get_capture_f64(closure, idx, value) @@ -213,9 +214,9 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { module.declare_function("js_closure_alloc", I64, &[PTR, I32]); module.declare_function("js_closure_alloc_init", I64, &[PTR, I32, PTR]); // Singleton-cached variant for non-capturing closures and FuncRef - // wrappers — same `func_ptr` returns the same cached ClosureHeader, + // wrappers — the same body info returns the same cached ClosureHeader, // skipping per-evaluation closure allocation on the hot loop. See - // `crates/perry-runtime/src/closure.rs::js_closure_alloc_singleton`. + // `crates/perry-runtime/src/closure/alloc.rs::js_closure_alloc_singleton`. module.declare_function("js_closure_alloc_singleton", I64, &[PTR]); // A class constructor as a value: the class's per-agent function object // (`object/class_value.rs`; #11414). @@ -225,7 +226,7 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { module.declare_function("js_class_static_field_get", DOUBLE, &[I32, PTR, I64]); module.declare_function("js_class_static_field_put", VOID, &[I32, PTR, I64, DOUBLE]); // Singleton-cached variant for closures with captures, keyed by - // `(func_ptr, capture_bits…)`. Args: (func_ptr, capture_count, + // `(info, capture_bits…)`. Args: (info, capture_count, // captures_ptr — pointer to `capture_count` u64 values). module.declare_function( "js_closure_alloc_with_captures_singleton", @@ -237,41 +238,11 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { module.declare_function("js_closure_get_capture_bits", I64, &[I64, I32]); module.declare_function("js_closure_set_capture_f64", VOID, &[I64, I32, DOUBLE]); module.declare_function("js_closure_get_capture_f64", DOUBLE, &[I64, I32]); - // Issue #493: register a closure body's rest-param arity in the runtime - // side-table so `js_closure_callN` can bundle trailing args at call - // sites where codegen doesn't know the closure's arity statically - // (e.g. `obj.cb(a, b, c)` where `cb` is a class field holding an - // arrow with `...rest`). Called once per closure body at module init. - module.declare_function("js_register_closure_rest", VOID, &[PTR, I32]); - // Refs #915 (gap 1 from #899): variant that flags the rest param as the - // HIR-synthesized `arguments` array — the dispatcher then bundles ALL - // passed args into the rest slot, matching JS spec semantics for - // `arguments.length` in a `function(a, b) { …arguments… }` returned from - // another function. - module.declare_function("js_register_closure_synthetic_arguments", VOID, &[PTR, I32]); - module.declare_function("js_register_closure_rest_and_arguments", VOID, &[PTR, I32]); - module.declare_function("js_register_closure_arity", VOID, &[PTR, I32]); - module.declare_function("js_register_closure_length", VOID, &[PTR, I32]); - module.declare_function("js_register_closure_arrow_function", VOID, &[PTR]); - module.declare_function( - "js_register_closure_trusted_direct", - VOID, - &[PTR, PTR, I32, I64], - ); - module.declare_function( - "js_register_closure_versioned_loop_direct", - VOID, - &[PTR, PTR, I32, I64], - ); module.declare_function( "js_closure_resolve_versioned_loop_direct_call", PTR, &[I64, I32], ); - module.declare_function("js_register_closure_strict_function", VOID, &[PTR]); - module.declare_function("js_register_closure_async_function", VOID, &[PTR]); - module.declare_function("js_register_closure_generator_function", VOID, &[PTR]); - module.declare_function("js_register_closure_async_generator_function", VOID, &[PTR]); // #5504: tag-checking unbox for dynamic call callees — throws // `TypeError: value is not a function` instead of masking a // non-pointer value's low 48 bits into a wild closure pointer. diff --git a/crates/perry-codegen/src/target_layout.rs b/crates/perry-codegen/src/target_layout.rs index fd9f19dc78..1f7996fe87 100644 --- a/crates/perry-codegen/src/target_layout.rs +++ b/crates/perry-codegen/src/target_layout.rs @@ -179,7 +179,7 @@ pub const OBJECT_META_SPILL_OFFSET_BYTES: u64 = 32; pub const ARRAY_HEADER_SIZE_BYTES: u64 = 8; /// `std::mem::size_of::()` for the -/// target: `{capture_count: u32, shape_id: u32, func_ptr, props}` — 24 bytes +/// target: `{capture_count: u32, shape_id: u32, info, props}` — 24 bytes /// on LP64 and 16 on ILP32 (`perry_abi::CLOSURE_HEADER_SIZE` is the LP64 /// value the runtime asserts). Trusted exact-arrow bodies use this offset to /// read compiler-installed raw box capture pointers directly from their @@ -194,10 +194,11 @@ pub fn closure_header_size_bytes(target_triple: &str) -> u64 { } } -/// Byte offset of `ClosureHeader::func_ptr` (8 on every target: the u32 -/// capture count and u32 ShapeId precede it). -pub fn closure_func_ptr_offset_bytes(_target_triple: &str) -> u64 { - crate::runtime_abi::CLOSURE_FUNC_PTR_OFFSET as u64 +/// Byte offset of `ClosureHeader::info`, the function object's +/// `JsFunctionInfo` pointer (8 on every target: the u32 capture count and u32 +/// ShapeId precede it). +pub fn closure_info_offset_bytes(_target_triple: &str) -> u64 { + crate::runtime_abi::CLOSURE_INFO_OFFSET as u64 } /// Minimum number of inline field slots `perry-runtime` allocates for EVERY diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index 8e0316c211..8614d19320 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -3203,7 +3203,7 @@ js_register_aux_tick_begin void ptr js_register_class_computed_accessor void i64,f64,i64,i64,i64,i64 js_register_class_computed_method void i64,f64,i64,i64,i64,i64,i64 js_register_class_constructor void i64,i64,i64,i64 -js_register_class_constructor_flags void i64,i64,i64 +js_register_class_constructor_flags void i64,i64,i64,i64 js_register_class_extends_data_view void i32u js_register_class_extends_error void i32u js_register_class_extends_typed_array void i32u @@ -3217,27 +3217,15 @@ js_register_class_method_bind_length void i64,ptr,i64,i64 js_register_class_name void i32u,ptr,i32u js_register_class_parent void i32u,i32u js_register_class_parent_dynamic void i32u,f64 -js_register_class_setter void i64,ptr,i64,i64 +js_register_class_setter void i64,ptr,i64,i64,i32s js_register_class_source void i32u,ptr,i32u js_register_class_source_static void i32u,ptr,i32u js_register_class_static_getter void i64,ptr,i64,i64 js_register_class_static_method void i64,ptr,i64,i64,i64,i64 js_register_class_static_method_bind_length void i64,ptr,i64,i64 -js_register_class_static_setter void i64,ptr,i64,i64 +js_register_class_static_setter void i64,ptr,i64,i64,i32s js_register_class_string_member_order void i64,ptr,i64,i64,i64 js_register_class_to_string_tag void i32u,i64 -js_register_closure_arity void ptr,i32u -js_register_closure_arrow_function void ptr -js_register_closure_async_function void ptr -js_register_closure_async_generator_function void ptr -js_register_closure_generator_function void ptr -js_register_closure_length void ptr,i32u -js_register_closure_rest void ptr,i32u -js_register_closure_rest_and_arguments void ptr,i32u -js_register_closure_strict_function void ptr -js_register_closure_synthetic_arguments void ptr,i32u -js_register_closure_trusted_direct void ptr,ptr,i32u,i64 -js_register_closure_versioned_loop_direct void ptr,ptr,i32u,i64 js_register_embedded_asset void ptr,usize,ptr,usize js_register_embedded_text_asset void ptr,usize,ptr,usize js_register_embedded_zstd_asset i32s ptr,usize,ptr,usize,usize,i32u diff --git a/crates/perry-ext-events/src/dispatch_ext.rs b/crates/perry-ext-events/src/dispatch_ext.rs index 267839b2dc..ceb6788d8e 100644 --- a/crates/perry-ext-events/src/dispatch_ext.rs +++ b/crates/perry-ext-events/src/dispatch_ext.rs @@ -198,7 +198,7 @@ mod tests { } fn listener_value() -> f64 { - let closure = perry_ffi::alloc_closure(noop_listener as perry_ffi::JsBody0, 0); + let closure = perry_ffi::alloc_closure(perry_ffi::js_function_info!(noop_listener, 0), 0); nanbox_pointer_bits(closure as i64) } diff --git a/crates/perry-ext-events/src/lib.rs b/crates/perry-ext-events/src/lib.rs index bb2898355a..d50ea64ce5 100644 --- a/crates/perry-ext-events/src/lib.rs +++ b/crates/perry-ext-events/src/lib.rs @@ -856,14 +856,6 @@ fn remove_listener_by_callback(emitter: &mut EventEmitterHandle, callback: i64) } } -static RAW_ONCE_WRAPPER_REST_REGISTERED: Once = Once::new(); - -fn ensure_raw_once_wrapper_rest_registered() { - RAW_ONCE_WRAPPER_REST_REGISTERED.call_once(|| { - perry_ffi::register_closure_rest(event_emitter_once_wrapper as perry_ffi::JsBody1, 0); - }); -} - unsafe fn set_closure_dynamic_prop(closure: *mut RawClosureHeader, name: &[u8], value: f64) { let key = js_string_from_bytes(name.as_ptr(), name.len() as u32); js_object_set_field_by_name(closure as *mut ObjectHeader, key, value); @@ -873,9 +865,8 @@ unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i6 if callback == 0 { return 0; } - ensure_raw_once_wrapper_rest_registered(); - let wrapper = perry_ffi::alloc_closure(event_emitter_once_wrapper as perry_ffi::JsBody1, 4); + let wrapper = perry_ffi::alloc_closure(&EVENT_EMITTER_ONCE_WRAPPER_INFO, 4); let event_ptr = js_string_from_bytes(event_name.as_ptr(), event_name.len() as u32); js_closure_set_capture_ptr(wrapper, 0, handle); js_closure_set_capture_ptr(wrapper, 1, event_ptr as i64); @@ -1187,8 +1178,10 @@ unsafe fn capture_listener_rejection(handle: Handle, result: f64) { if promise.is_null() { return; } - let on_rejected = - perry_ffi::alloc_closure(events_capture_rejection_handler as perry_ffi::JsBody1, 1); + let on_rejected = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_capture_rejection_handler, 1), + 1, + ); js_closure_set_capture_ptr(on_rejected, 0, handle); js_promise_then(promise, std::ptr::null_mut(), on_rejected); } @@ -1813,8 +1806,10 @@ pub unsafe extern "C" fn js_events_once( }; if let Some(signal) = signal { if let Some(signal_ptr) = object_ptr_from_value(signal) { - let abort_listener = - perry_ffi::alloc_closure(events_once_abort_listener as perry_ffi::JsBody0, 2); + let abort_listener = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_once_abort_listener, 0), + 2, + ); js_closure_set_capture_ptr(abort_listener, 0, handle); js_closure_set_capture_ptr(abort_listener, 1, raw as i64); js_abort_signal_add_listener( @@ -1838,8 +1833,10 @@ pub unsafe extern "C" fn js_events_once( if event_name_ptr.is_null() { return raw; } - let listener = - perry_ffi::alloc_closure(events_once_event_target_listener as perry_ffi::JsBody1, 3); + let listener = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_once_event_target_listener, 1), + 3, + ); js_closure_set_capture_ptr(listener, 0, raw as i64); js_closure_set_capture_ptr(listener, 1, target as i64); js_closure_set_capture_ptr(listener, 2, event_name_ptr as i64); @@ -1855,30 +1852,19 @@ pub unsafe extern "C" fn js_events_once( let event = scope.root_nanbox(f64::from_bits(nanbox_string_bits( event_name_ptr as *mut StringHeader, ))); - perry_ffi::register_closure_rest( - events_once_stream_resolve_listener as perry_ffi::JsBody1, - 0, - ); - let listener = - perry_ffi::alloc_closure(events_once_stream_resolve_listener as perry_ffi::JsBody1, 4); + let listener = perry_ffi::alloc_closure(&EVENTS_ONCE_STREAM_RESOLVE_LISTENER_INFO, 4); js_closure_set_capture_ptr(listener, 0, raw as i64); js_closure_set_capture_ptr(listener, 1, handle); js_closure_set_capture_ptr(listener, 2, 0); js_closure_set_capture_ptr(listener, 3, 0); let listener = scope.root_addr(listener as i64); if event_name != "error" { - perry_ffi::register_closure_rest( - events_once_stream_reject_listener as perry_ffi::JsBody1, - 0, - ); let error_event_ptr = js_string_from_bytes(b"error".as_ptr(), 5); let error_event = scope.root_nanbox(f64::from_bits(nanbox_string_bits( error_event_ptr as *mut StringHeader, ))); - let reject_listener = perry_ffi::alloc_closure( - events_once_stream_reject_listener as perry_ffi::JsBody1, - 4, - ); + let reject_listener = + perry_ffi::alloc_closure(&EVENTS_ONCE_STREAM_REJECT_LISTENER_INFO, 4); let reject_listener = scope.root_addr(reject_listener as i64); let event_name_ptr = (event.get().to_bits() & POINTER_MASK) as i64; js_closure_set_capture_ptr( @@ -1926,16 +1912,7 @@ pub unsafe extern "C" fn js_events_once( let event = scope.root_nanbox(f64::from_bits(nanbox_string_bits( event_name_ptr as *mut StringHeader, ))); - perry_ffi::register_closure_rest( - events_once_stream_resolve_listener as perry_ffi::JsBody1, - 0, - ); - perry_ffi::register_closure_rest( - events_once_stream_reject_listener as perry_ffi::JsBody1, - 0, - ); - let listener = - perry_ffi::alloc_closure(events_once_stream_resolve_listener as perry_ffi::JsBody1, 4); + let listener = perry_ffi::alloc_closure(&EVENTS_ONCE_STREAM_RESOLVE_LISTENER_INFO, 4); js_closure_set_capture_ptr(listener, 0, raw as i64); js_closure_set_capture_ptr(listener, 1, handle); js_closure_set_capture_ptr(listener, 2, 0); @@ -1946,10 +1923,8 @@ pub unsafe extern "C" fn js_events_once( let error_event = scope.root_nanbox(f64::from_bits(nanbox_string_bits( error_event_ptr as *mut StringHeader, ))); - let reject_listener = perry_ffi::alloc_closure( - events_once_stream_reject_listener as perry_ffi::JsBody1, - 4, - ); + let reject_listener = + perry_ffi::alloc_closure(&EVENTS_ONCE_STREAM_REJECT_LISTENER_INFO, 4); let reject_listener = scope.root_addr(reject_listener as i64); js_closure_set_capture_ptr( reject_listener.get() as *mut RawClosureHeader, @@ -1991,3 +1966,14 @@ pub unsafe extern "C" fn js_events_once( #[cfg(test)] mod tests; + +static EVENT_EMITTER_ONCE_WRAPPER_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(event_emitter_once_wrapper as perry_ffi::JsBody1).with_rest(0); + +static EVENTS_ONCE_STREAM_RESOLVE_LISTENER_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_once_stream_resolve_listener as perry_ffi::JsBody1) + .with_rest(0); + +static EVENTS_ONCE_STREAM_REJECT_LISTENER_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_once_stream_reject_listener as perry_ffi::JsBody1) + .with_rest(0); diff --git a/crates/perry-ext-events/src/module_iterators.rs b/crates/perry-ext-events/src/module_iterators.rs index 2f38ea6b51..0dbc78dec4 100644 --- a/crates/perry-ext-events/src/module_iterators.rs +++ b/crates/perry-ext-events/src/module_iterators.rs @@ -396,7 +396,7 @@ extern "C" fn events_on_async_iterator( packed.len() as u32, ); let object_root = scope.root_nanbox(nanbox_pointer_bits(object as i64)); - let next = perry_ffi::alloc_closure(events_on_next as perry_ffi::JsBody0, 1); + let next = perry_ffi::alloc_closure(&EVENTS_ON_NEXT_INFO, 1); js_closure_set_capture_ptr(next, 0, (state_root.get().to_bits() & POINTER_MASK) as i64); let next_root = scope.root_addr(next as i64); js_object_set_field( @@ -404,7 +404,7 @@ extern "C" fn events_on_async_iterator( 0, JsValue::from_object_ptr(next_root.get() as *mut u8), ); - let return_fn = perry_ffi::alloc_closure(events_on_return as perry_ffi::JsBody0, 1); + let return_fn = perry_ffi::alloc_closure(&EVENTS_ON_RETURN_INFO, 1); js_closure_set_capture_ptr( return_fn, 0, @@ -420,7 +420,7 @@ extern "C" fn events_on_async_iterator( let iterator = object_root.get(); let iterator_root = scope.root_nanbox(iterator); let symbol = js_symbol_well_known_async_iterator(); - let self_fn = perry_ffi::alloc_closure(events_on_iterator_self as perry_ffi::JsBody0, 1); + let self_fn = perry_ffi::alloc_closure(&EVENTS_ON_ITERATOR_SELF_INFO, 1); js_closure_set_capture_f64(self_fn, 0, iterator_root.get()); js_object_set_symbol_property( iterator_root.get(), @@ -438,11 +438,7 @@ pub(super) unsafe fn events_on_install_async_iterator( let scope = TransientRootScope::enter(); let queue_root = scope.root_nanbox(nanbox_pointer_bits(queue as i64)); let state_root = scope.root_nanbox(nanbox_pointer_bits(state as i64)); - perry_ffi::register_closure_arity(events_on_next as perry_ffi::JsBody0, 0); - perry_ffi::register_closure_arity(events_on_return as perry_ffi::JsBody0, 0); - perry_ffi::register_closure_arity(events_on_iterator_self as perry_ffi::JsBody0, 0); - perry_ffi::register_closure_arity(events_on_async_iterator as perry_ffi::JsBody0, 0); - let closure = perry_ffi::alloc_closure(events_on_async_iterator as perry_ffi::JsBody0, 1); + let closure = perry_ffi::alloc_closure(&EVENTS_ON_ASYNC_ITERATOR_INFO, 1); js_closure_set_capture_ptr( closure, 0, @@ -509,3 +505,15 @@ pub(super) extern "C" fn events_on_abort_listener( } undefined_value() } + +static EVENTS_ON_NEXT_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_on_next as perry_ffi::JsBody0).with_declared(0); + +static EVENTS_ON_RETURN_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_on_return as perry_ffi::JsBody0).with_declared(0); + +static EVENTS_ON_ITERATOR_SELF_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_on_iterator_self as perry_ffi::JsBody0).with_declared(0); + +static EVENTS_ON_ASYNC_ITERATOR_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(events_on_async_iterator as perry_ffi::JsBody0).with_declared(0); diff --git a/crates/perry-ext-events/src/module_on.rs b/crates/perry-ext-events/src/module_on.rs index 4140a6998a..b6663a676f 100644 --- a/crates/perry-ext-events/src/module_on.rs +++ b/crates/perry-ext-events/src/module_on.rs @@ -30,7 +30,8 @@ pub unsafe extern "C" fn js_events_on( if signal.is_some_and(signal_is_aborted) { js_throw(js_abort_error_value()); } - let listener = perry_ffi::alloc_closure(events_on_queue_listener as perry_ffi::JsBody1, 1); + let listener = + perry_ffi::alloc_closure(perry_ffi::js_function_info!(events_on_queue_listener, 1), 1); js_closure_set_capture_ptr( listener, 0, @@ -92,8 +93,10 @@ pub unsafe extern "C" fn js_events_on( }; let close_ptr = js_string_from_bytes(close_name.as_ptr(), close_name.len() as u32); - let close_listener = - perry_ffi::alloc_closure(events_on_close_listener as perry_ffi::JsBody0, 1); + let close_listener = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_on_close_listener, 0), + 1, + ); js_closure_set_capture_ptr( close_listener, 0, @@ -137,8 +140,10 @@ pub unsafe extern "C" fn js_events_on( } if let Some(signal) = signal { if let Some(signal_ptr) = object_ptr_from_value(signal) { - let abort_listener = - perry_ffi::alloc_closure(events_on_abort_listener as perry_ffi::JsBody0, 5); + let abort_listener = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_on_abort_listener, 0), + 5, + ); js_closure_set_capture_ptr(abort_listener, 0, handle); js_closure_set_capture_ptr(abort_listener, 1, listener_root.get()); js_closure_set_capture_ptr(abort_listener, 2, signal_ptr as i64); @@ -195,8 +200,10 @@ pub unsafe extern "C" fn js_events_add_abort_listener(signal: f64, listener: f64 abort_event_value(), nanbox_pointer_bits(callback_ptr), ); - let dispose_closure = - perry_ffi::alloc_closure(events_abort_listener_dispose as perry_ffi::JsBody0, 2); + let dispose_closure = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(events_abort_listener_dispose, 0), + 2, + ); let dispose_closure_root = root_scope.root_addr(dispose_closure as i64); let signal_ptr = object_ptr_from_value(signal_root.get()) .expect("validated AbortSignal remained a rooted object"); diff --git a/crates/perry-ext-events/src/tests.rs b/crates/perry-ext-events/src/tests.rs index db53a00c2d..37aa4cdb14 100644 --- a/crates/perry-ext-events/src/tests.rs +++ b/crates/perry-ext-events/src/tests.rs @@ -63,7 +63,7 @@ extern "C" fn noop_listener(_c: *const RawClosureHeader, _this: perry_ffi::JsThi } fn fake_listener() -> i64 { - let closure = perry_ffi::alloc_closure(noop_listener as perry_ffi::JsBody0, 0); + let closure = perry_ffi::alloc_closure(perry_ffi::js_function_info!(noop_listener, 0), 0); nanbox_pointer_bits(closure as i64).to_bits() as i64 } diff --git a/crates/perry-ext-http/src/agent.rs b/crates/perry-ext-http/src/agent.rs index 3370516358..b4a83f5758 100644 --- a/crates/perry-ext-http/src/agent.rs +++ b/crates/perry-ext-http/src/agent.rs @@ -364,8 +364,10 @@ unsafe fn install_connection_abort_signal(options: f64, socket: Handle) { perry_ext_net::js_ext_net_socket_emit_abort_error(socket); return; } - let listener = - perry_ffi::alloc_closure(agent_connection_abort_listener as perry_ffi::JsBody0, 2); + let listener = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(agent_connection_abort_listener, 0), + 2, + ); if listener.is_null() { return; } diff --git a/crates/perry-ext-http/src/client_abort.rs b/crates/perry-ext-http/src/client_abort.rs index a67a5a16b7..855e1ad305 100644 --- a/crates/perry-ext-http/src/client_abort.rs +++ b/crates/perry-ext-http/src/client_abort.rs @@ -40,7 +40,8 @@ pub(crate) unsafe fn attach_request_signal(request_handle: Handle, options: f64) return; } - let listener = perry_ffi::alloc_closure(request_signal_listener as perry_ffi::JsBody0, 1); + let listener = + perry_ffi::alloc_closure(perry_ffi::js_function_info!(request_signal_listener, 0), 1); if listener.is_null() { return; } diff --git a/crates/perry-ext-http/src/client_request_surface.rs b/crates/perry-ext-http/src/client_request_surface.rs index 3d39699fbd..a8a8895348 100644 --- a/crates/perry-ext-http/src/client_request_surface.rs +++ b/crates/perry-ext-http/src/client_request_surface.rs @@ -26,8 +26,6 @@ extern "C" { fn js_object_set_field_by_name(object: *mut ObjectHeader, key: *const StringHeader, value: f64); } -static CLIENT_ONCE_WRAPPER_REGISTERED: Once = Once::new(); - pub(crate) fn create_client_once_wrapper( handle: Handle, event: &str, @@ -37,15 +35,12 @@ pub(crate) fn create_client_once_wrapper( if callback == 0 { return 0; } - CLIENT_ONCE_WRAPPER_REGISTERED.call_once(|| { - perry_ffi::register_closure_rest(client_once_wrapper as perry_ffi::JsBody1, 0); - }); let scope = perry_ffi::TransientRootScope::enter(); let callback = scope.root_addr(callback); let event = scope.root_nanbox(f64::from_bits( JsValue::from_string_ptr(alloc_string(event).as_raw()).bits(), )); - let wrapper = perry_ffi::alloc_closure(client_once_wrapper as perry_ffi::JsBody1, 5); + let wrapper = perry_ffi::alloc_closure(&CLIENT_ONCE_WRAPPER_INFO, 5); let wrapper = scope.root_addr(wrapper as i64); let wrapper_ptr = wrapper.get() as *mut RawClosureHeader; unsafe { @@ -878,3 +873,6 @@ pub unsafe extern "C" fn js_ext_http_client_request_dispatch_method( }; dispatch_method(handle, &method, args).unwrap_or_else(undefined_value) } + +static CLIENT_ONCE_WRAPPER_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(client_once_wrapper as perry_ffi::JsBody1).with_rest(0); diff --git a/crates/perry-ext-http/src/lib.rs b/crates/perry-ext-http/src/lib.rs index 8f5f06e6ee..5dfd406704 100644 --- a/crates/perry-ext-http/src/lib.rs +++ b/crates/perry-ext-http/src/lib.rs @@ -919,15 +919,10 @@ unsafe fn invoke_create_socket( } let scope = perry_ffi::TransientRootScope::enter(); let cs = scope.root_addr(cs); - // Register the continuation's arity as 2 so a 1-arg `cb(err)` pads the - // socket slot with `undefined` (via the runtime's arity dispatch) instead - // of reading an uninitialized register for the second parameter. - static REGISTER_ARITY: Once = Once::new(); - REGISTER_ARITY.call_once(|| { - perry_ffi::register_closure_arity(http_create_socket_cb as perry_ffi::JsBody2, 2); - }); - - let cb = perry_ffi::alloc_closure(http_create_socket_cb as perry_ffi::JsBody2, 1); + // The continuation declares 2 parameters (its info), so a 1-arg + // `cb(err)` pads the socket slot with `undefined` instead of reading an + // uninitialized register for the second parameter. + let cb = perry_ffi::alloc_closure(&HTTP_CREATE_SOCKET_CB_INFO, 1); if cb.is_null() { return; } @@ -1821,3 +1816,6 @@ fn _force_link() -> Option<*mut ArrayHeader> { // Retain server exports through release LTO/staticlib emission. mod force_link; + +static HTTP_CREATE_SOCKET_CB_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(http_create_socket_cb as perry_ffi::JsBody2).with_declared(2); diff --git a/crates/perry-ext-http/src/server/server/upgrade_tests.rs b/crates/perry-ext-http/src/server/server/upgrade_tests.rs index 5275beeefd..18460a3f54 100644 --- a/crates/perry-ext-http/src/server/server/upgrade_tests.rs +++ b/crates/perry-ext-http/src/server/server/upgrade_tests.rs @@ -51,8 +51,7 @@ fn queue(server_handle: i64, request_handle: i64, raw_socket_id: i64) { fn delivery(https: bool) { let scope = perry_ffi::TransientRootScope::enter(); - perry_ffi::register_closure_arity(observe as perry_ffi::JsBody3, 3); - let callback = scope.root_addr(alloc_closure(observe as perry_ffi::JsBody3, 3) as i64); + let callback = scope.root_addr(alloc_closure(&OBSERVE_INFO, 3) as i64); unsafe { set_closure_capture_f64(callback.get() as *mut _, 0, 0.0); } @@ -125,3 +124,6 @@ fn deleted_server_releases_unclaimed_raw_upgrade() { ); } } + +static OBSERVE_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(observe as perry_ffi::JsBody3).with_declared(3); diff --git a/crates/perry-ext-http/src/server/types.rs b/crates/perry-ext-http/src/server/types.rs index 4831094cad..d2fe0e10a8 100644 --- a/crates/perry-ext-http/src/server/types.rs +++ b/crates/perry-ext-http/src/server/types.rs @@ -425,8 +425,8 @@ mod tests { fn listen_borrowed_values_preserve_port_host_backlog_and_callback() { let scope = perry_ffi::TransientRootScope::enter(); let callback = scope.root_nanbox(f64::from_bits( - JsValue::from_object_ptr(perry_runtime::closure::js_closure_alloc( - listen_test_callback as *const u8, + JsValue::from_object_ptr(perry_ffi::alloc_closure( + perry_ffi::js_function_info!(listen_test_callback, 0), 0, )) .bits(), diff --git a/crates/perry-ext-net/src/pipe.rs b/crates/perry-ext-net/src/pipe.rs index 1748c7852e..3cc2ceb4a8 100644 --- a/crates/perry-ext-net/src/pipe.rs +++ b/crates/perry-ext-net/src/pipe.rs @@ -35,8 +35,7 @@ use std::collections::HashMap; use std::sync::{Mutex, OnceLock}; use perry_ffi::{ - alloc_closure, closure_capture_f64, register_closure_arity, set_closure_capture_f64, - GcRootVisitor, RawClosureHeader, + alloc_closure, closure_capture_f64, set_closure_capture_f64, GcRootVisitor, RawClosureHeader, }; use crate::statics; @@ -146,15 +145,6 @@ extern "C" fn pipe_end_forward(closure: *const RawClosureHeader, _this: perry_ff f64::from_bits(TAG_UNDEFINED_BITS) } -static ARITY_REGISTERED: std::sync::Once = std::sync::Once::new(); - -fn ensure_pipe_closure_arities_registered() { - ARITY_REGISTERED.call_once(|| { - register_closure_arity(pipe_data_forward as perry_ffi::JsBody1, 1); - register_closure_arity(pipe_end_forward as perry_ffi::JsBody0, 0); - }); -} - /// One socket -> destination pipe route, tracked so `unpipe` can remove /// exactly the listener closures a matching `pipe()` call installed. /// @@ -220,7 +210,6 @@ pub(crate) fn socket_pipe(handle: i64, dest: f64, options: f64) -> f64 { return f64::from_bits(TAG_UNDEFINED_BITS); } crate::ensure_gc_scanner_registered(); - ensure_pipe_closure_arities_registered(); let end_on_finish = unsafe { if is_nullish(options) { @@ -235,8 +224,8 @@ pub(crate) fn socket_pipe(handle: i64, dest: f64, options: f64) -> f64 { } }; - let data_closure = alloc_closure(pipe_data_forward as perry_ffi::JsBody1, 1); - let end_closure = alloc_closure(pipe_end_forward as perry_ffi::JsBody0, 2); + let data_closure = alloc_closure(&PIPE_DATA_FORWARD_INFO, 1); + let end_closure = alloc_closure(&PIPE_END_FORWARD_INFO, 2); if data_closure.is_null() || end_closure.is_null() { return f64::from_bits(TAG_UNDEFINED_BITS); } @@ -348,3 +337,9 @@ pub unsafe extern "C" fn js_net_socket_unpipe(handle: i64, dest: f64) -> i64 { socket_unpipe(handle, dest); handle } + +static PIPE_DATA_FORWARD_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(pipe_data_forward as perry_ffi::JsBody1).with_declared(1); + +static PIPE_END_FORWARD_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(pipe_end_forward as perry_ffi::JsBody0).with_declared(0); diff --git a/crates/perry-ext-net/src/socket_events.rs b/crates/perry-ext-net/src/socket_events.rs index aea69f21f5..492a534329 100644 --- a/crates/perry-ext-net/src/socket_events.rs +++ b/crates/perry-ext-net/src/socket_events.rs @@ -531,9 +531,10 @@ mod listener_this_tests { } fn recording_listener() -> i64 { - let body = record_this as perry_ffi::JsBody0; - perry_ffi::register_closure_arity(body, 0); - let closure = perry_ffi::alloc_closure(body, 1); + let closure = perry_ffi::alloc_closure( + perry_ffi::js_function_info!(record_this, 0; with_declared(0)), + 1, + ); unsafe { perry_ffi::set_closure_capture_f64(closure, 0, NOT_CALLED) }; closure as i64 } diff --git a/crates/perry-ext-typescript/src/bun.rs b/crates/perry-ext-typescript/src/bun.rs index 6017de4459..1cefdcb4cd 100644 --- a/crates/perry-ext-typescript/src/bun.rs +++ b/crates/perry-ext-typescript/src/bun.rs @@ -16,9 +16,9 @@ use perry_ffi::{ alloc_closure, alloc_null_proto_object, alloc_string, closure_capture_f64, drop_handle, gc_register_mutable_root_scanner_named, get_handle, iter_handles_of_mut, js_array_alloc, js_array_get, js_array_length, js_array_push, json_stringify, object_field_by_name, - read_string, register_closure_arity, register_handle, set_closure_capture_f64, with_handle, - with_handle_mut, ArrayHeader, GcRootVisitor, Handle, JsClosure, JsPromise, JsString, JsValue, - Promise, RawClosureHeader, StringHeader, TransientRootScope, + read_string, register_handle, set_closure_capture_f64, with_handle, with_handle_mut, + ArrayHeader, GcRootVisitor, Handle, JsClosure, JsPromise, JsString, JsValue, Promise, + RawClosureHeader, StringHeader, TransientRootScope, }; use serde::{Deserialize, Serialize}; use swc_bundler::{ @@ -684,7 +684,6 @@ struct BuildHookSession { } static PLUGIN_GC_REGISTERED: Once = Once::new(); -static NATIVE_CLOSURE_ARITIES_REGISTERED: Once = Once::new(); fn ensure_build_runtime_registered() { PLUGIN_GC_REGISTERED.call_once(|| { @@ -693,11 +692,6 @@ fn ensure_build_runtime_registered() { scan_build_hook_roots, ); }); - NATIVE_CLOSURE_ARITIES_REGISTERED.call_once(|| { - register_closure_arity(bun_on_resolve as perry_ffi::JsBody2, 2); - register_closure_arity(bun_on_load as perry_ffi::JsBody2, 2); - register_closure_arity(bun_build_output_text as perry_ffi::JsBody0, 0); - }); } fn scan_build_hook_roots(visitor: &mut GcRootVisitor<'_>) { @@ -797,8 +791,8 @@ extern "C" fn bun_on_load( register_plugin_hook(closure, options, callback, false) } -fn native_closure_value(function: perry_ffi::JsBody2, session: Handle) -> JsValue { - let closure = alloc_closure(function, 1); +fn native_closure_value(info: &'static perry_ffi::JsFunctionInfo, session: Handle) -> JsValue { + let closure = alloc_closure(info, 1); if closure.is_null() { return JsValue::UNDEFINED; } @@ -832,10 +826,18 @@ fn configure_plugins(options: f64) -> Handle { return session; } let on_resolve = scope.root_nanbox(f64::from_bits( - native_closure_value(bun_on_resolve as perry_ffi::JsBody2, session).bits(), + native_closure_value( + perry_ffi::js_function_info!(bun_on_resolve, 2; with_declared(2)), + session, + ) + .bits(), )); let on_load = scope.root_nanbox(f64::from_bits( - native_closure_value(bun_on_load as perry_ffi::JsBody2, session).bits(), + native_closure_value( + perry_ffi::js_function_info!(bun_on_load, 2; with_declared(2)), + session, + ) + .bits(), )); let builder = scope.root_nanbox(f64::from_bits( alloc_null_proto_object(&[ @@ -1312,7 +1314,7 @@ fn build_output_value(output: BuildOutput) -> JsValue { let contents = scope.root_nanbox(f64::from_bits( JsValue::from_string_ptr(alloc_string(&output.contents).as_raw()).bits(), )); - let text = perry_ffi::alloc_closure(bun_build_output_text as perry_ffi::JsBody0, 1); + let text = perry_ffi::alloc_closure(&BUN_BUILD_OUTPUT_TEXT_INFO, 1); unsafe { set_closure_capture_f64(text, 0, contents.get()) }; let text = scope.root_nanbox(f64::from_bits(JsValue::from_object_ptr(text).bits())); let path = JsValue::from_string_ptr(alloc_string(&output.path).as_raw()); @@ -1449,3 +1451,6 @@ mod tests { assert!(BunLoader::parse("css").is_err()); } } + +static BUN_BUILD_OUTPUT_TEXT_INFO: perry_ffi::JsFunctionInfo = + perry_ffi::JsFunctionInfo::of(bun_build_output_text as perry_ffi::JsBody0).with_declared(0); diff --git a/crates/perry-ext-ws/src/tests.rs b/crates/perry-ext-ws/src/tests.rs index 36ed5750d3..ff8125e26d 100644 --- a/crates/perry-ext-ws/src/tests.rs +++ b/crates/perry-ext-ws/src/tests.rs @@ -222,7 +222,7 @@ extern "C" fn count_first_and_register( ) -> f64 { FIRST_CALLS.fetch_add(1, Ordering::SeqCst); let server = perry_runtime::closure::js_closure_get_capture_f64(c, 0) as Handle; - on_listening(server, closure_of(count_second)); + on_listening(server, closure_of(perry_runtime::fn_info!(count_second, 0))); f64::from_bits(JsValue::UNDEFINED.bits()) } @@ -239,13 +239,8 @@ fn on_listening(server: Handle, listener: i64) { unsafe { js_ws_on(server, event.as_raw(), listener.get()) }; } -fn closure_of( - f: extern "C" fn( - *const perry_runtime::closure::ClosureHeader, - perry_runtime::closure::JsThis, - ) -> f64, -) -> i64 { - perry_runtime::closure::js_closure_alloc(f as *const u8, 0) as i64 +fn closure_of(info: *const perry_runtime::closure::JsFunctionInfo) -> i64 { + perry_runtime::closure::js_closure_alloc(info, 0) as i64 } /// A server that reads as bound, with nothing of its own queued. @@ -282,7 +277,7 @@ fn listening_reaches_each_listener_once() { let server = listening_server(); push_ws_event(PendingWsEvent::Listening(server)); - on_listening(server, closure_of(count_first)); + on_listening(server, closure_of(perry_runtime::fn_info!(count_first, 0))); assert_eq!( listening_queued_for(server), 1, @@ -291,7 +286,7 @@ fn listening_reaches_each_listener_once() { js_ws_process_pending(); assert_eq!(FIRST_CALLS.load(Ordering::SeqCst), 1); - on_listening(server, closure_of(count_second)); + on_listening(server, closure_of(perry_runtime::fn_info!(count_second, 0))); assert_eq!( listening_queued_for(server), 1, @@ -317,7 +312,10 @@ fn a_listener_registered_during_its_event_gets_no_replay() { FIRST_CALLS.store(0, Ordering::SeqCst); SECOND_CALLS.store(0, Ordering::SeqCst); let server = listening_server(); - let first = perry_runtime::closure::js_closure_alloc(count_first_and_register as *const u8, 1); + let first = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(count_first_and_register, 0), + 1, + ); perry_runtime::closure::js_closure_set_capture_f64(first, 0, server as f64); push_ws_event(PendingWsEvent::Listening(server)); diff --git a/crates/perry-ext-zlib/src/lib.rs b/crates/perry-ext-zlib/src/lib.rs index dc3a901b37..9939980366 100644 --- a/crates/perry-ext-zlib/src/lib.rs +++ b/crates/perry-ext-zlib/src/lib.rs @@ -480,8 +480,7 @@ pub unsafe extern "C" fn js_ext_zlib_native_dispatch( mod tests { use super::*; use perry_ffi::{ - alloc_closure, alloc_string, read_buffer_bytes, register_closure_arity, JsString, JsValue, - RawClosureHeader, + alloc_closure, alloc_string, read_buffer_bytes, JsString, JsValue, RawClosureHeader, }; use std::cell::Cell; @@ -570,8 +569,10 @@ mod tests { DISPATCH_CALLBACK_FIRED.with(|fired| fired.set(false)); DISPATCH_CALLBACK_OK.with(|ok| ok.set(false)); - register_closure_arity(record_dispatch_callback as perry_ffi::JsBody2, 2); - let callback = alloc_closure(record_dispatch_callback as perry_ffi::JsBody2, 0); + let callback = alloc_closure( + perry_ffi::js_function_info!(record_dispatch_callback, 2; with_declared(2)), + 0, + ); assert!(!callback.is_null()); let input = alloc_buffer(b"captured zlib export"); @@ -614,9 +615,12 @@ mod tests { } DISPATCH_CALLBACK_FIRED.with(|fired| fired.set(false)); let scope = perry_ffi::TransientRootScope::enter(); - register_closure_arity(callback as perry_ffi::JsBody2, 2); let callback = scope.root_nanbox(f64::from_bits( - JsValue::from_object_ptr(alloc_closure(callback as perry_ffi::JsBody2, 0)).bits(), + JsValue::from_object_ptr(alloc_closure( + perry_ffi::js_function_info!(callback, 2; with_declared(2)), + 0, + )) + .bits(), )); let data = scope.root_nanbox(f64::from_bits( JsValue::from_object_ptr(alloc_buffer(&vec![b'A'; 4096])).bits(), diff --git a/crates/perry-ffi/src/closure.rs b/crates/perry-ffi/src/closure.rs index dd5a6ac40c..f35a0d3df5 100644 --- a/crates/perry-ffi/src/closure.rs +++ b/crates/perry-ffi/src/closure.rs @@ -48,13 +48,13 @@ pub use perry_abi::JsThis; /// The JS body types (`perry_abi::js_body_fn_ty!`) over perry-ffi's closure /// header: `JsBody1` is /// `unsafe extern "C" fn(*const RawClosureHeader, JsThis, f64) -> f64`. -/// [`alloc_closure`] and [`register_closure_arity`] take one of these, so a -/// body with any other signature — including a bare `*const u8` — does not -/// compile: +/// A body's [`JsFunctionInfo`] is built from one of these +/// ([`js_function_info!`](crate::js_function_info)), so a body with any +/// other signature — including a bare `*const u8` — does not compile: /// /// ```ignore /// extern "C" fn body(c: *const RawClosureHeader, this: JsThis, a0: f64) -> f64 { .. } -/// let closure = perry_ffi::alloc_closure(body as perry_ffi::JsBody1, 0); +/// let closure = perry_ffi::alloc_closure(perry_ffi::js_function_info!(body, 1), 0); /// ``` /// /// A JS body declaring 0 JS parameters. @@ -125,59 +125,95 @@ extern "C" { args: *const f64, args_len: usize, ) -> f64; - fn js_closure_alloc(func_ptr: *const u8, capture_count: u32) -> *mut ClosureHeader; - fn js_register_closure_arity(func_ptr: *const u8, arity: u32); - fn js_register_closure_rest(func_ptr: *const u8, fixed_arity: u32); + fn js_closure_alloc(info: *const JsFunctionInfo, capture_count: u32) -> *mut ClosureHeader; fn js_closure_get_capture_f64(closure: *const ClosureHeader, index: u32) -> f64; fn js_closure_set_capture_f64(closure: *mut ClosureHeader, index: u32, value: f64); } -/// Register the arity the runtime uses when dispatching a native closure -/// body (a rest-bundling body registers its fixed arity). -pub fn register_closure_arity>(func: F, arity: u32) { - unsafe { js_register_closure_arity(func.code(), arity) } -} +/// Everything the runtime knows about a native body, in one static record +/// the function object points to (perry-abi): its code address, parameter +/// count, and facts such as a rest parameter or `.length`. Build one with +/// [`js_function_info!`](crate::js_function_info) — the only safe +/// constructor takes the body's typed pointer. +pub use perry_abi::{JsFunctionInfo, FN_ARROW, FN_BUILTIN, FN_NON_CONSTRUCTOR, FN_STRICT}; -/// Register `func` as a rest-parameter body: the runtime bundles every JS -/// argument from index `fixed_arity` on into one array, passed as the body's -/// last parameter (so a body with `fixed_arity` fixed parameters is a -/// `JsBody{fixed_arity + 1}`). -pub fn register_closure_rest>(func: F, fixed_arity: u32) { - debug_assert_eq!( - F::ARITY, - fixed_arity + 1, - "a rest body takes its fixed parameters and the rest array" - ); - unsafe { js_register_closure_rest(func.code(), fixed_arity) } +/// A `&'static JsFunctionInfo` for the native body `body` declaring `n` JS +/// parameters — checked: the cast to [`JsBody0`]..[`JsBody16`] fails to +/// compile for any other signature. Facts beyond the parameter count follow +/// as [`JsFunctionInfo`] builder calls: +/// +/// ```ignore +/// alloc_closure(js_function_info!(listener, 2), 1); +/// alloc_closure(js_function_info!(variadic, 1; with_rest(0)), 0); +/// alloc_closure(js_function_info!(method, 1; with_length(0)), 0); +/// ``` +/// +/// Each expansion is its own `static`: define one per body where the same +/// body is allocated from several places. +#[macro_export] +macro_rules! js_function_info { + (@ty 0) => { $crate::JsBody0 }; + (@ty 1) => { $crate::JsBody1 }; + (@ty 2) => { $crate::JsBody2 }; + (@ty 3) => { $crate::JsBody3 }; + (@ty 4) => { $crate::JsBody4 }; + (@ty 5) => { $crate::JsBody5 }; + (@ty 6) => { $crate::JsBody6 }; + (@ty 7) => { $crate::JsBody7 }; + (@ty 8) => { $crate::JsBody8 }; + (@ty 9) => { $crate::JsBody9 }; + (@ty 10) => { $crate::JsBody10 }; + (@ty 11) => { $crate::JsBody11 }; + (@ty 12) => { $crate::JsBody12 }; + (@ty 13) => { $crate::JsBody13 }; + (@ty 14) => { $crate::JsBody14 }; + (@ty 15) => { $crate::JsBody15 }; + (@ty 16) => { $crate::JsBody16 }; + ($body:path, $n:tt $(; $($m:ident($($a:expr),*)),* $(,)?)?) => {{ + static INFO: $crate::JsFunctionInfo = + $crate::JsFunctionInfo::of($body as $crate::js_function_info!(@ty $n)) + $($(.$m($($a),*))*)?; + &INFO + }}; } -/// Allocate a native closure running `func` with `capture_count` f64 -/// capture slots. +/// Allocate a native closure running the body `info` describes, with +/// `capture_count` f64 capture slots. /// -/// `func` must be a JS body pointer type; anything else does not compile. -/// A body without the receiver: +/// `info` comes from [`js_function_info!`](crate::js_function_info), which +/// takes the body's typed pointer; a body of any other signature does not +/// compile. A body without the receiver: /// /// ```compile_fail,E0605 /// // NOT-A-JS-BODY: the example of a body missing its receiver. /// extern "C" fn no_this(_: *const perry_ffi::RawClosureHeader, a: f64) -> f64 { a } -/// let _ = perry_ffi::alloc_closure(no_this as perry_ffi::JsBody1, 0); +/// let _ = perry_ffi::alloc_closure(perry_ffi::js_function_info!(no_this, 1), 0); /// ``` /// /// an erased pointer: /// /// ```compile_fail,E0277 /// extern "C" fn body(_: *const perry_ffi::RawClosureHeader, _: perry_ffi::JsThis) -> f64 { 0.0 } -/// let _ = perry_ffi::alloc_closure(body as *const u8, 0); +/// static INFO: perry_ffi::JsFunctionInfo = perry_ffi::JsFunctionInfo::of(body as *const u8); /// ``` /// /// a JS argument of the wrong type: /// /// ```compile_fail,E0605 /// extern "C" fn body(_: *const perry_ffi::RawClosureHeader, _: perry_ffi::JsThis, a: i64) -> f64 { 0.0 } -/// let _ = perry_ffi::alloc_closure(body as perry_ffi::JsBody1, 0); +/// let _ = perry_ffi::alloc_closure(perry_ffi::js_function_info!(body, 1), 0); +/// ``` +/// +/// or an info assembled by hand: +/// +/// ```compile_fail +/// let _ = perry_ffi::JsFunctionInfo { code: std::ptr::null(), params: 0, rest_fixed: 0, +/// flags: 0, length: 0, trusted_captures: 0, trusted_code: std::ptr::null(), +/// trusted_boxed_mask: 0, versioned_code: std::ptr::null(), versioned_captures: 0, +/// reserved: 0, versioned_boxed_mask: 0 }; /// ``` -pub fn alloc_closure>(func: F, capture_count: u32) -> *mut ClosureHeader { - unsafe { js_closure_alloc(func.code(), capture_count) } +pub fn alloc_closure(info: &'static JsFunctionInfo, capture_count: u32) -> *mut ClosureHeader { + unsafe { js_closure_alloc(info, capture_count) } } /// Read an f64 capture slot from a native closure. @@ -304,8 +340,7 @@ mod tests { unsafe extern "C" fn callback(_: *const ClosureHeader, _this: crate::JsThis) -> f64 { 0.0 } - register_closure_arity(callback as JsBody0, 0); - let closure = alloc_closure(callback as JsBody0, 1); + let closure = alloc_closure(crate::js_function_info!(callback, 0), 1); unsafe { set_closure_capture_f64(closure, 0, 42.0) }; assert_eq!(unsafe { closure_capture_f64(closure, 0) }, 42.0); } diff --git a/crates/perry-ffi/src/lib.rs b/crates/perry-ffi/src/lib.rs index 3048222539..ffc14c885a 100644 --- a/crates/perry-ffi/src/lib.rs +++ b/crates/perry-ffi/src/lib.rs @@ -106,10 +106,10 @@ pub use jsvalue::{ mod closure; mod transient_roots; pub use closure::{ - alloc_closure, call_value, closure_capture_f64, register_closure_arity, register_closure_rest, - set_closure_capture_f64, JsBody, JsBody0, JsBody1, JsBody10, JsBody11, JsBody12, JsBody13, - JsBody14, JsBody15, JsBody16, JsBody2, JsBody3, JsBody4, JsBody5, JsBody6, JsBody7, JsBody8, - JsBody9, JsClosure, JsThis, RawClosureHeader, + alloc_closure, call_value, closure_capture_f64, set_closure_capture_f64, JsBody, JsBody0, + JsBody1, JsBody10, JsBody11, JsBody12, JsBody13, JsBody14, JsBody15, JsBody16, JsBody2, + JsBody3, JsBody4, JsBody5, JsBody6, JsBody7, JsBody8, JsBody9, JsClosure, JsFunctionInfo, + JsThis, RawClosureHeader, FN_ARROW, FN_BUILTIN, FN_NON_CONSTRUCTOR, FN_STRICT, }; pub use transient_roots::{TransientRootScope, TransientRootedAddr, TransientRootedNanbox}; diff --git a/crates/perry-ffi/src/types.rs b/crates/perry-ffi/src/types.rs index 2573abeea8..57c793343a 100644 --- a/crates/perry-ffi/src/types.rs +++ b/crates/perry-ffi/src/types.rs @@ -135,8 +135,9 @@ pub struct ClosureHeader { pub capture_count: u32, /// The function object's ShapeId. pub shape_id: u32, - /// Pointer to the compiled closure body. - pub func_ptr: *const u8, + /// The body's static `JsFunctionInfo` (perry-abi): its code address and + /// every fact the runtime needs about the body. + pub info: *const perry_abi::JsFunctionInfo, /// The function object's own-property record (runtime-internal). pub props: *mut u8, } @@ -309,8 +310,8 @@ mod layout_tests { fn closure_header_matches_runtime() { assert_layout!(ClosureHeader, perry_runtime::ClosureHeader); assert_eq!( - offset_of!(ClosureHeader, func_ptr), - offset_of!(perry_runtime::ClosureHeader, func_ptr) + offset_of!(ClosureHeader, info), + offset_of!(perry_runtime::ClosureHeader, info) ); assert_eq!( offset_of!(ClosureHeader, capture_count), diff --git a/crates/perry-runtime/build.rs b/crates/perry-runtime/build.rs index 2e39107a8b..74fe51afce 100644 --- a/crates/perry-runtime/build.rs +++ b/crates/perry-runtime/build.rs @@ -581,29 +581,6 @@ fn main() { .flag_if_supported("-fasynchronous-unwind-tables") .compile("perry_sjlj"); } - // WASI (#11379): the closure-body signature probe; `ref.test` needs the - // GC proposal, enabled for this one file. Only a clang that knows `-mgc` - // (wasi-sdk's, see scripts/wasi_toolchain.sh) can build it. With any other - // C compiler — a distro clang under `cargo check`, say — it is skipped - // with a warning, and a `--target wasi` link then fails loudly on the - // undefined `perry_wasi_closure_params` instead of misbehaving. - println!("cargo:rerun-if-changed=src/ffi/perry_wasi_sig.c"); - println!("cargo:rerun-if-env-changed=CC_wasm32_wasip2"); - if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("wasi") { - let mut sig = cc::Build::new(); - if sig.is_flag_supported("-mgc").unwrap_or(false) { - sig.file("src/ffi/perry_wasi_sig.c") - .flag("-mgc") - .opt_level(2) - .compile("perry_wasi_sig"); - } else { - println!( - "cargo:warning=perry_wasi_sig.c not built: the C compiler does not \ - support -mgc; set CC_wasm32_wasip2 to wasi-sdk's clang \ - (scripts/wasi_toolchain.sh) to build a linkable WASI runtime" - ); - } - } println!( "cargo:rustc-env=PERRY_RUNTIME_TARGET={}", std::env::var("TARGET").expect("TARGET not set by Cargo") diff --git a/crates/perry-runtime/src/array/collection_tag_tests.rs b/crates/perry-runtime/src/array/collection_tag_tests.rs index 33fb2e6ba1..0f3519a565 100644 --- a/crates/perry-runtime/src/array/collection_tag_tests.rs +++ b/crates/perry-runtime/src/array/collection_tag_tests.rs @@ -375,7 +375,7 @@ fn take_visits() -> Vec { fn recording_callback() -> *const ClosureHeader { take_visits(); - js_closure_alloc(record_first_arg as *const u8, 0) + js_closure_alloc(crate::fn_info!(record_first_arg, 3), 0) } #[test] diff --git a/crates/perry-runtime/src/array/generic/class_mutator_tests.rs b/crates/perry-runtime/src/array/generic/class_mutator_tests.rs index 9fd08e34ce..03339860d5 100644 --- a/crates/perry-runtime/src/array/generic/class_mutator_tests.rs +++ b/crates/perry-runtime/src/array/generic/class_mutator_tests.rs @@ -42,6 +42,7 @@ fn borrowed_class_mutators_read_and_write_length_once() { b"length".as_ptr(), 6, length_setter as *const () as i64, + 1, ); } let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index a5de77ff62..a55c6bb442 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -990,15 +990,15 @@ pub extern "C" fn js_array_some(arr: *const ArrayHeader, callback: *const Closur /// `Array.prototype.some` for a compiler-proved captureless inline arrow. /// /// The callback literal is consumed only by `some`, has no observable -/// function identity, and cannot read a closure environment. Passing its code -/// pointer directly avoids the singleton-closure TLS lookup and lets the loop +/// function identity, and cannot read a closure environment. Passing its +/// body's info directly avoids the singleton-closure TLS lookup and lets the loop /// call the body without rebuilding closure dispatch state. Non-Array /// receivers retain the generic path so Buffer, TypedArray, and array-like /// semantics remain centralized in [`js_array_some`]. #[no_mangle] pub extern "C" fn js_array_some_captureless( original_arr: *const ArrayHeader, - callback_func: *const u8, + callback_info: *const crate::closure::JsFunctionInfo, ) -> f64 { const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; @@ -1012,11 +1012,12 @@ pub extern "C" fn js_array_some_captureless( || super::header::receiver_may_be_registered_exotic(arr) && crate::buffer::is_registered_buffer(arr as usize) { - let callback = crate::closure::js_closure_alloc_singleton(callback_func); + let callback = crate::closure::js_closure_alloc_singleton(callback_info); return js_array_some(original_arr, callback); } - // `callback_func` is a captureless compiled closure body. + // `callback_info` is a captureless compiled closure body's static info. + let callback_func = unsafe { (*callback_info).code }; let callback: crate::closure::body_call::js_body_fn_ty!(value, index, array) = unsafe { crate::closure::body_call::js_body_fn!(callback_func; value, index, array) }; unsafe { diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index 095dce8c81..a740b5c131 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -391,10 +391,11 @@ fn async_from_sync_continue(iter: f64, step_result: f64, close_on_rejection: boo let outer = crate::promise::js_promise_new(); let outer_h = scope.root_raw_mut_ptr(outer); - let on_fulfilled = crate::closure::js_closure_alloc(async_from_sync_fulfilled as *const u8, 2); + let on_fulfilled = + crate::closure::js_closure_alloc(crate::fn_info!(async_from_sync_fulfilled, 1), 2); let on_fulfilled_h = scope.root_raw_mut_ptr(on_fulfilled); let on_rejected = - crate::closure::js_closure_alloc(async_from_sync_rejected_value as *const u8, 3); + crate::closure::js_closure_alloc(crate::fn_info!(async_from_sync_rejected_value, 1), 3); let on_rejected_h = scope.root_raw_mut_ptr(on_rejected); // All three allocations are done; re-read each through its handle before // wiring captures (no allocation happens between these stores). @@ -686,26 +687,10 @@ extern "C" fn async_from_sync_async_iterator( crate::closure::js_closure_get_capture_f64(closure, 0) } -fn register_async_from_sync_thunks_once() { - crate::perry_thread_local! { - static REGISTERED: std::cell::Cell = const { std::cell::Cell::new(false) }; - } - REGISTERED.with(|flag| { - if flag.get() { - return; - } - crate::closure::js_register_closure_rest(async_from_sync_next as *const u8, 0); - crate::closure::js_register_closure_rest(async_from_sync_return as *const u8, 0); - crate::closure::js_register_closure_rest(async_from_sync_throw as *const u8, 0); - crate::closure::js_register_closure_arity(async_from_sync_async_iterator as *const u8, 0); - flag.set(true); - }); -} - fn install_async_from_sync_method( obj: *mut crate::object::ObjectHeader, name: &[u8], - func: crate::closure::body_call::js_body_fn_ty!(a), + info: *const crate::closure::JsFunctionInfo, iter: f64, ) -> f64 { // `obj`, `iter` and the freshly-allocated closure are live young objects @@ -716,7 +701,7 @@ fn install_async_from_sync_method( let scope = crate::gc::RuntimeHandleScope::new(); let obj_h = scope.root_raw_mut_ptr(obj); let iter_h = scope.root_nanbox_f64(iter); - let closure = crate::closure::js_closure_alloc(func as *const u8, 1); + let closure = crate::closure::js_closure_alloc(info, 1); let closure_h = scope.root_raw_mut_ptr(closure); let key = crate::string::js_string_from_bytes_longlived(name.as_ptr(), name.len() as u32); crate::closure::js_closure_set_capture_f64( @@ -752,7 +737,8 @@ fn install_async_from_sync_next( let obj_h = scope.root_raw_mut_ptr(obj); let iter_h = scope.root_nanbox_f64(iter); let cached_next_h = scope.root_nanbox_f64(cached_next); - let closure = crate::closure::js_closure_alloc(async_from_sync_next as *const u8, 2); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(async_from_sync_next, 1; with_rest(0)), 2); let closure_h = scope.root_raw_mut_ptr(closure); let key = crate::string::js_string_from_bytes_longlived(b"next".as_ptr(), 4); crate::closure::js_closure_set_capture_f64( @@ -779,7 +765,6 @@ fn install_async_from_sync_next( } pub(crate) fn async_from_sync_wrap_iterator(iter: f64) -> f64 { - register_async_from_sync_thunks_once(); // The wrapper object and the sync iterator are live young values held // across a long series of allocations (three method installs plus the // async-iterator closure and the symbol-property store). Root them and @@ -802,17 +787,19 @@ pub(crate) fn async_from_sync_wrap_iterator(iter: f64) -> f64 { install_async_from_sync_method( obj_h.get_raw_mut_ptr::(), b"return", - async_from_sync_return, + crate::fn_info!(async_from_sync_return, 1; with_rest(0)), iter_h.get_nanbox_f64(), ); install_async_from_sync_method( obj_h.get_raw_mut_ptr::(), b"throw", - async_from_sync_throw, + crate::fn_info!(async_from_sync_throw, 1; with_rest(0)), iter_h.get_nanbox_f64(), ); - let async_iter = - crate::closure::js_closure_alloc(async_from_sync_async_iterator as *const u8, 1); + let async_iter = crate::closure::js_closure_alloc( + crate::fn_info!(async_from_sync_async_iterator, 0; with_declared(0)), + 1, + ); let async_iter_h = scope.root_raw_mut_ptr(async_iter); let wrapper = crate::value::js_nanbox_pointer( obj_h.get_raw_mut_ptr::() as i64, diff --git a/crates/perry-runtime/src/array/tests.rs b/crates/perry-runtime/src/array/tests.rs index 82258508bc..5eb135f997 100644 --- a/crates/perry-runtime/src/array/tests.rs +++ b/crates/perry-runtime/src/array/tests.rs @@ -172,7 +172,7 @@ fn captureless_some_calls_the_body_directly_and_short_circuits() { arr = js_array_push_f64(arr, 3.0); DIRECT_SOME_CALLS.store(0, Ordering::Relaxed); - let answer = js_array_some_captureless(arr, direct_some_is_two as *const u8); + let answer = js_array_some_captureless(arr, crate::fn_info!(direct_some_is_two, 3)); assert_eq!(answer.to_bits(), crate::value::TAG_TRUE); assert_eq!(DIRECT_SOME_CALLS.load(Ordering::Relaxed), 2); } @@ -1405,7 +1405,7 @@ fn test_numeric_array_layout_map_fast_path_downgrades_mapped_pointers() { arr = js_array_push_f64(arr, 4.0); assert_eq!(js_array_is_numeric_f64_layout(arr), 1); - let callback = crate::closure::js_closure_alloc(test_map_to_string as *const u8, 0); + let callback = crate::closure::js_closure_alloc(crate::fn_info!(test_map_to_string, 2), 0); let mapped = js_array_map(arr, callback); assert_eq!(js_array_length(mapped), 4); diff --git a/crates/perry-runtime/src/array/typed_array_receiver_tests.rs b/crates/perry-runtime/src/array/typed_array_receiver_tests.rs index 3ef32f96ae..31036387a1 100644 --- a/crates/perry-runtime/src/array/typed_array_receiver_tests.rs +++ b/crates/perry-runtime/src/array/typed_array_receiver_tests.rs @@ -250,7 +250,7 @@ extern "C" fn descending_cmp( } fn descending_comparator() -> *const crate::closure::ClosureHeader { - crate::closure::js_closure_alloc(descending_cmp as *const u8, 0) + crate::closure::js_closure_alloc(crate::fn_info!(descending_cmp, 2), 0) } #[test] @@ -842,7 +842,7 @@ fn is_true(v: f64) -> bool { v.to_bits() == crate::value::TAG_TRUE } -fn closure(func: *const u8) -> *const crate::closure::ClosureHeader { +fn closure(func: *const crate::closure::JsFunctionInfo) -> *const crate::closure::ClosureHeader { crate::closure::js_closure_alloc(func, 0) as *const crate::closure::ClosureHeader } @@ -867,7 +867,7 @@ fn subject() -> *mut ArrayHeader { fn js_array_map_maps_a_buffer_receivers_bytes() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let result = crate::array::js_array_map(buf, closure(cb_double as *const u8)); + let result = crate::array::js_array_map(buf, closure(crate::fn_info!(cb_double, 3))); assert_eq!( observed_values(), vec![3.0, 1.0, 2.0], @@ -887,7 +887,7 @@ fn js_array_map_discard_still_runs_the_callbacks_on_a_buffer() { let buf = subject(); // `map` whose result is unused: no allocation, but the callback must still // observe every real byte in order. - crate::array::js_array_map_discard(buf, closure(cb_double as *const u8)); + crate::array::js_array_map_discard(buf, closure(crate::fn_info!(cb_double, 3))); assert_eq!(observed_values(), vec![3.0, 1.0, 2.0]); assert_eq!(observed_indices(), vec![0.0, 1.0, 2.0]); } @@ -896,7 +896,8 @@ fn js_array_map_discard_still_runs_the_callbacks_on_a_buffer() { fn js_array_filter_filters_a_buffer_receivers_bytes() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let result = crate::array::js_array_filter(buf, closure(cb_is_three_or_two as *const u8)); + let result = + crate::array::js_array_filter(buf, closure(crate::fn_info!(cb_is_three_or_two, 3))); assert_eq!(observed_values(), vec![3.0, 1.0, 2.0]); assert_eq!( read_uint8_result(result), @@ -909,7 +910,7 @@ fn js_array_filter_filters_a_buffer_receivers_bytes() { fn js_array_find_finds_a_buffer_receivers_byte() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let found = crate::array::js_array_find(buf, closure(cb_is_one as *const u8)); + let found = crate::array::js_array_find(buf, closure(crate::fn_info!(cb_is_one, 3))); assert_eq!(observed_values(), vec![3.0, 1.0]); assert_eq!(found, 1.0, "node answers 1; `undefined` is #8137"); } @@ -918,7 +919,7 @@ fn js_array_find_finds_a_buffer_receivers_byte() { fn js_array_find_index_finds_a_buffer_receivers_index() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let index = crate::array::js_array_findIndex(buf, closure(cb_is_one as *const u8)); + let index = crate::array::js_array_findIndex(buf, closure(crate::fn_info!(cb_is_one, 3))); assert_eq!(observed_values(), vec![3.0, 1.0]); assert_eq!(index, 1, "node answers 1; `-1` is #8137"); } @@ -927,7 +928,7 @@ fn js_array_find_index_finds_a_buffer_receivers_index() { fn js_array_some_sees_a_buffer_receivers_bytes() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let answer = crate::array::js_array_some(buf, closure(cb_is_one as *const u8)); + let answer = crate::array::js_array_some(buf, closure(crate::fn_info!(cb_is_one, 3))); assert_eq!( observed_values(), vec![3.0, 1.0], @@ -941,7 +942,7 @@ fn js_array_some_sees_a_buffer_receivers_bytes() { fn captureless_some_keeps_the_buffer_receiver_fallback() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let answer = crate::array::js_array_some_captureless(buf, cb_is_one as *const u8); + let answer = crate::array::js_array_some_captureless(buf, crate::fn_info!(cb_is_one, 3)); assert_eq!( observed_values(), vec![3.0, 1.0], @@ -957,7 +958,8 @@ fn js_array_every_sees_a_buffer_receivers_bytes() { // `cb_is_a_source_byte`, NOT `x > 0`: the garbage reads are also `> 0`, so // a sign predicate answers `true` on the BROKEN path too (#8137's own // "vacuous probe to avoid"). - let answer = crate::array::js_array_every(buf, closure(cb_is_a_source_byte as *const u8)); + let answer = + crate::array::js_array_every(buf, closure(crate::fn_info!(cb_is_a_source_byte, 3))); assert_eq!(observed_values(), vec![3.0, 1.0, 2.0]); assert!(is_true(answer), "node answers true; `false` is #8137"); } @@ -966,7 +968,7 @@ fn js_array_every_sees_a_buffer_receivers_bytes() { fn js_array_for_each_visits_a_buffer_receivers_bytes() { let _serialized = crate::array::test_serialize(); let buf = subject(); - crate::array::js_array_forEach(buf, closure(cb_double as *const u8)); + crate::array::js_array_forEach(buf, closure(crate::fn_info!(cb_double, 3))); assert_eq!( observed_values(), vec![3.0, 1.0, 2.0], @@ -979,7 +981,7 @@ fn js_array_for_each_visits_a_buffer_receivers_bytes() { fn js_array_reduce_accumulates_a_buffer_receivers_bytes() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let sum = crate::array::js_array_reduce(buf, closure(cb_sum as *const u8), 1, 0.0); + let sum = crate::array::js_array_reduce(buf, closure(crate::fn_info!(cb_sum, 4)), 1, 0.0); assert_eq!(observed_values(), vec![3.0, 1.0, 2.0]); assert_eq!(sum, 6.0, "node answers 6; `9.12e-313` is #8137"); } @@ -993,7 +995,7 @@ fn js_array_reduce_without_a_seed_starts_at_the_first_byte() { // unconditionally would silently seed every reduce with 0.0 — the same // answer here, but the WRONG one for a non-additive callback, and it would // turn the empty-receiver TypeError into a silent `undefined`. - let sum = crate::array::js_array_reduce(buf, closure(cb_sum as *const u8), 0, 0.0); + let sum = crate::array::js_array_reduce(buf, closure(crate::fn_info!(cb_sum, 4)), 0, 0.0); assert_eq!( observed_values(), vec![1.0, 2.0], @@ -1006,7 +1008,7 @@ fn js_array_reduce_without_a_seed_starts_at_the_first_byte() { fn js_array_reduce_right_accumulates_a_buffer_receiver_in_reverse() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let sum = crate::array::js_array_reduce_right(buf, closure(cb_sum as *const u8), 1, 0.0); + let sum = crate::array::js_array_reduce_right(buf, closure(crate::fn_info!(cb_sum, 4)), 1, 0.0); assert_eq!( observed_indices(), vec![2.0, 1.0, 0.0], @@ -1024,7 +1026,7 @@ fn js_array_reduce_right_accumulates_a_buffer_receiver_in_reverse() { fn the_callbacks_third_argument_is_the_receiver_itself_not_a_copy() { let _serialized = crate::array::test_serialize(); let buf = subject(); - crate::array::js_array_forEach(buf, closure(cb_double as *const u8)); + crate::array::js_array_forEach(buf, closure(crate::fn_info!(cb_double, 3))); // Non-vacuity: the receiver-identity assertion below ALSO holds on the // broken path (the pre-fix helper passes `rooted.receiver()`, which is the @@ -1056,7 +1058,7 @@ fn the_callbacks_third_argument_is_the_receiver_itself_not_a_copy() { fn find_last_is_served_for_a_buffer_receiver() { let _serialized = crate::array::test_serialize(); let buf = subject(); - let cb = closure(cb_is_three_or_two as *const u8); + let cb = closure(crate::fn_info!(cb_is_three_or_two, 3)); let args = [f64::from_bits( crate::value::JSValue::pointer(cb as *const u8).bits(), )]; @@ -1095,7 +1097,7 @@ fn a_plain_array_still_maps_through_the_same_helper() { for v in [3.0, 1.0, 2.0] { arr = js_array_push_f64(arr, v); } - let result = crate::array::js_array_map(arr, closure(cb_double as *const u8)); + let result = crate::array::js_array_map(arr, closure(crate::fn_info!(cb_double, 3))); assert_eq!(observed_values(), vec![3.0, 1.0, 2.0]); let out = crate::array::header::clean_arr_ptr(result); assert!( @@ -1120,9 +1122,9 @@ fn a_plain_array_never_reaches_the_buffer_gate() { // receiver-resolution call — the same discipline #8140's probe-count test // needed, and for the same reason: reading the result runs its own // per-element probes and would swamp the window. - crate::array::js_array_map_discard(arr, closure(cb_double as *const u8)); + crate::array::js_array_map_discard(arr, closure(crate::fn_info!(cb_double, 3))); let before = crate::object::typed_array_proto_thunks::test_buffer_gate_probe_count(); - crate::array::js_array_map_discard(arr, closure(cb_double as *const u8)); + crate::array::js_array_map_discard(arr, closure(crate::fn_info!(cb_double, 3))); let after = crate::object::typed_array_proto_thunks::test_buffer_gate_probe_count(); assert_eq!( after, before, @@ -1146,7 +1148,10 @@ fn a_generic_array_like_object_receiver_still_materializes() { crate::object::js_object_set_field_by_name(obj, key("0"), 3.0); crate::object::js_object_set_field_by_name(obj, key("1"), 1.0); crate::object::js_object_set_field_by_name(obj, key("2"), 2.0); - crate::array::js_array_map_discard(obj as *const ArrayHeader, closure(cb_double as *const u8)); + crate::array::js_array_map_discard( + obj as *const ArrayHeader, + closure(crate::fn_info!(cb_double, 3)), + ); assert_eq!( observed_values(), vec![3.0, 1.0, 2.0], diff --git a/crates/perry-runtime/src/async_hooks.rs b/crates/perry-runtime/src/async_hooks.rs index ea67e2cfc1..3d8661f2b8 100644 --- a/crates/perry-runtime/src/async_hooks.rs +++ b/crates/perry-runtime/src/async_hooks.rs @@ -13,7 +13,7 @@ use crate::array::{js_array_length, ArrayHeader}; use crate::closure::{ js_closure_alloc, js_closure_call1, js_closure_call4, js_closure_get_capture_f64, js_closure_get_capture_ptr, js_closure_set_capture_f64, js_closure_set_capture_ptr, - js_register_closure_rest, ClosureHeader, + ClosureHeader, }; use crate::object::{js_object_get_field_by_name, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; @@ -1400,9 +1400,10 @@ extern "C" fn async_resource_bind_method_trampoline( } fn async_resource_bind_method_value(handle: i64) -> f64 { - let trampoline = async_resource_bind_method_trampoline as *const u8; - js_register_closure_rest(trampoline, 0); - let closure = js_closure_alloc(trampoline, 1); + let closure = js_closure_alloc( + crate::fn_info!(async_resource_bind_method_trampoline, 1; with_rest(0)), + 1, + ); if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -1689,7 +1690,6 @@ fn register_bind_trampoline_once() { if !flag.get() { // fixed_arity=0 → dispatch_rest_bundled calls // `f(closure, rest_array)` regardless of forwarded arity. - js_register_closure_rest(async_resource_bind_trampoline as *const u8, 0); flag.set(true); } }); @@ -1708,7 +1708,10 @@ pub extern "C" fn js_async_resource_bind(handle: i64, callback_value: f64, this_ return 0; }; register_bind_trampoline_once(); - let closure = js_closure_alloc(async_resource_bind_trampoline as *const u8, 3); + let closure = js_closure_alloc( + crate::fn_info!(async_resource_bind_trampoline, 1; with_rest(0)), + 3, + ); if closure.is_null() { return 0; } @@ -1780,7 +1783,7 @@ pub extern "C" fn js_async_resource_static_bind_value( let name = js_string_value_to_string(f64::from_bits(own_name.bits())); (!name.is_empty()).then_some(name) } else { - unsafe { crate::builtins::function_name_for_ptr((*callback).func_ptr as usize) } + unsafe { crate::builtins::function_name_for_ptr((*callback).code() as usize) } .filter(|name| !name.is_empty()) } }; @@ -1849,23 +1852,6 @@ pub use context_snapshots::{ js_async_local_storage_static_snapshot_direct, js_async_local_storage_static_snapshot_method, }; -// Stays beside the module's root scanner: its registration flag is -// per-thread state (`scripts/gc_runtime_root_holders.py`). -fn register_snapshot_trampoline_once() { - thread_local! { - static REGISTERED: Cell = const { Cell::new(false) }; - } - REGISTERED.with(|flag| { - if !flag.get() { - js_register_closure_rest( - context_snapshots::async_local_storage_snapshot_trampoline as *const u8, - 1, - ); - flag.set(true); - } - }); -} - pub fn scan_async_hooks_roots(mark: &mut dyn FnMut(f64)) { let mut visitor = crate::gc::RuntimeRootVisitor::for_copy(mark); scan_async_hooks_roots_mut(&mut visitor); diff --git a/crates/perry-runtime/src/async_hooks/context_snapshots.rs b/crates/perry-runtime/src/async_hooks/context_snapshots.rs index aaf81ecf63..2caea44b09 100644 --- a/crates/perry-runtime/src/async_hooks/context_snapshots.rs +++ b/crates/perry-runtime/src/async_hooks/context_snapshots.rs @@ -99,9 +99,11 @@ pub(super) extern "C" fn async_local_storage_snapshot_trampoline( } pub(super) fn async_local_storage_static_snapshot_value() -> f64 { - super::register_snapshot_trampoline_once(); let snapshot_id = register_context_snapshot(crate::async_context::capture_context()); - let closure = js_closure_alloc(async_local_storage_snapshot_trampoline as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(async_local_storage_snapshot_trampoline, 2; with_rest(1)), + 1, + ); if closure.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/async_hooks/provider_ffi.rs b/crates/perry-runtime/src/async_hooks/provider_ffi.rs index cf5f524a12..a8cf8e4919 100644 --- a/crates/perry-runtime/src/async_hooks/provider_ffi.rs +++ b/crates/perry-runtime/src/async_hooks/provider_ffi.rs @@ -20,10 +20,9 @@ extern "C" fn deferred_destroy_step( } fn schedule_deferred_destroy_step(async_id: u64, remaining: u32) { - crate::closure::js_register_closure_arity(deferred_destroy_step as *const u8, 0); let scope = crate::gc::RuntimeHandleScope::new(); let callback = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - deferred_destroy_step as *const u8, + crate::fn_info!(deferred_destroy_step, 0; with_declared(0)), 2, )); callback.with_mut_ptr(|callback| { diff --git a/crates/perry-runtime/src/async_hooks/test_support.rs b/crates/perry-runtime/src/async_hooks/test_support.rs index a264f1b335..34ab07ac20 100644 --- a/crates/perry-runtime/src/async_hooks/test_support.rs +++ b/crates/perry-runtime/src/async_hooks/test_support.rs @@ -72,7 +72,7 @@ mod tests { } fn enable_throwing_lifecycle_hook(before_phase: bool) { - let callback = js_closure_alloc(throwing_lifecycle_hook as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(throwing_lifecycle_hook, 1), 0); let mut callbacks = HookCallbacks::empty(); if before_phase { callbacks.before = callback; diff --git a/crates/perry-runtime/src/builtins/console.rs b/crates/perry-runtime/src/builtins/console.rs index 4267a81d69..252c2a9806 100644 --- a/crates/perry-runtime/src/builtins/console.rs +++ b/crates/perry-runtime/src/builtins/console.rs @@ -157,7 +157,8 @@ pub extern "C" fn js_console_log_as_closure() -> f64 { release_console_log_singleton_in_freed_ranges, ); }); - let fresh = crate::closure::js_closure_alloc(console_log_callable_thunk as *const u8, 0); + let fresh = + crate::closure::js_closure_alloc(crate::fn_info!(console_log_callable_thunk, 1), 0); // CAS so concurrent first-use callers don't leak a closure. // The loser's allocation is unreachable by any user code path // and will be reclaimed by the next GC sweep. The winner is @@ -528,12 +529,10 @@ unsafe fn console_string_len(value: f64) -> Option { fn console_make_named_function( scope: &crate::gc::RuntimeHandleScope, name: &str, - func_ptr: *const u8, - call_arity: u32, + info: *const crate::closure::JsFunctionInfo, exposed_length: u32, ) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, call_arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); let closure_handle = scope.root_raw_mut_ptr(closure); crate::object::set_bound_native_closure_name( closure_handle.get_raw_mut_ptr::(), @@ -620,8 +619,7 @@ pub extern "C" fn js_console_context(name: f64) -> f64 { let func = console_make_named_function( &scope, method, - console_context_method_noop as *const u8, - 0, + crate::fn_info!(console_context_method_noop, 0; with_declared(0)), 1, ); let func_handle = scope.root_nanbox_f64(func); @@ -650,7 +648,12 @@ pub extern "C" fn js_console_create_task(name: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let obj = crate::object::js_object_alloc(0, 0); let obj_handle = scope.root_raw_mut_ptr(obj); - let run = console_make_named_function(&scope, "run", console_task_run as *const u8, 1, 0); + let run = console_make_named_function( + &scope, + "run", + crate::fn_info!(console_task_run, 1; with_declared(1)), + 0, + ); let run_handle = scope.root_nanbox_f64(run); console_set_field( obj_handle.get_raw_mut_ptr::(), diff --git a/crates/perry-runtime/src/builtins/formatting.rs b/crates/perry-runtime/src/builtins/formatting.rs index c784f30e4f..c0280f2535 100644 --- a/crates/perry-runtime/src/builtins/formatting.rs +++ b/crates/perry-runtime/src/builtins/formatting.rs @@ -286,7 +286,7 @@ fn format_function_for_console(closure_ptr: *const crate::closure::ClosureHeader // `[Function: ChildProcess]` instead of `[Function (anonymous)]`, // matching Node. #1856. let registry_name: Option = unsafe { - let func_ptr = (*closure_ptr).func_ptr; + let func_ptr = (*closure_ptr).code(); if func_ptr.is_null() { None } else { @@ -310,7 +310,7 @@ fn format_function_for_console(closure_ptr: *const crate::closure::ClosureHeader // Synthesize (and cache) it the same way any other reader of // `.name` would. unsafe { - ((*closure_ptr).func_ptr == crate::closure::BOUND_FUNCTION_FUNC_PTR).then(|| { + ((*closure_ptr).code() == crate::closure::BOUND_FUNCTION_FUNC_PTR).then(|| { jsvalue_string_content(crate::closure::bound_function_lazy_name( closure_ptr as usize, )) diff --git a/crates/perry-runtime/src/builtins/formatting/util_format.rs b/crates/perry-runtime/src/builtins/formatting/util_format.rs index ba88ce3dc0..7e63b11a5d 100644 --- a/crates/perry-runtime/src/builtins/formatting/util_format.rs +++ b/crates/perry-runtime/src/builtins/formatting/util_format.rs @@ -196,7 +196,7 @@ unsafe fn object_has_a_user_to_string(val: f64) -> bool { return false; } let closure = addr as *const crate::closure::ClosureHeader; - (*closure).func_ptr != crate::object::object_prototype_to_string_thunk as *const u8 + (*closure).code() != crate::object::object_prototype_to_string_thunk as *const u8 } fn inspect_format_arg(val: f64) -> String { @@ -603,7 +603,7 @@ mod s_placeholder_policy_tests { let custom = crate::object::js_object_alloc(0, 1); let to_string_key = crate::string::js_string_from_bytes(b"toString".as_ptr(), 8); let user_fn = crate::closure::js_closure_alloc( - crate::object::object_prototype_value_of_thunk as *const u8, + crate::fn_info!(crate::object::object_prototype_value_of_thunk, 0), 0, ); crate::object::js_object_set_field_by_name( diff --git a/crates/perry-runtime/src/bun_compat/cli_utils.rs b/crates/perry-runtime/src/bun_compat/cli_utils.rs index 3bbd1a6036..6d6fc19ff9 100644 --- a/crates/perry-runtime/src/bun_compat/cli_utils.rs +++ b/crates/perry-runtime/src/bun_compat/cli_utils.rs @@ -9,7 +9,7 @@ use super::{ bool_value, boxed_str, is_string_value, is_undefined_or_null, key_ptr, object_field, payload_bytes, promise_rejected, promise_value, value_to_string, }; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::gc::{RootedValues, RuntimeHandle, RuntimeHandleScope}; use crate::object::{js_object_alloc, js_object_set_field_by_name}; use crate::string::js_string_from_bytes; @@ -53,29 +53,22 @@ fn number_arg(value: f64) -> Option { } } -fn closure1(name: &str, func: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { - js_register_closure_arity(func as *const u8, 1); - let closure = js_closure_alloc(func as *const u8, 0); +fn closure1(name: &str, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 1); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -fn closure2(name: &str, func: crate::closure::body_call::js_body_fn_ty!(a, a), length: u32) -> f64 { - js_register_closure_arity(func as *const u8, 2); - let closure = js_closure_alloc(func as *const u8, 0); +fn closure2(name: &str, info: *const crate::closure::JsFunctionInfo, length: u32) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, length); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -fn closure3( - name: &str, - func: crate::closure::body_call::js_body_fn_ty!(a, a, a), - length: u32, -) -> f64 { - js_register_closure_arity(func as *const u8, 3); - let closure = js_closure_alloc(func as *const u8, 0); +fn closure3(name: &str, info: *const crate::closure::JsFunctionInfo, length: u32) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, length); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) @@ -134,10 +127,13 @@ extern "C" fn yaml_stringify_closure( pub fn js_bun_yaml() -> f64 { namespace_object(&[ - (b"parse", closure1("parse", yaml_parse_closure)), + ( + b"parse", + closure1("parse", crate::fn_info!(yaml_parse_closure, 1)), + ), ( b"stringify", - closure3("stringify", yaml_stringify_closure, 1), + closure3("stringify", crate::fn_info!(yaml_stringify_closure, 3), 1), ), ]) } @@ -183,7 +179,10 @@ pub(crate) fn toml_parse_result(source: &str) -> Result { } pub fn js_bun_toml() -> f64 { - namespace_object(&[(b"parse", closure1("parse", toml_parse_closure))]) + namespace_object(&[( + b"parse", + closure1("parse", crate::fn_info!(toml_parse_closure, 1)), + )]) } fn normalize_semver_version(input: &str) -> &str { @@ -246,10 +245,13 @@ extern "C" fn semver_satisfies_closure( pub fn js_bun_semver() -> f64 { namespace_object(&[ - (b"order", closure2("order", semver_order_closure, 2)), + ( + b"order", + closure2("order", crate::fn_info!(semver_order_closure, 2), 2), + ), ( b"satisfies", - closure2("satisfies", semver_satisfies_closure, 2), + closure2("satisfies", crate::fn_info!(semver_satisfies_closure, 2), 2), ), ]) } @@ -267,7 +269,11 @@ extern "C" fn jsonl_parse_chunk_closure( pub fn js_bun_jsonl() -> f64 { namespace_object(&[( b"parseChunk", - closure3("parseChunk", jsonl_parse_chunk_closure, 1), + closure3( + "parseChunk", + crate::fn_info!(jsonl_parse_chunk_closure, 3), + 1, + ), )]) } @@ -594,7 +600,11 @@ extern "C" fn xxhash64_closure( pub fn decorate_bun_hash(value: f64) -> f64 { let scope = RuntimeHandleScope::new(); let hash = scope.root_nanbox_f64(value); - let xxhash = scope.root_nanbox_f64(closure2("xxHash64", xxhash64_closure, 1)); + let xxhash = scope.root_nanbox_f64(closure2( + "xxHash64", + crate::fn_info!(xxhash64_closure, 2), + 1, + )); let raw = JSValue::from_bits(hash.get_nanbox_f64().to_bits()).as_pointer::() as usize; crate::closure::closure_set_dynamic_prop(raw, "xxHash64", xxhash.get_nanbox_f64()); hash.get_nanbox_f64() diff --git a/crates/perry-runtime/src/bun_compat/glob.rs b/crates/perry-runtime/src/bun_compat/glob.rs index f37c3bda30..accc695827 100644 --- a/crates/perry-runtime/src/bun_compat/glob.rs +++ b/crates/perry-runtime/src/bun_compat/glob.rs @@ -46,17 +46,26 @@ pub extern "C-unwind" fn js_bun_glob_new(pattern: f64) -> f64 { set_field( obj, b"scan", - bound_method1(bun_glob_scan, pattern.get_nanbox_f64()), + bound_method( + crate::fn_info!(bun_glob_scan, 1; with_declared(1)), + pattern.get_nanbox_f64(), + ), ); set_field( obj, b"scanSync", - bound_method1(bun_glob_scan_sync, pattern.get_nanbox_f64()), + bound_method( + crate::fn_info!(bun_glob_scan_sync, 1; with_declared(1)), + pattern.get_nanbox_f64(), + ), ); set_field( obj, b"match", - bound_method1(bun_glob_match, pattern.get_nanbox_f64()), + bound_method( + crate::fn_info!(bun_glob_match, 1; with_declared(1)), + pattern.get_nanbox_f64(), + ), ); f64::from_bits(JSValue::pointer(obj as *const u8).bits()) } diff --git a/crates/perry-runtime/src/bun_compat/mod.rs b/crates/perry-runtime/src/bun_compat/mod.rs index f1ccb6f760..afdd7219a3 100644 --- a/crates/perry-runtime/src/bun_compat/mod.rs +++ b/crates/perry-runtime/src/bun_compat/mod.rs @@ -36,8 +36,8 @@ mod width_tables; mod wyhash; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader, + JsFunctionInfo, }; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, @@ -159,16 +159,9 @@ fn set_field(obj: *mut crate::object::ObjectHeader, key: &[u8], value: f64) { js_object_set_field_by_name(obj, key_ptr(key), value); } -fn bound_method0(func: crate::closure::body_call::js_body_fn_ty!(), capture: f64) -> f64 { - js_register_closure_arity(func as *const u8, 0); - let closure = js_closure_alloc(func as *const u8, 1); - js_closure_set_capture_f64(closure, 0, capture); - f64::from_bits(JSValue::pointer(closure as *const u8).bits()) -} - -fn bound_method1(func: crate::closure::body_call::js_body_fn_ty!(a), capture: f64) -> f64 { - js_register_closure_arity(func as *const u8, 1); - let closure = js_closure_alloc(func as *const u8, 1); +/// A method closure over `info`'s body capturing `capture` in slot 0. +fn bound_method(info: *const JsFunctionInfo, capture: f64) -> f64 { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_f64(closure, 0, capture); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } @@ -417,15 +410,46 @@ pub extern "C" fn js_bun_file(path: f64) -> f64 { b"type", boxed_str(mime_type_for_path(&path_string).as_bytes()), ); - set_field(obj, b"text", bound_method0(bun_file_text, path_value)); - set_field(obj, b"json", bound_method0(bun_file_json, path_value)); + set_field( + obj, + b"text", + bound_method( + crate::fn_info!(bun_file_text, 0; with_declared(0)), + path_value, + ), + ); + set_field( + obj, + b"json", + bound_method( + crate::fn_info!(bun_file_json, 0; with_declared(0)), + path_value, + ), + ); set_field( obj, b"arrayBuffer", - bound_method0(bun_file_array_buffer, path_value), + bound_method( + crate::fn_info!(bun_file_array_buffer, 0; with_declared(0)), + path_value, + ), + ); + set_field( + obj, + b"bytes", + bound_method( + crate::fn_info!(bun_file_bytes, 0; with_declared(0)), + path_value, + ), + ); + set_field( + obj, + b"exists", + bound_method( + crate::fn_info!(bun_file_exists, 0; with_declared(0)), + path_value, + ), ); - set_field(obj, b"bytes", bound_method0(bun_file_bytes, path_value)); - set_field(obj, b"exists", bound_method0(bun_file_exists, path_value)); f64::from_bits(JSValue::pointer(obj as *const u8).bits()) } @@ -524,12 +548,29 @@ pub extern "C" fn js_bun_stdin() -> f64 { set_field(obj, BUN_STD_FD_KEY, 0.0); set_field(obj, b"size", f64::INFINITY); set_field(obj, b"type", boxed_str(b"application/octet-stream")); - set_field(obj, b"text", bound_method0(bun_stdin_text, undefined())); - set_field(obj, b"json", bound_method0(bun_stdin_json, undefined())); + set_field( + obj, + b"text", + bound_method( + crate::fn_info!(bun_stdin_text, 0; with_declared(0)), + undefined(), + ), + ); + set_field( + obj, + b"json", + bound_method( + crate::fn_info!(bun_stdin_json, 0; with_declared(0)), + undefined(), + ), + ); set_field( obj, b"arrayBuffer", - bound_method0(bun_stdin_array_buffer, undefined()), + bound_method( + crate::fn_info!(bun_stdin_array_buffer, 0; with_declared(0)), + undefined(), + ), ); f64::from_bits(JSValue::pointer(obj as *const u8).bits()) } diff --git a/crates/perry-runtime/src/bun_compat/plugin.rs b/crates/perry-runtime/src/bun_compat/plugin.rs index 38cb2aaaf5..2c2b5f813d 100644 --- a/crates/perry-runtime/src/bun_compat/plugin.rs +++ b/crates/perry-runtime/src/bun_compat/plugin.rs @@ -2,7 +2,7 @@ //! builds cannot install JavaScript module loaders after compilation. use super::{key_ptr, object_field, undefined}; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::gc::{RuntimeHandle, RuntimeHandleScope}; use crate::object::{js_object_alloc, js_object_set_field_by_name}; use crate::value::{js_nanbox_pointer, JSValue}; @@ -16,8 +16,7 @@ extern "C" fn ignore( } fn noop() -> f64 { - js_register_closure_arity(ignore as *const u8, 1); - js_nanbox_pointer(js_closure_alloc(ignore as *const u8, 0) as i64) + js_nanbox_pointer(js_closure_alloc(crate::fn_info!(ignore, 1; with_declared(1)), 0) as i64) } fn set(scope: &RuntimeHandleScope, object: &RuntimeHandle, name: &[u8], value: f64) { @@ -135,9 +134,8 @@ mod tests { static SETUP_CALLS: AtomicU32 = AtomicU32::new(0); static LOADER_CALLS: AtomicU32 = AtomicU32::new(0); - fn closure(func: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { - js_register_closure_arity(func as *const u8, 1); - js_nanbox_pointer(js_closure_alloc(func as *const u8, 0) as i64) + fn closure(info: *const crate::closure::JsFunctionInfo) -> f64 { + js_nanbox_pointer(js_closure_alloc(info, 0) as i64) } extern "C" fn loader( @@ -157,7 +155,7 @@ mod tests { SETUP_CALLS.fetch_add(1, Ordering::SeqCst); let scope = RuntimeHandleScope::new(); let build = scope.root_nanbox_f64(build); - let callback = scope.root_nanbox_f64(closure(loader)); + let callback = scope.root_nanbox_f64(closure(crate::fn_info!(loader, 1; with_declared(1)))); // The builder must remain usable across collection in user setup. crate::gc::js_gc_collect(); for name in [ @@ -201,7 +199,7 @@ mod tests { SETUP_CALLS.store(0, Ordering::SeqCst); LOADER_CALLS.store(0, Ordering::SeqCst); let scope = RuntimeHandleScope::new(); - let setup = scope.root_nanbox_f64(closure(setup)); + let setup = scope.root_nanbox_f64(closure(crate::fn_info!(setup, 1; with_declared(1)))); let object = scope.root_nanbox_f64(js_nanbox_pointer(js_object_alloc(0, 1) as i64)); set(&scope, &object, b"setup", setup.get_nanbox_f64()); assert_eq!( @@ -227,7 +225,9 @@ mod tests { #[test] fn async_setup_preserves_its_result() { let scope = RuntimeHandleScope::new(); - let result = scope.root_nanbox_f64(js_bun_plugin(closure(async_setup))); + let result = scope.root_nanbox_f64(js_bun_plugin(closure( + crate::fn_info!(async_setup, 1; with_declared(1)), + ))); assert_eq!( crate::promise::js_value_is_promise(result.get_nanbox_f64()), 1 @@ -242,7 +242,9 @@ mod tests { #[test] fn clear_all_is_a_callable_noop() { let scope = RuntimeHandleScope::new(); - let value = scope.root_nanbox_f64(decorate_bun_plugin(closure(setup))); + let value = scope.root_nanbox_f64(decorate_bun_plugin(closure( + crate::fn_info!(setup, 1; with_declared(1)), + ))); let raw = JSValue::from_bits(value.get_nanbox_f64().to_bits()).as_pointer::(); let clear = crate::closure::closure_get_dynamic_prop(raw as usize, "clearAll"); assert!(!crate::fs::extract_closure_ptr(clear).is_null()); diff --git a/crates/perry-runtime/src/bun_compat/self_global.rs b/crates/perry-runtime/src/bun_compat/self_global.rs index 8d77af1a48..217de3b0d7 100644 --- a/crates/perry-runtime/src/bun_compat/self_global.rs +++ b/crates/perry-runtime/src/bun_compat/self_global.rs @@ -1,7 +1,7 @@ //! Bun's replaceable `self` accessor (#10306). The platform hook runs before //! every module, but must never undo a user's assignment or deletion. -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{AccessorDescriptor, ObjectHeader, PropertyAttrs}; use crate::value::{js_nanbox_pointer, JSValue}; @@ -19,10 +19,8 @@ pub(super) fn install_once() { let global = JSValue::from_bits(crate::object::js_get_global_this().to_bits()) .as_pointer::() .cast_mut(); - let getter_fn = get_self as *const u8; - let setter_fn = set_self as *const u8; - js_register_closure_arity(getter_fn, 0); - js_register_closure_arity(setter_fn, 1); + let getter_fn = crate::fn_info!(get_self, 0; with_declared(0)); + let setter_fn = crate::fn_info!(set_self, 1; with_declared(1)); let getter = js_closure_alloc(getter_fn, 0); let setter = js_closure_alloc(setter_fn, 0); for (closure, name, arity) in [(getter, "get", 0), (setter, "set", 1)] { diff --git a/crates/perry-runtime/src/bun_compat/spawn.rs b/crates/perry-runtime/src/bun_compat/spawn.rs index 773490f969..c2e40c6890 100644 --- a/crates/perry-runtime/src/bun_compat/spawn.rs +++ b/crates/perry-runtime/src/bun_compat/spawn.rs @@ -6,13 +6,11 @@ use super::*; use crate::child_process::{ - cp_array_ptr, cp_box_ptr, cp_box_string, cp_build_object, cp_cast0, cp_cast1, cp_cast2, - cp_get_field, cp_object_ptr, cp_set_field, cp_undefined, CpFn, TAG_FALSE_F64, TAG_NULL_F64, - TAG_TRUE_F64, + cp_array_ptr, cp_box_ptr, cp_box_string, cp_build_object, cp_get_field, cp_object_ptr, + cp_set_field, cp_undefined, CpFn, TAG_FALSE_F64, TAG_NULL_F64, TAG_TRUE_F64, }; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader, }; use crate::value::JSValue; @@ -101,33 +99,17 @@ fn call_method(receiver: f64, name: &[u8], args: &[f64]) -> f64 { } } -fn closure_with_captures(func: *const u8, arity: u32, captures: &[f64]) -> f64 { - js_register_closure_arity(func, arity); +/// `info` records the body's declared arity. +fn closure_with_captures(info: *const crate::closure::JsFunctionInfo, captures: &[f64]) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let captures = scope.root_nanbox_f64_slice(captures); - let closure = js_closure_alloc(func, captures.len() as u32); + let closure = js_closure_alloc(info, captures.len() as u32); for (index, value) in captures.iter().enumerate() { js_closure_set_capture_f64(closure, index as u32, value.get_nanbox_f64()); } cp_box_ptr(closure as *const u8) } -fn register_bun_spawn_arities() { - js_register_closure_arity(bun_terminal_write as *const u8, 1); - js_register_closure_arity(bun_terminal_resize as *const u8, 2); - js_register_closure_arity(bun_terminal_set_raw_mode as *const u8, 1); - js_register_closure_arity(bun_terminal_ref as *const u8, 0); - js_register_closure_arity(bun_terminal_unref as *const u8, 0); - js_register_closure_arity(bun_terminal_close as *const u8, 0); - #[cfg(unix)] - { - js_register_closure_arity(bun_pty_subprocess_kill as *const u8, 1); - js_register_closure_arity(bun_pty_subprocess_ref as *const u8, 0); - js_register_closure_arity(bun_pty_subprocess_unref as *const u8, 0); - js_register_closure_arity(bun_pty_subprocess_dispose as *const u8, 0); - } -} - fn captured_at(closure: *const ClosureHeader, index: u32) -> f64 { js_closure_get_capture_f64(closure, index) } @@ -219,19 +201,25 @@ fn decorate_readable(stream: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(stream); for (name, function) in [ - (b"text".as_slice(), bun_readable_text as *const u8), - (b"json".as_slice(), bun_readable_json as *const u8), + ( + b"text".as_slice(), + crate::fn_info!(bun_readable_text, 0; with_declared(0)), + ), + ( + b"json".as_slice(), + crate::fn_info!(bun_readable_json, 0; with_declared(0)), + ), ( b"arrayBuffer".as_slice(), - bun_readable_array_buffer as *const u8, + crate::fn_info!(bun_readable_array_buffer, 0; with_declared(0)), + ), + ( + b"bytes".as_slice(), + crate::fn_info!(bun_readable_bytes, 0; with_declared(0)), ), - (b"bytes".as_slice(), bun_readable_bytes as *const u8), ] { - let method = scope.root_nanbox_f64(closure_with_captures( - function, - 0, - &[stream.get_nanbox_f64()], - )); + let method = + scope.root_nanbox_f64(closure_with_captures(function, &[stream.get_nanbox_f64()])); cp_set_field(stream.get_nanbox_f64(), name, method.get_nanbox_f64()); } stream.get_nanbox_f64() @@ -250,8 +238,7 @@ fn decorate_sink(stream: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(stream); let flush = scope.root_nanbox_f64(closure_with_captures( - bun_sink_flush as *const u8, - 1, + crate::fn_info!(bun_sink_flush, 1; with_declared(1)), &[stream.get_nanbox_f64()], )); cp_set_field(stream.get_nanbox_f64(), b"flush", flush.get_nanbox_f64()); @@ -557,8 +544,7 @@ fn finish_non_pty_subprocess(subprocess: f64, options: f64) -> f64 { on_exit.get_nanbox_f64(), ); let listener = scope.root_nanbox_f64(closure_with_captures( - bun_subprocess_exit as *const u8, - 2, + crate::fn_info!(bun_subprocess_exit, 2; with_declared(2)), &[subprocess.get_nanbox_f64()], )); let event = scope.root_nanbox_f64(cp_box_string("exit")); @@ -721,7 +707,6 @@ fn terminal_options_i32(options: f64, key: &[u8], default: i32) -> i32 { /// callback owner. The underlying PTY is attached lazily by `Bun.spawn`. #[no_mangle] pub extern "C" fn js_bun_terminal_new(options: f64) -> f64 { - register_bun_spawn_arities(); #[cfg(not(unix))] { let _ = options; @@ -736,12 +721,30 @@ pub extern "C" fn js_bun_terminal_new(options: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let options = scope.root_nanbox_f64(options); let methods: [(&str, CpFn); 6] = [ - ("write", cp_cast1(bun_terminal_write)), - ("resize", cp_cast2(bun_terminal_resize)), - ("setRawMode", cp_cast1(bun_terminal_set_raw_mode)), - ("ref", cp_cast0(bun_terminal_ref)), - ("unref", cp_cast0(bun_terminal_unref)), - ("close", cp_cast0(bun_terminal_close)), + ( + "write", + crate::fn_info!(bun_terminal_write, 1; with_declared(1)), + ), + ( + "resize", + crate::fn_info!(bun_terminal_resize, 2; with_declared(2)), + ), + ( + "setRawMode", + crate::fn_info!(bun_terminal_set_raw_mode, 1; with_declared(1)), + ), + ( + "ref", + crate::fn_info!(bun_terminal_ref, 0; with_declared(0)), + ), + ( + "unref", + crate::fn_info!(bun_terminal_unref, 0; with_declared(0)), + ), + ( + "close", + crate::fn_info!(bun_terminal_close, 0; with_declared(0)), + ), ]; let terminal = scope.root_nanbox_f64(cp_box_ptr(cp_build_object( &methods, @@ -979,10 +982,22 @@ fn finish_pty_subprocess(command: &str, args: &[String], options: f64, terminal: ); let methods: [(&str, CpFn); 4] = [ - ("kill", cp_cast1(bun_pty_subprocess_kill)), - ("ref", cp_cast0(bun_pty_subprocess_ref)), - ("unref", cp_cast0(bun_pty_subprocess_unref)), - ("dispose", cp_cast0(bun_pty_subprocess_dispose)), + ( + "kill", + crate::fn_info!(bun_pty_subprocess_kill, 1; with_declared(1)), + ), + ( + "ref", + crate::fn_info!(bun_pty_subprocess_ref, 0; with_declared(0)), + ), + ( + "unref", + crate::fn_info!(bun_pty_subprocess_unref, 0; with_declared(0)), + ), + ( + "dispose", + crate::fn_info!(bun_pty_subprocess_dispose, 0; with_declared(0)), + ), ]; let subprocess = scope.root_nanbox_f64(cp_box_ptr(cp_build_object( &methods, @@ -1022,14 +1037,12 @@ fn finish_pty_subprocess(command: &str, args: &[String], options: f64, terminal: install_dispose_aliases(subprocess.get_nanbox_f64(), dispose, true); let data_bridge = scope.root_nanbox_f64(closure_with_captures( - bun_terminal_data_bridge as *const u8, - 1, + crate::fn_info!(bun_terminal_data_bridge, 1; with_declared(1)), &[terminal.get_nanbox_f64()], )); crate::pty::pty_register(ipty.get_nanbox_f64(), "data", data_bridge.get_nanbox_f64()); let exit_bridge = scope.root_nanbox_f64(closure_with_captures( - bun_pty_subprocess_exit as *const u8, - 1, + crate::fn_info!(bun_pty_subprocess_exit, 1; with_declared(1)), &[subprocess.get_nanbox_f64(), terminal.get_nanbox_f64()], )); crate::pty::pty_register(ipty.get_nanbox_f64(), "exit", exit_bridge.get_nanbox_f64()); @@ -1095,7 +1108,6 @@ fn parse_command(command_or_options: f64, options: f64) -> (String, Vec, /// `Bun.spawn(cmd, options?)` / `Bun.spawn({ cmd, ...options })`. #[no_mangle] pub extern "C" fn js_bun_spawn(command_or_options: f64, options: f64) -> f64 { - register_bun_spawn_arities(); let scope = crate::gc::RuntimeHandleScope::new(); let command_or_options = scope.root_nanbox_f64(command_or_options); let options_arg = scope.root_nanbox_f64(options); diff --git a/crates/perry-runtime/src/bun_ffi/callback.rs b/crates/perry-runtime/src/bun_ffi/callback.rs index 8f372d37e9..7e983b377f 100644 --- a/crates/perry-runtime/src/bun_ffi/callback.rs +++ b/crates/perry-runtime/src/bun_ffi/callback.rs @@ -323,10 +323,10 @@ extern "C" fn callback_close_thunk( } fn close_closure(index: usize) -> f64 { - let function = callback_close_thunk as *const u8; - crate::closure::js_register_closure_arity(function, 0); - crate::closure::js_register_closure_length(function, 0); - let closure = crate::closure::js_closure_alloc(function, 1); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(callback_close_thunk, 0; with_declared(0), with_length(0)), + 1, + ); crate::closure::js_closure_set_capture_bits(closure, 0, index as u64); crate::object::set_bound_native_closure_name(closure, "close"); crate::object::set_builtin_closure_length(closure as usize, 0); diff --git a/crates/perry-runtime/src/bun_ffi/dlopen.rs b/crates/perry-runtime/src/bun_ffi/dlopen.rs index a285c2810b..9113c66ad9 100644 --- a/crates/perry-runtime/src/bun_ffi/dlopen.rs +++ b/crates/perry-runtime/src/bun_ffi/dlopen.rs @@ -5,7 +5,7 @@ //! single capture is its `SYMS` index, so the shared per-arity thunks //! (`sym_thunk_0..=16`) stay signature-compatible with the closure call //! ABI (`extern "C" fn(*const ClosureHeader, f64 × arity) -> f64`, -//! arity-padded by `closure/dispatch` via `js_register_closure_arity`). +//! arity-padded by `closure/dispatch` from each thunk's `JsFunctionInfo`). //! //! `close()` calls `dlclose` and poisons the library's symbols: later //! calls throw instead of jumping through a dangling handle. This is @@ -323,25 +323,25 @@ sym_thunk!( a15 ); -fn sym_thunk_for(arity: usize) -> *const u8 { +fn sym_thunk_for(arity: usize) -> *const crate::closure::JsFunctionInfo { match arity { - 0 => sym_thunk_0 as *const u8, - 1 => sym_thunk_1 as *const u8, - 2 => sym_thunk_2 as *const u8, - 3 => sym_thunk_3 as *const u8, - 4 => sym_thunk_4 as *const u8, - 5 => sym_thunk_5 as *const u8, - 6 => sym_thunk_6 as *const u8, - 7 => sym_thunk_7 as *const u8, - 8 => sym_thunk_8 as *const u8, - 9 => sym_thunk_9 as *const u8, - 10 => sym_thunk_10 as *const u8, - 11 => sym_thunk_11 as *const u8, - 12 => sym_thunk_12 as *const u8, - 13 => sym_thunk_13 as *const u8, - 14 => sym_thunk_14 as *const u8, - 15 => sym_thunk_15 as *const u8, - _ => sym_thunk_16 as *const u8, + 0 => crate::fn_info!(sym_thunk_0, 0; with_declared(0), with_length(0)), + 1 => crate::fn_info!(sym_thunk_1, 1; with_declared(1), with_length(1)), + 2 => crate::fn_info!(sym_thunk_2, 2; with_declared(2), with_length(2)), + 3 => crate::fn_info!(sym_thunk_3, 3; with_declared(3), with_length(3)), + 4 => crate::fn_info!(sym_thunk_4, 4; with_declared(4), with_length(4)), + 5 => crate::fn_info!(sym_thunk_5, 5; with_declared(5), with_length(5)), + 6 => crate::fn_info!(sym_thunk_6, 6; with_declared(6), with_length(6)), + 7 => crate::fn_info!(sym_thunk_7, 7; with_declared(7), with_length(7)), + 8 => crate::fn_info!(sym_thunk_8, 8; with_declared(8), with_length(8)), + 9 => crate::fn_info!(sym_thunk_9, 9; with_declared(9), with_length(9)), + 10 => crate::fn_info!(sym_thunk_10, 10; with_declared(10), with_length(10)), + 11 => crate::fn_info!(sym_thunk_11, 11; with_declared(11), with_length(11)), + 12 => crate::fn_info!(sym_thunk_12, 12; with_declared(12), with_length(12)), + 13 => crate::fn_info!(sym_thunk_13, 13; with_declared(13), with_length(13)), + 14 => crate::fn_info!(sym_thunk_14, 14; with_declared(14), with_length(14)), + 15 => crate::fn_info!(sym_thunk_15, 15; with_declared(15), with_length(15)), + _ => crate::fn_info!(sym_thunk_16, 16; with_declared(16), with_length(16)), } } @@ -368,10 +368,13 @@ pub(crate) fn close_library_index(lib_index: usize) { /// u64 index. The capture is written as raw bits — a small integer never /// classifies as pointer-bearing in the GC layout, so the closure stays /// pointer-free. -fn index_closure(func: *const u8, index: usize, arity: u32, name: &str) -> f64 { - crate::closure::js_register_closure_arity(func, arity); - crate::closure::js_register_closure_length(func, arity); - let closure = crate::closure::js_closure_alloc(func, 1); +fn index_closure( + info: *const crate::closure::JsFunctionInfo, + index: usize, + arity: u32, + name: &str, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 1); crate::closure::js_closure_set_capture_bits(closure, 0, index as u64); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); @@ -385,10 +388,8 @@ extern "C" fn noop_close_thunk( super::undefined() } -fn no_capture_closure(func: *const u8, arity: u32, name: &str) -> f64 { - crate::closure::js_register_closure_arity(func, arity); - crate::closure::js_register_closure_length(func, arity); - let closure = crate::closure::js_closure_alloc(func, 0); +fn no_capture_closure(info: *const crate::closure::JsFunctionInfo, arity: u32, name: &str) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) @@ -802,7 +803,12 @@ pub(crate) unsafe fn dlopen_value(path_arg: f64, table_arg: f64) -> f64 { }); } - let close_value = index_closure(close_thunk as *const u8, lib_index, 0, "close"); + let close_value = index_closure( + crate::fn_info!(close_thunk, 0; with_declared(0), with_length(0)), + lib_index, + 0, + "close", + ); let close_handle = scope.root_nanbox_f64(close_value); let result = crate::object::js_object_alloc(0, 2); @@ -905,7 +911,7 @@ pub(crate) unsafe fn link_symbols_value(table_arg: f64) -> f64 { }); } let close = scope.root_nanbox_f64(no_capture_closure( - noop_close_thunk as *const u8, + crate::fn_info!(noop_close_thunk, 0; with_declared(0), with_length(0)), 0, "close", )); @@ -1172,19 +1178,19 @@ pub(crate) unsafe fn node_dlopen_value(path_arg: f64, definitions_arg: f64) -> f let lib = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 3)); let close = scope.root_nanbox_f64(index_closure( - close_thunk as *const u8, + crate::fn_info!(close_thunk, 0; with_declared(0), with_length(0)), lib_index, 0, "close", )); let register = scope.root_nanbox_f64(index_closure( - node_register_callback_thunk as *const u8, + crate::fn_info!(node_register_callback_thunk, 2; with_declared(2), with_length(2)), lib_index, 2, "registerCallback", )); let unregister = scope.root_nanbox_f64(index_closure( - node_unregister_callback_thunk as *const u8, + crate::fn_info!(node_unregister_callback_thunk, 1; with_declared(1), with_length(1)), lib_index, 1, "unregisterCallback", diff --git a/crates/perry-runtime/src/bun_ffi/read.rs b/crates/perry-runtime/src/bun_ffi/read.rs index b695ed73bc..dc40e0f648 100644 --- a/crates/perry-runtime/src/bun_ffi/read.rs +++ b/crates/perry-runtime/src/bun_ffi/read.rs @@ -127,27 +127,26 @@ reader!(read_u64, super::types::T_U64); reader!(read_f32, super::types::T_F32); reader!(read_f64, super::types::T_F64); -fn reader_function(kind: u8) -> *const u8 { +/// The reader body for `kind`. Every reader takes `(pointer, offset)` and +/// reports `.length` 1. +fn reader_function(kind: u8) -> *const crate::closure::JsFunctionInfo { match kind { - super::types::T_PTR => read_ptr as *const u8, - super::types::T_I8 => read_i8 as *const u8, - super::types::T_I16 => read_i16 as *const u8, - super::types::T_I32 => read_i32 as *const u8, - super::types::T_I64 => read_i64 as *const u8, - super::types::T_U8 => read_u8 as *const u8, - super::types::T_U16 => read_u16 as *const u8, - super::types::T_U32 => read_u32 as *const u8, - super::types::T_U64 => read_u64 as *const u8, - super::types::T_F32 => read_f32 as *const u8, - _ => read_f64 as *const u8, + super::types::T_PTR => crate::fn_info!(read_ptr, 2; with_declared(2), with_length(1)), + super::types::T_I8 => crate::fn_info!(read_i8, 2; with_declared(2), with_length(1)), + super::types::T_I16 => crate::fn_info!(read_i16, 2; with_declared(2), with_length(1)), + super::types::T_I32 => crate::fn_info!(read_i32, 2; with_declared(2), with_length(1)), + super::types::T_I64 => crate::fn_info!(read_i64, 2; with_declared(2), with_length(1)), + super::types::T_U8 => crate::fn_info!(read_u8, 2; with_declared(2), with_length(1)), + super::types::T_U16 => crate::fn_info!(read_u16, 2; with_declared(2), with_length(1)), + super::types::T_U32 => crate::fn_info!(read_u32, 2; with_declared(2), with_length(1)), + super::types::T_U64 => crate::fn_info!(read_u64, 2; with_declared(2), with_length(1)), + super::types::T_F32 => crate::fn_info!(read_f32, 2; with_declared(2), with_length(1)), + _ => crate::fn_info!(read_f64, 2; with_declared(2), with_length(1)), } } fn closure(name: &str, kind: u8) -> f64 { - let function = reader_function(kind); - crate::closure::js_register_closure_arity(function, 2); - crate::closure::js_register_closure_length(function, 1); - let closure = crate::closure::js_closure_alloc(function, 0); + let closure = crate::closure::js_closure_alloc(reader_function(kind), 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 1); crate::value::js_nanbox_pointer(closure as i64) diff --git a/crates/perry-runtime/src/child_process/builder.rs b/crates/perry-runtime/src/child_process/builder.rs index 159a1dbc9e..7c3f78dcbd 100644 --- a/crates/perry-runtime/src/child_process/builder.rs +++ b/crates/perry-runtime/src/child_process/builder.rs @@ -1,6 +1,6 @@ use super::*; -use crate::closure::{js_closure_alloc, js_closure_set_capture_ptr, js_register_closure_arity}; +use crate::closure::{js_closure_alloc, js_closure_set_capture_ptr}; use crate::object::{js_object_alloc_with_shape, js_object_set_field, ObjectHeader}; use crate::value::JSValue; @@ -12,57 +12,9 @@ pub(crate) const CP_WRITABLE_SHAPE_ID: u32 = 0x7FFF_FD80; // ----- object construction ----- -pub(crate) type CpFn = unsafe extern "C" fn(); -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cp_cast0(f: crate::closure::body_call::js_body_fn_ty!()) -> CpFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cp_cast1(f: crate::closure::body_call::js_body_fn_ty!(a)) -> CpFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cp_cast2(f: crate::closure::body_call::js_body_fn_ty!(a, a)) -> CpFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -/// Erase a three-argument native method to the common child-process method ABI. -pub(crate) fn cp_cast3(f: crate::closure::body_call::js_body_fn_ty!(a, a, a)) -> CpFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cp_cast4(f: crate::closure::body_call::js_body_fn_ty!(a, a, a, a)) -> CpFn { - unsafe { std::mem::transmute(f) } -} - -pub(crate) fn cp_register_arities() { - js_register_closure_arity(cp_method_on as *const u8, 2); - js_register_closure_arity(cp_method_emit as *const u8, 2); - js_register_closure_arity(cp_method_this0 as *const u8, 0); - js_register_closure_arity(cp_method_this1 as *const u8, 1); - js_register_closure_arity(cp_method_child_spawn as *const u8, 1); - js_register_closure_arity(cp_method_set_encoding as *const u8, 1); - js_register_closure_arity(cp_method_remove_listener as *const u8, 2); - js_register_closure_arity(cp_method_remove_all_listeners as *const u8, 1); - js_register_closure_arity(cp_method_kill as *const u8, 1); - js_register_closure_arity(cp_method_ref as *const u8, 0); - js_register_closure_arity(cp_method_unref as *const u8, 0); - js_register_closure_arity(cp_method_dispose as *const u8, 0); - crate::closure::js_register_closure_length(cp_method_dispose as *const u8, 0); - js_register_closure_arity(cp_method_read as *const u8, 1); - js_register_closure_arity(cp_method_pipe as *const u8, 1); - js_register_closure_arity(cp_method_stdin_write as *const u8, 3); - js_register_closure_arity(cp_method_stdin_end as *const u8, 3); - js_register_closure_arity(cp_stream_callback_thunk as *const u8, 0); - // #3316: `send(message, sendHandle, options, callback)` — dispatch with 4 - // padded slots so the trailing callback is visible regardless of call-site - // arity, and report `child.send.length === 4` like Node. - js_register_closure_arity(cp_method_send as *const u8, 4); - crate::closure::js_register_closure_length(cp_method_send as *const u8, 4); - js_register_closure_arity(cp_method_disconnect as *const u8, 0); - // The deferred send-callback thunk takes no JS args. - js_register_closure_arity(cp_send_callback_thunk as *const u8, 0); -} +/// A child-process method body's static info: what a method table lists and +/// `cp_build_object` allocates from (`crate::fn_info!(body, N)`). +pub(crate) type CpFn = *const crate::closure::JsFunctionInfo; /// Allocate a heap object whose method-name fields each hold a closure capturing /// the object itself in slot 0 (so method bodies recover `this`). @@ -80,7 +32,7 @@ pub(crate) fn cp_build_object(methods: &[(&str, CpFn)], shape_id: u32) -> *mut O ); let this_bits = JSValue::pointer(obj as *const u8).bits(); for (i, (_name, func)) in methods.iter().enumerate() { - let closure = js_closure_alloc(*func as *const u8, 1); + let closure = js_closure_alloc(*func, 1); js_closure_set_capture_ptr(closure, 0, this_bits as i64); js_object_set_field(obj, i as u32, JSValue::pointer(closure as *const u8)); } @@ -92,7 +44,10 @@ pub(crate) fn cp_install_dispose(cp: f64) { return; }; - let closure = js_closure_alloc(cp_method_dispose as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(cp_method_dispose, 0; with_declared(0), with_length(0)), + 1, + ); if closure.is_null() { return; } @@ -119,19 +74,43 @@ pub(crate) fn cp_install_dispose(cp: f64) { /// Build a stdout/stderr Readable-shaped EventEmitter. pub(crate) fn cp_build_readable() -> f64 { let methods: [(&str, CpFn); 13] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("prependListener", cp_cast2(cp_method_on)), - ("off", cp_cast2(cp_method_remove_listener)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("emit", cp_cast2(cp_method_emit)), - ("pause", cp_cast0(cp_method_this0)), - ("resume", cp_cast0(cp_method_this0)), - ("destroy", cp_cast0(cp_method_this0)), - ("setEncoding", cp_cast1(cp_method_set_encoding)), - ("read", cp_cast1(cp_method_read)), - ("pipe", cp_cast1(cp_method_pipe)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "prependListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), + ( + "pause", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), + ( + "resume", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), + ( + "destroy", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), + ( + "setEncoding", + crate::fn_info!(cp_method_set_encoding, 1; with_declared(1)), + ), + ("read", crate::fn_info!(cp_method_read, 1; with_declared(1))), + ("pipe", crate::fn_info!(cp_method_pipe, 1; with_declared(1))), ]; let obj = cp_build_object(&methods, CP_READABLE_SHAPE_ID + methods.len() as u32); let val = cp_box_ptr(obj as *const u8); @@ -158,17 +137,41 @@ pub(crate) fn cp_build_readable() -> f64 { /// Build a stdin Writable-shaped EventEmitter. pub(crate) fn cp_build_writable() -> f64 { let methods: [(&str, CpFn); 11] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("off", cp_cast2(cp_method_remove_listener)), - ("emit", cp_cast2(cp_method_emit)), - ("write", cp_cast3(cp_method_stdin_write)), - ("end", cp_cast3(cp_method_stdin_end)), - ("destroy", cp_cast0(cp_method_this0)), - ("cork", cp_cast0(cp_method_this0)), - ("uncork", cp_cast0(cp_method_this0)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), + ( + "write", + crate::fn_info!(cp_method_stdin_write, 3; with_declared(3)), + ), + ( + "end", + crate::fn_info!(cp_method_stdin_end, 3; with_declared(3)), + ), + ( + "destroy", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), + ( + "cork", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), + ( + "uncork", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), ]; let obj = cp_build_object(&methods, CP_WRITABLE_SHAPE_ID + methods.len() as u32); let val = cp_box_ptr(obj as *const u8); @@ -190,22 +193,36 @@ pub(crate) fn cp_build_writable() -> f64 { /// `fork()` construct their live variants in the reactor; this low-level Node /// API only needs the initial observable shape plus its validating `.spawn`. pub(crate) fn cp_build_unstarted_child_process() -> f64 { - cp_register_arities(); let methods: [(&str, CpFn); 11] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("off", cp_cast2(cp_method_remove_listener)), - ("emit", cp_cast2(cp_method_emit)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), ( "removeAllListeners", - cp_cast1(cp_method_remove_all_listeners), + crate::fn_info!(cp_method_remove_all_listeners, 1; with_declared(1)), + ), + ("kill", crate::fn_info!(cp_method_kill, 1; with_declared(1))), + ("ref", crate::fn_info!(cp_method_ref, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_unref, 0; with_declared(0)), + ), + ( + "spawn", + crate::fn_info!(cp_method_child_spawn, 1; with_declared(1)), ), - ("kill", cp_cast1(cp_method_kill)), - ("ref", cp_cast0(cp_method_ref)), - ("unref", cp_cast0(cp_method_unref)), - ("spawn", cp_cast1(cp_method_child_spawn)), ]; let obj = cp_build_object(&methods, CP_SHAPE_ID + 0x60 + methods.len() as u32); let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/child_process/emitter.rs b/crates/perry-runtime/src/child_process/emitter.rs index 43b111b50c..2ca9d1aa95 100644 --- a/crates/perry-runtime/src/child_process/emitter.rs +++ b/crates/perry-runtime/src/child_process/emitter.rs @@ -2,7 +2,7 @@ use super::*; use crate::closure::{ js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, js_native_call_value, - js_register_closure_arity, native_call_value_this, ClosureHeader, JsThis, + native_call_value_this, ClosureHeader, JsThis, }; use crate::string::js_string_from_bytes; use crate::value::JSValue; @@ -143,14 +143,6 @@ pub(crate) extern "C" fn cp_method_this0( ) -> f64 { cp_this(this, closure) } -pub(crate) extern "C" fn cp_method_this1( - closure: *const ClosureHeader, - this: crate::closure::JsThis, - _a: f64, -) -> f64 { - cp_this(this, closure) -} - /// Keep a live child attached to the event loop. Calls are idempotent, matching /// Node and Bun's process-handle contract. pub(crate) extern "C" fn cp_method_ref( @@ -314,10 +306,8 @@ pub(crate) extern "C" fn cp_method_pipe( dest: f64, ) -> f64 { let this = cp_this(_this, closure); - js_register_closure_arity(cp_pipe_data_thunk as *const u8, 1); - js_register_closure_arity(cp_pipe_end_thunk as *const u8, 0); - let data_thunk = js_closure_alloc(cp_pipe_data_thunk as *const u8, 1); + let data_thunk = js_closure_alloc(crate::fn_info!(cp_pipe_data_thunk, 1; with_declared(1)), 1); js_closure_set_capture_ptr(data_thunk, 0, dest.to_bits() as i64); cp_register( this, @@ -325,7 +315,7 @@ pub(crate) extern "C" fn cp_method_pipe( cp_box_ptr(data_thunk as *const u8), ); - let end_thunk = js_closure_alloc(cp_pipe_end_thunk as *const u8, 1); + let end_thunk = js_closure_alloc(crate::fn_info!(cp_pipe_end_thunk, 0; with_declared(0)), 1); js_closure_set_capture_ptr(end_thunk, 0, dest.to_bits() as i64); cp_register( this, @@ -386,8 +376,10 @@ fn cp_stream_callback(arg2: f64, arg3: f64) -> Option { fn cp_defer_stream_callback(callback: f64) { let scope = crate::gc::RuntimeHandleScope::new(); let callback = scope.root_nanbox_f64(callback); - let deferred = - scope.root_raw_mut_ptr(js_closure_alloc(cp_stream_callback_thunk as *const u8, 1)); + let deferred = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(cp_stream_callback_thunk, 0; with_declared(0)), + 1, + )); deferred.with_mut_ptr(|deferred: *mut ClosureHeader| { js_closure_set_capture_ptr(deferred, 0, callback.get_nanbox_f64().to_bits() as i64); }); @@ -544,7 +536,10 @@ pub(crate) extern "C" fn cp_method_send( /// `Error [ERR_IPC_CHANNEL_CLOSED]` (`message: "Channel closed"`). The deferred /// closure captures the callback in slot 0 and the success flag in slot 1. pub(crate) fn cp_defer_send_callback(cb: f64, ok: bool) { - let deferred = js_closure_alloc(cp_send_callback_thunk as *const u8, 2); + let deferred = js_closure_alloc( + crate::fn_info!(cp_send_callback_thunk, 0; with_declared(0)), + 2, + ); js_closure_set_capture_ptr(deferred, 0, cb.to_bits() as i64); let flag = if ok { TAG_TRUE_F64 } else { TAG_FALSE_F64 }; js_closure_set_capture_ptr(deferred, 1, flag.to_bits() as i64); @@ -602,8 +597,11 @@ pub(crate) extern "C" fn cp_method_disconnect( } cp_set_field(this, b"connected", TAG_FALSE_F64); cp_set_field(this, b"channel", TAG_NULL_F64); - js_register_closure_arity(cp_disconnect_emit_thunk as *const u8, 0); - let deferred = js_closure_alloc(cp_disconnect_emit_thunk as *const u8, 1); + + let deferred = js_closure_alloc( + crate::fn_info!(cp_disconnect_emit_thunk, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(deferred, 0, this.to_bits() as i64); crate::timer::js_set_immediate_callback(deferred as i64); cp_undefined() diff --git a/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs b/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs index f87d5edf34..6761f1ac97 100644 --- a/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs +++ b/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs @@ -58,9 +58,6 @@ fn child_dispatch_forwards_relocated_receiver_and_arguments_to_stream_listeners( } fn dispatch_after_listener_relocation(shared_stream_listener: bool) { - cp_register_arities(); - js_register_closure_arity(relocate as *const u8, 1); - js_register_closure_arity(observe as *const u8, 1); let scope = crate::gc::RuntimeHandleScope::new(); let source = scope.root_nanbox_f64(cp_box_ptr(crate::object::js_object_alloc(0, 0).cast())); let destination = @@ -69,10 +66,11 @@ fn dispatch_after_listener_relocation(shared_stream_listener: bool) { let moved_argument = scope.root_nanbox_f64(cp_box_ptr(crate::object::js_object_alloc(0, 0).cast())); let first = scope.root_nanbox_f64(cp_box_ptr( - js_closure_alloc(relocate as *const u8, 4).cast(), + js_closure_alloc(crate::fn_info!(relocate, 1; with_declared(1)), 4).cast(), + )); + let second = scope.root_nanbox_f64(cp_box_ptr( + js_closure_alloc(crate::fn_info!(observe, 1; with_declared(1)), 0).cast(), )); - let second = - scope.root_nanbox_f64(cp_box_ptr(js_closure_alloc(observe as *const u8, 0).cast())); let event = scope.root_nanbox_f64(cp_box_string("end")); for target in [&source, &destination] { if shared_stream_listener { diff --git a/crates/perry-runtime/src/child_process/exec.rs b/crates/perry-runtime/src/child_process/exec.rs index 2aeca9dea7..9994faa3f6 100644 --- a/crates/perry-runtime/src/child_process/exec.rs +++ b/crates/perry-runtime/src/child_process/exec.rs @@ -8,8 +8,7 @@ use sync_run::{ }; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, ClosureHeader, }; use crate::object::{js_object_set_field_by_name, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; @@ -497,8 +496,11 @@ fn cp_promisified_run( // promisify(exec)/promisify(execFile) yield string stdout/stderr (utf8). let mode = cp_read_output_mode(opts, true); let promise = crate::promise::js_promise_new(); - js_register_closure_arity(cp_promise_settle_cb as *const u8, 3); - let cb = js_closure_alloc(cp_promise_settle_cb as *const u8, 1); + + let cb = js_closure_alloc( + crate::fn_info!(cp_promise_settle_cb, 3; with_declared(3)), + 1, + ); js_closure_set_capture_ptr(cb, 0, cp_box_ptr(promise as *const u8).to_bits() as i64); let cb_val = crate::value::js_nanbox_pointer(cb as i64); let child = reactor::cp_exec_async( @@ -583,12 +585,10 @@ extern "C" fn cp_promisified_exec_file( /// can't reproduce; `util_promisify::js_util_promisify` detects the bound /// export and delegates here. #1857. pub(crate) fn make_promisified_child_process(method: &str) -> f64 { - let func: *const u8 = if method == "execFile" { - js_register_closure_arity(cp_promisified_exec_file as *const u8, 2); - cp_promisified_exec_file as *const u8 + let func = if method == "execFile" { + crate::fn_info!(cp_promisified_exec_file, 2; with_declared(2)) } else { - js_register_closure_arity(cp_promisified_exec as *const u8, 2); - cp_promisified_exec as *const u8 + crate::fn_info!(cp_promisified_exec, 2; with_declared(2)) }; let closure = js_closure_alloc(func, 0); crate::value::js_nanbox_pointer(closure as i64) diff --git a/crates/perry-runtime/src/child_process/failed_spawn.rs b/crates/perry-runtime/src/child_process/failed_spawn.rs index 0679ea9d77..f1ea4b3904 100644 --- a/crates/perry-runtime/src/child_process/failed_spawn.rs +++ b/crates/perry-runtime/src/child_process/failed_spawn.rs @@ -12,7 +12,8 @@ pub(super) extern "C" fn emit_error_then_close( let scope = crate::gc::RuntimeHandleScope::new(); let cp = scope.root_nanbox_f64(cp_this(this, closure)); reactor::cp_emit_spawn_error(closure, this); - let close = crate::closure::js_closure_alloc(reactor::cp_emit_spawn_close as *const u8, 1); + let close = + crate::closure::js_closure_alloc(crate::fn_info!(reactor::cp_emit_spawn_close, 0), 1); crate::closure::js_closure_set_capture_ptr(close, 0, cp.get_nanbox_f64().to_bits() as i64); crate::timer::js_set_timeout_callback(close as i64, 1.0); cp_undefined() @@ -57,7 +58,6 @@ mod tests { #[test] fn failed_output_retains_end_and_closed_state() { - cp_register_arities(); let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(cp_build_readable()); for _ in 0..2 { diff --git a/crates/perry-runtime/src/child_process/fork.rs b/crates/perry-runtime/src/child_process/fork.rs index 9b99218d02..ec0656a423 100644 --- a/crates/perry-runtime/src/child_process/fork.rs +++ b/crates/perry-runtime/src/child_process/fork.rs @@ -25,10 +25,6 @@ use std::time::Duration; /// codegen boundary string-coerces a URL module path before calling this. #[no_mangle] pub extern "C" fn js_child_process_fork(module_ptr: i64, args_ptr: i64, opts_ptr: i64) -> f64 { - cp_register_arities(); - - reactor::cp_register_reactor_arities(); - let module_raw = unsafe { cp_read_string_header(module_ptr) }; if module_raw.contains('\0') { crate::fs::validate::throw_type_error_with_code( @@ -127,22 +123,43 @@ pub extern "C" fn js_child_process_fork(module_ptr: i64, args_ptr: i64, opts_ptr } let cp_methods: [(&str, CpFn); 13] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("prependListener", cp_cast2(cp_method_on)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("off", cp_cast2(cp_method_remove_listener)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "prependListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), ( "removeAllListeners", - cp_cast1(cp_method_remove_all_listeners), + crate::fn_info!(cp_method_remove_all_listeners, 1; with_declared(1)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), + ("kill", crate::fn_info!(cp_method_kill, 1; with_declared(1))), + ("ref", crate::fn_info!(cp_method_ref, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_unref, 0; with_declared(0)), + ), + ( + "send", + crate::fn_info!(cp_method_send, 4; with_declared(4), with_length(4)), + ), + ( + "disconnect", + crate::fn_info!(cp_method_disconnect, 0; with_declared(0)), ), - ("emit", cp_cast2(cp_method_emit)), - ("kill", cp_cast1(cp_method_kill)), - ("ref", cp_cast0(cp_method_ref)), - ("unref", cp_cast0(cp_method_unref)), - ("send", cp_cast4(cp_method_send)), - ("disconnect", cp_cast0(cp_method_disconnect)), ]; // Distinct shape band from spawn's ChildProcess (which carries no send/disconnect). let cp_obj = cp_build_object(&cp_methods, CP_SHAPE_ID + 0x20 + cp_methods.len() as u32); @@ -253,8 +270,10 @@ pub extern "C" fn js_child_process_fork(module_ptr: i64, args_ptr: i64, opts_ptr ], ); cp_set_field(cp, b"__cpError", err); - let emit_closure = - crate::closure::js_closure_alloc(reactor::cp_emit_spawn_error as *const u8, 1); + let emit_closure = crate::closure::js_closure_alloc( + crate::fn_info!(reactor::cp_emit_spawn_error, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_ptr(emit_closure, 0, cp.to_bits() as i64); crate::timer::js_set_immediate_callback(emit_closure as i64); } @@ -318,8 +337,11 @@ fn fork_launch( cp_set_field(cp, b"connected", TAG_TRUE_F64); let channel = cp_build_object( &[ - ("ref", cp_cast0(cp_method_this0)), - ("unref", cp_cast0(cp_method_this0)), + ("ref", crate::fn_info!(cp_method_this0, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), ], CP_SHAPE_ID + 0x40, ); @@ -367,8 +389,11 @@ fn fork_launch( cp_set_field(cp, b"connected", TAG_TRUE_F64); let channel = cp_build_object( &[ - ("ref", cp_cast0(cp_method_this0)), - ("unref", cp_cast0(cp_method_this0)), + ("ref", crate::fn_info!(cp_method_this0, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_this0, 0; with_declared(0)), + ), ], CP_SHAPE_ID + 0x40, ); diff --git a/crates/perry-runtime/src/child_process/mod.rs b/crates/perry-runtime/src/child_process/mod.rs index 9be2e8fa65..d7a0ae0da0 100644 --- a/crates/perry-runtime/src/child_process/mod.rs +++ b/crates/perry-runtime/src/child_process/mod.rs @@ -95,15 +95,13 @@ pub(crate) use emitter::{ cp_method_kill, cp_method_on, cp_method_pipe, cp_method_read, cp_method_ref, cp_method_remove_all_listeners, cp_method_remove_listener, cp_method_send, cp_method_set_encoding, cp_method_stdin_end, cp_method_stdin_write, cp_method_this0, - cp_method_this1, cp_method_unref, cp_readable_end, cp_register, cp_send_callback_thunk, - cp_stream_callback_thunk, js_fork_child, + cp_method_unref, cp_readable_end, cp_register, js_fork_child, }; // builder.rs — heap object construction + shape ids. pub(crate) use builder::{ cp_build_object, cp_build_readable, cp_build_unstarted_child_process, cp_build_writable, - cp_cast0, cp_cast1, cp_cast2, cp_cast4, cp_install_dispose, cp_register_arities, CpFn, - CP_SHAPE_ID, + cp_install_dispose, CpFn, CP_SHAPE_ID, }; // options.rs — command option application (cwd/env/uid/gid/argv0/detached/stdio). diff --git a/crates/perry-runtime/src/child_process/reactor.rs b/crates/perry-runtime/src/child_process/reactor.rs index c8bdd961e9..92735c9b3b 100644 --- a/crates/perry-runtime/src/child_process/reactor.rs +++ b/crates/perry-runtime/src/child_process/reactor.rs @@ -65,7 +65,7 @@ use streams::{ }; mod exec_async; pub(super) use exec_async::{cp_defer_exec_callback, cp_exec_async}; -use exec_async::{cp_exec_accumulate, cp_exec_cb_thunk, cp_exec_fire_close}; +use exec_async::{cp_exec_accumulate, cp_exec_fire_close}; /// Monotonic registry key for live children. static CP_NEXT_LIVE_ID: AtomicU64 = AtomicU64::new(1); @@ -640,7 +640,7 @@ pub(super) fn cp_install_abort_signal(handle: u64, signal: Option, opts_val return; }; - let listener = js_closure_alloc(cp_abort_listener as *const u8, 1); + let listener = js_closure_alloc(crate::fn_info!(cp_abort_listener, 0; with_declared(0)), 1); js_closure_set_capture_ptr(listener, 0, handle as i64); let listener_val = cp_box_ptr(listener as *const u8); let kill_signal = cp_read_abort_kill_signal(opts_val); @@ -879,9 +879,6 @@ pub extern "C" fn js_child_process_spawn_streams( args_ptr: i64, opts_ptr: i64, ) -> f64 { - cp_register_arities(); - cp_register_reactor_arities(); - let (cmd_str, arg_strs) = unsafe { ( cp_read_string_header(cmd_ptr), @@ -918,20 +915,35 @@ pub extern "C" fn js_child_process_spawn_streams( } let cp_methods: [(&str, CpFn); 11] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("prependListener", cp_cast2(cp_method_on)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("off", cp_cast2(cp_method_remove_listener)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "prependListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), ( "removeAllListeners", - cp_cast1(cp_method_remove_all_listeners), + crate::fn_info!(cp_method_remove_all_listeners, 1; with_declared(1)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), + ("kill", crate::fn_info!(cp_method_kill, 1; with_declared(1))), + ("ref", crate::fn_info!(cp_method_ref, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_unref, 0; with_declared(0)), ), - ("emit", cp_cast2(cp_method_emit)), - ("kill", cp_cast1(cp_method_kill)), - ("ref", cp_cast0(cp_method_ref)), - ("unref", cp_cast0(cp_method_unref)), ]; let cp_obj = cp_build_object(&cp_methods, CP_SHAPE_ID + cp_methods.len() as u32); let cp = cp_box_ptr(cp_obj as *const u8); @@ -1025,7 +1037,7 @@ pub extern "C" fn js_child_process_spawn_streams( cp_set_field(cp, b"__cpSpawnErrno", super::cp_errno_number(code)); cp_set_field(cp, b"exitCode", super::cp_errno_number(code)); let emit_closure = crate::closure::js_closure_alloc( - super::failed_spawn::emit_error_then_close as *const u8, + crate::fn_info!(super::failed_spawn::emit_error_then_close, 0; with_declared(0)), 1, ); crate::closure::js_closure_set_capture_ptr(emit_closure, 0, cp.to_bits() as i64); @@ -1064,17 +1076,6 @@ pub(super) extern "C" fn cp_emit_spawn_close( cp_undefined() } -pub(super) fn cp_register_reactor_arities() { - crate::closure::js_register_closure_arity( - super::failed_spawn::emit_error_then_close as *const u8, - 0, - ); - crate::closure::js_register_closure_arity(cp_emit_spawn_error as *const u8, 0); - crate::closure::js_register_closure_arity(cp_emit_spawn_close as *const u8, 0); - crate::closure::js_register_closure_arity(cp_abort_listener as *const u8, 0); - crate::closure::js_register_closure_arity(cp_exec_cb_thunk as *const u8, 0); -} - // ============================================================================ // Main-thread pump // ============================================================================ diff --git a/crates/perry-runtime/src/child_process/reactor/exec_async.rs b/crates/perry-runtime/src/child_process/reactor/exec_async.rs index 27bc4b2ef7..cf2e5f351a 100644 --- a/crates/perry-runtime/src/child_process/reactor/exec_async.rs +++ b/crates/perry-runtime/src/child_process/reactor/exec_async.rs @@ -26,9 +26,6 @@ pub(in crate::child_process) fn cp_exec_async( run_options: CpRunOptions, mode: CpOutput, ) -> f64 { - cp_register_arities(); - cp_register_reactor_arities(); - // The program actually launched (`sh` for exec, the file for execFile) — // Node's spawn-failure error keys `syscall`/`path`/message off this, not // off the display command string. @@ -54,20 +51,35 @@ pub(in crate::child_process) fn cp_exec_async( let stdout_obj = cp_build_readable(); let stderr_obj = cp_build_readable(); let methods: [(&str, CpFn); 11] = [ - ("on", cp_cast2(cp_method_on)), - ("once", cp_cast2(cp_method_on)), - ("addListener", cp_cast2(cp_method_on)), - ("prependListener", cp_cast2(cp_method_on)), - ("removeListener", cp_cast2(cp_method_remove_listener)), - ("off", cp_cast2(cp_method_remove_listener)), + ("on", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ("once", crate::fn_info!(cp_method_on, 2; with_declared(2))), + ( + "addListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "prependListener", + crate::fn_info!(cp_method_on, 2; with_declared(2)), + ), + ( + "removeListener", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(cp_method_remove_listener, 2; with_declared(2)), + ), ( "removeAllListeners", - cp_cast1(cp_method_remove_all_listeners), + crate::fn_info!(cp_method_remove_all_listeners, 1; with_declared(1)), + ), + ("emit", crate::fn_info!(cp_method_emit, 2; with_declared(2))), + ("kill", crate::fn_info!(cp_method_kill, 1; with_declared(1))), + ("ref", crate::fn_info!(cp_method_ref, 0; with_declared(0))), + ( + "unref", + crate::fn_info!(cp_method_unref, 0; with_declared(0)), ), - ("emit", cp_cast2(cp_method_emit)), - ("kill", cp_cast1(cp_method_kill)), - ("ref", cp_cast0(cp_method_ref)), - ("unref", cp_cast0(cp_method_unref)), ]; let cp = cp_box_ptr(cp_build_object(&methods, CP_SHAPE_ID + methods.len() as u32) as *const u8); cp_set_field(cp, b"stdout", stdout_obj); @@ -305,8 +317,7 @@ pub(in crate::child_process) fn cp_defer_exec_callback( stdout: f64, stderr: f64, ) { - cp_register_reactor_arities(); - let deferred = js_closure_alloc(cp_exec_cb_thunk as *const u8, 4); + let deferred = js_closure_alloc(crate::fn_info!(cp_exec_cb_thunk, 0; with_declared(0)), 4); js_closure_set_capture_ptr(deferred, 0, cb_val.to_bits() as i64); js_closure_set_capture_ptr(deferred, 1, err.to_bits() as i64); js_closure_set_capture_ptr(deferred, 2, stdout.to_bits() as i64); diff --git a/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs b/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs index cfb6fb1b52..16116855fd 100644 --- a/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs +++ b/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs @@ -74,8 +74,7 @@ extern "C" fn data( } fn register(target: f64, name: &str, id: usize) { - crate::closure::js_register_closure_arity(event as *const u8, 2); - let f = crate::closure::js_closure_alloc(event as *const u8, 1); + let f = crate::closure::js_closure_alloc(crate::fn_info!(event, 2; with_declared(2)), 1); crate::closure::js_closure_set_capture_f64(f, 0, id as f64); super::super::emitter::cp_register(target, cp_box_string(name), cp_box_ptr(f.cast())); } @@ -122,8 +121,7 @@ fn spawn_fixture(mode: &str, timeout: Option) -> f64 { register(cp, name, id); } register(cp_get_field(cp, b"stdin"), "drain", 4); - crate::closure::js_register_closure_arity(data as *const u8, 1); - let f = crate::closure::js_closure_alloc(data as *const u8, 0); + let f = crate::closure::js_closure_alloc(crate::fn_info!(data, 1; with_declared(1)), 0); super::super::emitter::cp_register( cp_get_field(cp, b"stdout"), cp_box_string("data"), diff --git a/crates/perry-runtime/src/closure/alloc.rs b/crates/perry-runtime/src/closure/alloc.rs index 4f0568f730..f6a0efa024 100644 --- a/crates/perry-runtime/src/closure/alloc.rs +++ b/crates/perry-runtime/src/closure/alloc.rs @@ -4,7 +4,7 @@ use super::*; use std::cell::RefCell; crate::perry_thread_local! { - /// Singleton cache keyed by `func_ptr` for non-capturing closures. + /// Singleton cache keyed by body info for non-capturing closures. /// See `js_closure_alloc_singleton` and `scan_singleton_closure_roots_mut`. /// Pointer-keyed; uses `PtrHasher` (Fibonacci-multiplicative) to /// skip SipHash's per-byte cost — the function-pointer keys never @@ -12,7 +12,7 @@ crate::perry_thread_local! { static SINGLETON_CLOSURES: RefCell> = RefCell::new(crate::fast_hash::new_ptr_hash_map()); - /// Per-`func_ptr` small-LRU cache. Each value holds up to + /// Per-body small-LRU cache. Each value holds up to /// `MAX_CAPTURED_CLOSURE_SLOTS` (captures-bits, ClosureHeader) /// pairs. Multiple slots are critical for the parallel-instance /// async-await pattern (e.g. `Promise.all` of N async closures @@ -219,8 +219,13 @@ mod captured_closure_cache_tests { } test_clear_singleton_closure_caches(); let _guard = ClearCaches; - extern "C" fn literal() {} - let func = literal as *const u8; + extern "C" fn literal(_: *const ClosureHeader, _: crate::closure::JsThis) -> f64 { + 0.0 + } + static LITERAL: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + literal as crate::codegen_abi::JsBody0, + ); + let func: *const crate::closure::JsFunctionInfo = &LITERAL; let key = func as usize; let root_count = || { let mut count = 0; @@ -365,14 +370,32 @@ pub struct ClosureHeader { /// The function object's ShapeId (`closure::shape`): a /// `ShapeObjectKind::Function` / `FunctionDictionary` id in the exotic band. pub shape_id: u32, - /// Function pointer (the actual compiled function). - pub func_ptr: *const u8, + /// The body's static [`JsFunctionInfo`](crate::closure::JsFunctionInfo): + /// its code address and every fact a caller needs about the body. + pub info: *const crate::closure::JsFunctionInfo, /// The function object's own-property bag (`closure::props`, D1): null /// until the first own property, then a traced, rewritten raw-pointer /// child edge (`gc::layout`'s `ClosureCaptures` arm). pub props: *mut crate::object::ObjectHeader, } +impl ClosureHeader { + /// The body's code address (a sentinel for bound values), or null when + /// `self` is not a live function object with a body. + /// + /// The info word is dereferenced, so the cell is proven first + /// ([`get_valid_func_ptr`](crate::closure::get_valid_func_ptr)): callers + /// compare an arbitrary receiver's code against a known body, and the + /// word at this offset of any other heap cell is not an info. + /// + /// # Safety + /// `self` points into mapped memory (the proof reads its GC header). + #[inline(always)] + pub unsafe fn code(&self) -> *const u8 { + crate::closure::get_valid_func_ptr(self) + } +} + const _: () = { assert!(std::mem::offset_of!(ClosureHeader, capture_count) == 0); assert!( @@ -381,8 +404,7 @@ const _: () = { #[cfg(target_pointer_width = "64")] { assert!( - std::mem::offset_of!(ClosureHeader, func_ptr) - == crate::codegen_abi::CLOSURE_FUNC_PTR_OFFSET + std::mem::offset_of!(ClosureHeader, info) == crate::codegen_abi::CLOSURE_INFO_OFFSET ); assert!( std::mem::offset_of!(ClosureHeader, props) == crate::codegen_abi::CLOSURE_PROPS_OFFSET @@ -448,14 +470,14 @@ fn closure_alloc_storage_no_collect(actual_count: usize) -> Option<*mut u8> { /// codegen and never reach this entry. #[no_mangle] pub extern "C" fn js_closure_alloc_init( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, capture_count: u32, captures_ptr: *const u64, ) -> *mut ClosureHeader { crate::promise::bump(&CLOSURE_ALLOC_COUNT); let actual_count = real_capture_count(capture_count) as usize; if actual_count == 0 || captures_ptr.is_null() { - return js_closure_alloc(func_ptr, capture_count); + return js_closure_alloc(info, capture_count); } // The no-collect arm keeps `captures_ptr`'s VALUES valid raw: nothing on // the heap moved. The collecting fallback may have moved what those bits @@ -463,11 +485,11 @@ pub extern "C" fn js_closure_alloc_init( // per-setter path's contract, kept by taking that path. // Resolved BEFORE the storage exists: minting a base shape touches only // the shape table, never the GC heap. - let shape_id = super::shape::birth_shape_for_body(func_ptr); + let shape_id = super::shape::birth_shape_for_body(info); let raw = match closure_alloc_storage_no_collect(actual_count) { Some(raw) => raw, None => { - let closure = js_closure_alloc(func_ptr, capture_count); + let closure = js_closure_alloc(info, capture_count); for i in 0..actual_count { js_closure_set_capture_bits(closure, i as u32, unsafe { *captures_ptr.add(i) }); } @@ -478,7 +500,7 @@ pub extern "C" fn js_closure_alloc_init( unsafe { (*ptr).capture_count = capture_count; (*ptr).shape_id = shape_id; - (*ptr).func_ptr = func_ptr; + (*ptr).info = info; // GC_STORE_AUDIT(INIT): fresh closure, null props edge. (*ptr).props = std::ptr::null_mut(); let slots = closure_capture_slots_mut(ptr); @@ -606,10 +628,13 @@ pub(crate) unsafe fn gc_capture_slot_range( /// (`real_capture_count`). /// Returns pointer to ClosureHeader #[no_mangle] -pub extern "C" fn js_closure_alloc(func_ptr: *const u8, capture_count: u32) -> *mut ClosureHeader { +pub extern "C" fn js_closure_alloc( + info: *const crate::closure::JsFunctionInfo, + capture_count: u32, +) -> *mut ClosureHeader { crate::promise::bump(&CLOSURE_ALLOC_COUNT); let actual_count = real_capture_count(capture_count) as usize; - let shape_id = super::shape::birth_shape_for_body(func_ptr); + let shape_id = super::shape::birth_shape_for_body(info); let raw = closure_alloc_storage(actual_count); let ptr = raw as *mut ClosureHeader; @@ -617,7 +642,7 @@ pub extern "C" fn js_closure_alloc(func_ptr: *const u8, capture_count: u32) -> * unsafe { (*ptr).capture_count = capture_count; // Preserve flag in high bit (*ptr).shape_id = shape_id; - (*ptr).func_ptr = func_ptr; + (*ptr).info = info; // GC_STORE_AUDIT(INIT): fresh closure, null props edge. (*ptr).props = std::ptr::null_mut(); // #7154: a fresh closure's capture slots are raw recycled arena bytes. @@ -645,7 +670,7 @@ pub static CLOSURE_CAP_SINGLETON_MISS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); /// Singleton-cached closure allocation for non-capturing closures and FuncRef -/// wrappers. The same `func_ptr` always yields the SAME ClosureHeader, so a +/// wrappers. The same body info always yields the SAME ClosureHeader, so a /// hot loop like `arr.filter(x => x.kind === 'foo')` doesn't allocate (and /// trigger GC against) a fresh closure on every iteration. /// @@ -659,27 +684,30 @@ pub static CLOSURE_CAP_SINGLETON_MISS: std::sync::atomic::AtomicU64 = /// to allocating fresh. The closure is GC-rooted by the singleton table's /// mutable scanner so it stays live across collections. #[no_mangle] -pub extern "C" fn js_closure_alloc_singleton(func_ptr: *const u8) -> *mut ClosureHeader { +pub extern "C" fn js_closure_alloc_singleton( + info: *const crate::closure::JsFunctionInfo, +) -> *mut ClosureHeader { // Fast path: already cached. Drop the borrow before any potential // alloc so allocation/GC can re-enter SINGLETON_CLOSURES if needed. - if let Some(cached) = SINGLETON_CLOSURES.with(|s| s.borrow().get(&(func_ptr as usize)).copied()) - { + if let Some(cached) = SINGLETON_CLOSURES.with(|s| s.borrow().get(&(info as usize)).copied()) { return cached; } - let allocated = js_closure_alloc(func_ptr, 0); + let allocated = js_closure_alloc(info, 0); SINGLETON_CLOSURES.with(|s| { - s.borrow_mut().insert(func_ptr as usize, allocated); + s.borrow_mut().insert(info as usize, allocated); }); crate::gc::runtime_write_barrier_root_heap_word(allocated as u64); allocated } /// The no-capture singleton `js_closure_alloc_singleton` already minted for -/// `func_ptr`, without minting one. Lets a caller that decorates the singleton +/// `info`, without minting one. Lets a caller that decorates the singleton /// (name, length) do so once rather than on every lookup. The table's slot is /// GC-rewritten, so a value read here is current until the next allocation. -pub(crate) fn singleton_closure_if_cached(func_ptr: *const u8) -> Option<*mut ClosureHeader> { - SINGLETON_CLOSURES.with(|s| s.borrow().get(&(func_ptr as usize)).copied()) +pub(crate) fn singleton_closure_if_cached( + info: *const crate::closure::JsFunctionInfo, +) -> Option<*mut ClosureHeader> { + SINGLETON_CLOSURES.with(|s| s.borrow().get(&(info as usize)).copied()) } /// Mutable GC scanner for singleton closure caches. @@ -725,21 +753,24 @@ pub(crate) fn test_clear_singleton_closure_caches() { } #[cfg(test)] -pub(crate) fn test_seed_singleton_closure_cache(func_ptr: *const u8, closure: *mut ClosureHeader) { +pub(crate) fn test_seed_singleton_closure_cache( + info: *const crate::closure::JsFunctionInfo, + closure: *mut ClosureHeader, +) { SINGLETON_CLOSURES.with(|s| { - s.borrow_mut().insert(func_ptr as usize, closure); + s.borrow_mut().insert(info as usize, closure); }); } #[cfg(test)] pub(crate) fn test_seed_captured_singleton_closure_cache( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, capture_key: Vec, closure: *mut ClosureHeader, ) { SINGLETON_CAPTURED_CLOSURES.with(|s| { s.borrow_mut() - .entry(func_ptr as usize) + .entry(info as usize) .or_insert_with(CapturedClosureCache::new) .insert(capture_fingerprint(&capture_key), capture_key, closure); }); @@ -747,18 +778,18 @@ pub(crate) fn test_seed_captured_singleton_closure_cache( #[cfg(test)] pub(crate) fn test_singleton_closure_cache_entry( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, ) -> Option<*mut ClosureHeader> { - SINGLETON_CLOSURES.with(|s| s.borrow().get(&(func_ptr as usize)).copied()) + SINGLETON_CLOSURES.with(|s| s.borrow().get(&(info as usize)).copied()) } #[cfg(test)] pub(crate) fn test_captured_singleton_closure_cache_entries( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, ) -> Vec<(Vec, *mut ClosureHeader)> { SINGLETON_CAPTURED_CLOSURES.with(|s| { s.borrow() - .get(&(func_ptr as usize)) + .get(&(info as usize)) .map(|cache| { cache .entries @@ -792,11 +823,11 @@ const _: () = assert!( "hint_indices_plus_one stores an entry index plus one in a u8" ); -/// Per-`func_ptr` cache miss-streak counter for the adaptive bypass. +/// Per-body cache miss-streak counter for the adaptive bypass. /// Closures whose captures change every call (per-call boxes for /// `__step` / `__gen_state`, etc.) miss 100% of the time on the /// captures-tuple cache; after `CAPTURED_MISS_STREAK_DISABLE` consecutive -/// misses we mark the `func_ptr` as "cache-disabled" and route it to a +/// misses we mark the body as "cache-disabled" and route it to a /// direct `js_closure_alloc + memcpy` with no HashMap touch, no Vec scan, /// no Vec::to_vec capture-tuple allocation. Disabling drops the cache and /// its roots; bypass remains permanent for this literal. Hits before the @@ -809,7 +840,7 @@ crate::perry_thread_local! { RefCell::new(crate::fast_hash::new_ptr_hash_map()); } -/// Per-`func_ptr` bounded LRU cache for closures with captures. Exact capture +/// Per-body bounded LRU cache for closures with captures. Exact capture /// bits select a cached closure; misses allocate a fresh closure. After a /// sustained miss streak, drop the cache and bypass it for this literal. /// @@ -817,7 +848,7 @@ crate::perry_thread_local! { /// matching the layout `js_closure_set_capture_f64` writes. #[no_mangle] pub extern "C" fn js_closure_alloc_with_captures_singleton( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, capture_count: u32, captures_ptr: *const u64, ) -> *mut ClosureHeader { @@ -829,13 +860,13 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( }; let fingerprint = capture_fingerprint(captures_slice); - // Adaptive bypass: if this func_ptr has missed the cache N times in + // Adaptive bypass: if this body has missed the cache N times in // a row, skip the cache entirely. Async-step closures (`__step` / // `next` / `throw` / `__then_v` / `__then_e`) all capture a fresh // box pointer per invocation so they miss 100% of the time; the // bypass turns cache-lookup overhead into a direct allocation + memcpy. let streak = - CAPTURED_MISS_STREAK.with(|m| m.borrow().get(&(func_ptr as usize)).copied().unwrap_or(0)); + CAPTURED_MISS_STREAK.with(|m| m.borrow().get(&(info as usize)).copied().unwrap_or(0)); if streak == CAPTURED_DISABLED_SENTINEL { crate::promise::bump(&CLOSURE_CAP_SINGLETON_MISS); let capture_scope = crate::gc::RuntimeHandleScope::new(); @@ -843,7 +874,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( .iter() .map(|bits| capture_scope.root_heap_word_u64(*bits)) .collect(); - let allocated = js_closure_alloc(func_ptr, capture_count); + let allocated = js_closure_alloc(info, capture_count); if n > 0 && !captures_ptr.is_null() { let rewritten_captures: Vec = capture_handles .iter() @@ -866,14 +897,14 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( // eviction preserves the same least-recently-used policy. if let Some(cached) = SINGLETON_CAPTURED_CLOSURES.with(|s| { let mut s = s.borrow_mut(); - s.get_mut(&(func_ptr as usize)) + s.get_mut(&(info as usize)) .and_then(|cache| cache.lookup(fingerprint, captures_slice)) }) { crate::promise::bump(&CLOSURE_CAP_SINGLETON_HIT); // Cache hit — reset the streak so a workload that briefly // thrashed then settled into stable captures gets caching back. CAPTURED_MISS_STREAK.with(|m| { - m.borrow_mut().insert(func_ptr as usize, 0); + m.borrow_mut().insert(info as usize, 0); }); return cached; } @@ -886,7 +917,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( .iter() .map(|bits| capture_scope.root_heap_word_u64(*bits)) .collect(); - let allocated = js_closure_alloc(func_ptr, capture_count); + let allocated = js_closure_alloc(info, capture_count); let rewritten_captures: Vec = capture_handles .iter() .map(|handle| handle.get_heap_word_u64()) @@ -905,7 +936,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( } SINGLETON_CAPTURED_CLOSURES.with(|s| { let mut s = s.borrow_mut(); - s.entry(func_ptr as usize) + s.entry(info as usize) .or_insert_with(CapturedClosureCache::new) .insert( capture_fingerprint(&rewritten_captures), @@ -917,7 +948,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( // hit the threshold. CAPTURED_MISS_STREAK.with(|m| { let mut m = m.borrow_mut(); - let entry = m.entry(func_ptr as usize).or_insert(0); + let entry = m.entry(info as usize).or_insert(0); if *entry < CAPTURED_DISABLED_SENTINEL - 1 { *entry += 1; if *entry >= CAPTURED_MISS_STREAK_DISABLE { @@ -926,7 +957,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( // hints, and removes every GC root owned by this literal. // The returned closure remains owned by the caller. SINGLETON_CAPTURED_CLOSURES.with(|s| { - s.borrow_mut().remove(&(func_ptr as usize)); + s.borrow_mut().remove(&(info as usize)); }); } } @@ -937,7 +968,7 @@ pub extern "C" fn js_closure_alloc_with_captures_singleton( /// Get the function pointer from a closure #[no_mangle] pub extern "C" fn js_closure_get_func(closure: *const ClosureHeader) -> *const u8 { - unsafe { (*closure).func_ptr } + crate::closure::get_valid_func_ptr(closure) } /// Get a captured value (as f64) by index diff --git a/crates/perry-runtime/src/closure/body_call.rs b/crates/perry-runtime/src/closure/body_call.rs index 61870b1aa1..3987f70ca1 100644 --- a/crates/perry-runtime/src/closure/body_call.rs +++ b/crates/perry-runtime/src/closure/body_call.rs @@ -58,6 +58,95 @@ macro_rules! js_body_fn_ty { }; } +/// A `&'static JsFunctionInfo` for the native JS body `body`, which declares +/// `n` JS parameters — checked: the cast to the `JsBody{n}` type fails to +/// compile for any other signature. Facts beyond the parameter count follow +/// as `JsFunctionInfo` builder calls: +/// +/// ```ignore +/// js_closure_alloc(fn_info!(my_thunk, 2), 0); +/// js_closure_alloc(fn_info!(my_rest_thunk, 1; with_rest(0)), 0); +/// js_closure_alloc(fn_info!(my_method, 1; with_length(0), with_flags(FN_BUILTIN)), 0); +/// ``` +/// +/// Each expansion is its own `static`: define one per body where the same +/// body is allocated from several places. A body declared `extern +/// "C-unwind"` takes `fn_info!(unwind body, n; ...)`; one declared C-unwind +/// only in unwinding (test) builds and plain C under `panic = "abort"` takes +/// `fn_info!(unwind_in_tests body, n; ...)`. +#[macro_export] +macro_rules! fn_info { + (@ty 0) => { $crate::codegen_abi::JsBody0<$crate::closure::ClosureHeader> }; + (@ty 1) => { $crate::codegen_abi::JsBody1<$crate::closure::ClosureHeader> }; + (@ty 2) => { $crate::codegen_abi::JsBody2<$crate::closure::ClosureHeader> }; + (@ty 3) => { $crate::codegen_abi::JsBody3<$crate::closure::ClosureHeader> }; + (@ty 4) => { $crate::codegen_abi::JsBody4<$crate::closure::ClosureHeader> }; + (@ty 5) => { $crate::codegen_abi::JsBody5<$crate::closure::ClosureHeader> }; + (@ty 6) => { $crate::codegen_abi::JsBody6<$crate::closure::ClosureHeader> }; + (@ty 7) => { $crate::codegen_abi::JsBody7<$crate::closure::ClosureHeader> }; + (@ty 8) => { $crate::codegen_abi::JsBody8<$crate::closure::ClosureHeader> }; + (@ty 9) => { $crate::codegen_abi::JsBody9<$crate::closure::ClosureHeader> }; + (@ty 10) => { $crate::codegen_abi::JsBody10<$crate::closure::ClosureHeader> }; + (@ty 11) => { $crate::codegen_abi::JsBody11<$crate::closure::ClosureHeader> }; + (@ty 12) => { $crate::codegen_abi::JsBody12<$crate::closure::ClosureHeader> }; + (@ty 13) => { $crate::codegen_abi::JsBody13<$crate::closure::ClosureHeader> }; + (@ty 14) => { $crate::codegen_abi::JsBody14<$crate::closure::ClosureHeader> }; + (@ty 15) => { $crate::codegen_abi::JsBody15<$crate::closure::ClosureHeader> }; + (@ty 16) => { $crate::codegen_abi::JsBody16<$crate::closure::ClosureHeader> }; + (@ty 17) => { $crate::codegen_abi::JsBody17<$crate::closure::ClosureHeader> }; + (@ty 18) => { $crate::codegen_abi::JsBody18<$crate::closure::ClosureHeader> }; + (@ty 19) => { $crate::codegen_abi::JsBody19<$crate::closure::ClosureHeader> }; + (@ty 20) => { $crate::codegen_abi::JsBody20<$crate::closure::ClosureHeader> }; + (@ty 21) => { $crate::codegen_abi::JsBody21<$crate::closure::ClosureHeader> }; + (@ty 22) => { $crate::codegen_abi::JsBody22<$crate::closure::ClosureHeader> }; + (@ty 23) => { $crate::codegen_abi::JsBody23<$crate::closure::ClosureHeader> }; + (@ty 24) => { $crate::codegen_abi::JsBody24<$crate::closure::ClosureHeader> }; + (@ty 25) => { $crate::codegen_abi::JsBody25<$crate::closure::ClosureHeader> }; + (@ty 26) => { $crate::codegen_abi::JsBody26<$crate::closure::ClosureHeader> }; + (@ty 27) => { $crate::codegen_abi::JsBody27<$crate::closure::ClosureHeader> }; + (@ty 28) => { $crate::codegen_abi::JsBody28<$crate::closure::ClosureHeader> }; + (@ty 29) => { $crate::codegen_abi::JsBody29<$crate::closure::ClosureHeader> }; + (@ty 30) => { $crate::codegen_abi::JsBody30<$crate::closure::ClosureHeader> }; + (@ty 31) => { $crate::codegen_abi::JsBody31<$crate::closure::ClosureHeader> }; + (@ty 32) => { $crate::codegen_abi::JsBody32<$crate::closure::ClosureHeader> }; + (@uty 0) => { $crate::codegen_abi::JsBodyUnwind0<$crate::closure::ClosureHeader> }; + (@uty 1) => { $crate::codegen_abi::JsBodyUnwind1<$crate::closure::ClosureHeader> }; + (@uty 2) => { $crate::codegen_abi::JsBodyUnwind2<$crate::closure::ClosureHeader> }; + (@uty 3) => { $crate::codegen_abi::JsBodyUnwind3<$crate::closure::ClosureHeader> }; + (@uty 4) => { $crate::codegen_abi::JsBodyUnwind4<$crate::closure::ClosureHeader> }; + (@uty 5) => { $crate::codegen_abi::JsBodyUnwind5<$crate::closure::ClosureHeader> }; + (@uty 6) => { $crate::codegen_abi::JsBodyUnwind6<$crate::closure::ClosureHeader> }; + (@uty 7) => { $crate::codegen_abi::JsBodyUnwind7<$crate::closure::ClosureHeader> }; + (@uty 8) => { $crate::codegen_abi::JsBodyUnwind8<$crate::closure::ClosureHeader> }; + (@uty 9) => { $crate::codegen_abi::JsBodyUnwind9<$crate::closure::ClosureHeader> }; + (@uty 10) => { $crate::codegen_abi::JsBodyUnwind10<$crate::closure::ClosureHeader> }; + (@uty 11) => { $crate::codegen_abi::JsBodyUnwind11<$crate::closure::ClosureHeader> }; + (@uty 12) => { $crate::codegen_abi::JsBodyUnwind12<$crate::closure::ClosureHeader> }; + (@uty 13) => { $crate::codegen_abi::JsBodyUnwind13<$crate::closure::ClosureHeader> }; + (@uty 14) => { $crate::codegen_abi::JsBodyUnwind14<$crate::closure::ClosureHeader> }; + (@uty 15) => { $crate::codegen_abi::JsBodyUnwind15<$crate::closure::ClosureHeader> }; + (@uty 16) => { $crate::codegen_abi::JsBodyUnwind16<$crate::closure::ClosureHeader> }; + (unwind_in_tests $body:path, $n:tt $(; $($m:ident($($a:expr),*)),* $(,)?)?) => {{ + #[cfg(panic = "abort")] + let info = $crate::fn_info!($body, $n $(; $($m($($a),*)),*)?); + #[cfg(not(panic = "abort"))] + let info = $crate::fn_info!(unwind $body, $n $(; $($m($($a),*)),*)?); + info + }}; + (unwind $body:path, $n:tt $(; $($m:ident($($a:expr),*)),* $(,)?)?) => {{ + static INFO: $crate::closure::JsFunctionInfo = + $crate::closure::JsFunctionInfo::of($body as $crate::fn_info!(@uty $n)) + $($(.$m($($a),*))*)?; + &INFO as *const $crate::closure::JsFunctionInfo + }}; + ($body:path, $n:tt $(; $($m:ident($($a:expr),*)),* $(,)?)?) => {{ + static INFO: $crate::closure::JsFunctionInfo = + $crate::closure::JsFunctionInfo::of($body as $crate::fn_info!(@ty $n)) + $($(.$m($($a),*))*)?; + &INFO as *const $crate::closure::JsFunctionInfo + }}; +} + /// Reinterpret a validated, non-sentinel body code pointer as a callable /// taking the callee, the receiver and one `f64` per token /// (`js_body_fn!(code; a, b)`). diff --git a/crates/perry-runtime/src/closure/dispatch.rs b/crates/perry-runtime/src/closure/dispatch.rs index 76e4390de4..75e79d9dfb 100644 --- a/crates/perry-runtime/src/closure/dispatch.rs +++ b/crates/perry-runtime/src/closure/dispatch.rs @@ -30,7 +30,9 @@ pub use bound::{dispatch_bound_function, dispatch_bound_method, js_function_bind pub(crate) use errors::reset_throw_not_callable_counter; pub use errors::throw_not_callable; -pub use validate::{clean_closure_ptr, dispatch_proxy_callee_or_throw, get_valid_func_ptr}; +pub use validate::{ + clean_closure_ptr, dispatch_proxy_callee_or_throw, get_valid_func_ptr, get_valid_info, +}; pub use calln::{ js_closure_call0, js_closure_call1, js_closure_call10, js_closure_call11, js_closure_call12, @@ -64,14 +66,3 @@ pub(crate) const fn arity_needs_dispatch(declared: u32, n: u32) -> bool { declared != n } } - -/// The arity to dispatch a body with: its registered arity, or on WASI its -/// real parameter count (see `registry::wasi_exact_arity`). -#[inline(always)] -pub(crate) fn dispatch_arity(func_ptr: *const u8) -> Option { - #[cfg(target_os = "wasi")] - if let Some(params) = super::registry::wasi_body_params(func_ptr) { - return Some(params); - } - lookup_closure_arity(func_ptr) -} diff --git a/crates/perry-runtime/src/closure/dispatch/bound.rs b/crates/perry-runtime/src/closure/dispatch/bound.rs index e82b4f58e9..103c9ef34a 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound.rs @@ -13,7 +13,7 @@ use super::*; /// which correctly retain synthesized native source text. pub(crate) unsafe fn bound_method_source_func_ptr(closure: *const ClosureHeader) -> Option { if closure.is_null() - || (*closure).func_ptr != BOUND_METHOD_FUNC_PTR + || (*closure).code() != BOUND_METHOD_FUNC_PTR || crate::closure::real_capture_count((*closure).capture_count) < 3 { return None; @@ -381,7 +381,7 @@ pub(crate) fn coerce_call_this(target: f64, this_arg: f64) -> f64 { if closure.is_null() || !is_closure_ptr(closure as usize) { return this_arg; } - if std::ptr::eq(unsafe { (*closure).func_ptr }, BOUND_FUNCTION_FUNC_PTR) { + if std::ptr::eq(unsafe { (*closure).code() }, BOUND_FUNCTION_FUNC_PTR) { let inner = js_closure_get_capture_f64(closure, 0); let ij = crate::value::JSValue::from_bits(inner.to_bits()); if !ij.is_pointer() { @@ -392,10 +392,11 @@ pub(crate) fn coerce_call_this(target: f64, this_arg: f64) -> f64 { } break; } - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() - || crate::builtins::function_source_for_ptr(func_ptr as usize).is_none() - || crate::closure::is_registered_strict_function(func_ptr) + let Some(info) = crate::closure::closure_info(closure) else { + return this_arg; + }; + if crate::builtins::function_source_for_ptr(info.code as usize).is_none() + || info.flags & crate::closure::FN_STRICT != 0 { return this_arg; } @@ -497,7 +498,7 @@ unsafe fn bound_target_declared_name(target_value: f64) -> String { if target_jv.is_pointer() { let target_closure = target_jv.as_pointer::(); if !target_closure.is_null() && is_closure_ptr(target_closure as usize) { - return crate::builtins::function_name_for_ptr((*target_closure).func_ptr as usize) + return crate::builtins::function_name_for_ptr((*target_closure).code() as usize) .unwrap_or_default(); } return String::new(); @@ -595,7 +596,7 @@ pub(crate) unsafe fn bound_function_length(closure: usize) -> Option { return None; } let c = closure as *const ClosureHeader; - if (*c).func_ptr != BOUND_FUNCTION_FUNC_PTR + if (*c).code() != BOUND_FUNCTION_FUNC_PTR || crate::closure::real_capture_count((*c).capture_count) < BOUND_FUNCTION_CAPTURES { return None; @@ -783,7 +784,10 @@ pub unsafe extern "C" fn js_function_bind( // Allocate the bound closure with 5 capture slots: target, bound this, // partial-args array, the `.name` snapshot above, and the bound length. - let bound = crate::closure::js_closure_alloc(BOUND_FUNCTION_FUNC_PTR, BOUND_FUNCTION_CAPTURES); + let bound = crate::closure::js_closure_alloc( + &crate::closure::BOUND_FUNCTION_INFO, + BOUND_FUNCTION_CAPTURES, + ); let bound_h = scope.root_raw_mut_ptr(bound as *mut u8); let target_value = target_h.get_nanbox_f64(); let bound_this = this_h.get_nanbox_f64(); @@ -862,7 +866,7 @@ pub(crate) unsafe fn reify_function_method_value(receiver: f64, method: &'static receiver.get_nanbox_f64(), ); } - let closure = js_closure_alloc(BOUND_METHOD_FUNC_PTR, 3); + let closure = js_closure_alloc(&crate::closure::BOUND_METHOD_INFO, 3); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -893,10 +897,9 @@ pub(crate) unsafe fn reify_function_method_value(receiver: f64, method: &'static mod rebind_predicate_tests { use super::*; - // Distinct bodies on purpose: arrow-ness is registered per func_ptr, and - // two `extern "C"` bodies with identical machine code get folded to one - // address by the linker — which silently makes every case in this test the - // same closure body. + // Distinct bodies on purpose: two `extern "C"` bodies with identical + // machine code can be folded to one address by the linker, and the test + // wants genuinely different bodies. extern "C" fn arrow_probe( _closure: *const ClosureHeader, _this: crate::closure::JsThis, @@ -911,7 +914,15 @@ mod rebind_predicate_tests { 2.0 } - fn closure_value(body: *const u8, capture_count: u32) -> f64 { + static ARROW_PROBE: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + arrow_probe as crate::codegen_abi::JsBody0, + ) + .with_flags(crate::closure::FN_ARROW); + static METHOD_PROBE: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + method_probe as crate::codegen_abi::JsBody0, + ); + + fn closure_value(body: &'static crate::closure::JsFunctionInfo, capture_count: u32) -> f64 { let closure = crate::closure::js_closure_alloc(body, capture_count); f64::from_bits(crate::value::JSValue::pointer(closure as *mut u8).bits()) } @@ -924,9 +935,8 @@ mod rebind_predicate_tests { #[test] fn the_predicate_agrees_with_what_the_rebind_actually_does() { let receiver = f64::from_bits(crate::value::TAG_UNDEFINED); - let method_body = method_probe as *const u8; - let arrow_body = arrow_probe as *const u8; - crate::closure::js_register_closure_arrow_function(arrow_body); + let method_body = &METHOD_PROBE; + let arrow_body = &ARROW_PROBE; // The one shape that clones, and the shapes that look like it but // return the target untouched. diff --git a/crates/perry-runtime/src/closure/dispatch/bound/function_method_value_tests.rs b/crates/perry-runtime/src/closure/dispatch/bound/function_method_value_tests.rs index 07b0496a25..1a245dc8e8 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound/function_method_value_tests.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound/function_method_value_tests.rs @@ -50,7 +50,7 @@ fn detached_function_has_no_inherited_function_methods() { let scope = crate::gc::RuntimeHandleScope::new(); unsafe { let function = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(detached_function_fixture as *const () as *const u8, 0) + crate::closure::js_closure_alloc(crate::fn_info!(detached_function_fixture, 0), 0) as i64, )); let ptr = (function.get_nanbox_u64() & crate::value::POINTER_MASK) as usize; diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index b0de9c1f25..ba73c8dd0c 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -19,11 +19,11 @@ macro_rules! closure_call_dispatch { /// Route one closure call with a known receiver. #[inline(always)] pub(crate) fn $dispatch(closure: *const ClosureHeader, this: JsThis $(, $a: f64)*) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { + let Some(info) = crate::closure::closure_info(closure) else { return dispatch_proxy_callee_or_throw(closure, this, &[$($a),*]); - } - match resolve_strategy(func_ptr).kind() { + }; + let func_ptr = info.code; + match resolve_strategy(info).kind() { DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, this, &[$($a),*]) }, @@ -541,11 +541,11 @@ pub(crate) fn dispatch_call_slice( /// More than 16 arguments: route as the per-arity entries do, then call the /// body through the padded ladder (`wide_call`). fn dispatch_call_wide(closure: *const ClosureHeader, this: JsThis, args: &[f64]) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { + let Some(info) = crate::closure::closure_info(closure) else { return dispatch_proxy_callee_or_throw(closure, this, args); - } - match resolve_strategy(func_ptr).kind() { + }; + let func_ptr = info.code; + match resolve_strategy(info).kind() { DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, this, args) }, DispatchKind::BoundFunction => unsafe { dispatch_bound_function(closure, args) }, DispatchKind::Rest(fixed_arity, synth) => unsafe { @@ -572,9 +572,7 @@ mod plain_call_tests { #[test] fn a_plain_call_passes_undefined_this() { - let body = observe_dynamic_this as *const u8; - let closure = crate::closure::js_closure_alloc(body, 0); - crate::closure::js_register_closure_arity(body, 1); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(observe_dynamic_this, 1), 0); // A receiver passed to an enclosing call must not leak into a plain // call: the body sees exactly the `undefined` this entry passes. diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index cd766a818b..c34e84a7c9 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -64,22 +64,23 @@ pub(crate) fn resolve_direct_func_ptr( closure: *const ClosureHeader, arity: u32, ) -> Option<*const u8> { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() - || func_ptr == BOUND_METHOD_FUNC_PTR - || func_ptr == BOUND_FUNCTION_FUNC_PTR - { - return None; - } - if lookup_closure_rest(func_ptr).is_some() { - return None; - } - if let Some(declared) = super::dispatch_arity(func_ptr) { - if super::arity_needs_dispatch(declared, arity) { - return None; + resolve_direct_info(closure, arity).map(|info| info.code) +} + +/// [`resolve_direct_func_ptr`]'s info: `Some` when every call at `arity` can +/// jump straight to the body's code. +#[inline] +pub(crate) fn resolve_direct_info( + closure: *const ClosureHeader, + arity: u32, +) -> Option<&'static crate::closure::JsFunctionInfo> { + let info = crate::closure::closure_info(closure)?; + match resolve_strategy(info).kind() { + DispatchKind::Arity(declared) if !super::arity_needs_dispatch(declared, arity) => { + Some(info) } + _ => None, } - Some(func_ptr) } /// Resolve one callback for PLAIN calls (`f(x)`) at a method boundary: the @@ -92,10 +93,11 @@ pub extern "C" fn js_closure_resolve_plain_direct_call( closure: *const ClosureHeader, arity: u32, ) -> *const u8 { - let Some(func_ptr) = resolve_direct_func_ptr(closure, arity) else { + let Some(info) = resolve_direct_info(closure, arity) else { return std::ptr::null(); }; - let Some(trusted) = super::super::registry::lookup_closure_trusted_direct(func_ptr) else { + let func_ptr = info.code; + let Some(trusted) = crate::closure::info_trusted_direct(info) else { return func_ptr; }; let actual_capture_count = unsafe { real_capture_count((*closure).capture_count) }; @@ -122,14 +124,13 @@ pub extern "C" fn js_closure_resolve_versioned_loop_direct_call( closure: *const ClosureHeader, arity: u32, ) -> *const u8 { - let Some(func_ptr) = resolve_direct_func_ptr(closure, arity) else { + let Some(info) = resolve_direct_info(closure, arity) else { return std::ptr::null(); }; - if !resolve_strategy(func_ptr).is_arrow() { + if info.flags & crate::closure::FN_ARROW == 0 { return std::ptr::null(); } - let Some(target) = super::super::registry::lookup_closure_versioned_loop_direct(func_ptr) - else { + let Some(target) = crate::closure::info_versioned_loop_direct(info) else { return std::ptr::null(); }; let actual_capture_count = unsafe { real_capture_count((*closure).capture_count) }; @@ -334,54 +335,59 @@ mod tests { value } - fn closure_for(body: *const u8) -> *const ClosureHeader { - crate::closure::js_closure_alloc(body, 0) + use crate::closure::{JsFunctionInfo, FN_ARROW}; + use crate::codegen_abi::{JsBody1, JsBody2, JsBody3}; + type C = ClosureHeader; + + const ADD3: JsFunctionInfo = JsFunctionInfo::of(add3 as JsBody3).with_flags(FN_ARROW); + static ADD3_ARROW: JsFunctionInfo = ADD3; + static ADD3_TRUSTED: JsFunctionInfo = ADD3.with_trusted_direct(trusted_add3 as *const u8, 0, 0); + static ORDINARY3: JsFunctionInfo = JsFunctionInfo::of(ordinary3 as JsBody3); + static REST_ARROW: JsFunctionInfo = JsFunctionInfo::of(rest_body as JsBody2) + .with_rest(1) + .with_flags(FN_ARROW); + static SUM2: JsFunctionInfo = JsFunctionInfo::of(sum2 as JsBody2); + static SUM2_REST: JsFunctionInfo = JsFunctionInfo::of(sum2 as JsBody2).with_rest(1); + static BOXED1: JsFunctionInfo = JsFunctionInfo::of(boxed1 as JsBody1) + .with_flags(FN_ARROW) + .with_trusted_direct(trusted_boxed1 as *const u8, 1, 1); + static VERSIONED: JsFunctionInfo = JsFunctionInfo::of(versioned_source as JsBody1) + .with_flags(FN_ARROW) + .with_versioned_loop(versioned_boxed1 as *const u8, 1, 1); + + fn closure_for(info: &'static JsFunctionInfo) -> *const ClosureHeader { + crate::closure::js_closure_alloc(info, 0) } #[test] fn exported_resolver_admits_directly_callable_bodies() { - let arrow = closure_for(add3 as *const u8); - crate::closure::js_register_closure_arity(add3 as *const u8, 3); - crate::closure::js_register_closure_arrow_function(add3 as *const u8); + let arrow = closure_for(&ADD3_ARROW); assert_eq!( js_closure_resolve_plain_direct_call(arrow, 3), add3 as *const u8 ); - crate::closure::js_register_closure_trusted_direct( - add3 as *const u8, - trusted_add3 as *const u8, - 0, - 0, - ); + let trusted = closure_for(&ADD3_TRUSTED); assert_eq!( - js_closure_resolve_plain_direct_call(arrow, 3), + js_closure_resolve_plain_direct_call(trusted, 3), trusted_add3 as *const u8 ); - assert!(js_closure_resolve_plain_direct_call(arrow, 2).is_null()); - - let ordinary = closure_for(ordinary3 as *const u8); - crate::closure::js_register_closure_arity(ordinary3 as *const u8, 3); - crate::closure::js_register_closure_trusted_direct( - ordinary3 as *const u8, - trusted_add3 as *const u8, - 0, - 0, - ); + assert!(js_closure_resolve_plain_direct_call(trusted, 2).is_null()); + // An ordinary function is as directly callable as an arrow: the - // caller passes the plain-call `undefined` receiver itself. Trusted - // clones attach to arrow bodies only, so it resolves to its public - // body. + // caller passes the plain-call `undefined` receiver itself. + let ordinary = closure_for(&ORDINARY3); assert_eq!( js_closure_resolve_plain_direct_call(ordinary, 3), ordinary3 as *const u8 ); - let rest = closure_for(rest_body as *const u8); - crate::closure::js_register_closure_rest(rest_body as *const u8, 1); - crate::closure::js_register_closure_arrow_function(rest_body as *const u8); + let rest = closure_for(&REST_ARROW); assert!(js_closure_resolve_plain_direct_call(rest, 3).is_null()); - for sentinel in [BOUND_METHOD_FUNC_PTR, BOUND_FUNCTION_FUNC_PTR] { + for sentinel in [ + &crate::closure::BOUND_METHOD_INFO, + &crate::closure::BOUND_FUNCTION_INFO, + ] { let bound = closure_for(sentinel); assert!(js_closure_resolve_plain_direct_call(bound, 3).is_null()); } @@ -389,23 +395,14 @@ mod tests { #[test] fn trusted_target_requires_the_registered_capture_layout() { - crate::closure::js_register_closure_arity(boxed1 as *const u8, 1); - crate::closure::js_register_closure_arrow_function(boxed1 as *const u8); - crate::closure::js_register_closure_trusted_direct( - boxed1 as *const u8, - trusted_boxed1 as *const u8, - 1, - 1, - ); - - let wrong_count = closure_for(boxed1 as *const u8); + let wrong_count = closure_for(&BOXED1); assert_eq!( js_closure_resolve_plain_direct_call(wrong_count, 1), boxed1 as *const u8, "a wrong capture count must retain the checked public body" ); - let non_box = crate::closure::js_closure_alloc(boxed1 as *const u8, 1); + let non_box = crate::closure::js_closure_alloc(&BOXED1, 1); crate::closure::js_closure_set_capture_bits(non_box, 0, crate::value::TAG_UNDEFINED); assert_eq!( js_closure_resolve_plain_direct_call(non_box, 1), @@ -413,7 +410,7 @@ mod tests { "a non-box payload must retain the checked public body" ); - let valid = crate::closure::js_closure_alloc(boxed1 as *const u8, 1); + let valid = crate::closure::js_closure_alloc(&BOXED1, 1); let cell = crate::r#box::js_box_alloc_bits(crate::value::TAG_UNDEFINED as i64); crate::closure::js_closure_set_box_capture_ptr(valid, 0, cell as i64); assert_eq!( @@ -425,23 +422,14 @@ mod tests { #[test] fn versioned_target_is_exact_and_fails_closed() { - crate::closure::js_register_closure_arity(versioned_source as *const u8, 1); - crate::closure::js_register_closure_arrow_function(versioned_source as *const u8); - super::super::registry::js_register_closure_versioned_loop_direct( - versioned_source as *const u8, - versioned_boxed1 as *const u8, - 1, - 1, - ); - - let wrong_count = closure_for(versioned_source as *const u8); + let wrong_count = closure_for(&VERSIONED); assert!(js_closure_resolve_versioned_loop_direct_call(wrong_count, 1).is_null()); - let non_box = crate::closure::js_closure_alloc(versioned_source as *const u8, 1); + let non_box = crate::closure::js_closure_alloc(&VERSIONED, 1); crate::closure::js_closure_set_capture_bits(non_box, 0, crate::value::TAG_UNDEFINED); assert!(js_closure_resolve_versioned_loop_direct_call(non_box, 1).is_null()); - let valid = crate::closure::js_closure_alloc(versioned_source as *const u8, 1); + let valid = crate::closure::js_closure_alloc(&VERSIONED, 1); let cell = crate::r#box::js_box_alloc_bits(crate::value::TAG_UNDEFINED as i64); crate::closure::js_closure_set_box_capture_ptr(valid, 0, cell as i64); assert_eq!( @@ -453,16 +441,15 @@ mod tests { #[test] fn a_plain_callback_resolves_and_answers_identically_to_the_slow_path() { - let c = closure_for(add3 as *const u8); + let c = closure_for(&ADD3_ARROW); let site = DirectCall3::resolve(c); // ASSERT THE SUBJECT IS LIVE. Without this the test passes just as // happily when `resolve` always answers `None` and every call falls - // back — a "fast path" nobody can prove ran (CLAUDE.md, the fourth way - // a gate cannot fail). + // back — a "fast path" nobody can prove ran. assert!( site.is_direct(), - "an unregistered, capture-less, non-bound callback must resolve to \ - a direct target -- otherwise this whole module is inert" + "a capture-less, non-bound callback must resolve to a direct target \ + -- otherwise this whole module is inert" ); assert_eq!( site.call(c, crate::closure::plain_call_receiver(), 1.0, 2.0, 3.0), @@ -476,11 +463,10 @@ mod tests { #[test] fn a_declared_arity_above_the_call_arity_is_declined() { - let c = closure_for(add3 as *const u8); - // Same body, asked for at a LOWER arity than it declares: the call - // must keep going through `js_closure_call2`, which pads with - // undefined via `dispatch_with_arity`. - crate::closure::js_register_closure_arity(add3 as *const u8, 3); + let c = closure_for(&ADD3_ARROW); + // Asked for at a LOWER arity than the body declares: the call must + // keep going through `js_closure_call2`, which pads with undefined + // via `dispatch_with_arity`. assert!( !DirectCall2::resolve(c).is_direct(), "declared arity 3 > call arity 2 must decline: a direct 2-arg call \ @@ -492,14 +478,12 @@ mod tests { #[test] fn a_rest_closure_is_declined() { - let c = closure_for(sum2 as *const u8); assert!( - DirectCall2::resolve(c).is_direct(), - "precondition: it resolves before being registered as rest" + DirectCall2::resolve(closure_for(&SUM2)).is_direct(), + "precondition: the same body resolves without a rest parameter" ); - crate::closure::js_register_closure_rest(sum2 as *const u8, 1); assert!( - !DirectCall2::resolve(c).is_direct(), + !DirectCall2::resolve(closure_for(&SUM2_REST)).is_direct(), "a rest parameter needs `dispatch_rest_bundled` to build the rest \ array; a direct call would hand the body a bare f64" ); diff --git a/crates/perry-runtime/src/closure/dispatch/validate.rs b/crates/perry-runtime/src/closure/dispatch/validate.rs index 376cab5f43..86be5178b7 100644 --- a/crates/perry-runtime/src/closure/dispatch/validate.rs +++ b/crates/perry-runtime/src/closure/dispatch/validate.rs @@ -1,5 +1,5 @@ //! Closure-pointer validation: GC-forwarding resolution (`clean_closure_ptr`) -//! and the speculation-safe `get_valid_func_ptr` gate. +//! and the speculation-safe `get_valid_info` / `get_valid_func_ptr` gate. use super::*; @@ -42,43 +42,39 @@ pub fn clean_closure_ptr(mut closure: *const ClosureHeader) -> *const ClosureHea closure } -/// Validate a closure pointer and return its func_ptr if the closure is valid. +/// Validate a closure pointer and return its body's info if the closure is +/// valid (the bound-method / bound-function sentinel infos included). /// -/// Uses `read_volatile` for type_tag + `compiler_fence` to GUARANTEE that: -/// 1. the GC header's CLOSURE type byte is checked BEFORE func_ptr is ever read -/// 2. The optimizer cannot hoist the func_ptr read before that check +/// Uses `read_volatile` for the GC type byte + `compiler_fence` to GUARANTEE +/// that the CLOSURE type byte is checked BEFORE the info word is ever read, +/// and that the optimizer cannot hoist that load above the check. /// -/// Background: `#[inline(never)]` on `is_valid_closure_ptr` is insufficient — LLVM -/// still speculatively hoists the non-volatile func_ptr load before the kind -/// check in the caller. This produces code that only checks the kind when func_ptr==0, -/// allowing non-closure heap objects (Box, BigInt structs) to bypass validation -/// and execute their data as code via `br x1` → SIGBUS. +/// Background: `#[inline(never)]` on `is_valid_closure_ptr` was +/// insufficient — LLVM still speculatively hoisted the header-word load +/// before the kind check in the caller, so non-closure heap objects +/// (Box, BigInt structs) bypassed validation and their data ran as +/// code via `br x1` → SIGBUS. /// -/// Returns null pointer if invalid (address out of range, not a closure cell, bad func_ptr). +/// Returns null if invalid (address out of range, not a live closure cell, +/// null info). #[inline(always)] -pub fn get_valid_func_ptr(closure: *const ClosureHeader) -> *const u8 { +pub fn get_valid_info(closure: *const ClosureHeader) -> *const crate::closure::JsFunctionInfo { let addr = closure as u64; if !(0x1000..0x0001_0000_0000_0000).contains(&addr) { return std::ptr::null(); } - // #5976: reject the small-handle band BEFORE the header - // kind probe. Revocable-proxy ids, Web-Fetch/zlib/net handles and - // the generic stdlib registry ids are all NaN-boxed `POINTER_TAG | ` values, not heap pointers — a real closure is always a GC allocation - // above the band (`value::addr_class`). The 0x1000 floor above let every - // one of them through, so this probe dereferenced unmapped low memory: - // `new ProxyOfClass(...)` reaching the dynamic construct path passed the - // proxy value to `is_bound_function_closure_value` → `get_valid_func_ptr`, - // which faulted at `PROXY_ID_BAND_START + id + 12` before - // `js_new_function_construct` ever got to its Proxy branch. `is_closure_ptr` - // (closure/dynamic_props.rs) has carried this guard since #1843/#4800; the - // two validators here were the remaining hole. + // #5976: reject the small-handle band BEFORE the header kind probe. + // Revocable-proxy ids, Web-Fetch/zlib/net handles and the generic stdlib + // registry ids are all NaN-boxed `POINTER_TAG | ` values, not + // heap pointers — a real closure is always a GC allocation above the band + // (`value::addr_class`). The 0x1000 floor above let every one of them + // through, so this probe dereferenced unmapped low memory. if crate::value::addr_class::is_handle_band(addr as usize) { return std::ptr::null(); } // The kind is the GC header's type byte (no payload magic): a live, - // un-evacuated closure cell. Volatile + fence keep the func_ptr load - // below from being hoisted above this check (the SIGBUS history above). + // un-evacuated closure cell. Volatile + fence keep the info load below + // from being hoisted above this check (the SIGBUS history above). let header = (addr as usize - crate::gc::GC_HEADER_SIZE) as *const crate::gc::GcHeader; let (obj_type, gc_flags) = unsafe { ( @@ -90,45 +86,19 @@ pub fn get_valid_func_ptr(closure: *const ClosureHeader) -> *const u8 { return std::ptr::null(); } std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst); - let func_ptr = unsafe { std::ptr::read_volatile(std::ptr::addr_of!((*closure).func_ptr)) }; - let func_ptr_addr = func_ptr as usize; - if func_ptr_addr == 0 { - return std::ptr::null(); - } - // Issue #628: BOUND_METHOD_FUNC_PTR (0xBADD_DEAD) is an intentional - // sentinel — not a real code address. The js_closure_callN dispatch - // handlers check for it explicitly and route to dispatch_bound_method - // instead of transmuting func_ptr to a fn pointer. Pre-fix the macOS - // code-range check below rejected the sentinel because 0xBADD_DEAD - // (~3.1 GiB) sits below the 0x1_0000_0000 (4 GiB) lower bound, so - // get_valid_func_ptr returned null and the closure-call returned - // TAG_UNDEFINED before reaching the BOUND_METHOD_FUNC_PTR arm. Pass - // the sentinel through here; the call sites handle it correctly. - if func_ptr == BOUND_METHOD_FUNC_PTR { - return func_ptr; - } - // BOUND_FUNCTION_FUNC_PTR (0xBADD_B12D) is the Function.prototype.bind - // sentinel — like BOUND_METHOD_FUNC_PTR it's not a real code address, so - // pass it through here and let the call sites route to - // dispatch_bound_function (#2840). - if func_ptr == BOUND_FUNCTION_FUNC_PTR { - return func_ptr; - } - // Validate func_ptr is in a reasonable code address range. - // macOS ARM64: .text starts at 0x100000000, typically < 0x400000000 - // Windows x86_64: typically 0x7FF7_xxxx_xxxx (ASLR), so we allow up to 0x8000_0000_0000 - // Linux x86_64 PIE: .text is typically in 0x55xxxxxxxxxx range - // Skip this check on Linux since PIE addresses vary widely and the GC - // kind byte already provides strong validation. - #[cfg(target_os = "macos")] - if !(0x100000000..=0x400000000).contains(&func_ptr_addr) { - return std::ptr::null(); - } - #[cfg(target_os = "windows")] - if func_ptr_addr < 0x10000 || func_ptr_addr > 0x800000000000 { + unsafe { std::ptr::read_volatile(std::ptr::addr_of!((*closure).info)) } +} + +/// Validate a closure pointer and return its body's code address — the +/// bound-method / bound-function sentinels included, which the dispatchers +/// route by value (#628, #2840) — or null. +#[inline(always)] +pub fn get_valid_func_ptr(closure: *const ClosureHeader) -> *const u8 { + let info = get_valid_info(closure); + if info.is_null() { return std::ptr::null(); } - func_ptr + unsafe { (*info).code } } /// Last resort for a callee that [`get_valid_func_ptr`] rejected: it may be a diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index 5264ceeb73..02e91d1c59 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -178,9 +178,9 @@ unsafe fn native_call_value_this_impl( // from random stack memory, which evaluated truthy and fell into the // slow-path `#newResponse` chain that ended in `(number).set is not a // function`. Closures with rest params (`(a, ...rest) => …`) have their - // own registry path via `lookup_closure_rest` which already pads, so we - // skip the arity lookup when the rest registry has an entry. - let func_ptr = get_valid_func_ptr(closure); + // own path (`dispatch_rest_bundled`) which already pads. + let info = crate::closure::closure_info(closure); + let func_ptr = info.map_or(std::ptr::null(), |info| info.code); // %Function.prototype% is itself callable: it accepts any arguments and // returns `undefined` (ECMA-262 20.2.3). It is stored as a plain object, // so it lands here with no valid func_ptr — short-circuit before the @@ -206,13 +206,11 @@ unsafe fn native_call_value_this_impl( } return crate::object::js_new_function_construct(func_value, args_ptr, args_len); } - let dispatch_args_len = if !func_ptr.is_null() && lookup_closure_rest(func_ptr).is_none() { - match lookup_closure_arity(func_ptr) { - Some(declared) if (declared as usize) > args_len => declared as usize, - _ => args_len, + let dispatch_args_len = match info { + Some(info) if crate::closure::info_rest(info).is_none() => { + args_len.max(usize::from(info.params)) } - } else { - args_len + _ => args_len, }; let undef = f64::from_bits(crate::value::TAG_UNDEFINED); @@ -244,11 +242,9 @@ unsafe fn native_call_value_this_impl( // args) would silently drop the overflow. Route through the rest-bundler // with the full slice up front. (The arity-specific `js_closure_callN` // helpers do their own rest check, but only see the truncated arg list.) - if !func_ptr.is_null() { - if let Some((fixed_arity, synth)) = lookup_closure_rest_full(func_ptr) { - let all: Vec = (0..args_len).map(arg_at).collect(); - return dispatch_rest_bundled(closure, func_ptr, this, &all, fixed_arity, synth); - } + if let Some((fixed_arity, synth)) = info.and_then(crate::closure::info_rest) { + let all: Vec = (0..args_len).map(arg_at).collect(); + return dispatch_rest_bundled(closure, func_ptr, this, &all, fixed_arity, synth); } // Call with the appropriate arity (padded with `undefined` up to the @@ -339,8 +335,9 @@ unsafe fn call_array_this( // affected here — we never see BOUND_METHOD_FUNC_PTR through this // entry because `js_closure_call_apply_with_spread`'s caller always // resolves a real closure pointer first. - let fp_for_rest = get_valid_func_ptr(closure); - if let Some((fixed_arity, synth)) = lookup_closure_rest_full(fp_for_rest) { + let info_for_rest = crate::closure::closure_info(closure); + let fp_for_rest = info_for_rest.map_or(std::ptr::null(), |info| info.code); + if let Some((fixed_arity, synth)) = info_for_rest.and_then(crate::closure::info_rest) { let mut tmp: Vec = Vec::with_capacity(n); if !args_ptr.is_null() && n > 0 { for i in 0..n { @@ -412,24 +409,21 @@ unsafe fn call_array_this( // js_closure_call16(...)` silently dropped args 16.. — breaking // qs's recursive `stringify`, which self-calls with 18 args). // - // #10420: ONE memoized strategy probe decides the route, as in - // `js_closure_callN` — the registry helpers this arm used to chain - // re-read the body record on every call. A body with a registered - // arity is called at exactly that width: padded when it declares - // more than `n`, and never handed slots it does not declare (so a - // `fn.apply(null, arr)` with thousands of elements costs the body's - // own width). An unregistered body is a runtime-provided callee with - // a handful of params; clamp it to the widest dynamic call. + // #10420: the body's info decides the route, as in + // `js_closure_callN`. A body is called at exactly its declared + // width: padded when it declares more than `n`, and never handed + // slots it does not declare (so a `fn.apply(null, arr)` with + // thousands of elements costs the body's own width). _ => { let mut full: Vec = Vec::with_capacity(n); for i in 0..n { full.push(a(i)); } - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { + let Some(info) = crate::closure::closure_info(closure) else { throw_not_callable(); - } - match resolve_strategy(func_ptr).kind() { + }; + let func_ptr = info.code; + match resolve_strategy(info).kind() { DispatchKind::BoundMethod => dispatch_bound_method(closure, this, &full), DispatchKind::BoundFunction => dispatch_bound_function(closure, &full), DispatchKind::Rest(fixed_arity, synth) => { @@ -438,10 +432,6 @@ unsafe fn call_array_this( DispatchKind::Arity(declared) => { dispatch_with_arity(closure, func_ptr, this, &full, declared) } - DispatchKind::Direct => { - let width = n.min(crate::closure::MAX_DYNAMIC_CALL_WIDTH) as u32; - dispatch_with_arity(closure, func_ptr, this, &full, width) - } } } } diff --git a/crates/perry-runtime/src/closure/dynamic_props.rs b/crates/perry-runtime/src/closure/dynamic_props.rs index 80f511d556..cfd931383f 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -360,7 +360,7 @@ pub(crate) fn closure_get_dynamic_prop_keyed( let on_base = unsafe { super::shape::closure_on_base_shape(ptr as *const ClosureHeader) }; if on_base { // fall through to the data lookups below - } else if super::shape::is_class_code(unsafe { (*(ptr as *const ClosureHeader)).func_ptr }) { + } else if super::shape::is_class_info(unsafe { (*(ptr as *const ClosureHeader)).info }) { // A class constructor: its class lookup (statics, the parent chain, // `name`/`length`/`prototype`, Function.prototype) — never the plain // function fallbacks below. @@ -413,7 +413,7 @@ pub(crate) fn closure_get_dynamic_prop_keyed( // cached, the `closure_props` lookup above intercepts before this runs // again. if prop == "name" && (on_base || !closure_is_key_deleted(ptr, "name")) { - let func_ptr = unsafe { (*(ptr as *const ClosureHeader)).func_ptr }; + let func_ptr = unsafe { (*(ptr as *const ClosureHeader)).code() }; if func_ptr == crate::closure::BOUND_FUNCTION_FUNC_PTR { return unsafe { crate::closure::bound_function_lazy_name(ptr) }; } @@ -889,12 +889,11 @@ pub(crate) fn clone_closure_rebind_this(closure_bits: u64, recv_box: f64) -> u64 if count == 0 { return closure_bits; } - // Allocate a fresh closure with the same func_ptr + capture_count (preserving the flag). + // Allocate a fresh closure of the same body + capture_count (preserving the flag). let scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = scope.root_nanbox_u64(closure_bits); let recv_handle = scope.root_nanbox_f64(recv_box); - let func_ptr = (*header).func_ptr; - let new_closure = js_closure_alloc(func_ptr, raw_count); + let new_closure = js_closure_alloc((*header).info, raw_count); let source_bits = closure_handle.get_nanbox_u64(); let source_ptr = (source_bits & 0x0000_FFFF_FFFF_FFFF) as usize; if !closure_kind_probe(source_ptr) { diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index 0abe4f6023..a8ae6fcbb8 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -38,9 +38,8 @@ pub(crate) use alloc::{ pub use body_call::{call_value, plain_call_receiver, JsThis}; pub use shape::closure_kind_probe; -pub(crate) use registry::closure_registry_census; pub(crate) use registry::DispatchKind; -pub(crate) use wide_call::{dispatch_wide_abi, MAX_DYNAMIC_CALL_WIDTH}; +pub(crate) use wide_call::dispatch_wide_abi; /// `PERRY_GC_CENSUS`: every closure-keyed side table outside the registries. pub(crate) fn closure_side_table_census() -> Vec { @@ -48,23 +47,19 @@ pub(crate) fn closure_side_table_census() -> Vec).with_declared(3); +static PROBE_REST: JsFunctionInfo = + JsFunctionInfo::of(probe_rest as crate::codegen_abi::JsBody2).with_rest(1); +// SAFETY: `probe_wide` is a JS body declaring 36 JS parameters (wider than the +// typed `JsBody16` constructor reaches). +static PROBE_WIDE: JsFunctionInfo = + unsafe { JsFunctionInfo::from_code(probe_wide as *const u8, 36) }.with_declared(36); + const UNDEF: u64 = crate::value::TAG_UNDEFINED; fn receiver() -> f64 { @@ -68,8 +77,7 @@ fn this() -> JsThis { #[test] fn an_exact_arity_call_passes_the_receiver_then_the_arguments() { - let closure = js_closure_alloc(probe3 as *const u8, 0); - js_register_closure_arity(probe3 as *const u8, 3); + let closure = js_closure_alloc(&PROBE3, 0); let r = js_closure_call3(closure, this(), 1.0, 2.0, 3.0); assert_eq!(r, 3.0); let (this, args) = seen(); @@ -79,8 +87,7 @@ fn an_exact_arity_call_passes_the_receiver_then_the_arguments() { #[test] fn a_padded_call_passes_the_receiver_and_pads_after_it() { - let closure = js_closure_alloc(probe3 as *const u8, 0); - js_register_closure_arity(probe3 as *const u8, 3); + let closure = js_closure_alloc(&PROBE3, 0); js_closure_call1(closure, this(), 9.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); @@ -89,8 +96,7 @@ fn a_padded_call_passes_the_receiver_and_pads_after_it() { #[test] fn a_rest_bundled_call_passes_the_receiver_before_the_fixed_arguments() { - let closure = js_closure_alloc(probe_rest as *const u8, 0); - js_register_closure_rest(probe_rest as *const u8, 1); + let closure = js_closure_alloc(&PROBE_REST, 0); js_closure_call4(closure, this(), 5.0, 6.0, 7.0, 8.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); @@ -103,8 +109,7 @@ fn a_rest_bundled_call_passes_the_receiver_before_the_fixed_arguments() { #[test] fn a_wide_call_passes_the_receiver_and_every_argument_slot() { - let closure = js_closure_alloc(probe_wide as *const u8, 0); - js_register_closure_arity(probe_wide as *const u8, 36); + let closure = js_closure_alloc(&PROBE_WIDE, 0); let args: Vec = (0..36).map(f64::from).collect(); let r = unsafe { js_closure_call_array(closure as i64, this(), args.as_ptr(), args.len() as i64) }; @@ -119,8 +124,7 @@ fn a_wide_call_passes_the_receiver_and_every_argument_slot() { #[test] fn a_hoisted_direct_call_passes_the_receiver() { - let closure = js_closure_alloc(probe3 as *const u8, 0); - js_register_closure_arity(probe3 as *const u8, 3); + let closure = js_closure_alloc(&PROBE3, 0); let site = DirectCall3::resolve(closure); assert!(site.is_direct(), "the probe resolves to a direct call"); site.call(closure, this(), 1.0, 2.0, 3.0); @@ -131,8 +135,7 @@ fn a_hoisted_direct_call_passes_the_receiver() { #[test] fn a_plain_call_passes_undefined_even_inside_a_receiver_call() { - let closure = js_closure_alloc(probe3 as *const u8, 0); - js_register_closure_arity(probe3 as *const u8, 3); + let closure = js_closure_alloc(&PROBE3, 0); js_closure_call3(closure, this(), 1.0, 2.0, 3.0); js_closure_call3( closure, diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index a08ead8d05..ed980ae6eb 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -1,9 +1,22 @@ -//! Closure registries (rest/arity), dispatch-strategy resolution, and -//! rest-arg / arity-pad helpers. +//! Function-body facts, dispatch-strategy resolution, and rest-arg / arity-pad +//! helpers. +//! +//! Every fact a caller needs about a function object's BODY — its parameter +//! count, rest kind, `.length`, arrow / strict / async / generator / +//! built-in bits, and the compiler-private direct-call clones — lives in the +//! body's one static [`JsFunctionInfo`] (perry-abi), which the function object +//! points to from its header. Nothing is looked up by code address: a fact is +//! one load from the object's info. use super::*; -use std::cell::RefCell; +pub use crate::codegen_abi::{ + JsFunctionInfo, FN_ARROW, FN_ASYNC, FN_ASYNC_GENERATOR, FN_BUILTIN, FN_GENERATOR, + FN_HAS_DECLARED, FN_HAS_LENGTH, FN_NON_CONSTRUCTOR, FN_REST_MASK, FN_REST_SYNTHETIC_ARGUMENTS, + FN_REST_USER, FN_REST_USER_AND_ARGUMENTS, FN_STRICT, +}; + +/// A compiler-private direct-call clone of an arrow body, from its info. #[derive(Clone, Copy)] pub(crate) struct TrustedDirectTarget { pub func_ptr: *const u8, @@ -11,270 +24,137 @@ pub(crate) struct TrustedDirectTarget { pub boxed_capture_mask: u64, } -/// Everything module init records about one closure body, keyed by the -/// body's `func_ptr` in `CLOSURE_BODY_REGISTRY`. -/// -/// #9707: these attributes used to live in TEN separate thread-local maps -/// (rest, arity, length, arrow, versioned-loop, strict, async, generator, -/// async-generator, dispatch cache), every one keyed by the same code -/// pointer. A ~59k-function program paid ~200 B per function for that — -/// ten key copies, ten buckets' worth of load-factor slack, and up to four -/// hash probes on a dispatch miss. One 16-byte record per body holds all of -/// it: the two arities and `.length` inline, the boolean attributes as flag -/// bits, and the rare compiler-private direct-call bodies as an index into -/// `TRUSTED_TARGETS` (only the eligible-arrow subset pays for those 64 -/// bytes). A dispatch-strategy miss is now ONE probe that yields every -/// input the strategy needs, so the separate dispatch cache is gone too. -/// -/// Stored as a thread-local rather than a global RwLock because closures are -/// thread-local in perry's runtime model (each thread has its own arena + -/// GC), so a per-thread copy avoids the per-call lock acquisition. Module -/// init runs on the main thread and populates one record per registered -/// body in the program; worker threads (issue #29 `perry/thread`) currently -/// don't see the table because they aren't supposed to invoke arbitrary user -/// closures across the boundary anyway. -#[derive(Clone, Copy, Default)] -pub(crate) struct ClosureBodyRecord { - /// ECMAScript-visible `.length` (`js_register_closure_length`), valid - /// when `HAS_LENGTH` is set. Unlike declared arity, function length stops - /// at the first parameter with a default and before rest. Kept separate - /// from `arity` because dispatch padding needs the body's real ABI arity - /// while `fn.length` needs the ECMAScript-visible length. - length: u32, - /// Declared ABI param count (`js_register_closure_arity`), valid when - /// `HAS_ARITY` is set — for closures WITHOUT a rest param. Looked up by - /// `js_native_call_value` (the dynamic dispatch path used when a closure - /// is stored as a class field and called method-style on an any-typed - /// receiver) so the runtime can pad missing args with TAG_UNDEFINED to - /// match the closure body's declared arity. Without this, calling a - /// 3-param arrow with 1 arg through `js_native_call_method` → - /// `js_native_call_value` → `js_closure_call1` transmutes the func_ptr to - /// a 1-arg signature and the closure body reads garbage for params 2 and - /// 3 (refs #421 — hono's `c.text(text)` / `setDefaultContentType` chain - /// hit exactly this; uninit `headers` slot evaluated to a small denormal - /// float, slow-path runs, `setDefaultContentType` returns a header object, - /// `responseHeaders.set(k, v)` then fails with a #510-class TypeError). - /// - /// `u16`: closure dispatch tops out at 16 args (`js_closure_callN`), so - /// the narrow field costs nothing; a registration above `u16::MAX` - /// saturates rather than wraps. - arity: u16, - /// Number of fixed params declared BEFORE the rest param, valid when - /// `HAS_REST` is set (the rest KIND rides in `REST_KIND_MASK`). Populated - /// at module init by `js_register_closure_rest` for every closure body - /// whose HIR signature ends in `...rest`. Looked up by `js_closure_callN` - /// so that calls through dynamic dispatch (e.g. `obj.cb(a, b, c)` where - /// `cb` is a class field holding an arrow) bundle trailing args into the - /// rest array — the previous behavior passed unbundled args, leaving the - /// rest param bound to the first trailing arg as a scalar (issue #493 / - /// #421-rest fix). Static call sites (named functions, `Expr::FuncRef`, - /// local closure-bound `let`) keep their existing bundling at the call - /// site, which is faster — the registry is consulted only when needed. - rest_arity: u16, - /// `HAS_*` presence bits plus the boolean attributes (`ARROW`, `STRICT`, - /// `ASYNC`, `GENERATOR`, `ASYNC_GENERATOR`, `NON_CONSTRUCTOR`, `BUILTIN`) - /// and the - /// 2-bit rest kind. - flags: u16, - /// 1-based index into `TRUSTED_TARGETS`; 0 = this body has no - /// compiler-private direct-call bodies. Only ever non-zero on an arrow. - targets: u32, +/// The info of the function object `closure`, if it is a live, valid one +/// (the bound-method / bound-function sentinel infos included). +#[inline(always)] +pub fn closure_info(closure: *const ClosureHeader) -> Option<&'static JsFunctionInfo> { + let info = get_valid_info(closure); + // SAFETY: a validated header's info word points at a static info. + (!info.is_null()).then(|| unsafe { &*info }) } -/// `ClosureBodyRecord::flags` bits. -mod body_flags { - /// `arity` is valid (`js_register_closure_arity` ran for this body). - pub(super) const HAS_ARITY: u16 = 1 << 0; - /// `rest_arity` + `REST_KIND_MASK` are valid (one of the three - /// `js_register_closure_rest*` entry points ran for this body). - pub(super) const HAS_REST: u16 = 1 << 1; - /// Two-bit `RestDispatchKind` discriminant, valid iff `HAS_REST`. - pub(super) const REST_KIND_SHIFT: u32 = 2; - pub(super) const REST_KIND_MASK: u16 = 0b11 << REST_KIND_SHIFT; - /// `length` is valid (`js_register_closure_length` ran for this body). - pub(super) const HAS_LENGTH: u16 = 1 << 4; - /// The body came from an arrow function. Arrows are callable but not - /// constructable and inherit the restricted Function.prototype - /// caller/arguments accessors. A small eligible subset also records an - /// internal direct-call body that may use compiler-proven capture - /// invariants (see `targets`). Dynamic callers retain the public body; - /// only the exact-arrow resolver reads that optional target. - pub(super) const ARROW: u16 = 1 << 5; - /// The body is strict-mode code (file-level `"use strict"` or a body - /// directive). Drives OrdinaryCallBindThis in `call`/`apply`/`bind`: a - /// strict callee observes the raw primitive `thisArg`; a sloppy user - /// callee gets it boxed once. - pub(super) const STRICT: u16 = 1 << 6; - /// The body came from an async function. `util.types.isAsyncFunction` - /// uses this when the predicate sees a runtime closure value instead of - /// a statically-known HIR node. Async generators set it too. - pub(super) const ASYNC: u16 = 1 << 7; - /// The source function was a generator function. The generator transform - /// clears HIR's `is_generator` flag after lowering to a state machine, so - /// codegen registers the wrapper/closure symbols here for util.types - /// identity tests. - pub(super) const GENERATOR: u16 = 1 << 8; - /// #3664: the body came from an `async function*`. Async generators also - /// carry `GENERATOR` (they lower to the same `{next,return,throw}` - /// wrapper as sync generators), so this bit is what distinguishes the - /// two — it drives `%AsyncGeneratorFunction%` vs `%GeneratorFunction%` - /// intrinsic resolution. - pub(super) const ASYNC_GENERATOR: u16 = 1 << 9; - /// #10521: the body is a runtime-native built-in function KIND with no - /// `[[Construct]]` — `new f()` throws. A property of the function kind, - /// so it is recorded once per body instead of once per closure in the - /// per-instance `BUILTIN_CLOSURE_NON_CONSTRUCTABLE` table, which every - /// such closure had to populate at allocation and the collector had to - /// prune when it died. - pub(super) const NON_CONSTRUCTOR: u16 = 1 << 10; - /// #11509: the body is a runtime-native BUILT-IN function. ECMA-262 - /// §10.3.1: a built-in's `[[Call]]` does not run OrdinaryCallBindThis — - /// it receives `thisArg` unchanged and does its own coercion — so a - /// primitive receiver reaches it unboxed, exactly as for a `STRICT` body. - /// Recorded once per body by the built-in prototype-method installers, - /// never per closure instance. - pub(super) const BUILTIN: u16 = 1 << 11; +/// The dispatch strategy `info` implies: pure bit tests on the body's own +/// record. +#[inline(always)] +pub fn resolve_strategy(info: &JsFunctionInfo) -> DispatchStrategy { + let kind = if info.code == BOUND_METHOD_FUNC_PTR { + DispatchKind::BoundMethod + } else if info.code == BOUND_FUNCTION_FUNC_PTR { + DispatchKind::BoundFunction + } else if let Some((fixed, kind)) = info_rest(info) { + DispatchKind::Rest(fixed, kind) + } else { + DispatchKind::Arity(u32::from(info.params)) + }; + DispatchStrategy { kind } } -impl ClosureBodyRecord { - #[inline(always)] - fn has(self, bit: u16) -> bool { - self.flags & bit != 0 - } - - #[inline(always)] - fn arity(self) -> Option { - self.has(body_flags::HAS_ARITY) - .then_some(u32::from(self.arity)) - } +/// `info`'s rest kind and its fixed parameter count, if it has one. +#[inline(always)] +pub fn info_rest(info: &JsFunctionInfo) -> Option<(u32, RestDispatchKind)> { + let kind = if info.flags & FN_REST_USER != 0 { + RestDispatchKind::UserRest + } else if info.flags & FN_REST_SYNTHETIC_ARGUMENTS != 0 { + RestDispatchKind::SyntheticArguments + } else if info.flags & FN_REST_USER_AND_ARGUMENTS != 0 { + RestDispatchKind::UserRestAndArguments + } else { + return None; + }; + Some((u32::from(info.rest_fixed), kind)) +} - #[inline(always)] - fn rest(self) -> Option<(u32, RestDispatchKind)> { - if !self.has(body_flags::HAS_REST) { - return None; - } - let kind = match (self.flags & body_flags::REST_KIND_MASK) >> body_flags::REST_KIND_SHIFT { - 0 => RestDispatchKind::UserRest, - 1 => RestDispatchKind::SyntheticArguments, - _ => RestDispatchKind::UserRestAndArguments, - }; - Some((u32::from(self.rest_arity), kind)) - } +/// `info`'s ECMAScript `.length`, if it records one. +#[inline(always)] +pub fn info_length(info: &JsFunctionInfo) -> Option { + (info.flags & FN_HAS_LENGTH != 0).then_some(info.length) +} - #[inline(always)] - fn length(self) -> Option { - self.has(body_flags::HAS_LENGTH).then_some(self.length) - } +/// Whether `info` has every bit of `flags`. +#[inline(always)] +pub fn info_has(info: &JsFunctionInfo, flags: u32) -> bool { + info.flags & flags == flags +} - fn set_rest(&mut self, fixed_arity: u32, kind: RestDispatchKind) { - let kind_bits: u16 = match kind { - RestDispatchKind::UserRest => 0, - RestDispatchKind::SyntheticArguments => 1, - RestDispatchKind::UserRestAndArguments => 2, - }; - self.rest_arity = saturate_u16(fixed_arity); - self.flags = (self.flags & !body_flags::REST_KIND_MASK) - | body_flags::HAS_REST - | (kind_bits << body_flags::REST_KIND_SHIFT); - } +/// The trusted direct-call clone of `info`'s arrow body, if it has one. +#[inline(always)] +pub(crate) fn info_trusted_direct(info: &JsFunctionInfo) -> Option { + (!info.trusted_code.is_null()).then(|| TrustedDirectTarget { + func_ptr: info.trusted_code, + capture_count: info.trusted_captures, + boxed_capture_mask: info.trusted_boxed_mask, + }) +} - /// The dispatch strategy this body's attributes imply. Pure bit tests on - /// an already-fetched record, so a strategy miss costs exactly one hash - /// probe (the record's) instead of the old cache + rest + arity + arrow - /// chain. - #[inline(always)] - fn dispatch_strategy(self) -> DispatchStrategy { - let kind = if let Some((fixed_arity, rest_kind)) = self.rest() { - DispatchKind::Rest(fixed_arity, rest_kind) - } else if let Some(declared) = self.arity() { - DispatchKind::Arity(declared) - } else { - DispatchKind::Direct - }; - DispatchStrategy { - kind, - is_arrow: self.has(body_flags::ARROW), - } - } +/// The versioned-loop callback clone of `info`'s body, if it has one. +#[inline(always)] +pub(crate) fn info_versioned_loop_direct(info: &JsFunctionInfo) -> Option { + (!info.versioned_code.is_null()).then(|| TrustedDirectTarget { + func_ptr: info.versioned_code, + capture_count: info.versioned_captures, + boxed_capture_mask: info.versioned_boxed_mask, + }) } +/// Whether a call of `info`'s body receives a primitive receiver unboxed +/// (strict code or a built-in, ECMA-262 §10.2.1.2 / §10.3.1). #[inline(always)] -fn saturate_u16(value: u32) -> u16 { - debug_assert!( - value <= u32::from(u16::MAX), - "closure arity {value} exceeds the u16 registry field" - ); - u16::try_from(value).unwrap_or(u16::MAX) +pub(crate) fn info_receives_primitive_this(info: &JsFunctionInfo) -> bool { + info.flags & (FN_STRICT | FN_BUILTIN) != 0 } -/// The compiler-private direct-call bodies of one eligible arrow, reached -/// from `ClosureBodyRecord::targets`. -#[derive(Clone, Copy, Default)] -struct TrustedTargets { - /// Internal direct-call body that may use compiler-proven capture - /// invariants (`js_register_closure_trusted_direct`). - direct: Option, - /// Compiler-private callback body whose cold arms poison the caller's - /// versioned loop before observable fallback code - /// (`js_register_closure_versioned_loop_direct`). - versioned_loop: Option, +/// Whether `closure` is an arrow function. +pub fn closure_is_arrow(closure: *const ClosureHeader) -> bool { + closure_info(closure).is_some_and(|info| info.flags & FN_ARROW != 0) } -crate::perry_thread_local! { - /// One `ClosureBodyRecord` per registered closure body, keyed by the - /// body's `func_ptr`. See the record's doc for what used to be here. - static CLOSURE_BODY_REGISTRY: RefCell> = - RefCell::new(crate::fast_hash::new_ptr_hash_map()); +/// Whether `closure` is a bound-method value. +pub fn closure_is_bound_method(closure: *const ClosureHeader) -> bool { + get_valid_func_ptr(closure) == BOUND_METHOD_FUNC_PTR +} - /// Dense side array for the eligible-arrow subset that has trusted - /// direct-call bodies; `ClosureBodyRecord::targets` is a 1-based index - /// into it. Append-only, so an index never moves. - static TRUSTED_TARGETS: RefCell> = RefCell::new(Vec::new()); +/// Whether `closure`'s body is a built-in kind without `[[Construct]]`. +pub(crate) fn closure_body_is_non_constructor(closure: *const ClosureHeader) -> bool { + closure_info(closure).is_some_and(|info| info.flags & FN_NON_CONSTRUCTOR != 0) } -/// The record for `func_ptr`, if module init registered anything about it. -#[inline(always)] -fn body_record(func_ptr: *const u8) -> Option { - #[cfg(test)] - BODY_RECORD_LOOKUPS.with(|lookups| lookups.set(lookups.get() + 1)); - CLOSURE_BODY_REGISTRY.with(|r| r.borrow().get(&(func_ptr as usize)).copied()) +/// `closure`'s JS-visible declared parameter count: the fixed part of a rest +/// body's, else the body's recorded declared count. +pub fn closure_arity(closure: *const ClosureHeader) -> Option { + info_arity(closure_info(closure)?) } -/// Run `update` against the (possibly fresh) record for `func_ptr`. +/// [`closure_arity`] of a body. #[inline(always)] -fn update_body_record(func_ptr: *const u8, update: impl FnOnce(&mut ClosureBodyRecord)) { - CLOSURE_BODY_REGISTRY.with(|r| { - update(r.borrow_mut().entry(func_ptr as usize).or_default()); - }); +pub fn info_arity(info: &JsFunctionInfo) -> Option { + if let Some((fixed, _)) = info_rest(info) { + return Some(fixed); + } + (info.flags & FN_HAS_DECLARED != 0).then_some(u32::from(info.declared)) } -/// Per-call dispatch strategy for a closure body. Derived from the body's -/// `ClosureBodyRecord` on a miss and memoized in `DISPATCH_RECENT`. -/// Arrow-ness rides in the same entry so receiverless calls can avoid a -/// second TLS registry lookup. +/// `closure`'s ECMAScript-visible `.length`: a per-closure override +/// (`builtin_closure_length`), else its body's `.length`, else its declared +/// parameter count ([`closure_arity`]). +pub fn closure_length(closure: *const ClosureHeader) -> Option { + if let Some(length) = crate::object::builtin_closure_length(closure as usize) { + return Some(length); + } + let info = closure_info(closure)?; + info_length(info).or_else(|| info_arity(info)) +} + +/// Per-call dispatch strategy for a closure body, derived from its info. #[derive(Clone, Copy)] pub struct DispatchStrategy { kind: DispatchKind, - is_arrow: bool, } impl DispatchStrategy { - const fn direct() -> Self { - Self { - kind: DispatchKind::Direct, - is_arrow: false, - } - } - #[inline(always)] pub(crate) fn kind(self) -> DispatchKind { self.kind } - - #[inline(always)] - pub(crate) fn is_arrow(self) -> bool { - self.is_arrow - } } #[derive(Clone, Copy)] @@ -287,13 +167,10 @@ pub(crate) enum DispatchKind { BoundFunction, /// Closure body has a rest-like runtime bundling requirement. Rest(u32, RestDispatchKind), - /// Closure body declares an arity higher than the call sites use; - /// dispatch must pad with TAG_UNDEFINED via `dispatch_with_arity`. + /// A body declaring this many parameters: a call passing at least as + /// many calls it directly (over-application is safe); one passing fewer + /// pads with TAG_UNDEFINED via `dispatch_with_arity`. Arity(u32), - /// Direct callable: just transmute func_ptr to typed fn pointer - /// (declared arity == call site arity, no rest, no bound method). - /// The hot path for the vast majority of closure call sites. - Direct, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -303,800 +180,6 @@ pub enum RestDispatchKind { UserRestAndArguments, } -crate::perry_thread_local! { - /// Four-entry polymorphic cache for recently resolved closure bodies. - /// Avoids the per-call HashMap::get + RefCell::borrow both when one body - /// is invoked back-to-back and when a small set of bodies alternates, - /// which are the steady-state shapes of: - /// - microtask drain (same then_v_arrow / __step body each iter) - /// - tight `array.sort` callbacks (same comparator every comparison) - /// - hot `array.map` / `array.forEach` loops - /// - stage pipelines that rotate through a few closure bodies - /// - /// Hits do not reorder the entries: that keeps the monomorphic first - /// slot to one comparison and makes a warmed polymorphic cache read-only. - static DISPATCH_RECENT: DispatchRecent = const { DispatchRecent::new() }; -} - -#[cfg(test)] -std::thread_local! { - static RESOLVE_STRATEGY_SLOW_CALLS: std::cell::Cell = const { std::cell::Cell::new(0) }; - static BODY_RECORD_LOOKUPS: std::cell::Cell = const { std::cell::Cell::new(0) }; -} - -#[derive(Clone, Copy)] -struct DispatchCacheEntry { - key: usize, - strategy: DispatchStrategy, -} - -impl DispatchCacheEntry { - const EMPTY: Self = Self { - key: 0, - strategy: DispatchStrategy::direct(), - }; -} - -struct DispatchRecent { - entries: [std::cell::Cell; 4], -} - -impl DispatchRecent { - const fn new() -> Self { - Self { - entries: [const { std::cell::Cell::new(DispatchCacheEntry::EMPTY) }; 4], - } - } - - #[inline(always)] - fn get(&self, key: usize) -> Option { - for entry in &self.entries { - let entry = entry.get(); - if entry.key == key { - return Some(entry.strategy); - } - } - None - } - - #[inline(always)] - fn insert(&self, key: usize, strategy: DispatchStrategy) { - self.entries[3].set(self.entries[2].get()); - self.entries[2].set(self.entries[1].get()); - self.entries[1].set(self.entries[0].get()); - self.entries[0].set(DispatchCacheEntry { key, strategy }); - } - - fn invalidate(&self, key: usize) { - for entry in &self.entries { - if entry.get().key == key { - entry.set(DispatchCacheEntry::EMPTY); - } - } - } -} - -#[inline(always)] -pub fn resolve_strategy(func_ptr: *const u8) -> DispatchStrategy { - let key = func_ptr as usize; - if let Some(strategy) = DISPATCH_RECENT.with(|cache| cache.get(key)) { - return strategy; - } - let strategy = resolve_strategy_slow(func_ptr); - DISPATCH_RECENT.with(|cache| cache.insert(key, strategy)); - strategy -} - -#[inline(never)] -fn resolve_strategy_slow(func_ptr: *const u8) -> DispatchStrategy { - #[cfg(test)] - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.set(calls.get() + 1)); - - if func_ptr == BOUND_METHOD_FUNC_PTR { - return DispatchStrategy { - kind: DispatchKind::BoundMethod, - is_arrow: false, - }; - } - if func_ptr == BOUND_FUNCTION_FUNC_PTR { - return DispatchStrategy { - kind: DispatchKind::BoundFunction, - is_arrow: false, - }; - } - // One probe of the unified record yields rest, arity AND arrow-ness; - // an unregistered body (no record) is a plain direct callable. There is - // no separate memo of the answer any more: deriving it from the record - // is a couple of bit tests, cheaper than the second hash probe the old - // `DISPATCH_CACHE` cost, and it cannot go stale — a late registration - // (#6475) rewrites the very record the next miss reads. - let strategy = match body_record(func_ptr) { - Some(record) => record.dispatch_strategy(), - None => DispatchStrategy::direct(), - }; - #[cfg(target_os = "wasi")] - let strategy = wasi_exact_arity(func_ptr, strategy); - strategy -} - -/// WASI: dispatch every fixed-arity body with exactly its real parameter -/// count ([`wasi_body_params`]), since a wasm call whose type differs from -/// its target's traps. The registered arity can differ from it — runtime -/// thunks register their JS `.length` — and an unregistered body would -/// otherwise be called with whatever count the call site has. -#[cfg(target_os = "wasi")] -fn wasi_exact_arity(func_ptr: *const u8, strategy: DispatchStrategy) -> DispatchStrategy { - match (strategy.kind, wasi_body_params(func_ptr)) { - (DispatchKind::Arity(_) | DispatchKind::Direct, Some(params)) => DispatchStrategy { - kind: DispatchKind::Arity(params), - ..strategy - }, - _ => strategy, - } -} - -/// WASI: the number of `f64` parameters `func_ptr` really takes after the -/// closure pointer and the receiver, asked of the engine with `ref.test` -/// (`ffi/perry_wasi_sig.c`). `None` for a type outside `(closure, this, f64 -/// x 0..=32) -> f64`. -#[cfg(target_os = "wasi")] -pub(crate) fn wasi_body_params(func_ptr: *const u8) -> Option { - extern "C" { - fn perry_wasi_closure_params(fp: *const u8) -> core::ffi::c_int; - } - // Keyed by the function-table index a wasm function pointer is, never a - // heap address. - crate::perry_thread_local! { - static PARAMS: std::cell::RefCell> = - std::cell::RefCell::new(std::collections::HashMap::new()); - } - let key = func_ptr as usize as u32; - let n = PARAMS - .with(|m| m.borrow().get(&key).copied()) - .unwrap_or_else(|| { - let n = unsafe { perry_wasi_closure_params(func_ptr) }; - PARAMS.with(|m| m.borrow_mut().insert(key, n)); - n - }); - u32::try_from(n).ok() -} - -/// #6475: evict a func_ptr from the recent-bodies dispatch cache. -/// `DISPATCH_RECENT` memoizes the FIRST resolution per func_ptr — but -/// effect's module-init graph calls `.pipe` (an `arguments`-object method) -/// during init, and inside an import cycle such a call can precede the -/// module's own `js_register_closure_*` batch. The pre-registration miss -/// resolved to `Arity`/`Direct` and was cached FOREVER: every later -/// `obj.pipe(a, b, c)` transmuted three positional args onto the 1-slot -/// `arguments` ABI, so `arguments.length` read 1 and effect's -/// `pipeArguments` applied only the first stage (`HttpApiBuilder.group` -/// returned the wrong pipe stage; web.ts died with "Not a valid effect: -/// undefined"). Registration is init-time-rare, so invalidating here keeps -/// the hot-path cache lock-free and unchanged. Also covers the late arity -/// registration for plain closures. (The hash-level dispatch cache this -/// used to purge as well is gone — #9707 derives the strategy from the -/// registration record itself, so there is nothing else to go stale.) -fn invalidate_dispatch_strategy(func_ptr: *const u8) { - DISPATCH_RECENT.with(|cache| cache.invalidate(func_ptr as usize)); -} - -#[cfg(test)] -mod dispatch_recent_tests { - use super::*; - - extern "C" fn body_a(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 1.0 - } - extern "C" fn body_b(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 2.0 - } - extern "C" fn body_c(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 3.0 - } - extern "C" fn body_d(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 4.0 - } - - extern "C" fn add_two( - _: *const ClosureHeader, - _this: crate::closure::JsThis, - left: f64, - right: f64, - ) -> f64 { - left + right - } - - extern "C" fn identify_rest_array( - _: *const ClosureHeader, - _this: crate::closure::JsThis, - value: f64, - ) -> f64 { - let is_pointer = value.to_bits() >> 48 == crate::value::POINTER_TAG >> 48; - if is_pointer { - 1.0 - } else { - 0.0 - } - } - - /// A real (arena) closure: the kind is its GC header, so a stack-built - /// `ClosureHeader` with no header in front of it is not a closure. - fn heap_closure(func_ptr: *const u8) -> *mut ClosureHeader { - crate::closure::js_closure_alloc(func_ptr, 0) - } - - #[test] - fn four_alternating_bodies_stay_out_of_the_hash_lookup() { - let bodies = [ - body_a as *const u8, - body_b as *const u8, - body_c as *const u8, - body_d as *const u8, - ]; - let mut addresses = bodies.map(|body| body as usize); - addresses.sort_unstable(); - assert!(addresses.windows(2).all(|pair| pair[0] != pair[1])); - - for body in bodies { - invalidate_dispatch_strategy(body); - } - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.set(0)); - - for body in bodies { - assert!(matches!( - resolve_strategy(body).kind(), - DispatchKind::Direct - )); - } - assert_eq!(RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), 4); - - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.set(0)); - for body in bodies { - assert!(matches!( - resolve_strategy(body).kind(), - DispatchKind::Direct - )); - } - assert_eq!( - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), - 0, - "a warm four-body rotation must not reach resolve_strategy_slow" - ); - - js_register_closure_arity(body_c as *const u8, 3); - assert!(matches!( - resolve_strategy(body_c as *const u8).kind(), - DispatchKind::Arity(3) - )); - assert_eq!( - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), - 1, - "late registration must evict a body from every recent-cache slot" - ); - } - - #[test] - fn repeated_array_calls_probe_the_body_registry_only_once() { - let body = add_two as *const u8; - let _no_gc = crate::gc::GcSuppressScope::new(); - let closure = heap_closure(body); - let args = [20.0, 22.0]; - invalidate_dispatch_strategy(body); - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.set(0)); - BODY_RECORD_LOOKUPS.with(|lookups| lookups.set(0)); - - for _ in 0..32 { - assert_eq!( - unsafe { - crate::closure::js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - args.as_ptr(), - args.len() as i64, - ) - }, - 42.0 - ); - } - - assert_eq!( - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), - 1, - "a warm non-rest call-array path must not hash-probe the registry per call" - ); - assert_eq!( - BODY_RECORD_LOOKUPS.with(|lookups| lookups.get()), - 1, - "32 repeated calls must perform one total closure-body hash lookup" - ); - } - - #[test] - fn late_rest_registration_invalidates_the_call_array_memo() { - let body = identify_rest_array as *const u8; - let _no_gc = crate::gc::GcSuppressScope::new(); - let closure = heap_closure(body); - let direct_arg = [0.0]; - invalidate_dispatch_strategy(body); - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.set(0)); - BODY_RECORD_LOOKUPS.with(|lookups| lookups.set(0)); - - assert_eq!( - unsafe { - crate::closure::js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - direct_arg.as_ptr(), - direct_arg.len() as i64, - ) - }, - 0.0, - "the first unregistered call must use direct dispatch" - ); - assert_eq!(RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), 1); - assert_eq!(BODY_RECORD_LOOKUPS.with(|lookups| lookups.get()), 1); - - js_register_closure_rest(body, 0); - let rest_args = [1.0, 2.0, 3.0]; - for _ in 0..2 { - assert_eq!( - unsafe { - crate::closure::js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - rest_args.as_ptr(), - rest_args.len() as i64, - ) - }, - 1.0, - "late registration must switch the cached body to rest dispatch" - ); - } - assert_eq!( - RESOLVE_STRATEGY_SLOW_CALLS.with(|calls| calls.get()), - 2, - "registration should cause one fresh registry probe, then remain cached" - ); - assert_eq!( - BODY_RECORD_LOOKUPS.with(|lookups| lookups.get()), - 2, - "the initial call and post-registration miss should be the only hash lookups" - ); - } -} - -#[no_mangle] -pub extern "C" fn js_register_closure_rest(func_ptr: *const u8, fixed_arity: u32) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.set_rest(fixed_arity, RestDispatchKind::UserRest); - }); - invalidate_dispatch_strategy(func_ptr); -} - -/// Like `js_register_closure_rest`, but flags the rest param as the -/// synthesized `arguments` array. The HIR's `append_synthetic_arguments_param` -/// helper appends an `arguments` rest param whenever a function body reads -/// `arguments` and the user hasn't already declared a rest of their own. -/// JS spec semantics: `arguments.length` counts ALL passed args (data-first -/// AND any trailing). Without this flag, `dispatch_rest_bundled` was binding -/// fixed params first and then bundling only the post-`fixed_arity` tail -/// into the rest, so `function(a, b) { return arguments.length }` called as -/// `f(10, 20)` saw `arguments.length === 0`. Refs #915 (gap 1 from #899). -#[no_mangle] -pub extern "C" fn js_register_closure_synthetic_arguments(func_ptr: *const u8, fixed_arity: u32) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.set_rest(fixed_arity, RestDispatchKind::SyntheticArguments); - }); - invalidate_dispatch_strategy(func_ptr); -} - -/// Register a function with both a user-declared rest parameter and a hidden -/// raw-arguments slot. Dynamic dispatch must provide two arrays: the user rest -/// tail and the full argument list used to allocate the ECMAScript Arguments -/// object in the callee prologue. -#[no_mangle] -pub extern "C" fn js_register_closure_rest_and_arguments(func_ptr: *const u8, fixed_arity: u32) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.set_rest(fixed_arity, RestDispatchKind::UserRestAndArguments); - }); - invalidate_dispatch_strategy(func_ptr); -} - -#[no_mangle] -pub extern "C" fn js_register_closure_async_function(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| record.flags |= body_flags::ASYNC); - // The body's [[Prototype]] kind changed: its next birth re-reads it. - super::shape::forget_body_classification(func_ptr); -} - -#[inline(always)] -pub fn is_registered_async_function(func_ptr: *const u8) -> bool { - if func_ptr.is_null() { - return false; - } - body_record(func_ptr).is_some_and(|record| record.has(body_flags::ASYNC)) -} - -#[inline(always)] -pub fn lookup_closure_rest(func_ptr: *const u8) -> Option { - lookup_closure_rest_full(func_ptr).map(|(arity, _)| arity) -} - -#[inline(always)] -pub fn lookup_closure_rest_full(func_ptr: *const u8) -> Option<(u32, RestDispatchKind)> { - // Rest-ness is part of the same immutable-at-dispatch-time answer as - // bound routing and declared arity. In particular, do not bypass - // `DISPATCH_RECENT` here: `js_closure_call_array` probes this on every - // Function.prototype.call/apply invocation, and a direct `body_record` - // read would pay a TLS RefCell borrow plus a pointer hash lookup every - // time through that hot path. Registration invalidates the memo, so the - // #6475 call-before-registration case still observes a later rest entry. - match resolve_strategy(func_ptr).kind() { - DispatchKind::Rest(fixed_arity, kind) => Some((fixed_arity, kind)), - _ => None, - } -} - -/// Register a closure body's declared param count (for closures WITHOUT a rest -/// param). Called once per non-rest closure literal at module init time. -/// See `ClosureBodyRecord::arity` for rationale. -#[no_mangle] -pub extern "C" fn js_register_closure_arity(func_ptr: *const u8, arity: u32) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.arity = saturate_u16(arity); - record.flags |= body_flags::HAS_ARITY; - }); - // #6475: same pre-registration cache-poisoning hazard as the rest-family - // registrations above — a call before this registration caches `Direct`. - invalidate_dispatch_strategy(func_ptr); -} - -#[inline(always)] -pub fn lookup_closure_arity(func_ptr: *const u8) -> Option { - body_record(func_ptr)?.arity() -} - -/// Register a closure body's ECMAScript `.length` value. -#[no_mangle] -pub extern "C" fn js_register_closure_length(func_ptr: *const u8, length: u32) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.length = length; - record.flags |= body_flags::HAS_LENGTH; - }); -} - -#[inline(always)] -pub fn lookup_closure_length(func_ptr: *const u8) -> Option { - body_record(func_ptr)?.length() -} - -#[no_mangle] -pub extern "C" fn js_register_closure_arrow_function(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| record.flags |= body_flags::ARROW); - // A closure can be invoked during a cyclic module-init graph before its - // metadata batch runs. Keep arrow-ness coherent with the rest/arity data - // memoized in the recent-bodies dispatch cache. - invalidate_dispatch_strategy(func_ptr); -} - -#[inline(always)] -pub fn is_registered_arrow_function(func_ptr: *const u8) -> bool { - if func_ptr.is_null() { - return false; - } - body_record(func_ptr).is_some_and(|record| record.has(body_flags::ARROW)) -} - -/// #10521: mark every closure whose body is `func_ptr` as a built-in -/// non-constructor. Meant for runtime-native thunks (the promise resolving -/// functions, the combinator element functions) whose spec-visible facts are -/// all properties of the KIND: `length` from the registered arity, `name` `""` -/// from the absent func-ptr name, and no `[[Construct]]` from this bit. None -/// of their closures then needs a per-instance side-table entry. -/// -/// Does not touch the dispatch strategy, which never reads this bit. -pub(crate) fn register_closure_body_non_constructor(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.flags |= body_flags::NON_CONSTRUCTOR; - }); -} - -/// True when `closure` is a closure whose body was registered through -/// [`register_closure_body_non_constructor`]. Any other address (a -/// non-closure object, a handle-band id) answers `false`. -pub(crate) fn closure_body_is_non_constructor(closure: *const ClosureHeader) -> bool { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return false; - } - body_record(func_ptr).is_some_and(|record| record.has(body_flags::NON_CONSTRUCTOR)) -} - -/// Attach one of the compiler-private direct-call bodies to the arrow at -/// `public_func_ptr`, allocating its `TRUSTED_TARGETS` slot on first use. -/// Refuses (returns `false`) for a body that is not a registered arrow, so a -/// registration can never turn an ordinary function into an arrow. -fn attach_trusted_target( - public_func_ptr: *const u8, - target: TrustedDirectTarget, - select: impl FnOnce(&mut TrustedTargets) -> &mut Option, -) -> bool { - CLOSURE_BODY_REGISTRY.with(|r| { - let mut registry = r.borrow_mut(); - let Some(record) = registry.get_mut(&(public_func_ptr as usize)) else { - return false; - }; - if !record.has(body_flags::ARROW) { - return false; - } - TRUSTED_TARGETS.with(|targets| { - let mut targets = targets.borrow_mut(); - if record.targets == 0 { - targets.push(TrustedTargets::default()); - record.targets = u32::try_from(targets.len()) - .expect("more than u32::MAX trusted closure bodies"); - } - *select(&mut targets[record.targets as usize - 1]) = Some(target); - }); - true - }) -} - -#[inline(always)] -fn trusted_targets(func_ptr: *const u8) -> Option { - let record = body_record(func_ptr)?; - if record.targets == 0 { - return None; - } - TRUSTED_TARGETS.with(|targets| targets.borrow().get(record.targets as usize - 1).copied()) -} - -/// Associate a public closure body with its compiler-private direct-call -/// body. Both addresses are static code pointers emitted by the same module. -#[no_mangle] -pub extern "C" fn js_register_closure_trusted_direct( - public_func_ptr: *const u8, - trusted_func_ptr: *const u8, - capture_count: u32, - boxed_capture_mask: u64, -) { - if public_func_ptr.is_null() || trusted_func_ptr.is_null() { - return; - } - attach_trusted_target( - public_func_ptr, - TrustedDirectTarget { - func_ptr: trusted_func_ptr, - capture_count, - boxed_capture_mask, - }, - |targets| &mut targets.direct, - ); -} - -#[inline(always)] -pub(crate) fn lookup_closure_trusted_direct(func_ptr: *const u8) -> Option { - trusted_targets(func_ptr)?.direct -} - -#[no_mangle] -pub extern "C" fn js_register_closure_versioned_loop_direct( - public_func_ptr: *const u8, - versioned_func_ptr: *const u8, - capture_count: u32, - boxed_capture_mask: u64, -) { - if public_func_ptr.is_null() || versioned_func_ptr.is_null() { - return; - } - attach_trusted_target( - public_func_ptr, - TrustedDirectTarget { - func_ptr: versioned_func_ptr, - capture_count, - boxed_capture_mask, - }, - |targets| &mut targets.versioned_loop, - ); -} - -#[inline(always)] -pub(crate) fn lookup_closure_versioned_loop_direct( - func_ptr: *const u8, -) -> Option { - trusted_targets(func_ptr)?.versioned_loop -} - -/// Keepalive anchor for the auto-optimize whole-program build — registration -/// is referenced only by generated module-init code. -#[cfg(feature = "keepalive-anchors")] -#[used(compiler)] -static KEEP_JS_REGISTER_CLOSURE_TRUSTED_DIRECT: extern "C" fn(*const u8, *const u8, u32, u64) = - js_register_closure_trusted_direct; - -#[cfg(feature = "keepalive-anchors")] -#[used(compiler)] -static KEEP_JS_REGISTER_CLOSURE_VERSIONED_LOOP_DIRECT: extern "C" fn( - *const u8, - *const u8, - u32, - u64, -) = js_register_closure_versioned_loop_direct; - -/// Register a compiled function address as strict-mode code. Emitted from -/// module init alongside the arrow-function registration. -#[no_mangle] -pub extern "C" fn js_register_closure_strict_function(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| record.flags |= body_flags::STRICT); -} - -/// Keepalive anchor for the auto-optimize whole-program build — the strict -/// registration is emitted only from generated module-init code. -#[cfg(feature = "keepalive-anchors")] -#[used(compiler)] -static KEEP_JS_REGISTER_CLOSURE_STRICT_FUNCTION: extern "C" fn(*const u8) = - js_register_closure_strict_function; - -#[inline(always)] -pub fn is_registered_strict_function(func_ptr: *const u8) -> bool { - if func_ptr.is_null() { - return false; - } - body_record(func_ptr).is_some_and(|record| record.has(body_flags::STRICT)) -} - -/// #11509: mark every closure whose body is `func_ptr` as a built-in function, -/// so a method call on a primitive hands it the raw receiver instead of a -/// `ToObject` wrapper. Only runtime-native thunks may be registered here. -pub(crate) fn register_closure_body_builtin(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| record.flags |= body_flags::BUILTIN); -} - -/// OrdinaryCallBindThis, decided by function KIND in one registry lookup: -/// a strict body or a built-in body observes the primitive `thisArg` -/// unchanged; only a sloppy user body is owed the `ToObject` wrapper. -#[inline(always)] -pub(crate) fn body_receives_primitive_this(func_ptr: *const u8) -> bool { - if func_ptr.is_null() { - return false; - } - body_record(func_ptr) - .is_some_and(|record| record.has(body_flags::STRICT) || record.has(body_flags::BUILTIN)) -} - -pub fn closure_is_arrow(closure: *const ClosureHeader) -> bool { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return false; - } - // The unified dispatch strategy already carries arrow-ness (the same - // registry answer, memoised per body and kept coherent by - // `js_register_closure_arrow_function`'s invalidation), and the call - // that follows a receiver rebind resolves it anyway — so answer from the - // recent-bodies cache instead of a second thread-local hash probe per - // call. (`this.handler(a, b)` on a closure-typed field paid both.) - resolve_strategy(func_ptr).is_arrow() -} - -/// True if `closure` is a bound-method / bound-function value (its body is the -/// `BOUND_METHOD_FUNC_PTR` sentinel). Class method/getter/setter values read -/// via `C.prototype.m`, instance method reads, and `Function.prototype.bind` -/// results all use this sentinel. None of them are constructors, so they have -/// no `prototype` own property (ECMA-262: methods, accessors, and bound -/// functions are non-constructors). -pub fn closure_is_bound_method(closure: *const ClosureHeader) -> bool { - get_valid_func_ptr(closure) == BOUND_METHOD_FUNC_PTR -} - -#[no_mangle] -pub extern "C" fn js_register_closure_generator_function(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| record.flags |= body_flags::GENERATOR); - // The body's [[Prototype]] kind changed: its next birth re-reads it. - super::shape::forget_body_classification(func_ptr); -} - -#[inline(always)] -pub fn is_registered_generator_function(func_ptr: *const u8) -> bool { - body_record(func_ptr).is_some_and(|record| record.has(body_flags::GENERATOR)) -} - -/// #3664: register a closure body `func_ptr` as an `async function*`. Also -/// marks it in the async-function registry so `util.types.isAsyncFunction` -/// reports `true` for async generators (matching Node). -#[no_mangle] -pub extern "C" fn js_register_closure_async_generator_function(func_ptr: *const u8) { - if func_ptr.is_null() { - return; - } - update_body_record(func_ptr, |record| { - record.flags |= body_flags::ASYNC_GENERATOR | body_flags::ASYNC; - }); - // The body's [[Prototype]] kind changed: its next birth re-reads it. - super::shape::forget_body_classification(func_ptr); -} - -#[inline(always)] -pub fn is_registered_async_generator_function(func_ptr: *const u8) -> bool { - if func_ptr.is_null() { - return false; - } - body_record(func_ptr).is_some_and(|record| record.has(body_flags::ASYNC_GENERATOR)) -} - -/// Public helper: given a `*const ClosureHeader` pointer, return the -/// closure's declared ABI arity if known. Falls back to the rest-registry -/// fixed-arity entry for closures declared with `...rest`. -pub fn closure_arity(closure: *const ClosureHeader) -> Option { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return None; - } - let record = body_record(func_ptr)?; - // Closures declared with `...rest` register through a separate - // registry path; prefer the fixed-arity portion of that entry when - // present so `length` matches the user-visible declared params. - if let Some((arity, _synth)) = record.rest() { - return Some(arity); - } - record.arity() -} - -/// Public helper for the ECMAScript-visible function `.length`. -/// -/// Generated user closures register this explicitly because `.length` stops -/// at the first default parameter, while the dispatch arity must remain the -/// full declared parameter count for ABI-safe padding. -pub fn closure_length(closure: *const ClosureHeader) -> Option { - if let Some(length) = crate::object::builtin_closure_length(closure as usize) { - return Some(length); - } - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return None; - } - let record = body_record(func_ptr)?; - if let Some(length) = record.length() { - return Some(length); - } - if let Some((arity, _synth)) = record.rest() { - return Some(arity); - } - record.arity() -} - /// Build a JS array from a slice of NaN-boxed f64 values and return it /// NaN-boxed as a pointer. Used by the rest-bundling helper below. #[inline(always)] @@ -1249,8 +332,8 @@ pub unsafe fn dispatch_rest_bundled( /// correctly initialised slots instead of stale registers. /// /// `func_ptr` is already validated and known non-BOUND, non-rest. -/// `declared_arity` is what `js_register_closure_arity` recorded for this body -/// at module init time. Callers reach here only when `args.len() < declared_arity`. +/// `declared_arity` is the parameter count the body's `JsFunctionInfo` +/// records. Callers reach here only when `args.len() < declared_arity`. /// /// Refs #420: drizzle's `pgTable` is `(name, columns, extraConfig) => …` /// (3 params); user calls it as `pgTable("users", cols)` (2 args). Without @@ -1378,6 +461,18 @@ pub const BOUND_METHOD_FUNC_PTR: *const u8 = 0xBADD_DEAD_u64 as *const u8; /// [4] = `length` snapshot pub const BOUND_FUNCTION_FUNC_PTR: *const u8 = 0xBADD_B12D_u64 as *const u8; +/// The info every bound-method value points to (its "code" is the +/// [`BOUND_METHOD_FUNC_PTR`] sentinel the dispatchers route by). +// SAFETY: the dispatchers route this sentinel by value; it is never called. +pub static BOUND_METHOD_INFO: JsFunctionInfo = + unsafe { JsFunctionInfo::from_code(BOUND_METHOD_FUNC_PTR, 0) }; + +/// The info every `Function.prototype.bind` result points to (its "code" is +/// the [`BOUND_FUNCTION_FUNC_PTR`] sentinel). +// SAFETY: the dispatchers route this sentinel by value; it is never called. +pub static BOUND_FUNCTION_INFO: JsFunctionInfo = + unsafe { JsFunctionInfo::from_code(BOUND_FUNCTION_FUNC_PTR, 0) }; + /// Flag stored in the high bit of capture_count: the closure's LAST capture /// slot (index `real_capture_count - 1`) holds its `this` — an arrow's lexical /// receiver, or the object an object-literal method was created on (patched @@ -1402,211 +497,3 @@ pub const NO_THIS_REBIND_FLAG: u32 = 0x4000_0000; pub fn real_capture_count(capture_count: u32) -> u32 { capture_count & !(CAPTURES_THIS_FLAG | NO_THIS_REBIND_FLAG) } - -/// `PERRY_GC_CENSUS`: entries and estimated bytes of the per-`func_ptr` -/// registry filled from module init. `closure.body_registry` is the unified -/// record table (#9707 — it replaced the ten `closure.*_registry` / -/// `closure.dispatch_cache` rows an older census printed); -/// `closure.trusted_targets` is the dense side array only eligible arrows -/// with compiler-private bodies occupy. -pub(crate) fn closure_registry_census() -> Vec { - use crate::gc::census::{map_bytes, vec_bytes}; - let mut rows = Vec::new(); - CLOSURE_BODY_REGISTRY.with(|m| { - let m = m.borrow(); - rows.push(("closure.body_registry", m.len(), map_bytes(&m))); - }); - TRUSTED_TARGETS.with(|v| { - let v = v.borrow(); - rows.push(("closure.trusted_targets", v.len(), vec_bytes(&v))); - }); - rows -} - -#[cfg(test)] -mod body_record_tests { - use super::*; - - extern "C" fn rec_a(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 1.0 - } - extern "C" fn rec_b(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 2.0 - } - extern "C" fn rec_c(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 3.0 - } - extern "C" fn rec_d(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 4.0 - } - extern "C" fn rec_trusted(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 5.0 - } - extern "C" fn rec_versioned(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 6.0 - } - extern "C" fn rec_e(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 7.0 - } - extern "C" fn rec_f(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - 8.0 - } - - /// The whole point of #9707: one record per body, small enough that the - /// map's bucket is a third of what the ten-map layout summed to. - #[test] - fn record_is_sixteen_bytes_and_the_bucket_is_twenty_four() { - assert_eq!(std::mem::size_of::(), 16); - assert_eq!(std::mem::size_of::<(usize, ClosureBodyRecord)>(), 24); - } - - /// Every attribute a body can carry survives being packed into the one - /// record alongside every other attribute, and each reader still - /// answers from its own field. - #[test] - fn all_attributes_coexist_in_one_record() { - let body = rec_a as *const u8; - js_register_closure_rest_and_arguments(body, 3); - js_register_closure_length(body, 2); - js_register_closure_arrow_function(body); - js_register_closure_strict_function(body); - js_register_closure_async_generator_function(body); - js_register_closure_generator_function(body); - - assert!(matches!( - lookup_closure_rest_full(body), - Some((3, RestDispatchKind::UserRestAndArguments)) - )); - assert_eq!(lookup_closure_rest(body), Some(3)); - assert_eq!( - lookup_closure_arity(body), - None, - "rest does not imply arity" - ); - assert_eq!(lookup_closure_length(body), Some(2)); - assert!(is_registered_arrow_function(body)); - assert!(is_registered_strict_function(body)); - assert!( - is_registered_async_function(body), - "async generators are async" - ); - assert!(is_registered_generator_function(body)); - assert!(is_registered_async_generator_function(body)); - let strategy = resolve_strategy(body); - assert!(strategy.is_arrow()); - assert!(matches!( - strategy.kind(), - DispatchKind::Rest(3, RestDispatchKind::UserRestAndArguments) - )); - - // Registering the same body's arity afterwards adds the attribute - // without disturbing the others, and rest still wins dispatch. - js_register_closure_arity(body, 4); - assert_eq!(lookup_closure_arity(body), Some(4)); - assert_eq!(lookup_closure_rest(body), Some(3)); - assert!(matches!( - resolve_strategy(body).kind(), - DispatchKind::Rest(3, RestDispatchKind::UserRestAndArguments) - )); - } - - /// A body nobody registered has no record and is a plain direct - /// callable; a body with only a non-dispatch attribute has a record and - /// is STILL a plain direct callable. - #[test] - fn unregistered_and_flag_only_bodies_dispatch_direct() { - let unregistered = rec_b as *const u8; - assert!(body_record(unregistered).is_none()); - let strategy = resolve_strategy(unregistered); - assert!(matches!(strategy.kind(), DispatchKind::Direct)); - assert!(!strategy.is_arrow()); - assert_eq!(lookup_closure_arity(unregistered), None); - assert_eq!(lookup_closure_length(unregistered), None); - assert!(!is_registered_strict_function(unregistered)); - - let strict_only = rec_c as *const u8; - js_register_closure_strict_function(strict_only); - assert!(body_record(strict_only).is_some()); - assert!(is_registered_strict_function(strict_only)); - assert!(matches!( - resolve_strategy(strict_only).kind(), - DispatchKind::Direct - )); - assert_eq!(lookup_closure_arity(strict_only), None); - assert_eq!(lookup_closure_rest(strict_only), None); - } - - /// The two compiler-private bodies live in the dense side array, are - /// reachable only through an ARROW record, and a registration against a - /// non-arrow must not create one. - #[test] - fn trusted_targets_hang_off_the_arrow_record() { - let arrow = rec_d as *const u8; - let plain = rec_e as *const u8; - js_register_closure_arity(plain, 1); - - // Not an arrow (yet): both registrations are refused. - js_register_closure_trusted_direct(arrow, rec_trusted as *const u8, 2, 0b10); - js_register_closure_versioned_loop_direct(arrow, rec_versioned as *const u8, 2, 0b10); - assert!(lookup_closure_trusted_direct(arrow).is_none()); - assert!(lookup_closure_versioned_loop_direct(arrow).is_none()); - assert!( - !is_registered_arrow_function(arrow), - "refusal must not mint an arrow" - ); - - js_register_closure_arrow_function(arrow); - js_register_closure_trusted_direct(arrow, rec_trusted as *const u8, 2, 0b10); - let direct = lookup_closure_trusted_direct(arrow).expect("direct target"); - assert_eq!(direct.func_ptr, rec_trusted as *const u8); - assert_eq!(direct.capture_count, 2); - assert_eq!(direct.boxed_capture_mask, 0b10); - assert!( - lookup_closure_versioned_loop_direct(arrow).is_none(), - "the versioned slot is independent of the direct slot" - ); - - js_register_closure_versioned_loop_direct(arrow, rec_versioned as *const u8, 2, 0b10); - let versioned = lookup_closure_versioned_loop_direct(arrow).expect("versioned target"); - assert_eq!(versioned.func_ptr, rec_versioned as *const u8); - // Both targets share ONE side-array slot, so the second registration - // reused the first's index instead of appending. - let slot = body_record(arrow).unwrap().targets; - assert!(slot != 0); - assert_eq!( - lookup_closure_trusted_direct(arrow).unwrap().func_ptr, - rec_trusted as *const u8, - "attaching the versioned body must not clobber the direct one" - ); - - // A registered non-arrow is still refused, and stays targetless. - js_register_closure_trusted_direct(plain, rec_trusted as *const u8, 0, 0); - assert!(lookup_closure_trusted_direct(plain).is_none()); - assert_eq!(body_record(plain).unwrap().targets, 0); - assert!(!is_registered_arrow_function(plain)); - } - - /// The census reports the unified table by its new name, with one entry - /// per registered body — not one per attribute. - #[test] - fn census_counts_bodies_not_attributes() { - let body = rec_f as *const u8; - js_register_closure_arity(body, 1); - js_register_closure_length(body, 1); - js_register_closure_strict_function(body); - let rows = closure_registry_census(); - let (name, entries, bytes) = rows - .iter() - .find(|(name, _, _)| *name == "closure.body_registry") - .copied() - .expect("body registry row"); - assert_eq!(name, "closure.body_registry"); - let distinct_bodies = CLOSURE_BODY_REGISTRY.with(|m| m.borrow().len()); - assert_eq!(entries, distinct_bodies); - assert!(entries >= 1); - assert!(bytes > 0); - assert!(rows - .iter() - .any(|(name, _, _)| *name == "closure.trusted_targets")); - } -} diff --git a/crates/perry-runtime/src/closure/shape.rs b/crates/perry-runtime/src/closure/shape.rs index f37b6f1625..aee4c8e5d0 100644 --- a/crates/perry-runtime/src/closure/shape.rs +++ b/crates/perry-runtime/src/closure/shape.rs @@ -59,14 +59,14 @@ impl FunctionProtoKind { } } - /// The body kind recorded for `func_ptr` — the same registries + /// The body kind `info` records — the same bits /// `generator_function_proto_of` answers [[GetPrototypeOf]] from. - pub(crate) fn of_body(func_ptr: *const u8) -> FunctionProtoKind { - if super::is_registered_async_generator_function(func_ptr) { + pub(crate) fn of_body(info: &super::JsFunctionInfo) -> FunctionProtoKind { + if info.flags & super::FN_ASYNC_GENERATOR != 0 { FunctionProtoKind::AsyncGenerator - } else if super::is_registered_generator_function(func_ptr) { + } else if info.flags & super::FN_GENERATOR != 0 { FunctionProtoKind::Generator - } else if super::is_registered_async_function(func_ptr) { + } else if info.flags & super::FN_ASYNC != 0 { FunctionProtoKind::AsyncFunction } else { FunctionProtoKind::Function @@ -105,8 +105,6 @@ crate::perry_thread_local! { /// This agent's class-constructor ShapeId (0 = not minted yet). Its own /// cell: the base-shape array is copied on every closure birth. static CLASS_SHAPE: std::cell::Cell = const { std::cell::Cell::new(0) }; - /// One-entry body cache: the last `func_ptr` born and its base shape. - static LAST_BODY: std::cell::Cell<(usize, u32)> = const { std::cell::Cell::new((0, 0)) }; } /// The attribute summary a Function shape publishes. Its keys are not in a @@ -211,36 +209,26 @@ pub(crate) fn function_class_shape() -> u32 { id } -/// Is `func_ptr` the class [[Call]] code — i.e. is a closure carrying it a +/// Is `info` the class function object info — i.e. is a closure carrying it a /// class function object? Equivalent to its ShapeId being /// [`function_class_shape`] (both are set at mint and never change); used on /// hot paths where the shape id would need a thread-local read. #[inline(always)] -pub(crate) fn is_class_code(func_ptr: *const u8) -> bool { - func_ptr == crate::object::class_value::js_class_constructor_called as *const u8 +pub(crate) fn is_class_info(info: *const super::JsFunctionInfo) -> bool { + std::ptr::eq(info, &crate::object::class_value::CLASS_CONSTRUCTOR_INFO) } -/// The ShapeId a fresh closure of `func_ptr` is born with. +/// The ShapeId a fresh closure of the body `info` describes is born with. #[inline] -pub(crate) fn birth_shape_for_body(func_ptr: *const u8) -> u32 { - let (last, id) = LAST_BODY.with(std::cell::Cell::get); - if last == func_ptr as usize && id != 0 { - return id; - } - let id = function_base_shape(FunctionProtoKind::of_body(func_ptr)); - LAST_BODY.with(|c| c.set((func_ptr as usize, id))); - id -} - -/// A body was (re)classified as async/generator: forget the one-entry cache -/// so the next birth re-reads the registries. -#[inline] -pub(crate) fn forget_body_classification(func_ptr: *const u8) { - LAST_BODY.with(|c| { - if c.get().0 == func_ptr as usize { - c.set((0, 0)); - } - }); +pub(crate) fn birth_shape_for_body(info: *const super::JsFunctionInfo) -> u32 { + // SAFETY: a non-null info is a static one (the allocation entries' contract). + let kind = match unsafe { info.as_ref() } { + Some(info) => FunctionProtoKind::of_body(info), + // A function object with no body: every call through it is refused + // (`get_valid_info` answers null), and it has the plain Function shape. + None => FunctionProtoKind::Function, + }; + function_base_shape(kind) } /// Is `closure` on a DESCRIBED Function shape (base or keyed, any body @@ -260,10 +248,10 @@ pub(crate) unsafe fn closure_on_base_shape(closure: *const ClosureHeader) -> boo || shapes::shape_object_kind_by_id(id) == Some(ShapeObjectKind::Function), "a closure carries a Function, FunctionDictionary or class shape: {id:#x}" ); - // The class shape is sticky and implies the class [[Call]] code pointer, - // so the code-pointer compare (a link-time constant, no thread-local + // The class shape is sticky and implies the class info, + // so the info-pointer compare (a link-time constant, no thread-local // read) excludes it for free on this hot path. - id != function_dictionary_shape() && !is_class_code((*closure).func_ptr) + id != function_dictionary_shape() && !is_class_info((*closure).info) } /// Record that `closure` now answers something its base shape does not: @@ -278,7 +266,7 @@ pub(crate) unsafe fn closure_become_dictionary(closure: *mut ClosureHeader) { let dict = function_dictionary_shape(); let id = (*closure).shape_id; // A class function object keeps its (sticky, dictionary-kind) class shape. - if id != dict && !is_class_code((*closure).func_ptr) { + if id != dict && !is_class_info((*closure).info) { // GC_STORE_AUDIT(POINTER_FREE): a ShapeId, never a heap reference. (*closure).shape_id = dict; } @@ -300,14 +288,14 @@ pub(crate) fn refresh_closure_shape(ptr: usize) { unsafe { let closure = ptr as *mut ClosureHeader; let dict = function_dictionary_shape(); - if (*closure).shape_id == dict || is_class_code((*closure).func_ptr) { + if (*closure).shape_id == dict || is_class_info((*closure).info) { return; } if super::props::has_state(ptr) { closure_become_dictionary(closure); return; } - let base = birth_shape_for_body((*closure).func_ptr); + let base = birth_shape_for_body((*closure).info); let bag = super::props::bag_of(ptr); let next = if bag.is_null() { base @@ -481,15 +469,23 @@ mod tests { shapes::shape_object_kind_by_id(unsafe { (*c).shape_id }) } - fn fresh(body: crate::closure::body_call::js_body_fn_ty!()) -> *mut ClosureHeader { - js_closure_alloc(body as *const u8, 0) + static PLAIN_BODY: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + plain_body as crate::codegen_abi::JsBody0, + ); + static ASYNC_BODY: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + async_body as crate::codegen_abi::JsBody0, + ) + .with_flags(crate::closure::FN_ASYNC); + + fn fresh(info: &'static crate::closure::JsFunctionInfo) -> *mut ClosureHeader { + js_closure_alloc(info, 0) } #[test] fn a_closure_is_born_with_the_base_function_shape_at_plus_four() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - let c = fresh(plain_body); + let c = fresh(&PLAIN_BODY); let word = unsafe { *((c as *const u8).add(super::super::CLOSURE_SHAPE_OFFSET) as *const u32) }; assert!(shapes::is_exotic_shape_id(word), "{word:#x}"); @@ -510,15 +506,15 @@ mod tests { fn an_async_body_is_born_with_the_async_function_prototype() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - crate::closure::js_register_closure_async_function(async_body as *const u8); - let c = fresh(async_body); + + let c = fresh(&ASYNC_BODY); let id = unsafe { (*c).shape_id }; assert_eq!(kind_of(c), Some(ShapeObjectKind::Function)); assert_eq!( shapes::shape_proto_id(id), Some(INTRINSIC_SERIAL_ASYNC_FUNCTION) ); - assert_ne!(id, unsafe { (*fresh(plain_body)).shape_id }); + assert_ne!(id, unsafe { (*fresh(&PLAIN_BODY)).shape_id }); } /// An own string key moves a function to a KEYED Function shape: the @@ -528,8 +524,8 @@ mod tests { fn own_keys_give_a_canonical_keyed_function_shape() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - let a = fresh(plain_body); - let b = fresh(plain_body); + let a = fresh(&PLAIN_BODY); + let b = fresh(&PLAIN_BODY); let base = unsafe { (*a).shape_id }; for c in [a, b] { closure_set_dynamic_prop(c as usize, "tag", 7.0); @@ -563,7 +559,7 @@ mod tests { fn a_recorded_prototype_a_delete_and_an_accessor_each_leave_the_base_shape() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - let a = fresh(plain_body); + let a = fresh(&PLAIN_BODY); let proto = crate::object::js_object_alloc(0, 0); closure_set_static_prototype( a as usize, @@ -571,11 +567,11 @@ mod tests { ); assert_eq!(kind_of(a), Some(ShapeObjectKind::FunctionDictionary)); - let b = fresh(plain_body); + let b = fresh(&PLAIN_BODY); crate::closure::closure_mark_key_deleted(b as usize, "length"); assert_eq!(kind_of(b), Some(ShapeObjectKind::FunctionDictionary)); - let c = fresh(plain_body); + let c = fresh(&PLAIN_BODY); crate::object::descriptor_state::set_accessor_descriptor( c as usize, "x".to_string(), @@ -588,7 +584,7 @@ mod tests { fn a_symbol_keyed_own_property_leaves_the_base_shape() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - let c = fresh(plain_body); + let c = fresh(&PLAIN_BODY); let sym = unsafe { crate::symbol::js_symbol_new(f64::from_bits(crate::value::TAG_UNDEFINED)) }; crate::symbol::store_object_symbol_property_root( @@ -616,7 +612,7 @@ mod tests { assert!(!closure_kind_probe(obj as usize)); (*obj).parent_class_id = saved; } - assert!(crate::closure::is_closure_ptr(fresh(plain_body) as usize)); + assert!(crate::closure::is_closure_ptr(fresh(&PLAIN_BODY) as usize)); } extern "C" fn three_arg_body( @@ -637,8 +633,8 @@ mod tests { fn a_bind_result_keeps_its_length_in_a_capture() { let _lock = crate::gc::global_side_table_test_lock(); let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - crate::closure::js_register_closure_arity(three_arg_body as *const u8, 3); - let target = js_closure_alloc(three_arg_body as *const u8, 0); + + let target = js_closure_alloc(crate::fn_info!(three_arg_body, 3; with_declared(3)), 0); let args = [f64::from_bits(crate::value::TAG_UNDEFINED), 1.0]; let bound = unsafe { crate::closure::js_function_bind( @@ -677,7 +673,7 @@ mod tests { let _t = crate::gc::GcTriggerThresholdTestGuard::suppress_automatic_triggers(); let acc = crate::object::descriptor_state::AccessorDescriptor { get: 0, set: 0 }; let attrs = crate::object::PropertyAttrs::new(false, false, true); - let a = fresh(plain_body); + let a = fresh(&PLAIN_BODY); crate::object::descriptor_state::install_fresh_accessor_property( a as usize, "length".into(), @@ -685,7 +681,7 @@ mod tests { attrs, ); assert_eq!(kind_of(a), Some(ShapeObjectKind::FunctionDictionary)); - let b = fresh(plain_body); + let b = fresh(&PLAIN_BODY); crate::object::descriptor_state::set_builtin_accessor_descriptor( b as usize, "name".into(), diff --git a/crates/perry-runtime/src/closure/tests.rs b/crates/perry-runtime/src/closure/tests.rs index 491553e50e..a5319acd83 100644 --- a/crates/perry-runtime/src/closure/tests.rs +++ b/crates/perry-runtime/src/closure/tests.rs @@ -8,9 +8,12 @@ extern "C" fn test_closure_func( captured * 2.0 } +static TEST_CLOSURE_FUNC: JsFunctionInfo = + JsFunctionInfo::of(test_closure_func as crate::codegen_abi::JsBody0); + #[test] fn test_closure_basic() { - let closure = js_closure_alloc(test_closure_func as *const u8, 1); + let closure = js_closure_alloc(&TEST_CLOSURE_FUNC, 1); js_closure_set_capture_f64(closure, 0, 21.0); let result = js_closure_call0(closure, crate::closure::plain_call_receiver()); assert_eq!(result, 42.0); @@ -29,7 +32,7 @@ fn test_closure_capture_bits_roundtrip_tagged_values() { .bits(), (-0.0f64).to_bits(), ]; - let closure = js_closure_alloc(test_closure_func as *const u8, captures.len() as u32); + let closure = js_closure_alloc(&TEST_CLOSURE_FUNC, captures.len() as u32); for (index, &bits) in captures.iter().enumerate() { js_closure_set_capture_bits(closure, index as u32, bits); @@ -60,7 +63,7 @@ fn test_closure_alloc_with_captures_singleton_preserves_capture_bits() { ]; let closure = js_closure_alloc_with_captures_singleton( - test_closure_func as *const u8, + &TEST_CLOSURE_FUNC, captures.len() as u32, captures.as_ptr(), ); diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 4e12a5c36b..5d790e2e39 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -13,7 +13,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; -use std::sync::{Once, OnceLock}; +use std::sync::OnceLock; use crate::array::ArrayHeader; use crate::closure::{js_closure_get_capture_f64, ClosureHeader}; @@ -55,7 +55,6 @@ thread_local! { static CLUSTER_GC_REGISTERED: Cell = const { Cell::new(false) }; } -static CLUSTER_INIT: Once = Once::new(); static CLUSTER_WORKER_ID: OnceLock> = OnceLock::new(); fn cluster_worker_id() -> Option<&'static str> { @@ -652,9 +651,6 @@ fn ensure_cluster_runtime() { crate::gc::gc_register_mutable_root_scanner_named("node_cluster", cluster_root_scanner); registered.set(true); }); - CLUSTER_INIT.call_once(|| { - register_cluster_arities(); - }); } fn cluster_root_scanner(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { @@ -673,32 +669,66 @@ fn cluster_root_scanner(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { cluster_emitter_scan(visitor); } -fn register_cluster_arities() { - let arities: [(*const u8, u32); 3] = [ - (cluster_internal_online as *const u8, 0), - (cluster_internal_disconnect as *const u8, 0), - (cluster_internal_exit as *const u8, 2), - ]; - for (func, arity) in arities { - crate::closure::js_register_closure_arity(func, arity); - } - for (func, arity, length) in [ - (cluster_worker_send as *const u8, 4, 0), - (cluster_worker_kill as *const u8, 1, 0), - (cluster_worker_destroy as *const u8, 1, 1), - (cluster_worker_disconnect as *const u8, 0, 0), - (cluster_worker_is_connected as *const u8, 0, 0), - (cluster_worker_is_dead as *const u8, 0, 0), - (cluster_setup_emit_thunk as *const u8, 0, 0), - (cluster_fork_emit_thunk as *const u8, 0, 0), - (cluster_callback_thunk as *const u8, 0, 0), - (cluster_disconnect_emit_thunk as *const u8, 0, 0), - (cluster_exit_emit_thunk as *const u8, 0, 0), - (cluster_internal_message as *const u8, 1, 1), - ] { - crate::closure::js_register_closure_arity(func, arity); - crate::closure::js_register_closure_length(func, length); - } +/// The cluster bodies' infos, one per body (each is allocated from several +/// places). +mod infos { + use super::*; + use crate::closure::{ClosureHeader, JsFunctionInfo}; + use crate::codegen_abi::{JsBody0, JsBody1, JsBody2, JsBody4}; + pub(super) static CLUSTER_INTERNAL_ONLINE: JsFunctionInfo = + JsFunctionInfo::of(cluster_internal_online as JsBody0).with_declared(0); + pub(super) static CLUSTER_INTERNAL_DISCONNECT: JsFunctionInfo = + JsFunctionInfo::of(cluster_internal_disconnect as JsBody0).with_declared(0); + pub(super) static CLUSTER_INTERNAL_EXIT: JsFunctionInfo = + JsFunctionInfo::of(cluster_internal_exit as JsBody2).with_declared(2); + pub(super) static CLUSTER_WORKER_SEND: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_send as JsBody4) + .with_declared(4) + .with_length(0); + pub(super) static CLUSTER_WORKER_KILL: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_kill as JsBody1) + .with_declared(1) + .with_length(0); + pub(super) static CLUSTER_WORKER_DESTROY: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_destroy as JsBody1) + .with_declared(1) + .with_length(1); + pub(super) static CLUSTER_WORKER_DISCONNECT: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_disconnect as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_WORKER_IS_CONNECTED: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_is_connected as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_WORKER_IS_DEAD: JsFunctionInfo = + JsFunctionInfo::of(cluster_worker_is_dead as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_SETUP_EMIT_THUNK: JsFunctionInfo = + JsFunctionInfo::of(cluster_setup_emit_thunk as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_FORK_EMIT_THUNK: JsFunctionInfo = + JsFunctionInfo::of(cluster_fork_emit_thunk as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_CALLBACK_THUNK: JsFunctionInfo = + JsFunctionInfo::of(cluster_callback_thunk as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_DISCONNECT_EMIT_THUNK: JsFunctionInfo = + JsFunctionInfo::of(cluster_disconnect_emit_thunk as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_EXIT_EMIT_THUNK: JsFunctionInfo = + JsFunctionInfo::of(cluster_exit_emit_thunk as JsBody0) + .with_declared(0) + .with_length(0); + pub(super) static CLUSTER_INTERNAL_MESSAGE: JsFunctionInfo = + JsFunctionInfo::of(cluster_internal_message as JsBody1) + .with_declared(1) + .with_length(1); } pub(crate) fn ensure_worker_constructor(value: f64) -> f64 { @@ -721,12 +751,12 @@ pub(crate) fn ensure_worker_constructor(value: f64) -> f64 { crate::object::PropertyAttrs::new(true, false, true), ); for (name, func, length) in [ - ("send", cluster_worker_send as *const u8, 0), - ("kill", cluster_worker_kill as *const u8, 0), - ("destroy", cluster_worker_destroy as *const u8, 1), - ("disconnect", cluster_worker_disconnect as *const u8, 0), - ("isConnected", cluster_worker_is_connected as *const u8, 0), - ("isDead", cluster_worker_is_dead as *const u8, 0), + ("send", &infos::CLUSTER_WORKER_SEND, 0), + ("kill", &infos::CLUSTER_WORKER_KILL, 0), + ("destroy", &infos::CLUSTER_WORKER_DESTROY, 1), + ("disconnect", &infos::CLUSTER_WORKER_DISCONNECT, 0), + ("isConnected", &infos::CLUSTER_WORKER_IS_CONNECTED, 0), + ("isDead", &infos::CLUSTER_WORKER_IS_DEAD, 0), ] { let method = crate::closure::js_closure_alloc(func, 0); crate::object::set_bound_native_closure_name(method, name); @@ -963,7 +993,7 @@ fn apply_setup_primary(settings_arg: f64) { state.setup_called = true; state.settings_bits = next.to_bits(); }); - let deferred = crate::closure::js_closure_alloc(cluster_setup_emit_thunk as *const u8, 1); + let deferred = crate::closure::js_closure_alloc(&infos::CLUSTER_SETUP_EMIT_THUNK, 1); crate::closure::js_closure_set_capture_f64(deferred, 0, next); crate::timer::js_set_immediate_callback(deferred as i64); } @@ -978,7 +1008,7 @@ extern "C" fn cluster_setup_emit_thunk( } fn defer_cluster_fork_event(worker: f64) { - let deferred = crate::closure::js_closure_alloc(cluster_fork_emit_thunk as *const u8, 1); + let deferred = crate::closure::js_closure_alloc(&infos::CLUSTER_FORK_EMIT_THUNK, 1); crate::closure::js_closure_set_capture_f64(deferred, 0, worker); crate::timer::js_set_immediate_callback(deferred as i64); } @@ -1157,22 +1187,22 @@ fn decorate_worker(worker: f64, id: u32) { register_listener( worker, "spawn", - closure_value(cluster_internal_online as *const u8, worker), + closure_value(&infos::CLUSTER_INTERNAL_ONLINE, worker), ); register_listener( worker, "disconnect", - closure_value(cluster_internal_disconnect as *const u8, worker), + closure_value(&infos::CLUSTER_INTERNAL_DISCONNECT, worker), ); register_listener( worker, "exit", - closure_value(cluster_internal_exit as *const u8, worker), + closure_value(&infos::CLUSTER_INTERNAL_EXIT, worker), ); register_listener( worker, "message", - closure_value(cluster_internal_message as *const u8, worker), + closure_value(&infos::CLUSTER_INTERNAL_MESSAGE, worker), ); } @@ -1334,7 +1364,7 @@ fn drain_disconnect_callbacks_if_idle() { }); for bits in callbacks { - let deferred = crate::closure::js_closure_alloc(cluster_callback_thunk as *const u8, 1); + let deferred = crate::closure::js_closure_alloc(&infos::CLUSTER_CALLBACK_THUNK, 1); crate::closure::js_closure_set_capture_ptr(deferred, 0, bits as i64); crate::timer::js_set_immediate_callback(deferred as i64); } @@ -1392,7 +1422,7 @@ fn mark_worker_disconnected(worker: f64) { } set_field(worker, b"__clusterDisconnectEmitted", TAG_TRUE_F64); set_field(worker, b"state", box_string("disconnected")); - let deferred = crate::closure::js_closure_alloc(cluster_disconnect_emit_thunk as *const u8, 1); + let deferred = crate::closure::js_closure_alloc(&infos::CLUSTER_DISCONNECT_EMIT_THUNK, 1); crate::closure::js_closure_set_capture_f64(deferred, 0, worker); crate::timer::js_set_immediate_callback(deferred as i64); } @@ -1425,7 +1455,7 @@ extern "C" fn cluster_internal_exit( crate::cluster_sched::primary_remove_worker(handle); } remove_worker(worker); - let deferred = crate::closure::js_closure_alloc(cluster_exit_emit_thunk as *const u8, 3); + let deferred = crate::closure::js_closure_alloc(&infos::CLUSTER_EXIT_EMIT_THUNK, 3); crate::closure::js_closure_set_capture_f64(deferred, 0, worker); crate::closure::js_closure_set_capture_f64(deferred, 1, code); crate::closure::js_closure_set_capture_f64(deferred, 2, signal); @@ -1501,7 +1531,7 @@ fn closure_this(closure: *const ClosureHeader) -> f64 { } } -fn closure_value(func: *const u8, captured: f64) -> f64 { +fn closure_value(func: *const crate::closure::JsFunctionInfo, captured: f64) -> f64 { let closure = crate::closure::js_closure_alloc(func, 1); crate::closure::js_closure_set_capture_f64(closure, 0, captured); box_ptr(closure as *const u8) diff --git a/crates/perry-runtime/src/dgram.rs b/crates/perry-runtime/src/dgram.rs index 4ce677afd4..a9c3c44b0a 100644 --- a/crates/perry-runtime/src/dgram.rs +++ b/crates/perry-runtime/src/dgram.rs @@ -15,7 +15,7 @@ use std::sync::{LazyLock, Mutex}; use crate::array::ArrayHeader; use crate::closure::{ js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_closure_set_capture_ptr, js_register_closure_rest, ClosureHeader, + js_closure_set_capture_ptr, ClosureHeader, }; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader, @@ -116,141 +116,167 @@ type MethodThunk = crate::closure::body_call::js_body_fn_ty!(a); struct MethodSpec { name: &'static str, - thunk: MethodThunk, + /// The method body's info: every socket method bundles its arguments + /// (`with_rest(0)`). + info: &'static crate::closure::JsFunctionInfo, } -const SOCKET_METHODS: &[MethodSpec] = &[ +static SOCKET_METHODS: &[MethodSpec] = &[ MethodSpec { name: "send", - thunk: dgram_send_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_send_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "sendto", - thunk: dgram_sendto_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_sendto_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "bind", - thunk: dgram_bind_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_bind_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "close", - thunk: dgram_close_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_close_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "address", - thunk: dgram_address_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_address_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "remoteAddress", - thunk: dgram_remote_address_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_remote_address_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "connect", - thunk: dgram_connect_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_connect_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "disconnect", - thunk: dgram_disconnect_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_disconnect_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "on", - thunk: dgram_on_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_on_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "addListener", - thunk: dgram_on_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_on_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "once", - thunk: dgram_once_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_once_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "off", - thunk: dgram_remove_listener_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_remove_listener_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "removeListener", - thunk: dgram_remove_listener_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_remove_listener_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "emit", - thunk: dgram_emit_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_emit_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "listenerCount", - thunk: dgram_listener_count_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_listener_count_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "eventNames", - thunk: dgram_event_names_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_event_names_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "addMembership", - thunk: dgram_add_membership_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_add_membership_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "dropMembership", - thunk: dgram_drop_membership_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_drop_membership_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "addSourceSpecificMembership", - thunk: dgram_add_source_membership_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_add_source_membership_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "dropSourceSpecificMembership", - thunk: dgram_drop_source_membership_thunk, + info: &crate::closure::JsFunctionInfo::of( + dgram_drop_source_membership_thunk as MethodThunk, + ) + .with_rest(0), }, MethodSpec { name: "setBroadcast", - thunk: dgram_set_broadcast_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_set_broadcast_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "setMulticastTTL", - thunk: dgram_set_multicast_ttl_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_set_multicast_ttl_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "setMulticastLoopback", - thunk: dgram_set_multicast_loopback_thunk, + info: &crate::closure::JsFunctionInfo::of( + dgram_set_multicast_loopback_thunk as MethodThunk, + ) + .with_rest(0), }, MethodSpec { name: "setMulticastInterface", - thunk: dgram_set_multicast_interface_thunk, + info: &crate::closure::JsFunctionInfo::of( + dgram_set_multicast_interface_thunk as MethodThunk, + ) + .with_rest(0), }, MethodSpec { name: "setTTL", - thunk: dgram_set_ttl_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_set_ttl_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "setRecvBufferSize", - thunk: dgram_set_recv_buffer_size_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_set_recv_buffer_size_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "setSendBufferSize", - thunk: dgram_set_send_buffer_size_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_set_send_buffer_size_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "getRecvBufferSize", - thunk: dgram_get_recv_buffer_size_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_get_recv_buffer_size_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "getSendBufferSize", - thunk: dgram_get_send_buffer_size_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_get_send_buffer_size_thunk as MethodThunk) + .with_rest(0), }, MethodSpec { name: "getSendQueueSize", - thunk: dgram_zero_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_zero_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "getSendQueueCount", - thunk: dgram_zero_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_zero_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "ref", - thunk: dgram_ref_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_ref_thunk as MethodThunk).with_rest(0), }, MethodSpec { name: "unref", - thunk: dgram_unref_thunk, + info: &crate::closure::JsFunctionInfo::of(dgram_unref_thunk as MethodThunk).with_rest(0), }, ]; @@ -523,11 +549,13 @@ pub(crate) fn socket_value_from_handle(handle: i64) -> f64 { } } -pub(crate) fn method_value(socket: f64, name: &str, thunk: MethodThunk) -> f64 { - let func_ptr = thunk as *const u8; - let closure = js_closure_alloc(func_ptr, 1); +pub(crate) fn method_value( + socket: f64, + name: &str, + info: &'static crate::closure::JsFunctionInfo, +) -> f64 { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, socket.to_bits() as i64); - js_register_closure_rest(func_ptr, 0); crate::object::set_bound_native_closure_name(closure, name); js_nanbox_pointer(closure as i64) } @@ -550,7 +578,7 @@ pub(crate) fn socket_object(socket_type: &str) -> f64 { js_object_set_field_by_name( obj, key(method.name), - method_value(socket, method.name, method.thunk), + method_value(socket, method.name, method.info), ); } let async_dispose = crate::symbol::well_known_symbol("asyncDispose"); @@ -592,8 +620,7 @@ extern "C" fn dgram_async_dispose( } fn async_dispose_method(socket: f64) -> f64 { - crate::closure::js_register_closure_arity(dgram_async_dispose as *const u8, 0); - let closure = js_closure_alloc(dgram_async_dispose as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(dgram_async_dispose, 0; with_declared(0)), 1); crate::closure::js_closure_set_capture_ptr(closure, 0, socket.to_bits() as i64); boxed_pointer(closure as *const u8) } diff --git a/crates/perry-runtime/src/dgram/net.rs b/crates/perry-runtime/src/dgram/net.rs index b335c9b44e..c92a9633fb 100644 --- a/crates/perry-runtime/src/dgram/net.rs +++ b/crates/perry-runtime/src/dgram/net.rs @@ -494,8 +494,10 @@ extern "C" fn deferred_send_callback( } fn defer_send_callback(callback: f64, socket: f64, error: f64, bytes: f64) { - crate::closure::js_register_closure_arity(deferred_send_callback as *const u8, 0); - let closure = crate::closure::js_closure_alloc(deferred_send_callback as *const u8, 4); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(deferred_send_callback, 0; with_declared(0)), + 4, + ); crate::closure::js_closure_set_capture_f64(closure, 0, callback); crate::closure::js_closure_set_capture_f64(closure, 1, socket); crate::closure::js_closure_set_capture_f64(closure, 2, error); diff --git a/crates/perry-runtime/src/dgram/ops.rs b/crates/perry-runtime/src/dgram/ops.rs index 76711a6fbf..10fc790cd1 100644 --- a/crates/perry-runtime/src/dgram/ops.rs +++ b/crates/perry-runtime/src/dgram/ops.rs @@ -23,8 +23,11 @@ extern "C" fn dgram_abort_close_task( fn schedule_abort_close(socket: f64) { let scope = crate::gc::RuntimeHandleScope::new(); let socket = scope.root_nanbox_f64(socket); - crate::closure::js_register_closure_arity(dgram_abort_close_task as *const u8, 0); - let task = js_closure_alloc(dgram_abort_close_task as *const u8, 1); + + let task = js_closure_alloc( + crate::fn_info!(dgram_abort_close_task, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(task, 0, socket.get_nanbox_f64()); crate::builtins::js_queue_microtask(task as i64); } @@ -52,8 +55,10 @@ fn attach_abort_signal(socket: f64, signal: f64) { return; } - crate::closure::js_register_closure_arity(dgram_abort_listener as *const u8, 0); - let listener = js_closure_alloc(dgram_abort_listener as *const u8, 1); + let listener = js_closure_alloc( + crate::fn_info!(dgram_abort_listener, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(listener, 0, socket.get_nanbox_f64()); let listener = scope.root_nanbox_f64(boxed_pointer(listener as *const u8)); let abort_event = scope.root_nanbox_f64(str_value("abort")); @@ -500,8 +505,7 @@ extern "C" fn lookup_callback( } fn invoke_lookup(socket: f64, lookup: f64, address: &str) { - crate::closure::js_register_closure_rest(lookup_callback as *const u8, 0); - let callback = js_closure_alloc(lookup_callback as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(lookup_callback, 1; with_rest(0)), 0); let family = if address.contains(':') { 6.0 } else { 4.0 }; call_function( lookup, diff --git a/crates/perry-runtime/src/disposable.rs b/crates/perry-runtime/src/disposable.rs index 426bce4e2b..9d5734b1fb 100644 --- a/crates/perry-runtime/src/disposable.rs +++ b/crates/perry-runtime/src/disposable.rs @@ -32,8 +32,7 @@ use crate::array::{js_array_alloc, js_array_get_f64, js_array_length, js_array_push_f64}; use crate::closure::{ is_closure_ptr, js_closure_alloc, js_closure_call0, js_closure_get_capture_ptr, - js_closure_set_capture_ptr, js_register_closure_arity, native_call_value_this, ClosureHeader, - JsThis, + js_closure_set_capture_ptr, native_call_value_this, ClosureHeader, JsThis, }; use crate::object::{ js_object_alloc, js_object_get_field_f64, js_object_set_field_by_name, js_object_set_field_f64, @@ -111,9 +110,7 @@ extern "C" fn bound_dispose_thunk( } fn make_bound_dispose_thunk(method: f64, resource: f64) -> f64 { - let func = bound_dispose_thunk as *const u8; - js_register_closure_arity(func, 0); - let closure = js_closure_alloc(func, 2); + let closure = js_closure_alloc(crate::fn_info!(bound_dispose_thunk, 0; with_declared(0)), 2); if closure.is_null() { return undefined(); } @@ -262,9 +259,10 @@ extern "C" fn adopt_disposer_thunk( } fn make_adopt_disposer(value: f64, on_dispose: f64) -> f64 { - let func = adopt_disposer_thunk as *const u8; - js_register_closure_arity(func, 0); - let closure = js_closure_alloc(func, 2); + let closure = js_closure_alloc( + crate::fn_info!(adopt_disposer_thunk, 0; with_declared(0)), + 2, + ); if closure.is_null() { return undefined(); } diff --git a/crates/perry-runtime/src/dns.rs b/crates/perry-runtime/src/dns.rs index ac25e4c635..c175f821bc 100644 --- a/crates/perry-runtime/src/dns.rs +++ b/crates/perry-runtime/src/dns.rs @@ -13,7 +13,7 @@ use std::sync::{LazyLock, Mutex}; use crate::dns_resolver::{self, DnsError, QueryType}; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{js_object_alloc, js_object_set_field_by_name, ObjectHeader}; use crate::value::{js_nanbox_pointer, JSValue, TAG_NULL, TAG_UNDEFINED}; @@ -1243,14 +1243,13 @@ extern "C" fn dns_resolver_set_servers_thunk( } fn method_value(name: &str) -> f64 { - let (func_ptr, arity) = match name { - "getServers" => (dns_resolver_get_servers_thunk as *const u8, 0), - "setServers" => (dns_resolver_set_servers_thunk as *const u8, 1), - "setLocalAddress" => (dns_noop2_thunk as *const u8, 2), - _ => (dns_noop_thunk as *const u8, 0), + let info = match name { + "getServers" => crate::fn_info!(dns_resolver_get_servers_thunk, 0; with_declared(0)), + "setServers" => crate::fn_info!(dns_resolver_set_servers_thunk, 1; with_declared(1)), + "setLocalAddress" => crate::fn_info!(dns_noop2_thunk, 2; with_declared(2)), + _ => crate::fn_info!(dns_noop_thunk, 0; with_declared(0)), }; - let closure = js_closure_alloc(func_ptr, 0); - js_register_closure_arity(func_ptr, arity); + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); js_nanbox_pointer(closure as i64) } diff --git a/crates/perry-runtime/src/dyn_eval/interp.rs b/crates/perry-runtime/src/dyn_eval/interp.rs index 58f74b7c93..e82cefe96d 100644 --- a/crates/perry-runtime/src/dyn_eval/interp.rs +++ b/crates/perry-runtime/src/dyn_eval/interp.rs @@ -231,7 +231,10 @@ pub(crate) fn alloc_interp_closure( let this_idx = root_push(lexical_this.unwrap_or(f64::from_bits(NO_LEXICAL_THIS))); let global_idx = root_push(global); let intrinsics_idx = root_push(intrinsics); - let closure = crate::closure::js_closure_alloc(interp_thunk as *const u8, 7); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(unwind_in_tests interp_thunk, 1; with_rest(0)), + 7, + ); if closure.is_null() { roots_truncate(env_idx); bridge::throw_range_error("out of memory allocating dynamic function"); @@ -261,7 +264,6 @@ fn ensure_thunk_registered() { if registered.replace(true) { return; } - crate::closure::js_register_closure_rest(interp_thunk as *const u8, 0); }); } diff --git a/crates/perry-runtime/src/dyn_eval/tests.rs b/crates/perry-runtime/src/dyn_eval/tests.rs index 5b96d8ec9a..2f30102eb3 100644 --- a/crates/perry-runtime/src/dyn_eval/tests.rs +++ b/crates/perry-runtime/src/dyn_eval/tests.rs @@ -469,8 +469,7 @@ fn as_num_raw(v: f64) -> f64 { } fn host_double_fn() -> f64 { - let fp = host_double_thunk as *const u8; - crate::closure::js_register_closure_arity(fp, 1); + let fp = crate::fn_info!(host_double_thunk, 1; with_declared(1)); let closure = crate::closure::js_closure_alloc_singleton(fp); crate::value::js_nanbox_pointer(closure as i64) } diff --git a/crates/perry-runtime/src/error.rs b/crates/perry-runtime/src/error.rs index f0321eeea7..128c11aa45 100644 --- a/crates/perry-runtime/src/error.rs +++ b/crates/perry-runtime/src/error.rs @@ -1218,9 +1218,12 @@ extern "C" fn callsite_to_string( crate::value::js_nanbox_string(s as i64) } -fn attach_call_site_method(obj: *mut crate::object::ObjectHeader, name: &str, fp: *const u8) { - crate::closure::js_register_closure_arity(fp, 0); - // Singleton (func_ptr-keyed, permanent) closure — never collected/moved, so +fn attach_call_site_method( + obj: *mut crate::object::ObjectHeader, + name: &str, + fp: *const crate::closure::JsFunctionInfo, +) { + // Singleton (per body, permanent) closure — never collected/moved, so // the CallSite's method field stays valid across GC cycles. let closure = crate::closure::js_closure_alloc_singleton(fp); if closure.is_null() { @@ -1233,11 +1236,11 @@ fn attach_call_site_method(obj: *mut crate::object::ObjectHeader, name: &str, fp fn make_call_site() -> f64 { let obj = crate::object::js_object_alloc(0, 20); - let undef = callsite_undefined as *const u8; - let null = callsite_null as *const u8; - let zero = callsite_zero as *const u8; - let f = callsite_false as *const u8; - let t = callsite_true as *const u8; + let undef = crate::fn_info!(callsite_undefined, 0; with_declared(0)); + let null = crate::fn_info!(callsite_null, 0; with_declared(0)); + let zero = crate::fn_info!(callsite_zero, 0; with_declared(0)); + let f = crate::fn_info!(callsite_false, 0; with_declared(0)); + let t = crate::fn_info!(callsite_true, 0; with_declared(0)); attach_call_site_method(obj, "getFileName", undef); attach_call_site_method(obj, "getScriptNameOrSourceURL", undef); attach_call_site_method(obj, "getLineNumber", zero); @@ -1257,7 +1260,11 @@ fn make_call_site() -> f64 { attach_call_site_method(obj, "isConstructor", f); attach_call_site_method(obj, "isAsync", f); attach_call_site_method(obj, "isPromiseAll", f); - attach_call_site_method(obj, "toString", callsite_to_string as *const u8); + attach_call_site_method( + obj, + "toString", + crate::fn_info!(callsite_to_string, 0; with_declared(0)), + ); crate::value::js_nanbox_pointer(obj as i64) } @@ -1294,7 +1301,7 @@ fn error_prepare_stack_trace_override() -> Option { if !crate::closure::is_closure_ptr(ptr) { return None; } - let fp = unsafe { (*(ptr as *const crate::closure::ClosureHeader)).func_ptr } as usize; + let fp = unsafe { (*(ptr as *const crate::closure::ClosureHeader)).code() } as usize; if fp == crate::object::default_prepare_stack_trace_func_ptr() { return None; } @@ -1336,8 +1343,7 @@ extern "C" fn error_stack_lazy_getter( /// Install the lazy `stack` getter accessor on `target`. unsafe fn install_lazy_stack_accessor(target_ptr: *mut crate::object::ObjectHeader) { - let fp = error_stack_lazy_getter as *const u8; - crate::closure::js_register_closure_arity(fp, 0); + let fp = crate::fn_info!(error_stack_lazy_getter, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(fp, 0); if closure.is_null() { return; diff --git a/crates/perry-runtime/src/error_subclass_stack.rs b/crates/perry-runtime/src/error_subclass_stack.rs index 9224b05191..df6dc83295 100644 --- a/crates/perry-runtime/src/error_subclass_stack.rs +++ b/crates/perry-runtime/src/error_subclass_stack.rs @@ -244,16 +244,18 @@ pub extern "C" fn js_error_subclass_capture_stack(this_val: f64) { } let frame_handle = scope.root_string_ptr(frame_ptr); - let getter_fn = error_subclass_stack_getter as *const u8; - let setter_fn = error_subclass_stack_setter as *const u8; - crate::closure::js_register_closure_arity(getter_fn, 0); - crate::closure::js_register_closure_arity(setter_fn, 1); - let getter = crate::closure::js_closure_alloc(getter_fn, 1); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(error_subclass_stack_getter, 0; with_declared(0)), + 1, + ); if getter.is_null() { return; } let getter_handle = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(getter as i64)); - let setter = crate::closure::js_closure_alloc(setter_fn, 0); + let setter = crate::closure::js_closure_alloc( + crate::fn_info!(error_subclass_stack_setter, 1; with_declared(1)), + 0, + ); if setter.is_null() { return; } diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index 1f0cee8846..890dd9fa24 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -207,14 +207,12 @@ extern "C" fn event_stop_immediate_propagation_thunk( fn install_event_method( event: *mut ObjectHeader, name: &str, - func: crate::closure::body_call::js_body_fn_ty!(), + info: *const crate::closure::JsFunctionInfo, ) { - let func_ptr = func as *const u8; - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); let name_ptr = js_string_from_bytes(name.as_ptr(), name.len() as u32); crate::closure::closure_set_dynamic_prop( closure as usize, @@ -289,12 +287,20 @@ fn init_event_fields( constructor_name.len(), ); set_event_field(event, b"constructor", ctor); - install_event_method(event, "preventDefault", event_prevent_default_thunk); - install_event_method(event, "stopPropagation", event_stop_propagation_thunk); + install_event_method( + event, + "preventDefault", + crate::fn_info!(event_prevent_default_thunk, 0; with_declared(0)), + ); + install_event_method( + event, + "stopPropagation", + crate::fn_info!(event_stop_propagation_thunk, 0; with_declared(0)), + ); install_event_method( event, "stopImmediatePropagation", - event_stop_immediate_propagation_thunk, + crate::fn_info!(event_stop_immediate_propagation_thunk, 0; with_declared(0)), ); } @@ -912,9 +918,10 @@ pub unsafe extern "C" fn js_event_target_add_event_listener_with_options( js_object_set_field_by_name(bag, event_name_ptr, boxed_ptr(updated)); } if let Some(signal) = listener_signal(options) { - let func = event_target_abort_remove_listener as *const u8; - crate::closure::js_register_closure_arity(func, 0); - let abort_listener = crate::closure::js_closure_alloc(func, 4); + let abort_listener = crate::closure::js_closure_alloc( + crate::fn_info!(event_target_abort_remove_listener, 0; with_declared(0)), + 4, + ); crate::closure::js_closure_set_capture_ptr(abort_listener, 0, target as i64); crate::closure::js_closure_set_capture_ptr(abort_listener, 1, event_name_ptr as i64); crate::closure::js_closure_set_capture_ptr(abort_listener, 2, callback_ptr); @@ -1284,8 +1291,11 @@ pub(crate) fn install_web_event_proto_methods(name: &str, proto_obj: *mut Object let proto_h = scope.root_nanbox_f64(boxed_ptr(proto_obj)); let proto = || crate::value::js_nanbox_get_pointer(proto_h.get_nanbox_f64()) as *mut ObjectHeader; - let install = |method: &str, func: *const u8, call_arity: u32, spec_length: u32| { - let value = install_proto_method(proto(), method, func, call_arity); + let install = |method: &str, + info: *const crate::closure::JsFunctionInfo, + call_arity: u32, + spec_length: u32| { + let value = install_proto_method(proto(), method, info, call_arity); let closure = crate::value::js_nanbox_get_pointer(value) as usize; if closure != 0 { crate::object::native_module::set_builtin_closure_length(closure, spec_length); @@ -1310,46 +1320,51 @@ pub(crate) fn install_web_event_proto_methods(name: &str, proto_obj: *mut Object "EventTarget" => { install( "addEventListener", - event_target_add_event_listener_thunk as *const u8, + crate::fn_info!(event_target_add_event_listener_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), 3, 2, ); install( "removeEventListener", - event_target_remove_event_listener_thunk as *const u8, + crate::fn_info!(event_target_remove_event_listener_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), 3, 2, ); install( "dispatchEvent", - event_target_dispatch_event_thunk as *const u8, + crate::fn_info!(event_target_dispatch_event_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, 1, ); } "Event" => { - install("initEvent", event_proto_init_event_thunk as *const u8, 3, 1); + install( + "initEvent", + crate::fn_info!(event_proto_init_event_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + 1, + ); install( "stopImmediatePropagation", - event_proto_stop_immediate_propagation_thunk as *const u8, + crate::fn_info!(event_proto_stop_immediate_propagation_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, 0, ); install( "preventDefault", - event_proto_prevent_default_thunk as *const u8, + crate::fn_info!(event_proto_prevent_default_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, 0, ); install( "composedPath", - event_proto_composed_path_thunk as *const u8, + crate::fn_info!(event_proto_composed_path_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, 0, ); install( "stopPropagation", - event_proto_stop_propagation_thunk as *const u8, + crate::fn_info!(event_proto_stop_propagation_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, 0, ); @@ -1437,20 +1452,22 @@ pub(crate) fn is_event_target_method_name(name: &[u8]) -> bool { /// Returns `None` for any other name or receiver, so an unknown property still /// reads as `undefined` and a non-target object keeps its existing dispatch. pub(crate) fn event_target_method_bind(target: *mut ObjectHeader, name: &[u8]) -> Option { - let (func, arity): (*const u8, u32) = match name { - b"addEventListener" => (event_target_add_event_listener_thunk as *const u8, 3), - b"removeEventListener" => (event_target_remove_event_listener_thunk as *const u8, 3), - b"dispatchEvent" => (event_target_dispatch_event_thunk as *const u8, 1), + use crate::fn_info; + let info = match name { + b"addEventListener" => fn_info!(event_target_add_event_listener_thunk, 3; with_declared(3)), + b"removeEventListener" => { + fn_info!(event_target_remove_event_listener_thunk, 3; with_declared(3)) + } + b"dispatchEvent" => fn_info!(event_target_dispatch_event_thunk, 1; with_declared(1)), _ => return None, }; if !unsafe { is_event_target(target) } { return None; } - crate::closure::js_register_closure_arity(func, arity); // `js_closure_alloc` can GC-move the receiver — root it and re-read. let scope = crate::gc::RuntimeHandleScope::new(); let target_handle = scope.root_raw_mut_ptr(target); - let closure = crate::closure::js_closure_alloc(func, 1); + let closure = crate::closure::js_closure_alloc(info, 1); let target_bits = boxed_ptr(target_handle.get_raw_mut_ptr::()).to_bits(); crate::closure::js_closure_set_capture_ptr(closure, 0, target_bits as i64); Some(boxed_ptr(closure)) diff --git a/crates/perry-runtime/src/ffi/perry_wasi_sig.c b/crates/perry-runtime/src/ffi/perry_wasi_sig.c deleted file mode 100644 index e685919f53..0000000000 --- a/crates/perry-runtime/src/ffi/perry_wasi_sig.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * WASI only (#11379): report how many f64 arguments a closure body takes. - * - * Native C ABIs let the runtime call a closure body with more arguments than - * it declares (the extras are ignored) or fewer (it reads garbage the dispatch - * layer pads anyway). A wasm `call_indirect` whose type differs from its - * target's traps, and a Perry closure body's real parameter count is not - * always what the runtime registered for it: runtime thunks are declared with - * whatever signature suits them and registered with their JS `.length`. - * - * `__builtin_wasm_test_function_pointer_signature` compiles to `ref.test` - * (the GC proposal, hence `-mgc` on this file only), which asks the engine for - * the function's actual type. The dispatch layer calls this once per body and - * caches the answer, then always calls with exactly that many arguments. - * - * Every JS body takes the receiver (`this`, `perry_abi::JsThis`, a NaN-boxed - * `u64`) as its second native parameter (this-as-a-parameter, #11637): the - * candidate types below must carry that `unsigned long long` between the - * closure pointer and the `double` arguments, or none of them ever matches a - * real body's type and every WASI dispatch falls back to the caller's raw - * argument count. - */ - -typedef struct ClosureHeader ClosureHeader; - -typedef double (*perry_body_0)(const ClosureHeader *, unsigned long long); -typedef double (*perry_body_1)(const ClosureHeader *, unsigned long long, double); -typedef double (*perry_body_2)(const ClosureHeader *, unsigned long long, double, double); -typedef double (*perry_body_3)(const ClosureHeader *, unsigned long long, double, double, double); -typedef double (*perry_body_4)(const ClosureHeader *, unsigned long long, double, double, double, double); -typedef double (*perry_body_5)(const ClosureHeader *, unsigned long long, double, double, double, double, double); -typedef double (*perry_body_6)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double); -typedef double (*perry_body_7)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double); -typedef double (*perry_body_8)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double); -typedef double (*perry_body_9)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_10)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_11)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_12)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_13)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_14)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_15)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_16)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_17)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_18)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_19)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_20)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_21)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_22)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_23)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_24)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_25)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_26)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_27)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_28)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_29)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_30)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_31)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_32)(const ClosureHeader *, unsigned long long, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); - -/* The body's f64 parameter count after the closure pointer and the receiver, - * or -1 if its type is none of `double (closure, this, double x k)` for k in - * 0..=32. */ -int perry_wasi_closure_params(const void *fp) { - if (__builtin_wasm_test_function_pointer_signature((perry_body_0)fp)) return 0; - if (__builtin_wasm_test_function_pointer_signature((perry_body_1)fp)) return 1; - if (__builtin_wasm_test_function_pointer_signature((perry_body_2)fp)) return 2; - if (__builtin_wasm_test_function_pointer_signature((perry_body_3)fp)) return 3; - if (__builtin_wasm_test_function_pointer_signature((perry_body_4)fp)) return 4; - if (__builtin_wasm_test_function_pointer_signature((perry_body_5)fp)) return 5; - if (__builtin_wasm_test_function_pointer_signature((perry_body_6)fp)) return 6; - if (__builtin_wasm_test_function_pointer_signature((perry_body_7)fp)) return 7; - if (__builtin_wasm_test_function_pointer_signature((perry_body_8)fp)) return 8; - if (__builtin_wasm_test_function_pointer_signature((perry_body_9)fp)) return 9; - if (__builtin_wasm_test_function_pointer_signature((perry_body_10)fp)) return 10; - if (__builtin_wasm_test_function_pointer_signature((perry_body_11)fp)) return 11; - if (__builtin_wasm_test_function_pointer_signature((perry_body_12)fp)) return 12; - if (__builtin_wasm_test_function_pointer_signature((perry_body_13)fp)) return 13; - if (__builtin_wasm_test_function_pointer_signature((perry_body_14)fp)) return 14; - if (__builtin_wasm_test_function_pointer_signature((perry_body_15)fp)) return 15; - if (__builtin_wasm_test_function_pointer_signature((perry_body_16)fp)) return 16; - if (__builtin_wasm_test_function_pointer_signature((perry_body_17)fp)) return 17; - if (__builtin_wasm_test_function_pointer_signature((perry_body_18)fp)) return 18; - if (__builtin_wasm_test_function_pointer_signature((perry_body_19)fp)) return 19; - if (__builtin_wasm_test_function_pointer_signature((perry_body_20)fp)) return 20; - if (__builtin_wasm_test_function_pointer_signature((perry_body_21)fp)) return 21; - if (__builtin_wasm_test_function_pointer_signature((perry_body_22)fp)) return 22; - if (__builtin_wasm_test_function_pointer_signature((perry_body_23)fp)) return 23; - if (__builtin_wasm_test_function_pointer_signature((perry_body_24)fp)) return 24; - if (__builtin_wasm_test_function_pointer_signature((perry_body_25)fp)) return 25; - if (__builtin_wasm_test_function_pointer_signature((perry_body_26)fp)) return 26; - if (__builtin_wasm_test_function_pointer_signature((perry_body_27)fp)) return 27; - if (__builtin_wasm_test_function_pointer_signature((perry_body_28)fp)) return 28; - if (__builtin_wasm_test_function_pointer_signature((perry_body_29)fp)) return 29; - if (__builtin_wasm_test_function_pointer_signature((perry_body_30)fp)) return 30; - if (__builtin_wasm_test_function_pointer_signature((perry_body_31)fp)) return 31; - if (__builtin_wasm_test_function_pointer_signature((perry_body_32)fp)) return 32; - return -1; -} diff --git a/crates/perry-runtime/src/fs/deferred.rs b/crates/perry-runtime/src/fs/deferred.rs index dbc8d5786e..99e958a200 100644 --- a/crates/perry-runtime/src/fs/deferred.rs +++ b/crates/perry-runtime/src/fs/deferred.rs @@ -140,7 +140,7 @@ fn park(path: f64, data: f64, options: f64, sink: f64, mode: f64) { let data_handle = scope.root_nanbox_f64(data); let options_handle = scope.root_nanbox_f64(options); let sink_handle = scope.root_nanbox_f64(sink); - let closure = js_closure_alloc(perform_deferred_fs_op as *const u8, CAPTURE_COUNT); + let closure = js_closure_alloc(crate::fn_info!(perform_deferred_fs_op, 0), CAPTURE_COUNT); js_closure_set_capture_f64(closure, CAP_PATH, path_handle.get_nanbox_f64()); js_closure_set_capture_f64(closure, CAP_DATA, data_handle.get_nanbox_f64()); js_closure_set_capture_f64(closure, CAP_OPTIONS, options_handle.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs index 950673ca4a..9cb471e111 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -20,8 +20,7 @@ use std::sync::Once; use super::watch_backend::{self, Backend, RawEvent, Source, WatchError, WatchEvent}; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader, }; const WATCH_FILE_DEFAULT_INTERVAL_MS: f64 = 5007.0; @@ -228,15 +227,11 @@ fn signal_abort_reason(signal: f64) -> f64 { } } -fn add_abort_listener( - signal: f64, - id: usize, - func: crate::closure::body_call::js_body_fn_ty!(), -) -> f64 { +fn add_abort_listener(signal: f64, id: usize, func: *const crate::closure::JsFunctionInfo) -> f64 { let Some(signal_ptr) = crate::url::abort::abort_signal_ptr_from_value(signal) else { return undefined_value(); }; - let closure = js_closure_alloc(func as *const u8, 1); + let closure = js_closure_alloc(func, 1); js_closure_set_capture_f64(closure, 0, id as f64); let listener = boxed_ptr(closure as *const u8); crate::url::js_abort_signal_add_listener(signal_ptr, string_value(b"abort"), listener); @@ -1327,155 +1322,198 @@ extern "C" fn promise_watcher_self_impl( js_closure_get_capture_f64(closure, 1) } -fn ensure_watch_method_arities() { - static REGISTER: Once = Once::new(); - REGISTER.call_once(|| { - js_register_closure_arity(watch_file_poll_impl as *const u8, 0); - js_register_closure_arity(fs_watcher_abort_impl as *const u8, 0); - js_register_closure_arity(promise_watcher_abort_impl as *const u8, 0); - js_register_closure_arity(fs_watcher_close_impl as *const u8, 0); - js_register_closure_arity(fs_watcher_ref_impl as *const u8, 0); - js_register_closure_arity(fs_watcher_unref_impl as *const u8, 0); - js_register_closure_arity(fs_watcher_on_impl as *const u8, 2); - js_register_closure_arity(fs_watcher_once_impl as *const u8, 2); - js_register_closure_arity(fs_watcher_off_impl as *const u8, 2); - js_register_closure_arity(stat_watcher_ref_impl as *const u8, 0); - js_register_closure_arity(stat_watcher_unref_impl as *const u8, 0); - js_register_closure_arity(stat_watcher_on_impl as *const u8, 2); - js_register_closure_arity(stat_watcher_once_impl as *const u8, 2); - js_register_closure_arity(stat_watcher_off_impl as *const u8, 2); - js_register_closure_arity(promise_watcher_next_impl as *const u8, 0); - js_register_closure_arity(promise_watcher_return_impl as *const u8, 0); - js_register_closure_arity(promise_watcher_self_impl as *const u8, 0); - js_register_closure_arity(glob_iterator_next_impl as *const u8, 0); - js_register_closure_arity(glob_iterator_return_impl as *const u8, 0); - js_register_closure_arity(glob_iterator_self_impl as *const u8, 0); - js_register_closure_arity(bun_glob_sync_iterator_next_impl as *const u8, 0); - js_register_closure_arity(bun_glob_sync_iterator_return_impl as *const u8, 0); - js_register_closure_arity(bun_glob_sync_iterator_self_impl as *const u8, 0); - }); -} - -fn method_value(func: *const u8, id: usize, self_value: f64) -> f64 { +fn method_value(func: *const crate::closure::JsFunctionInfo, id: usize, self_value: f64) -> f64 { let closure = js_closure_alloc(func, 2); js_closure_set_capture_f64(closure, 0, id as f64); js_closure_set_capture_f64(closure, 1, self_value); boxed_ptr(closure as *const u8) } -fn poll_closure_value(func: *const u8, id: usize) -> *mut ClosureHeader { +fn poll_closure_value( + func: *const crate::closure::JsFunctionInfo, + id: usize, +) -> *mut ClosureHeader { let closure = js_closure_alloc(func, 1); js_closure_set_capture_f64(closure, 0, id as f64); closure } fn build_fs_watcher_object(id: usize) -> f64 { - ensure_watch_method_arities(); let obj = crate::object::js_object_alloc(0, 8); let self_value = boxed_ptr(obj as *const u8); set_named_field( obj, b"close", - method_value(fs_watcher_close_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_close_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"ref", - method_value(fs_watcher_ref_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_ref_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"unref", - method_value(fs_watcher_unref_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_unref_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"on", - method_value(fs_watcher_on_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_on_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"once", - method_value(fs_watcher_once_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_once_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"addListener", - method_value(fs_watcher_on_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_on_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"removeListener", - method_value(fs_watcher_off_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_off_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"off", - method_value(fs_watcher_off_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(fs_watcher_off_impl, 2; with_declared(2)), + id, + self_value, + ), ); self_value } fn build_stat_watcher_object(id: usize) -> f64 { - ensure_watch_method_arities(); let obj = crate::object::js_object_alloc(0, 7); let self_value = boxed_ptr(obj as *const u8); set_named_field( obj, b"ref", - method_value(stat_watcher_ref_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_ref_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"unref", - method_value(stat_watcher_unref_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_unref_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"on", - method_value(stat_watcher_on_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_on_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"once", - method_value(stat_watcher_once_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_once_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"addListener", - method_value(stat_watcher_on_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_on_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"removeListener", - method_value(stat_watcher_off_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_off_impl, 2; with_declared(2)), + id, + self_value, + ), ); set_named_field( obj, b"off", - method_value(stat_watcher_off_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(stat_watcher_off_impl, 2; with_declared(2)), + id, + self_value, + ), ); self_value } fn build_promise_watcher_object(id: usize) -> f64 { - ensure_watch_method_arities(); let obj = crate::object::js_object_alloc(0, 2); let self_value = boxed_ptr(obj as *const u8); set_named_field( obj, b"next", - method_value(promise_watcher_next_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(promise_watcher_next_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"return", - method_value(promise_watcher_return_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(promise_watcher_return_impl, 0; with_declared(0)), + id, + self_value, + ), ); let async_iterator = crate::symbol::well_known_symbol("asyncIterator"); if !async_iterator.is_null() { let symbol_value = boxed_ptr(async_iterator as *const u8); - let method = method_value(promise_watcher_self_impl as *const u8, id, self_value); + let method = method_value( + crate::fn_info!(promise_watcher_self_impl, 0; with_declared(0)), + id, + self_value, + ); unsafe { crate::symbol::js_object_set_symbol_property(self_value, symbol_value, method); } @@ -1484,23 +1522,34 @@ fn build_promise_watcher_object(id: usize) -> f64 { } fn build_glob_iterator_object(id: usize) -> f64 { - ensure_watch_method_arities(); let obj = crate::object::js_object_alloc(0, 3); let self_value = boxed_ptr(obj as *const u8); set_named_field( obj, b"next", - method_value(glob_iterator_next_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(glob_iterator_next_impl, 0; with_declared(0)), + id, + self_value, + ), ); set_named_field( obj, b"return", - method_value(glob_iterator_return_impl as *const u8, id, self_value), + method_value( + crate::fn_info!(glob_iterator_return_impl, 0; with_declared(0)), + id, + self_value, + ), ); let async_iterator = crate::symbol::well_known_symbol("asyncIterator"); if !async_iterator.is_null() { let symbol_value = boxed_ptr(async_iterator as *const u8); - let method = method_value(glob_iterator_self_impl as *const u8, id, self_value); + let method = method_value( + crate::fn_info!(glob_iterator_self_impl, 0; with_declared(0)), + id, + self_value, + ); unsafe { crate::symbol::js_object_set_symbol_property(self_value, symbol_value, method); } @@ -1509,14 +1558,13 @@ fn build_glob_iterator_object(id: usize) -> f64 { } fn build_bun_glob_sync_iterator_object(id: usize) -> f64 { - ensure_watch_method_arities(); let obj = crate::object::js_object_alloc(0, 3); let self_value = boxed_ptr(obj as *const u8); set_named_field( obj, b"next", method_value( - bun_glob_sync_iterator_next_impl as *const u8, + crate::fn_info!(bun_glob_sync_iterator_next_impl, 0; with_declared(0)), id, self_value, ), @@ -1525,7 +1573,7 @@ fn build_bun_glob_sync_iterator_object(id: usize) -> f64 { obj, b"return", method_value( - bun_glob_sync_iterator_return_impl as *const u8, + crate::fn_info!(bun_glob_sync_iterator_return_impl, 0; with_declared(0)), id, self_value, ), @@ -1534,7 +1582,7 @@ fn build_bun_glob_sync_iterator_object(id: usize) -> f64 { if !iterator.is_null() { let symbol_value = boxed_ptr(iterator as *const u8); let method = method_value( - bun_glob_sync_iterator_self_impl as *const u8, + crate::fn_info!(bun_glob_sync_iterator_self_impl, 0; with_declared(0)), id, self_value, ); @@ -1649,7 +1697,7 @@ pub extern "C" fn js_fs_watch(path_value: f64, arg1: f64, arg2: f64) -> f64 { ensure_pump_registered(); let backend = Backend::start(id, &path, recursive); let abort_listener = signal - .map(|signal| add_abort_listener(signal, id, fs_watcher_abort_impl)) + .map(|signal| add_abort_listener(signal, id, crate::fn_info!(fs_watcher_abort_impl, 0))) .unwrap_or_else(undefined_value); let signal_value = signal.unwrap_or_else(undefined_value); let mut listeners = HashMap::new(); @@ -1713,7 +1761,10 @@ pub extern "C" fn js_fs_watch_file(path_value: f64, arg1: f64, arg2: f64) -> f64 let interval = option_interval_ms(options_value); let persistent = option_bool_default_local(options_value, b"persistent", true); let bigint = unsafe { options_bool_field(options_value, b"bigint") }; - let timer_callback = poll_closure_value(watch_file_poll_impl as *const u8, id); + let timer_callback = poll_closure_value( + crate::fn_info!(watch_file_poll_impl, 0; with_declared(0)), + id, + ); let timer_id = crate::timer::setInterval(timer_callback as i64, interval); if !persistent { crate::timer::js_timer_unref(timer_id); @@ -1795,7 +1846,9 @@ pub extern "C" fn js_fs_promises_watch(path_value: f64, options_value: f64) -> f let object_value = build_promise_watcher_object(id); let abort_listener = signal .filter(|signal| !signal_is_aborted(*signal)) - .map(|signal| add_abort_listener(signal, id, promise_watcher_abort_impl)) + .map(|signal| { + add_abort_listener(signal, id, crate::fn_info!(promise_watcher_abort_impl, 0)) + }) .unwrap_or_else(undefined_value); let signal_value = signal.unwrap_or_else(undefined_value); let abort_reason = if signal.map(signal_is_aborted).unwrap_or(false) { diff --git a/crates/perry-runtime/src/fs/fd_ops.rs b/crates/perry-runtime/src/fs/fd_ops.rs index 4309d106ab..cdd420390a 100644 --- a/crates/perry-runtime/src/fs/fd_ops.rs +++ b/crates/perry-runtime/src/fs/fd_ops.rs @@ -910,7 +910,7 @@ pub(crate) fn dir_read_next(id: usize) -> f64 { } } -pub(crate) fn make_dir_method(id: usize, func: *const u8) -> f64 { +pub(crate) fn make_dir_method(id: usize, func: *const crate::closure::JsFunctionInfo) -> f64 { let closure = crate::closure::js_closure_alloc(func, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) @@ -982,7 +982,10 @@ fn dir_pending_read_promise(id: usize, result: Result, f64>) -> f64 Ok(None) => (f64::from_bits(TAG_NULL), false), Err(err) => (err, true), }; - let closure = crate::closure::js_closure_alloc(dir_finish_read_promise_impl as *const u8, 4); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(dir_finish_read_promise_impl, 0; with_declared(0)), + 4, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, promise as i64); crate::closure::js_closure_set_capture_f64(closure, 2, value); @@ -1041,7 +1044,10 @@ fn dir_schedule_read_callback( Ok(None) => (f64::from_bits(TAG_NULL), 1.0), Err(err) => (err, 2.0), }; - let closure = crate::closure::js_closure_alloc(dir_finish_read_callback_impl as *const u8, 4); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(dir_finish_read_callback_impl, 0; with_declared(0)), + 4, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, callback as i64); crate::closure::js_closure_set_capture_f64(closure, 2, value); @@ -1128,7 +1134,11 @@ pub(crate) extern "C" fn dir_close_impl( } } -fn dir_iterator_method(id: usize, self_value: f64, func: *const u8) -> f64 { +fn dir_iterator_method( + id: usize, + self_value: f64, + func: *const crate::closure::JsFunctionInfo, +) -> f64 { let closure = crate::closure::js_closure_alloc(func, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); crate::closure::js_closure_set_capture_f64(closure, 1, self_value); @@ -1246,16 +1256,28 @@ unsafe fn build_dir_iterator_object(id: usize) -> f64 { }; set( "next", - dir_iterator_method(id, self_value, dir_iterator_next_impl as *const u8), + dir_iterator_method( + id, + self_value, + crate::fn_info!(dir_iterator_next_impl, 0; with_declared(0)), + ), ); set( "return", - dir_iterator_method(id, self_value, dir_iterator_return_impl as *const u8), + dir_iterator_method( + id, + self_value, + crate::fn_info!(dir_iterator_return_impl, 0; with_declared(0)), + ), ); install_dir_symbol( self_value, "asyncIterator", - dir_iterator_method(id, self_value, dir_iterator_self_impl as *const u8), + dir_iterator_method( + id, + self_value, + crate::fn_info!(dir_iterator_self_impl, 0; with_declared(0)), + ), ); self_value } @@ -1275,10 +1297,12 @@ unsafe fn install_dir_proto_method( } unsafe fn install_dir_proto_path(proto: *mut crate::object::ObjectHeader, path: &str) { - crate::closure::js_register_closure_arity(dir_path_getter_impl as *const u8, 0); let path_ptr = js_string_from_bytes(path.as_ptr(), path.len() as u32); let path_value = crate::value::js_nanbox_string(path_ptr as i64); - let getter = crate::closure::js_closure_alloc(dir_path_getter_impl as *const u8, 1); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(dir_path_getter_impl, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(getter, 0, path_value); let getter_value = crate::value::js_nanbox_pointer(getter as i64); @@ -1300,19 +1324,6 @@ unsafe fn install_dir_proto_path(proto: *mut crate::object::ObjectHeader, path: } pub(crate) unsafe fn build_dir_object(id: usize, path: &str) -> f64 { - crate::closure::js_register_closure_arity(dir_read_impl as *const u8, 1); - crate::closure::js_register_closure_arity(dir_close_impl as *const u8, 1); - crate::closure::js_register_closure_arity(dir_entries_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_dispose_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_async_dispose_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_finish_read_promise_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_finish_read_callback_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_read_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_close_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_iterator_next_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_iterator_return_impl as *const u8, 0); - crate::closure::js_register_closure_arity(dir_iterator_self_impl as *const u8, 0); - let obj = crate::object::js_object_alloc(CLASS_ID_FS_DIR, 0); let self_value = f64::from_bits(crate::value::JSValue::pointer(obj as *const u8).bits()); let proto = crate::object::js_object_alloc(0, 7); @@ -1331,36 +1342,42 @@ pub(crate) unsafe fn build_dir_object(id: usize, path: &str) -> f64 { install_dir_proto_method( proto, "readSync", - make_dir_method(id, dir_read_sync_impl as *const u8), + make_dir_method(id, crate::fn_info!(dir_read_sync_impl, 0; with_declared(0))), ); install_dir_proto_method( proto, "closeSync", - make_dir_method(id, dir_close_sync_impl as *const u8), + make_dir_method( + id, + crate::fn_info!(dir_close_sync_impl, 0; with_declared(0)), + ), ); install_dir_proto_method( proto, "read", - make_dir_method(id, dir_read_impl as *const u8), + make_dir_method(id, crate::fn_info!(dir_read_impl, 1; with_declared(1))), ); install_dir_proto_method( proto, "close", - make_dir_method(id, dir_close_impl as *const u8), + make_dir_method(id, crate::fn_info!(dir_close_impl, 1; with_declared(1))), ); - let entries = make_dir_method(id, dir_entries_impl as *const u8); + let entries = make_dir_method(id, crate::fn_info!(dir_entries_impl, 0; with_declared(0))); install_dir_proto_method(proto, "entries", entries); install_dir_symbol(proto_value, "asyncIterator", entries); install_dir_symbol( proto_value, "dispose", - make_dir_method(id, dir_dispose_impl as *const u8), + make_dir_method(id, crate::fn_info!(dir_dispose_impl, 0; with_declared(0))), ); install_dir_symbol( proto_value, "asyncDispose", - make_dir_method(id, dir_async_dispose_impl as *const u8), + make_dir_method( + id, + crate::fn_info!(dir_async_dispose_impl, 0; with_declared(0)), + ), ); self_value } diff --git a/crates/perry-runtime/src/fs/filehandle.rs b/crates/perry-runtime/src/fs/filehandle.rs index 639a1325d5..b1d892b50d 100644 --- a/crates/perry-runtime/src/fs/filehandle.rs +++ b/crates/perry-runtime/src/fs/filehandle.rs @@ -96,14 +96,18 @@ pub(crate) unsafe fn build_file_io_result( f64::from_bits(crate::value::JSValue::pointer(obj as *const u8).bits()) } -pub(crate) fn make_filehandle_method(fd: i32, func: *const u8) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 1); +pub(crate) fn make_filehandle_method(fd: i32, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, fd as i64); f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) } -pub(crate) fn make_filehandle_method_with_handle(fd: i32, handle: f64, func: *const u8) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 2); +pub(crate) fn make_filehandle_method_with_handle( + fd: i32, + handle: f64, + info: *const crate::closure::JsFunctionInfo, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, fd as i64); crate::closure::js_closure_set_capture_f64(closure, 1, handle); f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) @@ -112,10 +116,10 @@ pub(crate) fn make_filehandle_method_with_handle(fd: i32, handle: f64, func: *co fn make_filehandle_method_with_handle_length( fd: i32, handle: f64, - func: *const u8, + info: *const crate::closure::JsFunctionInfo, length: u32, ) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 2); + let closure = crate::closure::js_closure_alloc(info, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, fd as i64); crate::closure::js_closure_set_capture_f64(closure, 1, handle); crate::object::set_builtin_closure_length(closure as usize, length); @@ -239,8 +243,8 @@ pub(crate) fn close_filehandle_fd(fd: i32, handle: f64) { set_filehandle_field_fd(handle, -1); } -fn make_read_lines_method(id: usize, func: *const u8) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 1); +fn make_read_lines_method(id: usize, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) } @@ -334,7 +338,10 @@ fn install_read_lines_async_iterator(target: f64, iterator: f64) { if async_iterator.is_null() { return; } - let closure = crate::closure::js_closure_alloc(read_lines_iterator_impl as *const u8, 1); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(read_lines_iterator_impl, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, iterator.to_bits() as i64); let closure_value = f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()); let symbol_value = @@ -362,9 +369,9 @@ fn make_filehandle_webstream_callback( fd: i32, handle: f64, auto_close: bool, - func: *const u8, + info: *const crate::closure::JsFunctionInfo, ) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 3); + let closure = crate::closure::js_closure_alloc(info, 3); crate::closure::js_closure_set_capture_ptr(closure, 0, fd as i64); crate::closure::js_closure_set_capture_f64(closure, 1, handle); crate::closure::js_closure_set_capture_ptr(closure, 2, if auto_close { 1 } else { 0 }); @@ -484,13 +491,13 @@ pub(crate) extern "C" fn filehandle_readable_web_stream_impl( fd, handle, auto_close, - filehandle_webstream_pull_impl as *const u8, + crate::fn_info!(filehandle_webstream_pull_impl, 0; with_declared(0)), ); let cancel = make_filehandle_webstream_callback( fd, handle, auto_close, - filehandle_webstream_cancel_impl as *const u8, + crate::fn_info!(filehandle_webstream_cancel_impl, 1; with_declared(1)), ); // NOT-A-JS-BODY: a native Rust helper registered by another crate. let factory: ReadableWebStreamFactory = unsafe { std::mem::transmute(raw) }; @@ -625,8 +632,12 @@ fn next_writer_id() -> usize { }) } -fn stream_iter_method(id: usize, self_value: f64, func: *const u8) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 2); +fn stream_iter_method( + id: usize, + self_value: f64, + info: *const crate::closure::JsFunctionInfo, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); crate::closure::js_closure_set_capture_f64(closure, 1, self_value); boxed_ptr(closure as *const u8) @@ -798,14 +809,14 @@ fn build_filehandle_stream_iter_object(id: usize, is_sync: bool) -> f64 { let self_value = boxed_ptr(obj as *const u8); let (next_fn, return_fn, symbol_name) = if is_sync { ( - stream_iter_next_sync_impl as *const u8, - stream_iter_return_sync_impl as *const u8, + crate::fn_info!(stream_iter_next_sync_impl, 0; with_declared(0)), + crate::fn_info!(stream_iter_return_sync_impl, 0; with_declared(0)), "iterator", ) } else { ( - stream_iter_next_async_impl as *const u8, - stream_iter_return_async_impl as *const u8, + crate::fn_info!(stream_iter_next_async_impl, 0; with_declared(0)), + crate::fn_info!(stream_iter_return_async_impl, 0; with_declared(0)), "asyncIterator", ) }; @@ -814,7 +825,11 @@ fn build_filehandle_stream_iter_object(id: usize, is_sync: bool) -> f64 { install_symbol_method( self_value, symbol_name, - stream_iter_method(id, self_value, stream_iter_self_impl as *const u8), + stream_iter_method( + id, + self_value, + crate::fn_info!(stream_iter_self_impl, 0; with_declared(0)), + ), ); self_value } @@ -867,8 +882,8 @@ pub(crate) extern "C" fn filehandle_pull_sync_impl( build_filehandle_stream_iter_object(id, true) } -fn writer_method(id: usize, func: *const u8) -> f64 { - let closure = crate::closure::js_closure_alloc(func, 1); +fn writer_method(id: usize, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); boxed_ptr(closure as *const u8) } @@ -1078,43 +1093,59 @@ pub(crate) extern "C" fn filehandle_writer_impl( object_set_field( obj, "write", - writer_method(id, writer_write_impl as *const u8), + writer_method(id, crate::fn_info!(writer_write_impl, 1; with_declared(1))), ); object_set_field( obj, "writev", - writer_method(id, writer_writev_impl as *const u8), + writer_method(id, crate::fn_info!(writer_writev_impl, 1; with_declared(1))), ); object_set_field( obj, "writeSync", - writer_method(id, writer_write_sync_impl as *const u8), + writer_method( + id, + crate::fn_info!(writer_write_sync_impl, 1; with_declared(1)), + ), ); object_set_field( obj, "writevSync", - writer_method(id, writer_writev_sync_impl as *const u8), + writer_method( + id, + crate::fn_info!(writer_writev_sync_impl, 1; with_declared(1)), + ), + ); + object_set_field( + obj, + "end", + writer_method(id, crate::fn_info!(writer_end_impl, 0; with_declared(0))), ); - object_set_field(obj, "end", writer_method(id, writer_end_impl as *const u8)); object_set_field( obj, "endSync", - writer_method(id, writer_end_sync_impl as *const u8), + writer_method( + id, + crate::fn_info!(writer_end_sync_impl, 0; with_declared(0)), + ), ); object_set_field( obj, "fail", - writer_method(id, writer_fail_impl as *const u8), + writer_method(id, crate::fn_info!(writer_fail_impl, 0; with_declared(0))), ); install_symbol_method( self_value, "dispose", - writer_method(id, writer_fail_impl as *const u8), + writer_method(id, crate::fn_info!(writer_fail_impl, 0; with_declared(0))), ); install_symbol_method( self_value, "asyncDispose", - writer_method(id, writer_async_dispose_impl as *const u8), + writer_method( + id, + crate::fn_info!(writer_async_dispose_impl, 0; with_declared(0)), + ), ); self_value } @@ -1543,11 +1574,17 @@ pub(crate) extern "C" fn filehandle_read_lines_impl( }; set_iterator( "next", - make_read_lines_method(id, read_lines_next_impl as *const u8), + make_read_lines_method( + id, + crate::fn_info!(read_lines_next_impl, 1; with_declared(1)), + ), ); set_iterator( "return", - make_read_lines_method(id, read_lines_return_impl as *const u8), + make_read_lines_method( + id, + crate::fn_info!(read_lines_return_impl, 1; with_declared(1)), + ), ); let iterator = f64::from_bits(crate::value::JSValue::pointer(iterator_obj as *const u8).bits()); install_read_lines_async_iterator(iterator, iterator); @@ -1557,7 +1594,10 @@ pub(crate) extern "C" fn filehandle_read_lines_impl( crate::object::js_object_set_field_by_name( interface_obj, close_key, - make_read_lines_method(id, read_lines_close_impl as *const u8), + make_read_lines_method( + id, + crate::fn_info!(read_lines_close_impl, 0; with_declared(0)), + ), ); let interface = f64::from_bits(crate::value::JSValue::pointer(interface_obj as *const u8).bits()); @@ -1583,35 +1623,6 @@ fn build_filehandle_object(fd: i32) -> f64 { // is minted here, so the thread codec is live from the first moment a // probe could ever match. crate::thread::arm_fs_thread_codec(&FS_THREAD_CODEC_IMPL); - crate::closure::js_register_closure_arity(filehandle_stat_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_write_file_impl as *const u8, 2); - crate::closure::js_register_closure_arity(filehandle_append_file_impl as *const u8, 2); - crate::closure::js_register_closure_arity(filehandle_read_impl as *const u8, 5); - crate::closure::js_register_closure_arity(filehandle_write_impl as *const u8, 5); - crate::closure::js_register_closure_arity(filehandle_read_lines_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_readable_web_stream_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_pull_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_pull_sync_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_writer_impl as *const u8, 1); - crate::closure::js_register_closure_arity(filehandle_webstream_pull_impl as *const u8, 0); - crate::closure::js_register_closure_arity(filehandle_webstream_cancel_impl as *const u8, 1); - crate::closure::js_register_closure_arity(read_lines_next_impl as *const u8, 1); - crate::closure::js_register_closure_arity(read_lines_return_impl as *const u8, 1); - crate::closure::js_register_closure_arity(read_lines_close_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_lines_iterator_impl as *const u8, 0); - crate::closure::js_register_closure_arity(stream_iter_next_async_impl as *const u8, 0); - crate::closure::js_register_closure_arity(stream_iter_return_async_impl as *const u8, 0); - crate::closure::js_register_closure_arity(stream_iter_next_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(stream_iter_return_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(stream_iter_self_impl as *const u8, 0); - crate::closure::js_register_closure_arity(writer_write_impl as *const u8, 1); - crate::closure::js_register_closure_arity(writer_writev_impl as *const u8, 1); - crate::closure::js_register_closure_arity(writer_write_sync_impl as *const u8, 1); - crate::closure::js_register_closure_arity(writer_writev_sync_impl as *const u8, 1); - crate::closure::js_register_closure_arity(writer_end_impl as *const u8, 0); - crate::closure::js_register_closure_arity(writer_end_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(writer_fail_impl as *const u8, 0); - crate::closure::js_register_closure_arity(writer_async_dispose_impl as *const u8, 0); let obj = crate::object::js_object_alloc(CLASS_ID_FS_FILEHANDLE, 23); let handle = f64::from_bits(crate::value::JSValue::pointer(obj as *const u8).bits()); let set = |name: &str, v: f64| { @@ -1620,71 +1631,93 @@ fn build_filehandle_object(fd: i32) -> f64 { }; set("fd", fd as f64); let close_method = - make_filehandle_method_with_handle(fd, handle, filehandle_close_impl as *const u8); + make_filehandle_method_with_handle(fd, handle, crate::fn_info!(filehandle_close_impl, 0)); set("close", close_method); install_filehandle_async_dispose(handle, close_method); set( "sync", - make_filehandle_method(fd, filehandle_sync_impl as *const u8), + make_filehandle_method(fd, crate::fn_info!(filehandle_sync_impl, 0)), ); set( "datasync", - make_filehandle_method(fd, filehandle_datasync_impl as *const u8), + make_filehandle_method(fd, crate::fn_info!(filehandle_datasync_impl, 0)), ); set( "stat", - make_filehandle_method_with_handle(fd, handle, filehandle_stat_impl as *const u8), + make_filehandle_method_with_handle( + fd, + handle, + crate::fn_info!(filehandle_stat_impl, 1; with_declared(1)), + ), ); set( "truncate", - make_filehandle_method_with_handle(fd, handle, filehandle_truncate_impl as *const u8), + make_filehandle_method_with_handle( + fd, + handle, + crate::fn_info!(filehandle_truncate_impl, 1), + ), ); set( "utimes", - make_filehandle_method_with_handle(fd, handle, filehandle_utimes_impl as *const u8), + make_filehandle_method_with_handle(fd, handle, crate::fn_info!(filehandle_utimes_impl, 2)), ); set( "chmod", - make_filehandle_method_with_handle(fd, handle, filehandle_chmod_impl as *const u8), + make_filehandle_method_with_handle(fd, handle, crate::fn_info!(filehandle_chmod_impl, 1)), ); set( "chown", - make_filehandle_method_with_handle(fd, handle, filehandle_chown_impl as *const u8), + make_filehandle_method_with_handle(fd, handle, crate::fn_info!(filehandle_chown_impl, 2)), ); set( "readFile", - make_filehandle_method(fd, filehandle_read_file_impl as *const u8), + make_filehandle_method(fd, crate::fn_info!(filehandle_read_file_impl, 1)), ); set( "writeFile", - make_filehandle_method_with_handle(fd, handle, filehandle_write_file_impl as *const u8), + make_filehandle_method_with_handle( + fd, + handle, + crate::fn_info!(filehandle_write_file_impl, 2; with_declared(2)), + ), ); set( "appendFile", - make_filehandle_method_with_handle(fd, handle, filehandle_append_file_impl as *const u8), + make_filehandle_method_with_handle( + fd, + handle, + crate::fn_info!(filehandle_append_file_impl, 2; with_declared(2)), + ), ); set( "read", - make_filehandle_method(fd, filehandle_read_impl as *const u8), + make_filehandle_method( + fd, + crate::fn_info!(filehandle_read_impl, 4; with_declared(5)), + ), ); set( "write", - make_filehandle_method(fd, filehandle_write_impl as *const u8), + make_filehandle_method( + fd, + crate::fn_info!(filehandle_write_impl, 4; with_declared(5)), + ), ); set( "readv", - make_filehandle_method(fd, filehandle_readv_impl as *const u8), + make_filehandle_method(fd, crate::fn_info!(filehandle_readv_impl, 2)), ); set( "writev", - make_filehandle_method(fd, filehandle_writev_impl as *const u8), + make_filehandle_method(fd, crate::fn_info!(filehandle_writev_impl, 2)), ); set( "createReadStream", make_filehandle_method_with_handle( fd, handle, - filehandle_create_read_stream_impl as *const u8, + crate::fn_info!(filehandle_create_read_stream_impl, 1), ), ); set( @@ -1692,31 +1725,40 @@ fn build_filehandle_object(fd: i32) -> f64 { make_filehandle_method_with_handle( fd, handle, - filehandle_create_write_stream_impl as *const u8, + crate::fn_info!(filehandle_create_write_stream_impl, 1), ), ); set( "readLines", - make_filehandle_method_with_handle(fd, handle, filehandle_read_lines_impl as *const u8), + make_filehandle_method_with_handle( + fd, + handle, + crate::fn_info!(filehandle_read_lines_impl, 1; with_declared(1)), + ), ); set( "readableWebStream", make_filehandle_method_with_handle( fd, handle, - filehandle_readable_web_stream_impl as *const u8, + crate::fn_info!(filehandle_readable_web_stream_impl, 1; with_declared(1)), ), ); set( "pull", - make_filehandle_method_with_handle_length(fd, handle, filehandle_pull_impl as *const u8, 0), + make_filehandle_method_with_handle_length( + fd, + handle, + crate::fn_info!(filehandle_pull_impl, 1; with_declared(1)), + 0, + ), ); set( "pullSync", make_filehandle_method_with_handle_length( fd, handle, - filehandle_pull_sync_impl as *const u8, + crate::fn_info!(filehandle_pull_sync_impl, 1; with_declared(1)), 0, ), ); @@ -1725,7 +1767,7 @@ fn build_filehandle_object(fd: i32) -> f64 { make_filehandle_method_with_handle_length( fd, handle, - filehandle_writer_impl as *const u8, + crate::fn_info!(filehandle_writer_impl, 1; with_declared(1)), 0, ), ); diff --git a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs index 3df41715eb..0eabc15a6e 100644 --- a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs +++ b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs @@ -60,12 +60,8 @@ extern "C" fn mkdtemp_disposable_async_remove_impl( } } -fn mkdtemp_disposable_method( - path_value: f64, - func: crate::closure::body_call::js_body_fn_ty!(), -) -> f64 { - crate::closure::js_register_closure_arity(func as *const u8, 0); - let closure = crate::closure::js_closure_alloc(func as *const u8, 1); +fn mkdtemp_disposable_method(path_value: f64, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 1); crate::closure::js_closure_set_capture_f64(closure, 0, path_value); f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) } @@ -86,9 +82,9 @@ fn build_mkdtemp_disposable_object( let display_path = encoded_string_ptr(&actual_path_bytes, &display_encoding); let display_path_value = f64::from_bits(crate::value::JSValue::string_ptr(display_path).bits()); let remove_func = if async_remove { - mkdtemp_disposable_async_remove_impl as crate::closure::body_call::js_body_fn_ty!() + crate::fn_info!(mkdtemp_disposable_async_remove_impl, 0; with_declared(0)) } else { - mkdtemp_disposable_remove_impl as crate::closure::body_call::js_body_fn_ty!() + crate::fn_info!(mkdtemp_disposable_remove_impl, 0; with_declared(0)) }; let remove_method = mkdtemp_disposable_method(actual_path_value, remove_func); let symbol_method = mkdtemp_disposable_method(actual_path_value, remove_func); diff --git a/crates/perry-runtime/src/fs/stats.rs b/crates/perry-runtime/src/fs/stats.rs index 8bb4ec8209..a8201d1b19 100644 --- a/crates/perry-runtime/src/fs/stats.rs +++ b/crates/perry-runtime/src/fs/stats.rs @@ -26,7 +26,8 @@ pub(crate) unsafe fn make_stats_predicate(value: bool) -> f64 { const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; let tag = if value { TAG_TRUE } else { TAG_FALSE }; - let closure = crate::closure::js_closure_alloc(stats_closure_return_captured as *const u8, 1); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(stats_closure_return_captured, 0), 1); crate::closure::js_closure_set_capture_f64(closure, 0, f64::from_bits(tag)); // NaN-box the closure pointer with POINTER_TAG so the dynamic // dispatch path in `js_native_call_method` can unwrap it. @@ -181,6 +182,7 @@ fn ensure_stats_date_accessors_registered() { name.as_ptr(), name.len() as i64, setter as i64, + 1, ); } } diff --git a/crates/perry-runtime/src/fs/stream.rs b/crates/perry-runtime/src/fs/stream.rs index 40aa9814cd..80267efaa2 100644 --- a/crates/perry-runtime/src/fs/stream.rs +++ b/crates/perry-runtime/src/fs/stream.rs @@ -356,8 +356,11 @@ pub(crate) use write_file_input::*; /// Allocate a fresh ClosureHeader whose func_ptr is `func` and /// whose slot 0 holds the given stream id. -pub(crate) fn make_stream_closure(func: extern "C" fn(), stream_id: usize) -> *mut ClosureHeader { - let closure = js_closure_alloc(func as *const u8, 1); +pub(crate) fn make_stream_closure( + info: *const crate::closure::JsFunctionInfo, + stream_id: usize, +) -> *mut ClosureHeader { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, stream_id as i64); closure } @@ -366,7 +369,7 @@ pub(crate) fn make_stream_closure(func: extern "C" fn(), stream_id: usize) -> *m pub(crate) fn build_stream_object( stream_id: usize, class_id: u32, - method_funcs: &[(&str, extern "C" fn())], + method_funcs: &[(&str, *const crate::closure::JsFunctionInfo)], ) -> *mut ObjectHeader { let mut packed: Vec = Vec::new(); for (name, _) in method_funcs { @@ -850,9 +853,7 @@ fn call_stream_callback1(callback: f64, arg: f64) { } mod write_turn; -use write_turn::{ - schedule_next_write_stream_step, schedule_write_stream_turn, write_stream_turn_impl, -}; +use write_turn::{schedule_next_write_stream_step, schedule_write_stream_turn}; pub(crate) extern "C" fn write_stream_write_impl( closure: *const ClosureHeader, @@ -1100,7 +1101,10 @@ fn finish_read_stream(id: usize) { } fn install_pipe_drain_resume(source_id: usize, dest: f64) { - let closure = js_closure_alloc(read_stream_resume_from_drain_impl as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(read_stream_resume_from_drain_impl, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, source_id as i64); let listener = f64::from_bits(JSValue::pointer(closure as *const u8).bits()); let _ = call_js_method2(dest, b"once", string_value(b"drain"), listener); @@ -1422,49 +1426,40 @@ pub(crate) fn extract_closure_ptr(v: f64) -> *const ClosureHeader { } fn create_write_stream_with_state(state: StreamState) -> f64 { - register_stream_method_arities(); let id = alloc_stream(state); - let method_funcs: [(&str, extern "C" fn()); 8] = [ - ("write", unsafe { - std::mem::transmute::( - write_stream_write_impl, - ) - }), - ("end", unsafe { - std::mem::transmute::( - write_stream_end_impl, - ) - }), - ("on", unsafe { - std::mem::transmute::( - write_stream_on_impl, - ) - }), - ("once", unsafe { - std::mem::transmute::( - write_stream_once_impl, - ) - }), - ("addListener", unsafe { - std::mem::transmute::( - write_stream_on_impl, - ) - }), - ("close", unsafe { - std::mem::transmute::( - write_stream_close_impl, - ) - }), - ("destroy", unsafe { - std::mem::transmute::( - write_stream_close_impl, - ) - }), - ("emit", unsafe { - std::mem::transmute::( - stream_emit_impl, - ) - }), + let method_funcs: [(&str, *const crate::closure::JsFunctionInfo); 8] = [ + ( + "write", + crate::fn_info!(write_stream_write_impl, 3; with_declared(3)), + ), + ( + "end", + crate::fn_info!(write_stream_end_impl, 3; with_declared(3)), + ), + ( + "on", + crate::fn_info!(write_stream_on_impl, 2; with_declared(2)), + ), + ( + "once", + crate::fn_info!(write_stream_once_impl, 2; with_declared(2)), + ), + ( + "addListener", + crate::fn_info!(write_stream_on_impl, 2; with_declared(2)), + ), + ( + "close", + crate::fn_info!(write_stream_close_impl, 1; with_declared(1)), + ), + ( + "destroy", + crate::fn_info!(write_stream_close_impl, 1; with_declared(1)), + ), + ( + "emit", + crate::fn_info!(stream_emit_impl, 2; with_declared(2)), + ), ]; let obj = build_stream_object(id, CLASS_ID_FS_WRITE_STREAM, &method_funcs); let value = object_value(obj); @@ -1488,60 +1483,49 @@ fn create_write_stream_with_state(state: StreamState) -> f64 { } fn create_read_stream_with_state(state: StreamState) -> f64 { - register_stream_method_arities(); let id = alloc_stream(state); store_open_failure(id); - let method_funcs: [(&str, extern "C" fn()); 10] = [ - ("on", unsafe { - std::mem::transmute::( - read_stream_on_impl, - ) - }), - ("once", unsafe { - std::mem::transmute::( - read_stream_once_impl, - ) - }), - ("addListener", unsafe { - std::mem::transmute::( - read_stream_on_impl, - ) - }), - ("pipe", unsafe { - std::mem::transmute::( - read_stream_pipe_impl, - ) - }), - ("pause", unsafe { - std::mem::transmute::( - read_stream_pause_impl, - ) - }), - ("resume", unsafe { - std::mem::transmute::( - read_stream_resume_impl, - ) - }), - ("isPaused", unsafe { - std::mem::transmute::( - read_stream_is_paused_impl, - ) - }), - ("close", unsafe { - std::mem::transmute::( - read_stream_close_impl, - ) - }), - ("destroy", unsafe { - std::mem::transmute::( - read_stream_close_impl, - ) - }), - ("emit", unsafe { - std::mem::transmute::( - stream_emit_impl, - ) - }), + let method_funcs: [(&str, *const crate::closure::JsFunctionInfo); 10] = [ + ( + "on", + crate::fn_info!(read_stream_on_impl, 2; with_declared(2)), + ), + ( + "once", + crate::fn_info!(read_stream_once_impl, 2; with_declared(2)), + ), + ( + "addListener", + crate::fn_info!(read_stream_on_impl, 2; with_declared(2)), + ), + ( + "pipe", + crate::fn_info!(read_stream_pipe_impl, 2; with_declared(2)), + ), + ( + "pause", + crate::fn_info!(read_stream_pause_impl, 0; with_declared(0)), + ), + ( + "resume", + crate::fn_info!(read_stream_resume_impl, 0; with_declared(0)), + ), + ( + "isPaused", + crate::fn_info!(read_stream_is_paused_impl, 0; with_declared(0)), + ), + ( + "close", + crate::fn_info!(read_stream_close_impl, 1; with_declared(1)), + ), + ( + "destroy", + crate::fn_info!(read_stream_close_impl, 1; with_declared(1)), + ), + ( + "emit", + crate::fn_info!(stream_emit_impl, 2; with_declared(2)), + ), ]; let obj = build_stream_object(id, CLASS_ID_FS_READ_STREAM, &method_funcs); let value = object_value(obj); @@ -1581,91 +1565,57 @@ fn install_utf8_stream_dispose_symbol(value: f64, method: f64) { } fn create_utf8_stream_with_state(state: Utf8StreamState) -> f64 { - register_stream_method_arities(); let periodic_flush = state.periodic_flush; let schedule_open = state.opening; let id = alloc_utf8_stream(state); - let method_funcs: [(&str, extern "C" fn()); 16] = [ - ("write", unsafe { - std::mem::transmute::( - utf8_stream_write_impl, - ) - }), - ("flush", unsafe { - std::mem::transmute::( - utf8_stream_flush_impl, - ) - }), - ("flushSync", unsafe { - std::mem::transmute::( - utf8_stream_flush_sync_impl, - ) - }), - ("end", unsafe { - std::mem::transmute::( - utf8_stream_end_impl, - ) - }), - ("destroy", unsafe { - std::mem::transmute::( - utf8_stream_destroy_impl, - ) - }), - ("reopen", unsafe { - std::mem::transmute::( - utf8_stream_reopen_impl, - ) - }), - ("on", unsafe { - std::mem::transmute::( - utf8_stream_on_impl, - ) - }), - ("once", unsafe { - std::mem::transmute::( - utf8_stream_once_impl, - ) - }), - ("addListener", unsafe { - std::mem::transmute::( - utf8_stream_on_impl, - ) - }), - ("off", unsafe { - std::mem::transmute::( - utf8_stream_off_impl, - ) - }), - ("removeListener", unsafe { - std::mem::transmute::( - utf8_stream_off_impl, - ) - }), - ("removeAllListeners", unsafe { - std::mem::transmute::( - utf8_stream_remove_all_impl, - ) - }), - ("listenerCount", unsafe { - std::mem::transmute::( - utf8_stream_listener_count_impl, - ) - }), - ("emit", unsafe { - std::mem::transmute::( - utf8_stream_emit_impl, - ) - }), - ("close", unsafe { - std::mem::transmute::( - utf8_stream_destroy_impl, - ) - }), - ("@@__perry_wk_dispose", unsafe { - std::mem::transmute::( - utf8_stream_destroy_impl, - ) - }), + // Aliased methods share one info, so their bodies keep one identity. + let destroy = crate::fn_info!(utf8_stream_destroy_impl, 0; with_declared(0)); + let on = crate::fn_info!(utf8_stream_on_impl, 2; with_declared(2)); + let off = crate::fn_info!(utf8_stream_off_impl, 2; with_declared(2)); + let method_funcs: [(&str, *const crate::closure::JsFunctionInfo); 16] = [ + ( + "write", + crate::fn_info!(utf8_stream_write_impl, 1; with_declared(1)), + ), + ( + "flush", + crate::fn_info!(utf8_stream_flush_impl, 1; with_declared(1)), + ), + ( + "flushSync", + crate::fn_info!(utf8_stream_flush_sync_impl, 0; with_declared(0)), + ), + ( + "end", + crate::fn_info!(utf8_stream_end_impl, 0; with_declared(0)), + ), + ("destroy", destroy), + ( + "reopen", + crate::fn_info!(utf8_stream_reopen_impl, 1; with_declared(1)), + ), + ("on", on), + ( + "once", + crate::fn_info!(utf8_stream_once_impl, 2; with_declared(2)), + ), + ("addListener", on), + ("off", off), + ("removeListener", off), + ( + "removeAllListeners", + crate::fn_info!(utf8_stream_remove_all_impl, 1; with_declared(1)), + ), + ( + "listenerCount", + crate::fn_info!(utf8_stream_listener_count_impl, 1; with_declared(1)), + ), + ( + "emit", + crate::fn_info!(utf8_stream_emit_impl, 2; with_declared(2)), + ), + ("close", destroy), + ("@@__perry_wk_dispose", destroy), ]; let obj = build_stream_object(id, CLASS_ID_FS_UTF8_STREAM, &method_funcs); let value = object_value(obj); @@ -1683,7 +1633,10 @@ fn create_utf8_stream_with_state(state: Utf8StreamState) -> f64 { utf8_start_async_open(id); } if periodic_flush > 0 { - let closure = js_closure_alloc(utf8_periodic_flush_impl as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(utf8_periodic_flush_impl, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, id as i64); let timer = crate::timer::setInterval(closure as i64, periodic_flush as f64); crate::timer::js_timer_unref(timer); diff --git a/crates/perry-runtime/src/fs/stream/options_init.rs b/crates/perry-runtime/src/fs/stream/options_init.rs index f6b0d5458d..a6b3c7d647 100644 --- a/crates/perry-runtime/src/fs/stream/options_init.rs +++ b/crates/perry-runtime/src/fs/stream/options_init.rs @@ -1,44 +1,8 @@ -//! Stream option parsing and method-arity registration, split out of -//! `stream.rs` to keep it under the 2000-line cap. +//! Stream option parsing, split out of `stream.rs` to keep it under the +//! 2000-line cap. use super::*; -pub(super) fn register_stream_method_arities() { - crate::closure::js_register_closure_arity(write_stream_write_impl as *const u8, 3); - crate::closure::js_register_closure_arity(write_stream_end_impl as *const u8, 3); - crate::closure::js_register_closure_arity(write_stream_on_impl as *const u8, 2); - crate::closure::js_register_closure_arity(write_stream_once_impl as *const u8, 2); - crate::closure::js_register_closure_arity(write_stream_close_impl as *const u8, 1); - crate::closure::js_register_closure_arity(stream_emit_impl as *const u8, 2); - crate::closure::js_register_closure_arity(write_stream_turn_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_stream_on_impl as *const u8, 2); - crate::closure::js_register_closure_arity(read_stream_once_impl as *const u8, 2); - crate::closure::js_register_closure_arity(read_stream_pipe_impl as *const u8, 2); - crate::closure::js_register_closure_arity(read_stream_pause_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_stream_resume_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_stream_is_paused_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_stream_close_impl as *const u8, 1); - crate::closure::js_register_closure_arity(read_stream_resume_from_drain_impl as *const u8, 0); - crate::closure::js_register_closure_arity(read_stream_turn_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_stream_write_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_stream_flush_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_stream_flush_sync_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_stream_end_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_stream_destroy_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_stream_reopen_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_stream_on_impl as *const u8, 2); - crate::closure::js_register_closure_arity(utf8_stream_once_impl as *const u8, 2); - crate::closure::js_register_closure_arity(utf8_stream_off_impl as *const u8, 2); - crate::closure::js_register_closure_arity(utf8_stream_remove_all_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_stream_listener_count_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_stream_emit_impl as *const u8, 2); - crate::closure::js_register_closure_arity(utf8_periodic_flush_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_async_open_impl as *const u8, 0); - crate::closure::js_register_closure_arity(utf8_async_open_done_impl as *const u8, 2); - crate::closure::js_register_closure_arity(utf8_async_mkdir_done_impl as *const u8, 1); - crate::closure::js_register_closure_arity(utf8_close_events_impl as *const u8, 0); -} - pub(super) fn init_read_state_from_options( path_value: f64, options_value: f64, diff --git a/crates/perry-runtime/src/fs/stream/read_turn.rs b/crates/perry-runtime/src/fs/stream/read_turn.rs index 09884d0bcc..e545a3025b 100644 --- a/crates/perry-runtime/src/fs/stream/read_turn.rs +++ b/crates/perry-runtime/src/fs/stream/read_turn.rs @@ -20,7 +20,10 @@ pub(super) fn schedule_read_stream_turn(id: usize) { true }); if should_schedule { - let closure = js_closure_alloc(read_stream_turn_impl as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(read_stream_turn_impl, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, id as i64); let _ = crate::timer::js_set_timeout_callback(closure as i64, 0.0); } diff --git a/crates/perry-runtime/src/fs/stream/utf8_stream.rs b/crates/perry-runtime/src/fs/stream/utf8_stream.rs index d6d2ab85ff..5f6cbc9af3 100644 --- a/crates/perry-runtime/src/fs/stream/utf8_stream.rs +++ b/crates/perry-runtime/src/fs/stream/utf8_stream.rs @@ -917,7 +917,10 @@ pub(crate) extern "C" fn utf8_close_events_impl( } fn utf8_schedule_close_events(id: usize, emit_finish: bool) { - let closure = js_closure_alloc(utf8_close_events_impl as *const u8, 2); + let closure = js_closure_alloc( + crate::fn_info!(utf8_close_events_impl, 0; with_declared(0)), + 2, + ); js_closure_set_capture_ptr(closure, 0, id as i64); js_closure_set_capture_ptr(closure, 1, if emit_finish { 1 } else { 0 }); crate::builtins::js_queue_microtask(closure as i64); @@ -1419,7 +1422,10 @@ fn utf8_custom_open(id: usize) -> bool { if is_undefined_value(custom_fs) || !object_has_callable_field(custom_fs, b"open") { return false; } - let cb = js_closure_alloc(utf8_async_open_done_impl as *const u8, 1); + let cb = js_closure_alloc( + crate::fn_info!(utf8_async_open_done_impl, 2; with_declared(2)), + 1, + ); js_closure_set_capture_ptr(cb, 0, id as i64); let cb_value = crate::value::js_nanbox_pointer(cb as i64); let flags_value = string_value_str(if append { "a" } else { "w" }); @@ -1472,7 +1478,10 @@ fn utf8_custom_mkdir_then_open(id: usize) -> bool { let key = js_string_from_bytes(b"recursive".as_ptr(), 9); crate::object::js_object_set_field_by_name(options_obj, key, bool_value(true)); let options_value = object_value(options_obj); - let cb = js_closure_alloc(utf8_async_mkdir_done_impl as *const u8, 1); + let cb = js_closure_alloc( + crate::fn_info!(utf8_async_mkdir_done_impl, 1; with_declared(1)), + 1, + ); js_closure_set_capture_ptr(cb, 0, id as i64); let cb_value = crate::value::js_nanbox_pointer(cb as i64); let parent_value = string_value_str(parent_str); @@ -1499,7 +1508,7 @@ fn utf8_needs_native_mkdir_before_custom_open(id: usize) -> bool { fn utf8_schedule_native_mkdir_then_custom_open(id: usize) { let closure = js_closure_alloc( - utf8_async_native_mkdir_then_custom_open_impl as *const u8, + crate::fn_info!(utf8_async_native_mkdir_then_custom_open_impl, 0), 1, ); js_closure_set_capture_ptr(closure, 0, id as i64); @@ -1507,7 +1516,10 @@ fn utf8_schedule_native_mkdir_then_custom_open(id: usize) { } fn utf8_schedule_native_open(id: usize) { - let closure = js_closure_alloc(utf8_async_open_impl as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(utf8_async_open_impl, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, id as i64); crate::builtins::js_queue_microtask(closure as i64); } diff --git a/crates/perry-runtime/src/fs/stream/write_turn.rs b/crates/perry-runtime/src/fs/stream/write_turn.rs index 389a7a4d92..03753696c1 100644 --- a/crates/perry-runtime/src/fs/stream/write_turn.rs +++ b/crates/perry-runtime/src/fs/stream/write_turn.rs @@ -32,7 +32,10 @@ pub(super) fn schedule_write_stream_turn(id: usize) { true }); if should_schedule { - let closure = js_closure_alloc(write_stream_turn_impl as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(write_stream_turn_impl, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, id as i64); let _ = crate::timer::js_set_timeout_callback(closure as i64, 0.0); } diff --git a/crates/perry-runtime/src/gc/census.rs b/crates/perry-runtime/src/gc/census.rs index d6a5096a50..775dfa7a2c 100644 --- a/crates/perry-runtime/src/gc/census.rs +++ b/crates/perry-runtime/src/gc/census.rs @@ -590,7 +590,6 @@ pub(crate) fn vec_bytes(v: &Vec) -> usize { pub(super) fn side_tables() -> Vec { let mut rows: Vec = Vec::new(); rows.extend(crate::builtins::function_registries_census()); - rows.extend(crate::closure::closure_registry_census()); rows.extend(crate::closure::closure_side_table_census()); rows.extend(crate::object::shapes::shape_table_census()); rows.extend(crate::object::class_registry_census()); diff --git a/crates/perry-runtime/src/gc/tests/alloc.rs b/crates/perry-runtime/src/gc/tests/alloc.rs index f214f80587..6d216e6752 100644 --- a/crates/perry-runtime/src/gc/tests/alloc.rs +++ b/crates/perry-runtime/src/gc/tests/alloc.rs @@ -711,7 +711,8 @@ fn test_small_js_string_alloc_uses_managed_nursery_page() { #[test] fn test_small_js_closure_alloc_uses_managed_nursery_page() { let _guard = CopyingNurseryTestGuard::new(0); - let closure = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 2); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 2); let header = unsafe { header_from_user_ptr(closure as *const u8) }; unsafe { @@ -805,7 +806,7 @@ fn test_large_js_closure_alloc_remains_malloc_tracked() { - std::mem::size_of::()) / std::mem::size_of::(); let closure = crate::closure::js_closure_alloc( - test_captured_singleton_func as *const u8, + crate::fn_info!(test_captured_singleton_func, 0), (max_managed_captures + 1) as u32, ); let header = unsafe { header_from_user_ptr(closure as *const u8) }; diff --git a/crates/perry-runtime/src/gc/tests/array_pointer_slot_enumeration.rs b/crates/perry-runtime/src/gc/tests/array_pointer_slot_enumeration.rs index 45a1017b10..db0e733c53 100644 --- a/crates/perry-runtime/src/gc/tests/array_pointer_slot_enumeration.rs +++ b/crates/perry-runtime/src/gc/tests/array_pointer_slot_enumeration.rs @@ -179,7 +179,7 @@ fn interrupted_species_copy_describes_late_pointer(splice: bool) { } let late = young_leaf(); crate::array::js_array_set_f64(source, 10, f64::from_bits(ptr_bits(late))); - let species = crate::closure::js_closure_alloc(species_destination as *const u8, 1); + let species = crate::closure::js_closure_alloc(crate::fn_info!(species_destination, 1), 1); crate::closure::js_closure_set_capture_f64( species, 0, @@ -196,7 +196,7 @@ fn interrupted_species_copy_describes_late_pointer(splice: bool) { } let key = crate::string::js_string_from_bytes(b"constructor".as_ptr(), 11); crate::array::js_array_set_string_key(source, key, species_value); - let getter = crate::closure::js_closure_alloc(interrupt_species_copy as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(interrupt_species_copy, 0), 0); let descriptor = crate::object::js_object_alloc(0, 0); let get_key = crate::string::js_string_from_bytes(b"get".as_ptr(), 3); crate::object::js_object_set_field_by_name( diff --git a/crates/perry-runtime/src/gc/tests/barrier.rs b/crates/perry-runtime/src/gc/tests/barrier.rs index 067a88bdf8..204412e0a3 100644 --- a/crates/perry-runtime/src/gc/tests/barrier.rs +++ b/crates/perry-runtime/src/gc/tests/barrier.rs @@ -1,6 +1,7 @@ use super::super::barrier::RememberedSetClearState; use super::super::*; use super::support::*; +use crate::closure::js_closure_alloc; unsafe fn field_index_not_on_last_page(fields: *mut u64, field_count: u32) -> usize { assert!(field_count > 1); @@ -1442,7 +1443,7 @@ fn test_incremental_barrier_marks_closure_capture_store() { reset_remembered_set(); clear_marks(); let child = crate::arena::arena_alloc_gc(40, 8, GC_TYPE_OBJECT) as usize; - let closure = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 1); mark_user_ptr(closure as usize); let valid_ptrs = build_valid_pointer_set(); let _barrier = IncrementalMarkBarrierTestGuard::new(&valid_ptrs); @@ -1464,7 +1465,7 @@ fn test_incremental_barrier_marks_closure_static_prototype_store() { reset_remembered_set(); clear_marks(); let proto = unsafe { alloc_nursery_test_object(0).0 as usize }; - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(crate::fn_info!(test_no_capture_singleton_func, 0), 0); mark_user_ptr(closure as usize); let valid_ptrs = build_valid_pointer_set(); let _barrier = IncrementalMarkBarrierTestGuard::new(&valid_ptrs); @@ -1577,7 +1578,7 @@ fn test_promise_pointer_field_stores_dirty_old_page() { reset_remembered_set(); clear_marks(); let promise = unsafe { alloc_old_test_promise() }; - let callback = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); let next = crate::promise::js_promise_then(promise, callback, std::ptr::null()); assert!( @@ -1606,8 +1607,8 @@ fn test_promise_pointer_field_stores_dirty_old_page() { reset_remembered_set(); clear_marks(); let promise = unsafe { alloc_old_test_promise() }; - let fulfill = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); - let reject = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); + let fulfill = js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); + let reject = js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); crate::promise::js_promise_attach_handlers(promise, fulfill, reject); assert!( @@ -1622,7 +1623,7 @@ fn test_promise_pointer_field_stores_dirty_old_page() { reset_remembered_set(); clear_marks(); let promise = unsafe { alloc_old_test_promise() }; - let on_finally = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); + let on_finally = js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); let _next = crate::promise::js_promise_finally(promise, on_finally); let (fulfill_wrap, reject_wrap) = unsafe { ((*promise).on_fulfilled, (*promise).on_rejected) }; diff --git a/crates/perry-runtime/src/gc/tests/copying.rs b/crates/perry-runtime/src/gc/tests/copying.rs index 9d253d821b..a660af419d 100644 --- a/crates/perry-runtime/src/gc/tests/copying.rs +++ b/crates/perry-runtime/src/gc/tests/copying.rs @@ -17,6 +17,18 @@ mod weakmap_index; use super::super::*; use super::support::*; +/// The infos the singleton-cache tests allocate through: one per body, so +/// every allocation of a body shares the cache key. +static TEST_CAPTURED_SINGLETON_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + test_captured_singleton_func as crate::codegen_abi::JsBody0, + ); +static TEST_NO_CAPTURE_SINGLETON_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + test_no_capture_singleton_func + as crate::codegen_abi::JsBody0, + ); + /// Element/field count whose backing store exceeds the POINTER-BEARING /// born-tenured threshold, so `arena_alloc_gc` births it in old-gen. /// @@ -185,7 +197,7 @@ fn test_old_managed_closure_capture_write_dirties_old_page() { GC_TYPE_CLOSURE, ) as *mut crate::closure::ClosureHeader; unsafe { - (*closure).func_ptr = test_captured_singleton_func as *const u8; + (*closure).info = &TEST_CAPTURED_SINGLETON_INFO; (*closure).capture_count = 1; (*closure).shape_id = crate::closure::shape::function_base_shape( crate::closure::shape::FunctionProtoKind::Function, @@ -211,7 +223,7 @@ fn test_copying_minor_relocates_managed_closure_and_rewrites_capture() { let _guard = CopyingNurseryTestGuard::new(1); let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); let child = young_leaf(); - let closure = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 1); + let closure = crate::closure::js_closure_alloc(&TEST_CAPTURED_SINGLETON_INFO, 1); crate::closure::js_closure_set_capture_f64(closure, 0, f64::from_bits(ptr_bits(child))); js_shadow_slot_set(0, ptr_bits(closure as usize)); @@ -461,7 +473,7 @@ fn test_copying_minor_rewrites_exact_closure_pointer_capture_only() { let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); let child = young_leaf(); - let closure = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 8); + let closure = crate::closure::js_closure_alloc(&TEST_CAPTURED_SINGLETON_INFO, 8); crate::closure::js_closure_set_capture_f64(closure, 0, 10.0); crate::closure::js_closure_set_capture_f64(closure, 1, f64::from_bits(ptr_bits(child))); crate::closure::js_closure_set_capture_f64(closure, 2, 30.0); @@ -594,8 +606,7 @@ fn test_copying_minor_marks_array_growth_forwarding_target() { for i in 0..50 { let child = young_leaf(); - let closure = - crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 1); + let closure = crate::closure::js_closure_alloc(&TEST_CAPTURED_SINGLETON_INFO, 1); crate::closure::js_closure_set_capture_f64(closure, 0, f64::from_bits(ptr_bits(child))); if i == 0 { first_closure = closure as usize; @@ -1546,7 +1557,7 @@ fn malloc_backed_large_closure_capture_in_old_container_survives_copied_minor() - std::mem::size_of::()) / std::mem::size_of::(); let closure = crate::closure::js_closure_alloc( - test_captured_singleton_func as *const u8, + &TEST_CAPTURED_SINGLETON_INFO, (max_managed_captures + 1) as u32, ); let closure_header = unsafe { header_from_user_ptr(closure as *const u8) }; @@ -1805,7 +1816,7 @@ fn test_copying_minor_rewrites_singleton_closure_caches() { crate::closure::test_clear_singleton_closure_caches(); gc_register_mutable_root_scanner(crate::closure::scan_singleton_closure_roots_mut); - let no_capture_func = test_no_capture_singleton_func as *const u8; + let no_capture_func: *const crate::closure::JsFunctionInfo = &TEST_NO_CAPTURE_SINGLETON_INFO; let no_capture = crate::closure::js_closure_alloc_singleton(no_capture_func); assert_eq!( crate::closure::test_singleton_closure_cache_entry(no_capture_func), @@ -1816,7 +1827,7 @@ fn test_copying_minor_rewrites_singleton_closure_caches() { let capture_bits = ptr_bits(captured_value); js_shadow_slot_set(0, capture_bits); - let captured_func = test_captured_singleton_func as *const u8; + let captured_func: *const crate::closure::JsFunctionInfo = &TEST_CAPTURED_SINGLETON_INFO; let captures = [capture_bits]; let captured = crate::closure::js_closure_alloc_with_captures_singleton( captured_func, diff --git a/crates/perry-runtime/src/gc/tests/copying/pointer_publish_7154.rs b/crates/perry-runtime/src/gc/tests/copying/pointer_publish_7154.rs index 1e4a623ccb..fa8796eff6 100644 --- a/crates/perry-runtime/src/gc/tests/copying/pointer_publish_7154.rs +++ b/crates/perry-runtime/src/gc/tests/copying/pointer_publish_7154.rs @@ -68,7 +68,7 @@ fn test_bound_this_capture_is_traced_after_method_bind_7154() { js_shadow_slot_set( 0, ptr_bits(crate::closure::js_closure_alloc( - test_bound_method_body as *const u8, + crate::fn_info!(test_bound_method_body, 0), crate::closure::CAPTURES_THIS_FLAG | 1, ) as usize), ); @@ -257,7 +257,7 @@ fn weak_entry_addr_for(map: f64, key: f64) -> usize { #[test] fn test_fresh_closure_capture_slots_are_initialized_7154() { let _guard = CopyingNurseryTestGuard::new(1); - let closure = crate::closure::js_closure_alloc(test_bound_method_body as *const u8, 3); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(test_bound_method_body, 0), 3); unsafe { let slots = crate::closure::closure_capture_slots_mut(closure); for i in 0..3 { diff --git a/crates/perry-runtime/src/gc/tests/copying/weak_holder_registry.rs b/crates/perry-runtime/src/gc/tests/copying/weak_holder_registry.rs index 617a4b07b1..892df094b3 100644 --- a/crates/perry-runtime/src/gc/tests/copying/weak_holder_registry.rs +++ b/crates/perry-runtime/src/gc/tests/copying/weak_holder_registry.rs @@ -341,7 +341,8 @@ fn prepare_moved_full_path_weak_holders() { f64::from_bits(js_shadow_slot_get(6)), ); - let callback = crate::closure::js_closure_alloc(finreg_registry_test_callback as *const u8, 0); + let callback = + crate::closure::js_closure_alloc(crate::fn_info!(finreg_registry_test_callback, 1), 0); let registry = crate::weakref::js_finreg_new(f64::from_bits(ptr_bits(callback as usize))); let finreg_target = crate::object::js_object_alloc(0, 0); js_shadow_slot_set(3, obj_bits(registry)); @@ -451,7 +452,7 @@ fn test_fallback_registry_path_handles_all_weak_kinds_moved_dead_and_stale_holde fn test_copied_minor_finreg_enqueues_cleanup_via_registry() { let _guard = CopyingNurseryTestGuard::new(1); - let cb = crate::closure::js_closure_alloc(finreg_registry_test_callback as *const u8, 0); + let cb = crate::closure::js_closure_alloc(crate::fn_info!(finreg_registry_test_callback, 1), 0); let reg = crate::weakref::js_finreg_new(f64::from_bits(ptr_bits(cb as usize))); js_shadow_slot_set(0, obj_bits(reg)); @@ -593,7 +594,7 @@ fn test_copied_minor_finreg_promoted_live_target_not_collected() { js_shadow_slot_set(1, obj_bits(target)); let target_old = promote_rooted_to_old(1); - let cb = crate::closure::js_closure_alloc(finreg_registry_test_callback as *const u8, 0); + let cb = crate::closure::js_closure_alloc(crate::fn_info!(finreg_registry_test_callback, 1), 0); let reg = crate::weakref::js_finreg_new(f64::from_bits(ptr_bits(cb as usize))); js_shadow_slot_set(0, obj_bits(reg)); let reg_v = f64::from_bits(js_shadow_slot_get(0)); diff --git a/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs b/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs index 478303a479..6e8f9c0a0d 100644 --- a/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs +++ b/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs @@ -126,7 +126,7 @@ extern "C" fn finreg_test_callback( /// root it in shadow slot 0. Returns nothing — read the registry back through /// the slot so post-move addresses stay correct. fn make_rooted_finreg() { - let cb = crate::closure::js_closure_alloc(finreg_test_callback as *const u8, 0); + let cb = crate::closure::js_closure_alloc(crate::fn_info!(finreg_test_callback, 1), 0); let cb_val = f64::from_bits(ptr_bits(cb as usize)); let reg = crate::weakref::js_finreg_new(cb_val); js_shadow_slot_set(0, object_bits(reg)); diff --git a/crates/perry-runtime/src/gc/tests/cycle_state.rs b/crates/perry-runtime/src/gc/tests/cycle_state.rs index 3251a3fdeb..c5e0a3ea03 100644 --- a/crates/perry-runtime/src/gc/tests/cycle_state.rs +++ b/crates/perry-runtime/src/gc/tests/cycle_state.rs @@ -1842,8 +1842,8 @@ fn full_cycle_step_scanner_covers_promise_overflow_reactions() { let promise = unsafe { alloc_old_test_promise() }; js_shadow_slot_set(0, ptr_bits(promise as usize)); - let cb1 = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); - let cb2 = crate::closure::js_closure_alloc(test_captured_singleton_func as *const u8, 0); + let cb1 = crate::closure::js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); + let cb2 = crate::closure::js_closure_alloc(crate::fn_info!(test_captured_singleton_func, 0), 0); // First reaction lands inline in the promise's own fields; the second // goes to PROMISE_OVERFLOW_REACTIONS — the table under test. let _next1 = crate::promise::js_promise_then(promise, cb1, std::ptr::null()); diff --git a/crates/perry-runtime/src/gc/tests/layout_trace/object_closure_slots.rs b/crates/perry-runtime/src/gc/tests/layout_trace/object_closure_slots.rs index 4627097e4c..c03bc3e48c 100644 --- a/crates/perry-runtime/src/gc/tests/layout_trace/object_closure_slots.rs +++ b/crates/perry-runtime/src/gc/tests/layout_trace/object_closure_slots.rs @@ -137,7 +137,7 @@ fn test_trace_closure_uses_pointer_layout_mask() { clear_marks(); clear_mark_seeds(); - let numeric = crate::closure::js_closure_alloc(layout_mask_test_closure as *const u8, 8); + let numeric = crate::closure::js_closure_alloc(crate::fn_info!(layout_mask_test_closure, 0), 8); crate::closure::js_closure_set_capture_f64(numeric, 0, 1.0); crate::closure::js_closure_set_capture_f64(numeric, 1, 2.0); crate::closure::js_closure_set_capture_ptr(numeric, 2, 7); @@ -157,7 +157,7 @@ fn test_trace_closure_uses_pointer_layout_mask() { let child = crate::string::js_string_from_bytes(b"closure-child".as_ptr(), 13) as *mut u8; let child_header = unsafe { header_from_user_ptr(child) }; - let mixed = crate::closure::js_closure_alloc(layout_mask_test_closure as *const u8, 8); + let mixed = crate::closure::js_closure_alloc(crate::fn_info!(layout_mask_test_closure, 0), 8); crate::closure::js_closure_set_capture_f64(mixed, 0, 1.0); crate::closure::js_closure_set_capture_f64( mixed, diff --git a/crates/perry-runtime/src/gc/tests/pinned_roots.rs b/crates/perry-runtime/src/gc/tests/pinned_roots.rs index 711cb70b2b..98d965c1b6 100644 --- a/crates/perry-runtime/src/gc/tests/pinned_roots.rs +++ b/crates/perry-runtime/src/gc/tests/pinned_roots.rs @@ -301,7 +301,7 @@ fn pinned_promise_reaction_runs(cross_thread: bool, fulls: usize) -> bool { crate::promise::js_promise_new() }; js_shadow_slot_set(0, ptr_bits(p as usize)); - let cb = crate::closure::js_closure_alloc(record_cb as *const u8, 0); + let cb = crate::closure::js_closure_alloc(crate::fn_info!(record_cb, 1), 0); let _derived = crate::promise::js_promise_then(slot_ptr(), cb, std::ptr::null()); let p: *mut crate::promise::Promise = slot_ptr(); if !cross_thread { @@ -315,7 +315,7 @@ fn pinned_promise_reaction_runs(cross_thread: bool, fulls: usize) -> bool { } // A freed reaction cell is handed back to a closure that records -1. for _ in 0..4096 { - let _ = crate::closure::js_closure_alloc(overwrite_cb as *const u8, 0); + let _ = crate::closure::js_closure_alloc(crate::fn_info!(overwrite_cb, 1), 0); } if !cross_thread { unsafe { crate::gc::unpin_object(header_of(p as *mut u8)) }; diff --git a/crates/perry-runtime/src/gc/tests/rooted_container_values.rs b/crates/perry-runtime/src/gc/tests/rooted_container_values.rs index d08ebcf323..3daa88c2b2 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_container_values.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_container_values.rs @@ -171,7 +171,7 @@ fn group_by_inputs(scope: &RuntimeHandleScope) -> (f64, f64) { let grown = crate::array::js_array_push(arr, element); items_handle.set_nanbox_f64(f64::from_bits(ptr_bits(grown as usize))); } - let closure = crate::closure::js_closure_alloc(group_by_moving_callback as *const u8, 0); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(group_by_moving_callback, 2), 0); let callback_handle = scope.root_nanbox_f64(f64::from_bits(ptr_bits(closure as usize))); ( items_handle.get_nanbox_f64(), diff --git a/crates/perry-runtime/src/gc/tests/rooted_define_property.rs b/crates/perry-runtime/src/gc/tests/rooted_define_property.rs index 9f6105cabd..3aa4362a12 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_define_property.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_define_property.rs @@ -96,7 +96,7 @@ extern "C" fn moving_value_getter( unsafe fn descriptor_bag_with_moving_value_getter(scope: &RuntimeHandleScope) -> f64 { let bag = scope.root_nanbox_f64(object_value(crate::object::js_object_alloc(0, 0))); let inner = scope.root_nanbox_f64(object_value(crate::object::js_object_alloc(0, 0))); - let getter = crate::closure::js_closure_alloc(moving_value_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(moving_value_getter, 0), 0); let getter_value = f64::from_bits(ptr_bits(getter as usize)); let get_key = crate::string::js_string_from_bytes(b"get".as_ptr(), 3); @@ -276,7 +276,8 @@ fn array_named_property_attributes_follow_a_move_in_the_final_descriptor_probe() let key = scope.root_nanbox_f64(string_value("tag")); let bag = scope.root_nanbox_f64(object_value(crate::object::js_object_alloc(0, 0))); let inner = scope.root_nanbox_f64(object_value(crate::object::js_object_alloc(0, 0))); - let getter = crate::closure::js_closure_alloc(moving_writable_getter as *const u8, 0); + let getter = + crate::closure::js_closure_alloc(crate::fn_info!(moving_writable_getter, 0), 0); let get_key = crate::string::js_string_from_bytes(b"get".as_ptr(), 3); crate::object::js_object_set_field_by_name( addr_of(inner.get_nanbox_f64()) as *mut crate::object::ObjectHeader, diff --git a/crates/perry-runtime/src/gc/tests/rooted_for_in.rs b/crates/perry-runtime/src/gc/tests/rooted_for_in.rs index 991780912f..9d2bbb03be 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_for_in.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_for_in.rs @@ -65,9 +65,12 @@ fn run(inherited_proxy: bool, descriptor_trap: bool) { } let handler = object(&scope); let (name, function) = if descriptor_trap { - ("getOwnPropertyDescriptor", moving_descriptor as *const u8) + ( + "getOwnPropertyDescriptor", + crate::fn_info!(moving_descriptor, 2), + ) } else { - ("ownKeys", moving_own_keys as *const u8) + ("ownKeys", crate::fn_info!(moving_own_keys, 1)) }; let callback = crate::closure::js_closure_alloc(function, 0); set(handler, name, f64::from_bits(ptr_bits(callback as usize))); @@ -175,7 +178,7 @@ extern "C" fn descriptor_with_moving_field( for name in ["enumerable", "configurable", "writable"] { set(result, name, f64::from_bits(crate::value::TAG_TRUE)); } - let getter = crate::closure::js_closure_alloc(moving_value as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(moving_value, 0), 0); let descriptor = object(&scope); set(descriptor, "get", f64::from_bits(ptr_bits(getter as usize))); let key = crate::string::js_string_from_bytes(b"value".as_ptr(), 5); @@ -209,7 +212,8 @@ fn descriptor_completion_reloads_after_field_getter_collection() { let target = object(&scope); set(target, "property_name", 7.0); let handler = object(&scope); - let callback = crate::closure::js_closure_alloc(descriptor_with_moving_field as *const u8, 0); + let callback = + crate::closure::js_closure_alloc(crate::fn_info!(descriptor_with_moving_field, 2), 0); set( handler, "getOwnPropertyDescriptor", diff --git a/crates/perry-runtime/src/gc/tests/roots.rs b/crates/perry-runtime/src/gc/tests/roots.rs index dbccd89229..5a25c13d3d 100644 --- a/crates/perry-runtime/src/gc/tests/roots.rs +++ b/crates/perry-runtime/src/gc/tests/roots.rs @@ -70,7 +70,7 @@ fn lock_safe_runtime_scanner_closure() -> (*mut u8, u64, f64) { ); unsafe { let closure = ptr as *mut crate::closure::ClosureHeader; - (*closure).func_ptr = test_no_capture_singleton_func as *const u8; + (*closure).info = crate::fn_info!(test_no_capture_singleton_func, 0); (*closure).capture_count = 0; (*closure).shape_id = crate::closure::shape::function_base_shape( crate::closure::shape::FunctionProtoKind::Function, diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/arraylike_callbacks.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/arraylike_callbacks.rs index 0ecfb78796..f86523b1be 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/arraylike_callbacks.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/arraylike_callbacks.rs @@ -40,7 +40,7 @@ fn arraylike_map_survives_a_moving_minor_inside_every_callback() { )); let recv_before = arr as usize; - let cb = crate::closure::js_closure_alloc_singleton(collect_then_double as *const u8); + let cb = crate::closure::js_closure_alloc_singleton(crate::fn_info!(collect_then_double, 3)); let cb_value = f64::from_bits(crate::JSValue::pointer(cb as *const u8).bits()); let cycles_before = copying_minor_cycles(); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/call_argument_lists.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/call_argument_lists.rs index 5afdf33174..701cb0664c 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/call_argument_lists.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/call_argument_lists.rs @@ -98,8 +98,11 @@ extern "C" fn record_rest_and_arguments_after_16( /// "value is not a function" out of a unit test instead of failing an /// assertion. Two minors under a pinned promotion age tenure it into the /// non-moving old generation. -fn tenured_closure(body: *const u8, capture_count: u32) -> *mut crate::closure::ClosureHeader { - let closure = crate::closure::js_closure_alloc(body, capture_count); +fn tenured_closure( + info: *const crate::closure::JsFunctionInfo, + capture_count: u32, +) -> *mut crate::closure::ClosureHeader { + let closure = crate::closure::js_closure_alloc(info, capture_count); let scope = RuntimeHandleScope::new(); let handle = scope.root_raw_mut_ptr(closure); { @@ -149,7 +152,7 @@ fn reflect_apply_roots_receiver_and_arguments_across_the_rebind_allocation() { // `this` capture — is the shape whose rebind CLONES, and that clone is the // allocation this test collects inside. let closure = tenured_closure( - record_this_and_six_args as *const u8, + crate::fn_info!(record_this_and_six_args, 6), crate::closure::CAPTURES_THIS_FLAG | 1, ); let scope = RuntimeHandleScope::new(); @@ -215,7 +218,7 @@ fn array_like_argument_lists_root_the_source_and_the_collected_elements() { let _evacuate = crate::gc::knob_overrides::ForcedEvacuationTestGuard::on(); register_runtime_handle_root_scanner_for_tests(); - let closure = tenured_closure(record_two_args as *const u8, 0); + let closure = tenured_closure(crate::fn_info!(record_two_args, 2), 0); let scope = RuntimeHandleScope::new(); let callee = scope.root_nanbox_f64(f64::from_bits(ptr_bits(closure as usize))); let source = crate::object::js_object_alloc(0, 3); @@ -279,9 +282,10 @@ fn rest_bundling_roots_the_rest_array_across_the_arguments_array() { crate::arena::ProtectionModeGuard::set(crate::arena::FromSpaceProtection::PoisonOnly); register_runtime_handle_root_scanner_for_tests(); - let body = record_rest_and_arguments_after_16 as *const u8; - crate::closure::js_register_closure_rest_and_arguments(body, 16); - let closure = crate::closure::js_closure_alloc(body, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(record_rest_and_arguments_after_16, 18; with_rest_kind(16, crate::closure::FN_REST_USER_AND_ARGUMENTS)), + 0, + ); let scope = RuntimeHandleScope::new(); let closure_handle = scope.root_raw_mut_ptr(closure); let tail = scope.root_nanbox_f64(test_string_value(b"rest-tail")); @@ -345,7 +349,7 @@ fn array_like_argument_lists_root_raw_untagged_heap_pointer_elements() { let _evacuate = crate::gc::knob_overrides::ForcedEvacuationTestGuard::on(); register_runtime_handle_root_scanner_for_tests(); - let callee = tenured_closure(record_two_args as *const u8, 0); + let callee = tenured_closure(crate::fn_info!(record_two_args, 2), 0); let scope = RuntimeHandleScope::new(); let callee_handle = scope.root_nanbox_f64(f64::from_bits(ptr_bits(callee as usize))); @@ -353,7 +357,7 @@ fn array_like_argument_lists_root_raw_untagged_heap_pointer_elements() { // address -- the exact shape `js_promise_new_with_executor` hands a // user's executor for `resolve`/`reject` (see proxy.rs's // `ValueMoveKind::RawHeapWord` doc comment). - let raw_closure = crate::closure::js_closure_alloc(record_two_args as *const u8, 0); + let raw_closure = crate::closure::js_closure_alloc(crate::fn_info!(record_two_args, 2), 0); let observer = scope.root_raw_mut_ptr(raw_closure); let raw_bits_before = raw_closure as usize as u64; diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/callback_scanners.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/callback_scanners.rs index f2d2ef32e8..6666a4f06b 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/callback_scanners.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/callback_scanners.rs @@ -1,4 +1,11 @@ use super::*; +use crate::closure::js_closure_alloc; + +/// The singleton-cache keys: each seed and its snapshot below name the same info. +static TEST_NO_CAPTURE_SINGLETON_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of(test_no_capture_singleton_func as crate::fn_info!(@ty 0)); +static TEST_CAPTURED_SINGLETON_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of(test_captured_singleton_func as crate::fn_info!(@ty 0)); #[test] fn test_map_set_foreach_runtime_handles_survive_callback_copied_minor_gc() { @@ -6,7 +13,7 @@ fn test_map_set_foreach_runtime_handles_survive_callback_copied_minor_gc() { let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); - let callback = crate::closure::js_closure_alloc(test_foreach_force_minor_gc as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(test_foreach_force_minor_gc, 2), 0); let callback_scope = RuntimeHandleScope::new(); let callback_handle = callback_scope.root_nanbox_f64(f64::from_bits(ptr_bits(callback as usize))); @@ -71,7 +78,7 @@ fn test_json_reviver_runtime_handles_survive_copied_minor_gc() { let setup_scope = RuntimeHandleScope::new(); let text = crate::string::js_string_from_bytes(input.as_ptr(), input.len() as u32); let text_handle = setup_scope.root_string_ptr(text); - let reviver = crate::closure::js_closure_alloc(test_reviver_force_minor_gc as *const u8, 0); + let reviver = js_closure_alloc(crate::fn_info!(test_reviver_force_minor_gc, 2), 0); let reviver_handle = setup_scope.root_raw_const_ptr(reviver); let before = gc_collection_count(); @@ -114,7 +121,7 @@ fn test_geisterhand_callback_then_json_reviver_copied_minor_gc() { gc_register_mutable_root_scanner(crate::object::shapes::scan_shape_table_rekey_mut); gc_register_mutable_root_scanner(crate::geisterhand_registry::scan_geisterhand_roots_mut); - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(&TEST_NO_CAPTURE_SINGLETON_INFO, 0); let original = closure as usize; let value = f64::from_bits(ptr_bits(original)); crate::geisterhand_registry::test_seed_geisterhand_roots(value, value, value); @@ -132,7 +139,7 @@ fn test_geisterhand_callback_then_json_reviver_copied_minor_gc() { let setup_scope = RuntimeHandleScope::new(); let text = crate::string::js_string_from_bytes(input.as_ptr(), input.len() as u32); let text_handle = setup_scope.root_string_ptr(text); - let reviver = crate::closure::js_closure_alloc(test_reviver_force_minor_gc as *const u8, 0); + let reviver = js_closure_alloc(crate::fn_info!(test_reviver_force_minor_gc, 2), 0); let reviver_handle = setup_scope.root_raw_const_ptr(reviver); let before = gc_collection_count(); @@ -166,7 +173,7 @@ fn test_json_reviver_treats_closure_property_as_leaf_after_copied_minor_gc() { let scope = RuntimeHandleScope::new(); let obj = crate::object::js_object_alloc(0, 1); let key = crate::string::js_string_from_bytes(b"fn".as_ptr(), 2); - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(&TEST_NO_CAPTURE_SINGLETON_INFO, 0); let original_obj = obj as usize; let original_closure = closure as usize; let obj_handle = scope.root_nanbox_u64(ptr_bits(obj as usize)); @@ -193,7 +200,7 @@ fn test_json_reviver_treats_closure_property_as_leaf_after_copied_minor_gc() { let empty_key = crate::string::js_string_from_bytes(b"".as_ptr(), 0); let empty_key_handle = scope.root_nanbox_f64(f64::from_bits(string_bits(empty_key as usize))); - let reviver = crate::closure::js_closure_alloc(test_reviver_count_closure_leaf as *const u8, 0); + let reviver = js_closure_alloc(crate::fn_info!(test_reviver_count_closure_leaf, 2), 0); let reviver_handle = scope.root_raw_const_ptr(reviver); let revived = unsafe { @@ -653,8 +660,7 @@ fn test_promise_then_and_finally_handles_survive_setup_gc() { let scope = RuntimeHandleScope::new(); let promise = crate::promise::js_promise_new(); let promise_handle = scope.root_raw_mut_ptr(promise); - let callback = - crate::closure::js_closure_alloc(test_promise_identity_force_minor_gc as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(test_promise_identity_force_minor_gc, 1), 0); let callback_handle = scope.root_raw_const_ptr(callback); let next = crate::promise::js_promise_then( @@ -680,8 +686,7 @@ fn test_promise_then_and_finally_handles_survive_setup_gc() { let promise = crate::promise::js_promise_new(); let promise_handle = scope.root_raw_mut_ptr(promise); - let on_finally = - crate::closure::js_closure_alloc(test_promise_finally_force_minor_gc as *const u8, 0); + let on_finally = js_closure_alloc(crate::fn_info!(test_promise_finally_force_minor_gc, 1), 0); let on_finally_handle = scope.root_raw_const_ptr(on_finally); let finally_next = crate::promise::js_promise_finally( @@ -880,8 +885,7 @@ fn test_microtask_dispatch_handles_survive_callback_gc() { let value = test_string_value(b"microtask"); let source = crate::promise::js_promise_resolved(value); let source_handle = scope.root_raw_mut_ptr(source); - let callback = - crate::closure::js_closure_alloc(test_promise_identity_force_minor_gc as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(test_promise_identity_force_minor_gc, 1), 0); let callback_handle = scope.root_raw_const_ptr(callback); let next = crate::promise::js_promise_then( source_handle.get_raw_mut_ptr::(), @@ -915,7 +919,7 @@ fn test_plugin_callback_registry_rewrites_young_closure_after_copied_minor_gc() let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); gc_register_mutable_root_scanner(crate::plugin::scan_plugin_roots_mut); - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(&TEST_NO_CAPTURE_SINGLETON_INFO, 0); let original = closure as usize; crate::plugin::test_seed_plugin_roots(ptr_bits(original)); @@ -936,7 +940,7 @@ fn test_geisterhand_callback_registry_rewrites_young_closure_after_copied_minor_ let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); gc_register_mutable_root_scanner(crate::geisterhand_registry::scan_geisterhand_roots_mut); - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(&TEST_NO_CAPTURE_SINGLETON_INFO, 0); let original = closure as usize; let value = f64::from_bits(ptr_bits(original)); crate::geisterhand_registry::test_seed_geisterhand_roots(value, value, value); @@ -956,7 +960,7 @@ fn test_ui_text_foreach_registry_rewrites_young_render_closure_after_copied_mino let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); gc_register_mutable_root_scanner(crate::ui_text_registry::scan_ui_text_registry_roots_mut); - let closure = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let closure = js_closure_alloc(&TEST_NO_CAPTURE_SINGLETON_INFO, 0); let original = closure as usize; let value = f64::from_bits(ptr_bits(original)); crate::ui_text_registry::test_seed_ui_text_registry_roots(value, value); @@ -1359,11 +1363,11 @@ fn test_gc_init_mutable_scanner_families_rewrite_runtime_slots() { ); crate::closure::test_clear_singleton_closure_caches(); crate::closure::test_seed_singleton_closure_cache( - test_no_capture_singleton_func as *const u8, + &TEST_NO_CAPTURE_SINGLETON_INFO, fixture.nursery_user as *mut crate::closure::ClosureHeader, ); crate::closure::test_seed_captured_singleton_closure_cache( - test_captured_singleton_func as *const u8, + &TEST_CAPTURED_SINGLETON_INFO, vec![fixture.nursery_bits], fixture.nursery_user as *mut crate::closure::ClosureHeader, ); @@ -1531,15 +1535,13 @@ fn test_gc_init_mutable_scanner_families_rewrite_runtime_slots() { (fixture.old_addr(), fixture.old_addr(), fixture.old_addr()) ); assert_eq!( - crate::closure::test_singleton_closure_cache_entry( - test_no_capture_singleton_func as *const u8 - ) - .map(|ptr| ptr as usize), + crate::closure::test_singleton_closure_cache_entry(&TEST_NO_CAPTURE_SINGLETON_INFO) + .map(|ptr| ptr as usize), Some(fixture.old_addr()) ); assert_eq!( crate::closure::test_captured_singleton_closure_cache_entries( - test_captured_singleton_func as *const u8 + &TEST_CAPTURED_SINGLETON_INFO ), vec![( vec![fixture.old_bits], @@ -1698,7 +1700,7 @@ fn test_array_sort_comparator_rooted_buffers_survive_copied_minor_gc() { SORT_COPIED_OBJECTS.with(|count| count.set(0)); let scope = RuntimeHandleScope::new(); let comparator = - crate::closure::js_closure_alloc(test_sort_comparator_force_minor_gc as *const u8, 0); + js_closure_alloc(crate::fn_info!(test_sort_comparator_force_minor_gc, 2), 0); let comparator_handle = scope.root_raw_const_ptr(comparator); let arr = crate::array::js_array_alloc(count as u32); let arr_handle = scope.root_raw_mut_ptr(arr); @@ -1761,7 +1763,7 @@ extern "C" fn test_tojson_force_minor_gc( } fn json_test_object_with_gc_forcing_tojson(scope: &RuntimeHandleScope) -> f64 { - let to_json = crate::closure::js_closure_alloc(test_tojson_force_minor_gc as *const u8, 0); + let to_json = js_closure_alloc(crate::fn_info!(test_tojson_force_minor_gc, 0), 0); let to_json_handle = scope.root_raw_mut_ptr(to_json); let nested = crate::object::js_object_alloc(0, 1); let nested_handle = scope.root_raw_mut_ptr(nested); @@ -1897,7 +1899,7 @@ fn test_string_replace_all_fn_rederives_subject_after_callback_minor_gc() { register_runtime_handle_root_scanner_for_tests(); let scope = RuntimeHandleScope::new(); - let callback = crate::closure::js_closure_alloc(test_replacer_force_minor_gc as *const u8, 0); + let callback = js_closure_alloc(crate::fn_info!(test_replacer_force_minor_gc, 3), 0); let callback_handle = scope.root_raw_mut_ptr(callback); let s = crate::string::js_string_from_bytes(b"one--two--three".as_ptr(), 15); let s_handle = scope.root_string_ptr(s); @@ -1956,8 +1958,7 @@ fn test_bigint64_sort_comparator_boxes_survive_copied_minor_gc() { register_runtime_handle_root_scanner_for_tests(); let scope = RuntimeHandleScope::new(); - let comparator = - crate::closure::js_closure_alloc(test_bigint_comparator_force_minor_gc as *const u8, 0); + let comparator = js_closure_alloc(crate::fn_info!(test_bigint_comparator_force_minor_gc, 2), 0); let comparator_handle = scope.root_raw_const_ptr(comparator); let lanes: [i64; 6] = [42, -7, 1_000_000_000_007, 0, -900_000_000_001, 13]; diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/generator_attach_prototype.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/generator_attach_prototype.rs index a27f628429..8de447ad19 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/generator_attach_prototype.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/generator_attach_prototype.rs @@ -238,10 +238,15 @@ fn attach_closure_prototype_survives_an_alloc_point_copying_minor_inside_the_cal register_runtime_handle_root_scanner_for_tests(); warm_generator_intrinsics(); - let func_ptr = fake_generator_body as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); - crate::closure::js_register_closure_generator_function(func_ptr); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!( + fake_generator_body, + 1; + with_declared(1), + with_flags(crate::closure::FN_GENERATOR) + ), + 0, + ); assert!(!closure.is_null(), "closure allocation failed"); let (obj_value, before) = rooted_instance(); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/hook_dispatch_handles.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/hook_dispatch_handles.rs index c872fcd4ab..d879806891 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/hook_dispatch_handles.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/hook_dispatch_handles.rs @@ -33,7 +33,7 @@ fn test_async_resource_subclass_run_in_scope_roots_inputs_across_a_resolve_gc() // The helper allocates (a key string, and the meta record the backing word // lives in), so it can move the receiver; take the address it hands back. let receiver = crate::async_hooks::test_link_async_resource_subclass(receiver, backing); - let callback = crate::closure::js_closure_alloc(test_current_async_id as *const u8, 0); + let callback = crate::closure::js_closure_alloc(crate::fn_info!(test_current_async_id, 0), 0); crate::async_hooks::test_force_next_async_resource_resolve_gc(); let before = crate::gc::copying_minor_cycles(); @@ -61,7 +61,8 @@ fn test_async_hook_option_lookup_roots_callbacks_across_copied_minor_gc() { let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); - let init = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let init = + crate::closure::js_closure_alloc(crate::fn_info!(test_no_capture_singleton_func, 0), 0); let original = init as usize; let options = hook_options(&[(b"init", init)]); @@ -83,8 +84,10 @@ fn test_closure_rest_dispatch_roots_args_during_rest_array_alloc_gc() { let trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); register_runtime_handle_root_scanner_for_tests(); - crate::closure::js_register_closure_rest(test_rest_first_value as *const u8, 0); - let closure = crate::closure::js_closure_alloc(test_rest_first_value as *const u8, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(test_rest_first_value, 1; with_rest(0)), + 0, + ); let value = test_string_value(b"rest-dispatch"); let args = [value]; @@ -114,11 +117,14 @@ fn test_bound_timer_dispatch_roots_args_during_async_hook_init_gc() { gc_register_mutable_root_scanner(crate::async_hooks::scan_async_hooks_roots_mut); gc_register_mutable_root_scanner(crate::timer::scan_timer_roots_mut); - let init_hook = - crate::closure::js_closure_alloc(test_async_hook_init_force_minor_gc as *const u8, 0); + let init_hook = crate::closure::js_closure_alloc( + crate::fn_info!(test_async_hook_init_force_minor_gc, 4), + 0, + ); enable_async_hook(&[(b"init", init_hook)]); - let timer_callback = crate::closure::js_closure_alloc(test_timer_capture_arg as *const u8, 0); + let timer_callback = + crate::closure::js_closure_alloc(crate::fn_info!(test_timer_capture_arg, 1), 0); let timer_callback_original = timer_callback as usize; let arg = test_string_value(b"bound-timer-arg"); let arg_original = (arg.to_bits() & POINTER_MASK) as usize; @@ -126,7 +132,7 @@ fn test_bound_timer_dispatch_roots_args_during_async_hook_init_gc() { let module_name = b"timers"; let namespace = crate::object::js_create_native_module_namespace(module_name.as_ptr(), module_name.len()); - let bound = crate::closure::js_closure_alloc(crate::closure::BOUND_METHOD_FUNC_PTR, 3); + let bound = crate::closure::js_closure_alloc(&crate::closure::BOUND_METHOD_INFO, 3); crate::closure::js_closure_set_capture_f64(bound, 0, namespace); let method = b"setTimeout"; crate::closure::js_closure_set_capture_ptr(bound, 1, method.as_ptr() as i64); @@ -166,14 +172,18 @@ fn test_timer_tick_roots_callback_args_and_previous_context_across_hooks() { gc_register_mutable_root_scanner(crate::async_hooks::scan_async_hooks_roots_mut); gc_register_mutable_root_scanner(crate::timer::scan_timer_roots_mut); - let before_hook = - crate::closure::js_closure_alloc(test_async_hook_event_force_minor_gc as *const u8, 0); - let after_hook = - crate::closure::js_closure_alloc(test_async_hook_event_force_minor_gc as *const u8, 0); + let before_hook = crate::closure::js_closure_alloc( + crate::fn_info!(test_async_hook_event_force_minor_gc, 1), + 0, + ); + let after_hook = crate::closure::js_closure_alloc( + crate::fn_info!(test_async_hook_event_force_minor_gc, 1), + 0, + ); enable_async_hook(&[(b"before", before_hook), (b"after", after_hook)]); crate::async_context::clear_store(ALS_HANDLE); - let callback = crate::closure::js_closure_alloc(test_timer_capture_arg as *const u8, 0); + let callback = crate::closure::js_closure_alloc(crate::fn_info!(test_timer_capture_arg, 1), 0); let arg = test_string_value(b"timer-tick-arg"); let timer_args = [arg]; let timer_id = unsafe { @@ -210,8 +220,9 @@ fn test_timer_tick_roots_the_complete_detached_expired_batch() { gc_register_mutable_root_scanner(crate::timer::scan_timer_roots_mut); let collecting_callback = - crate::closure::js_closure_alloc(test_timer_force_minor_gc as *const u8, 0); - let second_callback = crate::closure::js_closure_alloc(test_timer_capture_arg as *const u8, 0); + crate::closure::js_closure_alloc(crate::fn_info!(test_timer_force_minor_gc, 0), 0); + let second_callback = + crate::closure::js_closure_alloc(crate::fn_info!(test_timer_capture_arg, 1), 0); let second_callback_original = second_callback as usize; let second_arg = test_string_value(b"detached-timer-batch"); let second_arg_original = (second_arg.to_bits() & POINTER_MASK) as usize; @@ -259,12 +270,15 @@ fn test_next_tick_previous_context_survives_hook_gc() { gc_register_mutable_root_scanner(crate::async_hooks::scan_async_hooks_roots_mut); gc_register_mutable_root_scanner(crate::builtins::scan_queued_microtask_roots_mut); - let before_hook = - crate::closure::js_closure_alloc(test_async_hook_event_force_minor_gc as *const u8, 0); + let before_hook = crate::closure::js_closure_alloc( + crate::fn_info!(test_async_hook_event_force_minor_gc, 1), + 0, + ); enable_async_hook(&[(b"before", before_hook)]); crate::async_context::clear_store(ALS_HANDLE); - let callback = crate::closure::js_closure_alloc(test_no_capture_singleton_func as *const u8, 0); + let callback = + crate::closure::js_closure_alloc(crate::fn_info!(test_no_capture_singleton_func, 0), 0); crate::builtins::js_queue_next_tick(callback as i64); let previous = test_string_value(b"nexttick-previous-context"); @@ -294,7 +308,7 @@ fn test_array_map_runtime_handles_survive_callback_copied_minor_gc() { let input_ptr = (input.to_bits() & POINTER_MASK) as usize; let source = test_array_from_values(&[input]); let callback = - crate::closure::js_closure_alloc(test_array_identity_force_minor_gc as *const u8, 0); + crate::closure::js_closure_alloc(crate::fn_info!(test_array_identity_force_minor_gc, 2), 0); let before = gc_collection_count(); let result = crate::array::js_array_map(source, callback); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/json_record_output.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/json_record_output.rs index 3b577a35f7..355962fa2f 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/json_record_output.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/json_record_output.rs @@ -21,7 +21,7 @@ fn json_nested_records_fallback_runs_getter_once_and_survives_actual_movement() let child = crate::object::js_object_get_field(parent.as_pointer(), 1); let child = scope.root_nanbox_u64(child.bits()); let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - array_getter_collects as *const u8, + crate::fn_info!(array_getter_collects, 0), 0, )); let descriptor = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); @@ -96,7 +96,7 @@ fn json_array_getter_runs_once_and_rederives_later_elements_after_movement() { let scope = RuntimeHandleScope::new(); let array = scope.root_nanbox_u64(value.bits()); let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - array_getter_collects as *const u8, + crate::fn_info!(array_getter_collects, 0), 0, )); let descriptor = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); @@ -165,7 +165,7 @@ fn json_grown_array_getter_flags_live_head_and_survives_movement() { "the descriptor must be defined through a forwarding alias" ); let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - array_getter_collects as *const u8, + crate::fn_info!(array_getter_collects, 0), 0, )); let descriptor = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_construction.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_construction.rs index c86568e799..6f85ca2fce 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_construction.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_construction.rs @@ -324,9 +324,9 @@ fn perex_constructor_coercion_reacquires_original_pattern_before_successful_comp let before = pattern.with_const_ptr(|p: *const StringHeader| p as usize); let flags = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 1)); let key = text(&scope, b"toString"); - let fp = flags_collect_then_return as *const u8; - crate::closure::js_register_closure_arity(fp, 0); - let closure = crate::closure::js_closure_alloc_singleton(fp); + let closure = crate::closure::js_closure_alloc_singleton( + crate::fn_info!(flags_collect_then_return, 0; with_declared(0)), + ); flags.with_mut_ptr(|flags| { key.with_const_ptr(|key| { crate::object::js_object_set_field_by_name( @@ -364,9 +364,9 @@ fn perex_constructor_flags_coercion_can_collect_and_throw_without_source_copy() let before = pattern.with_const_ptr(|p: *const StringHeader| p as usize); let flags = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 1)); let key = text(&scope, b"toString"); - let fp = flags_collect_then_throw as *const u8; - crate::closure::js_register_closure_arity(fp, 0); - let closure = crate::closure::js_closure_alloc_singleton(fp); + let closure = crate::closure::js_closure_alloc_singleton( + crate::fn_info!(flags_collect_then_throw, 0; with_declared(0)), + ); flags.with_mut_ptr(|flags| { key.with_const_ptr(|key| { crate::object::js_object_set_field_by_name( diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_dispatch.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_dispatch.rs index 88d89cd4be..3dd9c53084 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_dispatch.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_dispatch.rs @@ -30,10 +30,14 @@ fn regex<'s>(scope: &'s RuntimeHandleScope) -> RuntimeHandle<'s> { }); scope.root_nanbox_f64(js_nanbox_pointer(re as i64)) } -fn function<'s>(scope: &'s RuntimeHandleScope, fp: *const u8, arity: u32) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); +/// A function object running the body `info` describes (its declared arity +/// included). +fn function<'s>( + scope: &'s RuntimeHandleScope, + info: *const crate::closure::JsFunctionInfo, +) -> RuntimeHandle<'s> { scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc_singleton(fp) as i64, + crate::closure::js_closure_alloc_singleton(info) as i64, )) } fn put(owner: &RuntimeHandle<'_>, name: &[u8], value: &RuntimeHandle<'_>) { @@ -107,8 +111,7 @@ extern "C" fn collecting_getter( _this: crate::closure::JsThis, ) -> f64 { gc_collect_minor(); - let fp = collect_and_echo as *const u8; - crate::closure::js_register_closure_arity(fp, 1); + let fp = crate::fn_info!(collect_and_echo, 1; with_declared(1)); js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(fp) as i64) } extern "C" fn collect_missing_apply( @@ -173,8 +176,7 @@ extern "C" fn ordered_getter( _this: crate::closure::JsThis, ) -> f64 { ORDER.with(|n| n.set(n.get() * 10 + 2)); - let fp = ordered_exec as *const u8; - crate::closure::js_register_closure_arity(fp, 1); + let fp = crate::fn_info!(ordered_exec, 1; with_declared(1)); js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(fp) as i64) } @@ -189,8 +191,8 @@ fn perex_dispatch_public_test_honors_exec_and_noncallable_fallback() { assert!(crate::object::regex_proto_thunks::is_builtin_regexp_exec( api::finish(dispatch::get(&re, b"exec")) )); - let yes = function(&scope, return_this as *const u8, 1); - let no = function(&scope, return_null as *const u8, 1); + let yes = function(&scope, crate::fn_info!(return_this, 1; with_declared(1))); + let no = function(&scope, crate::fn_info!(return_null, 1; with_declared(1))); put(&re, b"exec", &yes); assert!( test(&re, &subject), @@ -222,9 +224,9 @@ fn perex_dispatch_generic_prototype_test_coerces_before_getting_exec() { let scope = RuntimeHandleScope::new(); let receiver = object(&scope); let argument = object(&scope); - let stringer = function(&scope, ordered_string as *const u8, 0); + let stringer = function(&scope, crate::fn_info!(ordered_string, 0; with_declared(0))); put(&argument, b"toString", &stringer); - let get = function(&scope, ordered_getter as *const u8, 0); + let get = function(&scope, crate::fn_info!(ordered_getter, 0; with_declared(0))); getter(&receiver, &get); let method = scope.root_nanbox_f64(crate::collection_iter::builtin_prototype_method( "RegExp", "test", @@ -269,7 +271,10 @@ fn perex_dispatch_getter_and_callback_reacquire_original_input_after_gc() { let receiver = object(&scope); let input = text(&scope, b"\xed\xa0\x80-original"); let before = handle_address::(&input); - let method = function(&scope, collecting_getter as *const u8, 0); + let method = function( + &scope, + crate::fn_info!(collecting_getter, 0; with_declared(0)), + ); getter(&receiver, &method); let roots = RuntimeHandleScope::active_len_for_tests(); let result = api::finish(dispatch::execute( @@ -307,11 +312,11 @@ fn perex_dispatch_actual_throws_restore_this_roots_and_accounting() { let before = handle_address::(&input); let is_getter = mode == 1; let fp = if is_getter { - throw_getter as *const u8 + crate::fn_info!(throw_getter, 0; with_declared(0)) } else { - throw_exec as *const u8 + crate::fn_info!(throw_exec, 1; with_declared(1)) }; - let method = function(&scope, fp, u32::from(!is_getter)); + let method = function(&scope, fp); if mode == 2 { let handler = object(&scope); let proxy = scope.root_nanbox_f64(crate::proxy::js_proxy_new( @@ -347,7 +352,7 @@ fn perex_dispatch_validates_override_results_and_keeps_one_work_allowance() { let scope = RuntimeHandleScope::new(); let receiver = object(&scope); let input = text(&scope, b"x"); - let method = function(&scope, read_answer as *const u8, 1); + let method = function(&scope, crate::fn_info!(read_answer, 1; with_declared(1))); put(&receiver, b"exec", &method); for value in [ 1.0, @@ -411,7 +416,10 @@ fn perex_dispatch_callable_proxy_keeps_receiver_and_noncallable_proxy_falls_back let receiver = object(&scope); let input = text(&scope, b"original-proxy-input"); let before = handle_address::(&input); - let function = function(&scope, collect_and_echo as *const u8, 1); + let function = function( + &scope, + crate::fn_info!(collect_and_echo, 1; with_declared(1)), + ); let handler = object(&scope); let proxy = scope.root_nanbox_f64(crate::proxy::js_proxy_new( function.get_nanbox_f64(), @@ -458,10 +466,13 @@ fn perex_dispatch_proxy_apply_getter_and_nested_trap_survive_movement() { let receiver = object(&scope); let input = text(&scope, b"original-apply-input"); let before = handle_address::(&input); - let target = function(&scope, collect_and_echo as *const u8, 1); + let target = function( + &scope, + crate::fn_info!(collect_and_echo, 1; with_declared(1)), + ); let handler = object(&scope); if nested_trap { - let trap = function(&scope, echo_apply as *const u8, 3); + let trap = function(&scope, crate::fn_info!(echo_apply, 3; with_declared(3))); let inner_handler = object(&scope); let inner = scope.root_nanbox_f64(crate::proxy::js_proxy_new( trap.get_nanbox_f64(), @@ -469,7 +480,10 @@ fn perex_dispatch_proxy_apply_getter_and_nested_trap_survive_movement() { )); put(&handler, b"apply", &inner); } else { - let get = function(&scope, collect_missing_apply as *const u8, 0); + let get = function( + &scope, + crate::fn_info!(collect_missing_apply, 0; with_declared(0)), + ); getter_named(&handler, b"apply", &get); } let proxy = scope.root_nanbox_f64(crate::proxy::js_proxy_new( @@ -499,7 +513,7 @@ fn perex_dispatch_skips_the_exec_lookup_only_when_nothing_can_observe_it() { let miss = text(&scope, b"x"); let hit = text(&scope, b"NEVER"); let lookups = || dispatch::EXEC_LOOKUPS.with(Cell::get); - let yes = function(&scope, return_this as *const u8, 1); + let yes = function(&scope, crate::fn_info!(return_this, 1; with_declared(1))); // Untouched: the builtin runs with no lookup, and still answers. The // realm records RegExp.prototype's canonical site on first use, so the diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_glob.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_glob.rs index 2bc3485c95..013c0e6d61 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_glob.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_glob.rs @@ -192,11 +192,11 @@ fn perex_glob_filesystem_callbacks_reenter_collect_and_throw_without_lost_roots( crate::object::js_object_alloc(0, 4) as i64 )); set(&options, b"cwd", cwd.get_nanbox_f64()); - crate::closure::js_register_closure_arity(exclude as *const u8, 1); - let callback = scope - .root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc(exclude as *const u8, 0) as i64, - )); + + let callback = scope.root_nanbox_f64(js_nanbox_pointer(crate::closure::js_closure_alloc( + crate::fn_info!(exclude, 1; with_declared(1)), + 0, + ) as i64)); set(&options, b"exclude", callback.get_nanbox_f64()); let before = callback.get_nanbox_f64().to_bits(); let live = external_side_live_bytes(); @@ -213,9 +213,9 @@ fn perex_glob_filesystem_callbacks_reenter_collect_and_throw_without_lost_roots( ); assert_ne!(callback.get_nanbox_f64().to_bits(), before); assert_eq!(external_side_live_bytes(), live); - crate::closure::js_register_closure_arity(throw_exclude as *const u8, 1); + let callback = scope.root_nanbox_f64(js_nanbox_pointer(crate::closure::js_closure_alloc( - throw_exclude as *const u8, + crate::fn_info!(throw_exclude, 1; with_declared(1)), 0, ) as i64)); set(&options, b"exclude", callback.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_all.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_all.rs index 3e041d91b7..d0c99de270 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_all.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_all.rs @@ -24,10 +24,14 @@ fn regex<'s>(scope: &'s RuntimeHandleScope, source: &[u8], flags: &[u8]) -> Runt flags.get_nanbox_f64(), ) as i64)) } -fn function<'s>(scope: &'s RuntimeHandleScope, fp: *const u8, arity: u32) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); +/// A function object running the body `info` describes (its declared arity +/// included). +fn function<'s>( + scope: &'s RuntimeHandleScope, + info: *const crate::closure::JsFunctionInfo, +) -> RuntimeHandle<'s> { scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc_singleton(fp) as i64, + crate::closure::js_closure_alloc_singleton(info) as i64, )) } fn get(owner: &RuntimeHandle<'_>, name: &[u8]) -> f64 { @@ -189,7 +193,7 @@ fn custom<'s>( let matcher = object(scope); put(&receiver, b"matcher", matcher.get_nanbox_f64()); let holder = object(scope); - let constructor = function(scope, factory as *const u8, 2); + let constructor = function(scope, crate::fn_info!(factory, 2; with_declared(2))); symbol(&holder, "species", constructor.get_nanbox_f64()); put(&receiver, b"constructor", holder.get_nanbox_f64()); let flags = text(scope, flags); @@ -357,7 +361,7 @@ fn perex_match_all_is_lazy_observes_exec_changes_and_retries_after_throw() { super::perex_public::register_host_roots(); let scope = RuntimeHandleScope::new(); let (receiver, matcher) = custom(&scope, b"gu"); - let method = function(&scope, throw_exec as *const u8, 1); + let method = function(&scope, crate::fn_info!(throw_exec, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); let empty = text(&scope, b""); put(&matcher, b"0", empty.get_nanbox_f64()); @@ -381,7 +385,7 @@ fn perex_match_all_is_lazy_observes_exec_changes_and_retries_after_throw() { ); assert_eq!(RuntimeHandleScope::active_len_for_tests(), roots); assert_eq!(external_side_live_bytes(), live); - let method = function(&scope, return_this as *const u8, 1); + let method = function(&scope, crate::fn_info!(return_this, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); let step = scope.root_nanbox_f64(next(&iter)); assert_eq!( @@ -389,7 +393,7 @@ fn perex_match_all_is_lazy_observes_exec_changes_and_retries_after_throw() { matcher.get_nanbox_f64().to_bits() ); assert_eq!(get(&matcher, b"lastIndex"), 2.0); - let method = function(&scope, no_match as *const u8, 1); + let method = function(&scope, crate::fn_info!(no_match, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); let step = scope.root_nanbox_f64(next(&iter)); assert_eq!(get(&step, b"done").to_bits(), crate::value::TAG_TRUE); @@ -403,9 +407,9 @@ fn perex_match_all_string_hooks_precede_coercion_but_follow_global_validation() let scope = RuntimeHandleScope::new(); let receiver = object(&scope); let pattern = object(&scope); - let throw = function(&scope, throw_getter as *const u8, 0); + let throw = function(&scope, crate::fn_info!(throw_getter, 0; with_declared(0))); put(&receiver, b"toString", throw.get_nanbox_f64()); - let hook = function(&scope, identity as *const u8, 1); + let hook = function(&scope, crate::fn_info!(identity, 1; with_declared(1))); symbol(&pattern, "matchAll", hook.get_nanbox_f64()); let method = scope.root_nanbox_f64(crate::collection_iter::builtin_prototype_method( "String", "matchAll", @@ -449,9 +453,9 @@ fn perex_match_all_nonglobal_does_not_read_zero_and_builtin_next_has_its_own_bra super::perex_public::register_host_roots(); let scope = RuntimeHandleScope::new(); let (receiver, matcher) = custom(&scope, b""); - let method = function(&scope, return_this as *const u8, 1); + let method = function(&scope, crate::fn_info!(return_this, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); - let throw = function(&scope, throw_getter as *const u8, 0); + let throw = function(&scope, crate::fn_info!(throw_getter, 0; with_declared(0))); getter(&matcher, b"0", &throw); let input = text(&scope, b"original"); let iter = scope.root_nanbox_f64(api::finish(match_all::regexp( @@ -459,7 +463,7 @@ fn perex_match_all_nonglobal_does_not_read_zero_and_builtin_next_has_its_own_bra input.get_nanbox_f64(), ))); let builtin = scope.root_nanbox_f64(get(&iter, b"next")); - let override_next = function(&scope, identity as *const u8, 1); + let override_next = function(&scope, crate::fn_info!(identity, 1; with_declared(1))); put(&iter, b"next", override_next.get_nanbox_f64()); let argument = scope.root_nanbox_f64(57.0); assert_eq!( @@ -513,7 +517,10 @@ fn perex_match_all_proxy_species_and_dynamic_regexp_constructors_keep_original_s let holder = scope.root_nanbox_f64(get(&receiver, b"constructor")); let factory = scope.root_nanbox_f64(api::finish(dispatch::get_symbol(&holder, "species"))); let handler = object(&scope); - let collecting = function(&scope, collecting_missing_construct as *const u8, 0); + let collecting = function( + &scope, + crate::fn_info!(collecting_missing_construct, 0; with_declared(0)), + ); getter(&handler, b"construct", &collecting); let proxy = scope.root_nanbox_f64(crate::proxy::js_proxy_new( factory.get_nanbox_f64(), @@ -523,7 +530,7 @@ fn perex_match_all_proxy_species_and_dynamic_regexp_constructors_keep_original_s proxy.get_nanbox_f64() )); symbol(&holder, "species", proxy.get_nanbox_f64()); - let method = function(&scope, no_match as *const u8, 1); + let method = function(&scope, crate::fn_info!(no_match, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); let input = text(&scope, b"input through proxy species"); let iter = scope.root_nanbox_f64(api::finish(match_all::regexp( @@ -619,19 +626,34 @@ fn perex_match_all_species_and_lastindex_order_survives_collecting_callbacks() { let (receiver, matcher) = custom(&scope, b"g"); let holder = object(&scope); put(&receiver, b"holder", holder.get_nanbox_f64()); - let factory = function(&scope, ordered_factory as *const u8, 2); + let factory = function( + &scope, + crate::fn_info!(ordered_factory, 2; with_declared(2)), + ); put(&holder, b"factory", factory.get_nanbox_f64()); let flags = text(&scope, b"gu"); put(&receiver, b"flagText", flags.get_nanbox_f64()); for (name, fp) in [ - (b"constructor".as_slice(), ordered_constructor as *const u8), - (b"flags", ordered_flags as *const u8), - (b"lastIndex", ordered_index as *const u8), + ( + b"constructor".as_slice(), + crate::fn_info!(ordered_constructor, 0; with_declared(0)), + ), + ( + b"flags", + crate::fn_info!(ordered_flags, 0; with_declared(0)), + ), + ( + b"lastIndex", + crate::fn_info!(ordered_index, 0; with_declared(0)), + ), ] { - let method = function(&scope, fp, 0); + let method = function(&scope, fp); getter(&receiver, name, &method); } - let method = function(&scope, ordered_species as *const u8, 0); + let method = function( + &scope, + crate::fn_info!(ordered_species, 0; with_declared(0)), + ); let key = js_nanbox_pointer(crate::symbol::well_known_symbol("species") as i64); accessor( &holder, @@ -639,7 +661,10 @@ fn perex_match_all_species_and_lastindex_order_survives_collecting_callbacks() { method.get_nanbox_f64(), f64::from_bits(TAG_UNDEFINED), ); - let setter = function(&scope, ordered_set_index as *const u8, 1); + let setter = function( + &scope, + crate::fn_info!(ordered_set_index, 1; with_declared(1)), + ); accessor( &matcher, js_nanbox_string(crate::string::canonical_key(b"lastIndex") as i64), @@ -649,9 +674,9 @@ fn perex_match_all_species_and_lastindex_order_survives_collecting_callbacks() { let input = object(&scope); let text = text(&scope, "😀".as_bytes()); put(&input, b"text", text.get_nanbox_f64()); - let coercer = function(&scope, ordered_input as *const u8, 0); + let coercer = function(&scope, crate::fn_info!(ordered_input, 0; with_declared(0))); put(&input, b"toString", coercer.get_nanbox_f64()); - let method = function(&scope, throw_exec as *const u8, 1); + let method = function(&scope, crate::fn_info!(throw_exec, 1; with_declared(1))); put(&matcher, b"exec", method.get_nanbox_f64()); ORDER.with(|n| n.set(0)); let before = text.get_nanbox_f64().to_bits(); @@ -684,8 +709,11 @@ fn perex_match_all_species_rejects_proxy_of_arrow_before_flags_and_keeps_revoked let (receiver, _) = custom(&scope, b"g"); let holder = scope.root_nanbox_f64(get(&receiver, b"constructor")); let constructor = scope.root_nanbox_f64(api::finish(dispatch::get_symbol(&holder, "species"))); - let arrow = function(&scope, arrow_species as *const u8, 1); - crate::closure::js_register_closure_arrow_function(arrow_species as *const u8); + let arrow = function( + &scope, + crate::fn_info!(arrow_species, 1; with_declared(1), with_flags(crate::closure::FN_ARROW)), + ); + let handler = object(&scope); let arrow_proxy = scope.root_nanbox_f64(crate::proxy::js_proxy_new( arrow.get_nanbox_f64(), @@ -695,7 +723,7 @@ fn perex_match_all_species_rejects_proxy_of_arrow_before_flags_and_keeps_revoked arrow_proxy.get_nanbox_f64() )); symbol(&holder, "species", arrow_proxy.get_nanbox_f64()); - let throw = function(&scope, throw_getter as *const u8, 0); + let throw = function(&scope, crate::fn_info!(throw_getter, 0; with_declared(0))); getter(&receiver, b"flags", &throw); let input = text(&scope, b"input"); let error = crate::exception::catch_js_throw(|| { diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_search.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_search.rs index 7518c8fa5b..8d3c0a1e8e 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_search.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_match_search.rs @@ -24,10 +24,12 @@ fn regex<'s>(scope: &'s RuntimeHandleScope, p: &[u8], f: &[u8]) -> RuntimeHandle crate::value::js_get_string_pointer_unified(f.get_nanbox_f64()) as *const StringHeader, ) as i64)) } -fn function<'s>(scope: &'s RuntimeHandleScope, fp: *const u8, arity: u32) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); +fn function<'s>( + scope: &'s RuntimeHandleScope, + info: *const crate::closure::JsFunctionInfo, +) -> RuntimeHandle<'s> { scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc_singleton(fp) as i64, + crate::closure::js_closure_alloc_singleton(info) as i64, )) } fn put(owner: &RuntimeHandle<'_>, name: &[u8], value: &RuntimeHandle<'_>) { @@ -206,7 +208,10 @@ fn perex_search_restores_negative_zero_and_original_object_across_gc() { api::finish(dispatch::get(&re, b"lastIndex")).to_bits(), (-0.0f64).to_bits() ); - let method = function(&scope, search_override as *const u8, 1); + let method = function( + &scope, + crate::fn_info!(search_override, 1; with_declared(1)), + ); put(&re, b"exec", &method); let answer = text(&scope, b"arbitrary index"); put(&re, b"index", &answer); @@ -246,7 +251,7 @@ fn perex_search_throw_order_and_sticky_behavior_follow_public_operations() { re.get_nanbox_f64(), )); assert_eq!(api::finish(dispatch::get(&result, b"index")), 1.0); - let method = function(&scope, search_throw as *const u8, 1); + let method = function(&scope, crate::fn_info!(search_throw, 1; with_declared(1))); put(&re, b"exec", &method); api::finish(dispatch::set_last_index(&re, 9.0)); assert_eq!( @@ -291,7 +296,10 @@ fn perex_generic_global_match_preserves_output_during_collecting_overrides() { let flags = text(&scope, b"gu"); put(&receiver, b"flags", &flags); put_number(&receiver, b"calls", 0.0); - let method = function(&scope, global_override as *const u8, 1); + let method = function( + &scope, + crate::fn_info!(global_override, 1; with_declared(1)), + ); put(&receiver, b"exec", &method); let input = text(&scope, "😀".as_bytes()); let before = input.get_nanbox_f64().to_bits(); @@ -316,9 +324,9 @@ fn perex_string_symbol_hooks_preserve_values_before_receiver_coercion() { let scope = RuntimeHandleScope::new(); let receiver = object(&scope); let pattern = object(&scope); - let coercion = function(&scope, throw_string as *const u8, 0); + let coercion = function(&scope, crate::fn_info!(throw_string, 0; with_declared(0))); put(&receiver, b"toString", &coercion); - let hook = function(&scope, identity as *const u8, 1); + let hook = function(&scope, crate::fn_info!(identity, 1; with_declared(1))); for name in ["match", "search"] { symbol(&pattern, name, &hook); let method = scope.root_nanbox_f64(crate::collection_iter::builtin_prototype_method( @@ -363,7 +371,10 @@ fn perex_match_reads_overridden_flags_and_global_getters() { super::perex_public::register_host_roots(); let scope = RuntimeHandleScope::new(); let re = regex(&scope, b"(?:)", b"g"); - let getter = function(&scope, collecting_flags as *const u8, 0); + let getter = function( + &scope, + crate::fn_info!(collecting_flags, 0; with_declared(0)), + ); let descriptor = object(&scope); put(&descriptor, b"get", &getter); put_number( @@ -409,7 +420,7 @@ fn perex_regexp_prototype_ignores_constructor_and_honors_explicit_parent() { let original = scope.root_nanbox_f64(crate::object::js_object_get_prototype_of( re.get_nanbox_f64(), )); - let getter = function(&scope, throw_string as *const u8, 0); + let getter = function(&scope, crate::fn_info!(throw_string, 0; with_declared(0))); let descriptor = object(&scope); put(&descriptor, b"get", &getter); let key = crate::string::canonical_key(b"constructor"); @@ -433,7 +444,7 @@ fn perex_regexp_prototype_ignores_constructor_and_honors_explicit_parent() { original.get_nanbox_f64().to_bits() ); let parent = object(&scope); - let hook = function(&scope, identity as *const u8, 1); + let hook = function(&scope, crate::fn_info!(identity, 1; with_declared(1))); symbol(&parent, "match", &hook); assert_eq!( crate::proxy::js_reflect_set_prototype_of(re.get_nanbox_f64(), parent.get_nanbox_f64()) diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_public.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_public.rs index 8ffc0b3854..a500e0f2c6 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_public.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_public.rs @@ -333,9 +333,11 @@ fn perex_public_nonglobal_test_propagates_lastindex_coercion_throw() { let input = text(&scope, b"a"); let coercer = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 1)); let key = text(&scope, b"valueOf"); - let fp = throw_on_coercion as *const u8; - crate::closure::js_register_closure_arity(fp, 0); - let closure = crate::closure::js_closure_alloc_singleton(fp); + let closure = crate::closure::js_closure_alloc_singleton(crate::fn_info!( + throw_on_coercion, + 0; + with_declared(0) + )); coercer.with_mut_ptr::(|coercer| { key.with_const_ptr::(|key| { crate::object::js_object_set_field_by_name( diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace.rs index 6dd759f8ff..850ee8d392 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace.rs @@ -29,12 +29,10 @@ pub(super) fn regex<'s>( } pub(super) fn function<'s>( scope: &'s RuntimeHandleScope, - fp: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, ) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc_singleton(fp) as i64, + crate::closure::js_closure_alloc_singleton(info) as i64, )) } pub(super) fn get(owner: &RuntimeHandle<'_>, name: &[u8]) -> f64 { @@ -91,13 +89,11 @@ pub(super) fn bytes(value: f64) -> Vec { } pub(super) fn captured<'s>( scope: &'s RuntimeHandleScope, - fp: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, state: &RuntimeHandle<'_>, ) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); let f = scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc(fp, 1) as i64 + crate::closure::js_closure_alloc(info, 1) as i64 )); crate::closure::js_closure_set_capture_f64( js_nanbox_get_pointer(f.get_nanbox_f64()) as *mut _, @@ -316,7 +312,11 @@ fn perex_replace_callbacks_observe_completed_global_exec_and_original_input() { let re = regex(&scope, b"(?a)(z)?", b"g"); put(&re, b"input", input.get_nanbox_f64()); put(&re, b"calls", 0.0); - let callback = captured(&scope, builtin_callback as *const u8, 6, &re); + let callback = captured( + &scope, + crate::fn_info!(builtin_callback, 6; with_declared(6)), + &re, + ); let before = input.get_nanbox_f64().to_bits(); let result = scope.root_nanbox_f64(crate::regex::js_string_replace_all_js( input.get_nanbox_f64(), @@ -339,7 +339,7 @@ fn alias_state<'s>(scope: &'s RuntimeHandleScope) -> RuntimeHandle<'s> { put(&state, b"calls", 0.0); put(&state, b"replacements", 0.0); put(&state, b"length", 1.0); - let exec = function(scope, alias_exec as *const u8, 1); + let exec = function(scope, crate::fn_info!(alias_exec, 1; with_declared(1))); put(&state, b"exec", exec.get_nanbox_f64()); state } @@ -352,7 +352,11 @@ fn perex_replace_retains_aliases_and_calls_replacer_for_ignored_overlap() { super::perex_public::register_host_roots(); let scope = RuntimeHandleScope::new(); let state = alias_state(&scope); - let callback = captured(&scope, alias_callback as *const u8, 4, &state); + let callback = captured( + &scope, + crate::fn_info!(alias_callback, 4; with_declared(4)), + &state, + ); let input = text(&scope, b"abc"); assert_eq!( bytes(api::finish(replace::regexp( @@ -373,7 +377,11 @@ fn perex_replace_named_getters_run_for_ignored_overlap_and_coerce_values() { let scope = RuntimeHandleScope::new(); let state = alias_state(&scope); let groups = object(&scope); - let method = captured(&scope, named_getter as *const u8, 0, &state); + let method = captured( + &scope, + crate::fn_info!(named_getter, 0; with_declared(0)), + &state, + ); let key = text(&scope, b"k"); accessor( &groups, @@ -402,10 +410,10 @@ fn perex_replace_hooks_preserve_receiver_and_arbitrary_result_before_coercion() super::perex_public::register_host_roots(); let scope = RuntimeHandleScope::new(); let input = object(&scope); - let throwing = function(&scope, throw_text as *const u8, 0); + let throwing = function(&scope, crate::fn_info!(throw_text, 0; with_declared(0))); put(&input, b"toString", throwing.get_nanbox_f64()); let search = object(&scope); - let method = function(&scope, hook as *const u8, 2); + let method = function(&scope, crate::fn_info!(hook, 2; with_declared(2))); symbol(&search, "replace", method.get_nanbox_f64()); let result = crate::regex::js_string_replace_js(input.get_nanbox_f64(), search.get_nanbox_f64(), 47.0); @@ -429,7 +437,10 @@ fn perex_replace_collecting_throw_cleans_native_arguments_and_roots() { let scope = RuntimeHandleScope::new(); let input = text(&scope, b"abc"); let search = text(&scope, b"b"); - let callback = function(&scope, collecting_throw as *const u8, 3); + let callback = function( + &scope, + crate::fn_info!(collecting_throw, 3; with_declared(3)), + ); // A young object whose move proves the throwing callback's collection ran. let previous = object(&scope); // Warm lazy String/Function prototype setup before checking native bytes. @@ -462,8 +473,9 @@ extern "C" fn primitive_hook_getter( ) -> f64 { assert_eq!(this.as_f64(), 23.0); gc_collect_minor(); - crate::closure::js_register_closure_arity(hook as *const u8, 2); - js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(hook as *const u8) as i64) + js_nanbox_pointer(crate::closure::js_closure_alloc_singleton( + crate::fn_info!(hook, 2; with_declared(2)), + ) as i64) } #[test] @@ -480,7 +492,10 @@ fn perex_replace_primitive_search_skips_prototype_hook_and_coerces_receiver() { let key = scope.root_nanbox_f64(js_nanbox_pointer( crate::symbol::well_known_symbol("replace") as i64, )); - let getter = function(&scope, primitive_hook_getter as *const u8, 0); + let getter = function( + &scope, + crate::fn_info!(primitive_hook_getter, 0; with_declared(0)), + ); accessor( &proto, key.get_nanbox_f64(), @@ -488,7 +503,7 @@ fn perex_replace_primitive_search_skips_prototype_hook_and_coerces_receiver() { f64::from_bits(TAG_UNDEFINED), ); let input = object(&scope); - let throwing = function(&scope, throw_text as *const u8, 0); + let throwing = function(&scope, crate::fn_info!(throw_text, 0; with_declared(0))); put(&input, b"toString", throwing.get_nanbox_f64()); // The pinned Node oracle skips the primitive's hook for both operations. // Preserve the original receiver and getter; observable coercion must throw. diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace_direct.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace_direct.rs index 894dd8a0d6..1de71e223f 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace_direct.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_replace_direct.rs @@ -167,7 +167,7 @@ fn a_template_replacement_keeps_its_pieces_native() { let before = native(); let (out, _, direct) = replace_all(b"(b)?a", b"g", "bä a ba".as_bytes(), |scope, _| { - function(scope, describe as *const u8, 4).get_nanbox_f64() + function(scope, crate::fn_info!(describe, 4; with_declared(4))).get_nanbox_f64() }); assert!(direct, "fixture: the callback must take the direct path"); assert_eq!( @@ -187,7 +187,7 @@ fn direct_callbacks_receive_the_ordinary_arguments() { let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); super::perex_public::register_host_roots(); let callback = |scope: &RuntimeHandleScope, _: &RuntimeHandle<'_>| { - function(scope, describe as *const u8, 4).get_nanbox_f64() + function(scope, crate::fn_info!(describe, 4; with_declared(4))).get_nanbox_f64() }; let (output, _) = both(b"(b)?a", b"g", "bä a ba".as_bytes(), callback); assert_eq!( @@ -209,7 +209,7 @@ extern "C" fn meddle( // Rewind the receiver and give it an own exec that matches nothing. Every // match was collected before the first call, so neither can change them. api::finish(crate::regex::perex_dispatch::set_last_index(&state, 0.0)); - let never = function(&scope, never_exec as *const u8, 1); + let never = function(&scope, crate::fn_info!(never_exec, 1; with_declared(1))); put(&state, b"exec", never.get_nanbox_f64()); matched.get_nanbox_f64() } @@ -228,7 +228,7 @@ fn a_replacer_cannot_change_which_matches_are_replaced() { let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); super::perex_public::register_host_roots(); let callback = |scope: &RuntimeHandleScope, re: &RuntimeHandle<'_>| { - captured(scope, meddle as *const u8, 3, re).get_nanbox_f64() + captured(scope, crate::fn_info!(meddle, 3; with_declared(3)), re).get_nanbox_f64() }; let (output, last) = both(b"o", b"g", b"foo boo", callback); assert_eq!(output, b"foo boo"); @@ -254,7 +254,11 @@ fn an_own_exec_or_named_groups_keep_the_ordinary_loop() { let (_, _, served) = replace_all(b"a", b"g", b"banana", |scope, re| { put(re, b"calls", 0.0); - let exec = captured(scope, counting_exec as *const u8, 1, re); + let exec = captured( + scope, + crate::fn_info!(counting_exec, 1; with_declared(1)), + re, + ); put(re, b"exec", exec.get_nanbox_f64()); text(scope, b"X").get_nanbox_f64() }); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_split.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_split.rs index 7a8b517d1b..66c4821085 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/perex_split.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/perex_split.rs @@ -23,10 +23,12 @@ fn regex<'s>(scope: &'s RuntimeHandleScope, source: &[u8], flags: &[u8]) -> Runt flags.get_nanbox_f64(), ) as i64)) } -fn function<'s>(scope: &'s RuntimeHandleScope, fp: *const u8, arity: u32) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); +fn function<'s>( + scope: &'s RuntimeHandleScope, + info: *const crate::closure::JsFunctionInfo, +) -> RuntimeHandle<'s> { scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc_singleton(fp) as i64, + crate::closure::js_closure_alloc_singleton(info) as i64, )) } fn get(owner: &RuntimeHandle<'_>, name: &[u8]) -> f64 { @@ -83,13 +85,11 @@ fn bytes(value: f64) -> Vec { } fn captured<'s>( scope: &'s RuntimeHandleScope, - fp: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, state: &RuntimeHandle<'_>, ) -> RuntimeHandle<'s> { - crate::closure::js_register_closure_arity(fp, arity); let f = scope.root_nanbox_f64(js_nanbox_pointer( - crate::closure::js_closure_alloc(fp, 1) as i64 + crate::closure::js_closure_alloc(info, 1) as i64 )); crate::closure::js_closure_set_capture_f64( js_nanbox_get_pointer(f.get_nanbox_f64()) as *mut _, @@ -265,8 +265,9 @@ extern "C" fn primitive_hook( ) -> f64 { assert_eq!(this.as_f64(), 23.0); gc_collect_minor(); - crate::closure::js_register_closure_arity(hook as *const u8, 2); - js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(hook as *const u8) as i64) + js_nanbox_pointer(crate::closure::js_closure_alloc_singleton( + crate::fn_info!(hook, 2; with_declared(2)), + ) as i64) } #[test] @@ -279,11 +280,11 @@ fn perex_split_symbol_hook_precedes_coercion_and_preserves_arbitrary_result() { let scope = RuntimeHandleScope::new(); let input = object(&scope); let lim = object(&scope); - let throws = function(&scope, throwing as *const u8, 0); + let throws = function(&scope, crate::fn_info!(throwing, 0; with_declared(0))); put(&input, b"toString", throws.get_nanbox_f64()); put(&lim, b"valueOf", throws.get_nanbox_f64()); let sep = object(&scope); - let method = function(&scope, hook as *const u8, 2); + let method = function(&scope, crate::fn_info!(hook, 2; with_declared(2))); symbol(&sep, "split", method.get_nanbox_f64()); let result = run(&scope, &input, &sep, lim.get_nanbox_f64()); assert_eq!( @@ -293,7 +294,7 @@ fn perex_split_symbol_hook_precedes_coercion_and_preserves_arbitrary_result() { // Exact Node 26.5.1 skips primitive-prototype hooks. Keep the original // throwing receiver: its coercion must now be reached and propagated. let proto = scope.root_nanbox_f64(crate::object::builtin_prototype_value("Number")); - let get_hook = function(&scope, primitive_hook as *const u8, 0); + let get_hook = function(&scope, crate::fn_info!(primitive_hook, 0; with_declared(0))); let key = js_nanbox_pointer(crate::symbol::well_known_symbol("split") as i64); accessor( &proto, @@ -354,8 +355,9 @@ extern "C" fn matrix_hook_getter( assert!(crate::proxy::reflect_value_is_object(this.as_f64())); event(3); gc_collect_minor(); - crate::closure::js_register_closure_arity(matrix_hook as *const u8, 2); - js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(matrix_hook as *const u8) as i64) + js_nanbox_pointer(crate::closure::js_closure_alloc_singleton( + crate::fn_info!(matrix_hook, 2; with_declared(2)), + ) as i64) } fn matrix_call(method: &str, receiver: f64, argument: f64, third: f64) -> f64 { @@ -420,7 +422,10 @@ fn perex_string_methods_ignore_primitive_hooks_and_preserve_boxed_hooks_after_gc let key = scope.root_nanbox_f64(js_nanbox_pointer( crate::symbol::well_known_symbol(symbol_name) as i64, )); - let getter = function(&scope, matrix_hook_getter as *const u8, 0); + let getter = function( + &scope, + crate::fn_info!(matrix_hook_getter, 0; with_declared(0)), + ); accessor( &proto, key.get_nanbox_f64(), @@ -430,7 +435,7 @@ fn perex_string_methods_ignore_primitive_hooks_and_preserve_boxed_hooks_after_gc let input = object(&scope); let source = text(&scope, b"x23y23true"); put(&input, b"text", source.get_nanbox_f64()); - let convert = function(&scope, input_text as *const u8, 0); + let convert = function(&scope, crate::fn_info!(input_text, 0; with_declared(0))); put(&input, b"toString", convert.get_nanbox_f64()); let third = object(&scope); let replacement = text(&scope, b"R"); @@ -443,7 +448,7 @@ fn perex_string_methods_ignore_primitive_hooks_and_preserve_boxed_hooks_after_gc replacement.get_nanbox_f64() }, ); - let convert = function(&scope, matrix_third as *const u8, 0); + let convert = function(&scope, crate::fn_info!(matrix_third, 0; with_declared(0))); put( &third, if method == "split" { @@ -653,36 +658,48 @@ fn perex_split_species_order_zero_limit_and_empty_input() { put(&receiver, b"holder", holder.get_nanbox_f64()); put(&receiver, b"matcher", matcher.get_nanbox_f64()); put(&receiver, b"flagText", flags.get_nanbox_f64()); - let ctor = function(&scope, factory as *const u8, 2); + let ctor = function(&scope, crate::fn_info!(factory, 2; with_declared(2))); put(&holder, b"factory", ctor.get_nanbox_f64()); for (owner, name, fp) in [ - (&input, b"toString".as_slice(), input_text as *const u8), - (&lim, b"valueOf", limit_get as *const u8), + ( + &input, + b"toString".as_slice(), + crate::fn_info!(input_text, 0; with_declared(0)), + ), + ( + &lim, + b"valueOf", + crate::fn_info!(limit_get, 0; with_declared(0)), + ), ] { - let f = function(&scope, fp, 0); + let f = function(&scope, fp); put(owner, name, f.get_nanbox_f64()); } for (owner, name, fp) in [ ( &receiver, b"constructor".as_slice(), - constructor_get as *const u8, + crate::fn_info!(constructor_get, 0; with_declared(0)), + ), + ( + &receiver, + b"flags", + crate::fn_info!(flags_get, 0; with_declared(0)), ), - (&receiver, b"flags", flags_get as *const u8), ] { - let f = function(&scope, fp, 0); + let f = function(&scope, fp); getter(owner, name, &f); } - let f = function(&scope, species_get as *const u8, 0); + let f = function(&scope, crate::fn_info!(species_get, 0; with_declared(0))); accessor( &holder, js_nanbox_pointer(crate::symbol::well_known_symbol("species") as i64), f.get_nanbox_f64(), f64::from_bits(TAG_UNDEFINED), ); - let f = function(&scope, empty_exec as *const u8, 1); + let f = function(&scope, crate::fn_info!(empty_exec, 1; with_declared(1))); put(&matcher, b"exec", f.get_nanbox_f64()); - let f = function(&scope, throwing as *const u8, 0); + let f = function(&scope, crate::fn_info!(throwing, 0; with_declared(0))); getter(&receiver, b"lastIndex", &f); getter(&matcher, b"lastIndex", &f); let forward_before = forward_splits(); @@ -759,17 +776,25 @@ fn perex_split_custom_exec_capture_values_reentrancy_and_limit_short_circuit() { put(&re, b"matcher", matcher.get_nanbox_f64()); put(&re, b"constructor", holder.get_nanbox_f64()); put(&re, b"flags", flags.get_nanbox_f64()); - let ctor = function(&scope, factory as *const u8, 2); + let ctor = function(&scope, crate::fn_info!(factory, 2; with_declared(2))); symbol(&holder, "species", ctor.get_nanbox_f64()); put(&state, b"input", input.get_nanbox_f64()); put(&state, b"result", result.get_nanbox_f64()); put(&state, b"capture", capture.get_nanbox_f64()); - let exec = captured(&scope, custom_exec as *const u8, 1, &state); + let exec = captured( + &scope, + crate::fn_info!(custom_exec, 1; with_declared(1)), + &state, + ); put(&matcher, b"exec", exec.get_nanbox_f64()); put(&result, b"length", 3.0); - let getter_fn = captured(&scope, capture_get as *const u8, 0, &state); + let getter_fn = captured( + &scope, + crate::fn_info!(capture_get, 0; with_declared(0)), + &state, + ); getter(&result, b"1", &getter_fn); - let throws = function(&scope, throwing as *const u8, 0); + let throws = function(&scope, crate::fn_info!(throwing, 0; with_declared(0))); getter(&result, b"0", &throws); getter(&result, b"index", &throws); put(&capture, b"toString", throws.get_nanbox_f64()); @@ -819,7 +844,7 @@ fn perex_split_collecting_throw_releases_native_arguments() { let scope = RuntimeHandleScope::new(); let input = text(&scope, b"abc"); let sep = object(&scope); - let method = function(&scope, throwing_hook as *const u8, 2); + let method = function(&scope, crate::fn_info!(throwing_hook, 2; with_declared(2))); symbol(&sep, "split", method.get_nanbox_f64()); // A young object whose move proves the throwing hook's collection ran. let previous = object(&scope); @@ -1004,11 +1029,10 @@ fn perex_abstract_string_conversion_rejects_symbols_after_collecting_object_hook let method = captured( &local, if mode == 1 { - primitive_string_hint as *const u8 + crate::fn_info!(primitive_string_hint, 1; with_declared(1)) } else { - primitive_result as *const u8 + crate::fn_info!(primitive_result, 0; with_declared(0)) }, - if mode == 1 { 1 } else { 0 }, value, ); if mode == 1 { @@ -1017,8 +1041,11 @@ fn perex_abstract_string_conversion_rejects_symbols_after_collecting_object_hook put(&input, b"toString", method.get_nanbox_f64()); } else { let other_object = object(&local); - let nonprimitive = - captured(&local, primitive_result as *const u8, 0, &other_object); + let nonprimitive = captured( + &local, + crate::fn_info!(primitive_result, 0; with_declared(0)), + &other_object, + ); put(&input, b"toString", nonprimitive.get_nanbox_f64()); put(&input, b"valueOf", method.get_nanbox_f64()); } @@ -1054,7 +1081,11 @@ fn perex_construction_uses_strict_string_conversion_for_pattern_and_flags() { let local = RuntimeHandleScope::new(); let value = if converted { let value = object(&local); - let method = captured(&local, primitive_string_hint as *const u8, 1, &symbol_value); + let method = captured( + &local, + crate::fn_info!(primitive_string_hint, 1; with_declared(1)), + &symbol_value, + ); symbol(&value, "toPrimitive", method.get_nanbox_f64()); value } else { @@ -1124,11 +1155,23 @@ fn perex_split_fallback_coercion_order_including_zero_limit() { put(&input, b"text", abc.get_nanbox_f64()); put(&sep, b"text", b.get_nanbox_f64()); for (owner, name, fp) in [ - (&input, b"toString".as_slice(), input_text as *const u8), - (&sep, b"toString", separator_text as *const u8), - (&lim, b"valueOf", limit_get as *const u8), + ( + &input, + b"toString".as_slice(), + crate::fn_info!(input_text, 0; with_declared(0)), + ), + ( + &sep, + b"toString", + crate::fn_info!(separator_text, 0; with_declared(0)), + ), + ( + &lim, + b"valueOf", + crate::fn_info!(limit_get, 0; with_declared(0)), + ), ] { - let f = function(&scope, fp, 0); + let f = function(&scope, fp); put(owner, name, f.get_nanbox_f64()); } for limit in [0.0, 1.0] { @@ -1139,7 +1182,7 @@ fn perex_split_fallback_coercion_order_including_zero_limit() { assert_eq!(get(&out, b"length"), limit); } // A throwing separator conversion is still observed with limit zero. - let throws = function(&scope, throwing as *const u8, 0); + let throws = function(&scope, crate::fn_info!(throwing, 0; with_declared(0))); put(&sep, b"toString", throws.get_nanbox_f64()); let err = crate::exception::catch_js_throw(|| { crate::regex::js_string_split_js(input.get_nanbox_f64(), sep.get_nanbox_f64(), 0.0) @@ -1160,14 +1203,22 @@ fn perex_numeric_arguments_reject_bigint_after_observable_primitive_conversion() crate::bigint::js_bigint_from_i64(1) as i64, )); let ordinary = object(&scope); - let f = captured(&scope, primitive_result as *const u8, 0, &big); + let f = captured( + &scope, + crate::fn_info!(primitive_result, 0; with_declared(0)), + &big, + ); put(&ordinary, b"valueOf", f.get_nanbox_f64()); let exotic = object(&scope); - let f = captured(&scope, primitive_number_hint as *const u8, 1, &big); + let f = captured( + &scope, + crate::fn_info!(primitive_number_hint, 1; with_declared(1)), + &big, + ); symbol(&exotic, "toPrimitive", f.get_nanbox_f64()); let input = text(&scope, b"abc"); let separator = object(&scope); - let throws = function(&scope, throwing as *const u8, 0); + let throws = function(&scope, crate::fn_info!(throwing, 0; with_declared(0))); put(&separator, b"toString", throws.get_nanbox_f64()); let re = regex(&scope, b"a", b""); for value in [&big, &ordinary, &exotic] { @@ -1202,7 +1253,11 @@ fn perex_numeric_arguments_reject_bigint_after_observable_primitive_conversion() ); // An overridden array valueOf precedes inherited toString/join. let array = scope.root_nanbox_f64(js_nanbox_pointer(crate::array::js_array_alloc(0) as i64)); - let f = captured(&scope, primitive_result as *const u8, 0, &big); + let f = captured( + &scope, + crate::fn_info!(primitive_result, 0; with_declared(0)), + &big, + ); put(&array, b"valueOf", f.get_nanbox_f64()); assert!(matches!( dispatch::to_number(&array), @@ -1327,7 +1382,10 @@ fn perex_split_user_species_regexp_keeps_the_observable_sticky_loop() { let scope = RuntimeHandleScope::new(); let re = regex(&scope, b",", b""); let holder = object(&scope); - let species = function(&scope, recording_regexp_species as *const u8, 2); + let species = function( + &scope, + crate::fn_info!(recording_regexp_species, 2; with_declared(2)), + ); symbol(&holder, "species", species.get_nanbox_f64()); put(&re, b"constructor", holder.get_nanbox_f64()); let input = text(&scope, b"a,"); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/regexp_last_index.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/regexp_last_index.rs index db3c3eb259..00852210e6 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/regexp_last_index.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/regexp_last_index.rs @@ -99,8 +99,7 @@ fn regexp_exec_survives_a_moving_minor_inside_the_lastindex_coercion() { // `re.lastIndex = { valueOf() { … } }`. let coercer = crate::object::js_object_alloc(0, 1); let coercer_handle = scope.root_raw_mut_ptr(coercer); - let fp = collect_then_zero as *const u8; - crate::closure::js_register_closure_arity(fp, 0); + let fp = crate::fn_info!(collect_then_zero, 0; with_declared(0)); let value_of = crate::closure::js_closure_alloc_singleton(fp); let value_of_value = crate::value::js_nanbox_pointer(value_of as i64); let value_of_key = heap_string(b"valueOf"); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/side_table_scanners.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/side_table_scanners.rs index 0c06d6312f..10d974021f 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/side_table_scanners.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/side_table_scanners.rs @@ -543,6 +543,12 @@ fn test_builtin_closure_metadata_follows_forwarded_owner() { GC_TYPE_CLOSURE, ) as usize; + // The arena hands back uninitialized memory. A closure's `info` word is + // read as a pointer by `builtin_closure_length`'s bound-function probe, + // so a raw owner must carry the null (no-body) info, not stale bytes. + for owner in [nursery_owner, relocated_owner] { + unsafe { (*(owner as *mut crate::closure::ClosureHeader)).info = std::ptr::null() }; + } crate::object::set_builtin_closure_length(nursery_owner, 3); crate::object::set_builtin_closure_non_constructable(nursery_owner); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/sort_collection.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/sort_collection.rs index 1c439da528..f2629df981 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/sort_collection.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/sort_collection.rs @@ -63,7 +63,7 @@ fn indexed_accessor_set_returns_the_relocated_receiver() { let scope = RuntimeHandleScope::new(); let receiver = scope.root_raw_mut_ptr(crate::array::js_array_alloc_with_length(3)); let setter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - collecting_index_setter as *const u8, + crate::fn_info!(collecting_index_setter, 1), 0, )); let descriptor = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); @@ -125,7 +125,7 @@ fn sort_collection_getters_relocate_comparator_and_receiver() { for real_array in [true, false] { let scope = RuntimeHandleScope::new(); let comparator = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - captured_direction as *const u8, + crate::fn_info!(captured_direction, 2), 1, )); comparator.with_mut_ptr(|ptr| crate::closure::js_closure_set_capture_f64(ptr, 0, -1.0)); @@ -143,7 +143,7 @@ fn sort_collection_getters_relocate_comparator_and_receiver() { ); } let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - collecting_getter as *const u8, + crate::fn_info!(collecting_getter, 0), 1, )); getter.with_mut_ptr(|getter| { @@ -156,7 +156,7 @@ fn sort_collection_getters_relocate_comparator_and_receiver() { }); }); let setter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - accept_sorted_value as *const u8, + crate::fn_info!(accept_sorted_value, 1), 0, )); let descriptor = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/string_normalize_form.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/string_normalize_form.rs index e6cfd19ac1..036552c0ea 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/string_normalize_form.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/string_normalize_form.rs @@ -53,7 +53,8 @@ fn normalize_form_coercion_must_not_strand_the_subject_payload() { // `{ toString() { ; return "NFC" } }` let form = crate::object::js_object_alloc(0, 1); let form_handle = scope.root_raw_mut_ptr(form); - let to_string = crate::closure::js_closure_alloc(normalize_form_force_minor_gc as *const u8, 0); + let to_string = + crate::closure::js_closure_alloc(crate::fn_info!(normalize_form_force_minor_gc, 0), 0); let to_string_handle = scope.root_raw_mut_ptr(to_string); let key = crate::string::js_string_from_bytes(b"toString".as_ptr(), 8); let key_handle = scope.root_string_ptr(key); diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/thenable_assimilation.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/thenable_assimilation.rs index 5ceb821087..d042f500f1 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/thenable_assimilation.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/thenable_assimilation.rs @@ -47,11 +47,12 @@ fn test_assimilated_thenable_wrapper_survives_then_callback_copied_minor_gc() { activate_malloc_registry_for_tests(); register_runtime_handle_root_scanner_for_tests(); gc_register_mutable_root_scanner(promise_mutable_root_scanner); - crate::closure::js_register_closure_arity(test_thenable_then_force_minor_gc as *const u8, 2); let scope = RuntimeHandleScope::new(); - let then_closure = - crate::closure::js_closure_alloc(test_thenable_then_force_minor_gc as *const u8, 0); + let then_closure = crate::closure::js_closure_alloc( + crate::fn_info!(test_thenable_then_force_minor_gc, 2; with_declared(2)), + 0, + ); let then_handle = scope.root_raw_mut_ptr(then_closure); // `{ then(resolve, reject) { … } }` — an object literal (class_id 0), so diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/transient_handles.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/transient_handles.rs index 3f860c9987..cc10ea5752 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/transient_handles.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/transient_handles.rs @@ -704,11 +704,9 @@ fn test_transient_runtime_handle_closure_captures_gc() { force_next_general_arena_alloc_slow(); trigger_guard.make_arena_trigger_due(); let before = gc_collection_count(); - let closure = crate::closure::js_closure_alloc_with_captures_singleton( - captured_func as *const u8, - 1, - captures.as_ptr(), - ); + let info = crate::fn_info!(captured_func, 0); + let closure = + crate::closure::js_closure_alloc_with_captures_singleton(info, 1, captures.as_ptr()); let closure_scope = RuntimeHandleScope::new(); let closure_root = closure_scope.root_raw_mut_ptr(closure); @@ -724,8 +722,7 @@ fn test_transient_runtime_handle_closure_captures_gc() { assert_string_bytes(stored_ptr, b"closure-payload"); } - let entries = - crate::closure::test_captured_singleton_closure_cache_entries(captured_func as *const u8); + let entries = crate::closure::test_captured_singleton_closure_cache_entries(info); assert_eq!(entries.len(), 1); assert_eq!(entries[0].0.len(), 1); assert_eq!(entries[0].0[0] & TAG_MASK, STRING_TAG); diff --git a/crates/perry-runtime/src/gc/tests/step_bounds.rs b/crates/perry-runtime/src/gc/tests/step_bounds.rs index e8faf54ed2..52fb7106a3 100644 --- a/crates/perry-runtime/src/gc/tests/step_bounds.rs +++ b/crates/perry-runtime/src/gc/tests/step_bounds.rs @@ -34,7 +34,7 @@ extern "C" fn finreg_step_bounds_callback( /// registrations. Targets are deliberately unrooted: a registry whose records /// all resolve is the cheap case, and the expensive one is what needs bounding. fn rooted_registry_with_records(records: usize) { - let cb = crate::closure::js_closure_alloc(finreg_step_bounds_callback as *const u8, 0); + let cb = crate::closure::js_closure_alloc(crate::fn_info!(finreg_step_bounds_callback, 1), 0); let reg = crate::weakref::js_finreg_new(f64::from_bits(ptr_bits(cb as usize))); js_shadow_slot_set(0, ptr_bits(reg as usize)); for _ in 0..records { diff --git a/crates/perry-runtime/src/gc/tests/support.rs b/crates/perry-runtime/src/gc/tests/support.rs index ff2e3cce5f..0dfc1d5be4 100644 --- a/crates/perry-runtime/src/gc/tests/support.rs +++ b/crates/perry-runtime/src/gc/tests/support.rs @@ -761,7 +761,7 @@ pub(super) extern "C" fn test_captured_singleton_func( pub(super) unsafe fn init_test_closure(ptr: *mut u8) { let closure = ptr as *mut crate::closure::ClosureHeader; - (*closure).func_ptr = std::ptr::null(); + (*closure).info = std::ptr::null(); (*closure).capture_count = 0; (*closure).shape_id = crate::closure::shape::function_base_shape( crate::closure::shape::FunctionProtoKind::Function, @@ -774,7 +774,7 @@ pub(super) unsafe fn init_test_closure_with_one_capture( capture_bits: u64, ) -> *mut u64 { let closure = ptr as *mut crate::closure::ClosureHeader; - (*closure).func_ptr = std::ptr::null(); + (*closure).info = std::ptr::null(); (*closure).capture_count = 1; (*closure).shape_id = crate::closure::shape::function_base_shape( crate::closure::shape::FunctionProtoKind::Function, diff --git a/crates/perry-runtime/src/intl.rs b/crates/perry-runtime/src/intl.rs index 74e3c70604..542807faa7 100644 --- a/crates/perry-runtime/src/intl.rs +++ b/crates/perry-runtime/src/intl.rs @@ -1034,7 +1034,7 @@ fn make_instance_this( let format_fn = install_bound_instance_function_from_handle( &obj_handle, "format", - number_format_bound_format_thunk as *const u8, + crate::fn_info!(number_format_bound_format_thunk, 1; with_declared(1)), 1, ); if !format_fn.is_null() { @@ -1048,7 +1048,7 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "formatToParts", - number_format_bound_to_parts_thunk as *const u8, + crate::fn_info!(number_format_bound_to_parts_thunk, 1; with_declared(1)), 1, ); // `formatRange`/`formatRangeToParts` are installed as own instance @@ -1061,23 +1061,19 @@ fn make_instance_this( install_function_from_handle( &obj_handle, "formatRange", - number_format_range_thunk as *const u8, + crate::fn_info!(number_format_range_thunk, 2; with_declared(2)), 2, - 2, - false, ); install_function_from_handle( &obj_handle, "formatRangeToParts", - number_format_range_to_parts_thunk as *const u8, - 2, + crate::fn_info!(number_format_range_to_parts_thunk, 2; with_declared(2)), 2, - false, ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - number_format_bound_resolved_options_thunk as *const u8, + crate::fn_info!(number_format_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1354,7 +1350,7 @@ fn make_instance_this( let format_fn = install_bound_instance_function_from_handle( &obj_handle, "format", - date_time_format_bound_format_thunk as *const u8, + crate::fn_info!(date_time_format_bound_format_thunk, 1; with_declared(1)), 1, ); if !format_fn.is_null() { @@ -1368,25 +1364,25 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "formatToParts", - date_time_format_bound_to_parts_thunk as *const u8, + crate::fn_info!(date_time_format_bound_to_parts_thunk, 1; with_declared(1)), 1, ); install_bound_instance_function_from_handle( &obj_handle, "formatRange", - date_time_format_bound_range_thunk as *const u8, + crate::fn_info!(date_time_format_bound_range_thunk, 2; with_declared(2)), 2, ); install_bound_instance_function_from_handle( &obj_handle, "formatRangeToParts", - date_time_format_bound_range_to_parts_thunk as *const u8, + crate::fn_info!(date_time_format_bound_range_to_parts_thunk, 2; with_declared(2)), 2, ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - date_time_format_bound_resolved_options_thunk as *const u8, + crate::fn_info!(date_time_format_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1467,7 +1463,7 @@ fn make_instance_this( let compare_fn = install_bound_instance_function_from_handle( &obj_handle, "compare", - collator_bound_compare_thunk as *const u8, + crate::fn_info!(collator_bound_compare_thunk, 2; with_declared(2)), 2, ); if !compare_fn.is_null() { @@ -1481,7 +1477,7 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - collator_bound_resolved_options_thunk as *const u8, + crate::fn_info!(collator_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1505,13 +1501,13 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "segment", - segmenter_bound_segment_thunk as *const u8, + crate::fn_info!(segmenter_bound_segment_thunk, 1; with_declared(1)), 1, ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - segmenter_bound_resolved_options_thunk as *const u8, + crate::fn_info!(segmenter_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1543,19 +1539,19 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "format", - list_format_bound_format_thunk as *const u8, + crate::fn_info!(list_format_bound_format_thunk, 1; with_declared(1)), 1, ); install_bound_instance_function_from_handle( &obj_handle, "formatToParts", - list_format_bound_to_parts_thunk as *const u8, + crate::fn_info!(list_format_bound_to_parts_thunk, 1; with_declared(1)), 1, ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - list_format_bound_resolved_options_thunk as *const u8, + crate::fn_info!(list_format_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1607,19 +1603,19 @@ fn make_instance_this( install_bound_instance_function_from_handle( &obj_handle, "format", - rtf_bound_format_thunk as *const u8, + crate::fn_info!(rtf_bound_format_thunk, 2; with_declared(2)), 2, ); install_bound_instance_function_from_handle( &obj_handle, "formatToParts", - rtf_bound_to_parts_thunk as *const u8, + crate::fn_info!(rtf_bound_to_parts_thunk, 2; with_declared(2)), 2, ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - rtf_bound_resolved_options_thunk as *const u8, + crate::fn_info!(rtf_bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } @@ -1751,98 +1747,115 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_function( ns_obj, "getCanonicalLocales", - get_canonical_locales_thunk as *const u8, - 1, + crate::fn_info!(get_canonical_locales_thunk, 1; with_declared(1)), 1, - false, ); install_function( ns_obj, "supportedValuesOf", - supported_values_of_thunk as *const u8, + crate::fn_info!(supported_values_of_thunk, 1; with_declared(1)), 1, - 1, - false, ); locale::install_locale(ns_obj); install_constructor( ns_obj, "NumberFormat", - number_format_constructor_thunk as *const u8, + crate::fn_info!(number_format_constructor_thunk, 1; with_rest(0)), 0, &[ ( "formatToParts", - number_format_to_parts_thunk as *const u8, + crate::fn_info!(number_format_to_parts_thunk, 1; with_declared(1)), 1, ), // `formatRange`/`formatRangeToParts` are plain (non-bound) prototype // methods (Intl.NumberFormat-v3): a detached reference loses `this` // and the `this_intl_object` guard throws, so they are installed on // the prototype only — never as own bound instance functions. - ("formatRange", number_format_range_thunk as *const u8, 2), + ( + "formatRange", + crate::fn_info!(number_format_range_thunk, 2; with_declared(2)), + 2, + ), ( "formatRangeToParts", - number_format_range_to_parts_thunk as *const u8, + crate::fn_info!(number_format_range_to_parts_thunk, 2; with_declared(2)), 2, ), ( "resolvedOptions", - number_format_resolved_options_thunk as *const u8, + crate::fn_info!(number_format_resolved_options_thunk, 0; with_declared(0)), 0, ), ], // `format` is an accessor (getter) per ECMA-402, not a plain method. - &[("format", number_format_format_getter_thunk as *const u8)], + &[( + "format", + crate::fn_info!(number_format_format_getter_thunk, 0; with_declared(0)), + )], ); install_constructor( ns_obj, "DateTimeFormat", - date_time_format_constructor_thunk as *const u8, + crate::fn_info!(date_time_format_constructor_thunk, 1; with_rest(0)), 0, &[ // `format` is an accessor (getter) per ECMA-402 §11.4.3 — see below. ( "formatToParts", - date_time_format_to_parts_thunk as *const u8, + crate::fn_info!(date_time_format_to_parts_thunk, 1; with_declared(1)), 1, ), - ("formatRange", date_time_format_range_thunk as *const u8, 2), + ( + "formatRange", + crate::fn_info!(date_time_format_range_thunk, 2; with_declared(2)), + 2, + ), ( "formatRangeToParts", - date_time_format_range_to_parts_thunk as *const u8, + crate::fn_info!(date_time_format_range_to_parts_thunk, 2; with_declared(2)), 2, ), ( "resolvedOptions", - date_time_format_resolved_options_thunk as *const u8, + crate::fn_info!(date_time_format_resolved_options_thunk, 0; with_declared(0)), 0, ), ], - &[("format", date_time_format_format_getter_thunk as *const u8)], + &[( + "format", + crate::fn_info!(date_time_format_format_getter_thunk, 0; with_declared(0)), + )], ); install_constructor( ns_obj, "Collator", - collator_constructor_thunk as *const u8, + crate::fn_info!(collator_constructor_thunk, 1; with_rest(0)), 0, &[( "resolvedOptions", - collator_resolved_options_thunk as *const u8, + crate::fn_info!(collator_resolved_options_thunk, 0; with_declared(0)), 0, )], - &[("compare", collator_compare_getter_thunk as *const u8)], + &[( + "compare", + crate::fn_info!(collator_compare_getter_thunk, 0; with_declared(0)), + )], ); install_constructor( ns_obj, "Segmenter", - segmenter_constructor_thunk as *const u8, + crate::fn_info!(segmenter_constructor_thunk, 1; with_rest(0)), 0, &[ - ("segment", segmenter_segment_thunk as *const u8, 1), + ( + "segment", + crate::fn_info!(segmenter_segment_thunk, 1; with_declared(1)), + 1, + ), ( "resolvedOptions", - segmenter_resolved_options_thunk as *const u8, + crate::fn_info!(segmenter_resolved_options_thunk, 0; with_declared(0)), 0, ), ], @@ -1851,14 +1864,22 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_constructor( ns_obj, "ListFormat", - list_format_constructor_thunk as *const u8, + crate::fn_info!(list_format_constructor_thunk, 1; with_rest(0)), 0, &[ - ("format", list_format_format_thunk as *const u8, 1), - ("formatToParts", list_format_to_parts_thunk as *const u8, 1), + ( + "format", + crate::fn_info!(list_format_format_thunk, 1; with_declared(1)), + 1, + ), + ( + "formatToParts", + crate::fn_info!(list_format_to_parts_thunk, 1; with_declared(1)), + 1, + ), ( "resolvedOptions", - list_format_resolved_options_thunk as *const u8, + crate::fn_info!(list_format_resolved_options_thunk, 0; with_declared(0)), 0, ), ], @@ -1867,14 +1888,22 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_constructor( ns_obj, "RelativeTimeFormat", - relative_time_format_constructor_thunk as *const u8, + crate::fn_info!(relative_time_format_constructor_thunk, 1; with_rest(0)), 0, &[ - ("format", rtf_format_thunk as *const u8, 2), - ("formatToParts", rtf_to_parts_thunk as *const u8, 2), + ( + "format", + crate::fn_info!(rtf_format_thunk, 2; with_declared(2)), + 2, + ), + ( + "formatToParts", + crate::fn_info!(rtf_to_parts_thunk, 2; with_declared(2)), + 2, + ), ( "resolvedOptions", - rtf_resolved_options_thunk as *const u8, + crate::fn_info!(rtf_resolved_options_thunk, 0; with_declared(0)), 0, ), ], @@ -1883,18 +1912,22 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_constructor( ns_obj, "PluralRules", - plural_rules_constructor_thunk as *const u8, + crate::fn_info!(plural_rules_constructor_thunk, 1; with_rest(0)), 0, &[ - ("select", plural_rules_select_thunk as *const u8, 1), + ( + "select", + crate::fn_info!(plural_rules_select_thunk, 1; with_declared(1)), + 1, + ), ( "selectRange", - plural_rules_select_range_thunk as *const u8, + crate::fn_info!(plural_rules_select_range_thunk, 2; with_declared(2)), 2, ), ( "resolvedOptions", - plural_rules_resolved_options_thunk as *const u8, + crate::fn_info!(plural_rules_resolved_options_thunk, 0; with_declared(0)), 0, ), ], @@ -1903,18 +1936,22 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_constructor( ns_obj, "DurationFormat", - duration_format::constructor_thunk as *const u8, + crate::fn_info!(duration_format::constructor_thunk, 1; with_rest(0)), 0, &[ - ("format", duration_format::format_thunk as *const u8, 1), + ( + "format", + crate::fn_info!(duration_format::format_thunk, 1; with_declared(1)), + 1, + ), ( "formatToParts", - duration_format::to_parts_thunk as *const u8, + crate::fn_info!(duration_format::to_parts_thunk, 1; with_declared(1)), 1, ), ( "resolvedOptions", - duration_format::resolved_options_thunk as *const u8, + crate::fn_info!(duration_format::resolved_options_thunk, 0; with_declared(0)), 0, ), ], @@ -1923,13 +1960,17 @@ pub fn install_intl_namespace(ns_obj: *mut ObjectHeader) { install_constructor( ns_obj, "DisplayNames", - display_names::constructor_thunk as *const u8, + crate::fn_info!(display_names::constructor_thunk, 1; with_rest(0)), 2, &[ - ("of", display_names::of_thunk as *const u8, 1), + ( + "of", + crate::fn_info!(display_names::of_thunk, 1; with_declared(1)), + 1, + ), ( "resolvedOptions", - display_names::resolved_options_thunk as *const u8, + crate::fn_info!(display_names::resolved_options_thunk, 0; with_declared(0)), 0, ), ], diff --git a/crates/perry-runtime/src/intl/display_names.rs b/crates/perry-runtime/src/intl/display_names.rs index 11a11620d9..6e57886815 100644 --- a/crates/perry-runtime/src/intl/display_names.rs +++ b/crates/perry-runtime/src/intl/display_names.rs @@ -148,11 +148,16 @@ pub(super) fn configure(obj: *mut ObjectHeader, options_arg: f64) { ); } - install_bound_instance_function_from_handle(&obj_handle, "of", bound_of_thunk as *const u8, 1); + install_bound_instance_function_from_handle( + &obj_handle, + "of", + crate::fn_info!(bound_of_thunk, 1; with_declared(1)), + 1, + ); install_bound_instance_function_from_handle( &obj_handle, "resolvedOptions", - bound_resolved_options_thunk as *const u8, + crate::fn_info!(bound_resolved_options_thunk, 0; with_declared(0)), 0, ); } diff --git a/crates/perry-runtime/src/intl/duration_format.rs b/crates/perry-runtime/src/intl/duration_format.rs index d71791d8fa..dd2307d895 100644 --- a/crates/perry-runtime/src/intl/duration_format.rs +++ b/crates/perry-runtime/src/intl/duration_format.rs @@ -309,26 +309,20 @@ pub(super) fn configure(obj: *mut ObjectHeader, options: f64) { super::install_function_from_handle( &obj_handle, "format", - format_thunk as *const u8, + crate::fn_info!(format_thunk, 1; with_declared(1)), 1, - 1, - false, ); super::install_function_from_handle( &obj_handle, "formatToParts", - to_parts_thunk as *const u8, - 1, + crate::fn_info!(to_parts_thunk, 1; with_declared(1)), 1, - false, ); super::install_function_from_handle( &obj_handle, "resolvedOptions", - resolved_options_thunk as *const u8, - 0, + crate::fn_info!(resolved_options_thunk, 0; with_declared(0)), 0, - false, ); } diff --git a/crates/perry-runtime/src/intl/install.rs b/crates/perry-runtime/src/intl/install.rs index 356796248b..90a3cbcdf9 100644 --- a/crates/perry-runtime/src/intl/install.rs +++ b/crates/perry-runtime/src/intl/install.rs @@ -15,19 +15,20 @@ use super::{ supported_locales_of_thunk, }; +/// `ctor` is the constructor body's info (a rest body, `with_rest(0)`); +/// each method's info records its declared `arity`, each getter's 0. pub(super) fn install_constructor( ns_obj: *mut ObjectHeader, name: &str, - ctor_ptr: *const u8, + ctor: *const crate::closure::JsFunctionInfo, ctor_length: u32, - methods: &[(&str, *const u8, u32)], - getters: &[(&str, *const u8)], + methods: &[(&str, *const crate::closure::JsFunctionInfo, u32)], + getters: &[(&str, *const crate::closure::JsFunctionInfo)], ) { - let ctor = crate::closure::js_closure_alloc(ctor_ptr, 0); + let ctor = crate::closure::js_closure_alloc(ctor, 0); if ctor.is_null() { return; } - crate::closure::js_register_closure_rest(ctor_ptr, 0); crate::object::set_bound_native_closure_name(ctor, name); crate::object::set_builtin_closure_length(ctor as usize, ctor_length); crate::object::set_builtin_property_attrs( @@ -50,7 +51,7 @@ pub(super) fn install_constructor( set_field(proto, "constructor", ctor_value); set_builtin_attrs(proto, "constructor", PropertyAttrs::new(true, false, true)); for (method, ptr, arity) in methods.iter().copied() { - install_function(proto, method, ptr, arity, arity, false); + install_function(proto, method, ptr, arity); } // Accessor properties (e.g. `get Intl.NumberFormat.prototype.format`): a // getter-only descriptor on the prototype so reflection @@ -62,7 +63,6 @@ pub(super) fn install_constructor( if closure.is_null() { continue; } - crate::closure::js_register_closure_arity(ptr, 0); crate::object::set_bound_native_closure_name(closure, &format!("get {getter_name}")); crate::object::set_builtin_closure_length(closure as usize, 0); // The `get Intl.X.prototype.format` accessor is a built-in @@ -99,10 +99,8 @@ pub(super) fn install_constructor( let supported = install_function( ctor as *mut ObjectHeader, "supportedLocalesOf", - supported_locales_of_thunk as *const u8, - 0, + crate::fn_info!(supported_locales_of_thunk, 1; with_rest(0)), 1, - true, ); crate::closure::closure_set_dynamic_prop(ctor as usize, "supportedLocalesOf", supported); diff --git a/crates/perry-runtime/src/intl/list_relative_plural.rs b/crates/perry-runtime/src/intl/list_relative_plural.rs index 9e60b5e81b..d5e3720629 100644 --- a/crates/perry-runtime/src/intl/list_relative_plural.rs +++ b/crates/perry-runtime/src/intl/list_relative_plural.rs @@ -813,7 +813,7 @@ pub(super) fn configure_plural_rules( install_bound_instance_function( obj, "select", - plural_rules_bound_select_thunk as *const u8, + crate::fn_info!(plural_rules_bound_select_thunk, 1; with_declared(1)), 1, ) }); @@ -821,10 +821,8 @@ pub(super) fn configure_plural_rules( install_function( obj, "selectRange", - plural_rules_select_range_thunk as *const u8, + crate::fn_info!(plural_rules_select_range_thunk, 2; with_declared(2)), 2, - 2, - false, ) }); let (_, obj) = obj_handle.across_mut::(|| { @@ -832,7 +830,7 @@ pub(super) fn configure_plural_rules( install_bound_instance_function( obj, "resolvedOptions", - plural_rules_bound_resolved_options_thunk as *const u8, + crate::fn_info!(plural_rules_bound_resolved_options_thunk, 0; with_declared(0)), 0, ) }) diff --git a/crates/perry-runtime/src/intl/locale.rs b/crates/perry-runtime/src/intl/locale.rs index 0d8f477175..5abdde9cd8 100644 --- a/crates/perry-runtime/src/intl/locale.rs +++ b/crates/perry-runtime/src/intl/locale.rs @@ -554,49 +554,64 @@ fn make_locale_instance(proto_bits: u64, p: &ParsedLocale) -> f64 { // prototype chain, so install bound `toString`/`maximize`/`minimize` (and // the `Intl.Locale-info` getters) on the instance (mirroring the other // `Intl.*` constructors). - install_bound_instance_function(obj, "toString", locale_bound_to_string as *const u8, 0); - install_bound_instance_function(obj, "maximize", locale_bound_maximize as *const u8, 0); - install_bound_instance_function(obj, "minimize", locale_bound_minimize as *const u8, 0); + install_bound_instance_function( + obj, + "toString", + crate::fn_info!(locale_bound_to_string, 0; with_declared(0)), + 0, + ); + install_bound_instance_function( + obj, + "maximize", + crate::fn_info!(locale_bound_maximize, 0; with_declared(0)), + 0, + ); + install_bound_instance_function( + obj, + "minimize", + crate::fn_info!(locale_bound_minimize, 0; with_declared(0)), + 0, + ); install_bound_instance_function( obj, "getCalendars", - locale_bound_get_calendars as *const u8, + crate::fn_info!(locale_bound_get_calendars, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getCollations", - locale_bound_get_collations as *const u8, + crate::fn_info!(locale_bound_get_collations, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getHourCycles", - locale_bound_get_hour_cycles as *const u8, + crate::fn_info!(locale_bound_get_hour_cycles, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getNumberingSystems", - locale_bound_get_numbering_systems as *const u8, + crate::fn_info!(locale_bound_get_numbering_systems, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getTimeZones", - locale_bound_get_time_zones as *const u8, + crate::fn_info!(locale_bound_get_time_zones, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getTextInfo", - locale_bound_get_text_info as *const u8, + crate::fn_info!(locale_bound_get_text_info, 0; with_declared(0)), 0, ); install_bound_instance_function( obj, "getWeekInfo", - locale_bound_get_week_info as *const u8, + crate::fn_info!(locale_bound_get_week_info, 0; with_declared(0)), 0, ); @@ -1006,10 +1021,13 @@ extern "C" fn getter_numeric(_c: *const ClosureHeader, this: crate::closure::JsT } } -fn install_getter(proto: *mut ObjectHeader, name: &str, thunk: *const u8) { +fn install_getter( + proto: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { unsafe { - crate::closure::js_register_closure_arity(thunk, 0); - let closure = crate::closure::js_closure_alloc(thunk, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } @@ -1021,12 +1039,13 @@ fn install_getter(proto: *mut ObjectHeader, name: &str, thunk: *const u8) { } pub(super) fn install_locale(ns_obj: *mut ObjectHeader) { - let ctor_ptr = locale_constructor_thunk as *const u8; - let ctor = crate::closure::js_closure_alloc(ctor_ptr, 0); + let ctor = crate::closure::js_closure_alloc( + crate::fn_info!(locale_constructor_thunk, 1; with_rest(0)), + 0, + ); if ctor.is_null() { return; } - crate::closure::js_register_closure_rest(ctor_ptr, 0); crate::object::set_bound_native_closure_name(ctor, "Locale"); crate::object::set_builtin_closure_length(ctor as usize, 1); crate::object::set_builtin_property_attrs( @@ -1052,102 +1071,118 @@ pub(super) fn install_locale(ns_obj: *mut ObjectHeader) { install_function( proto, "toString", - locale_to_string_thunk as *const u8, + crate::fn_info!(locale_to_string_thunk, 0; with_declared(0)), 0, - 0, - false, ); install_function( proto, "maximize", - locale_maximize_thunk as *const u8, - 0, + crate::fn_info!(locale_maximize_thunk, 0; with_declared(0)), 0, - false, ); install_function( proto, "minimize", - locale_minimize_thunk as *const u8, - 0, + crate::fn_info!(locale_minimize_thunk, 0; with_declared(0)), 0, - false, ); install_function( proto, "getCalendars", - locale_get_calendars_thunk as *const u8, + crate::fn_info!(locale_get_calendars_thunk, 0; with_declared(0)), 0, - 0, - false, ); install_function( proto, "getCollations", - locale_get_collations_thunk as *const u8, - 0, + crate::fn_info!(locale_get_collations_thunk, 0; with_declared(0)), 0, - false, ); install_function( proto, "getHourCycles", - locale_get_hour_cycles_thunk as *const u8, + crate::fn_info!(locale_get_hour_cycles_thunk, 0; with_declared(0)), 0, - 0, - false, ); install_function( proto, "getNumberingSystems", - locale_get_numbering_systems_thunk as *const u8, - 0, + crate::fn_info!(locale_get_numbering_systems_thunk, 0; with_declared(0)), 0, - false, ); install_function( proto, "getTimeZones", - locale_get_time_zones_thunk as *const u8, + crate::fn_info!(locale_get_time_zones_thunk, 0; with_declared(0)), 0, - 0, - false, ); install_function( proto, "getTextInfo", - locale_get_text_info_thunk as *const u8, - 0, + crate::fn_info!(locale_get_text_info_thunk, 0; with_declared(0)), 0, - false, ); install_function( proto, "getWeekInfo", - locale_get_week_info_thunk as *const u8, - 0, + crate::fn_info!(locale_get_week_info_thunk, 0; with_declared(0)), 0, - false, ); - install_getter(proto, "baseName", getter_base_name as *const u8); - install_getter(proto, "language", getter_language as *const u8); - install_getter(proto, "script", getter_script as *const u8); - install_getter(proto, "region", getter_region as *const u8); - install_getter(proto, "calendar", getter_calendar as *const u8); - install_getter(proto, "caseFirst", getter_case_first as *const u8); - install_getter(proto, "collation", getter_collation as *const u8); - install_getter(proto, "hourCycle", getter_hour_cycle as *const u8); - install_getter(proto, "numeric", getter_numeric as *const u8); + install_getter( + proto, + "baseName", + crate::fn_info!(getter_base_name, 0; with_declared(0)), + ); + install_getter( + proto, + "language", + crate::fn_info!(getter_language, 0; with_declared(0)), + ); + install_getter( + proto, + "script", + crate::fn_info!(getter_script, 0; with_declared(0)), + ); + install_getter( + proto, + "region", + crate::fn_info!(getter_region, 0; with_declared(0)), + ); + install_getter( + proto, + "calendar", + crate::fn_info!(getter_calendar, 0; with_declared(0)), + ); + install_getter( + proto, + "caseFirst", + crate::fn_info!(getter_case_first, 0; with_declared(0)), + ); + install_getter( + proto, + "collation", + crate::fn_info!(getter_collation, 0; with_declared(0)), + ); + install_getter( + proto, + "hourCycle", + crate::fn_info!(getter_hour_cycle, 0; with_declared(0)), + ); + install_getter( + proto, + "numeric", + crate::fn_info!(getter_numeric, 0; with_declared(0)), + ); install_getter( proto, "firstDayOfWeek", - getter_first_day_of_week as *const u8, + crate::fn_info!(getter_first_day_of_week, 0; with_declared(0)), ); install_getter( proto, "numberingSystem", - getter_numbering_system as *const u8, + crate::fn_info!(getter_numbering_system, 0; with_declared(0)), ); set_proto_to_string_tag(proto, "Intl.Locale"); diff --git a/crates/perry-runtime/src/intl/method_install.rs b/crates/perry-runtime/src/intl/method_install.rs index 7a288e17b5..092a70d9a7 100644 --- a/crates/perry-runtime/src/intl/method_install.rs +++ b/crates/perry-runtime/src/intl/method_install.rs @@ -3,17 +3,16 @@ use super::*; pub(super) fn install_bound_instance_function( obj: *mut ObjectHeader, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) -> *mut ClosureHeader { let scope = crate::gc::RuntimeHandleScope::new(); let obj = scope.root_raw_mut_ptr(obj); - let closure = crate::closure::js_closure_alloc(func_ptr, 1); + let closure = crate::closure::js_closure_alloc(info, 1); if closure.is_null() { return closure; } let closure = scope.root_raw_mut_ptr(closure); - crate::closure::js_register_closure_arity(func_ptr, arity); closure.with_mut_ptr(|closure| { obj.with_mut_ptr(|obj: *mut ObjectHeader| { crate::closure::js_closure_set_capture_f64(closure, 0, js_nanbox_pointer(obj as i64)) @@ -56,32 +55,25 @@ pub(super) fn install_bound_instance_function( pub(super) fn install_bound_instance_function_from_handle( obj: &crate::gc::RuntimeHandle<'_>, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) -> *mut ClosureHeader { - obj.with_mut_ptr(|obj| install_bound_instance_function(obj, name, func_ptr, arity)) + obj.with_mut_ptr(|obj| install_bound_instance_function(obj, name, info, arity)) } pub(super) fn install_function( owner: *mut ObjectHeader, name: &str, - func_ptr: *const u8, - call_arity: u32, + info: *const crate::closure::JsFunctionInfo, length: u32, - has_rest: bool, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let owner = scope.root_raw_mut_ptr(owner); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return undefined(); } let closure = scope.root_raw_mut_ptr(closure); - if has_rest { - crate::closure::js_register_closure_rest(func_ptr, call_arity); - } else { - crate::closure::js_register_closure_arity(func_ptr, call_arity); - } closure.with_mut_ptr(|closure| crate::object::set_bound_native_closure_name(closure, name)); closure.with_mut_ptr::(|closure| { crate::object::set_builtin_closure_length(closure as usize, length) @@ -121,11 +113,8 @@ pub(super) fn install_function( pub(super) fn install_function_from_handle( owner: &crate::gc::RuntimeHandle<'_>, name: &str, - func_ptr: *const u8, - call_arity: u32, + info: *const crate::closure::JsFunctionInfo, length: u32, - has_rest: bool, ) -> f64 { - owner - .with_mut_ptr(|owner| install_function(owner, name, func_ptr, call_arity, length, has_rest)) + owner.with_mut_ptr(|owner| install_function(owner, name, info, length)) } diff --git a/crates/perry-runtime/src/intl/segmenter.rs b/crates/perry-runtime/src/intl/segmenter.rs index 4ba34920fb..ee332a1b07 100644 --- a/crates/perry-runtime/src/intl/segmenter.rs +++ b/crates/perry-runtime/src/intl/segmenter.rs @@ -366,10 +366,8 @@ pub(crate) fn build_segments(granularity: &str, value: f64) -> f64 { install_function( segments, "containing", - segmenter_containing_thunk as *const u8, + crate::fn_info!(segmenter_containing_thunk, 1; with_declared(1)), 1, - 1, - false, ) }); install_segments_iterator(&segments); @@ -384,13 +382,13 @@ fn install_segments_iterator(segments: &crate::gc::RuntimeHandle<'_>) { let scope = crate::gc::RuntimeHandleScope::new(); let symbol = scope.root_raw_mut_ptr(symbol); let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - segments_iterator_thunk as *const u8, + crate::fn_info!(segments_iterator_thunk, 0; with_declared(0)), 0, )); if closure.with_mut_ptr(|closure: *mut ClosureHeader| closure.is_null()) { return; } - crate::closure::js_register_closure_arity(segments_iterator_thunk as *const u8, 0); + closure.with_mut_ptr::(|ptr| { crate::object::set_bound_native_closure_name(ptr, "[Symbol.iterator]") }); diff --git a/crates/perry-runtime/src/intl/segments_view.rs b/crates/perry-runtime/src/intl/segments_view.rs index 34d89cfed6..a5312078a5 100644 --- a/crates/perry-runtime/src/intl/segments_view.rs +++ b/crates/perry-runtime/src/intl/segments_view.rs @@ -942,8 +942,10 @@ mod view_mode_tests { assert!(proto_ptr != 0, "the site must have been recorded"); let proto = proto_ptr as *mut ObjectHeader; let key = crate::string::js_string_from_bytes(b"test".as_ptr(), 4); - let replacement = crate::closure::js_closure_alloc(patched_test_thunk as *const u8, 0); - crate::closure::js_register_closure_arity(patched_test_thunk as *const u8, 0); + let replacement = crate::closure::js_closure_alloc( + crate::fn_info!(patched_test_thunk, 0; with_declared(0)), + 0, + ); crate::object::js_object_set_field_by_name( proto, key, @@ -1163,9 +1165,11 @@ mod view_mode_tests { 0.0, ); let method = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(patched_test_thunk as *const u8, 0) as i64, + crate::closure::js_closure_alloc( + crate::fn_info!(patched_test_thunk, 0; with_declared(0)), + 0, + ) as i64, )); - crate::closure::js_register_closure_arity(patched_test_thunk as *const u8, 0); for name in ["exec", "test"] { let key = scope.root_nanbox_f64(js_string(name)); assert!(crate::proxy::create_data_property( diff --git a/crates/perry-runtime/src/intl/subclass.rs b/crates/perry-runtime/src/intl/subclass.rs index f077ff7bac..da990f48d3 100644 --- a/crates/perry-runtime/src/intl/subclass.rs +++ b/crates/perry-runtime/src/intl/subclass.rs @@ -109,7 +109,8 @@ fn intl_constructor_kind(parent_val: f64) -> Option<&'static str> { if closure.is_null() { return None; } - let fp = unsafe { (*closure).func_ptr }; + // Validated: `parent_val` may be any heap object, not a function. + let fp = crate::closure::get_valid_func_ptr(closure); intl_constructor_entries() .iter() .find(|(p, _)| *p == fp) diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index 317454bcf9..b6f6ea29a8 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -149,16 +149,9 @@ extern "C" fn add( f64::from_bits(JSValue::number(sum).bits()) } -unsafe fn closure1(f: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { - let p = f as *const u8; - crate::closure::js_register_closure_arity(p, 1); - crate::value::js_nanbox_pointer(crate::closure::js_closure_alloc(p, 0) as i64) -} - -unsafe fn closure2(f: crate::closure::body_call::js_body_fn_ty!(a, a)) -> f64 { - let p = f as *const u8; - crate::closure::js_register_closure_arity(p, 2); - crate::value::js_nanbox_pointer(crate::closure::js_closure_alloc(p, 0) as i64) +/// A capture-less function object over `info`'s body. +unsafe fn closure(info: *const crate::closure::JsFunctionInfo) -> f64 { + crate::value::js_nanbox_pointer(crate::closure::js_closure_alloc(info, 0) as i64) } // --------------------------------------------------------------------------- @@ -279,7 +272,11 @@ fn helper_created_from_a_raw_iterator_inherits_the_same_next() { unsafe { let array = number_array(&[1.0]); let array_iter = crate::array::array_values_iter(array); - let helper = tower(array_iter, "map", &[closure1(double_it)]); + let helper = tower( + array_iter, + "map", + &[closure(crate::fn_info!(double_it, 1; with_declared(1)))], + ); let obj = crate::value::js_nanbox_get_pointer(helper) as *const ObjectHeader; assert_eq!((*obj).class_id, ITERATOR_HELPER_CLASS_ID); @@ -336,7 +333,11 @@ fn helper_next_rejects_a_non_helper_iterator_receiver() { #[test] fn map_returns_a_helper_and_transforms_lazily() { unsafe { - let m = tower(helper_over(&[1.0, 2.0, 3.0]), "map", &[closure1(double_it)]); + let m = tower( + helper_over(&[1.0, 2.0, 3.0]), + "map", + &[closure(crate::fn_info!(double_it, 1; with_declared(1)))], + ); let p = crate::value::js_nanbox_get_pointer(m); assert_ne!(p, 0, "`.map()` must return a helper OBJECT, not undefined"); assert_eq!( @@ -353,7 +354,7 @@ fn filter_returns_a_helper_and_keeps_matching_values() { let f = tower( helper_over(&[1.0, 2.0, 3.0, 4.0]), "filter", - &[closure1(is_even)], + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))], ); assert_ne!(crate::value::js_nanbox_get_pointer(f), 0); assert_eq!(drain(f), vec![2.0, 4.0]); @@ -392,7 +393,9 @@ fn flat_map_returns_a_helper_and_flattens_one_level() { let f = tower( helper_over(&[1.0, 2.0]), "flatMap", - &[closure1(pair_with_ten_times)], + &[closure( + crate::fn_info!(pair_with_ten_times, 1; with_declared(1)), + )], ); assert_ne!(crate::value::js_nanbox_get_pointer(f), 0); assert_eq!(drain(f), vec![1.0, 10.0, 2.0, 20.0]); @@ -409,7 +412,13 @@ fn map_iterator_inherits_flat_map_and_to_array_through_the_tower() { let raw_iter = crate::collection_iter_object::js_map_values_iter_obj(map); let iter = crate::value::js_nanbox_pointer(raw_iter); - let flattened = tower(iter, "flatMap", &[closure1(pair_with_ten_times)]); + let flattened = tower( + iter, + "flatMap", + &[closure( + crate::fn_info!(pair_with_ten_times, 1; with_declared(1)), + )], + ); let helper_ptr = crate::value::js_nanbox_get_pointer(flattened); assert_ne!( helper_ptr, 0, @@ -433,9 +442,13 @@ fn chained_helpers_compose() { let m = tower( helper_over(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]), "map", - &[closure1(double_it)], + &[closure(crate::fn_info!(double_it, 1; with_declared(1)))], + ); + let f = tower( + m, + "filter", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))], ); - let f = tower(m, "filter", &[closure1(is_even)]); let t = tower(f, "take", &[f64::from_bits(JSValue::number(2.0).bits())]); assert_eq!(drain(t), vec![2.0, 4.0]); } @@ -446,7 +459,11 @@ fn to_array_drains_the_chain() { unsafe { let a = tower(helper_over(&[1.0, 2.0, 3.0]), "toArray", &[]); assert_eq!(array_numbers(a), vec![1.0, 2.0, 3.0]); - let mapped = tower(helper_over(&[1.0, 2.0]), "map", &[closure1(double_it)]); + let mapped = tower( + helper_over(&[1.0, 2.0]), + "map", + &[closure(crate::fn_info!(double_it, 1; with_declared(1)))], + ); assert_eq!(array_numbers(tower(mapped, "toArray", &[])), vec![2.0, 4.0]); } } @@ -457,14 +474,17 @@ fn reduce_with_and_without_an_initial_value() { let with_init = tower( helper_over(&[1.0, 2.0, 3.0]), "reduce", - &[closure2(add), f64::from_bits(JSValue::number(10.0).bits())], + &[ + closure(crate::fn_info!(add, 2; with_declared(2))), + f64::from_bits(JSValue::number(10.0).bits()), + ], ); assert_eq!(f64::from_bits(with_init.to_bits()), 16.0); let no_init = tower( helper_over(&[1.0, 2.0, 3.0, 4.0]), "reduce", - &[closure2(add)], + &[closure(crate::fn_info!(add, 2; with_declared(2)))], ); assert_eq!(f64::from_bits(no_init.to_bits()), 10.0); } @@ -474,24 +494,52 @@ fn reduce_with_and_without_an_initial_value() { fn some_every_find_short_circuit() { unsafe { assert_eq!( - tower(helper_over(&[1.0, 2.0, 3.0]), "some", &[closure1(is_even)]).to_bits(), + tower( + helper_over(&[1.0, 2.0, 3.0]), + "some", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))] + ) + .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - tower(helper_over(&[1.0, 3.0, 5.0]), "some", &[closure1(is_even)]).to_bits(), + tower( + helper_over(&[1.0, 3.0, 5.0]), + "some", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))] + ) + .to_bits(), crate::value::TAG_FALSE ); assert_eq!( - tower(helper_over(&[2.0, 4.0]), "every", &[closure1(is_even)]).to_bits(), + tower( + helper_over(&[2.0, 4.0]), + "every", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))] + ) + .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - tower(helper_over(&[2.0, 3.0]), "every", &[closure1(is_even)]).to_bits(), + tower( + helper_over(&[2.0, 3.0]), + "every", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))] + ) + .to_bits(), crate::value::TAG_FALSE ); - let found = tower(helper_over(&[1.0, 2.0, 3.0]), "find", &[closure1(is_even)]); + let found = tower( + helper_over(&[1.0, 2.0, 3.0]), + "find", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))], + ); assert_eq!(f64::from_bits(found.to_bits()), 2.0); - let missing = tower(helper_over(&[1.0, 3.0]), "find", &[closure1(is_even)]); + let missing = tower( + helper_over(&[1.0, 3.0]), + "find", + &[closure(crate::fn_info!(is_even, 1; with_declared(1)))], + ); assert!(JSValue::from_bits(missing.to_bits()).is_undefined()); } } @@ -535,9 +583,10 @@ fn an_own_next_method_is_called_with_the_iterator_as_this() { // A bare `{ next() {...} }` object: one own field named `next`. let obj = crate::object::js_object_alloc(0, 1); let key = crate::string::js_string_from_bytes(b"next".as_ptr(), 4); - let p = next_recording_this as *const u8; - crate::closure::js_register_closure_arity(p, 0); - let closure = crate::closure::js_closure_alloc(p, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(next_recording_this, 1; with_declared(0)), + 0, + ); crate::object::js_object_set_field_by_name( obj, key, diff --git a/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs b/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs index 3df6808b00..79fb9d4f2f 100644 --- a/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs +++ b/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs @@ -16,8 +16,10 @@ unsafe fn check_hidden_fields(mode: u8) { let value = crate::json::test_json_parse_direct(source); let scope = crate::gc::RuntimeHandleScope::new(); let object = scope.root_raw_mut_ptr(value.as_pointer::().cast_mut()); - let replacer = - scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(identity as *const u8, 0)); + let replacer = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( + crate::fn_info!(identity, 2), + 0, + )); let keys: Vec = [ "visible", "__perry_cap_45m0000331fa678", diff --git a/crates/perry-runtime/src/json_tape/cached_read.rs b/crates/perry-runtime/src/json_tape/cached_read.rs index 6047154fe0..58dc265537 100644 --- a/crates/perry-runtime/src/json_tape/cached_read.rs +++ b/crates/perry-runtime/src/json_tape/cached_read.rs @@ -394,7 +394,7 @@ mod tests { let hdr = scope.root_raw_mut_ptr(fixture(b"[10,20,30]")); let arr = scope.root_raw_mut_ptr(hdr.with_mut_ptr(|hdr| force_materialize_lazy(hdr))); let getter = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(descriptor_getter as *const u8, 0) as i64, + crate::closure::js_closure_alloc(crate::fn_info!(descriptor_getter, 0), 0) as i64, )); let desc = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); let key = crate::string::js_string_from_bytes(b"get".as_ptr(), 3); diff --git a/crates/perry-runtime/src/jsx.rs b/crates/perry-runtime/src/jsx.rs index 9eb7cdb473..967e769489 100644 --- a/crates/perry-runtime/src/jsx.rs +++ b/crates/perry-runtime/src/jsx.rs @@ -415,7 +415,7 @@ mod tests { /// Allocate `span_component` as a real capture-free closure value. fn span_component_value() -> f64 { - let c = crate::closure::js_closure_alloc(span_component as *const u8, 0); + let c = crate::closure::js_closure_alloc(crate::fn_info!(span_component, 1), 0); f64::from_bits(JSValue::pointer(c as *const u8).bits()) } diff --git a/crates/perry-runtime/src/map_tombstone_tests.rs b/crates/perry-runtime/src/map_tombstone_tests.rs index 028ca68add..0612b90576 100644 --- a/crates/perry-runtime/src/map_tombstone_tests.rs +++ b/crates/perry-runtime/src/map_tombstone_tests.rs @@ -53,9 +53,9 @@ fn take_foreach_delete_visits() -> Vec<(f64, f64)> { }) } -fn foreach_callback(func: *const u8) -> f64 { +fn foreach_callback(info: *const crate::closure::JsFunctionInfo) -> f64 { FOREACH_DELETE_VISITS.with(|visits| visits.borrow_mut().clear()); - let callback = crate::closure::js_closure_alloc(func, 0); + let callback = crate::closure::js_closure_alloc(info, 0); crate::value::js_nanbox_pointer(callback as i64) } @@ -70,7 +70,7 @@ fn foreach_survives_delete_compaction_threshold() { } js_map_foreach( map, - foreach_callback(delete_current_map_entry as *const u8), + foreach_callback(crate::fn_info!(delete_current_map_entry, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!(take_foreach_delete_visits(), expected); @@ -89,7 +89,7 @@ fn foreach_survives_delete_compaction_threshold() { } js_map_foreach( map, - foreach_callback(delete_earlier_map_entry as *const u8), + foreach_callback(crate::fn_info!(delete_earlier_map_entry, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!(take_foreach_delete_visits(), expected); @@ -534,7 +534,7 @@ fn a_live_index_read_inside_foreach_defers_the_squeeze_and_skips_nothing() { } js_map_foreach( map, - foreach_callback(delete_earlier_then_live_read as *const u8), + foreach_callback(crate::fn_info!(delete_earlier_then_live_read, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!( diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index a200e3982e..cbaf85d501 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -1,7 +1,7 @@ //! Minimal Web messaging globals used by Node-compatible `globalThis` and //! `node:worker_threads` constructor identity. -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{self, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; use crate::value::JSValue; @@ -95,9 +95,8 @@ fn set_object_prototype(obj: *mut ObjectHeader, prototype: f64) { } } -fn closure_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - js_register_closure_arity(func_ptr, arity); - let closure = js_closure_alloc(func_ptr, 0); +fn closure_value(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = js_closure_alloc(info, 0); object::set_bound_native_closure_name(closure, name); object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) @@ -128,8 +127,13 @@ extern "C" fn has_ref(_closure: *const ClosureHeader, _this: crate::closure::JsT js_bool(false) } -fn install_method(obj: *mut ObjectHeader, name: &str, func_ptr: *const u8, arity: u32) { - set_field(obj, name, closure_value(func_ptr, name, arity)); +fn install_method( + obj: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, + arity: u32, +) { + set_field(obj, name, closure_value(info, name, arity)); } // ============================================================================ @@ -388,9 +392,7 @@ extern "C" fn deliver_one_message( /// `setImmediate` callback-timer queue, passing the port object pointer as a /// trailing argument so the native thunk knows which port to flush. fn schedule_delivery(port_ptr: usize) { - let func = deliver_one_message as *const u8; - js_register_closure_arity(func, 1); - let closure = js_closure_alloc(func, 0); + let closure = js_closure_alloc(crate::fn_info!(deliver_one_message, 1; with_declared(1)), 0); let closure_ptr = closure as i64; let port_box = crate::value::js_nanbox_pointer(port_ptr as i64); let args = [port_box]; @@ -573,29 +575,57 @@ fn is_message_type(type_value: f64) -> bool { /// Install the delivery-capable method set + onmessage accessor on a port. fn install_port_methods(obj: *mut ObjectHeader) { - install_method(obj, "postMessage", port_post_message as *const u8, 2); - install_method(obj, "start", port_start as *const u8, 0); - install_method(obj, "close", port_close as *const u8, 0); - install_method(obj, "ref", noop0 as *const u8, 0); - install_method(obj, "unref", noop0 as *const u8, 0); - install_method(obj, "hasRef", has_ref as *const u8, 0); + install_method( + obj, + "postMessage", + crate::fn_info!(port_post_message, 2; with_declared(2)), + 2, + ); + install_method( + obj, + "start", + crate::fn_info!(port_start, 0; with_declared(0)), + 0, + ); + install_method( + obj, + "close", + crate::fn_info!(port_close, 0; with_declared(0)), + 0, + ); + install_method(obj, "ref", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method(obj, "unref", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method( + obj, + "hasRef", + crate::fn_info!(has_ref, 0; with_declared(0)), + 0, + ); install_method( obj, "addEventListener", - port_add_event_listener as *const u8, + crate::fn_info!(port_add_event_listener, 2; with_declared(2)), 2, ); install_method( obj, "removeEventListener", - port_remove_event_listener as *const u8, + crate::fn_info!(port_remove_event_listener, 2; with_declared(2)), 2, ); set_field(obj, "onmessageerror", js_null()); // `onmessage` as a getter/setter so assignment starts the port + flushes. - let getter = closure_value(port_onmessage_get as *const u8, "get onmessage", 0); - let setter = closure_value(port_onmessage_set as *const u8, "set onmessage", 1); + let getter = closure_value( + crate::fn_info!(port_onmessage_get, 0; with_declared(0)), + "get onmessage", + 0, + ); + let setter = closure_value( + crate::fn_info!(port_onmessage_set, 1; with_declared(1)), + "set onmessage", + 1, + ); let obj_box = boxed_object(obj); let key_box = f64::from_bits(JSValue::string_ptr(key("onmessage")).bits()); object::js_object_define_accessor(obj_box, key_box, getter, setter); @@ -618,22 +648,62 @@ pub fn populate_messaging_prototype(builtin_name: &str, proto: *mut ObjectHeader match builtin_name { "MessagePort" => { - install_method(proto, "postMessage", noop2 as *const u8, 2); - install_method(proto, "start", noop0 as *const u8, 0); - install_method(proto, "ref", noop0 as *const u8, 0); - install_method(proto, "unref", noop0 as *const u8, 0); - install_method(proto, "hasRef", has_ref as *const u8, 0); + install_method( + proto, + "postMessage", + crate::fn_info!(noop2, 2; with_declared(2)), + 2, + ); + install_method( + proto, + "start", + crate::fn_info!(noop0, 0; with_declared(0)), + 0, + ); + install_method(proto, "ref", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method( + proto, + "unref", + crate::fn_info!(noop0, 0; with_declared(0)), + 0, + ); + install_method( + proto, + "hasRef", + crate::fn_info!(has_ref, 0; with_declared(0)), + 0, + ); set_field(proto, "onmessage", js_null()); set_field(proto, "onmessageerror", js_null()); - install_method(proto, "close", noop0 as *const u8, 0); + install_method( + proto, + "close", + crate::fn_info!(noop0, 0; with_declared(0)), + 0, + ); } "MessageChannel" => {} "BroadcastChannel" => { set_field(proto, "name", js_undefined()); - install_method(proto, "close", noop0 as *const u8, 0); - install_method(proto, "postMessage", noop1 as *const u8, 1); - install_method(proto, "ref", noop0 as *const u8, 0); - install_method(proto, "unref", noop0 as *const u8, 0); + install_method( + proto, + "close", + crate::fn_info!(noop0, 0; with_declared(0)), + 0, + ); + install_method( + proto, + "postMessage", + crate::fn_info!(noop1, 1; with_declared(1)), + 1, + ); + install_method(proto, "ref", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method( + proto, + "unref", + crate::fn_info!(noop0, 0; with_declared(0)), + 0, + ); set_field(proto, "onmessage", js_null()); set_field(proto, "onmessageerror", js_null()); } @@ -708,10 +778,15 @@ pub extern "C" fn js_broadcast_channel_new(name: f64) -> f64 { .across_mut::(|| crate::builtins::js_string_coerce(name)); let name_value = f64::from_bits(JSValue::string_ptr(name_ptr).bits()); set_field(obj, "name", name_value); - install_method(obj, "close", noop0 as *const u8, 0); - install_method(obj, "postMessage", noop1 as *const u8, 1); - install_method(obj, "ref", noop0 as *const u8, 0); - install_method(obj, "unref", noop0 as *const u8, 0); + install_method(obj, "close", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method( + obj, + "postMessage", + crate::fn_info!(noop1, 1; with_declared(1)), + 1, + ); + install_method(obj, "ref", crate::fn_info!(noop0, 0; with_declared(0)), 0); + install_method(obj, "unref", crate::fn_info!(noop0, 0; with_declared(0)), 0); set_field(obj, "onmessage", js_null()); set_field(obj, "onmessageerror", js_null()); boxed_object(obj) diff --git a/crates/perry-runtime/src/module_require.rs b/crates/perry-runtime/src/module_require.rs index 28d7c275b9..5b557bedc4 100644 --- a/crates/perry-runtime/src/module_require.rs +++ b/crates/perry-runtime/src/module_require.rs @@ -7,8 +7,7 @@ mod data_import; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader, }; use crate::object::{js_object_alloc, js_object_get_field_by_name, js_object_set_field_by_name}; use crate::string::js_string_from_bytes; @@ -221,15 +220,13 @@ fn set_closure_prop(closure: *mut ClosureHeader, name: &str, value: f64) { }); } +/// `info` records the body's declared arity and `.length` (`length`). fn named_closure( - func: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, length: u32, name: &str, ) -> (*mut ClosureHeader, f64) { - js_register_closure_arity(func, arity); - crate::closure::js_register_closure_length(func, length); - let closure = js_closure_alloc(func, 1); + let closure = js_closure_alloc(info, 1); let scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = scope.root_raw_mut_ptr(closure); closure_handle.with_mut_ptr(|closure: *mut ClosureHeader| { @@ -966,14 +963,22 @@ fn make_require(base: f64, main_value: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let base_handle = scope.root_nanbox_f64(base); let main_handle = scope.root_nanbox_f64(main_value); - let (_, paths_value) = named_closure(resolve_paths_thunk as *const u8, 1, 1, "paths"); + let (_, paths_value) = named_closure( + crate::fn_info!(resolve_paths_thunk, 1; with_declared(1), with_length(1)), + 1, + "paths", + ); let paths_handle = scope.root_nanbox_f64(paths_value); js_closure_set_capture_f64( object_ptr(paths_handle.get_nanbox_f64()) as *mut ClosureHeader, 0, base_handle.get_nanbox_f64(), ); - let (_, resolve_value) = named_closure(resolve_thunk as *const u8, 2, 2, "resolve"); + let (_, resolve_value) = named_closure( + crate::fn_info!(resolve_thunk, 2; with_declared(2), with_length(2)), + 2, + "resolve", + ); let resolve_handle = scope.root_nanbox_f64(resolve_value); js_closure_set_capture_f64( object_ptr(resolve_handle.get_nanbox_f64()) as *mut ClosureHeader, @@ -995,7 +1000,11 @@ fn make_require(base: f64, main_value: f64) -> f64 { let cache_handle = scope.root_nanbox_f64(crate::object::module_cjs_cache_value()); let extensions_handle = scope.root_nanbox_f64(crate::object::module_cjs_extensions_value()); - let (_, require_value) = named_closure(require_thunk as *const u8, 1, 1, "require"); + let (_, require_value) = named_closure( + crate::fn_info!(require_thunk, 1; with_declared(1), with_length(1)), + 1, + "require", + ); let require_handle = scope.root_nanbox_f64(require_value); js_closure_set_capture_f64( object_ptr(require_handle.get_nanbox_f64()) as *mut ClosureHeader, diff --git a/crates/perry-runtime/src/module_require/import_meta_resolve.rs b/crates/perry-runtime/src/module_require/import_meta_resolve.rs index 7ac7562954..bf0c4dc1ad 100644 --- a/crates/perry-runtime/src/module_require/import_meta_resolve.rs +++ b/crates/perry-runtime/src/module_require/import_meta_resolve.rs @@ -192,7 +192,7 @@ pub extern "C" fn js_import_meta_resolve(specifier: f64, parent: f64, fallback: } extern "C" fn resolve_closure( - closure: *mut ClosureHeader, + closure: *const ClosureHeader, _this: crate::closure::JsThis, specifier: f64, parent: f64, @@ -205,7 +205,11 @@ extern "C" fn resolve_closure( pub extern "C" fn js_import_meta_resolve_value(fallback: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fallback = scope.root_nanbox_f64(fallback); - let (closure, value) = named_closure(resolve_closure as *const u8, 2, 1, "resolve"); + let (closure, value) = named_closure( + crate::fn_info!(resolve_closure, 2; with_declared(2)), + 1, + "resolve", + ); js_closure_set_capture_f64(closure, 0, fallback.get_nanbox_f64()); value } diff --git a/crates/perry-runtime/src/node_api_host/functions.rs b/crates/perry-runtime/src/node_api_host/functions.rs index 670b85a15f..ecc8cc1ee2 100644 --- a/crates/perry-runtime/src/node_api_host/functions.rs +++ b/crates/perry-runtime/src/node_api_host/functions.rs @@ -171,11 +171,16 @@ pub unsafe extern "C" fn napi_create_function( None => return NapiStatus::InvalidArg, }; - let function_pointer = napi_callback_thunk as *const u8; - crate::closure::js_register_closure_synthetic_arguments(function_pointer, 0); - crate::closure::js_register_closure_arity(function_pointer, 0); - crate::closure::js_register_closure_length(function_pointer, 0); - let closure = crate::closure::js_closure_alloc(function_pointer, 1); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!( + napi_callback_thunk, + 1; + with_rest_kind(0, crate::closure::FN_REST_SYNTHETIC_ARGUMENTS), + with_declared(0), + with_length(0) + ), + 1, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, callback_index as i64); let handle = match add_handle(env, JSValue::pointer(closure.cast()).bits()) { Ok(handle) => handle, diff --git a/crates/perry-runtime/src/node_api_host/v10_tests.rs b/crates/perry-runtime/src/node_api_host/v10_tests.rs index c1b2420efa..05c891e902 100644 --- a/crates/perry-runtime/src/node_api_host/v10_tests.rs +++ b/crates/perry-runtime/src/node_api_host/v10_tests.rs @@ -140,7 +140,7 @@ fn typeof_classifies_every_value_kind_like_javascript_typeof() { }, NapiStatus::Ok ); - let closure = crate::closure::js_closure_alloc(plain_closure_body as *const u8, 0); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(plain_closure_body, 0), 0); let closure = handle(env, JSValue::pointer(closure.cast()).bits()); let no_arguments: [f64; 0] = []; let bound = unsafe { @@ -572,8 +572,10 @@ extern "C" fn count_uncaught_callback_error( fn listen_for_uncaught_exceptions() { crate::os::test_clear_process_event_listeners(); UNCAUGHT_CALLBACK_ERRORS.with(|count| *count.borrow_mut() = 0); - crate::closure::js_register_closure_arity(count_uncaught_callback_error as *const u8, 1); - let listener = crate::closure::js_closure_alloc(count_uncaught_callback_error as *const u8, 0); + let listener = crate::closure::js_closure_alloc( + crate::fn_info!(count_uncaught_callback_error, 1; with_declared(1)), + 0, + ); let listener = JSValue::pointer(listener.cast()).bits(); let event = crate::string::js_string_from_bytes(b"uncaughtException".as_ptr(), 17); let event = JSValue::string_ptr(event).bits(); diff --git a/crates/perry-runtime/src/node_inspector.rs b/crates/perry-runtime/src/node_inspector.rs index 0054c5a77c..e3ba55f8df 100644 --- a/crates/perry-runtime/src/node_inspector.rs +++ b/crates/perry-runtime/src/node_inspector.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::sync::{LazyLock, Mutex}; use crate::array::ArrayHeader; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader, }; @@ -1130,8 +1130,7 @@ fn install_dispose(obj: *mut ObjectHeader, method: f64) { } fn endpoint_handle() -> f64 { - js_register_closure_arity(endpoint_dispose as *const u8, 0); - let dispose = js_closure_alloc(endpoint_dispose as *const u8, 0); + let dispose = js_closure_alloc(crate::fn_info!(endpoint_dispose, 0; with_declared(0)), 0); let dispose_value = boxed_pointer(dispose as *const u8); let obj = js_object_alloc(0, 2); crate::object::set_bound_native_closure_name(dispose, "[Symbol.dispose]"); @@ -1263,13 +1262,28 @@ pub extern "C" fn js_node_inspector_console_object() -> f64 { let value = object(&[]); let obj = object_ptr_from_value(value).expect("fresh console object"); for (name, func) in [ - ("log", inspector_console_log as *const u8), - ("info", inspector_console_info as *const u8), - ("debug", inspector_console_debug as *const u8), - ("warn", inspector_console_warn as *const u8), - ("error", inspector_console_error as *const u8), + ( + "log", + crate::fn_info!(inspector_console_log, 3; with_declared(3)), + ), + ( + "info", + crate::fn_info!(inspector_console_info, 3; with_declared(3)), + ), + ( + "debug", + crate::fn_info!(inspector_console_debug, 3; with_declared(3)), + ), + ( + "warn", + crate::fn_info!(inspector_console_warn, 3; with_declared(3)), + ), + ( + "error", + crate::fn_info!(inspector_console_error, 3; with_declared(3)), + ), ] { - set_field(obj, name, fn_value(func, name, 3)); + set_field(obj, name, fn_value(func, name)); } value } @@ -1456,9 +1470,8 @@ extern "C" fn session_remove_all_listeners_thunk( js_node_inspector_session_remove_all_listeners(raw_ptr_from_value(this) as i64, event) } -fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 0); +fn fn_value(info: *const crate::closure::JsFunctionInfo, name: &str) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); boxed_pointer(closure as *const u8) } @@ -1466,22 +1479,26 @@ fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { fn install_session_event_methods(session: f64) { let scope = crate::gc::RuntimeHandleScope::new(); let session = scope.root_nanbox_f64(session); - for (name, func, arity) in [ - ("on", session_on_thunk as *const u8, 2), - ("once", session_once_thunk as *const u8, 2), - ("off", session_off_thunk as *const u8, 2), + for (name, func) in [ + ("on", crate::fn_info!(session_on_thunk, 2; with_declared(2))), + ( + "once", + crate::fn_info!(session_once_thunk, 2; with_declared(2)), + ), + ( + "off", + crate::fn_info!(session_off_thunk, 2; with_declared(2)), + ), ( "listenerCount", - session_listener_count_thunk as *const u8, - 1, + crate::fn_info!(session_listener_count_thunk, 1; with_declared(1)), ), ( "removeAllListeners", - session_remove_all_listeners_thunk as *const u8, - 1, + crate::fn_info!(session_remove_all_listeners_thunk, 1; with_declared(1)), ), ] { - let value = fn_value(func, name, arity); + let value = fn_value(func, name); if let Some(object) = object_ptr_from_value(session.get_nanbox_f64()) { set_field(object, name, value); crate::object::set_builtin_property_attrs( @@ -1520,7 +1537,10 @@ pub(crate) fn install_session_prototype(constructor: f64, promise_mode: bool) -> set_field( proto, "post", - fn_value(promises_session_post_thunk as *const u8, "post", 3), + fn_value( + crate::fn_info!(promises_session_post_thunk, 3; with_declared(3)), + "post", + ), ); crate::object::set_builtin_property_attrs( proto as usize, @@ -1528,17 +1548,25 @@ pub(crate) fn install_session_prototype(constructor: f64, promise_mode: bool) -> crate::object::PropertyAttrs::new(true, true, true), ); } else { - for (method, func, arity) in [ - ("connect", session_connect_thunk as *const u8, 0), + for (method, func) in [ + ( + "connect", + crate::fn_info!(session_connect_thunk, 0; with_declared(0)), + ), ( "connectToMainThread", - session_connect_main_thunk as *const u8, - 0, + crate::fn_info!(session_connect_main_thunk, 0; with_declared(0)), + ), + ( + "disconnect", + crate::fn_info!(session_disconnect_thunk, 0; with_declared(0)), + ), + ( + "post", + crate::fn_info!(session_post_thunk, 3; with_declared(3)), ), - ("disconnect", session_disconnect_thunk as *const u8, 0), - ("post", session_post_thunk as *const u8, 3), ] { - set_field(proto, method, fn_value(func, method, arity)); + set_field(proto, method, fn_value(func, method)); crate::object::set_builtin_property_attrs( proto as usize, method.to_string(), diff --git a/crates/perry-runtime/src/node_repl.rs b/crates/perry-runtime/src/node_repl.rs index 550da09b40..b268b8a2e1 100644 --- a/crates/perry-runtime/src/node_repl.rs +++ b/crates/perry-runtime/src/node_repl.rs @@ -1,5 +1,5 @@ use crate::array::ArrayHeader; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader, }; @@ -364,9 +364,9 @@ fn display_prompt_for(server: f64) { output_write(server, &prompt_string(server)); } -fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 0); +/// `info` records the body's declared arity. +fn fn_value(info: *const crate::closure::JsFunctionInfo, name: &str) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::value::js_nanbox_pointer(closure as i64) } @@ -476,50 +476,74 @@ extern "C" fn repl_write_thunk( } fn install_server_methods(obj: *mut ObjectHeader) { - set_field(obj, "on", fn_value(repl_on_thunk as *const u8, "on", 2)); + set_field( + obj, + "on", + fn_value(crate::fn_info!(repl_on_thunk, 2; with_declared(2)), "on"), + ); set_field( obj, "addListener", - fn_value(repl_on_thunk as *const u8, "addListener", 2), + fn_value( + crate::fn_info!(repl_on_thunk, 2; with_declared(2)), + "addListener", + ), ); set_field( obj, "once", - fn_value(repl_once_thunk as *const u8, "once", 2), + fn_value( + crate::fn_info!(repl_once_thunk, 2; with_declared(2)), + "once", + ), ); set_field( obj, "emit", - fn_value(repl_emit_thunk as *const u8, "emit", 1), + fn_value( + crate::fn_info!(repl_emit_thunk, 3; with_declared(1)), + "emit", + ), ); set_field( obj, "defineCommand", - fn_value(repl_define_command_thunk as *const u8, "defineCommand", 2), + fn_value( + crate::fn_info!(repl_define_command_thunk, 2; with_declared(2)), + "defineCommand", + ), ); set_field( obj, "displayPrompt", - fn_value(repl_display_prompt_thunk as *const u8, "displayPrompt", 1), + fn_value( + crate::fn_info!(repl_display_prompt_thunk, 1; with_declared(1)), + "displayPrompt", + ), ); set_field( obj, "clearBufferedCommand", fn_value( - repl_clear_buffered_command_thunk as *const u8, + crate::fn_info!(repl_clear_buffered_command_thunk, 0; with_declared(0)), "clearBufferedCommand", - 0, ), ); set_field( obj, "setupHistory", - fn_value(repl_setup_history_thunk as *const u8, "setupHistory", 2), + fn_value( + crate::fn_info!(repl_setup_history_thunk, 2; with_declared(2)), + "setupHistory", + ), ); set_field( obj, "write", - fn_value(repl_write_thunk as *const u8, "write", 1), + fn_value( + crate::fn_info!(repl_write_thunk, 1; with_declared(1)), + "write", + ), ); } diff --git a/crates/perry-runtime/src/node_stream.rs b/crates/perry-runtime/src/node_stream.rs index b497f239f3..7e9ea2ab9a 100644 --- a/crates/perry-runtime/src/node_stream.rs +++ b/crates/perry-runtime/src/node_stream.rs @@ -36,11 +36,10 @@ mod readable_from_promises; mod event_emitter; use event_emitter::{ add_stream_listener_for_event, call_listener_args, emit_stream_event, - emit_stream_event_from_array, is_callable_value, ns_capture_rejection, ns_event_names, - ns_get_max_listeners, ns_listener_count, ns_listeners, ns_off2, ns_on2, ns_once2, - ns_prepend_listener2, ns_prepend_once_listener2, ns_raw_listeners, ns_remove_all_listeners1, - ns_remove_listener2, ns_set_max_listeners, remove_stream_listener_for_event, - stream_listener_count_for_event, + emit_stream_event_from_array, is_callable_value, ns_event_names, ns_get_max_listeners, + ns_listener_count, ns_listeners, ns_off2, ns_on2, ns_once2, ns_prepend_listener2, + ns_prepend_once_listener2, ns_raw_listeners, ns_remove_all_listeners1, ns_remove_listener2, + ns_set_max_listeners, remove_stream_listener_for_event, stream_listener_count_for_event, }; /// Dispatch `event` to the listeners registered on `stream` through node:stream's @@ -150,7 +149,7 @@ const STREAM_EMIT_CLOSE_KEY: &[u8] = b"__perryStreamEmitClose"; const STREAM_PIPELINE_CALLBACK_DONE_KEY: &[u8] = b"__perryStreamPipelineCallbackDone"; const STREAM_READABLE_LIVE_PUSH_KEY: &[u8] = b"__perryStreamReadableLivePush"; -use destroy_state::{destroy_stream, ns_destroy1, ns_destroy_error_microtask}; +use destroy_state::{destroy_stream, ns_destroy1}; pub use destroy_state::{js_node_stream_method_destroy, js_node_stream_method_destroyed}; // ───────────────────────────────────────────────────────────────── @@ -183,9 +182,6 @@ fn this_value(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f6 this.as_f64() } -extern "C" fn ns_chain0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { - this_value(closure, this) -} extern "C" fn ns_chain1( closure: *const ClosureHeader, this: crate::closure::JsThis, @@ -193,14 +189,6 @@ extern "C" fn ns_chain1( ) -> f64 { this_value(closure, this) } -extern "C" fn ns_chain2( - closure: *const ClosureHeader, - this: crate::closure::JsThis, - _a: f64, - _b: f64, -) -> f64 { - this_value(closure, this) -} extern "C" fn ns_chain3( closure: *const ClosureHeader, this: crate::closure::JsThis, @@ -295,14 +283,10 @@ extern "C" fn ns_wrap1( old_stream: f64, ) -> f64 { let stream = this_value(closure, this); - crate::closure::js_register_closure_arity(ns_wrap_data as *const u8, 1); - crate::closure::js_register_closure_arity(ns_wrap_end as *const u8, 0); - crate::closure::js_register_closure_arity(ns_wrap_error as *const u8, 1); - crate::closure::js_register_closure_arity(ns_wrap_close as *const u8, 0); - let data = js_closure_alloc(ns_wrap_data as *const u8, 1); - let end = js_closure_alloc(ns_wrap_end as *const u8, 1); - let error = js_closure_alloc(ns_wrap_error as *const u8, 1); - let close = js_closure_alloc(ns_wrap_close as *const u8, 1); + let data = js_closure_alloc(crate::fn_info!(ns_wrap_data, 1; with_declared(1)), 1); + let end = js_closure_alloc(crate::fn_info!(ns_wrap_end, 0; with_declared(0)), 1); + let error = js_closure_alloc(crate::fn_info!(ns_wrap_error, 1; with_declared(1)), 1); + let close = js_closure_alloc(crate::fn_info!(ns_wrap_close, 0; with_declared(0)), 1); js_closure_set_capture_f64(data, 0, stream); js_closure_set_capture_f64(end, 0, stream); js_closure_set_capture_f64(error, 0, stream); @@ -553,20 +537,6 @@ extern "C" fn ns_writable_final_callback_done( f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_emit2( - closure: *const ClosureHeader, - this: crate::closure::JsThis, - event: f64, - arg: f64, -) -> f64 { - let stream = this_value(closure, this); - let mut args = crate::array::js_array_alloc(0); - if arg.to_bits() != TAG_UNDEFINED { - args = crate::array::js_array_push_f64(args, arg); - } - emit_stream_event_from_array(stream, event, args) -} - extern "C" fn ns_emit_rest( closure: *const ClosureHeader, this: crate::closure::JsThis, @@ -983,10 +953,16 @@ use pipe_listeners::{ }; fn install_pipe_destination_listeners(src: f64, dest: f64) { - let unpipe = js_closure_alloc(pipe_unpipe_callback as *const u8, 6); - let error = js_closure_alloc(pipe_error_callback as *const u8, 6); - let close = js_closure_alloc(pipe_close_callback as *const u8, 6); - let finish = js_closure_alloc(pipe_finish_callback as *const u8, 6); + let unpipe = js_closure_alloc( + crate::fn_info!(pipe_unpipe_callback, 1; with_declared(1)), + 6, + ); + let error = js_closure_alloc(crate::fn_info!(pipe_error_callback, 1; with_declared(1)), 6); + let close = js_closure_alloc(crate::fn_info!(pipe_close_callback, 0; with_declared(0)), 6); + let finish = js_closure_alloc( + crate::fn_info!(pipe_finish_callback, 0; with_declared(0)), + 6, + ); let unpipe_value = pipe_listener_value(unpipe); let error_value = pipe_listener_value(error); let close_value = pipe_listener_value(close); @@ -1034,7 +1010,7 @@ fn install_pipe_destination_listeners(src: f64, dest: f64) { } fn add_pipe_drain_listener(src: f64, dest: f64) { - let listener = js_closure_alloc(pipe_drain_callback as *const u8, 3); + let listener = js_closure_alloc(crate::fn_info!(pipe_drain_callback, 0; with_declared(0)), 3); let value = pipe_listener_value(listener); js_closure_set_capture_f64(listener, 0, src); js_closure_set_capture_f64(listener, 1, dest); @@ -1043,13 +1019,19 @@ fn add_pipe_drain_listener(src: f64, dest: f64) { } fn schedule_pipe_destination_finish(dest: f64) { - let closure = js_closure_alloc(pipe_finish_destination_callback as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(pipe_finish_destination_callback, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(closure, 0, dest); crate::builtins::js_queue_microtask(closure as i64); } fn schedule_pipe_destination_finish_check(dest: f64) { - let closure = js_closure_alloc(pipe_finish_destination_callback as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(pipe_finish_destination_callback, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(closure, 0, dest); crate::timer::js_set_immediate_callback(closure as i64); } @@ -1182,7 +1164,10 @@ extern "C" fn writable_write_callback_noop( fn invoke_writable_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: f64) { if let Some(write) = writable_hidden_write(stream) { - let cb = js_closure_alloc(ns_writable_write_done as *const u8, 3); + let cb = js_closure_alloc( + crate::fn_info!(ns_writable_write_done, 1; with_declared(1)), + 3, + ); js_closure_set_capture_f64(cb, 0, stream); js_closure_set_capture_f64(cb, 1, len); js_closure_set_capture_f64(cb, 2, callback); @@ -1203,7 +1188,10 @@ fn invoke_writable_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: fn invoke_writable_writev(stream: f64, chunks: f64) { if let Some(writev) = writable_hidden_writev(stream) { - let cb = js_closure_alloc(writable_write_callback_noop as *const u8, 0); + let cb = js_closure_alloc( + crate::fn_info!(writable_write_callback_noop, 0; with_declared(0)), + 0, + ); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let args = [chunks, cb_value]; unsafe { @@ -1247,7 +1235,10 @@ fn invoke_transform_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: return; } if let Some(transform) = transform_hidden_callback(stream) { - let cb = js_closure_alloc(transform_write_callback as *const u8, 3); + let cb = js_closure_alloc( + crate::fn_info!(transform_write_callback, 2; with_declared(2)), + 3, + ); js_closure_set_capture_f64(cb, 0, stream); js_closure_set_capture_f64(cb, 1, len); js_closure_set_capture_f64(cb, 2, callback); diff --git a/crates/perry-runtime/src/node_stream/async_iterator.rs b/crates/perry-runtime/src/node_stream/async_iterator.rs index 6bb38ff58f..0a12401155 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator.rs @@ -126,8 +126,14 @@ fn readable_iterator_chunk_result(value: f64) -> f64 { let inner = crate::value::js_nanbox_get_pointer(value) as *mut crate::promise::Promise; let outer = crate::promise::js_promise_new(); - let fulfill = js_closure_alloc(ns_readable_iterator_chunk_fulfilled as *const u8, 1); - let reject = js_closure_alloc(ns_readable_iterator_chunk_rejected as *const u8, 1); + let fulfill = js_closure_alloc( + crate::fn_info!(ns_readable_iterator_chunk_fulfilled, 1; with_declared(1)), + 1, + ); + let reject = js_closure_alloc( + crate::fn_info!(ns_readable_iterator_chunk_rejected, 1; with_declared(1)), + 1, + ); js_closure_set_capture_ptr(fulfill, 0, outer as i64); js_closure_set_capture_ptr(reject, 0, outer as i64); crate::promise::js_promise_attach_handlers(inner, fulfill, reject); @@ -436,10 +442,10 @@ fn attach_iterator_listener( iterator: f64, stream: f64, event: &[u8], - func: *const u8, + info: *const crate::closure::JsFunctionInfo, store_key: &[u8], ) { - let cb = js_closure_alloc(func, 1); + let cb = js_closure_alloc(info, 1); js_closure_set_capture_f64(cb, 0, iterator); let cb_value = box_pointer(cb as *const u8); set_hidden_value(iterator, hidden_key(store_key), cb_value); @@ -484,21 +490,21 @@ fn iterator_ensure_attached(iterator: f64, stream: f64) { iterator, stream, b"data", - ns_readable_iter_on_data as *const u8, + crate::fn_info!(ns_readable_iter_on_data, 1; with_declared(1)), READABLE_ITERATOR_DATA_CB_KEY, ); attach_iterator_listener( iterator, stream, b"end", - ns_readable_iter_on_end as *const u8, + crate::fn_info!(ns_readable_iter_on_end, 0; with_declared(0)), READABLE_ITERATOR_END_CB_KEY, ); attach_iterator_listener( iterator, stream, b"error", - ns_readable_iter_on_error as *const u8, + crate::fn_info!(ns_readable_iter_on_error, 1; with_declared(1)), READABLE_ITERATOR_ERROR_CB_KEY, ); @@ -636,9 +642,15 @@ extern "C" fn ns_readable_iterator_next( crate::promise::js_promise_resolved(next.get_nanbox_f64()) }; let promise = scope.root_raw_mut_ptr(promise); - let fulfilled = js_closure_alloc(ns_readable_source_iterator_fulfilled as *const u8, 1); + let fulfilled = js_closure_alloc( + crate::fn_info!(ns_readable_source_iterator_fulfilled, 1; with_declared(1)), + 1, + ); let fulfilled = scope.root_raw_mut_ptr(fulfilled); - let rejected = js_closure_alloc(ns_readable_source_iterator_rejected as *const u8, 1); + let rejected = js_closure_alloc( + crate::fn_info!(ns_readable_source_iterator_rejected, 1; with_declared(1)), + 1, + ); let rejected = scope.root_raw_mut_ptr(rejected); js_closure_set_capture_f64(fulfilled.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); js_closure_set_capture_f64(rejected.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); @@ -738,14 +750,14 @@ pub(super) extern "C" fn ns_iterator1( ) } -fn install_async_iterator_symbol(target: f64, func: crate::closure::body_call::js_body_fn_ty!()) { +fn install_async_iterator_symbol(target: f64, info: *const crate::closure::JsFunctionInfo) { let scope = crate::gc::RuntimeHandleScope::new(); let target = scope.root_nanbox_f64(target); let async_iterator = crate::symbol::well_known_symbol("asyncIterator"); if async_iterator.is_null() { return; } - let closure = scope.root_raw_mut_ptr(js_closure_alloc(func as *const u8, 1)); + let closure = scope.root_raw_mut_ptr(js_closure_alloc(info, 1)); closure.with_mut_ptr(|closure| { js_closure_set_capture_ptr(closure, 0, target.get_nanbox_f64().to_bits() as i64) }); @@ -776,8 +788,14 @@ pub(super) fn build_readable_async_iterator(stream: f64, destroy_on_return: bool let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(stream); let methods = [ - ("next", cast0(ns_readable_iterator_next)), - ("return", cast0(ns_readable_iterator_return)), + ( + "next", + crate::fn_info!(ns_readable_iterator_next, 0; with_declared(0)), + ), + ( + "return", + crate::fn_info!(ns_readable_iterator_return, 0; with_declared(0)), + ), ]; let obj = build_object(&methods, READABLE_ITERATOR_SHAPE_ID + methods.len() as u32); let iterator = scope.root_nanbox_f64(box_pointer(obj as *const u8)); @@ -801,7 +819,10 @@ pub(super) fn build_readable_async_iterator(stream: f64, destroy_on_return: bool TAG_FALSE }), ); - install_async_iterator_symbol(iterator.get_nanbox_f64(), ns_readable_iterator_self); + install_async_iterator_symbol( + iterator.get_nanbox_f64(), + crate::fn_info!(ns_readable_iterator_self, 0; with_declared(0)), + ); iterator.get_nanbox_f64() } @@ -822,8 +843,8 @@ pub(super) fn build_readable_async_iterator(stream: f64, destroy_on_return: bool #[no_mangle] pub extern "C" fn js_make_single_value_async_iterator(value: f64) -> f64 { let methods = [ - ("next", cast0(single_value_iterator_next)), - ("return", cast0(single_value_iterator_return)), + ("next", crate::fn_info!(single_value_iterator_next, 0)), + ("return", crate::fn_info!(single_value_iterator_return, 0)), ]; let obj = build_object( &methods, @@ -838,7 +859,10 @@ pub extern "C" fn js_make_single_value_async_iterator(value: f64) -> f64 { hidden_key(READABLE_ITERATOR_DONE_KEY), f64::from_bits(if empty { TAG_TRUE } else { TAG_FALSE }), ); - install_async_iterator_symbol(iterator, ns_readable_iterator_self); + install_async_iterator_symbol( + iterator, + crate::fn_info!(ns_readable_iterator_self, 0; with_declared(0)), + ); iterator } @@ -898,7 +922,10 @@ pub(crate) fn mark_foreign_readable_ended(stream: f64) { } pub(crate) fn install_readable_async_iterator_symbol(stream: f64) { - install_async_iterator_symbol(stream, ns_async_iterator); + install_async_iterator_symbol( + stream, + crate::fn_info!(ns_async_iterator, 0; with_declared(0)), + ); } /// #9400: make a readable that owns its own listener registry async-iterable — @@ -919,24 +946,6 @@ pub(crate) fn install_method_listener_readable_async_iterator_symbol(stream: f64 install_foreign_readable_async_iterator_symbol(stream); } -pub(super) fn register_arities() { - crate::closure::js_register_closure_arity(ns_async_iterator as *const u8, 0); - crate::closure::js_register_closure_arity(ns_iterator1 as *const u8, 1); - crate::closure::js_register_closure_arity(ns_readable_iterator_next as *const u8, 0); - crate::closure::js_register_closure_arity(ns_readable_iterator_return as *const u8, 0); - crate::closure::js_register_closure_arity(ns_readable_iterator_self as *const u8, 0); - crate::closure::js_register_closure_arity(ns_readable_iterator_chunk_fulfilled as *const u8, 1); - crate::closure::js_register_closure_arity(ns_readable_iterator_chunk_rejected as *const u8, 1); - crate::closure::js_register_closure_arity( - ns_readable_source_iterator_fulfilled as *const u8, - 1, - ); - crate::closure::js_register_closure_arity(ns_readable_source_iterator_rejected as *const u8, 1); - crate::closure::js_register_closure_arity(ns_readable_iter_on_data as *const u8, 1); - crate::closure::js_register_closure_arity(ns_readable_iter_on_end as *const u8, 0); - crate::closure::js_register_closure_arity(ns_readable_iter_on_error as *const u8, 1); -} - #[cfg(test)] mod foreign_eof_tests; @@ -968,7 +977,10 @@ mod fifo_pending_tests { iterator_push_pending(iterator, p1); iterator_push_pending(iterator, p2); - let data_cb = js_closure_alloc(ns_readable_iter_on_data as *const u8, 1); + let data_cb = js_closure_alloc( + crate::fn_info!(ns_readable_iter_on_data, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(data_cb, 0, iterator); set_readable_flowing(stream, f64::from_bits(TAG_TRUE)); @@ -1043,7 +1055,10 @@ mod fifo_pending_tests { fn retained_source_rejection_finishes_iterator_and_destroys_stream() { let stream = readable_from_chunks(crate::array::js_array_alloc(0)); let iterator = build_readable_async_iterator(stream, true); - let rejected = js_closure_alloc(ns_readable_source_iterator_rejected as *const u8, 1); + let rejected = js_closure_alloc( + crate::fn_info!(ns_readable_source_iterator_rejected, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(rejected, 0, iterator); let result = diff --git a/crates/perry-runtime/src/node_stream/async_iterator/foreign_eof_tests.rs b/crates/perry-runtime/src/node_stream/async_iterator/foreign_eof_tests.rs index 95011b1540..6206d54e5e 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator/foreign_eof_tests.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator/foreign_eof_tests.rs @@ -2,8 +2,6 @@ use super::*; use crate::promise::{Promise, PromiseState}; fn readable() -> f64 { - register_arities(); - crate::child_process::cp_register_arities(); crate::child_process::cp_build_readable() } diff --git a/crates/perry-runtime/src/node_stream/readable_from_promises.rs b/crates/perry-runtime/src/node_stream/readable_from_promises.rs index 5eba52f560..ea14a9f77e 100644 --- a/crates/perry-runtime/src/node_stream/readable_from_promises.rs +++ b/crates/perry-runtime/src/node_stream/readable_from_promises.rs @@ -32,8 +32,8 @@ pub(super) fn attach_readable_from_promise_chunk(stream: f64, chunk: f64) -> boo _ => {} } - let fulfill = js_closure_alloc(ns_readable_from_promise_fulfilled as *const u8, 2); - let reject = js_closure_alloc(ns_readable_from_promise_rejected as *const u8, 2); + let fulfill = js_closure_alloc(crate::fn_info!(ns_readable_from_promise_fulfilled, 1), 2); + let reject = js_closure_alloc(crate::fn_info!(ns_readable_from_promise_rejected, 1), 2); js_closure_set_capture_ptr(fulfill, 0, stream.to_bits() as i64); js_closure_set_capture_ptr(fulfill, 1, chunk.to_bits() as i64); js_closure_set_capture_ptr(reject, 0, stream.to_bits() as i64); diff --git a/crates/perry-runtime/src/node_stream_constructors.rs b/crates/perry-runtime/src/node_stream_constructors.rs index 87239e8f57..998c1f4f30 100644 --- a/crates/perry-runtime/src/node_stream_constructors.rs +++ b/crates/perry-runtime/src/node_stream_constructors.rs @@ -4,39 +4,6 @@ use super::*; use crate::value::JSValue; -thread_local! { - static ITER_HELPER_ARITIES_REGISTERED: std::cell::Cell = - const { std::cell::Cell::new(false) }; -} - -/// Register declared arities for the iterator-helper stubs (once per -/// thread) so the closure dispatcher pads missing trailing args with -/// `undefined` instead of reading register garbage. `reduce` strictly -/// needs it — `reduce(fn)` omits the initial value — and registering -/// the single-arg helpers makes a missing-callback call (`map()`) -/// degrade to a no-op rather than dereference junk. -pub(super) fn register_iter_helper_arities() { - if ITER_HELPER_ARITIES_REGISTERED.with(|c| c.replace(true)) { - return; - } - let entries: &[(StubFn, u32)] = &[ - (cast1(ns_iter_to_array), 1), - (cast2(ns_iter_map), 2), - (cast2(ns_iter_filter), 2), - (cast3(ns_iter_reduce), 3), - (cast2(ns_iter_for_each), 2), - (cast2(ns_iter_find), 2), - (cast2(ns_iter_some), 2), - (cast2(ns_iter_every), 2), - (cast2(ns_iter_flat_map), 2), - (cast1(ns_iter_take), 1), - (cast1(ns_iter_drop), 1), - ]; - for (f, arity) in entries { - crate::closure::js_register_closure_arity(*f as *const u8, *arity); - } -} - /// Coerce a NaN-boxed value to an `f64` if it is numeric (handling both the /// int32-boxed and double representations). Returns `None` for non-numbers. pub(super) fn jsvalue_as_f64(v: f64) -> Option { @@ -388,10 +355,6 @@ pub use pipeline::{ js_node_stream_compose, js_node_stream_compose_args, js_node_stream_duplex_from_options, js_node_stream_duplex_pair, js_node_stream_finished, js_node_stream_pipeline, }; -// Duplex-pair write/final callbacks are registered by the dispatch module -// (`node_stream_dispatch.rs`); surface them through the constructors trunk so -// `node_stream`'s `pub use constructors::*` carries them into that module. -pub(crate) use pipeline::{duplex_pair_final_callback, duplex_pair_write_callback}; pub use web_adapter::{ js_node_stream_duplex_from_web, js_node_stream_duplex_to_web, js_node_stream_from_web, diff --git a/crates/perry-runtime/src/node_stream_constructors/builders.rs b/crates/perry-runtime/src/node_stream_constructors/builders.rs index 3b4acfa968..d70f51da13 100644 --- a/crates/perry-runtime/src/node_stream_constructors/builders.rs +++ b/crates/perry-runtime/src/node_stream_constructors/builders.rs @@ -8,7 +8,6 @@ use crate::value::JSValue; #[no_mangle] pub extern "C" fn js_node_stream_readable_new(opts: f64) -> f64 { - register_iter_helper_arities(); let methods = readable_methods(); let obj = build_object(&methods, READABLE_SHAPE_ID + methods.len() as u32); let readable = f64::from_bits(JSValue::pointer(obj as *const u8).bits()); @@ -34,7 +33,6 @@ pub extern "C" fn js_node_stream_readable_new(opts: f64) -> f64 { #[no_mangle] pub extern "C" fn js_node_stream_readable_subclass_init(this: f64, opts: f64) -> f64 { - register_iter_helper_arities(); let raw = raw_ptr_from_value(this); if raw == 0 { return this; @@ -119,7 +117,7 @@ pub extern "C" fn js_node_stream_legacy_subclass_init(this: f64) -> f64 { } let obj = raw as *mut ObjectHeader; let mut methods: Vec<(&str, StubFn)> = emitter_methods().to_vec(); - methods.push(("pipe", cast2(ns_pipe2))); + methods.push(("pipe", crate::fn_info!(ns_pipe2, 2; with_declared(2)))); install_methods_on_existing_object(obj, this, &methods, &[]); this } @@ -243,8 +241,8 @@ pub extern "C" fn js_array_subclass_init(this: f64, n: f64) -> f64 { } let length_key = crate::string::js_string_from_bytes(b"length".as_ptr(), 6); js_object_set_field_by_name(obj, length_key, len); - crate::closure::js_register_closure_arity(ns_array_fill as *const u8, 3); - let methods: [(&str, StubFn); 1] = [("fill", super::cast3(ns_array_fill))]; + let methods: [(&str, StubFn); 1] = + [("fill", crate::fn_info!(ns_array_fill, 3; with_declared(3)))]; install_methods_on_existing_object(obj, this, &methods, &[]); this } @@ -393,7 +391,6 @@ pub extern "C" fn js_node_stream_writable_subclass_init(this: f64, opts: f64) -> #[no_mangle] pub extern "C" fn js_node_stream_duplex_new(opts: f64) -> f64 { - register_iter_helper_arities(); let methods = duplex_methods(); let obj = build_object(&methods, DUPLEX_SHAPE_ID + methods.len() as u32); let duplex = f64::from_bits(JSValue::pointer(obj as *const u8).bits()); @@ -436,7 +433,6 @@ pub extern "C" fn js_node_stream_duplex_new(opts: f64) -> f64 { #[no_mangle] pub extern "C" fn js_node_stream_duplex_subclass_init(this: f64, opts: f64) -> f64 { - register_iter_helper_arities(); let raw = raw_ptr_from_value(this); if raw == 0 { return this; diff --git a/crates/perry-runtime/src/node_stream_constructors/introspection.rs b/crates/perry-runtime/src/node_stream_constructors/introspection.rs index 15f891b782..34b0cd4a4a 100644 --- a/crates/perry-runtime/src/node_stream_constructors/introspection.rs +++ b/crates/perry-runtime/src/node_stream_constructors/introspection.rs @@ -208,7 +208,10 @@ pub(crate) fn attach_abort_signal(signal: f64, stream: f64) { let Some(signal_obj) = object_ptr_from_value(signal) else { return; }; - let listener = js_closure_alloc(ns_stream_abort_listener as *const u8, 1); + let listener = js_closure_alloc( + crate::fn_info!(ns_stream_abort_listener, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(listener, 0, stream.to_bits() as i64); crate::url::js_abort_signal_add_listener( signal_obj, diff --git a/crates/perry-runtime/src/node_stream_constructors/pipeline.rs b/crates/perry-runtime/src/node_stream_constructors/pipeline.rs index 228aca61e8..5e9b741895 100644 --- a/crates/perry-runtime/src/node_stream_constructors/pipeline.rs +++ b/crates/perry-runtime/src/node_stream_constructors/pipeline.rs @@ -80,7 +80,7 @@ fn install_duplex_from_writable(duplex: f64, writable: f64) { return; } let obj = raw as *mut ObjectHeader; - let write = js_closure_alloc(duplex_from_writable_write_callback as *const u8, 1); + let write = js_closure_alloc(crate::fn_info!(duplex_from_writable_write_callback, 3), 1); js_closure_set_capture_f64(write, 0, writable); js_object_set_field_by_name( obj, @@ -88,7 +88,7 @@ fn install_duplex_from_writable(duplex: f64, writable: f64) { f64::from_bits(JSValue::pointer(write as *const u8).bits()), ); - let final_cb = js_closure_alloc(duplex_from_writable_final_callback as *const u8, 1); + let final_cb = js_closure_alloc(crate::fn_info!(duplex_from_writable_final_callback, 1), 1); js_closure_set_capture_f64(final_cb, 0, writable); js_object_set_field_by_name( obj, @@ -150,7 +150,10 @@ pub(super) fn add_finished_once_listeners( watch_finish: bool, watch_close: bool, ) { - let listener = js_closure_alloc(ns_finished_error_false_close as *const u8, 3); + let listener = js_closure_alloc( + crate::fn_info!(ns_finished_error_false_close, 0; with_declared(0)), + 3, + ); js_closure_set_capture_f64(listener, 0, stream); js_closure_set_capture_f64(listener, 1, callback); js_closure_set_capture_f64(listener, 2, f64::from_bits(TAG_FALSE)); @@ -164,7 +167,10 @@ pub(super) fn add_finished_once_listeners( } pub(super) fn add_finished_signal_abort_listener(stream: f64, signal: f64, callback: f64) { - let listener = js_closure_alloc(ns_finished_signal_abort as *const u8, 4); + let listener = js_closure_alloc( + crate::fn_info!(ns_finished_signal_abort, 0; with_declared(0)), + 4, + ); js_closure_set_capture_f64(listener, 0, stream); js_closure_set_capture_f64(listener, 1, callback); js_closure_set_capture_f64(listener, 2, f64::from_bits(TAG_FALSE)); @@ -184,7 +190,10 @@ pub(super) fn add_finished_signal_abort_listener(stream: f64, signal: f64, callb } pub(super) fn add_finished_cleanup_completion_listener(stream: f64, callback: f64) { - let listener = js_closure_alloc(ns_finished_default_completion as *const u8, 3); + let listener = js_closure_alloc( + crate::fn_info!(ns_finished_default_completion, 0; with_declared(0)), + 3, + ); js_closure_set_capture_f64(listener, 0, stream); js_closure_set_capture_f64(listener, 1, callback); js_closure_set_capture_f64(listener, 2, f64::from_bits(TAG_FALSE)); @@ -334,7 +343,10 @@ fn install_duplex_pair_endpoint(endpoint: f64, peer: f64) { return; } let obj = raw as *mut ObjectHeader; - let write = js_closure_alloc(duplex_pair_write_callback as *const u8, 1); + let write = js_closure_alloc( + crate::fn_info!(duplex_pair_write_callback, 3; with_declared(3)), + 1, + ); js_closure_set_capture_f64(write, 0, peer); js_object_set_field_by_name( obj, @@ -342,7 +354,10 @@ fn install_duplex_pair_endpoint(endpoint: f64, peer: f64) { f64::from_bits(JSValue::pointer(write as *const u8).bits()), ); - let final_cb = js_closure_alloc(duplex_pair_final_callback as *const u8, 1); + let final_cb = js_closure_alloc( + crate::fn_info!(duplex_pair_final_callback, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(final_cb, 0, peer); js_object_set_field_by_name( obj, diff --git a/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs b/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs index 81d357b50c..afdaee06f8 100644 --- a/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs +++ b/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs @@ -129,10 +129,10 @@ fn closure_value(closure: *mut ClosureHeader) -> f64 { f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -fn closure_with_stream(func: *const u8, node_stream: f64) -> f64 { +fn closure_with_stream(info: *const crate::closure::JsFunctionInfo, node_stream: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let node_stream = scope.root_nanbox_f64(node_stream); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(func, 1)); + let closure = scope.root_raw_mut_ptr(js_closure_alloc(info, 1)); closure.with_mut_ptr(|closure| { js_closure_set_capture_f64(closure, 0, node_stream.get_nanbox_f64()) }); @@ -323,11 +323,11 @@ extern "C" fn foreign_readable_to_web_pull( } let fulfilled = scope.root_raw_mut_ptr(js_closure_alloc( - foreign_readable_pull_fulfilled as *const u8, + crate::fn_info!(foreign_readable_pull_fulfilled, 1; with_declared(1)), 1, )); let rejected = scope.root_raw_mut_ptr(js_closure_alloc( - foreign_readable_pull_rejected as *const u8, + crate::fn_info!(foreign_readable_pull_rejected, 1; with_declared(1)), 1, )); fulfilled.with_mut_ptr(|fulfilled| { @@ -382,22 +382,17 @@ fn node_readable_to_web(node_stream: f64) -> Option { let scope = crate::gc::RuntimeHandleScope::new(); let node_stream = scope.root_nanbox_f64(node_stream); if async_iterator::is_foreign_readable(node_stream.get_nanbox_f64()) { - crate::closure::js_register_closure_arity(foreign_readable_to_web_pull as *const u8, 1); - crate::closure::js_register_closure_arity(foreign_readable_to_web_cancel as *const u8, 1); - crate::closure::js_register_closure_arity(foreign_readable_pull_fulfilled as *const u8, 1); - crate::closure::js_register_closure_arity(foreign_readable_pull_rejected as *const u8, 1); - let iterator = scope.root_nanbox_f64(async_iterator::build_readable_async_iterator( node_stream.get_nanbox_f64(), true, )); let pull = scope.root_raw_mut_ptr(js_closure_alloc( - foreign_readable_to_web_pull as *const u8, + crate::fn_info!(foreign_readable_to_web_pull, 1; with_declared(1)), 1, )); pull.with_mut_ptr(|pull| js_closure_set_capture_f64(pull, 0, iterator.get_nanbox_f64())); let cancel = scope.root_raw_mut_ptr(js_closure_alloc( - foreign_readable_to_web_cancel as *const u8, + crate::fn_info!(foreign_readable_to_web_cancel, 1; with_declared(1)), 2, )); cancel.with_mut_ptr(|cancel| { @@ -415,12 +410,13 @@ fn node_readable_to_web(node_stream: f64) -> Option { }) })); } - crate::closure::js_register_closure_arity(node_to_web_readable_pull as *const u8, 1); - crate::closure::js_register_closure_arity(node_to_web_readable_cancel as *const u8, 1); - let pull = scope.root_raw_mut_ptr(js_closure_alloc(node_to_web_readable_pull as *const u8, 1)); + let pull = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(node_to_web_readable_pull, 1; with_declared(1)), + 1, + )); pull.with_mut_ptr(|pull| js_closure_set_capture_f64(pull, 0, node_stream.get_nanbox_f64())); let cancel = scope.root_raw_mut_ptr(js_closure_alloc( - node_to_web_readable_cancel as *const u8, + crate::fn_info!(node_to_web_readable_cancel, 1; with_declared(1)), 1, )); cancel @@ -475,16 +471,20 @@ extern "C" fn fallback_web_readable_get_reader( return f64::from_bits(TAG_UNDEFINED); } let node_stream = js_closure_get_capture_f64(closure, 0); - crate::closure::js_register_closure_arity(fallback_web_reader_read as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_web_reader_cancel as *const u8, 1); build_enumerable_object(&[ ( b"read", - closure_with_stream(fallback_web_reader_read as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_reader_read, 0; with_declared(0)), + node_stream, + ), ), ( b"cancel", - closure_with_stream(fallback_web_reader_cancel as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_reader_cancel, 1; with_declared(1)), + node_stream, + ), ), ]) } @@ -519,15 +519,13 @@ extern "C" fn fallback_foreign_get_reader( let scope = crate::gc::RuntimeHandleScope::new(); let iterator = scope.root_nanbox_f64(js_closure_get_capture_f64(closure, 0)); let stream = scope.root_nanbox_f64(js_closure_get_capture_f64(closure, 1)); - crate::closure::js_register_closure_arity(fallback_foreign_reader_read as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_foreign_reader_cancel as *const u8, 1); let read = scope.root_raw_mut_ptr(js_closure_alloc( - fallback_foreign_reader_read as *const u8, + crate::fn_info!(fallback_foreign_reader_read, 0; with_declared(0)), 1, )); read.with_mut_ptr(|read| js_closure_set_capture_f64(read, 0, iterator.get_nanbox_f64())); let cancel = scope.root_raw_mut_ptr(js_closure_alloc( - fallback_foreign_reader_cancel as *const u8, + crate::fn_info!(fallback_foreign_reader_cancel, 1; with_declared(1)), 2, )); cancel.with_mut_ptr(|cancel| { @@ -547,14 +545,12 @@ extern "C" fn fallback_foreign_get_reader( fn fallback_foreign_readable_to_web(node_stream: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let node_stream = scope.root_nanbox_f64(node_stream); - crate::closure::js_register_closure_arity(fallback_foreign_get_reader as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_foreign_reader_cancel as *const u8, 1); let iterator = scope.root_nanbox_f64(async_iterator::build_readable_async_iterator( node_stream.get_nanbox_f64(), true, )); let get_reader = scope.root_raw_mut_ptr(js_closure_alloc( - fallback_foreign_get_reader as *const u8, + crate::fn_info!(fallback_foreign_get_reader, 0; with_declared(0)), 2, )); get_reader.with_mut_ptr(|get_reader| { @@ -562,7 +558,7 @@ fn fallback_foreign_readable_to_web(node_stream: f64) -> f64 { js_closure_set_capture_f64(get_reader, 1, node_stream.get_nanbox_f64()); }); let cancel = scope.root_raw_mut_ptr(js_closure_alloc( - fallback_foreign_reader_cancel as *const u8, + crate::fn_info!(fallback_foreign_reader_cancel, 1; with_declared(1)), 2, )); cancel.with_mut_ptr(|cancel| { @@ -583,16 +579,20 @@ fn fallback_node_readable_to_web(node_stream: f64) -> f64 { if async_iterator::is_foreign_readable(node_stream) { return fallback_foreign_readable_to_web(node_stream); } - crate::closure::js_register_closure_arity(fallback_web_readable_get_reader as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_web_reader_cancel as *const u8, 1); build_enumerable_object(&[ ( b"getReader", - closure_with_stream(fallback_web_readable_get_reader as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_readable_get_reader, 0; with_declared(0)), + node_stream, + ), ), ( b"cancel", - closure_with_stream(fallback_web_reader_cancel as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_reader_cancel, 1; with_declared(1)), + node_stream, + ), ), ]) } @@ -643,14 +643,20 @@ extern "C" fn node_to_web_writable_abort( fn node_writable_to_web(node_stream: f64) -> Option { let writable_new = web_writable_new()?; - crate::closure::js_register_closure_arity(node_to_web_writable_write as *const u8, 1); - crate::closure::js_register_closure_arity(node_to_web_writable_close as *const u8, 0); - crate::closure::js_register_closure_arity(node_to_web_writable_abort as *const u8, 1); - let write = js_closure_alloc(node_to_web_writable_write as *const u8, 1); + let write = js_closure_alloc( + crate::fn_info!(node_to_web_writable_write, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(write, 0, node_stream); - let close = js_closure_alloc(node_to_web_writable_close as *const u8, 1); + let close = js_closure_alloc( + crate::fn_info!(node_to_web_writable_close, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(close, 0, node_stream); - let abort = js_closure_alloc(node_to_web_writable_abort as *const u8, 1); + let abort = js_closure_alloc( + crate::fn_info!(node_to_web_writable_abort, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(abort, 0, node_stream); Some(unsafe { writable_new( @@ -714,36 +720,46 @@ extern "C" fn fallback_web_writable_get_writer( return f64::from_bits(TAG_UNDEFINED); } let node_stream = js_closure_get_capture_f64(closure, 0); - crate::closure::js_register_closure_arity(fallback_web_writer_write as *const u8, 1); - crate::closure::js_register_closure_arity(fallback_web_writer_close as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_web_writer_abort as *const u8, 1); build_enumerable_object(&[ ( b"write", - closure_with_stream(fallback_web_writer_write as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_writer_write, 1; with_declared(1)), + node_stream, + ), ), ( b"close", - closure_with_stream(fallback_web_writer_close as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_writer_close, 0; with_declared(0)), + node_stream, + ), ), ( b"abort", - closure_with_stream(fallback_web_writer_abort as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_writer_abort, 1; with_declared(1)), + node_stream, + ), ), ]) } fn fallback_node_writable_to_web(node_stream: f64) -> f64 { - crate::closure::js_register_closure_arity(fallback_web_writable_get_writer as *const u8, 0); - crate::closure::js_register_closure_arity(fallback_web_writer_abort as *const u8, 1); build_enumerable_object(&[ ( b"getWriter", - closure_with_stream(fallback_web_writable_get_writer as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_writable_get_writer, 0; with_declared(0)), + node_stream, + ), ), ( b"abort", - closure_with_stream(fallback_web_writer_abort as *const u8, node_stream), + closure_with_stream( + crate::fn_info!(fallback_web_writer_abort, 1; with_declared(1)), + node_stream, + ), ), ]) } @@ -775,8 +791,10 @@ fn install_web_readable_adapter(node_stream: f64, web_stream: f64) -> bool { if reader.to_bits() == TAG_UNDEFINED { return false; } - crate::closure::js_register_closure_arity(web_to_node_readable_read as *const u8, 1); - let read = js_closure_alloc(web_to_node_readable_read as *const u8, 2); + let read = js_closure_alloc( + crate::fn_info!(web_to_node_readable_read, 1; with_declared(1)), + 2, + ); js_closure_set_capture_f64(read, 0, node_stream); js_closure_set_capture_f64(read, 1, reader); set_hidden_value(node_stream, hidden_read_key(), closure_value(read)); @@ -821,12 +839,16 @@ fn pump_web_reader(node_stream: f64, reader: f64) { if promise.is_null() { return; } - crate::closure::js_register_closure_arity(web_to_node_readable_read_fulfilled as *const u8, 1); - crate::closure::js_register_closure_arity(web_to_node_readable_read_rejected as *const u8, 1); - let fulfilled = js_closure_alloc(web_to_node_readable_read_fulfilled as *const u8, 2); + let fulfilled = js_closure_alloc( + crate::fn_info!(web_to_node_readable_read_fulfilled, 1; with_declared(1)), + 2, + ); js_closure_set_capture_f64(fulfilled, 0, node_stream); js_closure_set_capture_f64(fulfilled, 1, reader); - let rejected = js_closure_alloc(web_to_node_readable_read_rejected as *const u8, 1); + let rejected = js_closure_alloc( + crate::fn_info!(web_to_node_readable_read_rejected, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(rejected, 0, node_stream); crate::promise::js_promise_attach_handlers(promise, fulfilled, rejected); } @@ -882,14 +904,20 @@ fn install_web_writable_adapter(node_stream: f64, web_stream: f64) -> bool { if writer.to_bits() == TAG_UNDEFINED { return false; } - crate::closure::js_register_closure_arity(web_to_node_writable_write as *const u8, 3); - crate::closure::js_register_closure_arity(web_to_node_writable_final as *const u8, 1); - crate::closure::js_register_closure_arity(web_to_node_writable_destroy as *const u8, 2); - let write = js_closure_alloc(web_to_node_writable_write as *const u8, 1); + let write = js_closure_alloc( + crate::fn_info!(web_to_node_writable_write, 3; with_declared(3)), + 1, + ); js_closure_set_capture_f64(write, 0, writer); - let final_cb = js_closure_alloc(web_to_node_writable_final as *const u8, 1); + let final_cb = js_closure_alloc( + crate::fn_info!(web_to_node_writable_final, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(final_cb, 0, writer); - let destroy = js_closure_alloc(web_to_node_writable_destroy as *const u8, 1); + let destroy = js_closure_alloc( + crate::fn_info!(web_to_node_writable_destroy, 2; with_declared(2)), + 1, + ); js_closure_set_capture_f64(destroy, 0, writer); set_hidden_value(node_stream, hidden_write_key(), closure_value(write)); set_hidden_value( @@ -985,11 +1013,15 @@ fn attach_web_writable_callback(promise: *mut crate::promise::Promise, callback: call_stream_callback(callback, f64::from_bits(TAG_UNDEFINED)); return; } - crate::closure::js_register_closure_arity(web_to_node_writable_fulfilled as *const u8, 1); - crate::closure::js_register_closure_arity(web_to_node_writable_rejected as *const u8, 1); - let fulfilled = js_closure_alloc(web_to_node_writable_fulfilled as *const u8, 1); + let fulfilled = js_closure_alloc( + crate::fn_info!(web_to_node_writable_fulfilled, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(fulfilled, 0, callback); - let rejected = js_closure_alloc(web_to_node_writable_rejected as *const u8, 1); + let rejected = js_closure_alloc( + crate::fn_info!(web_to_node_writable_rejected, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(rejected, 0, callback); crate::promise::js_promise_attach_handlers(promise, fulfilled, rejected); } @@ -1097,8 +1129,14 @@ pub extern "C" fn js_node_stream_duplex_from_web(pair: f64, opts: f64) -> f64 { /// universes — that's the remaining #1540 gap. pub(super) fn build_web_stream_stub() -> f64 { let methods: [(&str, StubFn); 2] = [ - ("getReader", cast0(ns_undefined0)), - ("getWriter", cast0(ns_undefined0)), + ( + "getReader", + crate::fn_info!(ns_undefined0, 0; with_declared(0)), + ), + ( + "getWriter", + crate::fn_info!(ns_undefined0, 0; with_declared(0)), + ), ]; let obj = build_object(&methods, WEB_STREAM_SHAPE_ID + methods.len() as u32); f64::from_bits(JSValue::pointer(obj as *const u8).bits()) diff --git a/crates/perry-runtime/src/node_stream_destroy_state.rs b/crates/perry-runtime/src/node_stream_destroy_state.rs index 584b5c9d80..83305dc20e 100644 --- a/crates/perry-runtime/src/node_stream_destroy_state.rs +++ b/crates/perry-runtime/src/node_stream_destroy_state.rs @@ -48,7 +48,7 @@ extern "C" fn ns_destroy_option_done( } fn queue_destroy_events(stream: f64, err: f64) { - let closure = js_closure_alloc(ns_destroy_error_microtask as *const u8, 2); + let closure = js_closure_alloc(crate::fn_info!(ns_destroy_error_microtask, 0), 2); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); js_closure_set_capture_f64(closure, 1, err); crate::builtins::js_queue_microtask(closure as i64); @@ -62,8 +62,10 @@ pub(super) fn destroy_stream(stream: f64, err: f64) { super::refresh_readable_aborted_flag(stream); if let Some(destroy) = get_hidden_value(stream, hidden_key(b"__perryStreamDestroy")) { if super::is_callable_value(destroy) { - crate::closure::js_register_closure_arity(ns_destroy_option_done as *const u8, 1); - let cb = js_closure_alloc(ns_destroy_option_done as *const u8, 2); + let cb = js_closure_alloc( + crate::fn_info!(ns_destroy_option_done, 1; with_declared(1)), + 2, + ); js_closure_set_capture_f64(cb, 0, stream); js_closure_set_capture_f64(cb, 1, err); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index da3ae6ba19..11875afef0 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -9,24 +9,10 @@ use crate::object::{ }; use crate::value::JSValue; -pub(crate) type StubFn = unsafe extern "C" fn(); - -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast0(f: crate::closure::body_call::js_body_fn_ty!()) -> StubFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast1(f: crate::closure::body_call::js_body_fn_ty!(a)) -> StubFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast2(f: crate::closure::body_call::js_body_fn_ty!(a, a)) -> StubFn { - unsafe { std::mem::transmute(f) } -} -#[allow(clippy::missing_transmute_annotations)] -pub(super) fn cast3(f: crate::closure::body_call::js_body_fn_ty!(a, a, a)) -> StubFn { - unsafe { std::mem::transmute(f) } -} +/// A stream-method body's static info: what a method table lists and +/// `build_object` / `install_methods_on_existing_object` allocate from +/// (`crate::fn_info!(body, N)`). +pub(crate) type StubFn = *const crate::closure::JsFunctionInfo; // ───────────────────────────────────────────────────────────────── // Build the host object: allocate an ObjectHeader sized to the @@ -35,8 +21,6 @@ pub(super) fn cast3(f: crate::closure::body_call::js_body_fn_ty!(a, a, a)) -> St // ───────────────────────────────────────────────────────────────── pub(super) fn build_object(methods: &[(&str, StubFn)], shape_id: u32) -> *mut ObjectHeader { - register_stub_arities(); - // Pack the method names as a NUL-separated byte sequence, matching // the layout `js_object_alloc_with_shape` parses for shape keys. let mut packed: Vec = Vec::new(); @@ -65,7 +49,7 @@ pub(super) fn build_object(methods: &[(&str, StubFn)], shape_id: u32) -> *mut Ob continue; } } - let closure = scope.root_raw_mut_ptr(js_closure_alloc(*func as *const u8, 1)); + let closure = scope.root_raw_mut_ptr(js_closure_alloc(*func, 1)); // Reuse `set_capture_ptr` (i64 payload). We only need 64 bits // and the NaN-boxed pattern fits cleanly when reinterpreted. closure.with_mut_ptr(|closure| { @@ -159,7 +143,6 @@ pub(crate) fn install_methods_on_existing_object( methods: &[(&str, StubFn)], skip_names: &[&str], ) { - register_stub_arities(); // `js_closure_alloc` and the key interning below both allocate and can // therefore GC-move the receiver, so root it and re-read the raw pointer at // every use rather than trusting the `obj` snapshot across the loop. The @@ -192,7 +175,7 @@ pub(crate) fn install_methods_on_existing_object( continue; } } - let closure = js_closure_alloc(*func as *const u8, 1); + let closure = js_closure_alloc(*func, 1); crate::closure::js_closure_set_capture_ptr( closure, 0, @@ -235,7 +218,6 @@ fn native_or_plain_key(name: &str, overridden: bool) -> *mut crate::string::Stri pub(crate) fn install_event_emitter_prototype_methods(proto: *mut ObjectHeader) { let scope = crate::gc::RuntimeHandleScope::new(); let proto = scope.root_raw_mut_ptr(proto); - register_stub_arities(); let methods = super::emitter_methods(); let mut on_method: Option> = None; for (name, func) in methods { @@ -250,7 +232,7 @@ pub(crate) fn install_event_emitter_prototype_methods(proto: *mut ObjectHeader) continue; } } - let closure = js_closure_alloc(func as *const u8, 1); + let closure = js_closure_alloc(func, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, crate::value::TAG_UNDEFINED as i64); let val = scope.root_nanbox_f64(f64::from_bits( JSValue::pointer(closure as *const u8).bits(), @@ -358,6 +340,20 @@ extern "C" fn ns_ee_async_resource_destroy( } } +/// `EventEmitterAsyncResource.prototype.emit`'s body: `(event, ...args)`. +static NS_EE_ASYNC_RESOURCE_EMIT_REST: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + ns_ee_async_resource_emit_rest as crate::codegen_abi::JsBody2, + ) + .with_rest(1); + +/// `emitDestroy`'s body. +static NS_EE_ASYNC_RESOURCE_DESTROY: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + ns_ee_async_resource_destroy as crate::codegen_abi::JsBody0, + ) + .with_declared(0); + extern "C" fn ns_ee_async_resource_getter( closure: *const ClosureHeader, this: crate::closure::JsThis, @@ -384,11 +380,7 @@ extern "C" fn ns_ee_async_resource_getter( pub(crate) unsafe fn install_event_emitter_async_resource_prototype(proto: *mut ObjectHeader) { let scope = crate::gc::RuntimeHandleScope::new(); let proto = scope.root_raw_mut_ptr(proto); - crate::closure::js_register_closure_rest(ns_ee_async_resource_emit_rest as *const u8, 1); - crate::closure::js_register_closure_arity(ns_ee_async_resource_destroy as *const u8, 0); - crate::closure::js_register_closure_arity(ns_ee_async_resource_getter as *const u8, 0); - - let install_method = |name: &str, function: *const u8| { + let install_method = |name: &str, function: *const crate::closure::JsFunctionInfo| { let closure = js_closure_alloc(function, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, crate::value::TAG_UNDEFINED as i64); let closure = scope.root_raw_mut_ptr(closure); @@ -412,15 +404,18 @@ pub(crate) unsafe fn install_event_emitter_async_resource_prototype(proto: *mut ); }); }; - install_method("emit", ns_ee_async_resource_emit_rest as *const u8); - install_method("emitDestroy", ns_ee_async_resource_destroy as *const u8); + install_method("emit", &NS_EE_ASYNC_RESOURCE_EMIT_REST); + install_method("emitDestroy", &NS_EE_ASYNC_RESOURCE_DESTROY); for (name, operation) in [ ("asyncId", 0_i64), ("triggerAsyncId", 1), ("asyncResource", 2), ] { - let closure = js_closure_alloc(ns_ee_async_resource_getter as *const u8, 2); + let closure = js_closure_alloc( + crate::fn_info!(ns_ee_async_resource_getter, 0; with_declared(0)), + 2, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, crate::value::TAG_UNDEFINED as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, operation); let closure = scope.root_raw_mut_ptr(closure); @@ -457,12 +452,9 @@ pub(crate) unsafe fn install_event_emitter_async_resource_instance_methods( let scope = crate::gc::RuntimeHandleScope::new(); let obj = scope.root_raw_mut_ptr(obj); let this_value = scope.root_nanbox_f64(this_value); - crate::closure::js_register_closure_rest(ns_ee_async_resource_emit_rest as *const u8, 1); - crate::closure::js_register_closure_arity(ns_ee_async_resource_destroy as *const u8, 0); - crate::closure::js_register_closure_arity(ns_ee_async_resource_getter as *const u8, 0); for (name, function) in [ - ("emit", ns_ee_async_resource_emit_rest as *const u8), - ("emitDestroy", ns_ee_async_resource_destroy as *const u8), + ("emit", &NS_EE_ASYNC_RESOURCE_EMIT_REST), + ("emitDestroy", &NS_EE_ASYNC_RESOURCE_DESTROY), ] { let closure = js_closure_alloc(function, 1); crate::closure::js_closure_set_capture_ptr( @@ -494,7 +486,10 @@ pub(crate) unsafe fn install_event_emitter_async_resource_instance_methods( ("triggerAsyncId", 1), ("asyncResource", 2), ] { - let closure = js_closure_alloc(ns_ee_async_resource_getter as *const u8, 2); + let closure = js_closure_alloc( + crate::fn_info!(ns_ee_async_resource_getter, 0; with_declared(0)), + 2, + ); crate::closure::js_closure_set_capture_ptr( closure, 0, @@ -534,113 +529,6 @@ pub extern "C" fn js_event_emitter_async_resource_subclass_backing(receiver: i64 } } -pub(super) fn register_stub_arities() { - let register = |func: *const u8, arity: u32| { - crate::closure::js_register_closure_arity(func, arity); - }; - register(ns_chain0 as *const u8, 0); - register(ns_chain1 as *const u8, 1); - register(ns_wrap1 as *const u8, 1); - register(ns_wrap_data as *const u8, 1); - register(ns_wrap_end as *const u8, 0); - register(ns_wrap_error as *const u8, 1); - register(ns_wrap_close as *const u8, 0); - register(ns_destroy_error_microtask as *const u8, 0); - register(ns_stream_abort_listener as *const u8, 0); - register(ns_destroy1 as *const u8, 1); - register(ns_chain2 as *const u8, 2); - register(ns_chain3 as *const u8, 3); - register(ns_on2 as *const u8, 2); - register(ns_once2 as *const u8, 2); - register(ns_prepend_listener2 as *const u8, 2); - register(ns_prepend_once_listener2 as *const u8, 2); - register(ns_remove_listener2 as *const u8, 2); - register(ns_off2 as *const u8, 2); - register(ns_remove_all_listeners1 as *const u8, 1); - register(ns_readable_from_drain as *const u8, 0); - register(ns_readable_event_microtask as *const u8, 0); - register(ns_readable_end_microtask as *const u8, 0); - register(ns_writable_finish_microtask as *const u8, 0); - register(ns_construct_callback_done as *const u8, 1); - register(ns_writable_final_callback_done as *const u8, 1); - register(ns_capture_rejection as *const u8, 1); - register(ns_emit2 as *const u8, 2); - crate::closure::js_register_closure_rest(ns_emit_rest as *const u8, 1); - register(ns_resume0 as *const u8, 0); - register(ns_async_dispose as *const u8, 0); - register(ns_read1 as *const u8, 1); - register(ns_pipe2 as *const u8, 2); - register(ns_writable_write_done as *const u8, 1); - register(pipe_unpipe_callback as *const u8, 1); - register(pipe_error_callback as *const u8, 1); - register(pipe_close_callback as *const u8, 0); - register(pipe_finish_callback as *const u8, 0); - register(pipe_drain_callback as *const u8, 0); - register(pipe_finish_destination_callback as *const u8, 0); - register(writable_write_callback_noop as *const u8, 0); - register(duplex_pair_write_callback as *const u8, 3); - register(duplex_pair_final_callback as *const u8, 1); - register(transform_write_callback as *const u8, 2); - register(transform_flush_callback as *const u8, 2); - register(pipeline_success_callback as *const u8, 0); - register(pipeline_error_callback as *const u8, 1); - register(pipeline_close_callback as *const u8, 0); - register(compose_stage_error_callback as *const u8, 1); - register(compose_source_data_callback as *const u8, 1); - register(compose_source_end_callback as *const u8, 0); - register(compose_source_error_callback as *const u8, 1); - register(compose_duplex_write_callback as *const u8, 3); - register(compose_duplex_final_callback as *const u8, 1); - register(ns_write3 as *const u8, 3); - register(ns_end3 as *const u8, 3); - register(ns_cork0 as *const u8, 0); - register(ns_uncork0 as *const u8, 0); - register(ns_set_max_listeners as *const u8, 1); - register(ns_get_max_listeners as *const u8, 0); - register(ns_event_names as *const u8, 0); - register(ns_listener_count as *const u8, 1); - register(ns_listeners as *const u8, 1); - register(ns_raw_listeners as *const u8, 1); - register(ns_undefined0 as *const u8, 0); - register(ns_push1 as *const u8, 1); - register(ns_unshift1 as *const u8, 1); - register(ns_compose1 as *const u8, 1); - register(ns_pause0 as *const u8, 0); - register(ns_is_paused0 as *const u8, 0); - register(ns_unpipe1 as *const u8, 1); - register(ns_readable_resume_microtask as *const u8, 0); - register( - super::readable_from_promises::ns_readable_from_promise_fulfilled as *const u8, - 1, - ); - register( - super::readable_from_promises::ns_readable_from_promise_rejected as *const u8, - 1, - ); - register(ns_finished_error_false_close as *const u8, 0); - register(ns_finished_default_completion as *const u8, 0); - register(ns_finished_signal_abort as *const u8, 0); - register(ns_iter_to_array as *const u8, 1); - register(ns_iter_map as *const u8, 2); - register(ns_iter_filter as *const u8, 2); - register(ns_iter_reduce as *const u8, 3); - register(ns_iter_for_each as *const u8, 2); - register(ns_iter_find as *const u8, 2); - register(ns_iter_some as *const u8, 2); - register(ns_iter_every as *const u8, 2); - register(ns_iter_flat_map as *const u8, 2); - register(ns_iter_take as *const u8, 1); - register(ns_take_source_next as *const u8, 0); - register(ns_take_source_return as *const u8, 0); - register(ns_take_source_fulfilled as *const u8, 1); - register(ns_take_source_rejected as *const u8, 1); - register(ns_take_limit_fulfilled as *const u8, 1); - register(ns_take_limit_rejected as *const u8, 1); - register(ns_iter_drop as *const u8, 1); - register_consume_arities(); - async_iterator::register_arities(); -} - #[inline] pub(super) fn box_pointer(ptr: *const u8) -> f64 { f64::from_bits(JSValue::pointer(ptr).bits()) @@ -651,7 +539,7 @@ pub(super) fn install_stream_async_dispose_symbol(stream: f64) { if async_dispose.is_null() { return; } - let closure = js_closure_alloc(ns_async_dispose as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(ns_async_dispose, 0; with_declared(0)), 1); crate::closure::js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); set_hidden_value( stream, diff --git a/crates/perry-runtime/src/node_stream_duplex_methods.rs b/crates/perry-runtime/src/node_stream_duplex_methods.rs index 35773321ca..e3c483d35d 100644 --- a/crates/perry-runtime/src/node_stream_duplex_methods.rs +++ b/crates/perry-runtime/src/node_stream_duplex_methods.rs @@ -7,49 +7,100 @@ pub(super) fn duplex_methods() -> [(&'static str, StubFn); 44] { // removeListener/removeAllListeners/emit/listenerCount/listeners/ // destroy` appear once each). [ - ("on", cast2(ns_on2)), - ("once", cast2(ns_once2)), - ("prependListener", cast2(ns_prepend_listener2)), - ("prependOnceListener", cast2(ns_prepend_once_listener2)), - ("off", cast2(ns_off2)), - ("addListener", cast2(ns_on2)), - ("removeListener", cast2(ns_remove_listener2)), - ("removeAllListeners", cast1(ns_remove_all_listeners1)), - ("emit", cast2(ns_emit_rest)), - ("setMaxListeners", cast1(ns_set_max_listeners)), - ("getMaxListeners", cast0(ns_get_max_listeners)), - ("eventNames", cast0(ns_event_names)), - ("listenerCount", cast1(ns_listener_count)), - ("listeners", cast1(ns_listeners)), - ("rawListeners", cast1(ns_raw_listeners)), - ("read", cast1(ns_read1)), - ("pipe", cast2(ns_pipe2)), - ("unpipe", cast1(ns_unpipe1)), - ("wrap", cast1(ns_wrap1)), - ("pause", cast0(ns_pause0)), - ("resume", cast0(ns_resume0)), - ("setEncoding", cast1(ns_set_encoding1)), - ("isPaused", cast0(ns_is_paused0)), - ("toArray", cast1(ns_iter_to_array)), - ("map", cast2(ns_iter_map)), - ("filter", cast2(ns_iter_filter)), - ("reduce", cast3(ns_iter_reduce)), - ("forEach", cast2(ns_iter_for_each)), - ("find", cast2(ns_iter_find)), - ("some", cast2(ns_iter_some)), - ("every", cast2(ns_iter_every)), - ("flatMap", cast2(ns_iter_flat_map)), - ("take", cast1(ns_iter_take)), - ("drop", cast1(ns_iter_drop)), - ("iterator", cast1(async_iterator::ns_iterator1)), - ("push", cast1(ns_push1)), - ("unshift", cast1(ns_unshift1)), - ("compose", cast1(ns_compose1)), - ("write", cast3(ns_write3)), - ("end", cast3(ns_end3)), - ("cork", cast0(ns_cork0)), - ("uncork", cast0(ns_uncork0)), - ("destroy", cast1(ns_destroy1)), - ("setDefaultEncoding", cast1(ns_chain1)), + ("on", crate::fn_info!(ns_on2, 2; with_declared(2))), + ("once", crate::fn_info!(ns_once2, 2; with_declared(2))), + ( + "prependListener", + crate::fn_info!(ns_prepend_listener2, 2; with_declared(2)), + ), + ( + "prependOnceListener", + crate::fn_info!(ns_prepend_once_listener2, 2; with_declared(2)), + ), + ("off", crate::fn_info!(ns_off2, 2; with_declared(2))), + ("addListener", crate::fn_info!(ns_on2, 2; with_declared(2))), + ( + "removeListener", + crate::fn_info!(ns_remove_listener2, 2; with_declared(2)), + ), + ( + "removeAllListeners", + crate::fn_info!(ns_remove_all_listeners1, 1; with_declared(1)), + ), + ("emit", crate::fn_info!(ns_emit_rest, 2; with_rest(1))), + ( + "setMaxListeners", + crate::fn_info!(ns_set_max_listeners, 1; with_declared(1)), + ), + ( + "getMaxListeners", + crate::fn_info!(ns_get_max_listeners, 0; with_declared(0)), + ), + ( + "eventNames", + crate::fn_info!(ns_event_names, 0; with_declared(0)), + ), + ( + "listenerCount", + crate::fn_info!(ns_listener_count, 1; with_declared(1)), + ), + ( + "listeners", + crate::fn_info!(ns_listeners, 1; with_declared(1)), + ), + ( + "rawListeners", + crate::fn_info!(ns_raw_listeners, 1; with_declared(1)), + ), + ("read", crate::fn_info!(ns_read1, 1; with_declared(1))), + ("pipe", crate::fn_info!(ns_pipe2, 2; with_declared(2))), + ("unpipe", crate::fn_info!(ns_unpipe1, 1; with_declared(1))), + ("wrap", crate::fn_info!(ns_wrap1, 1; with_declared(1))), + ("pause", crate::fn_info!(ns_pause0, 0; with_declared(0))), + ("resume", crate::fn_info!(ns_resume0, 0; with_declared(0))), + ("setEncoding", crate::fn_info!(ns_set_encoding1, 1)), + ( + "isPaused", + crate::fn_info!(ns_is_paused0, 0; with_declared(0)), + ), + ( + "toArray", + crate::fn_info!(ns_iter_to_array, 1; with_declared(1)), + ), + ("map", crate::fn_info!(ns_iter_map, 2; with_declared(2))), + ( + "filter", + crate::fn_info!(ns_iter_filter, 2; with_declared(2)), + ), + ( + "reduce", + crate::fn_info!(ns_iter_reduce, 3; with_declared(3)), + ), + ( + "forEach", + crate::fn_info!(ns_iter_for_each, 2; with_declared(2)), + ), + ("find", crate::fn_info!(ns_iter_find, 2; with_declared(2))), + ("some", crate::fn_info!(ns_iter_some, 2; with_declared(2))), + ("every", crate::fn_info!(ns_iter_every, 2; with_declared(2))), + ( + "flatMap", + crate::fn_info!(ns_iter_flat_map, 2; with_declared(2)), + ), + ("take", crate::fn_info!(ns_iter_take, 1; with_declared(1))), + ("drop", crate::fn_info!(ns_iter_drop, 1; with_declared(1))), + ("iterator", crate::fn_info!(async_iterator::ns_iterator1, 1)), + ("push", crate::fn_info!(ns_push1, 1; with_declared(1))), + ("unshift", crate::fn_info!(ns_unshift1, 1; with_declared(1))), + ("compose", crate::fn_info!(ns_compose1, 1; with_declared(1))), + ("write", crate::fn_info!(ns_write3, 3; with_declared(3))), + ("end", crate::fn_info!(ns_end3, 3; with_declared(3))), + ("cork", crate::fn_info!(ns_cork0, 0; with_declared(0))), + ("uncork", crate::fn_info!(ns_uncork0, 0; with_declared(0))), + ("destroy", crate::fn_info!(ns_destroy1, 1; with_declared(1))), + ( + "setDefaultEncoding", + crate::fn_info!(ns_chain1, 1; with_declared(1)), + ), ] } diff --git a/crates/perry-runtime/src/node_stream_event_emitter.rs b/crates/perry-runtime/src/node_stream_event_emitter.rs index 7484c91fe4..624cd8d136 100644 --- a/crates/perry-runtime/src/node_stream_event_emitter.rs +++ b/crates/perry-runtime/src/node_stream_event_emitter.rs @@ -759,7 +759,10 @@ fn capture_listener_rejection(stream: f64, result: f64) { if promise.is_null() { return; } - let on_rejected = js_closure_alloc(ns_capture_rejection as *const u8, 1); + let on_rejected = js_closure_alloc( + crate::fn_info!(ns_capture_rejection, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(on_rejected, 0, stream); crate::promise::js_promise_then(promise, std::ptr::null(), on_rejected); } diff --git a/crates/perry-runtime/src/node_stream_iter_helpers.rs b/crates/perry-runtime/src/node_stream_iter_helpers.rs index 850740474c..b98fe6d481 100644 --- a/crates/perry-runtime/src/node_stream_iter_helpers.rs +++ b/crates/perry-runtime/src/node_stream_iter_helpers.rs @@ -322,7 +322,7 @@ fn register_to_array_abort(stream: f64, opts: f64, result: f64) { let Some(sig_obj) = object_ptr_from_value(sig) else { return; }; - let abort_cl = js_closure_alloc(ns_to_array_abort as *const u8, 2); + let abort_cl = js_closure_alloc(crate::fn_info!(ns_to_array_abort, 0; with_declared(0)), 2); js_closure_set_capture_ptr(abort_cl, 0, result.to_bits() as i64); js_closure_set_capture_f64(abort_cl, 1, stream); crate::url::js_abort_signal_add_listener( @@ -671,15 +671,6 @@ extern "C" fn consume_reject( f64::from_bits(TAG_UNDEFINED) } -pub(super) fn register_consume_arities() { - crate::closure::js_register_closure_arity(consume_on_next as *const u8, 1); - crate::closure::js_register_closure_arity(consume_on_cb as *const u8, 1); - crate::closure::js_register_closure_arity(consume_reject as *const u8, 1); - // Abort listeners are dispatched via `js_closure_call0` (0 args). - crate::closure::js_register_closure_arity(ns_consume_abort as *const u8, 0); - crate::closure::js_register_closure_arity(ns_to_array_abort as *const u8, 0); -} - /// Drive a consuming helper truly-asynchronously over the stream's async /// iterator. `initial`/`has_initial` apply to `reduce`. fn consume_stream(stream: f64, callback: f64, op: f64, initial: f64, opts: f64) -> f64 { @@ -709,9 +700,9 @@ fn consume_stream(stream: f64, callback: f64, op: f64, initial: f64, opts: f64) }; let result = crate::promise::js_promise_new(); - let state = js_closure_alloc(consume_on_next as *const u8, 9); - let on_cb = js_closure_alloc(consume_on_cb as *const u8, 1); - let reject = js_closure_alloc(consume_reject as *const u8, 1); + let state = js_closure_alloc(crate::fn_info!(consume_on_next, 1; with_declared(1)), 9); + let on_cb = js_closure_alloc(crate::fn_info!(consume_on_cb, 1; with_declared(1)), 1); + let reject = js_closure_alloc(crate::fn_info!(consume_reject, 1; with_declared(1)), 1); js_closure_set_capture_ptr(reject, 0, state as i64); js_closure_set_capture_ptr(on_cb, 0, state as i64); @@ -746,7 +737,7 @@ fn register_consume_abort(stream: f64, opts: f64, state: *const ClosureHeader) { let Some(sig_obj) = object_ptr_from_value(sig) else { return; }; - let abort_cl = js_closure_alloc(ns_consume_abort as *const u8, 1); + let abort_cl = js_closure_alloc(crate::fn_info!(ns_consume_abort, 0; with_declared(0)), 1); js_closure_set_capture_ptr(abort_cl, 0, state as i64); crate::url::js_abort_signal_add_listener( sig_obj, @@ -1134,9 +1125,9 @@ pub(super) extern "C" fn ns_take_source_next( finish_take_source(iterator.get_nanbox_f64(), None, close_source); return take_source_done_result(); } - let fulfilled = js_closure_alloc(ns_take_limit_fulfilled as *const u8, 1); + let fulfilled = js_closure_alloc(crate::fn_info!(ns_take_limit_fulfilled, 1), 1); let fulfilled = scope.root_raw_mut_ptr(fulfilled); - let rejected = js_closure_alloc(ns_take_limit_rejected as *const u8, 1); + let rejected = js_closure_alloc(crate::fn_info!(ns_take_limit_rejected, 1), 1); let rejected = scope.root_raw_mut_ptr(rejected); js_closure_set_capture_f64(fulfilled.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); js_closure_set_capture_f64(rejected.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); @@ -1151,9 +1142,9 @@ pub(super) extern "C" fn ns_take_source_next( if crate::promise::js_value_is_promise(next.get_nanbox_f64()) == 0 { return take_source_fulfilled(iterator.get_nanbox_f64(), next.get_nanbox_f64()); } - let fulfilled = js_closure_alloc(ns_take_source_fulfilled as *const u8, 1); + let fulfilled = js_closure_alloc(crate::fn_info!(ns_take_source_fulfilled, 1), 1); let fulfilled = scope.root_raw_mut_ptr(fulfilled); - let rejected = js_closure_alloc(ns_take_source_rejected as *const u8, 1); + let rejected = js_closure_alloc(crate::fn_info!(ns_take_source_rejected, 1), 1); let rejected = scope.root_raw_mut_ptr(rejected); js_closure_set_capture_f64(fulfilled.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); js_closure_set_capture_f64(rejected.get_raw_mut_ptr(), 0, iterator.get_nanbox_f64()); @@ -1179,8 +1170,14 @@ fn take_source_iterator(source: f64, result: f64, count: u32) -> f64 { let source = scope.root_nanbox_f64(source); let result = scope.root_nanbox_f64(result); let methods = [ - ("next", cast0(ns_take_source_next)), - ("return", cast0(ns_take_source_return)), + ( + "next", + crate::fn_info!(ns_take_source_next, 0; with_declared(0)), + ), + ( + "return", + crate::fn_info!(ns_take_source_return, 0; with_declared(0)), + ), ]; let iterator = box_pointer(build_object(&methods, 0x7FFF_FF70) as *const u8); set_hidden_value( @@ -1274,6 +1271,13 @@ pub(super) extern "C" fn ns_iter_drop( mod take_tests { use super::*; + static SOURCE_NEXT_INFO: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + source_next as crate::codegen_abi::JsBody0, + ); + static SOURCE_RETURN_INFO: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + source_return as crate::codegen_abi::JsBody0, + ); + extern "C" fn source_next(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { let source = this_value(closure, this); let value = get_hidden_value(source, hidden_key(b"count")).unwrap_or(0.0) + 1.0; @@ -1321,14 +1325,14 @@ mod take_tests { #[test] fn take_closes_retained_source_after_limit() { let methods = [ - ("next", cast0(source_next)), - ("return", cast0(source_return)), + ("next", &SOURCE_NEXT_INFO as StubFn), + ("return", &SOURCE_RETURN_INFO as StubFn), ]; let source = box_pointer(build_object(&methods, 0x7FFF_FF72) as *const u8); let chunks = crate::array::js_array_alloc(0); let stream = js_node_stream_readable_from(box_pointer(chunks as *const u8)); set_hidden_value(stream, hidden_key(READABLE_SOURCE_ITERATOR_KEY), source); - let take = js_closure_alloc(ns_iter_take as *const u8, 1); + let take = js_closure_alloc(crate::fn_info!(ns_iter_take, 1), 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); @@ -1348,15 +1352,15 @@ mod take_tests { #[test] fn take_does_not_return_source_that_finished_naturally() { let methods = [ - ("next", cast0(source_next)), - ("return", cast0(source_return)), + ("next", &SOURCE_NEXT_INFO as StubFn), + ("return", &SOURCE_RETURN_INFO as StubFn), ]; let source = box_pointer(build_object(&methods, 0x7FFF_FF73) as *const u8); set_hidden_value(source, hidden_key(b"doneAfter"), 1.0); let chunks = crate::array::js_array_alloc(0); let stream = js_node_stream_readable_from(box_pointer(chunks as *const u8)); set_hidden_value(stream, hidden_key(READABLE_SOURCE_ITERATOR_KEY), source); - let take = js_closure_alloc(ns_iter_take as *const u8, 1); + let take = js_closure_alloc(crate::fn_info!(ns_iter_take, 1), 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); @@ -1374,8 +1378,8 @@ mod take_tests { fn take_propagates_retained_source_state() { let source = box_pointer(build_object( &[ - ("next", cast0(source_next)), - ("return", cast0(source_return)), + ("next", &SOURCE_NEXT_INFO as StubFn), + ("return", &SOURCE_RETURN_INFO as StubFn), ], 0x7FFF_FF75, ) as *const u8); @@ -1384,7 +1388,7 @@ mod take_tests { set_hidden_value(stream, hidden_key(READABLE_SOURCE_ITERATOR_KEY), source); set_hidden_value(stream, hidden_error_key(), 7.0); set_hidden_value(stream, hidden_signal_key(), 8.0); - let take = js_closure_alloc(ns_iter_take as *const u8, 1); + let take = js_closure_alloc(crate::fn_info!(ns_iter_take, 1), 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); @@ -1397,8 +1401,8 @@ mod take_tests { fn take_suppresses_sync_and_async_lookahead_errors() { for reject in [false, true] { let methods = [ - ("next", cast0(source_next)), - ("return", cast0(source_return)), + ("next", &SOURCE_NEXT_INFO as StubFn), + ("return", &SOURCE_RETURN_INFO as StubFn), ]; let source = box_pointer(build_object(&methods, 0x7FFF_FF74) as *const u8); set_hidden_value( diff --git a/crates/perry-runtime/src/node_stream_pipeline.rs b/crates/perry-runtime/src/node_stream_pipeline.rs index 2beaf7d020..7f951eb0fe 100644 --- a/crates/perry-runtime/src/node_stream_pipeline.rs +++ b/crates/perry-runtime/src/node_stream_pipeline.rs @@ -203,13 +203,19 @@ pub(super) fn add_pipeline_callback_listeners( let error_event = string_value(b"error"); let close_event = string_value(b"close"); for stage in stages { - let listener = js_closure_alloc(pipeline_error_callback as *const u8, 3); + let listener = js_closure_alloc( + crate::fn_info!(pipeline_error_callback, 1; with_declared(1)), + 3, + ); js_closure_set_capture_f64(listener, 0, state); js_closure_set_capture_f64(listener, 1, callback); js_closure_set_capture_f64(listener, 2, stage_array); add_stream_listener_for_event(*stage, error_event, box_pointer(listener as *const u8)); if !pipeline_stage_already_complete(*stage) { - let close_listener = js_closure_alloc(pipeline_close_callback as *const u8, 4); + let close_listener = js_closure_alloc( + crate::fn_info!(pipeline_close_callback, 0; with_declared(0)), + 4, + ); js_closure_set_capture_f64(close_listener, 0, state); js_closure_set_capture_f64(close_listener, 1, callback); js_closure_set_capture_f64(close_listener, 2, stage_array); @@ -237,7 +243,10 @@ pub(super) fn add_pipeline_callback_listeners( } else { string_value(b"end") }; - let success = js_closure_alloc(pipeline_success_callback as *const u8, 2); + let success = js_closure_alloc( + crate::fn_info!(pipeline_success_callback, 0; with_declared(0)), + 2, + ); js_closure_set_capture_f64(success, 0, state); js_closure_set_capture_f64(success, 1, callback); add_stream_listener_for_event( @@ -551,13 +560,15 @@ extern "C" fn collected_pipeline_error_noop( } fn install_collected_pipeline_error_guards(stages: &[f64]) { - crate::closure::js_register_closure_arity(collected_pipeline_error_noop as *const u8, 1); let scope = crate::gc::RuntimeHandleScope::new(); let stages = scope.root_nanbox_f64_slice(stages); let error = scope.root_nanbox_f64(string_value(b"error")); for stage in &stages { if is_pipeline_stream(stage.get_nanbox_f64()) { - let listener = js_closure_alloc(collected_pipeline_error_noop as *const u8, 0); + let listener = js_closure_alloc( + crate::fn_info!(collected_pipeline_error_noop, 1; with_declared(1)), + 0, + ); let listener = scope.root_raw_mut_ptr(listener); add_stream_listener_for_event( stage.get_nanbox_f64(), @@ -1044,7 +1055,10 @@ fn install_compose_stage_error_listeners(composite: f64, source: f64, stages: f6 if !is_pipeline_stream(stage.get_nanbox_f64()) { continue; } - let listener = js_closure_alloc(compose_stage_error_callback as *const u8, 3); + let listener = js_closure_alloc( + crate::fn_info!(compose_stage_error_callback, 1; with_declared(1)), + 3, + ); let listener = scope.root_raw_mut_ptr(listener); js_closure_set_capture_f64(listener.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); js_closure_set_capture_f64(listener.get_raw_mut_ptr(), 1, source.get_nanbox_f64()); @@ -1065,7 +1079,10 @@ fn install_compose_source_listeners(composite: f64, source: f64, stages: f64) { if !is_pipeline_stream(source.get_nanbox_f64()) { return; } - let data = js_closure_alloc(compose_source_data_callback as *const u8, 1); + let data = js_closure_alloc( + crate::fn_info!(compose_source_data_callback, 1; with_declared(1)), + 1, + ); let data = scope.root_raw_mut_ptr(data); js_closure_set_capture_f64(data.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); add_stream_listener_for_event( @@ -1074,7 +1091,10 @@ fn install_compose_source_listeners(composite: f64, source: f64, stages: f64) { box_pointer(data.get_raw_const_ptr()), ); - let end = js_closure_alloc(compose_source_end_callback as *const u8, 1); + let end = js_closure_alloc( + crate::fn_info!(compose_source_end_callback, 0; with_declared(0)), + 1, + ); let end = scope.root_raw_mut_ptr(end); js_closure_set_capture_f64(end.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); add_stream_listener_for_event( @@ -1097,7 +1117,10 @@ fn install_compose_source_error_listener(composite: f64, source: f64, stages: f6 let composite = scope.root_nanbox_f64(composite); let source = scope.root_nanbox_f64(source); let stages = scope.root_nanbox_f64(stages); - let error = js_closure_alloc(compose_source_error_callback as *const u8, 3); + let error = js_closure_alloc( + crate::fn_info!(compose_source_error_callback, 1; with_declared(1)), + 3, + ); let error = scope.root_raw_mut_ptr(error); js_closure_set_capture_f64(error.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); js_closure_set_capture_f64(error.get_raw_mut_ptr(), 1, source.get_nanbox_f64()); @@ -1118,7 +1141,10 @@ fn install_composed_duplex_callbacks(composite: f64, stages: f64, source: f64, w if raw < 0x10000 { return; } - let write = js_closure_alloc(compose_duplex_write_callback as *const u8, 3); + let write = js_closure_alloc( + crate::fn_info!(compose_duplex_write_callback, 3; with_declared(3)), + 3, + ); let write = scope.root_raw_mut_ptr(write); js_closure_set_capture_f64(write.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); js_closure_set_capture_f64(write.get_raw_mut_ptr(), 1, stages.get_nanbox_f64()); @@ -1130,7 +1156,10 @@ fn install_composed_duplex_callbacks(composite: f64, stages: f64, source: f64, w box_pointer(write.get_raw_const_ptr()), ); - let final_cb = js_closure_alloc(compose_duplex_final_callback as *const u8, 3); + let final_cb = js_closure_alloc( + crate::fn_info!(compose_duplex_final_callback, 1; with_declared(1)), + 3, + ); let final_cb = scope.root_raw_mut_ptr(final_cb); js_closure_set_capture_f64(final_cb.get_raw_mut_ptr(), 0, composite.get_nanbox_f64()); js_closure_set_capture_f64(final_cb.get_raw_mut_ptr(), 1, stages.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/node_stream_readwrite.rs b/crates/perry-runtime/src/node_stream_readwrite.rs index 9964b85786..11675a2c75 100644 --- a/crates/perry-runtime/src/node_stream_readwrite.rs +++ b/crates/perry-runtime/src/node_stream_readwrite.rs @@ -329,7 +329,7 @@ pub(super) fn schedule_readable_resume(stream: f64) { hidden_readable_resume_scheduled_key(), f64::from_bits(TAG_TRUE), ); - let closure = js_closure_alloc(ns_readable_resume_microtask as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(ns_readable_resume_microtask, 0), 1); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); crate::builtins::js_queue_microtask(closure as i64); } @@ -630,7 +630,7 @@ pub(super) fn schedule_readable_from_drain(stream: f64) { hidden_drain_scheduled_key(), f64::from_bits(TAG_TRUE), ); - let closure = js_closure_alloc(ns_readable_from_drain as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(ns_readable_from_drain, 0), 1); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); crate::builtins::js_queue_microtask(closure as i64); } @@ -655,7 +655,7 @@ pub(super) fn queue_readable_event(stream: f64) { hidden_readable_scheduled_key(), f64::from_bits(TAG_TRUE), ); - let closure = js_closure_alloc(ns_readable_event_microtask as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(ns_readable_event_microtask, 0), 1); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); crate::builtins::js_queue_microtask(closure as i64); } @@ -675,7 +675,7 @@ pub(super) fn schedule_readable_end(stream: f64) { return; } set_hidden_value(stream, hidden_end_scheduled_key(), f64::from_bits(TAG_TRUE)); - let closure = js_closure_alloc(ns_readable_end_microtask as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(ns_readable_end_microtask, 0), 1); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); crate::builtins::js_queue_microtask(closure as i64); } @@ -699,7 +699,7 @@ pub(super) fn schedule_writable_finish(stream: f64, callback: Option) { hidden_writable_final_pending_key(), f64::from_bits(TAG_TRUE), ); - let cb = js_closure_alloc(ns_writable_final_callback_done as *const u8, 2); + let cb = js_closure_alloc(crate::fn_info!(ns_writable_final_callback_done, 1), 2); js_closure_set_capture_f64(cb, 0, stream); js_closure_set_capture_f64( cb, @@ -723,7 +723,7 @@ pub(super) fn schedule_writable_finish(stream: f64, callback: Option) { hidden_finish_scheduled_key(), f64::from_bits(TAG_TRUE), ); - let closure = js_closure_alloc(ns_writable_finish_microtask as *const u8, 2); + let closure = js_closure_alloc(crate::fn_info!(ns_writable_finish_microtask, 0), 2); js_closure_set_capture_ptr(closure, 0, stream.to_bits() as i64); js_closure_set_capture_ptr( closure, @@ -1027,7 +1027,7 @@ pub(super) fn finish_transform_stream(stream: f64, callback: Option) -> boo hidden_transform_finishing_key(), f64::from_bits(TAG_TRUE), ); - let cb = js_closure_alloc(transform_flush_callback as *const u8, 2); + let cb = js_closure_alloc(crate::fn_info!(transform_flush_callback, 2), 2); js_closure_set_capture_f64(cb, 0, stream); js_closure_set_capture_f64( cb, @@ -1311,7 +1311,7 @@ pub(super) fn invoke_construct_callback(stream: f64, opts: f64) { }; let construct = rebind_callback_this(construct, stream); set_hidden_value(stream, hidden_key(STREAM_CONSTRUCT_KEY), construct); - let cb = js_closure_alloc(ns_construct_callback_done as *const u8, 1); + let cb = js_closure_alloc(crate::fn_info!(ns_construct_callback_done, 1), 1); js_closure_set_capture_f64(cb, 0, stream); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); unsafe { @@ -1805,51 +1805,115 @@ pub(crate) fn js_node_stream_readable_chunks_result(stream: f64) -> Result { + JsFunctionInfo::of($body as crate::fn_info!(@ty $n)) + }; +} + +/// The stream method bodies' infos: one per body, shared by every table below. +static NS_ON2_INFO: JsFunctionInfo = ns_info!(ns_on2, 2).with_declared(2); +static NS_ONCE2_INFO: JsFunctionInfo = ns_info!(ns_once2, 2).with_declared(2); +static NS_PREPEND_LISTENER2_INFO: JsFunctionInfo = + ns_info!(ns_prepend_listener2, 2).with_declared(2); +static NS_PREPEND_ONCE_LISTENER2_INFO: JsFunctionInfo = + ns_info!(ns_prepend_once_listener2, 2).with_declared(2); +static NS_OFF2_INFO: JsFunctionInfo = ns_info!(ns_off2, 2).with_declared(2); +static NS_REMOVE_LISTENER2_INFO: JsFunctionInfo = ns_info!(ns_remove_listener2, 2).with_declared(2); +static NS_REMOVE_ALL_LISTENERS1_INFO: JsFunctionInfo = + ns_info!(ns_remove_all_listeners1, 1).with_declared(1); +static NS_EMIT_REST_INFO: JsFunctionInfo = ns_info!(ns_emit_rest, 2).with_rest(1); +static NS_SET_MAX_LISTENERS_INFO: JsFunctionInfo = + ns_info!(ns_set_max_listeners, 1).with_declared(1); +static NS_GET_MAX_LISTENERS_INFO: JsFunctionInfo = + ns_info!(ns_get_max_listeners, 0).with_declared(0); +static NS_EVENT_NAMES_INFO: JsFunctionInfo = ns_info!(ns_event_names, 0).with_declared(0); +static NS_LISTENER_COUNT_INFO: JsFunctionInfo = ns_info!(ns_listener_count, 1).with_declared(1); +static NS_LISTENERS_INFO: JsFunctionInfo = ns_info!(ns_listeners, 1).with_declared(1); +static NS_RAW_LISTENERS_INFO: JsFunctionInfo = ns_info!(ns_raw_listeners, 1).with_declared(1); +static NS_READ1_INFO: JsFunctionInfo = ns_info!(ns_read1, 1).with_declared(1); +static NS_PIPE2_INFO: JsFunctionInfo = ns_info!(ns_pipe2, 2).with_declared(2); +static NS_UNPIPE1_INFO: JsFunctionInfo = ns_info!(ns_unpipe1, 1).with_declared(1); +static NS_WRAP1_INFO: JsFunctionInfo = ns_info!(ns_wrap1, 1).with_declared(1); +static NS_PAUSE0_INFO: JsFunctionInfo = ns_info!(ns_pause0, 0).with_declared(0); +static NS_RESUME0_INFO: JsFunctionInfo = ns_info!(ns_resume0, 0).with_declared(0); +static NS_DESTROY1_INFO: JsFunctionInfo = ns_info!(ns_destroy1, 1).with_declared(1); +static NS_SET_ENCODING1_INFO: JsFunctionInfo = ns_info!(ns_set_encoding1, 1); +static NS_IS_PAUSED0_INFO: JsFunctionInfo = ns_info!(ns_is_paused0, 0).with_declared(0); +static NS_ITER_TO_ARRAY_INFO: JsFunctionInfo = ns_info!(ns_iter_to_array, 1).with_declared(1); +static NS_ITER_MAP_INFO: JsFunctionInfo = ns_info!(ns_iter_map, 2).with_declared(2); +static NS_ITER_FILTER_INFO: JsFunctionInfo = ns_info!(ns_iter_filter, 2).with_declared(2); +static NS_ITER_REDUCE_INFO: JsFunctionInfo = ns_info!(ns_iter_reduce, 3).with_declared(3); +static NS_ITER_FOR_EACH_INFO: JsFunctionInfo = ns_info!(ns_iter_for_each, 2).with_declared(2); +static NS_ITER_FIND_INFO: JsFunctionInfo = ns_info!(ns_iter_find, 2).with_declared(2); +static NS_ITER_SOME_INFO: JsFunctionInfo = ns_info!(ns_iter_some, 2).with_declared(2); +static NS_ITER_EVERY_INFO: JsFunctionInfo = ns_info!(ns_iter_every, 2).with_declared(2); +static NS_ITER_FLAT_MAP_INFO: JsFunctionInfo = ns_info!(ns_iter_flat_map, 2).with_declared(2); +static NS_ITER_TAKE_INFO: JsFunctionInfo = ns_info!(ns_iter_take, 1).with_declared(1); +static NS_ITER_DROP_INFO: JsFunctionInfo = ns_info!(ns_iter_drop, 1).with_declared(1); +static NS_ITERATOR1_INFO: crate::closure::JsFunctionInfo = crate::closure::JsFunctionInfo::of( + async_iterator::ns_iterator1 as crate::codegen_abi::JsBody1, +) +.with_declared(1); +static NS_PUSH1_INFO: JsFunctionInfo = ns_info!(ns_push1, 1).with_declared(1); +static NS_UNSHIFT1_INFO: JsFunctionInfo = ns_info!(ns_unshift1, 1).with_declared(1); +static NS_COMPOSE1_INFO: JsFunctionInfo = ns_info!(ns_compose1, 1).with_declared(1); +static NS_WRITE3_INFO: JsFunctionInfo = ns_info!(ns_write3, 3).with_declared(3); +static NS_END3_INFO: JsFunctionInfo = ns_info!(ns_end3, 3).with_declared(3); +static NS_CORK0_INFO: JsFunctionInfo = ns_info!(ns_cork0, 0).with_declared(0); +static NS_UNCORK0_INFO: JsFunctionInfo = ns_info!(ns_uncork0, 0).with_declared(0); +static NS_CHAIN1_INFO: JsFunctionInfo = ns_info!(ns_chain1, 1).with_declared(1); +static NS_CHAIN3_INFO: JsFunctionInfo = ns_info!(ns_chain3, 3).with_declared(3); + // Method table order determines packed-key order and shape-cache identity. pub(super) fn readable_methods() -> [(&'static str, StubFn); 39] { [ - ("on", cast2(ns_on2)), - ("once", cast2(ns_once2)), - ("prependListener", cast2(ns_prepend_listener2)), - ("prependOnceListener", cast2(ns_prepend_once_listener2)), - ("off", cast2(ns_off2)), - ("addListener", cast2(ns_on2)), - ("removeListener", cast2(ns_remove_listener2)), - ("removeAllListeners", cast1(ns_remove_all_listeners1)), - ("emit", cast2(ns_emit_rest)), - ("setMaxListeners", cast1(ns_set_max_listeners)), - ("getMaxListeners", cast0(ns_get_max_listeners)), - ("eventNames", cast0(ns_event_names)), - ("listenerCount", cast1(ns_listener_count)), - ("listeners", cast1(ns_listeners)), - ("rawListeners", cast1(ns_raw_listeners)), - ("read", cast1(ns_read1)), - ("pipe", cast2(ns_pipe2)), - ("unpipe", cast1(ns_unpipe1)), - ("wrap", cast1(ns_wrap1)), - ("pause", cast0(ns_pause0)), - ("resume", cast0(ns_resume0)), - ("destroy", cast1(ns_destroy1)), - ("setEncoding", cast1(ns_set_encoding1)), - ("isPaused", cast0(ns_is_paused0)), + ("on", &NS_ON2_INFO), + ("once", &NS_ONCE2_INFO), + ("prependListener", &NS_PREPEND_LISTENER2_INFO), + ("prependOnceListener", &NS_PREPEND_ONCE_LISTENER2_INFO), + ("off", &NS_OFF2_INFO), + ("addListener", &NS_ON2_INFO), + ("removeListener", &NS_REMOVE_LISTENER2_INFO), + ("removeAllListeners", &NS_REMOVE_ALL_LISTENERS1_INFO), + ("emit", &NS_EMIT_REST_INFO), + ("setMaxListeners", &NS_SET_MAX_LISTENERS_INFO), + ("getMaxListeners", &NS_GET_MAX_LISTENERS_INFO), + ("eventNames", &NS_EVENT_NAMES_INFO), + ("listenerCount", &NS_LISTENER_COUNT_INFO), + ("listeners", &NS_LISTENERS_INFO), + ("rawListeners", &NS_RAW_LISTENERS_INFO), + ("read", &NS_READ1_INFO), + ("pipe", &NS_PIPE2_INFO), + ("unpipe", &NS_UNPIPE1_INFO), + ("wrap", &NS_WRAP1_INFO), + ("pause", &NS_PAUSE0_INFO), + ("resume", &NS_RESUME0_INFO), + ("destroy", &NS_DESTROY1_INFO), + ("setEncoding", &NS_SET_ENCODING1_INFO), + ("isPaused", &NS_IS_PAUSED0_INFO), // #1558: async iterator helpers; arities pad missing options args. - ("toArray", cast1(ns_iter_to_array)), - ("map", cast2(ns_iter_map)), - ("filter", cast2(ns_iter_filter)), - ("reduce", cast3(ns_iter_reduce)), - ("forEach", cast2(ns_iter_for_each)), - ("find", cast2(ns_iter_find)), - ("some", cast2(ns_iter_some)), - ("every", cast2(ns_iter_every)), - ("flatMap", cast2(ns_iter_flat_map)), - ("take", cast1(ns_iter_take)), - ("drop", cast1(ns_iter_drop)), - ("iterator", cast1(async_iterator::ns_iterator1)), + ("toArray", &NS_ITER_TO_ARRAY_INFO), + ("map", &NS_ITER_MAP_INFO), + ("filter", &NS_ITER_FILTER_INFO), + ("reduce", &NS_ITER_REDUCE_INFO), + ("forEach", &NS_ITER_FOR_EACH_INFO), + ("find", &NS_ITER_FIND_INFO), + ("some", &NS_ITER_SOME_INFO), + ("every", &NS_ITER_EVERY_INFO), + ("flatMap", &NS_ITER_FLAT_MAP_INFO), + ("take", &NS_ITER_TAKE_INFO), + ("drop", &NS_ITER_DROP_INFO), + ("iterator", &NS_ITERATOR1_INFO), // #1539 — push() backpressure return + readable.compose() instance form. - ("push", cast1(ns_push1)), - ("unshift", cast1(ns_unshift1)), - ("compose", cast1(ns_compose1)), + ("push", &NS_PUSH1_INFO), + ("unshift", &NS_UNSHIFT1_INFO), + ("compose", &NS_COMPOSE1_INFO), ] } @@ -1865,47 +1929,47 @@ pub(super) fn readable_methods() -> [(&'static str, StubFn); 39] { /// constructor. pub(super) fn emitter_methods() -> [(&'static str, StubFn); 15] { [ - ("on", cast2(ns_on2)), - ("once", cast2(ns_once2)), - ("prependListener", cast2(ns_prepend_listener2)), - ("prependOnceListener", cast2(ns_prepend_once_listener2)), - ("off", cast2(ns_off2)), - ("addListener", cast2(ns_on2)), - ("removeListener", cast2(ns_remove_listener2)), - ("removeAllListeners", cast1(ns_remove_all_listeners1)), - ("emit", cast2(ns_emit_rest)), - ("setMaxListeners", cast1(ns_set_max_listeners)), - ("getMaxListeners", cast0(ns_get_max_listeners)), - ("eventNames", cast0(ns_event_names)), - ("listenerCount", cast1(ns_listener_count)), - ("listeners", cast1(ns_listeners)), - ("rawListeners", cast1(ns_raw_listeners)), + ("on", &NS_ON2_INFO), + ("once", &NS_ONCE2_INFO), + ("prependListener", &NS_PREPEND_LISTENER2_INFO), + ("prependOnceListener", &NS_PREPEND_ONCE_LISTENER2_INFO), + ("off", &NS_OFF2_INFO), + ("addListener", &NS_ON2_INFO), + ("removeListener", &NS_REMOVE_LISTENER2_INFO), + ("removeAllListeners", &NS_REMOVE_ALL_LISTENERS1_INFO), + ("emit", &NS_EMIT_REST_INFO), + ("setMaxListeners", &NS_SET_MAX_LISTENERS_INFO), + ("getMaxListeners", &NS_GET_MAX_LISTENERS_INFO), + ("eventNames", &NS_EVENT_NAMES_INFO), + ("listenerCount", &NS_LISTENER_COUNT_INFO), + ("listeners", &NS_LISTENERS_INFO), + ("rawListeners", &NS_RAW_LISTENERS_INFO), ] } pub(super) fn writable_methods() -> [(&'static str, StubFn); 22] { [ - ("on", cast2(ns_on2)), - ("once", cast2(ns_once2)), - ("prependListener", cast2(ns_prepend_listener2)), - ("prependOnceListener", cast2(ns_prepend_once_listener2)), - ("off", cast2(ns_off2)), - ("addListener", cast2(ns_on2)), - ("removeListener", cast2(ns_remove_listener2)), - ("removeAllListeners", cast1(ns_remove_all_listeners1)), - ("emit", cast2(ns_emit_rest)), - ("setMaxListeners", cast1(ns_set_max_listeners)), - ("getMaxListeners", cast0(ns_get_max_listeners)), - ("eventNames", cast0(ns_event_names)), - ("listenerCount", cast1(ns_listener_count)), - ("listeners", cast1(ns_listeners)), - ("rawListeners", cast1(ns_raw_listeners)), - ("write", cast3(ns_write3)), - ("end", cast3(ns_end3)), - ("cork", cast0(ns_cork0)), - ("uncork", cast0(ns_uncork0)), - ("destroy", cast1(ns_destroy1)), - ("setDefaultEncoding", cast1(ns_chain1)), - ("_write", cast3(ns_chain3)), + ("on", &NS_ON2_INFO), + ("once", &NS_ONCE2_INFO), + ("prependListener", &NS_PREPEND_LISTENER2_INFO), + ("prependOnceListener", &NS_PREPEND_ONCE_LISTENER2_INFO), + ("off", &NS_OFF2_INFO), + ("addListener", &NS_ON2_INFO), + ("removeListener", &NS_REMOVE_LISTENER2_INFO), + ("removeAllListeners", &NS_REMOVE_ALL_LISTENERS1_INFO), + ("emit", &NS_EMIT_REST_INFO), + ("setMaxListeners", &NS_SET_MAX_LISTENERS_INFO), + ("getMaxListeners", &NS_GET_MAX_LISTENERS_INFO), + ("eventNames", &NS_EVENT_NAMES_INFO), + ("listenerCount", &NS_LISTENER_COUNT_INFO), + ("listeners", &NS_LISTENERS_INFO), + ("rawListeners", &NS_RAW_LISTENERS_INFO), + ("write", &NS_WRITE3_INFO), + ("end", &NS_END3_INFO), + ("cork", &NS_CORK0_INFO), + ("uncork", &NS_UNCORK0_INFO), + ("destroy", &NS_DESTROY1_INFO), + ("setDefaultEncoding", &NS_CHAIN1_INFO), + ("_write", &NS_CHAIN3_INFO), ] } diff --git a/crates/perry-runtime/src/node_stream_state_tests.rs b/crates/perry-runtime/src/node_stream_state_tests.rs index f1f40c34dd..29efbea464 100644 --- a/crates/perry-runtime/src/node_stream_state_tests.rs +++ b/crates/perry-runtime/src/node_stream_state_tests.rs @@ -128,9 +128,10 @@ fn stream_object_mode_flags_default_false_and_follow_options() { #[test] fn passthrough_subclass_uses_override_or_identity_transform() { - crate::closure::js_register_closure_arity(super::tests::noop_listener as *const u8, 0); - let callback = - box_pointer(js_closure_alloc(super::tests::noop_listener as *const u8, 0) as *const u8); + let callback = box_pointer(js_closure_alloc( + crate::fn_info!(super::tests::noop_listener, 0; with_declared(0)), + 0, + ) as *const u8); let overridden_obj = crate::object::js_object_alloc(0, 1); js_object_set_field_by_name(overridden_obj, hidden_key(b"_transform"), callback); diff --git a/crates/perry-runtime/src/node_stream_state_view.rs b/crates/perry-runtime/src/node_stream_state_view.rs index 47298536b3..2108a7d61b 100644 --- a/crates/perry-runtime/src/node_stream_state_view.rs +++ b/crates/perry-runtime/src/node_stream_state_view.rs @@ -276,8 +276,6 @@ fn state_proto(kind: usize) -> f64 { return f64::from_bits(cached); } ensure_state_proto_scanner(); - crate::closure::js_register_closure_arity(stream_state_get as *const u8, 0); - crate::closure::js_register_closure_arity(stream_state_set as *const u8, 1); let fields = if kind == READABLE_KIND { READABLE_FIELDS } else { @@ -295,9 +293,15 @@ fn state_proto(kind: usize) -> f64 { for (index, name) in fields.iter().enumerate() { let scope = crate::gc::RuntimeHandleScope::new(); let id = (kind * 64 + index) as f64; - let getter = scope.root_raw_mut_ptr(js_closure_alloc(stream_state_get as *const u8, 1)); + let getter = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(stream_state_get, 0; with_declared(0)), + 1, + )); getter.with_mut_ptr(|g| js_closure_set_capture_f64(g, 0, id)); - let setter = scope.root_raw_mut_ptr(js_closure_alloc(stream_state_set as *const u8, 1)); + let setter = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(stream_state_set, 1; with_declared(1)), + 1, + )); setter.with_mut_ptr(|s| js_closure_set_capture_f64(s, 0, id)); let key = scope.root_raw_mut_ptr(crate::string::js_string_from_bytes( name.as_ptr(), diff --git a/crates/perry-runtime/src/node_stream_tests.rs b/crates/perry-runtime/src/node_stream_tests.rs index 88f18fb97d..6791ba2285 100644 --- a/crates/perry-runtime/src/node_stream_tests.rs +++ b/crates/perry-runtime/src/node_stream_tests.rs @@ -4,6 +4,13 @@ use super::*; use std::cell::RefCell; +/// The info of a native test body whose `.length` is its parameter count. +macro_rules! declared_fn { + ($body:path, $n:tt) => { + crate::fn_info!($body, $n; with_declared($n)) + }; +} + thread_local! { pub(super) static WRITE_CAPTURED: RefCell>> = const { RefCell::new(Vec::new()) }; static WRITEV_CAPTURED: RefCell>> = const { RefCell::new(Vec::new()) }; @@ -46,11 +53,10 @@ extern "C" fn capture_finished_callback( #[test] fn finished_waits_for_both_passthrough_sides() { FINISHED_CALLBACK_COUNT.with(|count| *count.borrow_mut() = 0); - crate::closure::js_register_closure_arity(capture_finished_callback as *const u8, 0); let stream = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); let callback = - box_pointer(js_closure_alloc(capture_finished_callback as *const u8, 0) as *const u8); + box_pointer(js_closure_alloc(declared_fn!(capture_finished_callback, 0), 0) as *const u8); let mut args = crate::array::js_array_alloc(2); args = crate::array::js_array_push_f64(args, stream); args = crate::array::js_array_push_f64(args, callback); @@ -87,7 +93,7 @@ fn buffer_value(bytes: &[u8]) -> f64 { #[test] fn readable_from_validation_rejects_promises_and_functions() { let promise = crate::promise::js_promise_new(); - let function = crate::closure::js_closure_alloc(write_capture as *const u8, 0); + let function = crate::closure::js_closure_alloc(declared_fn!(write_capture, 3), 0); assert!(is_invalid_readable_from_input(box_pointer( promise as *const u8 @@ -129,8 +135,8 @@ extern "C" fn capture_uncaught_stream_error( fn destroy_with_unhandled_error_reaches_process_uncaught_exception() { crate::os::test_clear_process_event_listeners(); UNCAUGHT_STREAM_ERROR_COUNT.with(|count| *count.borrow_mut() = 0); - let listener = js_closure_alloc(capture_uncaught_stream_error as *const u8, 0); - crate::closure::js_register_closure_arity(capture_uncaught_stream_error as *const u8, 1); + let listener = js_closure_alloc(declared_fn!(capture_uncaught_stream_error, 1), 0); + let event = string_value("uncaughtException"); let listener = box_pointer(listener as *const u8); let _ = crate::os::js_process_on(event.to_bits() as i64, listener.to_bits() as i64); @@ -151,8 +157,8 @@ fn destroy_with_unhandled_error_reaches_process_uncaught_exception() { fn pipe_cleanup_does_not_swallow_destination_error() { crate::os::test_clear_process_event_listeners(); UNCAUGHT_STREAM_ERROR_COUNT.with(|count| *count.borrow_mut() = 0); - let listener = js_closure_alloc(capture_uncaught_stream_error as *const u8, 0); - crate::closure::js_register_closure_arity(capture_uncaught_stream_error as *const u8, 1); + let listener = js_closure_alloc(declared_fn!(capture_uncaught_stream_error, 1), 0); + let event = string_value("uncaughtException"); let listener = box_pointer(listener as *const u8); let _ = crate::os::js_process_on(event.to_bits() as i64, listener.to_bits() as i64); @@ -186,8 +192,7 @@ extern "C" fn return_pipeline_source( fn pipeline_function_stage_receives_async_iterable_stream() { let mut chunks = crate::array::js_array_alloc(1); chunks = crate::array::js_array_push_f64(chunks, string_value("x")); - let stage = js_closure_alloc(return_pipeline_source as *const u8, 0); - crate::closure::js_register_closure_arity(return_pipeline_source as *const u8, 1); + let stage = js_closure_alloc(declared_fn!(return_pipeline_source, 1), 0); let result = call_pipeline_function_stage( box_pointer(stage as *const u8), @@ -431,8 +436,8 @@ fn readable_set_encoding_emits_buffer_chunks_as_strings() { let handle = raw_ptr_from_value(stream) as i64; js_node_stream_method_set_encoding(handle, string_value("base64")); - let data_closure = js_closure_alloc(capture_data_text_listener as *const u8, 0); - crate::closure::js_register_closure_arity(capture_data_text_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_text_listener, 1), 0); + let _ = js_node_stream_method_on( handle, string_value("data"), @@ -797,8 +802,8 @@ fn readable_from_map_retains_entry_pairs_in_insertion_order() { fn writable_options_write_callback_is_invoked_by_stub_write() { WRITE_CAPTURED.with(|captured| captured.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let closure = js_closure_alloc(write_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); + let closure = js_closure_alloc(declared_fn!(write_capture, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -832,8 +837,8 @@ fn transform_option_callback_transforms_written_chunks() { TRANSFORM_THIS_HAS_STREAM_STATE.with(|matches| matches.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let transform_cb = js_closure_alloc(transform_upper_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_upper_callback as *const u8, 3); + let transform_cb = js_closure_alloc(declared_fn!(transform_upper_callback, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -842,8 +847,8 @@ fn transform_option_callback_transforms_written_chunks() { let stream = js_node_stream_transform_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let _ = js_node_stream_method_on( handle, @@ -877,8 +882,8 @@ fn transform_pipe_chain_applies_callback_output() { let src = js_node_stream_readable_from(box_pointer(chunks as *const u8)); let opts = crate::object::js_object_alloc(0, 1); - let transform_cb = js_closure_alloc(transform_upper_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_upper_callback as *const u8, 3); + let transform_cb = js_closure_alloc(declared_fn!(transform_upper_callback, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -887,8 +892,8 @@ fn transform_pipe_chain_applies_callback_output() { let upper = js_node_stream_transform_new(box_pointer(opts as *const u8)); let sink = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); - let sink_data = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let sink_data = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(sink_data, 0, sink); let _ = js_node_stream_method_on( raw_ptr_from_value(sink) as i64, @@ -939,8 +944,8 @@ fn compose_source_transform_applies_stage_snapshot() { let src = js_node_stream_readable_from(box_pointer(chunks as *const u8)); let opts = crate::object::js_object_alloc(0, 1); - let transform_cb = js_closure_alloc(transform_upper_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_upper_callback as *const u8, 3); + let transform_cb = js_closure_alloc(declared_fn!(transform_upper_callback, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -963,8 +968,8 @@ fn compose_snapshot_stage_error_reaches_only_composite_listener() { crate::os::test_clear_process_event_listeners(); ERROR_COUNT.with(|count| *count.borrow_mut() = 0); UNCAUGHT_STREAM_ERROR_COUNT.with(|count| *count.borrow_mut() = 0); - let uncaught = js_closure_alloc(capture_uncaught_stream_error as *const u8, 0); - crate::closure::js_register_closure_arity(capture_uncaught_stream_error as *const u8, 1); + let uncaught = js_closure_alloc(declared_fn!(capture_uncaught_stream_error, 1), 0); + let _ = crate::os::js_process_on( string_value("uncaughtException").to_bits() as i64, box_pointer(uncaught as *const u8).to_bits() as i64, @@ -978,8 +983,8 @@ fn compose_snapshot_stage_error_reaches_only_composite_listener() { let error = crate::value::js_nanbox_pointer(crate::error::js_error_new_with_message(message) as i64); let options = crate::object::js_object_alloc(0, 1); - let transform = js_closure_alloc(transform_error_callback as *const u8, 1); - crate::closure::js_register_closure_arity(transform_error_callback as *const u8, 3); + let transform = js_closure_alloc(declared_fn!(transform_error_callback, 3), 1); + crate::closure::js_closure_set_capture_f64(transform, 0, error); js_object_set_field_by_name( options, @@ -993,8 +998,8 @@ fn compose_snapshot_stage_error_reaches_only_composite_listener() { args = crate::array::js_array_push_f64(args, stage); let composite = js_node_stream_compose_args(args); - let listener = js_closure_alloc(capture_error_listener as *const u8, 0); - crate::closure::js_register_closure_arity(capture_error_listener as *const u8, 1); + let listener = js_closure_alloc(declared_fn!(capture_error_listener, 1), 0); + let _ = js_node_stream_method_on( raw_ptr_from_value(composite) as i64, string_value("error"), @@ -1020,8 +1025,8 @@ fn compose_snapshot_read_throw_returns_errored_composite() { crate::error::js_error_new_with_message(message.get_raw_mut_ptr()) as i64, )); let options = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 1)); - let read = scope.root_raw_mut_ptr(js_closure_alloc(read_throws as *const u8, 1)); - crate::closure::js_register_closure_arity(read_throws as *const u8, 1); + let read = scope.root_raw_mut_ptr(js_closure_alloc(declared_fn!(read_throws, 1), 1)); + crate::closure::js_closure_set_capture_f64(read.get_raw_mut_ptr(), 0, error.get_nanbox_f64()); js_object_set_field_by_name( options.get_raw_mut_ptr(), @@ -1049,8 +1054,9 @@ fn compose_snapshot_read_throw_returns_errored_composite() { hidden_key(b"__perryStreamComposePriming") )); - let listener = scope.root_raw_mut_ptr(js_closure_alloc(capture_error_listener as *const u8, 0)); - crate::closure::js_register_closure_arity(capture_error_listener as *const u8, 1); + let listener = + scope.root_raw_mut_ptr(js_closure_alloc(declared_fn!(capture_error_listener, 1), 0)); + let error_event = scope.root_nanbox_f64(string_value("error")); let _ = js_node_stream_method_on( raw_ptr_from_value(composite.get_nanbox_f64()) as i64, @@ -1069,8 +1075,8 @@ fn compose_single_transform_returns_stage() { TRANSFORM_THIS_HAS_STREAM_STATE.with(|matches| matches.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let transform_cb = js_closure_alloc(transform_upper_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_upper_callback as *const u8, 3); + let transform_cb = js_closure_alloc(declared_fn!(transform_upper_callback, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -1084,8 +1090,8 @@ fn compose_single_transform_returns_stage() { assert_eq!(composed.to_bits(), upper.to_bits()); let handle = raw_ptr_from_value(composed) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(data_closure, 0, composed); let _ = js_node_stream_method_on( handle, @@ -1115,10 +1121,9 @@ fn transform_flush_callback_pushes_tail_before_finish() { TRANSFORM_FLUSH_COUNT.with(|count| *count.borrow_mut() = 0); let opts = crate::object::js_object_alloc(0, 2); - let transform_cb = js_closure_alloc(transform_identity_callback as *const u8, 0); - let flush_cb = js_closure_alloc(transform_flush_tail_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_identity_callback as *const u8, 3); - crate::closure::js_register_closure_arity(transform_flush_tail_callback as *const u8, 1); + let transform_cb = js_closure_alloc(declared_fn!(transform_identity_callback, 3), 0); + let flush_cb = js_closure_alloc(declared_fn!(transform_flush_tail_callback, 1), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -1132,8 +1137,8 @@ fn transform_flush_callback_pushes_tail_before_finish() { let stream = js_node_stream_transform_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let _ = js_node_stream_method_on( handle, @@ -1168,10 +1173,9 @@ fn piped_transform_flush_callback_pushes_tail_before_finish() { TRANSFORM_FLUSH_COUNT.with(|count| *count.borrow_mut() = 0); let opts = crate::object::js_object_alloc(0, 2); - let transform_cb = js_closure_alloc(transform_identity_callback as *const u8, 0); - let flush_cb = js_closure_alloc(transform_flush_tail_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_identity_callback as *const u8, 3); - crate::closure::js_register_closure_arity(transform_flush_tail_callback as *const u8, 1); + let transform_cb = js_closure_alloc(declared_fn!(transform_identity_callback, 3), 0); + let flush_cb = js_closure_alloc(declared_fn!(transform_flush_tail_callback, 1), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -1186,8 +1190,8 @@ fn piped_transform_flush_callback_pushes_tail_before_finish() { let source = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); let destination = js_node_stream_transform_new(box_pointer(opts as *const u8)); let destination_handle = raw_ptr_from_value(destination) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(data_closure, 0, destination); let _ = js_node_stream_method_on( destination_handle, @@ -1227,8 +1231,8 @@ fn transform_callback_can_push_multiple_outputs_per_input() { READABLE_DATA_CAPTURED.with(|captured| captured.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let transform_cb = js_closure_alloc(transform_push_pair_callback as *const u8, 0); - crate::closure::js_register_closure_arity(transform_push_pair_callback as *const u8, 3); + let transform_cb = js_closure_alloc(declared_fn!(transform_push_pair_callback, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"transform"), @@ -1237,8 +1241,8 @@ fn transform_callback_can_push_multiple_outputs_per_input() { let stream = js_node_stream_transform_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc(declared_fn!(capture_data_listener, 1), 1); + crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let _ = js_node_stream_method_on( handle, @@ -1265,10 +1269,10 @@ fn transform_callback_can_push_multiple_outputs_per_input() { fn readable_options_read_callback_this_is_rebound_to_stream() { let opts = crate::object::js_object_alloc(0, 1); let closure = js_closure_alloc( - read_records_this as *const u8, + declared_fn!(read_records_this, 0), crate::closure::CAPTURES_THIS_FLAG | 1, ); - crate::closure::js_register_closure_arity(read_records_this as *const u8, 0); + crate::closure::js_closure_set_capture_f64(closure, 0, box_pointer(opts as *const u8)); js_object_set_field_by_name( opts, @@ -1510,8 +1514,8 @@ fn writable_cork_and_uncork_update_counter_and_return_undefined() { fn writable_cork_buffers_writes_until_uncorked() { WRITE_CAPTURED.with(|captured| captured.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let closure = js_closure_alloc(write_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); + let closure = js_closure_alloc(declared_fn!(write_capture, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1545,8 +1549,8 @@ fn writable_write_returns_false_at_high_water_mark() { WRITE_CAPTURED.with(|captured| captured.borrow_mut().clear()); PENDING_WRITE_CALLBACK.with(|pending| *pending.borrow_mut() = None); let opts = crate::object::js_object_alloc(0, 2); - let closure = js_closure_alloc(write_capture_pending as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_pending as *const u8, 3); + let closure = js_closure_alloc(declared_fn!(write_capture_pending, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1580,10 +1584,9 @@ fn writable_cork_uses_writev_for_multi_chunk_flush() { WRITEV_BUFFER_SHAPE.with(|shape| shape.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 2); - let write = js_closure_alloc(write_capture as *const u8, 0); - let writev = js_closure_alloc(writev_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); - crate::closure::js_register_closure_arity(writev_capture as *const u8, 2); + let write = js_closure_alloc(declared_fn!(write_capture, 3), 0); + let writev = js_closure_alloc(declared_fn!(writev_capture, 2), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1621,10 +1624,9 @@ fn writable_cork_with_decode_strings_false_preserves_writev_strings() { WRITEV_BUFFER_SHAPE.with(|shape| shape.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 3); - let write = js_closure_alloc(write_capture as *const u8, 0); - let writev = js_closure_alloc(writev_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); - crate::closure::js_register_closure_arity(writev_capture as *const u8, 2); + let write = js_closure_alloc(declared_fn!(write_capture, 3), 0); + let writev = js_closure_alloc(declared_fn!(writev_capture, 2), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1666,8 +1668,8 @@ fn writable_write_after_end_emits_error_without_calling_write() { ERROR_COUNT.with(|count| *count.borrow_mut() = 0); let opts = crate::object::js_object_alloc(0, 1); - let write = js_closure_alloc(write_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); + let write = js_closure_alloc(declared_fn!(write_capture, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1676,8 +1678,8 @@ fn writable_write_after_end_emits_error_without_calling_write() { let stream = js_node_stream_writable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let error = js_closure_alloc(capture_error_listener as *const u8, 0); - crate::closure::js_register_closure_arity(capture_error_listener as *const u8, 1); + let error = js_closure_alloc(declared_fn!(capture_error_listener, 1), 0); + let _ = js_node_stream_method_on( handle, string_value("error"), @@ -1706,8 +1708,8 @@ fn writable_write_callback_error_emits_error_and_destroys() { let err_value = crate::value::js_nanbox_pointer(err as i64); let opts = crate::object::js_object_alloc(0, 1); - let write = js_closure_alloc(write_callback_error as *const u8, 1); - crate::closure::js_register_closure_arity(write_callback_error as *const u8, 3); + let write = js_closure_alloc(declared_fn!(write_callback_error, 3), 1); + crate::closure::js_closure_set_capture_f64(write, 0, err_value); js_object_set_field_by_name( opts, @@ -1717,9 +1719,8 @@ fn writable_write_callback_error_emits_error_and_destroys() { let stream = js_node_stream_writable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - crate::closure::js_register_closure_arity(capture_expected_arg_listener as *const u8, 1); - let error = js_closure_alloc(capture_expected_arg_listener as *const u8, 1); + let error = js_closure_alloc(declared_fn!(capture_expected_arg_listener, 1), 1); crate::closure::js_closure_set_capture_f64(error, 0, err_value); let _ = js_node_stream_method_on( handle, @@ -1727,7 +1728,7 @@ fn writable_write_callback_error_emits_error_and_destroys() { f64::from_bits(JSValue::pointer(error as *const u8).bits()), ); - let write_cb = js_closure_alloc(capture_expected_arg_listener as *const u8, 1); + let write_cb = js_closure_alloc(declared_fn!(capture_expected_arg_listener, 1), 1); crate::closure::js_closure_set_capture_f64(write_cb, 0, err_value); let write_cb_value = f64::from_bits(JSValue::pointer(write_cb as *const u8).bits()); let result = js_node_stream_method_write3( @@ -1758,8 +1759,8 @@ fn writable_write_decodes_string_chunks_and_runs_callback() { WRITE_CALLBACK_COUNT.with(|count| *count.borrow_mut() = 0); let opts = crate::object::js_object_alloc(0, 1); - let write = js_closure_alloc(write_capture_encoding as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_encoding as *const u8, 3); + let write = js_closure_alloc(declared_fn!(write_capture_encoding, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1768,8 +1769,8 @@ fn writable_write_decodes_string_chunks_and_runs_callback() { let stream = js_node_stream_writable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let cb = js_closure_alloc(capture_write_callback as *const u8, 0); - crate::closure::js_register_closure_arity(capture_write_callback as *const u8, 0); + let cb = js_closure_alloc(declared_fn!(capture_write_callback, 0), 0); + let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let undefined = f64::from_bits(TAG_UNDEFINED); @@ -1806,8 +1807,8 @@ fn writable_decode_strings_false_preserves_string_chunks() { WRITE_CALLBACK_COUNT.with(|count| *count.borrow_mut() = 0); let opts = crate::object::js_object_alloc(0, 2); - let write = js_closure_alloc(write_capture_encoding as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_encoding as *const u8, 3); + let write = js_closure_alloc(declared_fn!(write_capture_encoding, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1821,8 +1822,8 @@ fn writable_decode_strings_false_preserves_string_chunks() { let stream = js_node_stream_writable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let cb = js_closure_alloc(capture_write_callback as *const u8, 0); - crate::closure::js_register_closure_arity(capture_write_callback as *const u8, 0); + let cb = js_closure_alloc(declared_fn!(capture_write_callback, 0), 0); + let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let undefined = f64::from_bits(TAG_UNDEFINED); @@ -1856,8 +1857,8 @@ fn writable_buffer_write_passes_buffer_encoding() { WRITE_CAPTURED.with(|captured| captured.borrow_mut().clear()); WRITE_ENCODINGS.with(|encodings| encodings.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 1); - let write = js_closure_alloc(write_capture_encoding as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_encoding as *const u8, 3); + let write = js_closure_alloc(declared_fn!(write_capture_encoding, 3), 0); + js_object_set_field_by_name( opts, hidden_key(b"write"), diff --git a/crates/perry-runtime/src/node_stream_tests_extra.rs b/crates/perry-runtime/src/node_stream_tests_extra.rs index ca63e98b03..4c59c9aaaa 100644 --- a/crates/perry-runtime/src/node_stream_tests_extra.rs +++ b/crates/perry-runtime/src/node_stream_tests_extra.rs @@ -110,7 +110,9 @@ fn stream_event_names_raw_listeners_and_prepend_chainability() { 0 ); - let cb = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); + let cb = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); let returned = unsafe { crate::closure::js_native_call_value( prepend, @@ -179,9 +181,15 @@ fn stream_listener_count_and_listeners_reflect_data_end_storage() { let add_listener = js_object_get_field_by_name_f64(obj, hidden_key(b"addListener")); assert_eq!(on.to_bits(), add_listener.to_bits()); - let cb1 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); - let cb2 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); - let cb3 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); + let cb1 = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); + let cb2 = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); + let cb3 = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); unsafe { let _ = crate::closure::js_native_call_value( @@ -251,8 +259,12 @@ fn stream_listener_count_and_listeners_reflect_data_end_storage() { let native_stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); let native_handle = raw_ptr_from_value(native_stream) as i64; - let cb4 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); - let cb5 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); + let cb4 = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); + let cb5 = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); let _ = js_node_stream_method_on(native_handle, string_value("data"), cb4); let _ = js_node_stream_method_on(native_handle, string_value("data"), cb5); assert_eq!( @@ -281,8 +293,10 @@ fn pipe_and_unpipe_emit_destination_events_with_source() { hidden_key(b"unpipe"), ); - let pipe_listener = js_closure_alloc(capture_expected_arg_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_expected_arg_listener as *const u8, 1); + let pipe_listener = js_closure_alloc( + crate::fn_info!(capture_expected_arg_listener, 1; with_declared(1)), + 1, + ); crate::closure::js_closure_set_capture_f64(pipe_listener, 0, src); let listener = box_pointer(pipe_listener as *const u8); let _ = js_node_stream_method_on(dest_handle, string_value("pipe"), listener); @@ -340,9 +354,13 @@ fn readable_from_pipe_ends_destination() { hidden_key(b"pipe"), ); - let data = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); - let end_closure = js_closure_alloc(capture_end_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_end_listener as *const u8, 0); + let data = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); + let end_closure = js_closure_alloc( + crate::fn_info!(capture_end_listener, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(end_closure, 0, dest); let end = box_pointer(end_closure as *const u8); let _ = js_node_stream_method_on(dest_handle, string_value("data"), data); @@ -385,9 +403,14 @@ fn readable_from_pipe_chain_ends_tail_destination() { hidden_key(b"pipe"), ); - let sink_data = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); - let sink_end_closure = js_closure_alloc(capture_end_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_end_listener as *const u8, 0); + let sink_data = box_pointer(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + ) as *const u8); + let sink_end_closure = js_closure_alloc( + crate::fn_info!(capture_end_listener, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(sink_end_closure, 0, sink); let sink_end = box_pointer(sink_end_closure as *const u8); let _ = js_node_stream_method_on(sink_handle, string_value("data"), sink_data); @@ -462,11 +485,13 @@ fn pause_resume_track_readable_flowing_and_events() { TAG_FALSE ); - let data = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); + let data = box_pointer( + js_closure_alloc(crate::fn_info!(noop_listener, 0; with_declared(0)), 0) as *const u8, + ); let pause_listener = - box_pointer(js_closure_alloc(capture_pause_listener as *const u8, 0) as *const u8); + box_pointer(js_closure_alloc(crate::fn_info!(capture_pause_listener, 0), 0) as *const u8); let resume_listener = - box_pointer(js_closure_alloc(capture_resume_listener as *const u8, 0) as *const u8); + box_pointer(js_closure_alloc(crate::fn_info!(capture_resume_listener, 0), 0) as *const u8); let _ = js_node_stream_method_on(handle, string_value("pause"), pause_listener); let _ = js_node_stream_method_on(handle, string_value("resume"), resume_listener); let _ = js_node_stream_method_on(handle, string_value("data"), data); @@ -543,13 +568,17 @@ fn readable_push_emits_data_with_stream_this_and_deferred_end() { test_install_manual_read(stream); let handle = raw_ptr_from_value(stream) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc( + crate::fn_info!(capture_data_listener, 1; with_declared(1)), + 1, + ); crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let data_listener = box_pointer(data_closure as *const u8); - let end_closure = js_closure_alloc(capture_end_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_end_listener as *const u8, 0); + let end_closure = js_closure_alloc( + crate::fn_info!(capture_end_listener, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(end_closure, 0, stream); let end_listener = box_pointer(end_closure as *const u8); @@ -598,8 +627,10 @@ fn readable_read_default_size_drains_buffer_as_buffer_then_null() { let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); let handle = raw_ptr_from_value(stream) as i64; - let readable_closure = js_closure_alloc(capture_readable_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_readable_listener as *const u8, 0); + let readable_closure = js_closure_alloc( + crate::fn_info!(capture_readable_listener, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(readable_closure, 0, stream); let _ = js_node_stream_method_push(handle, string_value("abc")); @@ -715,8 +746,10 @@ fn destroyed_readable_drops_late_push_data() { let stream = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); let handle = raw_ptr_from_value(stream) as i64; - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let data_closure = js_closure_alloc( + crate::fn_info!(capture_data_listener, 1; with_declared(1)), + 1, + ); crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let data_listener = box_pointer(data_closure as *const u8); @@ -959,8 +992,10 @@ fn writable_backpressure_tracks_length_need_drain_and_drain_event() { PENDING_WRITE_CALLBACK.with(|pending| *pending.borrow_mut() = None); let opts = crate::object::js_object_alloc(0, 2); - let closure = js_closure_alloc(write_capture_pending as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_pending as *const u8, 3); + let closure = js_closure_alloc( + crate::fn_info!(write_capture_pending, 3; with_declared(3)), + 0, + ); js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -971,7 +1006,8 @@ fn writable_backpressure_tracks_length_need_drain_and_drain_event() { let stream = js_node_stream_writable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; let undefined = f64::from_bits(TAG_UNDEFINED); - let drain = box_pointer(js_closure_alloc(capture_drain_listener as *const u8, 0) as *const u8); + let drain = + box_pointer(js_closure_alloc(crate::fn_info!(capture_drain_listener, 0), 0) as *const u8); let _ = js_node_stream_method_on(handle, string_value("drain"), drain); assert_eq!( @@ -1007,8 +1043,7 @@ fn writable_write_returns_false_before_sync_callback_clears_length() { WRITE_CAPTURED.with(|captured| captured.borrow_mut().clear()); let opts = crate::object::js_object_alloc(0, 2); - let closure = js_closure_alloc(write_capture as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); + let closure = js_closure_alloc(crate::fn_info!(write_capture, 3; with_declared(3)), 0); js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1039,8 +1074,10 @@ fn writable_end_waits_for_pending_write_before_finish() { PENDING_WRITE_CALLBACK.with(|pending| *pending.borrow_mut() = None); let opts = crate::object::js_object_alloc(0, 1); - let closure = js_closure_alloc(write_capture_pending as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_pending as *const u8, 3); + let closure = js_closure_alloc( + crate::fn_info!(write_capture_pending, 3; with_declared(3)), + 0, + ); js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1051,8 +1088,9 @@ fn writable_end_waits_for_pending_write_before_finish() { let handle = raw_ptr_from_value(stream) as i64; let undefined = f64::from_bits(TAG_UNDEFINED); let finish = - box_pointer(js_closure_alloc(capture_finish_listener as *const u8, 0) as *const u8); - let close = box_pointer(js_closure_alloc(capture_close_listener as *const u8, 0) as *const u8); + box_pointer(js_closure_alloc(crate::fn_info!(capture_finish_listener, 0), 0) as *const u8); + let close = + box_pointer(js_closure_alloc(crate::fn_info!(capture_close_listener, 0), 0) as *const u8); let _ = js_node_stream_method_on(handle, string_value("finish"), finish); let _ = js_node_stream_method_on(handle, string_value("close"), close); @@ -1084,8 +1122,10 @@ fn writable_end_callback_runs_before_finish_and_close_events() { PENDING_WRITE_CALLBACK.with(|pending| *pending.borrow_mut() = None); let opts = crate::object::js_object_alloc(0, 1); - let closure = js_closure_alloc(write_capture_pending as *const u8, 0); - crate::closure::js_register_closure_arity(write_capture_pending as *const u8, 3); + let closure = js_closure_alloc( + crate::fn_info!(write_capture_pending, 3; with_declared(3)), + 0, + ); js_object_set_field_by_name( opts, hidden_key(b"write"), @@ -1096,9 +1136,10 @@ fn writable_end_callback_runs_before_finish_and_close_events() { let handle = raw_ptr_from_value(stream) as i64; let undefined = f64::from_bits(TAG_UNDEFINED); let finish = - box_pointer(js_closure_alloc(capture_finish_listener as *const u8, 0) as *const u8); - let close = box_pointer(js_closure_alloc(capture_close_listener as *const u8, 0) as *const u8); - let end_cb = js_closure_alloc(capture_end_callback_state as *const u8, 1); + box_pointer(js_closure_alloc(crate::fn_info!(capture_finish_listener, 0), 0) as *const u8); + let close = + box_pointer(js_closure_alloc(crate::fn_info!(capture_close_listener, 0), 0) as *const u8); + let end_cb = js_closure_alloc(crate::fn_info!(capture_end_callback_state, 0), 1); js_closure_set_capture_f64(end_cb, 0, stream); let end_cb_value = box_pointer(end_cb as *const u8); let _ = js_node_stream_method_on(handle, string_value("finish"), finish); @@ -1134,8 +1175,10 @@ fn writable_lifecycle_flags_reflect_end_and_finish() { let scope = crate::gc::RuntimeHandleScope::new(); let opts = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 1)); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(write_capture as *const u8, 0)); - crate::closure::js_register_closure_arity(write_capture as *const u8, 3); + let closure = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(write_capture, 3; with_declared(3)), + 0, + )); js_object_set_field_by_name( opts.get_raw_mut_ptr(), hidden_key(b"write"), @@ -1165,8 +1208,14 @@ fn writable_lifecycle_flags_reflect_end_and_finish() { TAG_FALSE ); - let finish = scope.root_raw_mut_ptr(js_closure_alloc(capture_finish_listener as *const u8, 0)); - let close = scope.root_raw_mut_ptr(js_closure_alloc(capture_close_listener as *const u8, 0)); + let finish = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(capture_finish_listener, 0), + 0, + )); + let close = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(capture_close_listener, 0), + 0, + )); let finish_event = scope.root_nanbox_f64(string_value("finish")); let close_event = scope.root_nanbox_f64(string_value("close")); let _ = js_node_stream_method_on( @@ -1220,7 +1269,8 @@ fn readable_emit_close_false_suppresses_destroy_close_event() { js_object_set_field_by_name(opts, hidden_key(b"emitClose"), f64::from_bits(TAG_FALSE)); let stream = js_node_stream_readable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let close = box_pointer(js_closure_alloc(capture_close_listener as *const u8, 0) as *const u8); + let close = + box_pointer(js_closure_alloc(crate::fn_info!(capture_close_listener, 0), 0) as *const u8); let _ = js_node_stream_method_on(handle, string_value("close"), close); let _ = js_node_stream_method_destroy(handle, f64::from_bits(TAG_UNDEFINED)); @@ -1241,13 +1291,18 @@ fn readable_auto_destroy_false_does_not_close_after_end() { js_object_set_field_by_name(opts, hidden_key(b"autoDestroy"), f64::from_bits(TAG_FALSE)); let stream = js_node_stream_readable_new(box_pointer(opts as *const u8)); let handle = raw_ptr_from_value(stream) as i64; - let close = box_pointer(js_closure_alloc(capture_close_listener as *const u8, 0) as *const u8); - let data_closure = js_closure_alloc(capture_data_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_data_listener as *const u8, 1); + let close = + box_pointer(js_closure_alloc(crate::fn_info!(capture_close_listener, 0), 0) as *const u8); + let data_closure = js_closure_alloc( + crate::fn_info!(capture_data_listener, 1; with_declared(1)), + 1, + ); crate::closure::js_closure_set_capture_f64(data_closure, 0, stream); let data = box_pointer(data_closure as *const u8); - let end_closure = js_closure_alloc(capture_end_listener as *const u8, 1); - crate::closure::js_register_closure_arity(capture_end_listener as *const u8, 0); + let end_closure = js_closure_alloc( + crate::fn_info!(capture_end_listener, 0; with_declared(0)), + 1, + ); crate::closure::js_closure_set_capture_f64(end_closure, 0, stream); let end = box_pointer(end_closure as *const u8); @@ -1275,7 +1330,10 @@ fn stream_destroy_with_error_marks_errored_state() { hidden_key(b"destroy"), )); let err = scope.root_nanbox_f64(string_value("boom")); - let error_listener = scope.root_raw_mut_ptr(js_closure_alloc(noop_listener as *const u8, 0)); + let error_listener = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + )); let error_event = scope.root_nanbox_f64(string_value("error")); let handle = || raw_ptr_from_value(stream.get_nanbox_f64()) as i64; let _ = js_node_stream_method_on( @@ -1352,7 +1410,10 @@ fn readable_aborted_reflects_destroy_before_end() { let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED))); let err = scope.root_nanbox_f64(string_value("abort")); - let error_listener = scope.root_raw_mut_ptr(js_closure_alloc(noop_listener as *const u8, 0)); + let error_listener = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + )); let error_event = scope.root_nanbox_f64(string_value("error")); let handle = || raw_ptr_from_value(stream.get_nanbox_f64()) as i64; let _ = js_node_stream_method_on( @@ -1394,8 +1455,10 @@ fn readable_aborted_reflects_destroy_before_end() { ); let ended = scope.root_nanbox_f64(js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED))); - let ended_error_listener = - scope.root_raw_mut_ptr(js_closure_alloc(noop_listener as *const u8, 0)); + let ended_error_listener = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + )); let ended_handle = || raw_ptr_from_value(ended.get_nanbox_f64()) as i64; let _ = js_node_stream_method_on( ended_handle(), @@ -1418,7 +1481,10 @@ fn stream_native_receiver_methods_update_hidden_state() { ))); let err = scope.root_nanbox_f64(string_value("boom")); let error_event = scope.root_nanbox_f64(string_value("error")); - let emit_listener = scope.root_raw_mut_ptr(js_closure_alloc(noop_listener as *const u8, 0)); + let emit_listener = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + )); let emitting_handle = || raw_ptr_from_value(emitting.get_nanbox_f64()) as i64; let _ = js_node_stream_method_on( emitting_handle(), @@ -1449,7 +1515,10 @@ fn stream_native_receiver_methods_update_hidden_state() { let destroyed = scope.root_nanbox_f64(js_node_stream_passthrough_new(f64::from_bits( TAG_UNDEFINED, ))); - let destroy_listener = scope.root_raw_mut_ptr(js_closure_alloc(noop_listener as *const u8, 0)); + let destroy_listener = scope.root_raw_mut_ptr(js_closure_alloc( + crate::fn_info!(noop_listener, 0; with_declared(0)), + 0, + )); let destroyed_handle = || raw_ptr_from_value(destroyed.get_nanbox_f64()) as i64; let _ = js_node_stream_method_on( destroyed_handle(), @@ -1461,42 +1530,3 @@ fn stream_native_receiver_methods_update_hidden_state() { let _ = crate::promise::js_promise_run_microtasks(); assert!(js_node_stream_hidden_error_after_read(destroyed.get_nanbox_f64()).is_some()); } - -#[test] -fn stream_stub_arities_are_registered_per_thread() { - let _ = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); - assert_eq!( - crate::closure::lookup_closure_arity(ns_end3 as *const u8), - Some(3) - ); - assert_eq!( - crate::closure::lookup_closure_arity(ns_destroy1 as *const u8), - Some(1) - ); - assert_eq!( - crate::closure::lookup_closure_arity(ns_destroy_error_microtask as *const u8), - Some(0) - ); - - std::thread::spawn(|| { - let _ = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); - assert_eq!( - crate::closure::lookup_closure_arity(ns_end3 as *const u8), - Some(3) - ); - assert_eq!( - crate::closure::lookup_closure_arity(ns_destroy1 as *const u8), - Some(1) - ); - assert_eq!( - crate::closure::lookup_closure_arity(ns_destroy_error_microtask as *const u8), - Some(0) - ); - assert_eq!( - crate::closure::lookup_closure_arity(ns_write3 as *const u8), - Some(3) - ); - }) - .join() - .expect("stream arity registration thread should not panic"); -} diff --git a/crates/perry-runtime/src/node_submodules/blob.rs b/crates/perry-runtime/src/node_submodules/blob.rs index b447044bde..abc9999a57 100644 --- a/crates/perry-runtime/src/node_submodules/blob.rs +++ b/crates/perry-runtime/src/node_submodules/blob.rs @@ -10,8 +10,7 @@ use super::consumers::{ }; use super::fs_promises::{promise_rejected, promise_value}; use crate::closure::{ - js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, ClosureHeader, }; use crate::object::{js_object_alloc, js_object_set_field_by_name, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; @@ -232,17 +231,26 @@ extern "C" fn file_blob_stream_get_reader_method( set_named_value( obj, b"read", - file_blob_stream_method_value(file_blob_stream_next_method as *const u8, 0, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_next_method, 0; with_declared(0)), + stream_id, + ), ); set_named_value( obj, b"releaseLock", - file_blob_stream_method_value(file_blob_stream_undefined_method as *const u8, 0, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_undefined_method, 0; with_declared(0)), + stream_id, + ), ); set_named_value( obj, b"cancel", - file_blob_stream_method_value(file_blob_stream_cancel_method as *const u8, 1, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_cancel_method, 1; with_declared(1)), + stream_id, + ), ); set_named_value( obj, @@ -339,38 +347,29 @@ fn object_value(obj: *mut ObjectHeader) -> f64 { f64::from_bits(JSValue::pointer(obj as *const u8).bits()) } -#[allow(clippy::missing_transmute_annotations)] fn blob_method_value( - func: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, backing: *mut crate::buffer::BufferHeader, ) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 1); + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, backing as i64); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -#[allow(clippy::missing_transmute_annotations)] -fn file_blob_method_value(func: *const u8, arity: u32, id: usize) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 1); +fn file_blob_method_value(info: *const crate::closure::JsFunctionInfo, id: usize) -> f64 { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, id as i64); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -#[allow(clippy::missing_transmute_annotations)] -fn file_blob_stream_method_value(func: *const u8, arity: u32, id: usize) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 1); +fn file_blob_stream_method_value(info: *const crate::closure::JsFunctionInfo, id: usize) -> f64 { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, id as i64); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -#[allow(clippy::missing_transmute_annotations)] -fn file_blob_stream_self_method_value(func: *const u8, arity: u32) -> f64 { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, 0); +fn file_blob_stream_self_method_value(info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = js_closure_alloc(info, 0); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } @@ -386,27 +385,42 @@ fn blob_value_from_bytes_and_type(bytes: &[u8], content_type: &str) -> f64 { set_named_value( obj, b"text", - blob_method_value(blob_text_method as *const u8, 0, backing), + blob_method_value( + crate::fn_info!(blob_text_method, 0; with_declared(0)), + backing, + ), ); set_named_value( obj, b"arrayBuffer", - blob_method_value(blob_array_buffer_method as *const u8, 0, backing), + blob_method_value( + crate::fn_info!(blob_array_buffer_method, 0; with_declared(0)), + backing, + ), ); set_named_value( obj, b"bytes", - blob_method_value(blob_bytes_method as *const u8, 0, backing), + blob_method_value( + crate::fn_info!(blob_bytes_method, 0; with_declared(0)), + backing, + ), ); set_named_value( obj, b"slice", - blob_method_value(blob_slice_method as *const u8, 3, backing), + blob_method_value( + crate::fn_info!(blob_slice_method, 3; with_declared(3)), + backing, + ), ); set_named_value( obj, b"stream", - blob_method_value(blob_stream_method as *const u8, 0, backing), + blob_method_value( + crate::fn_info!(blob_stream_method, 0; with_declared(0)), + backing, + ), ); f64::from_bits(JSValue::pointer(obj as *const u8).bits()) } @@ -435,27 +449,42 @@ fn blob_value_from_file_state(state: FileBlobState) -> f64 { set_named_value( obj, b"text", - file_blob_method_value(file_blob_text_method as *const u8, 0, id), + file_blob_method_value( + crate::fn_info!(file_blob_text_method, 0; with_declared(0)), + id, + ), ); set_named_value( obj, b"arrayBuffer", - file_blob_method_value(file_blob_array_buffer_method as *const u8, 0, id), + file_blob_method_value( + crate::fn_info!(file_blob_array_buffer_method, 0; with_declared(0)), + id, + ), ); set_named_value( obj, b"bytes", - file_blob_method_value(file_blob_bytes_method as *const u8, 0, id), + file_blob_method_value( + crate::fn_info!(file_blob_bytes_method, 0; with_declared(0)), + id, + ), ); set_named_value( obj, b"slice", - file_blob_method_value(file_blob_slice_method as *const u8, 3, id), + file_blob_method_value( + crate::fn_info!(file_blob_slice_method, 3; with_declared(3)), + id, + ), ); set_named_value( obj, b"stream", - file_blob_method_value(file_blob_stream_method as *const u8, 0, id), + file_blob_method_value( + crate::fn_info!(file_blob_stream_method, 0; with_declared(0)), + id, + ), ); object_value(obj) } @@ -611,30 +640,40 @@ fn file_blob_stream_value(blob_id: usize) -> f64 { obj, b"getReader", file_blob_stream_method_value( - file_blob_stream_get_reader_method as *const u8, - 0, + crate::fn_info!(file_blob_stream_get_reader_method, 0; with_declared(0)), stream_id, ), ); set_named_value( obj, b"next", - file_blob_stream_method_value(file_blob_stream_next_method as *const u8, 0, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_next_method, 0; with_declared(0)), + stream_id, + ), ); set_named_value( obj, b"return", - file_blob_stream_method_value(file_blob_stream_return_method as *const u8, 0, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_return_method, 0; with_declared(0)), + stream_id, + ), ); set_named_value( obj, b"values", - file_blob_stream_self_method_value(file_blob_stream_values_method as *const u8, 1), + file_blob_stream_self_method_value( + crate::fn_info!(file_blob_stream_values_method, 1; with_declared(1)), + ), ); set_named_value( obj, b"cancel", - file_blob_stream_method_value(file_blob_stream_cancel_method as *const u8, 1, stream_id), + file_blob_stream_method_value( + crate::fn_info!(file_blob_stream_cancel_method, 1; with_declared(1)), + stream_id, + ), ); set_named_value(obj, b"locked", bool_value(false)); let obj_value = object_value(obj); @@ -646,8 +685,7 @@ fn file_blob_stream_value(blob_id: usize) -> f64 { obj_value, sym_value, file_blob_stream_self_method_value( - file_blob_stream_async_iterator_method as *const u8, - 0, + crate::fn_info!(file_blob_stream_async_iterator_method, 0; with_declared(0)), ), ); } diff --git a/crates/perry-runtime/src/node_submodules/consumers.rs b/crates/perry-runtime/src/node_submodules/consumers.rs index 661cf4d002..daffb74f41 100644 --- a/crates/perry-runtime/src/node_submodules/consumers.rs +++ b/crates/perry-runtime/src/node_submodules/consumers.rs @@ -485,8 +485,8 @@ fn call_collector_method( fn collect_by_method_promise(kind: ConsumerKind, receiver: f64, method: CollectMethod) -> f64 { let result_promise = crate::promise::js_promise_new(); let result_arr = crate::array::js_array_alloc(0); - let step = js_closure_alloc(consumer_collect_step as *const u8, 6); - let reject = js_closure_alloc(consumer_collect_rejected as *const u8, 1); + let step = js_closure_alloc(crate::fn_info!(consumer_collect_step, 1), 6); + let reject = js_closure_alloc(crate::fn_info!(consumer_collect_rejected, 1), 1); js_closure_set_capture_ptr(step, 0, result_promise as i64); js_closure_set_capture_ptr(step, 1, result_arr as i64); js_closure_set_capture_f64(step, 2, receiver); diff --git a/crates/perry-runtime/src/node_submodules/diagnostics.rs b/crates/perry-runtime/src/node_submodules/diagnostics.rs index 75d9a70c8e..eb32a3332f 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics.rs @@ -9,10 +9,7 @@ use std::sync::{LazyLock, Mutex, OnceLock}; use std::thread::ThreadId; use crate::array::{js_array_alloc, js_array_get_f64, js_array_length, js_array_push_f64}; -use crate::closure::{ - js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_synthetic_arguments, ClosureHeader, -}; +use crate::closure::{js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader}; use crate::object::ObjectHeader; use crate::string::{js_string_from_bytes, StringHeader}; use crate::thread::SerializedValue; @@ -697,30 +694,11 @@ pub(crate) fn get_field_value(obj: *mut ObjectHeader, name: &str) -> f64 { } } -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast0(f: crate::closure::body_call::js_body_fn_ty!()) -> *const u8 { - f as *const u8 -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast1(f: crate::closure::body_call::js_body_fn_ty!(a)) -> *const u8 { - f as *const u8 -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast2(f: crate::closure::body_call::js_body_fn_ty!(a, a)) -> *const u8 { - f as *const u8 -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast3(f: crate::closure::body_call::js_body_fn_ty!(a, a, a)) -> *const u8 { - f as *const u8 -} -#[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast4(f: crate::closure::body_call::js_body_fn_ty!(a, a, a, a)) -> *const u8 { - f as *const u8 -} -pub(crate) fn method_closure(func: *const u8, arity: u32, id: i64) -> f64 { - let c = js_closure_alloc(func, 1); +/// A method closure capturing the channel `id`; `info` records the method +/// body's declared arity. +pub(crate) fn method_closure(info: *const crate::closure::JsFunctionInfo, id: i64) -> f64 { + let c = js_closure_alloc(info, 1); js_closure_set_capture_ptr(c, 0, id); - js_register_closure_arity(func, arity); boxed_ptr(c) } @@ -874,35 +852,49 @@ pub(crate) fn ensure_channel(name: f64) -> i64 { set_field_value( obj, "subscribe", - method_closure(cast1(diag_channel_subscribe), 1, id), + method_closure( + crate::fn_info!(diag_channel_subscribe, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "unsubscribe", - method_closure(cast1(diag_channel_unsubscribe), 1, id), + method_closure( + crate::fn_info!(diag_channel_unsubscribe, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "publish", - method_closure(cast1(diag_channel_publish), 1, id), + method_closure( + crate::fn_info!(diag_channel_publish, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "bindStore", - method_closure(cast2(diag_channel_bind_store), 2, id), + method_closure( + crate::fn_info!(diag_channel_bind_store, 2; with_declared(2)), + id, + ), ); set_field_value( obj, "unbindStore", - method_closure(cast1(diag_channel_unbind_store), 1, id), + method_closure( + crate::fn_info!(diag_channel_unbind_store, 1; with_declared(1)), + id, + ), ); set_field_value(obj, "runStores", run_stores_method_closure(id)); set_field_value( obj, "withStoreScope", method_closure( - cast1(super::diagnostics_tail::diag_channel_with_store_scope), - 1, + crate::fn_info!(super::diagnostics_tail::diag_channel_with_store_scope, 1; with_declared(1)), id, ), ); @@ -1276,31 +1268,33 @@ pub(crate) extern "C" fn store_chain_thunk( call_store_run(store, context, next) } -/// Build the `runStores` method closure. Registered as a synthetic-arguments +/// Build the `runStores` method closure. Its body is a synthetic-arguments /// rest closure (fixed_arity 0) so the dispatcher bundles the FULL argument /// list — `context`, `callback`, `thisArg`, and every trailing `...args` — into /// a single JS array, regardless of how many arguments the caller passed /// (#3082). The fixed five-argument `cast5` entrypoint used previously capped /// the forwarded callback arguments at two. pub(crate) fn run_stores_method_closure(id: i64) -> f64 { - let func = cast1(diag_channel_run_stores); - let c = js_closure_alloc(func, 1); + let c = js_closure_alloc( + crate::fn_info!(diag_channel_run_stores, 1; with_rest_kind(0, crate::closure::FN_REST_SYNTHETIC_ARGUMENTS)), + 1, + ); js_closure_set_capture_ptr(c, 0, id); - js_register_closure_synthetic_arguments(func, 0); boxed_ptr(c) } -/// Build the `traceCallback` method closure. Like `runStores`, registered as a +/// Build the `traceCallback` method closure. Like `runStores`, its body is a /// synthetic-arguments closure so the FULL argument list — `fn`, `position`, /// `context`, `thisArg`, and every trailing `...args` — is bundled into one JS /// array, letting `traceCallback` honor `position` and forward all surrounding /// arguments (#3086). The fixed seven-argument `cast7` entrypoint used /// previously ignored `position` and dropped extra arguments. pub(crate) fn trace_callback_method_closure(id: i64) -> f64 { - let func = cast1(diag_trace_callback); - let c = js_closure_alloc(func, 1); + let c = js_closure_alloc( + crate::fn_info!(diag_trace_callback, 1; with_rest_kind(0, crate::closure::FN_REST_SYNTHETIC_ARGUMENTS)), + 1, + ); js_closure_set_capture_ptr(c, 0, id); - js_register_closure_synthetic_arguments(func, 0); boxed_ptr(c) } @@ -1365,8 +1359,7 @@ pub(crate) extern "C" fn diag_channel_run_stores( // fixed-width scalar slots, so the variadic tail rides in a single array // handle). let cb_args_arr = unsafe { build_arg_array(&cb_args) }; - let next = js_closure_alloc(cast0(store_next_thunk), 5); - js_register_closure_arity(cast0(store_next_thunk), 0); + let next = js_closure_alloc(crate::fn_info!(store_next_thunk, 0; with_declared(0)), 5); js_closure_set_capture_ptr(next, 0, id); js_closure_set_capture_ptr(next, 1, data.to_bits() as i64); js_closure_set_capture_ptr(next, 2, fn_value.to_bits() as i64); @@ -1395,8 +1388,7 @@ pub(crate) extern "C" fn diag_channel_run_stores( } StoreTransform::None => data, }; - let chain = js_closure_alloc(cast0(store_chain_thunk), 3); - js_register_closure_arity(cast0(store_chain_thunk), 0); + let chain = js_closure_alloc(crate::fn_info!(store_chain_thunk, 0; with_declared(0)), 3); js_closure_set_capture_ptr(chain, 0, store.to_bits() as i64); js_closure_set_capture_ptr(chain, 1, context.to_bits() as i64); js_closure_set_capture_ptr(chain, 2, next_value.to_bits() as i64); @@ -1695,7 +1687,7 @@ pub(crate) extern "C" fn diag_trace_promise( } /// The callback installed in place of the user's callback by -/// `traceCallback`. Registered as a synthetic-arguments closure so it forwards +/// `traceCallback`. Its body is a synthetic-arguments body so it forwards /// every argument the traced function passes (`cb(null, "a", "b")`) to the /// user callback, matching Node's `ReflectApply(callback, this, arguments)` /// (#3086). `arguments[0]` is `err`, `arguments[1]` is `res`. @@ -1810,13 +1802,16 @@ pub(crate) extern "C" fn diag_trace_callback( throw_trace_callback_not_function(); } - let wrapped = js_closure_alloc(diag_trace_wrapped_callback as *const u8, 5); + let wrapped = js_closure_alloc( + crate::fn_info!(diag_trace_wrapped_callback, 1; with_rest_kind(0, crate::closure::FN_REST_SYNTHETIC_ARGUMENTS)), + 5, + ); js_closure_set_capture_f64(wrapped, 0, callback); js_closure_set_capture_f64(wrapped, 1, context); js_closure_set_capture_ptr(wrapped, 2, events[2]); js_closure_set_capture_ptr(wrapped, 3, events[3]); js_closure_set_capture_ptr(wrapped, 4, events[4]); - js_register_closure_synthetic_arguments(diag_trace_wrapped_callback as *const u8, 0); + let wrapped_value = boxed_ptr(wrapped); // Splice the wrapped callback in at the resolved position, preserving all @@ -1886,22 +1881,28 @@ pub(crate) extern "C" fn thunk_diag_tracing_channel( set_field_value( obj, "subscribe", - method_closure(cast1(diag_trace_subscribe), 1, id), + method_closure( + crate::fn_info!(diag_trace_subscribe, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "unsubscribe", - method_closure(cast1(diag_trace_unsubscribe), 1, id), + method_closure( + crate::fn_info!(diag_trace_unsubscribe, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "traceSync", - method_closure(cast4(diag_trace_sync), 4, id), + method_closure(crate::fn_info!(diag_trace_sync, 4; with_declared(4)), id), ); set_field_value( obj, "tracePromise", - method_closure(cast3(diag_trace_promise), 3, id), + method_closure(crate::fn_info!(diag_trace_promise, 3; with_declared(3)), id), ); set_field_value(obj, "traceCallback", trace_callback_method_closure(id)); DIAG_TRACES.with(|m| { @@ -1926,7 +1927,7 @@ pub(crate) fn ensure_diag_noop_closure() -> *mut ClosureHeader { if let Some(ptr) = *slot.borrow() { return ptr; } - let allocated = js_closure_alloc(thunk_diag_noop as *const u8, 0); + let allocated = js_closure_alloc(crate::fn_info!(thunk_diag_noop, 1), 0); *slot.borrow_mut() = Some(allocated); ANY_SINGLETON_ALLOCATED.store(1, Ordering::Release); allocated diff --git a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs index 1fe9815e30..be157a22f1 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::array::{js_array_get_f64, js_array_length}; use crate::closure::{ js_closure_alloc, js_closure_call1, js_closure_get_capture_ptr, js_closure_set_capture_ptr, - js_register_closure_arity, js_register_closure_synthetic_arguments, ClosureHeader, + ClosureHeader, }; use crate::object::{js_object_alloc, ObjectHeader}; use crate::value::{js_nanbox_get_pointer, JSValue}; @@ -170,9 +170,11 @@ pub(crate) extern "C" fn diag_channel_with_store_scope( ); }); let obj = js_object_alloc(0, 3); - let dispose = js_closure_alloc(cast0(diag_store_scope_dispose), 1); + let dispose = js_closure_alloc( + crate::fn_info!(diag_store_scope_dispose, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(dispose, 0, scope_id); - js_register_closure_arity(cast0(diag_store_scope_dispose), 0); let dispose_value = boxed_ptr(dispose); install_dispose(obj, dispose_value); boxed_ptr(obj) @@ -302,21 +304,24 @@ pub(crate) extern "C" fn diag_bounded_with_scope( let events = bounded_events(method_id(closure)); publish_channel(events[0], context); let obj = js_object_alloc(0, 3); - let dispose = js_closure_alloc(cast0(diag_bounded_scope_dispose), 3); + let dispose = js_closure_alloc( + crate::fn_info!(diag_bounded_scope_dispose, 0; with_declared(0)), + 3, + ); js_closure_set_capture_ptr(dispose, 0, events[1]); js_closure_set_capture_ptr(dispose, 1, context.to_bits() as i64); js_closure_set_capture_ptr(dispose, 2, 0); - js_register_closure_arity(cast0(diag_bounded_scope_dispose), 0); let dispose_value = boxed_ptr(dispose); install_dispose(obj, dispose_value); boxed_ptr(obj) } fn bounded_run_method_closure(id: i64) -> f64 { - let func = cast1(diag_bounded_run); - let c = js_closure_alloc(func, 1); + let c = js_closure_alloc( + crate::fn_info!(diag_bounded_run, 1; with_rest_kind(0, crate::closure::FN_REST_SYNTHETIC_ARGUMENTS)), + 1, + ); js_closure_set_capture_ptr(c, 0, id); - js_register_closure_synthetic_arguments(func, 0); boxed_ptr(c) } @@ -349,18 +354,27 @@ pub(crate) extern "C" fn thunk_diag_bounded_channel( set_field_value( obj, "subscribe", - method_closure(cast1(diag_bounded_subscribe), 1, id), + method_closure( + crate::fn_info!(diag_bounded_subscribe, 1; with_declared(1)), + id, + ), ); set_field_value( obj, "unsubscribe", - method_closure(cast1(diag_bounded_unsubscribe), 1, id), + method_closure( + crate::fn_info!(diag_bounded_unsubscribe, 1; with_declared(1)), + id, + ), ); set_field_value(obj, "run", bounded_run_method_closure(id)); set_field_value( obj, "withScope", - method_closure(cast1(diag_bounded_with_scope), 1, id), + method_closure( + crate::fn_info!(diag_bounded_with_scope, 1; with_declared(1)), + id, + ), ); DIAG_BOUNDED_CHANNELS.with(|m| { m.borrow_mut().insert(id, DiagBoundedState { obj, events }); diff --git a/crates/perry-runtime/src/node_submodules/fs_promises.rs b/crates/perry-runtime/src/node_submodules/fs_promises.rs index 1f78e6cab6..5f83b1a1c2 100644 --- a/crates/perry-runtime/src/node_submodules/fs_promises.rs +++ b/crates/perry-runtime/src/node_submodules/fs_promises.rs @@ -9,7 +9,7 @@ use crate::closure::{ get_valid_func_ptr, js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_native_call_value, js_register_closure_arity, ClosureHeader, + js_native_call_value, ClosureHeader, }; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader, @@ -662,32 +662,10 @@ fn promise_ptr_from_value(value: f64) -> Option<*mut crate::promise::Promise> { raw_ptr_from_value(value).map(|raw| raw as *mut crate::promise::Promise) } -fn readline_bound_method0( - func: crate::closure::body_call::js_body_fn_ty!(), - this_value: f64, -) -> f64 { - js_register_closure_arity(func as *const u8, 0); - let closure = js_closure_alloc(func as *const u8, 1); - js_closure_set_capture_f64(closure, 0, this_value); - f64::from_bits(JSValue::pointer(closure as *const u8).bits()) -} - -fn readline_bound_method1( - func: crate::closure::body_call::js_body_fn_ty!(a), - this_value: f64, -) -> f64 { - js_register_closure_arity(func as *const u8, 1); - let closure = js_closure_alloc(func as *const u8, 1); - js_closure_set_capture_f64(closure, 0, this_value); - f64::from_bits(JSValue::pointer(closure as *const u8).bits()) -} - -fn readline_bound_method2( - func: crate::closure::body_call::js_body_fn_ty!(a, a), - this_value: f64, -) -> f64 { - js_register_closure_arity(func as *const u8, 2); - let closure = js_closure_alloc(func as *const u8, 1); +/// A readline method closure bound to `this_value`; `info` records the +/// method body's declared arity. +fn readline_bound_method(info: *const crate::closure::JsFunctionInfo, this_value: f64) -> f64 { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_f64(closure, 0, this_value); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } @@ -791,11 +769,11 @@ fn attach_interface_input(interface: f64, input: f64) { return; }; - let data = js_closure_alloc(readline_promises_input_data as *const u8, 1); + let data = js_closure_alloc(crate::fn_info!(readline_promises_input_data, 1), 1); js_closure_set_capture_f64(data, 0, interface); let data_value = f64::from_bits(JSValue::pointer(data as *const u8).bits()); - let close = js_closure_alloc(readline_promises_input_close as *const u8, 1); + let close = js_closure_alloc(crate::fn_info!(readline_promises_input_close, 0), 1); js_closure_set_capture_f64(close, 0, interface); let close_value = f64::from_bits(JSValue::pointer(close as *const u8).bits()); @@ -836,7 +814,8 @@ extern "C" fn readline_promises_question( return promise_value; } - let listener = js_closure_alloc(readline_promises_abort_question as *const u8, 1); + let listener = + js_closure_alloc(crate::fn_info!(readline_promises_abort_question, 0), 1); js_closure_set_capture_f64(listener, 0, interface); let listener_value = f64::from_bits(JSValue::pointer(listener as *const u8).bits()); set_value_field(interface, RL_IF_ABORT_SIGNAL, signal); @@ -877,12 +856,18 @@ fn readline_promises_create_interface(opts: f64) -> f64 { set_object_field_value( obj, b"close", - readline_bound_method0(readline_promises_close, obj_value), + readline_bound_method( + crate::fn_info!(readline_promises_close, 0; with_declared(0)), + obj_value, + ), ); set_object_field_value( obj, b"question", - readline_bound_method2(readline_promises_question, obj_value), + readline_bound_method( + crate::fn_info!(readline_promises_question, 2; with_declared(2)), + obj_value, + ), ); attach_interface_input(obj_value, input); @@ -955,7 +940,7 @@ fn schedule_readline_auto_commit(value: f64) { return; } set_value_field(value, RL_ACTION_AUTO_PENDING, bool_value(true)); - let callback = js_closure_alloc(readline_auto_commit_callback as *const u8, 1); + let callback = js_closure_alloc(crate::fn_info!(readline_auto_commit_callback, 0), 1); js_closure_set_capture_f64(callback, 0, value); crate::timer::js_set_immediate_callback(callback as i64); } @@ -1068,32 +1053,50 @@ pub extern "C" fn js_readline_promises_readline_new(output: f64, options: f64) - set_object_field_value( obj, b"clearLine", - readline_bound_method1(readline_action_clear_line, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_clear_line, 1; with_declared(1)), + obj_value, + ), ); set_object_field_value( obj, b"clearScreenDown", - readline_bound_method0(readline_action_clear_screen_down, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_clear_screen_down, 0; with_declared(0)), + obj_value, + ), ); set_object_field_value( obj, b"cursorTo", - readline_bound_method2(readline_action_cursor_to, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_cursor_to, 2; with_declared(2)), + obj_value, + ), ); set_object_field_value( obj, b"moveCursor", - readline_bound_method2(readline_action_move_cursor, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_move_cursor, 2; with_declared(2)), + obj_value, + ), ); set_object_field_value( obj, b"commit", - readline_bound_method0(readline_action_commit, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_commit, 0; with_declared(0)), + obj_value, + ), ); set_object_field_value( obj, b"rollback", - readline_bound_method0(readline_action_rollback, obj_value), + readline_bound_method( + crate::fn_info!(readline_action_rollback, 0; with_declared(0)), + obj_value, + ), ); obj_value } diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index f876c2739c..57e9e8fcc6 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -25,9 +25,9 @@ use std::sync::atomic::{AtomicI64, Ordering}; use crate::closure::{ js_closure_alloc, js_closure_call0, js_closure_call1, js_closure_call2, - js_closure_get_capture_ptr, js_closure_set_capture_ptr, js_register_closure_arity, - ClosureHeader, + js_closure_get_capture_ptr, js_closure_set_capture_ptr, ClosureHeader, JsFunctionInfo, }; +use crate::codegen_abi::{JsBody1, JsBody2, JsBody3}; use crate::object::{ js_object_alloc, js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader, PropertyAttrs, @@ -41,33 +41,11 @@ pub use diagnostics::*; pub(crate) mod diagnostics_tail; pub(crate) use diagnostics_tail::*; -/// One entry per named export of one submodule. +/// One entry per named export of one submodule: its name and its thunk's +/// info (the declared arity, or the rest parameter of a variadic export). struct ExportSpec { name: &'static str, - thunk: ExportThunk, -} - -enum ExportThunk { - Fn1(crate::closure::body_call::js_body_fn_ty!(a)), - Fn2(crate::closure::body_call::js_body_fn_ty!(a, a)), - Fn3(crate::closure::body_call::js_body_fn_ty!(a, a, a)), -} - -impl ExportThunk { - fn as_ptr(&self) -> *const u8 { - match self { - ExportThunk::Fn1(f) => *f as *const u8, - ExportThunk::Fn2(f) => *f as *const u8, - ExportThunk::Fn3(f) => *f as *const u8, - } - } - fn arity(&self) -> u32 { - match self { - ExportThunk::Fn1(_) => 1, - ExportThunk::Fn2(_) => 2, - ExportThunk::Fn3(_) => 3, - } - } + info: JsFunctionInfo, } /// One entry per submodule. `exports` lists every named export the @@ -212,7 +190,8 @@ static SUBMOD_VM: SubmoduleSpec = SubmoduleSpec { key: "vm", exports: &[ExportSpec { name: "createContext", - thunk: ExportThunk::Fn2(thunk_vm_create_context), + info: JsFunctionInfo::of(thunk_vm_create_context as JsBody2) + .with_declared(2), }], }; @@ -223,27 +202,31 @@ static SUBMOD_TIMERS: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "setTimeout", - thunk: ExportThunk::Fn3(timers_ns_set_timeout), + info: JsFunctionInfo::of(timers_ns_set_timeout as JsBody3).with_rest(2), }, ExportSpec { name: "setInterval", - thunk: ExportThunk::Fn3(timers_ns_set_interval), + info: JsFunctionInfo::of(timers_ns_set_interval as JsBody3).with_rest(2), }, ExportSpec { name: "setImmediate", - thunk: ExportThunk::Fn2(timers_ns_set_immediate), + info: JsFunctionInfo::of(timers_ns_set_immediate as JsBody2) + .with_rest(1), }, ExportSpec { name: "clearTimeout", - thunk: ExportThunk::Fn1(timers_ns_clear_timeout), + info: JsFunctionInfo::of(timers_ns_clear_timeout as JsBody1) + .with_declared(1), }, ExportSpec { name: "clearInterval", - thunk: ExportThunk::Fn1(timers_ns_clear_interval), + info: JsFunctionInfo::of(timers_ns_clear_interval as JsBody1) + .with_declared(1), }, ExportSpec { name: "clearImmediate", - thunk: ExportThunk::Fn1(timers_ns_clear_immediate), + info: JsFunctionInfo::of(timers_ns_clear_immediate as JsBody1) + .with_declared(1), }, ], }; @@ -253,19 +236,23 @@ static SUBMOD_TIMERS_PROMISES: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "setTimeout", - thunk: ExportThunk::Fn3(timers_promises_set_timeout), + info: JsFunctionInfo::of(timers_promises_set_timeout as JsBody3) + .with_declared(3), }, ExportSpec { name: "setImmediate", - thunk: ExportThunk::Fn2(timers_promises_set_immediate), + info: JsFunctionInfo::of(timers_promises_set_immediate as JsBody2) + .with_declared(2), }, ExportSpec { name: "setInterval", - thunk: ExportThunk::Fn3(timers_promises_set_interval), + info: JsFunctionInfo::of(timers_promises_set_interval as JsBody3) + .with_declared(3), }, ExportSpec { name: "scheduler", - thunk: ExportThunk::Fn1(timers_promises_scheduler), + info: JsFunctionInfo::of(timers_promises_scheduler as JsBody1) + .with_declared(1), }, ], }; @@ -275,135 +262,168 @@ static SUBMOD_FS_PROMISES: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "readFile", - thunk: ExportThunk::Fn2(thunk_fs_promises_readFile), + info: JsFunctionInfo::of(thunk_fs_promises_readFile as JsBody2) + .with_declared(2), }, ExportSpec { name: "open", - thunk: ExportThunk::Fn3(thunk_fs_promises_open), + info: JsFunctionInfo::of(thunk_fs_promises_open as JsBody3) + .with_declared(3), }, ExportSpec { name: "writeFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_writeFile), + info: JsFunctionInfo::of(thunk_fs_promises_writeFile as JsBody3) + .with_declared(3), }, ExportSpec { name: "appendFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_appendFile), + info: JsFunctionInfo::of(thunk_fs_promises_appendFile as JsBody3) + .with_declared(3), }, ExportSpec { name: "chmod", - thunk: ExportThunk::Fn2(thunk_fs_promises_chmod), + info: JsFunctionInfo::of(thunk_fs_promises_chmod as JsBody2) + .with_declared(2), }, ExportSpec { name: "chown", - thunk: ExportThunk::Fn3(thunk_fs_promises_chown), + info: JsFunctionInfo::of(thunk_fs_promises_chown as JsBody3) + .with_declared(3), }, ExportSpec { name: "lchown", - thunk: ExportThunk::Fn3(thunk_fs_promises_lchown), + info: JsFunctionInfo::of(thunk_fs_promises_lchown as JsBody3) + .with_declared(3), }, ExportSpec { name: "lchmod", - thunk: ExportThunk::Fn2(thunk_fs_promises_lchmod), + info: JsFunctionInfo::of(thunk_fs_promises_lchmod as JsBody2) + .with_declared(2), }, ExportSpec { name: "mkdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdir), + info: JsFunctionInfo::of(thunk_fs_promises_mkdir as JsBody2) + .with_declared(2), }, ExportSpec { name: "readdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_readdir), + info: JsFunctionInfo::of(thunk_fs_promises_readdir as JsBody2) + .with_declared(2), }, ExportSpec { name: "stat", - thunk: ExportThunk::Fn2(thunk_fs_promises_stat), + info: JsFunctionInfo::of(thunk_fs_promises_stat as JsBody2) + .with_declared(2), }, ExportSpec { name: "statfs", - thunk: ExportThunk::Fn2(thunk_fs_promises_statfs), + info: JsFunctionInfo::of(thunk_fs_promises_statfs as JsBody2) + .with_declared(2), }, ExportSpec { name: "lstat", - thunk: ExportThunk::Fn2(thunk_fs_promises_lstat), + info: JsFunctionInfo::of(thunk_fs_promises_lstat as JsBody2) + .with_declared(2), }, ExportSpec { name: "rm", - thunk: ExportThunk::Fn2(thunk_fs_promises_rm), + info: JsFunctionInfo::of(thunk_fs_promises_rm as JsBody2) + .with_declared(2), }, ExportSpec { name: "rmdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_rmdir), + info: JsFunctionInfo::of(thunk_fs_promises_rmdir as JsBody2) + .with_declared(2), }, ExportSpec { name: "unlink", - thunk: ExportThunk::Fn1(thunk_fs_promises_unlink), + info: JsFunctionInfo::of(thunk_fs_promises_unlink as JsBody1) + .with_declared(1), }, ExportSpec { name: "rename", - thunk: ExportThunk::Fn2(thunk_fs_promises_rename), + info: JsFunctionInfo::of(thunk_fs_promises_rename as JsBody2) + .with_declared(2), }, ExportSpec { name: "copyFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_copyFile), + info: JsFunctionInfo::of(thunk_fs_promises_copyFile as JsBody3) + .with_declared(3), }, ExportSpec { name: "cp", - thunk: ExportThunk::Fn3(thunk_fs_promises_cp), + info: JsFunctionInfo::of(thunk_fs_promises_cp as JsBody3) + .with_declared(3), }, ExportSpec { name: "truncate", - thunk: ExportThunk::Fn2(thunk_fs_promises_truncate), + info: JsFunctionInfo::of(thunk_fs_promises_truncate as JsBody2) + .with_declared(2), }, ExportSpec { name: "utimes", - thunk: ExportThunk::Fn3(thunk_fs_promises_utimes), + info: JsFunctionInfo::of(thunk_fs_promises_utimes as JsBody3) + .with_declared(3), }, ExportSpec { name: "lutimes", - thunk: ExportThunk::Fn3(thunk_fs_promises_lutimes), + info: JsFunctionInfo::of(thunk_fs_promises_lutimes as JsBody3) + .with_declared(3), }, ExportSpec { name: "link", - thunk: ExportThunk::Fn2(thunk_fs_promises_link), + info: JsFunctionInfo::of(thunk_fs_promises_link as JsBody2) + .with_declared(2), }, ExportSpec { name: "symlink", - thunk: ExportThunk::Fn3(thunk_fs_promises_symlink), + info: JsFunctionInfo::of(thunk_fs_promises_symlink as JsBody3) + .with_declared(3), }, ExportSpec { name: "readlink", - thunk: ExportThunk::Fn2(thunk_fs_promises_readlink), + info: JsFunctionInfo::of(thunk_fs_promises_readlink as JsBody2) + .with_declared(2), }, ExportSpec { name: "realpath", - thunk: ExportThunk::Fn2(thunk_fs_promises_realpath), + info: JsFunctionInfo::of(thunk_fs_promises_realpath as JsBody2) + .with_declared(2), }, ExportSpec { name: "mkdtemp", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtemp), + info: JsFunctionInfo::of(thunk_fs_promises_mkdtemp as JsBody2) + .with_declared(2), }, ExportSpec { name: "mkdtempDisposable", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtempDisposable), + info: JsFunctionInfo::of(thunk_fs_promises_mkdtempDisposable as JsBody2) + .with_declared(2), }, ExportSpec { name: "opendir", - thunk: ExportThunk::Fn1(thunk_fs_promises_opendir), + info: JsFunctionInfo::of(thunk_fs_promises_opendir as JsBody1) + .with_declared(1), }, ExportSpec { name: "glob", - thunk: ExportThunk::Fn2(thunk_fs_promises_glob), + info: JsFunctionInfo::of(thunk_fs_promises_glob as JsBody2) + .with_declared(2), }, ExportSpec { name: "watch", - thunk: ExportThunk::Fn2(thunk_fs_promises_watch), + info: JsFunctionInfo::of(thunk_fs_promises_watch as JsBody2) + .with_declared(2), }, ExportSpec { name: "access", - thunk: ExportThunk::Fn2(thunk_fs_promises_access), + info: JsFunctionInfo::of(thunk_fs_promises_access as JsBody2) + .with_declared(2), }, ExportSpec { name: "constants", - thunk: ExportThunk::Fn1(thunk_fs_promises_constants), + info: JsFunctionInfo::of(thunk_fs_promises_constants as JsBody1) + .with_declared(1), }, ], }; @@ -413,15 +433,18 @@ static SUBMOD_READLINE_PROMISES: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "createInterface", - thunk: ExportThunk::Fn1(thunk_readline_createInterface), + info: JsFunctionInfo::of(thunk_readline_createInterface as JsBody1) + .with_declared(1), }, ExportSpec { name: "Interface", - thunk: ExportThunk::Fn1(thunk_readline_Interface), + info: JsFunctionInfo::of(thunk_readline_Interface as JsBody1) + .with_declared(1), }, ExportSpec { name: "Readline", - thunk: ExportThunk::Fn2(thunk_readline_Readline), + info: JsFunctionInfo::of(thunk_readline_Readline as JsBody2) + .with_declared(2), }, ], }; @@ -431,11 +454,12 @@ static SUBMOD_STREAM_PROMISES: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "pipeline", - thunk: ExportThunk::Fn3(thunk_streamP_pipeline), + info: JsFunctionInfo::of(thunk_streamP_pipeline as JsBody3).with_rest(2), }, ExportSpec { name: "finished", - thunk: ExportThunk::Fn2(thunk_streamP_finished), + info: JsFunctionInfo::of(thunk_streamP_finished as JsBody2) + .with_declared(2), }, ], }; @@ -445,27 +469,33 @@ static SUBMOD_STREAM_CONSUMERS: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "text", - thunk: ExportThunk::Fn1(thunk_consumers_text), + info: JsFunctionInfo::of(thunk_consumers_text as JsBody1) + .with_declared(1), }, ExportSpec { name: "json", - thunk: ExportThunk::Fn1(thunk_consumers_json), + info: JsFunctionInfo::of(thunk_consumers_json as JsBody1) + .with_declared(1), }, ExportSpec { name: "buffer", - thunk: ExportThunk::Fn1(thunk_consumers_buffer), + info: JsFunctionInfo::of(thunk_consumers_buffer as JsBody1) + .with_declared(1), }, ExportSpec { name: "arrayBuffer", - thunk: ExportThunk::Fn1(thunk_consumers_arrayBuffer), + info: JsFunctionInfo::of(thunk_consumers_arrayBuffer as JsBody1) + .with_declared(1), }, ExportSpec { name: "bytes", - thunk: ExportThunk::Fn1(thunk_consumers_bytes), + info: JsFunctionInfo::of(thunk_consumers_bytes as JsBody1) + .with_declared(1), }, ExportSpec { name: "blob", - thunk: ExportThunk::Fn1(thunk_consumers_blob), + info: JsFunctionInfo::of(thunk_consumers_blob as JsBody1) + .with_declared(1), }, ], }; @@ -475,71 +505,88 @@ static SUBMOD_STREAM_WEB: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "ReadableStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ReadableStreamDefaultReader", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ReadableStreamBYOBReader", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ReadableStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ReadableByteStreamController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ReadableStreamBYOBRequest", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "WritableStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "WritableStreamDefaultWriter", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "WritableStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "TransformStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "TransformStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "ByteLengthQueuingStrategy", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "CountQueuingStrategy", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "TextEncoderStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "TextDecoderStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "CompressionStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ExportSpec { name: "DecompressionStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + info: JsFunctionInfo::of(thunk_stream_web_ctor as JsBody1) + .with_declared(1), }, ], }; @@ -549,19 +596,20 @@ static SUBMOD_HONO_JSX_SERVER: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "jsx", - thunk: ExportThunk::Fn2(thunk_hono_jsx), + info: JsFunctionInfo::of(thunk_hono_jsx as JsBody2).with_declared(2), }, ExportSpec { name: "jsxs", - thunk: ExportThunk::Fn2(thunk_hono_jsxs), + info: JsFunctionInfo::of(thunk_hono_jsxs as JsBody2).with_declared(2), }, ExportSpec { name: "Fragment", - thunk: ExportThunk::Fn1(thunk_hono_fragment), + info: JsFunctionInfo::of(thunk_hono_fragment as JsBody1) + .with_declared(1), }, ExportSpec { name: "JSXNode", - thunk: ExportThunk::Fn1(thunk_hono_jsxnode), + info: JsFunctionInfo::of(thunk_hono_jsxnode as JsBody1).with_declared(1), }, ], }; @@ -571,11 +619,15 @@ static SUBMOD_HONO_JSX_STREAMING: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "renderToReadableStream", - thunk: ExportThunk::Fn2(thunk_hono_render_to_readable_stream), + info: JsFunctionInfo::of( + thunk_hono_render_to_readable_stream as JsBody2, + ) + .with_declared(2), }, ExportSpec { name: "Suspense", - thunk: ExportThunk::Fn1(thunk_hono_suspense), + info: JsFunctionInfo::of(thunk_hono_suspense as JsBody1) + .with_declared(1), }, ], }; @@ -585,31 +637,34 @@ static SUBMOD_SYS: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "format", - thunk: ExportThunk::Fn1(thunk_sys_format), + info: JsFunctionInfo::of(thunk_sys_format as JsBody1).with_declared(1), }, ExportSpec { name: "inspect", - thunk: ExportThunk::Fn1(thunk_sys_inspect), + info: JsFunctionInfo::of(thunk_sys_inspect as JsBody1).with_declared(1), }, ExportSpec { name: "debuglog", - thunk: ExportThunk::Fn1(thunk_sys_debuglog), + info: JsFunctionInfo::of(thunk_sys_debuglog as JsBody1).with_declared(1), }, ExportSpec { name: "deprecate", - thunk: ExportThunk::Fn1(thunk_sys_deprecate), + info: JsFunctionInfo::of(thunk_sys_deprecate as JsBody1) + .with_declared(1), }, ExportSpec { name: "promisify", - thunk: ExportThunk::Fn1(thunk_sys_promisify), + info: JsFunctionInfo::of(thunk_sys_promisify as JsBody1) + .with_declared(1), }, ExportSpec { name: "callbackify", - thunk: ExportThunk::Fn1(thunk_sys_callbackify), + info: JsFunctionInfo::of(thunk_sys_callbackify as JsBody1) + .with_declared(1), }, ExportSpec { name: "isArray", - thunk: ExportThunk::Fn1(thunk_sys_isArray), + info: JsFunctionInfo::of(thunk_sys_isArray as JsBody1).with_declared(1), }, ], }; @@ -619,35 +674,41 @@ static SUBMOD_DIAGNOSTICS_CHANNEL: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "tracingChannel", - thunk: ExportThunk::Fn1(thunk_diag_tracing_channel), + info: JsFunctionInfo::of(thunk_diag_tracing_channel as JsBody1) + .with_declared(1), }, ExportSpec { name: "boundedChannel", - thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), + info: JsFunctionInfo::of(thunk_diag_bounded_channel as JsBody1) + .with_declared(1), }, ExportSpec { name: "channel", - thunk: ExportThunk::Fn1(thunk_diag_channel), + info: JsFunctionInfo::of(thunk_diag_channel as JsBody1).with_declared(1), }, ExportSpec { name: "subscribe", - thunk: ExportThunk::Fn2(thunk_diag_subscribe), + info: JsFunctionInfo::of(thunk_diag_subscribe as JsBody2) + .with_declared(2), }, ExportSpec { name: "unsubscribe", - thunk: ExportThunk::Fn2(thunk_diag_unsubscribe), + info: JsFunctionInfo::of(thunk_diag_unsubscribe as JsBody2) + .with_declared(2), }, ExportSpec { name: "hasSubscribers", - thunk: ExportThunk::Fn1(thunk_diag_has_subscribers), + info: JsFunctionInfo::of(thunk_diag_has_subscribers as JsBody1) + .with_declared(1), }, ExportSpec { name: "Channel", - thunk: ExportThunk::Fn1(thunk_diag_noop), + info: JsFunctionInfo::of(thunk_diag_noop as JsBody1).with_declared(1), }, ExportSpec { name: "BoundedChannel", - thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), + info: JsFunctionInfo::of(thunk_diag_bounded_channel as JsBody1) + .with_declared(1), }, ], }; @@ -657,11 +718,15 @@ static SUBMOD_TRACE_EVENTS: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "createTracing", - thunk: ExportThunk::Fn1(thunk_trace_events_createTracing), + info: JsFunctionInfo::of(thunk_trace_events_createTracing as JsBody1) + .with_declared(1), }, ExportSpec { name: "getEnabledCategories", - thunk: ExportThunk::Fn1(thunk_trace_events_getEnabledCategories), + info: JsFunctionInfo::of( + thunk_trace_events_getEnabledCategories as JsBody1, + ) + .with_rest(0), }, ], }; @@ -672,64 +737,66 @@ static SUBMOD_TEST: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "default", - thunk: ExportThunk::Fn3(thunk_test), + info: JsFunctionInfo::of(thunk_test as JsBody3).with_declared(3), }, ExportSpec { name: "test", - thunk: ExportThunk::Fn3(thunk_test), + info: JsFunctionInfo::of(thunk_test as JsBody3).with_declared(3), }, ExportSpec { name: "skip", - thunk: ExportThunk::Fn3(thunk_test_skip), + info: JsFunctionInfo::of(thunk_test_skip as JsBody3).with_declared(3), }, ExportSpec { name: "todo", - thunk: ExportThunk::Fn3(thunk_test_todo), + info: JsFunctionInfo::of(thunk_test_todo as JsBody3).with_declared(3), }, ExportSpec { name: "only", - thunk: ExportThunk::Fn3(thunk_test_only), + info: JsFunctionInfo::of(thunk_test_only as JsBody3).with_declared(3), }, ExportSpec { name: "suite", - thunk: ExportThunk::Fn3(thunk_test_suite), + info: JsFunctionInfo::of(thunk_test_suite as JsBody3).with_declared(3), }, ExportSpec { name: "describe", - thunk: ExportThunk::Fn3(thunk_test_suite), + info: JsFunctionInfo::of(thunk_test_suite as JsBody3).with_declared(3), }, ExportSpec { name: "it", - thunk: ExportThunk::Fn3(thunk_test), + info: JsFunctionInfo::of(thunk_test as JsBody3).with_declared(3), }, ExportSpec { name: "before", - thunk: ExportThunk::Fn1(thunk_test_before), + info: JsFunctionInfo::of(thunk_test_before as JsBody1).with_declared(1), }, ExportSpec { name: "after", - thunk: ExportThunk::Fn1(thunk_test_after), + info: JsFunctionInfo::of(thunk_test_after as JsBody1).with_declared(1), }, ExportSpec { name: "beforeEach", - thunk: ExportThunk::Fn1(thunk_test_before_each), + info: JsFunctionInfo::of(thunk_test_before_each as JsBody1) + .with_declared(1), }, ExportSpec { name: "afterEach", - thunk: ExportThunk::Fn1(thunk_test_after_each), + info: JsFunctionInfo::of(thunk_test_after_each as JsBody1) + .with_declared(1), }, ExportSpec { name: "run", - thunk: ExportThunk::Fn1(thunk_test_run), + info: JsFunctionInfo::of(thunk_test_run as JsBody1).with_declared(1), }, // Object-valued exports are handled by `special_export_value`. ExportSpec { name: "mock", - thunk: ExportThunk::Fn1(thunk_test_run), + info: JsFunctionInfo::of(thunk_test_run as JsBody1).with_declared(1), }, ExportSpec { name: "snapshot", - thunk: ExportThunk::Fn1(thunk_test_run), + info: JsFunctionInfo::of(thunk_test_run as JsBody1).with_declared(1), }, ], }; @@ -740,23 +807,26 @@ static SUBMOD_TEST_REPORTERS: SubmoduleSpec = SubmoduleSpec { exports: &[ ExportSpec { name: "spec", - thunk: ExportThunk::Fn1(thunk_reporter_spec), + info: JsFunctionInfo::of(thunk_reporter_spec as JsBody1) + .with_declared(1), }, ExportSpec { name: "tap", - thunk: ExportThunk::Fn1(thunk_reporter_tap), + info: JsFunctionInfo::of(thunk_reporter_tap as JsBody1).with_declared(1), }, ExportSpec { name: "dot", - thunk: ExportThunk::Fn1(thunk_reporter_dot), + info: JsFunctionInfo::of(thunk_reporter_dot as JsBody1).with_declared(1), }, ExportSpec { name: "junit", - thunk: ExportThunk::Fn1(thunk_reporter_junit), + info: JsFunctionInfo::of(thunk_reporter_junit as JsBody1) + .with_declared(1), }, ExportSpec { name: "lcov", - thunk: ExportThunk::Fn1(thunk_reporter_lcov), + info: JsFunctionInfo::of(thunk_reporter_lcov as JsBody1) + .with_declared(1), }, ], }; @@ -1113,16 +1183,22 @@ fn timers_promises_scheduler_value() -> f64 { let obj = js_object_alloc(0, 2); - let wait = js_closure_alloc(timers_promises_scheduler_wait as *const u8, 0); - crate::closure::js_register_closure_arity(timers_promises_scheduler_wait as *const u8, 2); + let wait = js_closure_alloc( + crate::fn_info!(timers_promises_scheduler_wait, 2; with_declared(2)), + 0, + ); + set_named_value( obj, "wait", f64::from_bits(JSValue::pointer(wait as *const u8).bits()), ); - let yield_fn = js_closure_alloc(timers_promises_scheduler_yield as *const u8, 0); - crate::closure::js_register_closure_arity(timers_promises_scheduler_yield as *const u8, 0); + let yield_fn = js_closure_alloc( + crate::fn_info!(timers_promises_scheduler_yield, 0; with_declared(0)), + 0, + ); + set_named_value( obj, "yield", @@ -1220,27 +1296,24 @@ fn ensure_export_singleton( if let Some(cached) = EXPORT_SINGLETONS.with(|m| m.borrow().get(&key).copied()) { return cached; } - let thunk_ptr = export.thunk.as_ptr(); - let allocated = js_closure_alloc(thunk_ptr, 0); - if let Some(fixed_arity) = export_rest_fixed_arity(submod.key, export.name) { - crate::closure::js_register_closure_rest(thunk_ptr, fixed_arity); - } else { - // Arity is encoded in the ExportThunk variant, so the closure dispatch - // pads missing args with undefined for variadic-friendly thunks. This - // replaces the per-submodule arity tables in earlier revisions. - crate::closure::js_register_closure_arity(thunk_ptr, export.thunk.arity()); - } + let allocated = js_closure_alloc(&export.info, 0); if submod.key == "timers_promises" && export.name == "scheduler" { - let wait = js_closure_alloc(timers_promises_scheduler_wait as *const u8, 0); - crate::closure::js_register_closure_arity(timers_promises_scheduler_wait as *const u8, 2); + let wait = js_closure_alloc( + crate::fn_info!(timers_promises_scheduler_wait, 2; with_declared(2)), + 0, + ); + crate::closure::closure_set_dynamic_prop( allocated as usize, "wait", f64::from_bits(JSValue::pointer(wait as *const u8).bits()), ); - let yield_fn = js_closure_alloc(timers_promises_scheduler_yield as *const u8, 0); - crate::closure::js_register_closure_arity(timers_promises_scheduler_yield as *const u8, 0); + let yield_fn = js_closure_alloc( + crate::fn_info!(timers_promises_scheduler_yield, 0; with_declared(0)), + 0, + ); + crate::closure::closure_set_dynamic_prop( allocated as usize, "yield", @@ -1256,7 +1329,7 @@ fn ensure_export_singleton( ); crate::object::set_builtin_closure_length( allocated_handle.get_raw_mut_ptr::() as usize, - export_rest_fixed_arity(submod.key, export.name).unwrap_or(export.thunk.arity()), + crate::closure::info_arity(&export.info).unwrap_or(0), ); crate::object::set_builtin_closure_non_constructable( allocated_handle.get_raw_mut_ptr::() as usize, @@ -1275,16 +1348,6 @@ fn ensure_export_singleton( allocated } -fn export_rest_fixed_arity(submod_key: &str, export_name: &str) -> Option { - match (submod_key, export_name) { - ("stream_promises", "pipeline") => Some(2), - ("timers", "setTimeout" | "setInterval") => Some(2), - ("timers", "setImmediate") => Some(1), - ("trace_events", "getEnabledCategories") => Some(0), - _ => None, - } -} - fn submodule_has_default_object(submod_key: &str) -> bool { matches!( submod_key, diff --git a/crates/perry-runtime/src/node_submodules/stream_promises.rs b/crates/perry-runtime/src/node_submodules/stream_promises.rs index 59d409d09f..f36b300902 100644 --- a/crates/perry-runtime/src/node_submodules/stream_promises.rs +++ b/crates/perry-runtime/src/node_submodules/stream_promises.rs @@ -196,7 +196,7 @@ pub(crate) fn register_abort_listener(signal: f64, promise: *mut crate::promise: let Some(signal_obj) = object_ptr_from_value(signal) else { return; }; - let closure = js_closure_alloc(stream_promises_abort_listener as *const u8, 2); + let closure = js_closure_alloc(crate::fn_info!(stream_promises_abort_listener, 0), 2); js_closure_set_capture_f64(closure, 0, promise_value_from_ptr(promise)); js_closure_set_capture_f64(closure, 1, signal); let event = b"abort"; @@ -271,20 +271,7 @@ fn add_once_listener(stream_handle: i64, event: &[u8], listener: f64) { let _ = crate::node_stream::js_node_stream_method_once(stream_handle, event, listener); } -fn register_finished_listener_arities() { - crate::closure::js_register_closure_arity( - stream_promises_finished_error_listener as *const u8, - 1, - ); - crate::closure::js_register_closure_arity( - stream_promises_finished_side_listener as *const u8, - 0, - ); - crate::closure::js_register_closure_arity( - stream_promises_finished_close_listener as *const u8, - 0, - ); -} +fn register_finished_listener_arities() {} fn finished_state_key(name: &[u8]) -> *mut crate::string::StringHeader { js_string_from_bytes(name.as_ptr(), name.len() as u32) @@ -419,25 +406,36 @@ fn pending_finished_promise( return promise_value_from_ptr(promise); } - let error_listener = js_closure_alloc(stream_promises_finished_error_listener as *const u8, 1); + let error_listener = js_closure_alloc( + crate::fn_info!(stream_promises_finished_error_listener, 1; with_declared(1)), + 1, + ); js_closure_set_capture_ptr(error_listener, 0, state as i64); add_once_listener(handle, b"error", listener_value(error_listener)); if need_readable { - let end_listener = js_closure_alloc(stream_promises_finished_side_listener as *const u8, 2); + let end_listener = js_closure_alloc( + crate::fn_info!(stream_promises_finished_side_listener, 0; with_declared(0)), + 2, + ); js_closure_set_capture_ptr(end_listener, 0, state as i64); js_closure_set_capture_f64(end_listener, 1, f64::from_bits(crate::value::TAG_TRUE)); add_once_listener(handle, b"end", listener_value(end_listener)); } if need_writable { - let finish_listener = - js_closure_alloc(stream_promises_finished_side_listener as *const u8, 2); + let finish_listener = js_closure_alloc( + crate::fn_info!(stream_promises_finished_side_listener, 0; with_declared(0)), + 2, + ); js_closure_set_capture_ptr(finish_listener, 0, state as i64); js_closure_set_capture_f64(finish_listener, 1, f64::from_bits(crate::value::TAG_FALSE)); add_once_listener(handle, b"finish", listener_value(finish_listener)); } - let close_listener = js_closure_alloc(stream_promises_finished_close_listener as *const u8, 1); + let close_listener = js_closure_alloc( + crate::fn_info!(stream_promises_finished_close_listener, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(close_listener, 0, state as i64); add_once_listener(handle, b"close", listener_value(close_listener)); @@ -597,8 +595,10 @@ pub(crate) extern "C" fn thunk_streamP_pipeline( let promise = crate::promise::js_promise_new(); let promise_value = promise_value_from_ptr(promise); - crate::closure::js_register_closure_arity(stream_promises_pipeline_callback as *const u8, 2); - let callback = js_closure_alloc(stream_promises_pipeline_callback as *const u8, 1); + let callback = js_closure_alloc( + crate::fn_info!(stream_promises_pipeline_callback, 2; with_declared(2)), + 1, + ); js_closure_set_capture_f64(callback, 0, promise_value); let mut args = crate::array::js_array_alloc(4); diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index a00a1c2b8d..a8670c4105 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -10,7 +10,7 @@ use std::fs; use crate::closure::{ js_closure_alloc, js_closure_call0, js_closure_call1, js_closure_get_capture_f64, js_closure_get_capture_ptr, js_closure_set_capture_f64, js_closure_set_capture_ptr, - js_register_closure_arity, js_register_closure_rest, ClosureHeader, + ClosureHeader, }; use crate::object::{js_object_alloc, js_object_set_field_by_name}; use crate::string::js_string_from_bytes; @@ -88,29 +88,35 @@ fn set_field(obj: *mut crate::object::ObjectHeader, name: &str, value: f64) { js_object_set_field_by_name(obj, key, value); } -fn make_closure(func: *const u8, arity: u32, captures: u32) -> *mut crate::closure::ClosureHeader { - js_register_closure_arity(func, arity); - let closure = js_closure_alloc(func, captures); - // Optimized Windows links may fold identical COMDAT function bodies, so - // the function-pointer registry is not a stable identity for reflective - // metadata. Pin the requested arity to this closure instance as well. +fn make_closure( + info: *const crate::closure::JsFunctionInfo, + arity: u32, + captures: u32, +) -> *mut crate::closure::ClosureHeader { + let closure = js_closure_alloc(info, captures); + // Optimized Windows links may fold identical COMDAT function bodies, so a + // code address is not a stable identity for reflective metadata. Pin the + // requested arity to this closure instance as well. crate::object::set_builtin_closure_length(closure as usize, arity); closure } -fn closure_value(func: *const u8, arity: u32) -> f64 { - boxed_ptr(make_closure(func, arity, 0)) +fn closure_value(info: *const crate::closure::JsFunctionInfo, arity: u32) -> f64 { + boxed_ptr(make_closure(info, arity, 0)) } -fn closure_value_with_id(func: *const u8, arity: u32, id: i64) -> f64 { - let closure = make_closure(func, arity, 1); +fn closure_value_with_id(info: *const crate::closure::JsFunctionInfo, arity: u32, id: i64) -> f64 { + let closure = make_closure(info, arity, 1); js_closure_set_capture_ptr(closure, 0, id); boxed_ptr(closure) } -fn rest_closure_value_with_id(func: *const u8, fixed_arity: u32, id: i64) -> f64 { - js_register_closure_rest(func, fixed_arity); - let closure = js_closure_alloc(func, 1); +fn rest_closure_value_with_id( + info: *const crate::closure::JsFunctionInfo, + fixed_arity: u32, + id: i64, +) -> f64 { + let closure = js_closure_alloc(info, 1); crate::object::set_builtin_closure_length(closure as usize, fixed_arity); js_closure_set_capture_ptr(closure, 0, id); boxed_ptr(closure) @@ -567,28 +573,48 @@ fn mock_context_object(id: i64, calls: f64, include_call_tracking: bool) -> f64 set_field( obj, "callCount", - closure_value_with_id(mock_context_call_count as *const u8, 0, id), + closure_value_with_id( + crate::fn_info!(mock_context_call_count, 0; with_declared(0)), + 0, + id, + ), ); set_field( obj, "resetCalls", - closure_value_with_id(mock_context_reset_calls as *const u8, 0, id), + closure_value_with_id( + crate::fn_info!(mock_context_reset_calls, 0; with_declared(0)), + 0, + id, + ), ); set_field( obj, "mockImplementation", - closure_value_with_id(mock_context_mock_implementation as *const u8, 1, id), + closure_value_with_id( + crate::fn_info!(mock_context_mock_implementation, 1; with_declared(1)), + 1, + id, + ), ); set_field( obj, "mockImplementationOnce", - closure_value_with_id(mock_context_mock_implementation_once as *const u8, 2, id), + closure_value_with_id( + crate::fn_info!(mock_context_mock_implementation_once, 2; with_declared(2)), + 2, + id, + ), ); } set_field( obj, "restore", - closure_value_with_id(mock_context_restore as *const u8, 0, id), + closure_value_with_id( + crate::fn_info!(mock_context_restore, 0; with_declared(0)), + 0, + id, + ), ); boxed_ptr(obj) } @@ -601,7 +627,7 @@ fn mock_function_metadata(original: f64) -> (String, u32) { let dynamic_name = crate::closure::closure_get_own_dynamic_prop(closure as usize, "name") .and_then(value_to_string); let name = dynamic_name - .or_else(|| unsafe { crate::builtins::function_name_for_ptr((*closure).func_ptr as usize) }) + .or_else(|| unsafe { crate::builtins::function_name_for_ptr((*closure).code() as usize) }) .unwrap_or_default(); let length = crate::closure::closure_length(closure).unwrap_or(0); (name, length) @@ -628,7 +654,7 @@ fn create_mock_function( let calls = scope.root_nanbox_f64(boxed_ptr(crate::array::js_array_alloc(0))); let context = scope.root_nanbox_f64(mock_context_object(id, calls.get_nanbox_f64(), true)); let function = scope.root_nanbox_f64(rest_closure_value_with_id( - mock_function_invoke as *const u8, + crate::fn_info!(mock_function_invoke, 1; with_rest(0)), 0, id, )); @@ -1273,28 +1299,35 @@ fn mock_object_value() -> f64 { set_field( timers, "enable", - closure_value(mock_timers_enable as *const u8, 1), + closure_value(crate::fn_info!(mock_timers_enable, 1; with_declared(1)), 1), ); set_field( timers, "tick", - closure_value(mock_timers_tick as *const u8, 1), + closure_value(crate::fn_info!(mock_timers_tick, 1; with_declared(1)), 1), ); set_field( timers, "runAll", - closure_value(mock_timers_run_all as *const u8, 0), + closure_value(crate::fn_info!(mock_timers_run_all, 0; with_declared(0)), 0), ); set_field( timers, "setTime", - closure_value(mock_timers_set_time as *const u8, 1), + closure_value( + crate::fn_info!(mock_timers_set_time, 1; with_declared(1)), + 1, + ), ); - let reset = closure_value(mock_timers_reset as *const u8, 0); + let reset = closure_value(crate::fn_info!(mock_timers_reset, 0; with_declared(0)), 0); set_field(timers, "reset", reset); let dispose = crate::symbol::well_known_symbol("dispose"); if !dispose.is_null() { - let dispose_closure = make_closure(mock_timers_reset as *const u8, 0, 0); + let dispose_closure = make_closure( + crate::fn_info!(mock_timers_reset, 0; with_declared(0)), + 0, + 0, + ); crate::object::set_bound_native_closure_name(dispose_closure, "[Symbol.dispose]"); unsafe { crate::symbol::js_object_set_symbol_property( @@ -1311,32 +1344,39 @@ fn mock_object_value() -> f64 { } let mock = js_object_alloc(0, 8); - set_field(mock, "fn", closure_value(mock_fn_thunk as *const u8, 3)); + set_field( + mock, + "fn", + closure_value(crate::fn_info!(mock_fn_thunk, 3; with_declared(3)), 3), + ); set_field( mock, "method", - closure_value(mock_method_thunk as *const u8, 4), + closure_value(crate::fn_info!(mock_method_thunk, 4; with_declared(4)), 4), ); set_field( mock, "getter", - closure_value(mock_getter_thunk as *const u8, 4), + closure_value(crate::fn_info!(mock_getter_thunk, 4; with_declared(4)), 4), ); set_field( mock, "setter", - closure_value(mock_setter_thunk as *const u8, 4), + closure_value(crate::fn_info!(mock_setter_thunk, 4; with_declared(4)), 4), ); set_field(mock, "property", property_mock::tracker_property_value()); set_field( mock, "reset", - closure_value(mock_reset_thunk as *const u8, 0), + closure_value(crate::fn_info!(mock_reset_thunk, 0; with_declared(0)), 0), ); set_field( mock, "restoreAll", - closure_value(mock_restore_all_thunk as *const u8, 0), + closure_value( + crate::fn_info!(mock_restore_all_thunk, 0; with_declared(0)), + 0, + ), ); set_field(mock, "timers", boxed_ptr(timers)); *slot.borrow_mut() = Some(mock); @@ -1488,64 +1528,99 @@ extern "C" fn test_context_assert_if_error( fn test_context_value(name: &str) -> f64 { let assert = js_object_alloc(0, 17); for (name, func, arity) in [ - ("deepEqual", test_context_assert_deep_equal as *const u8, 3), + ( + "deepEqual", + crate::fn_info!(test_context_assert_deep_equal, 3; with_declared(3)), + 3, + ), ( "deepStrictEqual", - test_context_assert_deep_strict_equal as *const u8, + crate::fn_info!(test_context_assert_deep_strict_equal, 3; with_declared(3)), 3, ), ( "doesNotMatch", - test_context_assert_does_not_match as *const u8, + crate::fn_info!(test_context_assert_does_not_match, 3; with_declared(3)), 3, ), ( "doesNotReject", - test_context_assert_does_not_reject as *const u8, + crate::fn_info!(test_context_assert_does_not_reject, 3; with_declared(3)), 3, ), ( "doesNotThrow", - test_context_assert_does_not_throw as *const u8, + crate::fn_info!(test_context_assert_does_not_throw, 3; with_declared(3)), + 3, + ), + ( + "equal", + crate::fn_info!(test_context_assert_equal, 3; with_declared(3)), + 3, + ), + ( + "fail", + crate::fn_info!(test_context_assert_fail, 1; with_declared(1)), + 1, + ), + ( + "ifError", + crate::fn_info!(test_context_assert_if_error, 1; with_declared(1)), + 1, + ), + ( + "match", + crate::fn_info!(test_context_assert_match, 3; with_declared(3)), 3, ), - ("equal", test_context_assert_equal as *const u8, 3), - ("fail", test_context_assert_fail as *const u8, 1), - ("ifError", test_context_assert_if_error as *const u8, 1), - ("match", test_context_assert_match as *const u8, 3), ( "notDeepEqual", - test_context_assert_not_deep_equal as *const u8, + crate::fn_info!(test_context_assert_not_deep_equal, 3; with_declared(3)), 3, ), ( "notStrictEqual", - test_context_assert_not_strict_equal as *const u8, + crate::fn_info!(test_context_assert_not_strict_equal, 3; with_declared(3)), + 3, + ), + ( + "ok", + crate::fn_info!(test_context_assert_ok, 2; with_declared(2)), + 2, + ), + ( + "rejects", + crate::fn_info!(test_context_assert_rejects, 3; with_declared(3)), 3, ), - ("ok", test_context_assert_ok as *const u8, 2), - ("rejects", test_context_assert_rejects as *const u8, 3), ( "strictEqual", - test_context_assert_strict_equal as *const u8, + crate::fn_info!(test_context_assert_strict_equal, 3; with_declared(3)), + 3, + ), + ( + "throws", + crate::fn_info!(test_context_assert_throws, 3; with_declared(3)), 3, ), - ("throws", test_context_assert_throws as *const u8, 3), ] { set_field(assert, name, closure_value(func, arity)); } set_field( assert, "snapshot", - closure_value(assert_snapshot as *const u8, 2), + closure_value(crate::fn_info!(assert_snapshot, 2; with_declared(2)), 2), ); set_field( assert, "fileSnapshot", - closure_value(assert_file_snapshot as *const u8, 3), + closure_value( + crate::fn_info!(assert_file_snapshot, 3; with_declared(3)), + 3, + ), ); let ctx = js_object_alloc(0, 8); - let test_fn = closure_value(thunk_test as *const u8, 3); + let test_fn = closure_value(crate::fn_info!(thunk_test, 3; with_declared(3)), 3); let test_fn_ptr = raw_ptr_from_value(test_fn); let test_fn = if crate::value::addr_class::is_plausible_heap_addr(test_fn_ptr) { boxed_ptr(decorate_test_export( @@ -1563,22 +1638,25 @@ fn test_context_value(name: &str) -> f64 { set_field( ctx, "diagnostic", - closure_value(test_context_diagnostic as *const u8, 1), + closure_value( + crate::fn_info!(test_context_diagnostic, 1; with_declared(1)), + 1, + ), ); set_field( ctx, "plan", - closure_value(test_context_plan as *const u8, 1), + closure_value(crate::fn_info!(test_context_plan, 1; with_declared(1)), 1), ); set_field( ctx, "skip", - closure_value(test_context_skip as *const u8, 1), + closure_value(crate::fn_info!(test_context_skip, 1; with_declared(1)), 1), ); set_field( ctx, "todo", - closure_value(test_context_todo as *const u8, 1), + closure_value(crate::fn_info!(test_context_todo, 1; with_declared(1)), 1), ); boxed_ptr(ctx) } @@ -1745,15 +1823,33 @@ pub(crate) fn decorate_test_export( } let decorators = if suite { [ - ("skip", thunk_test_suite_skip as *const u8), - ("todo", thunk_test_suite_todo as *const u8), - ("only", thunk_test_suite_only as *const u8), + ( + "skip", + crate::fn_info!(thunk_test_suite_skip, 3; with_declared(3)), + ), + ( + "todo", + crate::fn_info!(thunk_test_suite_todo, 3; with_declared(3)), + ), + ( + "only", + crate::fn_info!(thunk_test_suite_only, 3; with_declared(3)), + ), ] } else { [ - ("skip", thunk_test_skip as *const u8), - ("todo", thunk_test_todo as *const u8), - ("only", thunk_test_only as *const u8), + ( + "skip", + crate::fn_info!(thunk_test_skip, 3; with_declared(3)), + ), + ( + "todo", + crate::fn_info!(thunk_test_todo, 3; with_declared(3)), + ), + ( + "only", + crate::fn_info!(thunk_test_only, 3; with_declared(3)), + ), ] }; for (name, func) in decorators { diff --git a/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs b/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs index b8ed9b1290..44a71aa4e1 100644 --- a/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs +++ b/crates/perry-runtime/src/node_submodules/test_metadata_unit_tests.rs @@ -11,7 +11,7 @@ extern "C" fn original_two_args( #[test] fn mock_function_preserves_original_name_length_and_descriptors() { - let original = make_closure(original_two_args as *const u8, 2, 0); + let original = make_closure(crate::fn_info!(original_two_args, 2), 2, 0); crate::object::set_bound_native_closure_name(original, "original"); let original = boxed_ptr(original); diff --git a/crates/perry-runtime/src/node_submodules/test_once_unit_tests.rs b/crates/perry-runtime/src/node_submodules/test_once_unit_tests.rs index 4852eb8bf3..05d6cd4272 100644 --- a/crates/perry-runtime/src/node_submodules/test_once_unit_tests.rs +++ b/crates/perry-runtime/src/node_submodules/test_once_unit_tests.rs @@ -114,9 +114,9 @@ fn restoring_a_mock_preserves_calls_and_scheduled_implementations() { #[test] fn reentrant_dispatch_uses_completed_call_indices_like_node() { MOCK_STATES.with(|states| states.borrow_mut().clear()); - let outer = closure_value(reentrant_implementation as *const u8, 0); - let explicit_once = closure_value(return_twenty as *const u8, 0); - let scheduled_inside = closure_value(return_thirty as *const u8, 0); + let outer = closure_value(crate::fn_info!(reentrant_implementation, 0), 0); + let explicit_once = closure_value(crate::fn_info!(return_twenty, 0), 0); + let scheduled_inside = closure_value(crate::fn_info!(return_thirty, 0), 0); let mock = create_mock_function(outer, outer, None, MockRestoreTarget::None); let mock_ptr = raw_ptr_from_value(mock) as *const ClosureHeader; let id = closure_id(mock_ptr); diff --git a/crates/perry-runtime/src/node_submodules/test_property.rs b/crates/perry-runtime/src/node_submodules/test_property.rs index cc8818ffad..75e2c0bc14 100644 --- a/crates/perry-runtime/src/node_submodules/test_property.rs +++ b/crates/perry-runtime/src/node_submodules/test_property.rs @@ -46,7 +46,7 @@ fn property_context_object(id: i64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let context = scope.root_nanbox_f64(boxed_ptr(js_object_alloc(0, 6))); let access_count = scope.root_nanbox_f64(closure_value_with_id( - mock_property_access_count as *const u8, + crate::fn_info!(mock_property_access_count, 0), 0, id, )); @@ -56,7 +56,7 @@ fn property_context_object(id: i64) -> f64 { access_count.get_nanbox_f64(), ); let reset_accesses = scope.root_nanbox_f64(closure_value_with_id( - mock_property_reset_accesses as *const u8, + crate::fn_info!(mock_property_reset_accesses, 0), 0, id, )); @@ -66,7 +66,7 @@ fn property_context_object(id: i64) -> f64 { reset_accesses.get_nanbox_f64(), ); let mock_implementation = scope.root_nanbox_f64(closure_value_with_id( - mock_property_implementation as *const u8, + crate::fn_info!(mock_property_implementation, 1), 1, id, )); @@ -76,7 +76,7 @@ fn property_context_object(id: i64) -> f64 { mock_implementation.get_nanbox_f64(), ); let mock_implementation_once = scope.root_nanbox_f64(rest_closure_value_with_id( - mock_property_implementation_once as *const u8, + crate::fn_info!(mock_property_implementation_once, 2; with_rest(1)), 1, id, )); @@ -86,7 +86,7 @@ fn property_context_object(id: i64) -> f64 { mock_implementation_once.get_nanbox_f64(), ); let restore = scope.root_nanbox_f64(closure_value_with_id( - mock_property_restore as *const u8, + crate::fn_info!(mock_property_restore, 0), 0, id, )); @@ -97,7 +97,7 @@ fn property_context_object(id: i64) -> f64 { ); let accesses_getter = scope.root_nanbox_f64(closure_value_with_id( - mock_property_accesses as *const u8, + crate::fn_info!(mock_property_accesses, 0), 0, id, )); @@ -471,7 +471,11 @@ extern "C" fn tracker_property_thunk( } pub(super) fn tracker_property_value() -> f64 { - let value = rest_closure_value_with_id(tracker_property_thunk as *const u8, 2, 0); + let value = rest_closure_value_with_id( + crate::fn_info!(tracker_property_thunk, 3; with_rest(2)), + 2, + 0, + ); let raw = raw_ptr_from_value(value); crate::object::set_builtin_closure_length(raw, 3); value @@ -515,10 +519,16 @@ pub(super) fn create(target: f64, property: f64, value_present: bool, value: f64 let id = next_mock_id(); let accesses = scope.root_nanbox_f64(boxed_ptr(crate::array::js_array_alloc(0))); let context = scope.root_nanbox_f64(property_context_object(id)); - let getter = - scope.root_nanbox_f64(closure_value_with_id(mock_property_get as *const u8, 0, id)); - let setter = - scope.root_nanbox_f64(closure_value_with_id(mock_property_set as *const u8, 1, id)); + let getter = scope.root_nanbox_f64(closure_value_with_id( + crate::fn_info!(mock_property_get, 0), + 0, + id, + )); + let setter = scope.root_nanbox_f64(closure_value_with_id( + crate::fn_info!(mock_property_set, 1), + 1, + id, + )); PROPERTY_STATES.with(|states| { states.borrow_mut().push(MockPropertyState { @@ -546,7 +556,7 @@ pub(super) fn create(target: f64, property: f64, value_present: bool, value: f64 let handler = scope.root_nanbox_f64(boxed_ptr(js_object_alloc(0, 1))); let get_trap = scope.root_nanbox_f64(closure_value_with_id( - mock_property_proxy_get as *const u8, + crate::fn_info!(mock_property_proxy_get, 3), 3, id, )); diff --git a/crates/perry-runtime/src/node_submodules/test_reporters.rs b/crates/perry-runtime/src/node_submodules/test_reporters.rs index 8f6877931a..9b9766da63 100644 --- a/crates/perry-runtime/src/node_submodules/test_reporters.rs +++ b/crates/perry-runtime/src/node_submodules/test_reporters.rs @@ -57,7 +57,7 @@ pub(crate) extern "C" fn thunk_reporter_lcov( fn reporter_transform(kind: i32) -> f64 { let captures = if kind == REPORTER_SPEC { 2 } else { 1 }; - let transform = make_closure(reporter_transform_chunk as *const u8, 3, captures); + let transform = make_closure(crate::fn_info!(reporter_transform_chunk, 3), 3, captures); js_closure_set_capture_f64(transform, 0, kind as f64); if kind == REPORTER_SPEC { js_closure_set_capture_f64(transform, 1, undefined_value()); diff --git a/crates/perry-runtime/src/node_submodules/test_runner.rs b/crates/perry-runtime/src/node_submodules/test_runner.rs index 9d1c821f5c..6c50146cec 100644 --- a/crates/perry-runtime/src/node_submodules/test_runner.rs +++ b/crates/perry-runtime/src/node_submodules/test_runner.rs @@ -116,7 +116,7 @@ fn ensure_runner_scheduled() { } }); if should_schedule { - let closure = make_closure(test_runner_task as *const u8, 0, 0); + let closure = make_closure(crate::fn_info!(test_runner_task, 0), 0, 0); crate::timer::js_set_immediate_callback(closure as i64); } } @@ -382,7 +382,7 @@ fn call_test_callback(callback: f64, name: &str) -> Result { let arity = crate::closure::closure_length(callback_ptr).unwrap_or(0); catch_js(|| { if arity >= 2 { - let done = scope.root_nanbox_f64(closure_value(test_done as *const u8, 1)); + let done = scope.root_nanbox_f64(closure_value(crate::fn_info!(test_done, 1), 1)); crate::closure::js_closure_call2( callback_ptr, crate::closure::plain_call_receiver(), diff --git a/crates/perry-runtime/src/node_submodules/test_snapshot.rs b/crates/perry-runtime/src/node_submodules/test_snapshot.rs index 53102e35f1..9c6099082a 100644 --- a/crates/perry-runtime/src/node_submodules/test_snapshot.rs +++ b/crates/perry-runtime/src/node_submodules/test_snapshot.rs @@ -159,12 +159,12 @@ pub(super) fn snapshot_object_value() -> f64 { set_field( obj, "setDefaultSnapshotSerializers", - closure_value(snapshot_set_default_serializers as *const u8, 1), + closure_value(crate::fn_info!(snapshot_set_default_serializers, 1), 1), ); set_field( obj, "setResolveSnapshotPath", - closure_value(snapshot_set_resolve_snapshot_path as *const u8, 1), + closure_value(crate::fn_info!(snapshot_set_resolve_snapshot_path, 1), 1), ); *slot.borrow_mut() = Some(obj); boxed_ptr(obj) diff --git a/crates/perry-runtime/src/node_submodules/test_unit_tests.rs b/crates/perry-runtime/src/node_submodules/test_unit_tests.rs index 8f4a4cfe9d..9aa24dbb84 100644 --- a/crates/perry-runtime/src/node_submodules/test_unit_tests.rs +++ b/crates/perry-runtime/src/node_submodules/test_unit_tests.rs @@ -109,7 +109,7 @@ fn mock_method_options_survive_an_allocating_getter() { boxed_ptr(options), "getter", crate::object::AccessorDescriptor { - get: closure_value(allocating_true_option as *const u8, 0).to_bits(), + get: closure_value(crate::fn_info!(allocating_true_option, 0), 0).to_bits(), set: 0, }, ); diff --git a/crates/perry-runtime/src/node_submodules/tests.rs b/crates/perry-runtime/src/node_submodules/tests.rs index d5050afb2b..287dcab5b5 100644 --- a/crates/perry-runtime/src/node_submodules/tests.rs +++ b/crates/perry-runtime/src/node_submodules/tests.rs @@ -902,8 +902,6 @@ extern "C" fn pipeline_end_capture( #[test] fn stream_promises_pipeline_transfers_readable_from_chunks() { PIPELINE_CAPTURED.with(|captured| captured.borrow_mut().clear()); - crate::closure::js_register_closure_arity(pipeline_write_capture as *const u8, 2); - crate::closure::js_register_closure_arity(pipeline_end_capture as *const u8, 1); let mut arr = crate::array::js_array_alloc(2); arr = crate::array::js_array_push_f64(arr, string_value("await-")); @@ -911,8 +909,14 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { let source = crate::node_stream::js_node_stream_readable_from(boxed_ptr(arr as *const u8)); let sink = js_object_alloc(0, 2); - let write = js_closure_alloc(pipeline_write_capture as *const u8, 0); - let end = js_closure_alloc(pipeline_end_capture as *const u8, 0); + let write = js_closure_alloc( + crate::fn_info!(pipeline_write_capture, 2; with_declared(2)), + 0, + ); + let end = js_closure_alloc( + crate::fn_info!(pipeline_end_capture, 1; with_declared(1)), + 0, + ); js_object_set_field_by_name( sink, js_string_from_bytes(b"write".as_ptr(), 5), diff --git a/crates/perry-runtime/src/node_submodules/timers.rs b/crates/perry-runtime/src/node_submodules/timers.rs index 570c177ad3..2cc8b75fa6 100644 --- a/crates/perry-runtime/src/node_submodules/timers.rs +++ b/crates/perry-runtime/src/node_submodules/timers.rs @@ -299,7 +299,7 @@ pub(crate) extern "C" fn timers_promises_set_interval( let obj = js_object_alloc(0, 4); let obj_value = boxed_ptr(obj as *const u8); - let next = js_closure_alloc(timers_promises_interval_next as *const u8, 6); + let next = js_closure_alloc(crate::fn_info!(timers_promises_interval_next, 0), 6); js_closure_set_capture_f64(next, 0, value); js_closure_set_capture_f64(next, 1, signal); js_closure_set_capture_f64(next, 2, delay_ms); @@ -308,11 +308,11 @@ pub(crate) extern "C" fn timers_promises_set_interval( js_closure_set_capture_f64(next, 5, validation_error); js_object_set_field_by_name(obj, string_key(b"next"), boxed_ptr(next as *const u8)); - let ret = js_closure_alloc(timers_promises_interval_return as *const u8, 1); + let ret = js_closure_alloc(crate::fn_info!(timers_promises_interval_return, 0), 1); js_closure_set_capture_f64(ret, 0, boxed_ptr(next as *const u8)); js_object_set_field_by_name(obj, string_key(b"return"), boxed_ptr(ret as *const u8)); - let ret = js_closure_alloc(timers_promises_interval_self as *const u8, 1); + let ret = js_closure_alloc(crate::fn_info!(timers_promises_interval_self, 0), 1); js_closure_set_capture_f64(ret, 0, obj_value); let sym = crate::symbol::well_known_symbol("asyncIterator"); if !sym.is_null() { diff --git a/crates/perry-runtime/src/node_submodules/trace_events.rs b/crates/perry-runtime/src/node_submodules/trace_events.rs index d4626369a4..f7e592c75b 100644 --- a/crates/perry-runtime/src/node_submodules/trace_events.rs +++ b/crates/perry-runtime/src/node_submodules/trace_events.rs @@ -8,7 +8,7 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::sync::atomic::{AtomicI64, Ordering}; use crate::array::{js_array_get_f64, js_array_length, ArrayHeader}; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use crate::object::{js_object_alloc, AccessorDescriptor, ObjectHeader, PropertyAttrs}; use crate::string::{js_string_from_bytes, StringHeader}; use crate::value::JSValue; @@ -179,9 +179,8 @@ fn set_function_name(closure: *mut ClosureHeader, name: &str) { crate::closure::closure_set_dynamic_prop(closure as usize, "name", string_value(name)); } -fn function_value(func: *const u8, arity: u32, name: &str) -> f64 { - let closure = js_closure_alloc(func, 0); - js_register_closure_arity(func, arity); +fn function_value(info: *const crate::closure::JsFunctionInfo, name: &str) -> f64 { + let closure = js_closure_alloc(info, 0); set_function_name(closure, name); boxed_ptr(closure) } @@ -358,11 +357,26 @@ fn ensure_trace_prototype() -> *mut ObjectHeader { } let proto = js_object_alloc(0, 5); - let ctor = function_value(trace_tracing_constructor as *const u8, 1, "Tracing"); - let enable = function_value(trace_tracing_enable as *const u8, 0, "enable"); - let disable = function_value(trace_tracing_disable as *const u8, 0, "disable"); - let categories = function_value(trace_categories_getter as *const u8, 0, "get categories"); - let enabled = function_value(trace_enabled_getter as *const u8, 0, "get enabled"); + let ctor = function_value( + crate::fn_info!(trace_tracing_constructor, 1; with_declared(1)), + "Tracing", + ); + let enable = function_value( + crate::fn_info!(trace_tracing_enable, 0; with_declared(0)), + "enable", + ); + let disable = function_value( + crate::fn_info!(trace_tracing_disable, 0; with_declared(0)), + "disable", + ); + let categories = function_value( + crate::fn_info!(trace_categories_getter, 0; with_declared(0)), + "get categories", + ); + let enabled = function_value( + crate::fn_info!(trace_enabled_getter, 0; with_declared(0)), + "get enabled", + ); define_non_enum_data(proto, "constructor", ctor, true); define_non_enum_data(proto, "enable", enable, true); diff --git a/crates/perry-runtime/src/node_test.rs b/crates/perry-runtime/src/node_test.rs index 8b550151ae..bc85d6581b 100644 --- a/crates/perry-runtime/src/node_test.rs +++ b/crates/perry-runtime/src/node_test.rs @@ -30,9 +30,8 @@ fn set(obj: *mut ObjectHeader, name: &str, value: f64) { crate::object::js_object_set_field_by_name(obj, key(name), value); } -fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_arity(func, arity); - let closure = crate::closure::js_closure_alloc(func, 0); +fn fn_value(info: *const crate::closure::JsFunctionInfo, name: &str) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return undefined(); } @@ -73,29 +72,42 @@ fn mock_context_object() -> *mut ObjectHeader { set( obj, "callCount", - fn_value(zero0 as *const u8, "callCount", 0), + fn_value(crate::fn_info!(zero0, 0; with_declared(0)), "callCount"), ); set( obj, "resetCalls", - fn_value(noop0 as *const u8, "resetCalls", 0), + fn_value(crate::fn_info!(noop0, 0; with_declared(0)), "resetCalls"), ); set( obj, "mockImplementation", - fn_value(noop1 as *const u8, "mockImplementation", 1), + fn_value( + crate::fn_info!(noop1, 1; with_declared(1)), + "mockImplementation", + ), ); set( obj, "mockImplementationOnce", - fn_value(noop1 as *const u8, "mockImplementationOnce", 1), + fn_value( + crate::fn_info!(noop1, 1; with_declared(1)), + "mockImplementationOnce", + ), + ); + set( + obj, + "restore", + fn_value(crate::fn_info!(noop0, 0; with_declared(0)), "restore"), ); - set(obj, "restore", fn_value(noop0 as *const u8, "restore", 0)); obj } fn mock_function_value() -> f64 { - let value = fn_value(noop3 as *const u8, "mockConstructor", 3); + let value = fn_value( + crate::fn_info!(noop3, 3; with_declared(3)), + "mockConstructor", + ); let closure_ptr = crate::value::js_nanbox_get_pointer(value) as usize; let context = object_value(mock_context_object()); crate::closure::closure_set_dynamic_prop(closure_ptr, "mock", context); @@ -130,7 +142,11 @@ pub fn property(property: &str) -> Option { fn test_assert_object() -> f64 { let obj = crate::object::js_object_alloc(0, 0); // `assert.register(name, fn)` — length 2 in Node; stubbed (shape parity). - set(obj, "register", fn_value(noop3 as *const u8, "register", 2)); + set( + obj, + "register", + fn_value(crate::fn_info!(noop3, 3; with_declared(2)), "register"), + ); object_value(obj) } diff --git a/crates/perry-runtime/src/node_v8.rs b/crates/perry-runtime/src/node_v8.rs index 2414424848..24090a4284 100644 --- a/crates/perry-runtime/src/node_v8.rs +++ b/crates/perry-runtime/src/node_v8.rs @@ -654,7 +654,7 @@ pub extern "C" fn js_v8_throw_not_building_snapshot() -> f64 { /// `const stop = onInit(fn); stop()` round-trips. #[no_mangle] pub extern "C" fn js_v8_promise_hook_register() -> f64 { - let c = crate::closure::js_closure_alloc_singleton(v8_noop_undefined_body as *const u8); + let c = crate::closure::js_closure_alloc_singleton(crate::fn_info!(v8_noop_undefined_body, 0)); crate::value::js_nanbox_pointer(c as i64) } diff --git a/crates/perry-runtime/src/node_vm.rs b/crates/perry-runtime/src/node_vm.rs index 78341aa249..d101786aa8 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -252,7 +252,7 @@ pub(crate) fn function_source_for_closure(closure: usize) -> String { let func_ptr = unsafe { crate::closure::bound_method_source_func_ptr(closure_ptr) .or_else(|| crate::object::class_accessor_source_func_ptr(closure_ptr)) - .unwrap_or((*closure_ptr).func_ptr as usize) + .unwrap_or((*closure_ptr).code() as usize) }; crate::builtins::function_source_for_func_ptr(func_ptr) }) @@ -1311,15 +1311,13 @@ fn script_metadata(script_value: f64) -> Option { fn install_script_method( obj: *mut ObjectHeader, name: &str, - func: crate::closure::body_call::js_body_fn_ty!(a, a), + info: *const crate::closure::JsFunctionInfo, arity: u32, ) { let scope = crate::gc::RuntimeHandleScope::new(); let obj = scope.root_raw_mut_ptr(obj); let key = scope.root_string_ptr(field_key(name)); - let func_ptr = func as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr::(|closure| { crate::object::set_builtin_closure_length(closure as usize, arity) @@ -1402,33 +1400,35 @@ pub(crate) fn install_script_prototypes(constructor: f64) { PropertyAttrs::new(true, false, true), ) }); + // One info for both `runInContext` installs, so the two share a body identity. + let run_in_context = crate::fn_info!(vm_script_run_in_context_method, 2; with_declared(2)); proto.with_mut_ptr::(|proto| { install_script_method( proto, "runInThisContext", - vm_script_run_in_this_context_method, + crate::fn_info!(vm_script_run_in_this_context_method, 2; with_declared(2)), 1, ) }); proto.with_mut_ptr::(|proto| { - install_script_method(proto, "runInContext", vm_script_run_in_context_method, 2) + install_script_method(proto, "runInContext", run_in_context, 2) }); proto.with_mut_ptr::(|proto| { install_script_method( proto, "runInNewContext", - vm_script_run_in_new_context_method, + crate::fn_info!(vm_script_run_in_new_context_method, 2; with_declared(2)), 2, ) }); base.with_mut_ptr::(|base| { - install_script_method(base, "runInContext", vm_script_run_in_context_method, 2) + install_script_method(base, "runInContext", run_in_context, 2) }); base.with_mut_ptr::(|base| { install_script_method( base, "createCachedData", - vm_script_create_cached_data_method, + crate::fn_info!(vm_script_create_cached_data_method, 2; with_declared(2)), 0, ) }); diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index 68bde0b489..cc5395f7d1 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -176,13 +176,12 @@ extern "C" fn module_error_getter( fn install_module_accessor( module: *mut ObjectHeader, name: &str, - getter: crate::closure::body_call::js_body_fn_ty!(), + getter: *const crate::closure::JsFunctionInfo, ) { let scope = crate::gc::RuntimeHandleScope::new(); let module = scope.root_raw_mut_ptr(module); - let closure = crate::closure::js_closure_alloc(getter as *const u8, 1); + let closure = crate::closure::js_closure_alloc(getter, 1); let closure = scope.root_raw_mut_ptr(closure); - crate::closure::js_register_closure_arity(getter as *const u8, 0); with_hmut(&closure, |closure| { with_hmut(&module, |module| { crate::closure::js_closure_set_capture_f64(closure, 0, object_value(module)) @@ -270,10 +269,18 @@ pub extern "C" fn js_vm_source_text_module_new(code: f64, options: f64) -> f64 { set_field(module, FIELD_NAMESPACE, namespace_value) }); with_hmut(&module, |module| { - install_module_accessor(module, "namespace", module_namespace_getter) + install_module_accessor( + module, + "namespace", + crate::fn_info!(module_namespace_getter, 0; with_declared(0)), + ) }); with_hmut(&module, |module| { - install_module_accessor(module, "error", module_error_getter) + install_module_accessor( + module, + "error", + crate::fn_info!(module_error_getter, 0; with_declared(0)), + ) }); with_hmut(&module, |module| { set_field(module, FIELD_CONTEXT, context.get_nanbox_f64()) @@ -357,10 +364,18 @@ pub extern "C" fn js_vm_synthetic_module_new( set_field(module, FIELD_NAMESPACE, namespace_value) }); with_hmut(&module, |module| { - install_module_accessor(module, "namespace", module_namespace_getter) + install_module_accessor( + module, + "namespace", + crate::fn_info!(module_namespace_getter, 0; with_declared(0)), + ) }); with_hmut(&module, |module| { - install_module_accessor(module, "error", module_error_getter) + install_module_accessor( + module, + "error", + crate::fn_info!(module_error_getter, 0; with_declared(0)), + ) }); with_hmut(&module, |module| { set_field(module, FIELD_CONTEXT, context.get_nanbox_f64()) diff --git a/crates/perry-runtime/src/object/accessor_pair_tests.rs b/crates/perry-runtime/src/object/accessor_pair_tests.rs index 308a42f48e..0804641778 100644 --- a/crates/perry-runtime/src/object/accessor_pair_tests.rs +++ b/crates/perry-runtime/src/object/accessor_pair_tests.rs @@ -13,7 +13,7 @@ unsafe fn closure_bits() -> u64 { ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } - let c = crate::closure::js_closure_alloc(noop as *const u8, 0); + let c = crate::closure::js_closure_alloc(crate::fn_info!(noop, 0), 0); crate::value::js_nanbox_pointer(c as i64).to_bits() } diff --git a/crates/perry-runtime/src/object/arguments.rs b/crates/perry-runtime/src/object/arguments.rs index 7563128249..fe1f2efc7e 100644 --- a/crates/perry-runtime/src/object/arguments.rs +++ b/crates/perry-runtime/src/object/arguments.rs @@ -215,8 +215,9 @@ pub(super) const THROWER_FROZEN_FLAGS: u16 = crate::gc::OBJ_FLAG_FROZEN | crate::gc::OBJ_FLAG_SEALED | crate::gc::OBJ_FLAG_NO_EXTEND; pub(super) fn thrower_closure_value() -> f64 { - let closure = - crate::closure::js_closure_alloc_singleton(arguments_throw_type_error as *const u8); + let closure = crate::closure::js_closure_alloc_singleton( + crate::fn_info!(arguments_throw_type_error, 0; with_declared(0)), + ); // #10509: configure the per-thread singleton once, not per strict call. // The frozen bits are the last step below and live in the closure's own // header; every side table the earlier steps write (closure props, @@ -230,7 +231,6 @@ pub(super) fn thrower_closure_value() -> f64 { { return crate::value::js_nanbox_pointer(closure as i64); } - crate::closure::js_register_closure_arity(arguments_throw_type_error as *const u8, 0); super::native_module::set_bound_native_closure_name(closure, ""); super::native_module::set_builtin_closure_length(closure as usize, 0); super::native_module::set_builtin_closure_non_constructable(closure as usize); @@ -530,7 +530,7 @@ pub extern "C" fn js_arguments_bundle_index_get(raw_args: f64, key: f64) -> f64 /// #10509: the cold half of [`js_arguments_bundle_index_get`]. Builds the /// Arguments object the prologue would have built (`callee_wrapper`, when -/// non-null, names the function whose singleton closure is `callee`) and +/// non-null, is the info of the function whose singleton closure is `callee`) and /// performs an ordinary `obj[key]` on it, so a non-element key sees the real /// object's `callee`, `length`, `Symbol.iterator` and `Object.prototype` /// surface. The object is not kept: codegen only takes this path for a @@ -540,7 +540,7 @@ pub extern "C" fn js_arguments_bundle_get_slow( raw_args: f64, key: f64, callee: f64, - callee_wrapper: *const u8, + callee_wrapper: *const crate::closure::JsFunctionInfo, restricted_callee: i32, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/object/assert.rs b/crates/perry-runtime/src/object/assert.rs index 5906c3d842..5c8278dfac 100644 --- a/crates/perry-runtime/src/object/assert.rs +++ b/crates/perry-runtime/src/object/assert.rs @@ -601,7 +601,7 @@ extern "C" fn assert_does_not_reject_rejected( } fn closure3( - func: *const u8, + func: *const crate::closure::JsFunctionInfo, result: *mut crate::promise::Promise, expected: f64, message: f64, @@ -1151,13 +1151,13 @@ pub extern "C" fn js_assert_rejects(input: f64, expected: f64, message: f64) -> } let result = crate::promise::js_promise_new(); let on_fulfilled = closure3( - assert_rejects_fulfilled as *const u8, + crate::fn_info!(assert_rejects_fulfilled, 1), result, expected, message, ); let on_rejected = closure3( - assert_rejects_rejected as *const u8, + crate::fn_info!(assert_rejects_rejected, 1), result, expected, message, @@ -1174,9 +1174,9 @@ pub extern "C" fn js_assert_does_not_reject(input: f64, expected: f64, message: } let result = crate::promise::js_promise_new(); let on_fulfilled = - crate::closure::js_closure_alloc(assert_does_not_reject_fulfilled as *const u8, 1); + crate::closure::js_closure_alloc(crate::fn_info!(assert_does_not_reject_fulfilled, 1), 1); let on_rejected = - crate::closure::js_closure_alloc(assert_does_not_reject_rejected as *const u8, 3); + crate::closure::js_closure_alloc(crate::fn_info!(assert_does_not_reject_rejected, 1), 3); crate::closure::js_closure_set_capture_ptr(on_fulfilled, 0, result as i64); crate::closure::js_closure_set_capture_ptr(on_rejected, 0, result as i64); crate::closure::js_closure_set_capture_f64(on_rejected, 1, message); diff --git a/crates/perry-runtime/src/object/async_generator_queue.rs b/crates/perry-runtime/src/object/async_generator_queue.rs index 95964ae426..463d86e584 100644 --- a/crates/perry-runtime/src/object/async_generator_queue.rs +++ b/crates/perry-runtime/src/object/async_generator_queue.rs @@ -74,7 +74,6 @@ pub(crate) fn wrap_async_generator_instance(obj: *mut ObjectHeader) { if obj.is_null() { return; } - register_wrapper_arities(); let Some(next) = own_closure(obj, b"next") else { return; @@ -117,20 +116,24 @@ pub(crate) fn wrap_async_generator_instance(obj: *mut ObjectHeader) { let obj_now = || js_nanbox_get_pointer(obj_h.get_nanbox_f64()) as *mut ObjectHeader; for (name, original_h, func) in [ + (b"next".as_slice(), &next_h, &ASYNC_GENERATOR_NEXT_WRAPPER), ( - b"next".as_slice(), - &next_h, - async_generator_next_wrapper as crate::closure::body_call::js_body_fn_ty!(a), + b"return".as_slice(), + &ret_h, + &ASYNC_GENERATOR_RETURN_WRAPPER, + ), + ( + b"throw".as_slice(), + &throw_h, + &ASYNC_GENERATOR_THROW_WRAPPER, ), - (b"return".as_slice(), &ret_h, async_generator_return_wrapper), - (b"throw".as_slice(), &throw_h, async_generator_throw_wrapper), ] { // Inlined rather than routed through `make_method_wrapper` for one // reason: that helper takes `original` as a parameter, so the caller // would have to bind it BEFORE `js_closure_alloc` runs and the capture // store would write a pre-collection address. Reading it from the // handle after the allocation is the whole fix. - let wrapper = js_closure_alloc(func as *const u8, 2); + let wrapper = js_closure_alloc(func, 2); js_closure_set_capture_f64(wrapper, 0, state_id as f64); js_closure_set_capture_ptr(wrapper, 1, closure_now(original_h) as i64); // `set_method` roots both of its pointer arguments before it allocates @@ -186,24 +189,24 @@ fn set_method(obj: *mut ObjectHeader, name: &[u8], closure: *mut ClosureHeader) } /// #4547: the queue wrappers each take a single `arg` (the value passed to -/// `next`/`return`/`throw`). Without a registered arity, dispatch padding has -/// no declared count, so a 0-arg `gen.return()` / `gen.throw()` read an -/// uninitialized stack slot for `arg` instead of `undefined`. Record arity 1 -/// for all three func pointers so the call path pads the missing argument. -fn register_wrapper_arities() { - crate::perry_thread_local! { - static REGISTERED: std::cell::Cell = const { std::cell::Cell::new(false) }; - } - REGISTERED.with(|done| { - if done.get() { - return; - } - done.set(true); - crate::closure::js_register_closure_arity(async_generator_next_wrapper as *const u8, 1); - crate::closure::js_register_closure_arity(async_generator_return_wrapper as *const u8, 1); - crate::closure::js_register_closure_arity(async_generator_throw_wrapper as *const u8, 1); - }); -} +/// `next`/`return`/`throw`); their infos record it, so a 0-arg +/// `gen.return()` / `gen.throw()` pads the missing argument with +/// `undefined` instead of reading an uninitialized slot. +static ASYNC_GENERATOR_NEXT_WRAPPER: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + async_generator_next_wrapper as crate::codegen_abi::JsBody1, + ) + .with_declared(1); +static ASYNC_GENERATOR_RETURN_WRAPPER: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + async_generator_return_wrapper as crate::codegen_abi::JsBody1, + ) + .with_declared(1); +static ASYNC_GENERATOR_THROW_WRAPPER: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + async_generator_throw_wrapper as crate::codegen_abi::JsBody1, + ) + .with_declared(1); // #7577: `make_method_wrapper(state_id, original, func)` used to live here. Its // signature was the defect — taking `original` as a parameter forced every @@ -219,9 +222,9 @@ fn make_settle_wrapper( is_fulfilled: bool, ) -> *mut ClosureHeader { let func = if is_fulfilled { - async_generator_settle_fulfill as *const u8 + crate::fn_info!(async_generator_settle_fulfill, 1) } else { - async_generator_settle_reject as *const u8 + crate::fn_info!(async_generator_settle_reject, 1) }; let wrapper = js_closure_alloc(func, 2); js_closure_set_capture_f64(wrapper, 0, state_id as f64); @@ -230,7 +233,7 @@ fn make_settle_wrapper( } fn make_drain_wrapper(state_id: usize) -> *mut ClosureHeader { - let wrapper = js_closure_alloc(async_generator_drain_wrapper as *const u8, 1); + let wrapper = js_closure_alloc(crate::fn_info!(async_generator_drain_wrapper, 0), 1); js_closure_set_capture_f64(wrapper, 0, state_id as f64); wrapper } @@ -252,7 +255,7 @@ fn is_queue_wrapper(closure: *const ClosureHeader) -> bool { if closure.is_null() { return false; } - let func = unsafe { (*closure).func_ptr }; + let func = unsafe { (*closure).code() }; func == async_generator_next_wrapper as *const u8 || func == async_generator_return_wrapper as *const u8 || func == async_generator_throw_wrapper as *const u8 @@ -426,9 +429,9 @@ fn make_return_step_wrapper( is_fulfilled: bool, ) -> *mut ClosureHeader { let func = if is_fulfilled { - async_generator_return_step_fulfill as *const u8 + crate::fn_info!(async_generator_return_step_fulfill, 1) } else { - async_generator_return_step_reject as *const u8 + crate::fn_info!(async_generator_return_step_reject, 1) }; let wrapper = js_closure_alloc(func, 3); js_closure_set_capture_f64(wrapper, 0, state_id as f64); diff --git a/crates/perry-runtime/src/object/bound_method_receiver_tests.rs b/crates/perry-runtime/src/object/bound_method_receiver_tests.rs index 280496797f..a38e538798 100644 --- a/crates/perry-runtime/src/object/bound_method_receiver_tests.rs +++ b/crates/perry-runtime/src/object/bound_method_receiver_tests.rs @@ -61,6 +61,7 @@ unsafe fn register_class() -> (f64, f64) { ACCESSOR.as_ptr(), ACCESSOR.len() as i64, record_setter_11201 as *const () as usize as i64, + 1, ); let proto_ref = super::native_module::class_prototype_ref_value(RECEIVER_TEST_CLASS_ID); let method = diff --git a/crates/perry-runtime/src/object/class_constructors.rs b/crates/perry-runtime/src/object/class_constructors.rs index 2f542a2aca..de5f6635b6 100644 --- a/crates/perry-runtime/src/object/class_constructors.rs +++ b/crates/perry-runtime/src/object/class_constructors.rs @@ -227,13 +227,15 @@ fn lookup_class_constructor(class_id: u32) -> Option<(usize, u32, u32)> { static CLASS_CONSTRUCTOR_FLAGS: ImageTable>> = ImageTable::new(|image| &image.constructor_flags); -/// Codegen FFI: record `(has_synthetic_arguments, has_rest)` for a class ctor. -/// See [`CLASS_CONSTRUCTOR_FLAGS`]. +/// Codegen FFI: record `(has_synthetic_arguments, has_rest)` for a class +/// ctor, and `rest_fixed` — the parameters before its first trailing array +/// (`-1` when the constructor has none). See [`CLASS_CONSTRUCTOR_FLAGS`]. #[no_mangle] pub extern "C" fn js_register_class_constructor_flags( class_id: i64, has_synthetic_arguments: i64, has_rest: i64, + rest_fixed: i64, ) { if class_id == 0 { return; @@ -244,23 +246,27 @@ pub extern "C" fn js_register_class_constructor_flags( } guard.as_mut().unwrap().insert( class_id as u32, - (has_synthetic_arguments != 0, has_rest != 0), + ( + has_synthetic_arguments != 0, + has_rest != 0, + u32::try_from(rest_fixed).ok(), + ), ); } /// Keepalive anchor (generated-code-only callee). #[cfg(feature = "keepalive-anchors")] #[used(compiler)] -static KEEP_JS_REGISTER_CLASS_CONSTRUCTOR_FLAGS: extern "C" fn(i64, i64, i64) = +static KEEP_JS_REGISTER_CLASS_CONSTRUCTOR_FLAGS: extern "C" fn(i64, i64, i64, i64) = js_register_class_constructor_flags; -/// Look up a class ctor's `(has_synthetic_arguments, has_rest)` flags. -fn lookup_class_constructor_flags(class_id: u32) -> (bool, bool) { +/// Look up a class ctor's `(has_synthetic_arguments, has_rest, rest_fixed)`. +fn lookup_class_constructor_flags(class_id: u32) -> (bool, bool, Option) { CLASS_CONSTRUCTOR_FLAGS .read() .ok() .and_then(|g| g.as_ref().and_then(|m| m.get(&class_id).copied())) - .unwrap_or((false, false)) + .unwrap_or((false, false, None)) } /// Bind the USER parameter slots of `ctor_cid`'s registered constructor (every @@ -269,9 +275,10 @@ fn lookup_class_constructor_flags(class_id: u32) -> (bool, bool) { /// /// Codegen lowers the trailing array parameters as /// `[fixed..., user_rest?, synthesized_arguments?]` and registers the position -/// of the first one in the closure-rest table (`ctor_rest_regs`). The flags say -/// which arrays follow: a user rest receives the arguments from that position -/// on, the synthesized `arguments` slot receives EVERY argument. +/// of the first one with the class's constructor flags +/// (`js_register_class_constructor_flags`). The flags say which arrays +/// follow: a user rest receives the arguments from that position on, the +/// synthesized `arguments` slot receives EVERY argument. /// /// #10484: the dynamic construct paths used to pack only a user-rest tail at /// that position, so a constructor reading `arguments` saw just the arguments @@ -282,7 +289,6 @@ fn lookup_class_constructor_flags(class_id: u32) -> (bool, bool) { /// The returned words are not rooted. Callers hand them to the constructor call /// without allocating in between. unsafe fn constructor_user_arg_slots( - ctor_ptr: usize, ctor_cid: u32, user_params: usize, args_ptr: *const f64, @@ -296,11 +302,11 @@ unsafe fn constructor_user_arg_slots( undef } }; - let (has_synth, flagged_rest) = lookup_class_constructor_flags(ctor_cid); + let (has_synth, flagged_rest, rest_fixed) = lookup_class_constructor_flags(ctor_cid); // An unflagged registration is a plain `constructor(a, ...rest)`. let has_rest = flagged_rest || !has_synth; let trailing = usize::from(has_rest) + usize::from(has_synth); - let Some(fixed) = crate::closure::lookup_closure_rest(ctor_ptr as *const u8) + let Some(fixed) = rest_fixed .map(|fixed| fixed as usize) .filter(|fixed| fixed + trailing <= user_params) else { @@ -673,7 +679,7 @@ pub unsafe extern "C" fn js_super_construct_apply( } else { crate::array::js_array_length(arr) } as usize; - let (has_synth, has_rest_flag) = lookup_class_constructor_flags(cur); + let (has_synth, has_rest_flag, _) = lookup_class_constructor_flags(cur); let (final_args, call_synth, call_rest) = if sig_caps == 0 { // No signature cap params: #6018's synth/rest handling applies // cleanly — forward all spread args flat and let @@ -706,13 +712,8 @@ pub unsafe extern "C" fn js_super_construct_apply( let spread: Vec = (0..n) .map(|i| crate::array::js_array_get_f64(arr, i as u32)) .collect(); - let mut fa = constructor_user_arg_slots( - ctor_ptr, - cur, - user_params, - spread.as_ptr(), - spread.len(), - ); + let mut fa = + constructor_user_arg_slots(cur, user_params, spread.as_ptr(), spread.len()); for slot in 0..sig_caps as usize { fa.push(caps.get(slot).map(|b| f64::from_bits(*b)).unwrap_or(undef)); } @@ -1103,11 +1104,10 @@ pub(crate) unsafe fn run_class_constructor_on_this_flat( sig_caps, caps.len(), args_len, - crate::closure::lookup_closure_rest(ctor_ptr as *const u8) + lookup_class_constructor_flags(cur).2 ); } - let mut final_args = - constructor_user_arg_slots(ctor_ptr, cur, user_params, args_ptr, args_len); + let mut final_args = constructor_user_arg_slots(cur, user_params, args_ptr, args_len); for slot in 0..sig_caps as usize { final_args.push(caps.get(slot).map(|b| f64::from_bits(*b)).unwrap_or(undef)); } @@ -1478,8 +1478,7 @@ unsafe fn replay_class_object_constructor_impl( // caps are appended. Without this the rest binds to the first arg as a // scalar (`args`=opts, not [opts]) and `super(...args)` spreads a bare object // → 0x400000000 mis-box → crash (Next.js `new c.AppPageRouteModule({...})`). - let mut final_args = - constructor_user_arg_slots(ctor_ptr, ctor_cid, user_params, args_ptr, args_len); + let mut final_args = constructor_user_arg_slots(ctor_cid, user_params, args_ptr, args_len); // Exactly `sig_caps` trailing cap slots: per-evaluation snapshot first // (class EXPRESSIONS carry `__perry_ctor_caps`), decl-site snapshot second // (class DECLARATIONS reached as heap values), undefined last. @@ -1583,8 +1582,7 @@ pub(crate) unsafe fn replay_registered_class_constructor( // are appended. Without this the rest binds to the first arg as a // scalar (`args`=opts, not [opts]) and `super(...args)` spreads a bare object // → 0x400000000 mis-box → crash (Next.js `new c.AppPageRouteModule({...})`). - let mut final_args = - constructor_user_arg_slots(ctor_ptr, ctor_cid, user_params, args_ptr, args_len); + let mut final_args = constructor_user_arg_slots(ctor_cid, user_params, args_ptr, args_len); for slot in 0..sig_caps as usize { final_args.push(caps.get(slot).map(|b| f64::from_bits(*b)).unwrap_or(undef)); } @@ -1603,13 +1601,10 @@ pub(crate) unsafe fn replay_registered_class_constructor( mod constructor_arg_slot_tests { use super::*; - /// Distinct registry keys per case: `CLASS_CONSTRUCTOR_FLAGS` is - /// process-global and the closure-rest table is keyed by function pointer. - fn key(case: u32) -> (usize, u32) { - // Any stable non-null address works — the helper only READS the - // registrations under this key, it never calls through the pointer. - let ptr = (0x10_484_000usize) + case as usize * 0x40; - (ptr, 10_484_000 + case) + /// A distinct class id per case: `CLASS_CONSTRUCTOR_FLAGS` is + /// process-global. + fn key(case: u32) -> u32 { + 10_484_000 + case } fn array_of(value: f64) -> (usize, u32) { @@ -1626,14 +1621,13 @@ mod constructor_arg_slot_tests { #[test] fn the_synthesized_arguments_slot_takes_every_argument() { - let (ptr, cid) = key(1); + let cid = key(1); // `constructor(p, q)` reading `arguments`: two fixed slots, then the // synthesized array at index 2. - js_register_class_constructor_flags(cid as i64, 1, 0); - crate::closure::js_register_closure_rest(ptr as *const u8, 2); + js_register_class_constructor_flags(cid as i64, 1, 0, 2); let args = [11.0, 22.0, 33.0]; - let slots = unsafe { constructor_user_arg_slots(ptr, cid, 3, args.as_ptr(), args.len()) }; + let slots = unsafe { constructor_user_arg_slots(cid, 3, args.as_ptr(), args.len()) }; assert_eq!(slots.len(), 3); assert_eq!(slots[0], 11.0); assert_eq!(slots[1], 22.0); @@ -1652,24 +1646,23 @@ mod constructor_arg_slot_tests { // Fewer arguments than declared parameters: the fixed slots pad with // `undefined` while `arguments.length` stays at what was passed. let one = [11.0]; - let slots = unsafe { constructor_user_arg_slots(ptr, cid, 3, one.as_ptr(), one.len()) }; + let slots = unsafe { constructor_user_arg_slots(cid, 3, one.as_ptr(), one.len()) }; assert_eq!(slots[1].to_bits(), crate::value::TAG_UNDEFINED); assert_eq!(array_of(slots[2]).1, 1); - let slots = unsafe { constructor_user_arg_slots(ptr, cid, 3, std::ptr::null(), 0) }; + let slots = unsafe { constructor_user_arg_slots(cid, 3, std::ptr::null(), 0) }; assert_eq!(array_of(slots[2]).1, 0); } #[test] fn a_user_rest_and_arguments_constructor_fills_both_arrays() { - let (ptr, cid) = key(2); + let cid = key(2); // `constructor(first, ...rest)` reading `arguments`: one fixed slot, // the rest array at index 1, the full argument list at index 2. - js_register_class_constructor_flags(cid as i64, 1, 1); - crate::closure::js_register_closure_rest(ptr as *const u8, 1); + js_register_class_constructor_flags(cid as i64, 1, 1, 1); let args = [11.0, 22.0, 33.0]; - let slots = unsafe { constructor_user_arg_slots(ptr, cid, 3, args.as_ptr(), args.len()) }; + let slots = unsafe { constructor_user_arg_slots(cid, 3, args.as_ptr(), args.len()) }; assert_eq!(slots.len(), 3); assert_eq!(slots[0], 11.0); assert_eq!(array_of(slots[1]).1, 2, "rest holds the tail only"); @@ -1679,12 +1672,13 @@ mod constructor_arg_slot_tests { #[test] fn an_unflagged_rest_constructor_keeps_tail_only_packing() { - let (ptr, cid) = key(3); - // No flags registered at all — a plain `constructor(a, ...rest)`. - crate::closure::js_register_closure_rest(ptr as *const u8, 1); + let cid = key(3); + // Neither `arguments` nor a rest flag — a plain `constructor(a, ...rest)` + // registered only with its rest position. + js_register_class_constructor_flags(cid as i64, 0, 0, 1); let args = [11.0, 22.0, 33.0]; - let slots = unsafe { constructor_user_arg_slots(ptr, cid, 2, args.as_ptr(), args.len()) }; + let slots = unsafe { constructor_user_arg_slots(cid, 2, args.as_ptr(), args.len()) }; assert_eq!(slots.len(), 2); assert_eq!(slots[0], 11.0); assert_eq!(array_of(slots[1]).1, 2); @@ -1692,11 +1686,11 @@ mod constructor_arg_slot_tests { #[test] fn a_constructor_with_no_trailing_array_stays_positional() { - let (_, cid) = key(4); - // An unregistered constructor pointer: positional, padded to the - // declared parameter count. + let cid = key(4); + // An unregistered constructor: positional, padded to the declared + // parameter count. let args = [11.0]; - let slots = unsafe { constructor_user_arg_slots(0x10_484_900, cid, 2, args.as_ptr(), 1) }; + let slots = unsafe { constructor_user_arg_slots(cid, 2, args.as_ptr(), 1) }; assert_eq!(slots.len(), 2); assert_eq!(slots[0], 11.0); assert_eq!(slots[1].to_bits(), crate::value::TAG_UNDEFINED); diff --git a/crates/perry-runtime/src/object/class_image.rs b/crates/perry-runtime/src/object/class_image.rs index 972bbc5ea9..165310b58d 100644 --- a/crates/perry-runtime/src/object/class_image.rs +++ b/crates/perry-runtime/src/object/class_image.rs @@ -90,9 +90,11 @@ pub(crate) const PARENT_DENSE_CAP: usize = 1 << 16; /// `HashMap` stays on SipHash because its keys are JS-supplied /// member names. pub type StaticMethodTable = PtrHashMap>; -/// class_id -> { name -> (getter func_ptr, setter func_ptr) } for static accessors. +/// class_id -> { name -> accessor halves (and the setter's `.length`) } for +/// static accessors. /// Outer map fast-hashed, inner `String`-keyed map deliberately not — see above. -pub type StaticAccessorTable = PtrHashMap>; +pub type StaticAccessorTable = + PtrHashMap>; /// `(class_id, is_static, property_name) -> source-order token` for declared /// string-keyed methods and accessors. The token is the member function's HIR /// id, which is allocated while walking the class body and therefore orders @@ -100,8 +102,9 @@ pub type StaticAccessorTable = PtrHashMap>; pub type StringMemberOrderTable = HashMap<(u32, bool, String), u32>; /// class_id -> (ctor func_ptr, total param count, signature capture count). pub type ConstructorTable = PtrHashMap; -/// class_id -> (has_synthetic_arguments, has_rest) for a registered constructor. -pub type ConstructorFlagTable = PtrHashMap; +/// class_id -> (has_synthetic_arguments, has_rest, parameters before the first +/// trailing array) for a registered constructor. +pub type ConstructorFlagTable = PtrHashMap)>; /// One compiled image's class metadata: every class-id-keyed table module init /// writes. Field docs live on the `static` handles that select them. diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index dcbfb60604..0aa58f0f63 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -198,7 +198,8 @@ pub(crate) use gc_roots::{ // ── registration.rs ───────────────────────────────────────────────────────── pub(crate) use registration::{ class_accessor_function_value, class_accessor_source_func_ptr, class_own_accessor_ptrs, - class_registered_static_accessor_ptrs, invalidate_class_string_member_order, + class_own_setter_length, class_registered_static_accessor_ptrs, + invalidate_class_string_member_order, }; pub use registration::{ is_class_id_registered, js_register_class_getter, js_register_class_method, diff --git a/crates/perry-runtime/src/object/class_registry/class_meta.rs b/crates/perry-runtime/src/object/class_registry/class_meta.rs index 960939aade..4644a78999 100644 --- a/crates/perry-runtime/src/object/class_registry/class_meta.rs +++ b/crates/perry-runtime/src/object/class_registry/class_meta.rs @@ -335,7 +335,7 @@ pub(crate) fn identify_global_builtin_constructor(func_value: f64) -> Option<&'s if !crate::closure::closure_kind_probe(ptr as usize) { return None; } - let func_ptr = (*ptr).func_ptr as usize; + let func_ptr = (*ptr).code() as usize; let is_global_builtin_func = func_ptr == global_this_builtin_noop_thunk as *const u8 as usize || func_ptr == typed_array_constructor_call_thunk as *const u8 as usize diff --git a/crates/perry-runtime/src/object/class_registry/construct.rs b/crates/perry-runtime/src/object/class_registry/construct.rs index 636dd7afee..7745083a4b 100644 --- a/crates/perry-runtime/src/object/class_registry/construct.rs +++ b/crates/perry-runtime/src/object/class_registry/construct.rs @@ -1386,11 +1386,14 @@ pub(crate) fn extends_target_must_throw(value: f64) -> bool { } // Arrow / async / generator / async-generator function bodies are // non-constructors. - if crate::closure::is_registered_arrow_function(fp) - || crate::closure::is_registered_async_function(fp) - || crate::closure::is_registered_generator_function(fp) - || crate::closure::is_registered_async_generator_function(fp) - { + if crate::closure::closure_info(ptr).is_some_and(|info| { + info.flags + & (crate::closure::FN_ARROW + | crate::closure::FN_ASYNC + | crate::closure::FN_GENERATOR + | crate::closure::FN_ASYNC_GENERATOR) + != 0 + }) { return true; } } diff --git a/crates/perry-runtime/src/object/class_registry/decl_accessors.rs b/crates/perry-runtime/src/object/class_registry/decl_accessors.rs index bfc72cc328..d0b4a52e67 100644 --- a/crates/perry-runtime/src/object/class_registry/decl_accessors.rs +++ b/crates/perry-runtime/src/object/class_registry/decl_accessors.rs @@ -52,7 +52,12 @@ pub(crate) fn install_decl_prototype_accessor(proto: *mut ObjectHeader, class_id } else if raw == have_raw && have != 0 { have } else { - class_accessor_function_value(raw, is_setter, name).to_bits() + let set_length = if is_setter { + class_own_setter_length(class_id, name, false) + } else { + None + }; + class_accessor_function_value(raw, is_setter, name, set_length).to_bits() } }; let get = scope.root_nanbox_u64(half(raw_get, existing.raw_get, existing.get, false)); diff --git a/crates/perry-runtime/src/object/class_registry/function_prototype.rs b/crates/perry-runtime/src/object/class_registry/function_prototype.rs index b0f3229a1a..bcfba6d12d 100644 --- a/crates/perry-runtime/src/object/class_registry/function_prototype.rs +++ b/crates/perry-runtime/src/object/class_registry/function_prototype.rs @@ -81,10 +81,10 @@ fn is_plain_async_function_value(func_value: f64) -> bool { if ptr.is_null() || !crate::value::addr_class::is_plausible_heap_addr(ptr as usize) { return false; } - let fp = crate::closure::get_valid_func_ptr(ptr); - !fp.is_null() - && crate::closure::is_registered_async_function(fp) - && !crate::closure::is_registered_generator_function(fp) + crate::closure::closure_info(ptr).is_some_and(|info| { + crate::closure::info_has(info, crate::closure::FN_ASYNC) + && !crate::closure::info_has(info, crate::closure::FN_GENERATOR) + }) } #[no_mangle] diff --git a/crates/perry-runtime/src/object/class_registry/parent_static.rs b/crates/perry-runtime/src/object/class_registry/parent_static.rs index b06a4522c4..81f710c844 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static.rs @@ -868,12 +868,12 @@ pub unsafe extern "C" fn js_register_class_computed_accessor( .entry(class_id) .or_default() .entry(name.clone()) - .or_insert((0, 0)); + .or_default(); if getter_ptr != 0 { - entry.0 = getter_ptr as usize; + entry.get = getter_ptr as usize; } if setter_ptr != 0 { - entry.1 = setter_ptr as usize; + entry.set = setter_ptr as usize; } } crate::object::class_value::note_intrinsic_registration(class_id, &name); diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index 1262ec73fb..b05650543f 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -201,14 +201,37 @@ pub(crate) fn class_registered_static_accessor_ptrs( ) -> Option<(usize, usize)> { let guard = CLASS_STATIC_ACCESSORS.read().ok()?; let reg = guard.as_ref()?; - let pair = reg.get(&class_id)?.get(name).copied()?; - if pair.0 == 0 && pair.1 == 0 { - None + let decl = reg.get(&class_id)?.get(name).copied()?; + (decl.get != 0 || decl.set != 0).then_some((decl.get, decl.set)) +} + +/// The recorded spec `.length` of `class_id`'s own setter `name` (static when +/// `is_static`), for [`class_accessor_function_value`]. +pub(crate) fn class_own_setter_length(class_id: u32, name: &str, is_static: bool) -> Option { + if is_static { + let guard = CLASS_STATIC_ACCESSORS.read().ok()?; + guard.as_ref()?.get(&class_id)?.get(name)?.set_length } else { - Some(pair) + let guard = CLASS_VTABLE_REGISTRY.read().ok()?; + guard + .as_ref()? + .get(&class_id)? + .accessor_decl(name)? + .set_length } } +/// The accessor trampolines' infos: every reflected accessor value shares one +/// per body, so [`class_accessor_source_func_ptr`]'s code compare holds. +static CLASS_ACCESSOR_GETTER_THUNK_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + class_accessor_getter_thunk as crate::codegen_abi::JsBody0, + ); +static CLASS_ACCESSOR_SETTER_THUNK_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + class_accessor_setter_thunk as crate::codegen_abi::JsBody1, + ); + /// Trampoline giving a raw vtable getter func_ptr (`fn(this) -> f64`) the /// closure calling convention. The receiver is the `this` argument passed /// by the method-call dispatch the closure value travels through. @@ -249,7 +272,7 @@ pub(crate) unsafe fn class_accessor_source_func_ptr( if closure.is_null() || crate::closure::real_capture_count((*closure).capture_count) < 1 { return None; } - let thunk = (*closure).func_ptr; + let thunk = (*closure).code(); if thunk != class_accessor_getter_thunk as *const u8 && thunk != class_accessor_setter_thunk as *const u8 { @@ -266,18 +289,22 @@ pub(crate) unsafe fn class_accessor_source_func_ptr( /// `.name` of a `get`/`set` accessor is the key prefixed with `"get "`/`"set "` /// (Function Definitions: SetFunctionName with the "get"/"set" prefix), e.g. /// `Object.getOwnPropertyDescriptor(C.prototype, "x").get.name === "get x"`. +/// +/// `setter_length` is the setter's spec `.length` as the class accessor table +/// recorded it ([`AccessorDecl::set_length`]); ignored for a getter. pub(crate) fn class_accessor_function_value( raw_ptr: usize, is_setter: bool, prop_name: &str, + setter_length: Option, ) -> f64 { if raw_ptr == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); } let thunk = if is_setter { - class_accessor_setter_thunk as *const u8 + &CLASS_ACCESSOR_SETTER_THUNK_INFO } else { - class_accessor_getter_thunk as *const u8 + &CLASS_ACCESSOR_GETTER_THUNK_INFO }; let closure = crate::closure::js_closure_alloc(thunk, 1); if closure.is_null() { @@ -286,13 +313,15 @@ pub(crate) fn class_accessor_function_value( crate::closure::js_closure_set_capture_ptr(closure, 0, raw_ptr as i64); // Spec `.length`: params before the first default/rest. A getter takes no // params (0); a setter takes exactly one formal param — but `set m(x = 42)` - // has `.length === 0` (defaults don't count). Codegen registers the raw - // accessor func_ptr's default-aware spec length via - // `js_register_closure_length`; consult it so a defaulted setter reports 0. - // Fall back to the fixed 1/0 when no registration exists (e.g. native or - // cross-module accessors whose length wasn't emitted). - let spec_length = crate::closure::lookup_closure_length(raw_ptr as *const u8) - .unwrap_or(if is_setter { 1 } else { 0 }); + // has `.length === 0` (defaults don't count). Codegen records the setter's + // default-aware spec length with the setter in the class accessor table + // (`js_register_class_setter`'s `spec_length`). Fall back to the fixed 1 + // when none was recorded (e.g. a `defineProperty`-installed half). + let spec_length = if is_setter { + setter_length.unwrap_or(1) + } else { + 0 + }; super::super::native_module::set_builtin_closure_length(closure as usize, spec_length); super::super::native_module::set_builtin_closure_non_constructable(closure as usize); // Spec `.name` = "get " / "set " with attributes @@ -356,11 +385,15 @@ pub unsafe extern "C" fn js_register_class_getter( /// Setter signature: `fn(this_f64, value_f64) -> f64` (returns ignored, but /// codegen emits a return so the LLVM signature matches a regular method body). #[no_mangle] +/// +/// `spec_length` is the setter's spec `.length` (0 for `set m(x = 1)`), kept +/// with the setter for descriptor reflection. pub unsafe extern "C" fn js_register_class_setter( class_id: i64, name_ptr: *const u8, name_len: i64, func_ptr: i64, + spec_length: i32, ) { // `name_len == 0` is a legal empty-string member key (`get ''()`), so only // reject a negative length / null pointer. @@ -378,7 +411,12 @@ pub unsafe extern "C" fn js_register_class_setter( } let reg = registry.as_mut().unwrap(); let vtable = reg.entry(class_id as u32).or_default(); - vtable.declare_accessor_half(&name, func_ptr as usize, true); + vtable.declare_accessor_half_with_length( + &name, + func_ptr as usize, + true, + u32::try_from(spec_length).ok(), + ); VTABLE_GEN.fetch_add(1, Ordering::Release); super::verdict_classes::note_verdict_class_accessor_change(class_id as u32); drop(registry); @@ -398,7 +436,7 @@ pub unsafe extern "C" fn js_register_class_static_getter( name_len: i64, func_ptr: i64, ) { - register_class_static_accessor_half(class_id, name_ptr, name_len, func_ptr, true); + register_class_static_accessor_half(class_id, name_ptr, name_len, func_ptr, true, None); } /// Register a `static set name(v)` accessor. See `js_register_class_static_getter`. @@ -408,8 +446,16 @@ pub unsafe extern "C" fn js_register_class_static_setter( name_ptr: *const u8, name_len: i64, func_ptr: i64, + spec_length: i32, ) { - register_class_static_accessor_half(class_id, name_ptr, name_len, func_ptr, false); + register_class_static_accessor_half( + class_id, + name_ptr, + name_len, + func_ptr, + false, + u32::try_from(spec_length).ok(), + ); } // These two are only ever called from codegen-emitted module-init IR (no Rust @@ -421,7 +467,7 @@ static KEEP_REGISTER_STATIC_GETTER: unsafe extern "C" fn(i64, *const u8, i64, i6 js_register_class_static_getter; #[cfg(feature = "keepalive-anchors")] #[used(compiler)] -static KEEP_REGISTER_STATIC_SETTER: unsafe extern "C" fn(i64, *const u8, i64, i64) = +static KEEP_REGISTER_STATIC_SETTER: unsafe extern "C" fn(i64, *const u8, i64, i64, i32) = js_register_class_static_setter; /// Record the spec `.length` (params before the first default/rest) for a class @@ -498,6 +544,7 @@ unsafe fn register_class_static_accessor_half( name_len: i64, func_ptr: i64, is_getter: bool, + set_length: Option, ) { // Empty-string keys (`static get ''()`) are legal — admit `name_len == 0` // as long as the pointer is non-null. @@ -520,11 +567,12 @@ unsafe fn register_class_static_accessor_half( .entry(class_id as u32) .or_default() .entry(name.clone()) - .or_insert((0, 0)); + .or_default(); if is_getter { - entry.0 = func_ptr as usize; + entry.get = func_ptr as usize; } else { - entry.1 = func_ptr as usize; + entry.set = func_ptr as usize; + entry.set_length = set_length; } } VTABLE_GEN.fetch_add(1, Ordering::Release); diff --git a/crates/perry-runtime/src/object/class_registry/state.rs b/crates/perry-runtime/src/object/class_registry/state.rs index 69a5583a89..886b33d3bd 100644 --- a/crates/perry-runtime/src/object/class_registry/state.rs +++ b/crates/perry-runtime/src/object/class_registry/state.rs @@ -296,10 +296,13 @@ pub struct VTableMethodEntry { /// The compiled halves of one declared accessor, each 0 when that half is /// absent: `get` is `fn(this) -> f64`, `set` is `fn(this, value) -> f64`. +/// `set_length` is the setter's spec `.length` (0 for `set m(x = 1)`), when +/// codegen recorded one. #[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] pub struct AccessorDecl { pub get: usize, pub set: usize, + pub set_length: Option, } /// Per-class vtable: the method dispatch table plus the class's accessor @@ -328,6 +331,17 @@ impl ClassVTable { /// Record one compiled half of the accessor `name` (`#x` goes to the /// private record). A zero pointer records nothing. pub(crate) fn declare_accessor_half(&mut self, name: &str, func_ptr: usize, is_setter: bool) { + self.declare_accessor_half_with_length(name, func_ptr, is_setter, None); + } + + /// [`Self::declare_accessor_half`] recording a setter's spec `.length`. + pub(crate) fn declare_accessor_half_with_length( + &mut self, + name: &str, + func_ptr: usize, + is_setter: bool, + set_length: Option, + ) { if func_ptr == 0 { return; } @@ -339,6 +353,7 @@ impl ClassVTable { let decl = table.entry(name.to_string()).or_default(); if is_setter { decl.set = func_ptr; + decl.set_length = set_length; } else { decl.get = func_ptr; } @@ -380,8 +395,8 @@ pub static CLASS_VTABLE_REGISTRY: ImageTable< pub static CLASS_STATIC_METHODS: ImageTable>> = ImageTable::new(|image| &image.static_methods); -/// Static accessors on the class constructor: class_id -> { name -> (getter -/// func_ptr, setter func_ptr) }, each 0 when that half is absent. +/// Static accessors on the class constructor: class_id -> { name -> +/// [`AccessorDecl`] }, each half 0 when absent. pub static CLASS_STATIC_ACCESSORS: ImageTable>> = ImageTable::new(|image| &image.static_accessors); diff --git a/crates/perry-runtime/src/object/class_value.rs b/crates/perry-runtime/src/object/class_value.rs index 68606494dd..eb2cf4f30e 100644 --- a/crates/perry-runtime/src/object/class_value.rs +++ b/crates/perry-runtime/src/object/class_value.rs @@ -64,9 +64,11 @@ pub fn class_closure_id(ptr: usize) -> Option { return None; } // SAFETY: an exotic-band ShapeId word means a closure-or-exotic header, - // at least 16 bytes; +8 is the code pointer of a closure. - let code = unsafe { *((ptr as *const u8).add(8) as *const *const u8) }; - if code != js_class_constructor_called as *const u8 { + // at least 16 bytes; +8 is the info word of a closure (compared, never + // dereferenced here). + let info = + unsafe { *((ptr as *const u8).add(8) as *const *const crate::closure::JsFunctionInfo) }; + if !std::ptr::eq(info, &CLASS_CONSTRUCTOR_INFO) { return None; } class_closure_id_exotic(ptr) @@ -90,7 +92,7 @@ fn class_closure_id_exotic(ptr: usize) -> Option { /// `closure` is a live, non-forwarded `GC_TYPE_CLOSURE` cell. #[inline] pub(crate) unsafe fn class_closure_id_unchecked(closure: *const ClosureHeader) -> Option { - if (*closure).func_ptr != js_class_constructor_called as *const u8 { + if !std::ptr::eq((*closure).info, &CLASS_CONSTRUCTOR_INFO) { return None; } let slot0 = *((closure as *const u8).add(std::mem::size_of::()) as *const u64); @@ -113,6 +115,14 @@ pub unsafe extern "C" fn js_class_constructor_called( crate::node_submodules::diagnostics::throw_type_error_no_code(message.as_bytes()) } +/// The one info of every class function object: its body is +/// [`js_class_constructor_called`]. A class function object is recognized by +/// its info word being exactly this static (one pointer compare, no deref). +pub(crate) static CLASS_CONSTRUCTOR_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + js_class_constructor_called as crate::codegen_abi::JsBody0, + ); + /// The PRE-MIGRATION gate spelling, kept exact for the legacy form while it /// also admits the function-object form: any INT32 word (registered or not, /// either half — those gates accepted every `0x7FFE` word, which is #11414) @@ -244,7 +254,7 @@ fn class_value_mint(class_id: u32) -> *mut ClosureHeader { // is an INT32 class id and the props edge is null — pointer-free. (*ptr).capture_count = 1; (*ptr).shape_id = crate::closure::shape::function_class_shape(); - (*ptr).func_ptr = js_class_constructor_called as *const u8; + (*ptr).info = &CLASS_CONSTRUCTOR_INFO; (*ptr).props = std::ptr::null_mut(); std::ptr::write( crate::closure::closure_capture_slots_mut(ptr), @@ -589,7 +599,18 @@ fn install_declared_static_accessor(class_id: u32, name: &str) { } else if raw == have_raw && have != 0 { have } else { - super::class_registry::class_accessor_function_value(raw, is_setter, name).to_bits() + let setter_length = if is_setter { + super::class_registry::class_own_setter_length(class_id, name, true) + } else { + None + }; + super::class_registry::class_accessor_function_value( + raw, + is_setter, + name, + setter_length, + ) + .to_bits() } }; let get = half(raw_get, have.raw_get, have.get, false); @@ -988,8 +1009,10 @@ mod tests { class_static_set(cid, "s", 1.0); crate::closure::shape::note_function_own_state_changed(ptr as usize); assert_eq!(unsafe { (*ptr).shape_id }, class_shape, "sticky"); - extern "C" fn body() {} - let f = crate::closure::js_closure_alloc(body as *const u8, 0); + extern "C" fn body(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { + 0.0 + } + let f = crate::closure::js_closure_alloc(crate::fn_info!(body, 0), 0); crate::closure::shape::note_function_own_state_changed(f as usize); assert_ne!( unsafe { (*f).shape_id }, @@ -1098,8 +1121,10 @@ mod tests { /// Only the function object's own code pointer names a class. #[test] fn ordinary_closures_and_numbers_are_not_class_values() { - extern "C" fn body() {} - let c = crate::closure::js_closure_alloc(body as *const u8, 0); + extern "C" fn body(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { + 0.0 + } + let c = crate::closure::js_closure_alloc(crate::fn_info!(body, 0), 0); assert_eq!(class_closure_id(c as usize), None); assert_eq!(class_value_id(42.0), None); assert_eq!( diff --git a/crates/perry-runtime/src/object/collection_proto_thunks.rs b/crates/perry-runtime/src/object/collection_proto_thunks.rs index 84ed851edc..3b20d66df8 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks.rs @@ -123,27 +123,62 @@ pub(super) fn install_collection_proto_methods( install_collection_size_getter( proto_obj, "size", - map_proto_size_getter_thunk as *const u8, + crate::fn_info!(map_proto_size_getter_thunk, 0; with_declared(0)), + ); + ipm( + proto_obj, + "clear", + crate::fn_info!(map_proto_clear_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + ipm( + proto_obj, + "delete", + crate::fn_info!(map_proto_delete_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, ); - ipm(proto_obj, "clear", map_proto_clear_thunk as *const u8, 0); - ipm(proto_obj, "delete", map_proto_delete_thunk as *const u8, 1); let entries_value = ipm( proto_obj, "entries", - map_proto_entries_thunk as *const u8, + crate::fn_info!(map_proto_entries_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); ipm( proto_obj, "forEach", - map_proto_foreach_thunk as *const u8, + crate::fn_info!(map_proto_foreach_thunk, 2; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); - ipm(proto_obj, "get", map_proto_get_thunk as *const u8, 1); - ipm(proto_obj, "has", map_proto_has_thunk as *const u8, 1); - ipm(proto_obj, "keys", map_proto_keys_thunk as *const u8, 0); - let set_value = ipm(proto_obj, "set", map_proto_set_thunk as *const u8, 2); - ipm(proto_obj, "values", map_proto_values_thunk as *const u8, 0); + ipm( + proto_obj, + "get", + crate::fn_info!(map_proto_get_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "has", + crate::fn_info!(map_proto_has_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "keys", + crate::fn_info!(map_proto_keys_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + let set_value = ipm( + proto_obj, + "set", + crate::fn_info!(map_proto_set_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + ipm( + proto_obj, + "values", + crate::fn_info!(map_proto_values_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); install_collection_iterator_symbol(proto_obj, entries_value); remember_builtin_collection_method( proto_obj, @@ -157,26 +192,56 @@ pub(super) fn install_collection_proto_methods( install_collection_size_getter( proto_obj, "size", - set_proto_size_getter_thunk as *const u8, + crate::fn_info!(set_proto_size_getter_thunk, 0; with_declared(0)), + ); + let add_value = ipm( + proto_obj, + "add", + crate::fn_info!(set_proto_add_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "clear", + crate::fn_info!(set_proto_clear_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + ipm( + proto_obj, + "delete", + crate::fn_info!(set_proto_delete_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, ); - let add_value = ipm(proto_obj, "add", set_proto_add_thunk as *const u8, 1); - ipm(proto_obj, "clear", set_proto_clear_thunk as *const u8, 0); - ipm(proto_obj, "delete", set_proto_delete_thunk as *const u8, 1); ipm( proto_obj, "entries", - set_proto_entries_thunk as *const u8, + crate::fn_info!(set_proto_entries_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); ipm( proto_obj, "forEach", - set_proto_foreach_thunk as *const u8, + crate::fn_info!(set_proto_foreach_thunk, 2; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "has", + crate::fn_info!(set_proto_has_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); - ipm(proto_obj, "has", set_proto_has_thunk as *const u8, 1); - ipm(proto_obj, "keys", set_proto_keys_thunk as *const u8, 0); - let values_value = ipm(proto_obj, "values", set_proto_values_thunk as *const u8, 0); + ipm( + proto_obj, + "keys", + crate::fn_info!(set_proto_keys_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + let values_value = ipm( + proto_obj, + "values", + crate::fn_info!(set_proto_values_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); // ECMA-262 specifies these as the same function object, not merely // two functions with equivalent behavior. let keys_key = crate::string::js_string_from_bytes(b"keys".as_ptr(), 4); @@ -193,22 +258,47 @@ pub(super) fn install_collection_proto_methods( ipm( proto_obj, "delete", - weakmap_proto_delete_thunk as *const u8, + crate::fn_info!(weakmap_proto_delete_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); - ipm(proto_obj, "get", weakmap_proto_get_thunk as *const u8, 1); - ipm(proto_obj, "has", weakmap_proto_has_thunk as *const u8, 1); - ipm(proto_obj, "set", weakmap_proto_set_thunk as *const u8, 2); + ipm( + proto_obj, + "get", + crate::fn_info!(weakmap_proto_get_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "has", + crate::fn_info!(weakmap_proto_has_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "set", + crate::fn_info!(weakmap_proto_set_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); } "WeakSet" => { - ipm(proto_obj, "add", weakset_proto_add_thunk as *const u8, 1); + ipm( + proto_obj, + "add", + crate::fn_info!(weakset_proto_add_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); ipm( proto_obj, "delete", - weakset_proto_delete_thunk as *const u8, + crate::fn_info!(weakset_proto_delete_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "has", + crate::fn_info!(weakset_proto_has_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); - ipm(proto_obj, "has", weakset_proto_has_thunk as *const u8, 1); } _ => return false, } @@ -237,13 +327,16 @@ fn install_collection_iterator_symbol(proto_obj: *mut ObjectHeader, method_value ); } -fn install_collection_size_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) { +fn install_collection_size_getter( + proto_obj: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { if proto_obj.is_null() { return; } unsafe { - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } diff --git a/crates/perry-runtime/src/object/dataview_proto_thunks.rs b/crates/perry-runtime/src/object/dataview_proto_thunks.rs index 797faa9949..284b00057f 100644 --- a/crates/perry-runtime/src/object/dataview_proto_thunks.rs +++ b/crates/perry-runtime/src/object/dataview_proto_thunks.rs @@ -200,8 +200,7 @@ pub(crate) fn install_dataview_proto_methods(proto_obj: *mut ObjectHeader) { // Accessor getters (0-arg). Reflect as `{ get, set: undefined, // enumerable: false, configurable: true }`. unsafe { - let mk = |f: *const u8| -> u64 { - crate::closure::js_register_closure_arity(f, 0); + let mk = |f: *const crate::closure::JsFunctionInfo| -> u64 { let c = crate::closure::js_closure_alloc(f, 0); if c.is_null() { 0 @@ -209,15 +208,15 @@ pub(crate) fn install_dataview_proto_methods(proto_obj: *mut ObjectHeader) { crate::value::js_nanbox_pointer(c as i64).to_bits() } }; - let bl = mk(dataview_byte_length_getter_thunk as *const u8); + let bl = mk(crate::fn_info!(dataview_byte_length_getter_thunk, 0; with_declared(0))); if bl != 0 { super::object_ops::install_builtin_getter(proto_obj, "byteLength", bl); } - let bo = mk(dataview_byte_offset_getter_thunk as *const u8); + let bo = mk(crate::fn_info!(dataview_byte_offset_getter_thunk, 0; with_declared(0))); if bo != 0 { super::object_ops::install_builtin_getter(proto_obj, "byteOffset", bo); } - let bf = mk(dataview_buffer_getter_thunk as *const u8); + let bf = mk(crate::fn_info!(dataview_buffer_getter_thunk, 0; with_declared(0))); if bf != 0 { super::object_ops::install_builtin_getter(proto_obj, "buffer", bf); } @@ -225,38 +224,98 @@ pub(crate) fn install_dataview_proto_methods(proto_obj: *mut ObjectHeader) { // get* methods: spec `.length === 1`, fixed call arity 1 (`byteOffset`), // the optional `littleEndian` flag is collected into `rest`. - let gets: &[(&str, *const u8)] = &[ - ("getInt8", dv_get_int8 as *const u8), - ("getUint8", dv_get_uint8 as *const u8), - ("getInt16", dv_get_int16 as *const u8), - ("getUint16", dv_get_uint16 as *const u8), - ("getInt32", dv_get_int32 as *const u8), - ("getUint32", dv_get_uint32 as *const u8), - ("getFloat32", dv_get_float32 as *const u8), - ("getFloat64", dv_get_float64 as *const u8), - ("getBigInt64", dv_get_bigint64 as *const u8), - ("getBigUint64", dv_get_biguint64 as *const u8), + let gets: &[(&str, *const crate::closure::JsFunctionInfo)] = &[ + ( + "getInt8", + crate::fn_info!(dv_get_int8, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUint8", + crate::fn_info!(dv_get_uint8, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getInt16", + crate::fn_info!(dv_get_int16, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUint16", + crate::fn_info!(dv_get_uint16, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getInt32", + crate::fn_info!(dv_get_int32, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUint32", + crate::fn_info!(dv_get_uint32, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getFloat32", + crate::fn_info!(dv_get_float32, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getFloat64", + crate::fn_info!(dv_get_float64, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getBigInt64", + crate::fn_info!(dv_get_bigint64, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getBigUint64", + crate::fn_info!(dv_get_biguint64, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + ), ]; for (name, ptr) in gets.iter().copied() { - install_proto_method_rest_with_length(proto_obj, name, ptr, 1, 1); + install_proto_method_rest_with_length(proto_obj, name, ptr, 1); } // set* methods: spec `.length === 2`, fixed call arity 2 // (`byteOffset`, `value`), the optional `littleEndian` flag → `rest`. - let sets: &[(&str, *const u8)] = &[ - ("setInt8", dv_set_int8 as *const u8), - ("setUint8", dv_set_uint8 as *const u8), - ("setInt16", dv_set_int16 as *const u8), - ("setUint16", dv_set_uint16 as *const u8), - ("setInt32", dv_set_int32 as *const u8), - ("setUint32", dv_set_uint32 as *const u8), - ("setFloat32", dv_set_float32 as *const u8), - ("setFloat64", dv_set_float64 as *const u8), - ("setBigInt64", dv_set_bigint64 as *const u8), - ("setBigUint64", dv_set_biguint64 as *const u8), + let sets: &[(&str, *const crate::closure::JsFunctionInfo)] = &[ + ( + "setInt8", + crate::fn_info!(dv_set_int8, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setUint8", + crate::fn_info!(dv_set_uint8, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setInt16", + crate::fn_info!(dv_set_int16, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setUint16", + crate::fn_info!(dv_set_uint16, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setInt32", + crate::fn_info!(dv_set_int32, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setUint32", + crate::fn_info!(dv_set_uint32, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setFloat32", + crate::fn_info!(dv_set_float32, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setFloat64", + crate::fn_info!(dv_set_float64, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setBigInt64", + crate::fn_info!(dv_set_bigint64, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "setBigUint64", + crate::fn_info!(dv_set_biguint64, 3; with_rest(2), with_flags(crate::closure::FN_BUILTIN)), + ), ]; for (name, ptr) in sets.iter().copied() { - // `install_proto_method_rest` registers spec_length == call_fixed_arity. + // `.length` == the fixed arity (`install_proto_method_rest`). install_proto_method_rest(proto_obj, name, ptr, 2); } } diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index f746764445..e6e771be46 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -347,9 +347,10 @@ pub(crate) fn install_date_proto_to_primitive(proto_obj: *mut ObjectHeader) { if proto_obj.is_null() { return; } - let func_ptr = date_to_primitive as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(date_to_primitive, 1; with_declared(1)), + 0, + ); if closure.is_null() { return; } @@ -432,29 +433,25 @@ pub(crate) fn install_date_constructor_statics(ctor: *mut crate::closure::Closur super::global_this::install_constructor_static( ctor, "now", - date_now_static as *const u8, + crate::fn_info!(date_now_static, 0; with_declared(0)), 0, - false, ); super::global_this::install_constructor_static( ctor, "parse", - date_parse_static as *const u8, + crate::fn_info!(date_parse_static, 1; with_declared(1)), 1, - false, ); // `date_utc_static` collects *all* arguments into its `rest` param, so the // call-arity (fixed params before the rest) must be 0 — otherwise the rest // registration reserves 7 fixed slots and `Date.UTC(2020, 0)` (fewer than 7 // args) puts nothing in the rest array → js_date_utc([]) → NaN. The spec // `.length` stays 7. (#4596) - super::global_this::install_constructor_static_with_call_arity( + super::global_this::install_constructor_static( ctor, "UTC", - date_utc_static as *const u8, + crate::fn_info!(date_utc_static, 1; with_rest(0)), 7, - 0, - true, ); } @@ -478,30 +475,90 @@ pub(crate) fn install_date_proto_getters(proto_obj: *mut ObjectHeader) { if proto_obj.is_null() { return; } - let methods: &[(&str, *const u8)] = &[ - ("getTime", date_get_time as *const u8), - ("valueOf", date_get_time as *const u8), - ("getFullYear", date_get_full_year as *const u8), - ("getMonth", date_get_month as *const u8), - ("getDate", date_get_date as *const u8), - ("getHours", date_get_hours as *const u8), - ("getMinutes", date_get_minutes as *const u8), - ("getSeconds", date_get_seconds as *const u8), - ("getMilliseconds", date_get_milliseconds as *const u8), - ("getDay", date_get_day as *const u8), - ("getUTCFullYear", date_get_utc_full_year as *const u8), - ("getUTCMonth", date_get_utc_month as *const u8), - ("getUTCDate", date_get_utc_date as *const u8), - ("getUTCHours", date_get_utc_hours as *const u8), - ("getUTCMinutes", date_get_utc_minutes as *const u8), - ("getUTCSeconds", date_get_utc_seconds as *const u8), - ("getUTCMilliseconds", date_get_utc_milliseconds as *const u8), - ("getUTCDay", date_get_utc_day as *const u8), - ("getTimezoneOffset", date_get_timezone_offset as *const u8), - ("getYear", date_get_year as *const u8), + let methods: &[(&str, *const crate::closure::JsFunctionInfo)] = &[ + ( + "getTime", + crate::fn_info!(date_get_time, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "valueOf", + crate::fn_info!(date_get_time, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getFullYear", + crate::fn_info!(date_get_full_year, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getMonth", + crate::fn_info!(date_get_month, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getDate", + crate::fn_info!(date_get_date, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getHours", + crate::fn_info!(date_get_hours, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getMinutes", + crate::fn_info!(date_get_minutes, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getSeconds", + crate::fn_info!(date_get_seconds, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getMilliseconds", + crate::fn_info!(date_get_milliseconds, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getDay", + crate::fn_info!(date_get_day, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCFullYear", + crate::fn_info!(date_get_utc_full_year, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCMonth", + crate::fn_info!(date_get_utc_month, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCDate", + crate::fn_info!(date_get_utc_date, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCHours", + crate::fn_info!(date_get_utc_hours, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCMinutes", + crate::fn_info!(date_get_utc_minutes, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCSeconds", + crate::fn_info!(date_get_utc_seconds, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCMilliseconds", + crate::fn_info!(date_get_utc_milliseconds, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getUTCDay", + crate::fn_info!(date_get_utc_day, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getTimezoneOffset", + crate::fn_info!(date_get_timezone_offset, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), + ( + "getYear", + crate::fn_info!(date_get_year, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + ), ]; - for (name, ptr) in methods.iter().copied() { - super::global_this::install_proto_method(proto_obj, name, ptr, 0); + for (name, info) in methods.iter().copied() { + super::global_this::install_proto_method(proto_obj, name, info, 0); } // String-returning / generic methods that also need real reflective thunks // (overwriting their no-op entries). `toJSON` is generic (`.length === 1`), @@ -509,20 +566,25 @@ pub(crate) fn install_date_proto_getters(proto_obj: *mut ObjectHeader) { super::global_this::install_proto_method( proto_obj, "toISOString", - date_to_iso_string as *const u8, + crate::fn_info!(date_to_iso_string, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); - super::global_this::install_proto_method(proto_obj, "toJSON", date_to_json as *const u8, 1); + super::global_this::install_proto_method( + proto_obj, + "toJSON", + crate::fn_info!(date_to_json, 0; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); super::global_this::install_proto_method( proto_obj, "toTemporalInstant", - date_to_temporal_instant as *const u8, + crate::fn_info!(date_to_temporal_instant, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); let utc = super::global_this::install_proto_method( proto_obj, "toUTCString", - date_to_utc_string as *const u8, + crate::fn_info!(date_to_utc_string, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // Annex B: `Date.prototype.toGMTString` is the SAME function object as @@ -691,32 +753,92 @@ pub(crate) fn install_date_proto_setters(proto_obj: *mut ObjectHeader) { return; } // (name, func_ptr, spec `.length`) - let methods: &[(&str, *const u8, u32)] = &[ - ("setTime", date_set_time as *const u8, 1), - ("setFullYear", date_set_full_year as *const u8, 3), - ("setMonth", date_set_month as *const u8, 2), - ("setDate", date_set_date as *const u8, 1), - ("setHours", date_set_hours as *const u8, 4), - ("setMinutes", date_set_minutes as *const u8, 3), - ("setSeconds", date_set_seconds as *const u8, 2), - ("setMilliseconds", date_set_milliseconds as *const u8, 1), - ("setYear", date_set_year as *const u8, 1), - ("setUTCFullYear", date_set_utc_full_year as *const u8, 3), - ("setUTCMonth", date_set_utc_month as *const u8, 2), - ("setUTCDate", date_set_utc_date as *const u8, 1), - ("setUTCHours", date_set_utc_hours as *const u8, 4), - ("setUTCMinutes", date_set_utc_minutes as *const u8, 3), - ("setUTCSeconds", date_set_utc_seconds as *const u8, 2), + let methods: &[(&str, *const crate::closure::JsFunctionInfo, u32)] = &[ + ( + "setTime", + crate::fn_info!(date_set_time, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "setFullYear", + crate::fn_info!(date_set_full_year, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "setMonth", + crate::fn_info!(date_set_month, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "setDate", + crate::fn_info!(date_set_date, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "setHours", + crate::fn_info!(date_set_hours, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 4, + ), + ( + "setMinutes", + crate::fn_info!(date_set_minutes, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "setSeconds", + crate::fn_info!(date_set_seconds, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "setMilliseconds", + crate::fn_info!(date_set_milliseconds, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "setYear", + crate::fn_info!(date_set_year, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "setUTCFullYear", + crate::fn_info!(date_set_utc_full_year, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "setUTCMonth", + crate::fn_info!(date_set_utc_month, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "setUTCDate", + crate::fn_info!(date_set_utc_date, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "setUTCHours", + crate::fn_info!(date_set_utc_hours, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 4, + ), + ( + "setUTCMinutes", + crate::fn_info!(date_set_utc_minutes, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "setUTCSeconds", + crate::fn_info!(date_set_utc_seconds, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), ( "setUTCMilliseconds", - date_set_utc_milliseconds as *const u8, + crate::fn_info!(date_set_utc_milliseconds, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 1, ), ]; - for (name, ptr, length) in methods.iter().copied() { - // call_fixed_arity = 0: every argument arrives in the `rest` array, so - // one thunk shape covers the 0..=4-arg setters uniformly. - super::global_this::install_proto_method_rest_with_length(proto_obj, name, ptr, length, 0); + for (name, info, length) in methods.iter().copied() { + // Rest at 0: every argument arrives in the `rest` array, so one thunk + // shape covers the 0..=4-arg setters uniformly. + super::global_this::install_proto_method_rest_with_length(proto_obj, name, info, length); } } @@ -731,8 +853,7 @@ pub(crate) fn install_date_proto_to_locale_string(proto_obj: *mut ObjectHeader) super::global_this::install_proto_method_rest_with_length( proto_obj, "toLocaleString", - date_to_locale_string_opts as *const u8, - 0, + crate::fn_info!(date_to_locale_string_opts, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // #5800 follow-up: `toLocaleDateString` / `toLocaleTimeString` with @@ -742,15 +863,13 @@ pub(crate) fn install_date_proto_to_locale_string(proto_obj: *mut ObjectHeader) super::global_this::install_proto_method_rest_with_length( proto_obj, "toLocaleDateString", - date_to_locale_date_string_opts as *const u8, - 0, + crate::fn_info!(date_to_locale_date_string_opts, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); super::global_this::install_proto_method_rest_with_length( proto_obj, "toLocaleTimeString", - date_to_locale_time_string_opts as *const u8, - 0, + crate::fn_info!(date_to_locale_time_string_opts, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } diff --git a/crates/perry-runtime/src/object/descriptors.rs b/crates/perry-runtime/src/object/descriptors.rs index 68409e3918..8ed0e47fcd 100644 --- a/crates/perry-runtime/src/object/descriptors.rs +++ b/crates/perry-runtime/src/object/descriptors.rs @@ -711,8 +711,7 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu )) } else { let func_ptr = (*(ptr as *const crate::closure::ClosureHeader)) - .func_ptr - as usize; + .code() as usize; let fname = crate::builtins::function_name_for_ptr(func_ptr) .unwrap_or_default(); let s = crate::string::js_string_from_bytes( diff --git a/crates/perry-runtime/src/object/descriptors/builders.rs b/crates/perry-runtime/src/object/descriptors/builders.rs index b6b73845c4..af30665e6f 100644 --- a/crates/perry-runtime/src/object/descriptors/builders.rs +++ b/crates/perry-runtime/src/object/descriptors/builders.rs @@ -70,8 +70,18 @@ pub(crate) unsafe fn symbol_own_property_descriptor(obj_value: f64, key_value: f super::class_registry::class_own_symbol_accessor_ptrs(cid, sym_key, is_static) { return build_accessor_descriptor( - super::class_registry::class_accessor_function_value(get, false, &display_name), - super::class_registry::class_accessor_function_value(set, true, &display_name), + super::class_registry::class_accessor_function_value( + get, + false, + &display_name, + None, + ), + super::class_registry::class_accessor_function_value( + set, + true, + &display_name, + None, + ), false, true, ); diff --git a/crates/perry-runtime/src/object/disposable_proto_thunks.rs b/crates/perry-runtime/src/object/disposable_proto_thunks.rs index 91b533c2c9..36b98260da 100644 --- a/crates/perry-runtime/src/object/disposable_proto_thunks.rs +++ b/crates/perry-runtime/src/object/disposable_proto_thunks.rs @@ -164,13 +164,15 @@ pub(super) extern "C" fn ads_proto_disposed_getter_thunk( } /// Install a `disposed` accessor (getter only) onto a stack prototype. -fn install_disposed_getter(proto_obj: *mut ObjectHeader, func_ptr: *const u8) { +fn install_disposed_getter( + proto_obj: *mut ObjectHeader, + info: *const crate::closure::JsFunctionInfo, +) { if proto_obj.is_null() { return; } unsafe { - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } @@ -222,27 +224,78 @@ pub(super) fn install_disposable_proto_methods( // adding an accessor descriptor onto a prototype that already holds // data properties desyncs the accessor/data-field bookkeeping and // corrupts a data slot (see the Map arm in `collection_proto_thunks`). - install_disposed_getter(proto_obj, ds_proto_disposed_getter_thunk as *const u8); - ipm(proto_obj, "use", ds_proto_use_thunk as *const u8, 1); - ipm(proto_obj, "adopt", ds_proto_adopt_thunk as *const u8, 2); - ipm(proto_obj, "defer", ds_proto_defer_thunk as *const u8, 1); - let dispose_value = ipm(proto_obj, "dispose", ds_proto_dispose_thunk as *const u8, 0); - ipm(proto_obj, "move", ds_proto_move_thunk as *const u8, 0); + install_disposed_getter( + proto_obj, + crate::fn_info!(ds_proto_disposed_getter_thunk, 0; with_declared(0)), + ); + ipm( + proto_obj, + "use", + crate::fn_info!(ds_proto_use_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "adopt", + crate::fn_info!(ds_proto_adopt_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + ipm( + proto_obj, + "defer", + crate::fn_info!(ds_proto_defer_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + let dispose_value = ipm( + proto_obj, + "dispose", + crate::fn_info!(ds_proto_dispose_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + ipm( + proto_obj, + "move", + crate::fn_info!(ds_proto_move_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); install_symbol_dispose_alias(proto_obj, "dispose", dispose_value); install_toplevel_string_tag(proto_obj, "DisposableStack"); } "AsyncDisposableStack" => { - install_disposed_getter(proto_obj, ads_proto_disposed_getter_thunk as *const u8); - ipm(proto_obj, "use", ads_proto_use_thunk as *const u8, 1); - ipm(proto_obj, "adopt", ads_proto_adopt_thunk as *const u8, 2); - ipm(proto_obj, "defer", ads_proto_defer_thunk as *const u8, 1); + install_disposed_getter( + proto_obj, + crate::fn_info!(ads_proto_disposed_getter_thunk, 0; with_declared(0)), + ); + ipm( + proto_obj, + "use", + crate::fn_info!(ads_proto_use_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + ipm( + proto_obj, + "adopt", + crate::fn_info!(ads_proto_adopt_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + ipm( + proto_obj, + "defer", + crate::fn_info!(ads_proto_defer_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); let dispose_value = ipm( proto_obj, "disposeAsync", - ads_proto_dispose_async_thunk as *const u8, + crate::fn_info!(ads_proto_dispose_async_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); + ipm( + proto_obj, + "move", + crate::fn_info!(ads_proto_move_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); - ipm(proto_obj, "move", ads_proto_move_thunk as *const u8, 0); install_symbol_dispose_alias(proto_obj, "asyncDispose", dispose_value); install_toplevel_string_tag(proto_obj, "AsyncDisposableStack"); } diff --git a/crates/perry-runtime/src/object/field_get_set/enumeration.rs b/crates/perry-runtime/src/object/field_get_set/enumeration.rs index 0b6bf6e98f..68d9b918cc 100644 --- a/crates/perry-runtime/src/object/field_get_set/enumeration.rs +++ b/crates/perry-runtime/src/object/field_get_set/enumeration.rs @@ -662,7 +662,8 @@ fn closure_dynamic_enumerable_props(ptr: usize) -> Vec<(String, f64)> { } else { // "name" let func_ptr = - unsafe { (*(ptr as *const crate::closure::ClosureHeader)).func_ptr as usize }; + crate::closure::get_valid_func_ptr(ptr as *const crate::closure::ClosureHeader) + as usize; let fname = crate::builtins::function_name_for_ptr(func_ptr).unwrap_or_default(); let s = crate::string::js_string_from_bytes(fname.as_ptr(), fname.len() as u32); f64::from_bits(JSValue::string_ptr(s).bits()) diff --git a/crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs b/crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs index 7cd6c780b7..447835d1fd 100644 --- a/crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs +++ b/crates/perry-runtime/src/object/field_get_set/get_field_by_name_tail.rs @@ -131,8 +131,8 @@ pub(crate) fn get_field_by_name_object_tail( return JSValue::undefined(); } // A class constructor: its class lookup with this key header. - if crate::closure::shape::is_class_code( - (*(obj as *const crate::closure::ClosureHeader)).func_ptr, + if crate::closure::shape::is_class_info( + (*(obj as *const crate::closure::ClosureHeader)).info, ) { if let Some(value) = super::has_property::class_closure_read_by_key(obj as usize, key) @@ -217,8 +217,7 @@ pub(crate) fn get_field_by_name_object_tail( return JSValue::number(length.unwrap_or(0) as f64); } if name_str == "name" { - let func_ptr = - (*(obj as *const crate::closure::ClosureHeader)).func_ptr as usize; + let func_ptr = (*(obj as *const crate::closure::ClosureHeader)).code() as usize; let fname = crate::builtins::function_name_for_ptr(func_ptr).unwrap_or_default(); let s = crate::string::js_string_from_bytes(fname.as_ptr(), fname.len() as u32); @@ -660,7 +659,7 @@ pub(crate) fn get_field_by_name_object_tail( // read back `""`, matching Node, not `undefined`. if name_str == "name" { let func_ptr = - (*(obj as *const crate::closure::ClosureHeader)).func_ptr as usize; + (*(obj as *const crate::closure::ClosureHeader)).code() as usize; let fname = crate::builtins::function_name_for_ptr(func_ptr).unwrap_or_default(); let s = diff --git a/crates/perry-runtime/src/object/field_get_set/has_property/evaluation_accessor_tests.rs b/crates/perry-runtime/src/object/field_get_set/has_property/evaluation_accessor_tests.rs index ff5491bc57..4c6bf9ff1c 100644 --- a/crates/perry-runtime/src/object/field_get_set/has_property/evaluation_accessor_tests.rs +++ b/crates/perry-runtime/src/object/field_get_set/has_property/evaluation_accessor_tests.rs @@ -32,6 +32,7 @@ fn evaluated_class_accessors_are_present_on_the_actual_prototype_chain() { b"setOnly".as_ptr(), 7, setter as *const () as i64, + 1, ); let scope = crate::gc::RuntimeHandleScope::new(); let class = scope.root_raw_mut_ptr(crate::object::js_object_alloc( diff --git a/crates/perry-runtime/src/object/field_get_set/has_property/function_presence_tests.rs b/crates/perry-runtime/src/object/field_get_set/has_property/function_presence_tests.rs index 3e7d47f542..ccd222aea9 100644 --- a/crates/perry-runtime/src/object/field_get_set/has_property/function_presence_tests.rs +++ b/crates/perry-runtime/src/object/field_get_set/has_property/function_presence_tests.rs @@ -12,7 +12,7 @@ fn function_presence_follows_prototypes_without_reading_values() { let _lock = crate::gc::global_side_table_test_lock(); let scope = crate::gc::RuntimeHandleScope::new(); let function = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(noop as *const u8, 0) as i64, + crate::closure::js_closure_alloc(crate::fn_info!(noop, 0), 0) as i64, )); let has = |receiver: f64, name: &str| { let key = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32); @@ -56,10 +56,12 @@ fn generator_presence_initializes_function_parents() { ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } - crate::closure::js_register_closure_generator_function(generator_body as *const u8); let scope = crate::gc::RuntimeHandleScope::new(); let function = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(generator_body as *const u8, 0) as i64, + crate::closure::js_closure_alloc( + crate::fn_info!(generator_body, 0; with_flags(crate::closure::FN_GENERATOR)), + 0, + ) as i64, )); let key = crate::string::js_string_from_bytes(b"call".as_ptr(), 4); assert_ne!( diff --git a/crates/perry-runtime/src/object/global_this.rs b/crates/perry-runtime/src/object/global_this.rs index 79c061d9e3..3e8cd2fc5e 100644 --- a/crates/perry-runtime/src/object/global_this.rs +++ b/crates/perry-runtime/src/object/global_this.rs @@ -66,8 +66,8 @@ pub(crate) fn function_prototype_intrinsic_of(func: *const u8) -> Option<&'stati } #[cfg(test)] -pub(crate) fn function_prototype_bind_thunk_for_test() -> *const u8 { - array_error::function_prototype_bind_thunk as *const u8 +pub(crate) fn function_prototype_bind_thunk_for_test() -> *const crate::closure::JsFunctionInfo { + crate::fn_info!(array_error::function_prototype_bind_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)) } pub(crate) use array_error::{ @@ -161,8 +161,7 @@ pub use install_static::js_promise_static_function_value; pub(crate) use install_static::{ builtin_species_getter_thunk, install_atomics_namespace_members, install_builtin_constructor_statics, install_builtin_species_accessor, - install_constructor_static, install_constructor_static_with_call_arity, - install_json_namespace_members, install_noop_proto_methods, + install_constructor_static, install_json_namespace_members, install_noop_proto_methods, install_number_static_data_properties, install_proto_method, install_proto_method_alias, install_proto_method_rest, install_proto_method_rest_with_length, install_reflect_namespace_members, subtle_crypto_decapsulate_bits_thunk, diff --git a/crates/perry-runtime/src/object/global_this/array_error/apply_args_tests.rs b/crates/perry-runtime/src/object/global_this/array_error/apply_args_tests.rs index 50cc7e2211..e6d331700a 100644 --- a/crates/perry-runtime/src/object/global_this/array_error/apply_args_tests.rs +++ b/crates/perry-runtime/src/object/global_this/array_error/apply_args_tests.rs @@ -41,9 +41,8 @@ fn function_apply_reads_proxy_wrapped_arrays() { vec![3.0, 4.0] ); - crate::closure::js_register_closure_arity(add as *const u8, 2); let callee = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( - crate::closure::js_closure_alloc(add as *const u8, 0) as i64, + crate::closure::js_closure_alloc(crate::fn_info!(add, 2; with_declared(2)), 0) as i64, )); for arguments in [&proxy, &nested] { let args = [ diff --git a/crates/perry-runtime/src/object/global_this/bigint_promise.rs b/crates/perry-runtime/src/object/global_this/bigint_promise.rs index 560cf22221..5c9e6677f8 100644 --- a/crates/perry-runtime/src/object/global_this/bigint_promise.rs +++ b/crates/perry-runtime/src/object/global_this/bigint_promise.rs @@ -912,6 +912,28 @@ pub(crate) extern "C" fn typed_array_of_thunk( typed_array_create_from_values(this, kind_opt, arr) } +/// The body info and spec `.length` of the `Promise.` static function +/// (the same table as [`promise_static_function_spec`]). +pub(crate) fn promise_static_function_info( + name: &str, +) -> Option<(*const crate::closure::JsFunctionInfo, u32)> { + use crate::fn_info; + Some(match name { + "resolve" => (fn_info!(promise_resolve_static, 1; with_declared(1)), 1), + "reject" => (fn_info!(promise_reject_static, 1; with_declared(1)), 1), + "all" => (fn_info!(promise_all_static, 1; with_declared(1)), 1), + "race" => (fn_info!(promise_race_static, 1; with_declared(1)), 1), + "allSettled" => (fn_info!(promise_all_settled_static, 1; with_declared(1)), 1), + "any" => (fn_info!(promise_any_static, 1; with_declared(1)), 1), + "withResolvers" => ( + fn_info!(promise_with_resolvers_static, 0; with_declared(0)), + 0, + ), + "try" => (fn_info!(promise_try_static, 2; with_rest(1)), 1), + _ => return None, + }) +} + pub(crate) fn promise_static_function_spec(name: &str) -> Option<(*const u8, u32, u32, bool)> { // All eight statics use the spec-aware `*_static` thunks, which honor the // `this` constructor via `NewPromiseCapability(this)` — so a `Promise` diff --git a/crates/perry-runtime/src/object/global_this/builtin_thunks.rs b/crates/perry-runtime/src/object/global_this/builtin_thunks.rs index 37e4448def..19e0767000 100644 --- a/crates/perry-runtime/src/object/global_this/builtin_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/builtin_thunks.rs @@ -536,8 +536,7 @@ fn js_function_ctor_from_strings_impl(args_ptr: *const f64, args_len: usize) -> && body.contains("log.call(deprecate, message, site)") && body.contains("return fn.apply(this, arguments)") { - let fp = depd_wrapfunction_outer_thunk as *const u8; - crate::closure::js_register_closure_arity(fp, 5); + let fp = crate::fn_info!(depd_wrapfunction_outer_thunk, 5; with_declared(5)); let closure = crate::closure::js_closure_alloc_singleton(fp); if !closure.is_null() { return crate::value::js_nanbox_pointer(closure as i64); @@ -576,7 +575,7 @@ fn js_function_ctor_from_strings_impl(args_ptr: *const f64, args_len: usize) -> /// is spec-identical to `new Function(…)`, so this is the same entry the /// construct paths reach (`lower_call/new.rs` and `construct.rs`). The value /// used to carry the shared no-op thunk, so every such call returned -/// `undefined`. Registered as a rest closure (`populate.rs`), so `rest` holds +/// `undefined`. Its info records a rest parameter (`populate.rs`), so `rest` holds /// every argument. #[cfg(not(panic = "abort"))] pub(crate) extern "C-unwind" fn global_this_function_call_thunk( diff --git a/crates/perry-runtime/src/object/global_this/ctor_thunks.rs b/crates/perry-runtime/src/object/global_this/ctor_thunks.rs index 68701e1531..b634366592 100644 --- a/crates/perry-runtime/src/object/global_this/ctor_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/ctor_thunks.rs @@ -454,34 +454,32 @@ pub(crate) extern "C" fn cryptokey_usages_getter_thunk( /// closure for the same `func_ptr` every time — the func_ptr IS the method /// identity here since these thunks take no captures. pub(crate) fn webcrypto_method_value(property_name: &str) -> Option { - let (func_ptr, arity) = match property_name { - "getRandomValues" => (webcrypto_get_random_values_thunk as *const u8, 1), - "randomUUID" => (webcrypto_random_uuid_thunk as *const u8, 0), + let (info, arity) = match property_name { + "getRandomValues" => ( + crate::fn_info!(webcrypto_get_random_values_thunk, 1; with_declared(1)), + 1, + ), + "randomUUID" => ( + crate::fn_info!(webcrypto_random_uuid_thunk, 0; with_declared(0)), + 0, + ), _ => return None, }; - Some(webcrypto_singleton_method( - func_ptr, - property_name, - arity, - || crate::closure::js_register_closure_arity(func_ptr, arity), - )) -} - -/// #10523: decorate a Web Crypto method's singleton closure (body registry -/// entry, `name`, `length`) only when it is first minted. Redoing it on every -/// read allocated a name string, reinstalled the `name` descriptor and -/// invalidated the thunk's cached call-dispatch strategy each time, which was + Some(webcrypto_singleton_method(info, property_name, arity)) +} + +/// #10523: decorate a Web Crypto method's singleton closure (`name`, +/// `length`) only when it is first minted. Redoing it on every read allocated +/// a name string and reinstalled the `name` descriptor each time, which was /// most of what `crypto.randomUUID ? crypto.randomUUID() : …` spent in Perry. fn webcrypto_singleton_method( - func_ptr: *const u8, + func_ptr: *const crate::closure::JsFunctionInfo, name: &str, length: u32, - register_body: impl FnOnce(), ) -> f64 { if let Some(closure) = crate::closure::singleton_closure_if_cached(func_ptr) { return crate::value::js_nanbox_pointer(closure as i64); } - register_body(); let closure = crate::closure::js_closure_alloc_singleton(func_ptr); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -493,12 +491,27 @@ fn webcrypto_singleton_method( crate::value::js_nanbox_pointer(closure as i64) } -fn subtle_crypto_method_spec(property_name: &str) -> Option<(*const u8, u32)> { +/// The KEM method's body info (a rest body) and its `.length`. +fn subtle_crypto_method_spec( + property_name: &str, +) -> Option<(*const crate::closure::JsFunctionInfo, u32)> { match property_name { - "encapsulateBits" => Some((subtle_crypto_encapsulate_bits_thunk as *const u8, 2)), - "decapsulateBits" => Some((subtle_crypto_decapsulate_bits_thunk as *const u8, 3)), - "encapsulateKey" => Some((subtle_crypto_encapsulate_key_thunk as *const u8, 5)), - "decapsulateKey" => Some((subtle_crypto_decapsulate_key_thunk as *const u8, 6)), + "encapsulateBits" => Some(( + crate::fn_info!(subtle_crypto_encapsulate_bits_thunk, 1; with_rest(0)), + 2, + )), + "decapsulateBits" => Some(( + crate::fn_info!(subtle_crypto_decapsulate_bits_thunk, 1; with_rest(0)), + 3, + )), + "encapsulateKey" => Some(( + crate::fn_info!(subtle_crypto_encapsulate_key_thunk, 1; with_rest(0)), + 5, + )), + "decapsulateKey" => Some(( + crate::fn_info!(subtle_crypto_decapsulate_key_thunk, 1; with_rest(0)), + 6, + )), _ => None, } } @@ -509,12 +522,7 @@ fn subtle_crypto_method_spec(property_name: &str) -> Option<(*const u8, u32)> { /// see #10427's PR body for which paths were and weren't affected). pub(crate) fn subtle_crypto_method_value(property_name: &str) -> Option { let (func_ptr, length) = subtle_crypto_method_spec(property_name)?; - Some(webcrypto_singleton_method( - func_ptr, - property_name, - length, - || crate::closure::js_register_closure_rest(func_ptr, 0), - )) + Some(webcrypto_singleton_method(func_ptr, property_name, length)) } #[cfg(test)] diff --git a/crates/perry-runtime/src/object/global_this/fetch_globals.rs b/crates/perry-runtime/src/object/global_this/fetch_globals.rs index 0aefd49f8c..5839d8cdf3 100644 --- a/crates/perry-runtime/src/object/global_this/fetch_globals.rs +++ b/crates/perry-runtime/src/object/global_this/fetch_globals.rs @@ -1284,7 +1284,7 @@ mod dynamic_super_new_target_tests { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_raw_mut_ptr(crate::object::js_object_alloc(61_147, 0)); let parent = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - read_new_target as *const u8, + crate::fn_info!(read_new_target, 0), 0, )); let previous = scope.root_nanbox_f64(crate::object::js_new_target_set(f64::from_bits( @@ -1312,7 +1312,7 @@ mod dynamic_super_new_target_tests { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_raw_mut_ptr(crate::object::js_object_alloc(61_147, 0)); let parent = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - read_new_target as *const u8, + crate::fn_info!(read_new_target, 0), 0, )); let explicit = crate::object::class_constructor_ref_value(61_148); diff --git a/crates/perry-runtime/src/object/global_this/generator.rs b/crates/perry-runtime/src/object/global_this/generator.rs index c5996a0b93..ba2327bed7 100644 --- a/crates/perry-runtime/src/object/global_this/generator.rs +++ b/crates/perry-runtime/src/object/global_this/generator.rs @@ -29,21 +29,18 @@ enum GeneratorKind { } /// Classify a `GC_TYPE_CLOSURE` pointer as a (plain | async) generator -/// function, or `None` for any other closure. Async generators register in -/// BOTH the generator and async registries (the lowering carries `is_async && -/// is_generator`), so async-registry membership disambiguates the two. +/// function, or `None` for any other closure. An async generator's info may +/// carry both FN_GENERATOR and FN_ASYNC (the lowering carries `is_async && +/// is_generator`), so FN_ASYNC_GENERATOR disambiguates the two. fn closure_generator_kind(closure_ptr: usize) -> Option { let closure = closure_ptr as *const crate::closure::ClosureHeader; - let func_ptr = crate::closure::get_valid_func_ptr(closure); - if func_ptr.is_null() { - return None; - } - // Async generators are registered in BOTH registries (they share the sync - // generator's `{next,return,throw}` lowering), so check the async-generator - // registry first — it's the only signal that disambiguates the two. - if crate::closure::is_registered_async_generator_function(func_ptr) { + let info = crate::closure::closure_info(closure)?; + // An async generator's info may carry FN_GENERATOR as well (it shares the + // sync generator's `{next,return,throw}` lowering), so check + // FN_ASYNC_GENERATOR first — it is the only flag that disambiguates the two. + if crate::closure::info_has(info, crate::closure::FN_ASYNC_GENERATOR) { Some(GeneratorKind::Async) - } else if crate::closure::is_registered_generator_function(func_ptr) { + } else if crate::closure::info_has(info, crate::closure::FN_GENERATOR) { Some(GeneratorKind::Sync) } else { None @@ -55,11 +52,11 @@ fn closure_generator_kind(closure_ptr: usize) -> Option { /// async-generator tower classified above. fn is_plain_async_function(closure_ptr: usize) -> bool { let closure = closure_ptr as *const crate::closure::ClosureHeader; - let func_ptr = crate::closure::get_valid_func_ptr(closure); - !func_ptr.is_null() - && crate::closure::is_registered_async_function(func_ptr) - && !crate::closure::is_registered_generator_function(func_ptr) - && !crate::closure::is_registered_async_generator_function(func_ptr) + crate::closure::closure_info(closure).is_some_and(|info| { + crate::closure::info_has(info, crate::closure::FN_ASYNC) + && !crate::closure::info_has(info, crate::closure::FN_GENERATOR) + && !crate::closure::info_has(info, crate::closure::FN_ASYNC_GENERATOR) + }) } fn intrinsic_pointer_value(slot: i64) -> Option { @@ -106,8 +103,10 @@ pub(crate) fn generator_function_proto_of(closure_ptr: usize) -> Option { /// once the global `Function` constructor has been populated. fn build_async_function_tower() { let _no_move = crate::gc::GcSuppressScope::new(); - let noop = global_this_builtin_noop_thunk as *const u8; - let ctor = crate::closure::js_closure_alloc(noop, 0); + let ctor = crate::closure::js_closure_alloc( + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1)), + 0, + ); let proto = js_object_alloc(0, 0); if ctor.is_null() || proto.is_null() { return; @@ -115,7 +114,6 @@ fn build_async_function_tower() { let configurable = super::super::PropertyAttrs::new(false, false, true); let fixed = super::super::PropertyAttrs::new(false, false, false); - crate::closure::js_register_closure_arity(noop, 1); super::super::native_module::set_bound_native_closure_name(ctor, "AsyncFunction"); super::super::native_module::set_builtin_closure_length(ctor as usize, 1); super::super::set_builtin_property_attrs(ctor as usize, "name".to_string(), configurable); @@ -477,13 +475,12 @@ fn install_proto_symbol_self_method( proto: *mut ObjectHeader, symbol_name: &str, display_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, ) { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); super::super::native_module::set_bound_native_closure_name(closure, display_name); super::super::native_module::set_builtin_closure_length(closure as usize, 0); let configurable = super::super::PropertyAttrs::new(false, false, true); @@ -699,11 +696,11 @@ pub extern "C" fn js_generator_attach_closure_prototype( // AsyncGeneratorEnqueue) and `.return(v)` awaits `v`. The non-closure // fallback (`js_generator_attach_prototype`) does this when codegen knows // the function is async; on the closure-identity path we read the async - // brand from the function's registration (the `async function*` wrapper - // symbol is recorded via `js_register_closure_async_generator_function`). - if crate::closure::is_registered_async_generator_function(crate::closure::get_valid_func_ptr( - closure, - )) { + // brand from the function's info (FN_ASYNC_GENERATOR on the + // `async function*` wrapper's info). + if crate::closure::closure_info(closure) + .is_some_and(|info| crate::closure::info_has(info, crate::closure::FN_ASYNC_GENERATOR)) + { let async_target = handle_object_ptr(&obj_h); super::super::async_generator_queue::wrap_async_generator_instance(async_target); } @@ -778,8 +775,10 @@ fn build_generator_tower( } else { ("GeneratorFunction", "GeneratorFunction", "Generator") }; - let noop = global_this_builtin_noop_thunk as *const u8; - let ctor = crate::closure::js_closure_alloc(noop, 0); + let ctor = crate::closure::js_closure_alloc( + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1)), + 0, + ); let proto = js_object_alloc(0, 0); // %Generator% / %AsyncGenerator% let gen_proto = js_object_alloc(0, 0); // %Generator.prototype% if ctor.is_null() || proto.is_null() || gen_proto.is_null() { @@ -789,7 +788,6 @@ fn build_generator_tower( let configurable = super::super::PropertyAttrs::new(false, false, true); // --- %GeneratorFunction% constructor --- - crate::closure::js_register_closure_arity(noop, 1); super::super::native_module::set_bound_native_closure_name(ctor, ctor_name); super::super::native_module::set_builtin_closure_length(ctor as usize, 1); super::super::set_builtin_property_attrs(ctor as usize, "name".to_string(), configurable); @@ -823,22 +821,22 @@ fn build_generator_tower( crate::value::js_nanbox_pointer(proto as i64), configurable, ); - let (next_thunk, return_thunk, throw_thunk) = if is_async { + let (next_info, return_info, throw_info) = if is_async { ( - async_generator_proto_next_thunk as *const u8, - async_generator_proto_return_thunk as *const u8, - async_generator_proto_throw_thunk as *const u8, + crate::fn_info!(async_generator_proto_next_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + crate::fn_info!(async_generator_proto_return_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + crate::fn_info!(async_generator_proto_throw_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), ) } else { ( - generator_proto_next_thunk as *const u8, - generator_proto_return_thunk as *const u8, - generator_proto_throw_thunk as *const u8, + crate::fn_info!(generator_proto_next_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + crate::fn_info!(generator_proto_return_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + crate::fn_info!(generator_proto_throw_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), ) }; - install_proto_method(gen_proto, "next", next_thunk, 1); - install_proto_method(gen_proto, "return", return_thunk, 1); - install_proto_method(gen_proto, "throw", throw_thunk, 1); + install_proto_method(gen_proto, "next", next_info, 1); + install_proto_method(gen_proto, "return", return_info, 1); + install_proto_method(gen_proto, "throw", throw_info, 1); // Spec: `%AsyncGenerator.prototype%` inherits `[Symbol.asyncIterator]` from // `%AsyncIteratorPrototype%` and `%Generator.prototype%` inherits // `[Symbol.iterator]` from `%IteratorPrototype%` — both returning `this`. @@ -863,13 +861,13 @@ fn build_generator_tower( ( "asyncIterator", "[Symbol.asyncIterator]", - async_generator_proto_async_iterator_thunk as *const u8, + crate::fn_info!(async_generator_proto_async_iterator_thunk, 1; with_declared(0)), ) } else { ( "iterator", "[Symbol.iterator]", - generator_proto_iterator_thunk as *const u8, + crate::fn_info!(generator_proto_iterator_thunk, 1; with_declared(0)), ) }; if is_async { diff --git a/crates/perry-runtime/src/object/global_this/install_static.rs b/crates/perry-runtime/src/object/global_this/install_static.rs index 844ab0f115..0569dca432 100644 --- a/crates/perry-runtime/src/object/global_this/install_static.rs +++ b/crates/perry-runtime/src/object/global_this/install_static.rs @@ -9,7 +9,7 @@ pub extern "C" fn js_promise_static_function_value(name_ptr: *const u8, name_len let Ok(name) = std::str::from_utf8(name_bytes) else { return f64::from_bits(crate::value::TAG_UNDEFINED); }; - let Some((func_ptr, spec_length, call_arity, has_rest)) = promise_static_function_spec(name) + let Some((info, spec_length)) = super::bigint_promise::promise_static_function_info(name) else { return f64::from_bits(crate::value::TAG_UNDEFINED); }; @@ -24,15 +24,10 @@ pub extern "C" fn js_promise_static_function_value(name_ptr: *const u8, name_len } } - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - if has_rest { - crate::closure::js_register_closure_rest(func_ptr, call_arity); - } else { - crate::closure::js_register_closure_arity(func_ptr, call_arity); - } super::super::native_module::set_bound_native_closure_name(closure, name); super::super::native_module::set_builtin_closure_length(closure as usize, spec_length); super::super::native_module::set_builtin_closure_non_constructable(closure as usize); @@ -223,35 +218,19 @@ pub(crate) extern "C" fn subtle_crypto_decapsulate_key_thunk( /// Install a single callable static method on a constructor closure as a /// `{ writable: true, enumerable: false, configurable: true }` data property -/// (matching Node's descriptors for built-in statics). `has_rest` registers -/// the func pointer as a rest-arg closure so trailing args arrive as an array. +/// (matching Node's descriptors for built-in statics). `info` is the body's +/// (its call arity or rest parameter included); `spec_length` is the +/// function's `.length`. pub(crate) fn install_constructor_static( ctor: *mut crate::closure::ClosureHeader, name: &str, - func_ptr: *const u8, - arity: u32, - has_rest: bool, -) { - install_constructor_static_with_call_arity(ctor, name, func_ptr, arity, arity, has_rest); -} - -pub(crate) fn install_constructor_static_with_call_arity( - ctor: *mut crate::closure::ClosureHeader, - name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, spec_length: u32, - call_arity: u32, - has_rest: bool, ) { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - if has_rest { - crate::closure::js_register_closure_rest(func_ptr, call_arity); - } else { - crate::closure::js_register_closure_arity(func_ptr, call_arity); - } super::super::native_module::set_bound_native_closure_name(closure, name); super::super::native_module::set_builtin_closure_length(closure as usize, spec_length); super::super::native_module::set_builtin_closure_non_constructable(closure as usize); @@ -309,126 +288,144 @@ pub(crate) fn install_builtin_constructor_statics( } match name { "Object" => { - install_constructor_static(ctor, "keys", object_keys_thunk as *const u8, 1, false); - install_constructor_static(ctor, "values", object_values_thunk as *const u8, 1, false); + install_constructor_static( + ctor, + "keys", + crate::fn_info!(object_keys_thunk, 1; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "values", + crate::fn_info!(object_values_thunk, 1; with_declared(1)), + 1, + ); install_constructor_static( ctor, "entries", - object_entries_thunk as *const u8, + crate::fn_info!(object_entries_thunk, 1; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "freeze", + crate::fn_info!(object_freeze_thunk, 1; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "create", + crate::fn_info!(object_create_thunk, 2; with_declared(2)), + 2, + ); + install_constructor_static( + ctor, + "seal", + crate::fn_info!(object_seal_thunk, 1; with_declared(1)), 1, - false, ); - install_constructor_static(ctor, "freeze", object_freeze_thunk as *const u8, 1, false); - install_constructor_static(ctor, "create", object_create_thunk as *const u8, 2, false); - install_constructor_static(ctor, "seal", object_seal_thunk as *const u8, 1, false); install_constructor_static( ctor, "isSealed", - object_is_sealed_thunk as *const u8, + crate::fn_info!(object_is_sealed_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "isFrozen", - object_is_frozen_thunk as *const u8, + crate::fn_info!(object_is_frozen_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "isExtensible", - object_is_extensible_thunk as *const u8, + crate::fn_info!(object_is_extensible_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "preventExtensions", - object_prevent_extensions_thunk as *const u8, + crate::fn_info!(object_prevent_extensions_thunk, 1; with_declared(1)), 1, - false, ); - install_constructor_static(ctor, "is", object_is_thunk as *const u8, 2, false); + install_constructor_static( + ctor, + "is", + crate::fn_info!(object_is_thunk, 2; with_declared(2)), + 2, + ); install_constructor_static( ctor, "setPrototypeOf", - object_set_prototype_of_thunk as *const u8, + crate::fn_info!(object_set_prototype_of_thunk, 2; with_declared(2)), 2, - false, ); install_constructor_static( ctor, "getOwnPropertySymbols", - object_get_own_property_symbols_thunk as *const u8, + crate::fn_info!(object_get_own_property_symbols_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "getOwnPropertyDescriptors", - object_get_own_property_descriptors_thunk as *const u8, + crate::fn_info!(object_get_own_property_descriptors_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "defineProperties", - object_define_properties_thunk as *const u8, + crate::fn_info!(object_define_properties_thunk, 2; with_declared(2)), 2, - false, ); install_constructor_static( ctor, "groupBy", - object_group_by_thunk as *const u8, + crate::fn_info!(object_group_by_thunk, 2; with_declared(2)), 2, - false, ); install_constructor_static( ctor, "getPrototypeOf", - object_get_prototype_of_thunk as *const u8, + crate::fn_info!(object_get_prototype_of_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "getOwnPropertyNames", - object_get_own_property_names_thunk as *const u8, + crate::fn_info!(object_get_own_property_names_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "getOwnPropertyDescriptor", - object_get_own_property_descriptor_thunk as *const u8, + crate::fn_info!(object_get_own_property_descriptor_thunk, 2; with_declared(2)), 2, - false, ); install_constructor_static( ctor, "defineProperty", - object_define_property_thunk as *const u8, + crate::fn_info!(object_define_property_thunk, 3; with_declared(3)), 3, - false, ); install_constructor_static( ctor, "fromEntries", - object_from_entries_thunk as *const u8, + crate::fn_info!(object_from_entries_thunk, 1; with_declared(1)), 1, - false, ); - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "assign", - object_assign_thunk as *const u8, + crate::fn_info!(object_assign_thunk, 2; with_rest(1)), + 2, + ); + install_constructor_static( + ctor, + "hasOwn", + crate::fn_info!(object_hasown_thunk, 2; with_declared(2)), 2, - 1, - true, ); - install_constructor_static(ctor, "hasOwn", object_hasown_thunk as *const u8, 2, false); // `Object` is a function, so reading a non-static member resolves up // its prototype chain (Function.prototype → Object.prototype). In // particular `Object.hasOwnProperty` IS `Object.prototype.hasOwnProperty` @@ -441,56 +438,59 @@ pub(crate) fn install_builtin_constructor_statics( install_constructor_static( ctor, "hasOwnProperty", - object_prototype_has_own_property_thunk as *const u8, + crate::fn_info!(object_prototype_has_own_property_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "isPrototypeOf", - object_prototype_is_prototype_of_thunk as *const u8, + crate::fn_info!(object_prototype_is_prototype_of_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "propertyIsEnumerable", - object_prototype_property_is_enumerable_thunk as *const u8, + crate::fn_info!(object_prototype_property_is_enumerable_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "toString", - object_prototype_to_string_thunk as *const u8, + crate::fn_info!(object_prototype_to_string_thunk, 0; with_declared(0)), 0, - false, ); install_constructor_static( ctor, "toLocaleString", - object_prototype_to_locale_string_thunk as *const u8, + crate::fn_info!(object_prototype_to_locale_string_thunk, 0; with_declared(0)), 0, - false, ); install_constructor_static( ctor, "valueOf", - object_prototype_value_of_thunk as *const u8, + crate::fn_info!(object_prototype_value_of_thunk, 0; with_declared(0)), 0, - false, ); } "Array" => { install_constructor_static( ctor, "isArray", - array_is_array_thunk as *const u8, + crate::fn_info!(array_is_array_thunk, 1; with_declared(1)), 1, - false, ); - install_constructor_static(ctor, "from", array_from_thunk as *const u8, 1, false); - install_constructor_static(ctor, "of", array_of_thunk as *const u8, 0, true); + install_constructor_static( + ctor, + "from", + crate::fn_info!(array_from_thunk, 1; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "of", + crate::fn_info!(array_of_thunk, 1; with_rest(0)), + 0, + ); } "Promise" => { for static_name in [ @@ -503,17 +503,10 @@ pub(crate) fn install_builtin_constructor_statics( "withResolvers", "try", ] { - if let Some((func_ptr, spec_length, call_arity, has_rest)) = - promise_static_function_spec(static_name) + if let Some((info, spec_length)) = + super::bigint_promise::promise_static_function_info(static_name) { - install_constructor_static_with_call_arity( - ctor, - static_name, - func_ptr, - spec_length, - call_arity, - has_rest, - ); + install_constructor_static(ctor, static_name, info, spec_length); } } } @@ -524,41 +517,41 @@ pub(crate) fn install_builtin_constructor_statics( date_proto_thunks::install_date_constructor_statics(ctor); } "Number" => { - install_constructor_static(ctor, "isNaN", number_is_nan_thunk as *const u8, 1, false); + install_constructor_static( + ctor, + "isNaN", + crate::fn_info!(number_is_nan_thunk, 1; with_declared(1)), + 1, + ); install_constructor_static( ctor, "isFinite", - number_is_finite_thunk as *const u8, + crate::fn_info!(number_is_finite_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "isInteger", - number_is_integer_thunk as *const u8, + crate::fn_info!(number_is_integer_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "isSafeInteger", - number_is_safe_integer_thunk as *const u8, + crate::fn_info!(number_is_safe_integer_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "parseFloat", - number_parse_float_thunk as *const u8, + crate::fn_info!(number_parse_float_thunk, 1; with_declared(1)), 1, - false, ); install_constructor_static( ctor, "parseInt", - number_parse_int_thunk as *const u8, + crate::fn_info!(number_parse_int_thunk, 2; with_declared(2)), 2, - false, ); } "BigInt" => { @@ -566,21 +559,29 @@ pub(crate) fn install_builtin_constructor_statics( install_constructor_static( ctor, "asIntN", - bigint_as_int_n_thunk as *const u8, + crate::fn_info!(bigint_as_int_n_thunk, 2; with_declared(2)), 2, - false, ); install_constructor_static( ctor, "asUintN", - bigint_as_uint_n_thunk as *const u8, + crate::fn_info!(bigint_as_uint_n_thunk, 2; with_declared(2)), 2, - false, ); } "Symbol" => { - install_constructor_static(ctor, "for", symbol_for_thunk as *const u8, 1, false); - install_constructor_static(ctor, "keyFor", symbol_key_for_thunk as *const u8, 1, false); + install_constructor_static( + ctor, + "for", + crate::fn_info!(symbol_for_thunk, 1; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "keyFor", + crate::fn_info!(symbol_key_for_thunk, 1; with_declared(1)), + 1, + ); for name in ["iterator", "asyncIterator"] { let symbol = crate::symbol::well_known_symbol(name); crate::closure::closure_set_dynamic_prop( @@ -601,65 +602,53 @@ pub(crate) fn install_builtin_constructor_statics( // `.length`, usable via reference / spread). Call-arity 0 (all args // collected into `rest`) with spec `.length` 1. `String.raw` (a tag // function) is left on its intrinsic path for now. - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "fromCharCode", - string_from_char_code_static as *const u8, + crate::fn_info!(string_from_char_code_static, 1; with_rest(0)), 1, - 0, - true, ); - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "fromCodePoint", - string_from_code_point_static as *const u8, + crate::fn_info!(string_from_code_point_static, 1; with_rest(0)), 1, - 0, - true, ); // #4627: `String.raw` (tag function) — 1 fixed param (template // object) + rest substitutions; spec `.length` 1. - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "raw", - string_raw_static as *const u8, - 1, + crate::fn_info!(string_raw_static, 2; with_rest(1)), 1, - true, ); } "ArrayBuffer" => { install_constructor_static( ctor, "isView", - array_buffer_is_view_thunk as *const u8, + crate::fn_info!(array_buffer_is_view_thunk, 1; with_declared(1)), 1, - false, ); } "Response" => { install_constructor_static( ctor, "error", - global_this_response_error_thunk as *const u8, + crate::fn_info!(global_this_response_error_thunk, 0; with_declared(0)), 0, - false, ); - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "json", - global_this_response_json_thunk as *const u8, + crate::fn_info!(global_this_response_json_thunk, 2; with_declared(2)), 1, - 2, - false, ); - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "redirect", - global_this_response_redirect_thunk as *const u8, + crate::fn_info!(global_this_response_redirect_thunk, 2; with_declared(2)), 1, - 2, - false, ); } #[cfg(feature = "global-url")] @@ -667,21 +656,23 @@ pub(crate) fn install_builtin_constructor_statics( install_constructor_static( ctor, "canParse", - url_can_parse_thunk as *const u8, + crate::fn_info!(url_can_parse_thunk, 2; with_declared(1)), + 1, + ); + install_constructor_static( + ctor, + "parse", + crate::fn_info!(url_parse_thunk, 2; with_declared(1)), 1, - false, ); - install_constructor_static(ctor, "parse", url_parse_thunk as *const u8, 1, false); } #[cfg(feature = "global-webcrypto")] "SubtleCrypto" => { - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "supports", - subtle_crypto_supports_thunk as *const u8, + crate::fn_info!(subtle_crypto_supports_thunk, 1; with_rest(0)), 2, - 0, - true, ); super::super::set_builtin_property_attrs( ctor as usize, @@ -693,9 +684,8 @@ pub(crate) fn install_builtin_constructor_statics( install_constructor_static( ctor, "revocable", - proxy_revocable_thunk as *const u8, + crate::fn_info!(proxy_revocable_thunk, 2; with_declared(2)), 2, - false, ); } // #6674: TC39 `Uint8Array.fromBase64(str, opts)` / `fromHex(str)` static @@ -709,20 +699,17 @@ pub(crate) fn install_builtin_constructor_statics( // path uses, so the produced value is identical. `fromBase64.length` is // 1 (the optional opts is the 2nd, undefined-padded, ABI slot). "Uint8Array" => { - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "fromBase64", - uint8array_from_base64_thunk as *const u8, + crate::fn_info!(uint8array_from_base64_thunk, 2; with_declared(2)), 1, - 2, - false, ); install_constructor_static( ctor, "fromHex", - uint8array_from_hex_thunk as *const u8, + crate::fn_info!(uint8array_from_hex_thunk, 1; with_declared(1)), 1, - false, ); } _ => {} @@ -730,7 +717,7 @@ pub(crate) fn install_builtin_constructor_statics( } /// Install a method on a prototype object as a callable closure value with -/// the proper `name` property and registered arity. Used to reify built-in +/// the proper `name` property and `.length`. Used to reify built-in /// prototype methods so `Array.prototype.map`, `Date.prototype.toISOString`, /// etc. read back as `typeof === "function"` (issue #2142) — the actual /// method *call* path is already covered by codegen's NativeMethodCall and @@ -739,27 +726,25 @@ pub(crate) fn install_builtin_constructor_statics( /// through indirection (`const m = Array.prototype.map; m.call(arr, fn)`), /// a rare pattern. The reification is the value-read parity win. /// -/// `func_ptr` defaults to `global_this_builtin_noop_thunk` (returns +/// The body defaults to `global_this_builtin_noop_thunk` (returns /// undefined) for methods we don't have a dedicated thunk for; callers /// that want spec-accurate call behavior pass a custom thunk instead /// (`array_prototype_slice_thunk`, `object_prototype_to_string_thunk`). +/// `info` is the body's (a built-in: `FN_BUILTIN`, declared `arity`). pub(crate) fn install_proto_method( proto_obj: *mut ObjectHeader, method_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) -> f64 { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - crate::closure::js_register_closure_arity(func_ptr, arity); - crate::closure::register_closure_body_builtin(func_ptr); super::super::native_module::set_bound_native_closure_name(closure, method_name); - // #3143: record this method's spec `.length` per closure instance — all - // noop-backed methods share one func_ptr, so the func-ptr arity registry - // can't distinguish `map` (1) from `slice` (2). Read back by the `.length` - // value-accessor and `getOwnPropertyDescriptor`. + // #3143: record this method's spec `.length` per closure instance — many + // methods share the noop body. Read back by the `.length` value-accessor + // and `getOwnPropertyDescriptor`. super::super::native_module::set_builtin_closure_length(closure as usize, arity); super::super::native_module::set_builtin_closure_non_constructable(closure as usize); let key = crate::string::js_string_from_bytes(method_name.as_ptr(), method_name.len() as u32); @@ -818,34 +803,30 @@ pub(crate) fn install_proto_method_alias( ); } +/// [`install_proto_method`] for a rest body: `info` carries its rest +/// parameter (`with_rest(fixed_arity)`) and `FN_BUILTIN`; `.length` is +/// `fixed_arity`. pub(crate) fn install_proto_method_rest( proto_obj: *mut ObjectHeader, method_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, fixed_arity: u32, ) { - install_proto_method_rest_with_length( - proto_obj, - method_name, - func_ptr, - fixed_arity, - fixed_arity, - ); + install_proto_method_rest_with_length(proto_obj, method_name, info, fixed_arity); } +/// [`install_proto_method_rest`] with a `.length` other than the fixed +/// arity. pub(crate) fn install_proto_method_rest_with_length( proto_obj: *mut ObjectHeader, method_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, spec_length: u32, - call_fixed_arity: u32, ) -> f64 { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - crate::closure::js_register_closure_rest(func_ptr, call_fixed_arity); - crate::closure::register_closure_body_builtin(func_ptr); super::super::native_module::set_bound_native_closure_name(closure, method_name); super::super::native_module::set_builtin_closure_length(closure as usize, spec_length); super::super::native_module::set_builtin_closure_non_constructable(closure as usize); @@ -874,14 +855,30 @@ pub(crate) fn install_proto_method_rest_with_length( /// #4139/#4437: reify the `JSON` namespace's own methods for reflection parity /// and detached value calls. Direct call sites are still codegen intrinsics. pub(crate) fn install_json_namespace_members(ns_obj: *mut ObjectHeader) { - const METHODS: &[(&str, *const u8, u32)] = &[ - ("parse", json_parse_thunk as *const u8, 2), - ("stringify", json_stringify_thunk as *const u8, 3), - ("rawJSON", json_raw_json_thunk as *const u8, 1), - ("isRawJSON", json_is_raw_json_thunk as *const u8, 1), + const METHODS: &[(&str, *const crate::closure::JsFunctionInfo, u32)] = &[ + ( + "parse", + crate::fn_info!(json_parse_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "stringify", + crate::fn_info!(json_stringify_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "rawJSON", + crate::fn_info!(json_raw_json_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "isRawJSON", + crate::fn_info!(json_is_raw_json_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), ]; - for (name, func_ptr, arity) in METHODS.iter().copied() { - install_proto_method(ns_obj, name, func_ptr, arity); + for (name, info, arity) in METHODS.iter().copied() { + install_proto_method(ns_obj, name, info, arity); } } @@ -897,80 +894,144 @@ pub(crate) fn install_reflect_namespace_members(ns_obj: *mut ObjectHeader) { let methods = [ ( "defineProperty", - reflect_define_property_thunk as *const u8, + crate::fn_info!(reflect_define_property_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), 3, ), ( "deleteProperty", - reflect_delete_property_thunk as *const u8, + crate::fn_info!(reflect_delete_property_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "apply", + crate::fn_info!(reflect_apply_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "construct", + crate::fn_info!(reflect_construct_thunk, 3; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "get", + crate::fn_info!(reflect_get_thunk, 3; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ), - ("apply", reflect_apply_thunk as *const u8, 3), - ("construct", reflect_construct_thunk as *const u8, 2), - ("get", reflect_get_thunk as *const u8, 2), ( "getOwnPropertyDescriptor", - reflect_get_own_property_descriptor_thunk as *const u8, + crate::fn_info!(reflect_get_own_property_descriptor_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ), ( "getPrototypeOf", - reflect_get_prototype_of_thunk as *const u8, + crate::fn_info!(reflect_get_prototype_of_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "has", + crate::fn_info!(reflect_has_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "isExtensible", + crate::fn_info!(reflect_is_extensible_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "ownKeys", + crate::fn_info!(reflect_own_keys_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ), - ("has", reflect_has_thunk as *const u8, 2), - ("isExtensible", reflect_is_extensible_thunk as *const u8, 1), - ("ownKeys", reflect_own_keys_thunk as *const u8, 1), ( "preventExtensions", - reflect_prevent_extensions_thunk as *const u8, + crate::fn_info!(reflect_prevent_extensions_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ), - ("set", reflect_set_thunk as *const u8, 3), + ( + "set", + crate::fn_info!(reflect_set_thunk, 4; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), ( "setPrototypeOf", - reflect_set_prototype_of_thunk as *const u8, + crate::fn_info!(reflect_set_prototype_of_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ), ]; - for (name, func_ptr, arity) in methods { - install_proto_method(ns_obj, name, func_ptr, arity); + for (name, info, arity) in methods { + install_proto_method(ns_obj, name, info, arity); } } pub(crate) fn install_atomics_namespace_members(ns_obj: *mut ObjectHeader) { - for (name, func_ptr, arity) in [ - ("load", crate::atomics::js_atomics_load as *const u8, 2), + for (name, info, arity) in [ + ( + "load", + crate::fn_info!(crate::atomics::js_atomics_load, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), ( "isLockFree", - crate::atomics::js_atomics_is_lock_free as *const u8, + crate::fn_info!(crate::atomics::js_atomics_is_lock_free, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ), - ("store", crate::atomics::js_atomics_store as *const u8, 3), - ("add", crate::atomics::js_atomics_add as *const u8, 3), - ("sub", crate::atomics::js_atomics_sub as *const u8, 3), - ("and", crate::atomics::js_atomics_and as *const u8, 3), - ("or", crate::atomics::js_atomics_or as *const u8, 3), - ("xor", crate::atomics::js_atomics_xor as *const u8, 3), + ( + "store", + crate::fn_info!(crate::atomics::js_atomics_store, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "add", + crate::fn_info!(crate::atomics::js_atomics_add, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "sub", + crate::fn_info!(crate::atomics::js_atomics_sub, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "and", + crate::fn_info!(crate::atomics::js_atomics_and, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "or", + crate::fn_info!(crate::atomics::js_atomics_or, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "xor", + crate::fn_info!(crate::atomics::js_atomics_xor, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), ( "exchange", - crate::atomics::js_atomics_exchange as *const u8, + crate::fn_info!(crate::atomics::js_atomics_exchange, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), 3, ), ( "compareExchange", - crate::atomics::js_atomics_compare_exchange as *const u8, + crate::fn_info!(crate::atomics::js_atomics_compare_exchange, 4; with_declared(4), with_flags(crate::closure::FN_BUILTIN)), + 4, + ), + ( + "notify", + crate::fn_info!(crate::atomics::js_atomics_notify, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)), + 3, + ), + ( + "wait", + crate::fn_info!(crate::atomics::js_atomics_wait, 4; with_declared(4), with_flags(crate::closure::FN_BUILTIN)), 4, ), - ("notify", crate::atomics::js_atomics_notify as *const u8, 3), - ("wait", crate::atomics::js_atomics_wait as *const u8, 4), ( "waitAsync", - crate::atomics::js_atomics_wait_async as *const u8, + crate::fn_info!(crate::atomics::js_atomics_wait_async, 4; with_declared(4), with_flags(crate::closure::FN_BUILTIN)), 4, ), ] { - install_proto_method(ns_obj, name, func_ptr, arity); + install_proto_method(ns_obj, name, info, arity); } } @@ -979,17 +1040,32 @@ pub(crate) fn install_atomics_namespace_members(ns_obj: *mut ObjectHeader) { /// `global_this_builtin_noop_thunk`, but inherited Object methods with /// observable receiver-sensitive behavior use their real thunk. pub(crate) fn install_noop_proto_methods(proto_obj: *mut ObjectHeader, methods: &[(&str, u32)]) { + // One info per body: the per-method `.length` rides on each closure + // (`install_proto_method` records it), so the bodies need no declared + // arity of their own. + use crate::closure::FN_BUILTIN; + use crate::fn_info; for (name, arity) in methods.iter().copied() { - let func_ptr = match name { - "isPrototypeOf" => object_prototype_is_prototype_of_thunk as *const u8, + let info = match name { + "isPrototypeOf" => { + fn_info!(object_prototype_is_prototype_of_thunk, 1; with_flags(FN_BUILTIN)) + } // Annex B accessor methods get real thunks (reflective `.call`). - "__defineGetter__" => object_prototype_define_getter_thunk as *const u8, - "__defineSetter__" => object_prototype_define_setter_thunk as *const u8, - "__lookupGetter__" => object_prototype_lookup_getter_thunk as *const u8, - "__lookupSetter__" => object_prototype_lookup_setter_thunk as *const u8, - _ => global_this_builtin_noop_thunk as *const u8, + "__defineGetter__" => { + fn_info!(object_prototype_define_getter_thunk, 2; with_flags(FN_BUILTIN)) + } + "__defineSetter__" => { + fn_info!(object_prototype_define_setter_thunk, 2; with_flags(FN_BUILTIN)) + } + "__lookupGetter__" => { + fn_info!(object_prototype_lookup_getter_thunk, 1; with_flags(FN_BUILTIN)) + } + "__lookupSetter__" => { + fn_info!(object_prototype_lookup_setter_thunk, 1; with_flags(FN_BUILTIN)) + } + _ => fn_info!(global_this_builtin_noop_thunk, 1; with_flags(FN_BUILTIN)), }; - install_proto_method(proto_obj, name, func_ptr, arity); + install_proto_method(proto_obj, name, info, arity); } } @@ -1052,9 +1128,10 @@ pub(crate) fn install_builtin_species_accessor(ctor: *mut crate::closure::Closur if sym.is_null() { return; } - let f = builtin_species_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(f, 0); - let getter = crate::closure::js_closure_alloc(f, 0); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(builtin_species_getter_thunk, 0; with_declared(0)), + 0, + ); if getter.is_null() { return; } diff --git a/crates/perry-runtime/src/object/global_this/math_temporal.rs b/crates/perry-runtime/src/object/global_this/math_temporal.rs index 305f132f5b..b40c49cfe7 100644 --- a/crates/perry-runtime/src/object/global_this/math_temporal.rs +++ b/crates/perry-runtime/src/object/global_this/math_temporal.rs @@ -8,52 +8,195 @@ pub(crate) fn install_math_namespace(ns_obj: *mut ObjectHeader) { if ns_obj.is_null() { return; } - for (name, func_ptr, arity) in [ - ("abs", math_abs_thunk as *const u8, 1), - ("acos", math_acos_thunk as *const u8, 1), - ("acosh", math_acosh_thunk as *const u8, 1), - ("asin", math_asin_thunk as *const u8, 1), - ("asinh", math_asinh_thunk as *const u8, 1), - ("atan", math_atan_thunk as *const u8, 1), - ("atanh", math_atanh_thunk as *const u8, 1), - ("atan2", math_atan2_thunk as *const u8, 2), - ("ceil", math_ceil_thunk as *const u8, 1), - ("cbrt", math_cbrt_thunk as *const u8, 1), - ("expm1", math_expm1_thunk as *const u8, 1), - ("clz32", math_clz32_thunk as *const u8, 1), - ("cos", math_cos_thunk as *const u8, 1), - ("cosh", math_cosh_thunk as *const u8, 1), - ("exp", math_exp_thunk as *const u8, 1), - ("floor", math_floor_thunk as *const u8, 1), - ("fround", math_fround_thunk as *const u8, 1), + for (name, info, arity) in [ + ( + "abs", + crate::fn_info!(math_abs_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "acos", + crate::fn_info!(math_acos_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "acosh", + crate::fn_info!(math_acosh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "asin", + crate::fn_info!(math_asin_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "asinh", + crate::fn_info!(math_asinh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "atan", + crate::fn_info!(math_atan_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "atanh", + crate::fn_info!(math_atanh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "atan2", + crate::fn_info!(math_atan2_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "ceil", + crate::fn_info!(math_ceil_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "cbrt", + crate::fn_info!(math_cbrt_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "expm1", + crate::fn_info!(math_expm1_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "clz32", + crate::fn_info!(math_clz32_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "cos", + crate::fn_info!(math_cos_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "cosh", + crate::fn_info!(math_cosh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "exp", + crate::fn_info!(math_exp_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "floor", + crate::fn_info!(math_floor_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "fround", + crate::fn_info!(math_fround_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), ] { - install_proto_method(ns_obj, name, func_ptr, arity); + install_proto_method(ns_obj, name, info, arity); } - install_proto_method_rest_with_length(ns_obj, "hypot", math_hypot_thunk as *const u8, 2, 0); - for (name, func_ptr, arity) in [ - ("imul", math_imul_thunk as *const u8, 2), - ("log", math_log_thunk as *const u8, 1), - ("log1p", math_log1p_thunk as *const u8, 1), - ("log2", math_log2_thunk as *const u8, 1), - ("log10", math_log10_thunk as *const u8, 1), + install_proto_method_rest_with_length( + ns_obj, + "hypot", + crate::fn_info!(math_hypot_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + for (name, info, arity) in [ + ( + "imul", + crate::fn_info!(math_imul_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "log", + crate::fn_info!(math_log_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "log1p", + crate::fn_info!(math_log1p_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "log2", + crate::fn_info!(math_log2_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "log10", + crate::fn_info!(math_log10_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), ] { - install_proto_method(ns_obj, name, func_ptr, arity); + install_proto_method(ns_obj, name, info, arity); } - install_proto_method_rest_with_length(ns_obj, "max", math_max_thunk as *const u8, 2, 0); - install_proto_method_rest_with_length(ns_obj, "min", math_min_thunk as *const u8, 2, 0); - for (name, func_ptr, arity) in [ - ("pow", math_pow_thunk as *const u8, 2), - ("random", math_random_thunk as *const u8, 0), - ("round", math_round_thunk as *const u8, 1), - ("sign", math_sign_thunk as *const u8, 1), - ("sin", math_sin_thunk as *const u8, 1), - ("sinh", math_sinh_thunk as *const u8, 1), - ("sqrt", math_sqrt_thunk as *const u8, 1), - ("tan", math_tan_thunk as *const u8, 1), - ("tanh", math_tanh_thunk as *const u8, 1), - ("trunc", math_trunc_thunk as *const u8, 1), + install_proto_method_rest_with_length( + ns_obj, + "max", + crate::fn_info!(math_max_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + install_proto_method_rest_with_length( + ns_obj, + "min", + crate::fn_info!(math_min_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); + for (name, info, arity) in [ + ( + "pow", + crate::fn_info!(math_pow_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ), + ( + "random", + crate::fn_info!(math_random_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ( + "round", + crate::fn_info!(math_round_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "sign", + crate::fn_info!(math_sign_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "sin", + crate::fn_info!(math_sin_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "sinh", + crate::fn_info!(math_sinh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "sqrt", + crate::fn_info!(math_sqrt_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "tan", + crate::fn_info!(math_tan_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "tanh", + crate::fn_info!(math_tanh_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "trunc", + crate::fn_info!(math_trunc_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), ] { - install_proto_method(ns_obj, name, func_ptr, arity); + install_proto_method(ns_obj, name, info, arity); } let constant_attrs = super::super::PropertyAttrs::new(false, false, false); @@ -70,7 +213,12 @@ pub(crate) fn install_math_namespace(ns_obj: *mut ObjectHeader) { set_intrinsic_data_prop(ns_obj, name, value, constant_attrs); } - install_proto_method(ns_obj, "f16round", math_f16round_thunk as *const u8, 1); + install_proto_method( + ns_obj, + "f16round", + crate::fn_info!(math_f16round_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); } // ---- TC39 Temporal namespace (#4686) ------------------------------------- @@ -371,31 +519,39 @@ fn build_temporal_now_namespace() -> f64 { if now_obj.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - for (name, thunk, len) in [ - ("instant", temporal_now_instant_thunk as *const u8, 0u32), - ("timeZoneId", temporal_now_timezone_id_thunk as *const u8, 0), + for (name, info, len) in [ + ( + "instant", + crate::fn_info!(temporal_now_instant_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 0u32, + ), + ( + "timeZoneId", + crate::fn_info!(temporal_now_timezone_id_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), ( "plainDateTimeISO", - temporal_now_plain_date_time_iso_thunk as *const u8, + crate::fn_info!(temporal_now_plain_date_time_iso_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ), ( "plainDateISO", - temporal_now_plain_date_iso_thunk as *const u8, + crate::fn_info!(temporal_now_plain_date_iso_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ), ( "plainTimeISO", - temporal_now_plain_time_iso_thunk as *const u8, + crate::fn_info!(temporal_now_plain_time_iso_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ), ( "zonedDateTimeISO", - temporal_now_zoned_date_time_iso_thunk as *const u8, + crate::fn_info!(temporal_now_zoned_date_time_iso_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ), ] { - install_proto_method_rest_with_length(now_obj, name, thunk, len, 0); + install_proto_method_rest_with_length(now_obj, name, info, len); } set_intrinsic_to_string_tag(now_obj, "Temporal.Now"); crate::value::js_nanbox_pointer(now_obj as i64) @@ -467,11 +623,13 @@ extern "C" fn temporal_proto_method_thunk( /// enumerable: false, configurable: true }`) on a Temporal prototype. #[cfg(feature = "temporal")] fn install_temporal_proto_getter(proto: *mut ObjectHeader, kind: u8, name: &str) { - let c = crate::closure::js_closure_alloc(temporal_proto_getter_thunk as *const u8, 0); + let c = crate::closure::js_closure_alloc( + crate::fn_info!(temporal_proto_getter_thunk, 0; with_declared(0)), + 0, + ); if c.is_null() { return; } - crate::closure::js_register_closure_arity(temporal_proto_getter_thunk as *const u8, 0); let cl = c as usize; crate::closure::closure_set_dynamic_prop(cl, "__tkind", kind as f64); crate::closure::closure_set_dynamic_prop( @@ -496,13 +654,15 @@ fn install_temporal_proto_getter(proto: *mut ObjectHeader, kind: u8, name: &str) /// Temporal prototype. #[cfg(feature = "temporal")] fn install_temporal_proto_method(proto: *mut ObjectHeader, kind: u8, name: &str, spec_length: u32) { - let c = crate::closure::js_closure_alloc(temporal_proto_method_thunk as *const u8, 0); + let c = crate::closure::js_closure_alloc( + crate::fn_info!(temporal_proto_method_thunk, 1; with_rest(0)), + 0, + ); if c.is_null() { return; } // Rest ABI so every argument is bundled regardless of the shared thunk's // fixed signature. - crate::closure::js_register_closure_rest(temporal_proto_method_thunk as *const u8, 0); let cl = c as usize; crate::closure::closure_set_dynamic_prop(cl, "__tkind", kind as f64); crate::closure::closure_set_dynamic_prop( @@ -582,14 +742,13 @@ fn install_temporal_prototype( fn install_temporal_constructor( ns_obj: *mut ObjectHeader, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, spec_length: u32, ) -> *mut crate::closure::ClosureHeader { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return std::ptr::null_mut(); } - crate::closure::js_register_closure_rest(func_ptr, 0); super::super::native_module::set_bound_native_closure_name(closure, name); super::super::native_module::set_builtin_closure_length(closure as usize, spec_length); let key = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32); @@ -676,10 +835,13 @@ extern "C" fn temporal_zdt_proto_method_thunk( /// proper getter `name` (`"get "`) / `length` (0). Mirrors the RegExp /// prototype getter install. #[cfg(feature = "temporal")] -fn install_temporal_getter(proto: *mut ObjectHeader, prop: &str, func_ptr: *const u8) { +fn install_temporal_getter( + proto: *mut ObjectHeader, + prop: &str, + info: *const crate::closure::JsFunctionInfo, +) { unsafe { - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } @@ -745,7 +907,11 @@ fn build_zoned_date_time_prototype() -> *mut ObjectHeader { "calendarId", ]; for g in GETTERS { - install_temporal_getter(proto, g, temporal_zdt_proto_getter_thunk as *const u8); + install_temporal_getter( + proto, + g, + crate::fn_info!(temporal_zdt_proto_getter_thunk, 0; with_declared(0)), + ); } // (name, spec_length) const METHODS: &[(&str, u32)] = &[ @@ -774,9 +940,8 @@ fn build_zoned_date_time_prototype() -> *mut ObjectHeader { install_proto_method_rest_with_length( proto, name, - temporal_zdt_proto_method_thunk as *const u8, + crate::fn_info!(temporal_zdt_proto_method_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), *len, - 0, ); } set_intrinsic_to_string_tag(proto, "Temporal.ZonedDateTime"); @@ -802,7 +967,7 @@ pub(crate) fn temporal_ctor_kind(type_ref: f64) -> Option global_this_array_thunk as *const u8, + let info = match name { + "Array" => crate::fn_info!(global_this_array_thunk, 1; with_rest(0)), // #10423: `F(p, body)` through a `Function` value creates a // function, exactly like `new F(p, body)`. - "Function" => global_this_function_call_thunk as *const u8, - "Object" => global_this_object_thunk as *const u8, - "String" => global_this_string_thunk as *const u8, + "Function" => { + crate::fn_info!(unwind_in_tests global_this_function_call_thunk, 1; with_rest(0)) + } + "Object" => crate::fn_info!(global_this_object_thunk, 1; with_declared(1)), + "String" => crate::fn_info!(global_this_string_thunk, 1; with_declared(1)), // #2889: call-form `Number(x)` / `Boolean(x)` through a rebound // global value coerce like the bare-call lowering does. - "Number" => global_this_number_thunk as *const u8, - "Boolean" => global_this_boolean_thunk as *const u8, - "Error" => error_constructor_call_thunk as *const u8, - "TypeError" => type_error_constructor_call_thunk as *const u8, - "RangeError" => range_error_constructor_call_thunk as *const u8, - "ReferenceError" => reference_error_constructor_call_thunk as *const u8, - "SyntaxError" => syntax_error_constructor_call_thunk as *const u8, - "EvalError" => eval_error_constructor_call_thunk as *const u8, - "URIError" => uri_error_constructor_call_thunk as *const u8, + "Number" => crate::fn_info!(global_this_number_thunk, 1; with_declared(1)), + "Boolean" => crate::fn_info!(global_this_boolean_thunk, 1; with_declared(1)), + "Error" => crate::fn_info!(error_constructor_call_thunk, 1; with_declared(1)), + "TypeError" => crate::fn_info!(type_error_constructor_call_thunk, 1; with_declared(1)), + "RangeError" => { + crate::fn_info!(range_error_constructor_call_thunk, 1; with_declared(1)) + } + "ReferenceError" => { + crate::fn_info!(reference_error_constructor_call_thunk, 1; with_declared(1)) + } + "SyntaxError" => { + crate::fn_info!(syntax_error_constructor_call_thunk, 1; with_declared(1)) + } + "EvalError" => crate::fn_info!(eval_error_constructor_call_thunk, 1; with_declared(1)), + "URIError" => crate::fn_info!(uri_error_constructor_call_thunk, 1; with_declared(1)), "MessageChannel" => { - crate::messaging::js_message_channel_constructor_call_error as *const u8 + crate::fn_info!(crate::messaging::js_message_channel_constructor_call_error, 0; with_declared(0)) + } + "MessagePort" => { + crate::fn_info!(crate::messaging::js_message_port_constructor_call_error, 0; with_declared(0)) } - "MessagePort" => crate::messaging::js_message_port_constructor_call_error as *const u8, "BroadcastChannel" => { - crate::messaging::js_broadcast_channel_constructor_call_error as *const u8 + crate::fn_info!(crate::messaging::js_broadcast_channel_constructor_call_error, 1; with_declared(1)) + } + "Date" => crate::fn_info!(global_this_date_thunk, 1; with_declared(1)), + "Blob" => crate::fn_info!(global_this_blob_thunk, 2; with_declared(2)), + "File" => crate::fn_info!(global_this_file_thunk, 3; with_declared(3)), + "Headers" => crate::fn_info!(global_this_headers_thunk, 1; with_declared(1)), + "Request" => crate::fn_info!(global_this_request_thunk, 2; with_declared(2)), + "Response" => crate::fn_info!(global_this_response_thunk, 2; with_declared(2)), + "URLPattern" => { + crate::fn_info!(global_this_url_pattern_call_thunk, 2; with_declared(2)) + } + "Storage" => { + crate::fn_info!(crate::web_storage::storage_constructor_illegal, 0; with_declared(0)) } - "Date" => global_this_date_thunk as *const u8, - "Blob" => global_this_blob_thunk as *const u8, - "File" => global_this_file_thunk as *const u8, - "Headers" => global_this_headers_thunk as *const u8, - "Request" => global_this_request_thunk as *const u8, - "Response" => global_this_response_thunk as *const u8, - "URLPattern" => global_this_url_pattern_call_thunk as *const u8, - "Storage" => crate::web_storage::storage_constructor_illegal as *const u8, "Crypto" | "CryptoKey" | "SubtleCrypto" => { - webcrypto_illegal_constructor_thunk as *const u8 + crate::fn_info!(webcrypto_illegal_constructor_thunk, 0) } "Int8Array" | "Uint8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array" | "Int32Array" | "Uint32Array" | "Float16Array" | "Float32Array" | "Float64Array" - | "BigInt64Array" | "BigUint64Array" => typed_array_constructor_call_thunk as *const u8, + | "BigInt64Array" | "BigUint64Array" => { + crate::fn_info!(typed_array_constructor_call_thunk, 1; with_declared(0)) + } // #4569: collection constructors throw when called without `new`. - "RegExp" => regexp_constructor_call_thunk as *const u8, - "Map" => map_constructor_call_thunk as *const u8, - "Set" => set_constructor_call_thunk as *const u8, - "WeakMap" => weak_map_constructor_call_thunk as *const u8, - "WeakSet" => weak_set_constructor_call_thunk as *const u8, - "WeakRef" => weak_ref_constructor_call_thunk as *const u8, - "Promise" => promise_constructor_call_thunk as *const u8, + "RegExp" => crate::fn_info!(regexp_constructor_call_thunk, 2; with_declared(2)), + "Map" => crate::fn_info!(map_constructor_call_thunk, 1), + "Set" => crate::fn_info!(set_constructor_call_thunk, 1), + "WeakMap" => crate::fn_info!(weak_map_constructor_call_thunk, 1), + "WeakSet" => crate::fn_info!(weak_set_constructor_call_thunk, 1), + "WeakRef" => crate::fn_info!(weak_ref_constructor_call_thunk, 1), + "Promise" => crate::fn_info!(promise_constructor_call_thunk, 1), "ArrayBuffer" | "SharedArrayBuffer" | "DataView" => { - construct_only_builtin_call_thunk as *const u8 + crate::fn_info!(construct_only_builtin_call_thunk, 0) } - _ => global_this_builtin_noop_thunk as *const u8, + _ => crate::fn_info!(global_this_builtin_noop_thunk, 1), }; - let closure_ptr = crate::closure::js_closure_alloc(func_ptr, 0); + let closure_ptr = crate::closure::js_closure_alloc(info, 0); if closure_ptr.is_null() { continue; } - match name { - "Array" | "Function" => { - crate::closure::js_register_closure_rest(func_ptr, 0); - } - "Date" => { - crate::closure::js_register_closure_arity(func_ptr, 1); - } - "Object" | "String" | "Number" | "Boolean" | "BroadcastChannel" => { - crate::closure::js_register_closure_arity(func_ptr, 1); - } - "Headers" => { - crate::closure::js_register_closure_arity(func_ptr, 1); - } - "Blob" | "Request" | "Response" => { - crate::closure::js_register_closure_arity(func_ptr, 2); - } - "File" => { - crate::closure::js_register_closure_arity(func_ptr, 3); - } - "Error" | "TypeError" | "RangeError" | "ReferenceError" | "SyntaxError" - | "EvalError" | "URIError" => { - crate::closure::js_register_closure_arity(func_ptr, 1); - } - "MessageChannel" | "MessagePort" | "Storage" => { - crate::closure::js_register_closure_arity(func_ptr, 0); - } - "URLPattern" => { - crate::closure::js_register_closure_arity(func_ptr, 2); - } - // RegExp(pattern, flags) — the call form constructs (22.2.4). - "RegExp" => { - crate::closure::js_register_closure_arity(func_ptr, 2); - } - "Int8Array" | "Uint8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array" - | "Int32Array" | "Uint32Array" | "Float16Array" | "Float32Array" | "Float64Array" - | "BigInt64Array" | "BigUint64Array" => { - crate::closure::js_register_closure_arity(func_ptr, 0); - } - _ => {} - } // #2889: install static methods (`Object.keys`, `Array.isArray`, ...) // on the constructor closure so rebound usage like // `const O = Object; O.keys(x)` dispatches through the real helpers. @@ -593,84 +569,140 @@ pub(crate) fn populate_global_this_builtins(singleton_at_entry: *mut ObjectHeade // Callable global functions: ClosureHeader-backed values with real // dispatch so direct property reads and rebound calls match bare calls. for name in GLOBAL_THIS_BUILTIN_FUNCTIONS.iter().copied() { - let (func_ptr, arity, has_rest, enumerable) = match name { - "eval" => (global_this_eval_thunk as *const u8, 1, false, false), + let (info, arity, enumerable) = match name { + "eval" => ( + crate::fn_info!(global_this_eval_thunk, 1; with_declared(1)), + 1, + false, + ), "fetch" => ( - super::super::global_fetch::global_this_fetch_thunk as *const u8, + crate::fn_info!(super::super::global_fetch::global_this_fetch_thunk, 2; with_rest(1)), 1, true, - true, ), "structuredClone" => ( - global_this_structured_clone_thunk as *const u8, + crate::fn_info!(global_this_structured_clone_thunk, 2; with_declared(2)), + 2, + true, + ), + "atob" => ( + crate::fn_info!(global_this_atob_thunk, 1; with_declared(1)), + 1, + true, + ), + "btoa" => ( + crate::fn_info!(global_this_btoa_thunk, 1; with_declared(1)), + 1, + true, + ), + "setTimeout" => ( + crate::fn_info!(global_this_set_timeout_thunk, 3; with_rest(2)), + 2, + true, + ), + "clearTimeout" => ( + crate::fn_info!(global_this_clear_timeout_thunk, 1; with_declared(1)), + 1, + true, + ), + "setInterval" => ( + crate::fn_info!(global_this_set_interval_thunk, 3; with_rest(2)), 2, - false, true, ), - "atob" => (global_this_atob_thunk as *const u8, 1, false, true), - "btoa" => (global_this_btoa_thunk as *const u8, 1, false, true), - "setTimeout" => (global_this_set_timeout_thunk as *const u8, 2, true, true), - "clearTimeout" => (global_this_clear_timeout_thunk as *const u8, 1, false, true), - "setInterval" => (global_this_set_interval_thunk as *const u8, 2, true, true), "clearInterval" => ( - global_this_clear_interval_thunk as *const u8, + crate::fn_info!(global_this_clear_interval_thunk, 1; with_declared(1)), + 1, + true, + ), + "setImmediate" => ( + crate::fn_info!(global_this_set_immediate_thunk, 2; with_rest(1)), 1, - false, true, ), - "setImmediate" => (global_this_set_immediate_thunk as *const u8, 1, true, true), "clearImmediate" => ( - global_this_clear_immediate_thunk as *const u8, + crate::fn_info!(global_this_clear_immediate_thunk, 1; with_declared(1)), 1, - false, true, ), "queueMicrotask" => ( - global_this_queue_microtask_thunk as *const u8, + crate::fn_info!(global_this_queue_microtask_thunk, 1; with_declared(1)), 1, - false, true, ), // `gc([force])` — value form of the bare `gc()` call-intrinsic. // Non-enumerable (a debug/diagnostic global). The optional `force` // arg is accepted (arity 1) but ignored — Perry's gc is full. - "gc" => (global_this_gc_thunk as *const u8, 1, false, false), + "gc" => ( + crate::fn_info!(global_this_gc_thunk, 1; with_declared(1)), + 1, + false, + ), // #2905: standard global helper functions. - "parseInt" => (global_this_parse_int_thunk as *const u8, 2, false, false), - "parseFloat" => (global_this_parse_float_thunk as *const u8, 1, false, false), - "isNaN" => (global_this_is_nan_thunk as *const u8, 1, false, false), - "isFinite" => (global_this_is_finite_thunk as *const u8, 1, false, false), - "encodeURI" => (global_this_encode_uri_thunk as *const u8, 1, false, false), - "decodeURI" => (global_this_decode_uri_thunk as *const u8, 1, false, false), - "encodeURIComponent" => ( - global_this_encode_uri_component_thunk as *const u8, + "parseInt" => ( + crate::fn_info!(global_this_parse_int_thunk, 2; with_declared(2)), + 2, + false, + ), + "parseFloat" => ( + crate::fn_info!(global_this_parse_float_thunk, 1; with_declared(1)), 1, false, + ), + "isNaN" => ( + crate::fn_info!(global_this_is_nan_thunk, 1; with_declared(1)), + 1, false, ), - "decodeURIComponent" => ( - global_this_decode_uri_component_thunk as *const u8, + "isFinite" => ( + crate::fn_info!(global_this_is_finite_thunk, 1; with_declared(1)), 1, false, + ), + "encodeURI" => ( + crate::fn_info!(global_this_encode_uri_thunk, 1; with_declared(1)), + 1, + false, + ), + "decodeURI" => ( + crate::fn_info!(global_this_decode_uri_thunk, 1; with_declared(1)), + 1, + false, + ), + "encodeURIComponent" => ( + crate::fn_info!(global_this_encode_uri_component_thunk, 1; with_declared(1)), + 1, + false, + ), + "decodeURIComponent" => ( + crate::fn_info!(global_this_decode_uri_component_thunk, 1; with_declared(1)), + 1, false, ), // #4511: legacy escape/unescape (ES Annex B). // #4511: legacy escape/unescape (ES Annex B). - "escape" => (global_this_escape_thunk as *const u8, 1, false, false), - "unescape" => (global_this_unescape_thunk as *const u8, 1, false, false), + "escape" => ( + crate::fn_info!(global_this_escape_thunk, 1; with_declared(1)), + 1, + false, + ), + "unescape" => ( + crate::fn_info!(global_this_unescape_thunk, 1; with_declared(1)), + 1, + false, + ), _ => continue, }; - let closure_ptr = crate::closure::js_closure_alloc(func_ptr, 0); + let closure_ptr = crate::closure::js_closure_alloc(info, 0); if closure_ptr.is_null() { continue; } - if has_rest { - crate::closure::js_register_closure_rest(func_ptr, arity); - } else { - crate::closure::js_register_closure_arity(func_ptr, arity); - } unsafe { - crate::builtins::js_register_function_name(func_ptr, name.as_ptr(), name.len() as u32); + crate::builtins::js_register_function_name( + (*info).code, + name.as_ptr(), + name.len() as u32, + ); } super::super::native_module::set_builtin_closure_length(closure_ptr as usize, arity); // Every global helper installed here (parseInt/parseFloat/isNaN/ @@ -797,9 +829,10 @@ pub(crate) fn populate_global_this_builtins(singleton_at_entry: *mut ObjectHeade js_object_set_field_by_name(singleton(), key, value); } super::super::native_module::install_global_webcrypto(singleton()); - let func_ptr = global_this_crypto_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); - let getter = crate::closure::js_closure_alloc(func_ptr, 0); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(global_this_crypto_getter_thunk, 0; with_declared(0)), + 0, + ); let getter_bits = if getter.is_null() { 0 } else { @@ -1028,12 +1061,13 @@ fn install_error_static_methods(ctor: *mut crate::closure::ClosureHeader) { return; } ERROR_CONSTRUCTOR_PTR.with(|c| c.set(ctor as usize)); - let func_ptr = global_this_error_capture_stack_trace_thunk as *const u8; - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(global_this_error_capture_stack_trace_thunk, 2; with_declared(2)), + 0, + ); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 2); super::super::native_module::set_bound_native_closure_name(closure, "captureStackTrace"); let key = crate::string::js_string_from_bytes(b"captureStackTrace".as_ptr(), 17); @@ -1050,16 +1084,14 @@ fn install_error_static_methods(ctor: *mut crate::closure::ClosureHeader) { install_error_static_fn( ctor, "isError", - global_this_error_is_error_thunk as *const u8, - 1, + crate::fn_info!(global_this_error_is_error_thunk, 1; with_declared(1)), ); // #2904: `Error.prepareStackTrace` — default stack-formatting hook. install_error_static_fn( ctor, "prepareStackTrace", - global_this_error_prepare_stack_trace_thunk as *const u8, - 2, + crate::fn_info!(global_this_error_prepare_stack_trace_thunk, 2; with_declared(2)), ); // #2904: `Error.stackTraceLimit` — writable number controlling captured @@ -1081,14 +1113,12 @@ fn install_error_static_methods(ctor: *mut crate::closure::ClosureHeader) { fn install_error_static_fn( ctor: *mut crate::closure::ClosureHeader, name: &str, - func_ptr: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, ) { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, arity); super::super::native_module::set_bound_native_closure_name(closure, name); let key = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32); let value = crate::value::js_nanbox_pointer(closure as i64); diff --git a/crates/perry-runtime/src/object/global_this/proto_methods.rs b/crates/perry-runtime/src/object/global_this/proto_methods.rs index 975420c954..f828d6b437 100644 --- a/crates/perry-runtime/src/object/global_this/proto_methods.rs +++ b/crates/perry-runtime/src/object/global_this/proto_methods.rs @@ -185,24 +185,17 @@ fn install_object_prototype_dunder_proto(proto_obj: *mut ObjectHeader) { return; } let getter = crate::closure::js_closure_alloc( - object_prototype_dunder_proto_getter_thunk as *const u8, + crate::fn_info!(object_prototype_dunder_proto_getter_thunk, 0; with_declared(0)), 0, ); let setter = crate::closure::js_closure_alloc( - object_prototype_dunder_proto_setter_thunk as *const u8, + crate::fn_info!(object_prototype_dunder_proto_setter_thunk, 1; with_declared(1)), 0, ); if getter.is_null() || setter.is_null() { return; } - crate::closure::js_register_closure_arity( - object_prototype_dunder_proto_getter_thunk as *const u8, - 0, - ); - crate::closure::js_register_closure_arity( - object_prototype_dunder_proto_setter_thunk as *const u8, - 1, - ); + super::super::native_module::set_bound_native_closure_name(getter, "get __proto__"); super::super::native_module::set_bound_native_closure_name(setter, "set __proto__"); super::super::native_module::set_builtin_closure_length(getter as usize, 0); @@ -278,9 +271,12 @@ extern "C" fn object_prototype_dunder_proto_setter_thunk( /// `{ enumerable: true, configurable: true }`, and node prints /// `for-in d: decode,encoding,fatal,ignoreBOM`. #[cfg(feature = "global-text")] -fn install_text_accessor(proto_obj: *mut ObjectHeader, name: &str, getter: *const u8) { +fn install_text_accessor( + proto_obj: *mut ObjectHeader, + name: &str, + getter: *const crate::closure::JsFunctionInfo, +) { unsafe { - crate::closure::js_register_closure_arity(getter, 0); let closure = crate::closure::js_closure_alloc(getter, 0); if closure.is_null() { return; @@ -365,13 +361,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "slice", - array_prototype_slice_thunk as *const u8, + crate::fn_info!(array_prototype_slice_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); install_proto_method( proto_obj, "toString", - array_prototype_to_string_thunk as *const u8, + crate::fn_info!(array_prototype_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_noop_proto_methods( @@ -390,7 +386,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "flat", - array_prototype_flat_thunk as *const u8, + crate::fn_info!(array_prototype_flat_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // Generic mutators get REAL thunks (vs the noop above) so a borrowed @@ -407,43 +403,45 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: let values_value = install_proto_method( proto_obj, "values", - array_prototype_values_thunk as *const u8, + crate::fn_info!(array_prototype_values_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_builtin_iterator_symbol(proto_obj, values_value); - install_proto_method(proto_obj, "pop", array_prototype_pop_thunk as *const u8, 0); + install_proto_method( + proto_obj, + "pop", + crate::fn_info!(array_prototype_pop_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); install_proto_method( proto_obj, "shift", - array_prototype_shift_thunk as *const u8, + crate::fn_info!(array_prototype_shift_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "reverse", - array_prototype_reverse_thunk as *const u8, + crate::fn_info!(array_prototype_reverse_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method_rest_with_length( proto_obj, "push", - array_prototype_push_thunk as *const u8, + crate::fn_info!(array_prototype_push_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 1, - 0, ); install_proto_method_rest_with_length( proto_obj, "unshift", - array_prototype_unshift_thunk as *const u8, + crate::fn_info!(array_prototype_unshift_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 1, - 0, ); install_proto_method_rest_with_length( proto_obj, "splice", - array_prototype_splice_thunk as *const u8, + crate::fn_info!(array_prototype_splice_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 2, - 0, ); // #6908: `fill` / `copyWithin` get real thunks too — a Proxy // receiver resolves them through `Get(proxy, name)` to the @@ -452,16 +450,14 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method_rest_with_length( proto_obj, "fill", - array_prototype_fill_thunk as *const u8, + crate::fn_info!(array_prototype_fill_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 1, - 0, ); install_proto_method_rest_with_length( proto_obj, "copyWithin", - array_prototype_copy_within_thunk as *const u8, + crate::fn_info!(array_prototype_copy_within_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 2, - 0, ); // `sort` / `concat` get real thunks too: a borrowed // `obj.sort = Array.prototype.sort; obj.sort()` must run the @@ -471,58 +467,130 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "sort", - array_prototype_sort_thunk as *const u8, + crate::fn_info!(array_prototype_sort_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); // Iteration / search methods: real generic-engine thunks (rest // shape — spec `.length` recorded separately below). - type RestThunk = crate::closure::body_call::js_body_fn_ty!(a); - let arraylike_thunks: [(&str, RestThunk, u32); 14] = [ - ("forEach", array_proto_forEach_thunk, 1), - ("map", array_proto_map_thunk, 1), - ("filter", array_proto_filter_thunk, 1), - ("some", array_proto_some_thunk, 1), - ("every", array_proto_every_thunk, 1), - ("find", array_proto_find_thunk, 1), - ("findIndex", array_proto_findIndex_thunk, 1), - ("findLast", array_proto_findLast_thunk, 1), - ("findLastIndex", array_proto_findLastIndex_thunk, 1), - ("reduce", array_proto_reduce_thunk, 1), - ("reduceRight", array_proto_reduceRight_thunk, 1), - ("indexOf", array_proto_indexOf_thunk, 1), - ("lastIndexOf", array_proto_lastIndexOf_thunk, 1), - ("includes", array_proto_includes_thunk, 1), + let arraylike_thunks: [(&str, *const crate::closure::JsFunctionInfo, u32); 14] = [ + ( + "forEach", + crate::fn_info!(array_proto_forEach_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "map", + crate::fn_info!(array_proto_map_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "filter", + crate::fn_info!(array_proto_filter_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "some", + crate::fn_info!(array_proto_some_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "every", + crate::fn_info!(array_proto_every_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "find", + crate::fn_info!(array_proto_find_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "findIndex", + crate::fn_info!(array_proto_findIndex_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "findLast", + crate::fn_info!(array_proto_findLast_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "findLastIndex", + crate::fn_info!(array_proto_findLastIndex_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "reduce", + crate::fn_info!(array_proto_reduce_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "reduceRight", + crate::fn_info!(array_proto_reduceRight_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "indexOf", + crate::fn_info!(array_proto_indexOf_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "lastIndexOf", + crate::fn_info!(array_proto_lastIndexOf_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ( + "includes", + crate::fn_info!(array_proto_includes_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), ]; for (name, thunk, len) in arraylike_thunks { - install_proto_method_rest_with_length(proto_obj, name, thunk as *const u8, len, 0); + install_proto_method_rest_with_length(proto_obj, name, thunk, len); } - install_proto_method(proto_obj, "at", array_proto_at_thunk as *const u8, 1); - install_proto_method(proto_obj, "join", array_proto_join_thunk as *const u8, 1); + install_proto_method( + proto_obj, + "at", + crate::fn_info!(array_proto_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + install_proto_method( + proto_obj, + "join", + crate::fn_info!(array_proto_join_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); install_proto_method_rest_with_length( proto_obj, "concat", - array_prototype_concat_thunk as *const u8, + crate::fn_info!(array_prototype_concat_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 1, - 0, ); install_noop_proto_methods(proto_obj, OBJECT_PROTO_METHODS); } "ArrayBuffer" => { - install_proto_method(proto_obj, "slice", array_buffer_slice_thunk as *const u8, 2); + install_proto_method( + proto_obj, + "slice", + crate::fn_info!(array_buffer_slice_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); // ES2024 (#10873): `resize`, `transfer`, `transferToFixedLength`. install_proto_method( proto_obj, "resize", - array_buffer_resize_thunk as *const u8, + crate::fn_info!(array_buffer_resize_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); // The thunks take the optional `newLength` (call arity 1); the // spec `.length` of both is 0. - let transfers: [(&str, *const u8); 2] = [ - ("transfer", array_buffer_transfer_thunk as *const u8), + let transfers: [(&str, *const crate::closure::JsFunctionInfo); 2] = [ + ( + "transfer", + crate::fn_info!(array_buffer_transfer_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + ), ( "transferToFixedLength", - array_buffer_transfer_to_fixed_length_thunk as *const u8, + crate::fn_info!(array_buffer_transfer_to_fixed_length_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), ), ]; for (name, thunk) in transfers { @@ -532,24 +600,26 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: super::super::native_module::set_builtin_closure_length(closure, 0); } } - let getters: [(&str, *const u8); 4] = [ + let getters: [(&str, *const crate::closure::JsFunctionInfo); 4] = [ ( "byteLength", - array_buffer_byte_length_getter_thunk as *const u8, + crate::fn_info!(array_buffer_byte_length_getter_thunk, 0; with_declared(0)), ), ( "resizable", - array_buffer_resizable_getter_thunk as *const u8, + crate::fn_info!(array_buffer_resizable_getter_thunk, 0; with_declared(0)), ), ( "maxByteLength", - array_buffer_max_byte_length_getter_thunk as *const u8, + crate::fn_info!(array_buffer_max_byte_length_getter_thunk, 0; with_declared(0)), + ), + ( + "detached", + crate::fn_info!(array_buffer_detached_getter_thunk, 0; with_declared(0)), ), - ("detached", array_buffer_detached_getter_thunk as *const u8), ]; for (name, thunk) in getters { unsafe { - crate::closure::js_register_closure_arity(thunk, 0); let getter = crate::closure::js_closure_alloc(thunk, 0); if !getter.is_null() { let getter_bits = crate::value::js_nanbox_pointer(getter as i64).to_bits(); @@ -568,16 +638,12 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "slice", - shared_array_buffer_slice_thunk as *const u8, + crate::fn_info!(shared_array_buffer_slice_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); unsafe { - crate::closure::js_register_closure_arity( - shared_array_buffer_byte_length_getter_thunk as *const u8, - 0, - ); let getter = crate::closure::js_closure_alloc( - shared_array_buffer_byte_length_getter_thunk as *const u8, + crate::fn_info!(shared_array_buffer_byte_length_getter_thunk, 0; with_declared(0)), 0, ); if !getter.is_null() { @@ -602,49 +668,49 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - object_prototype_to_string_thunk as *const u8, + crate::fn_info!(object_prototype_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "isPrototypeOf", - object_prototype_is_prototype_of_thunk as *const u8, + crate::fn_info!(object_prototype_is_prototype_of_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "hasOwnProperty", - object_prototype_has_own_property_thunk as *const u8, + crate::fn_info!(object_prototype_has_own_property_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "propertyIsEnumerable", - object_prototype_property_is_enumerable_thunk as *const u8, + crate::fn_info!(object_prototype_property_is_enumerable_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "toLocaleString", - object_prototype_to_locale_string_thunk as *const u8, + crate::fn_info!(object_prototype_to_locale_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "valueOf", - object_prototype_value_of_thunk as *const u8, + crate::fn_info!(object_prototype_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "hasOwnProperty", - object_prototype_has_own_property_thunk as *const u8, + crate::fn_info!(object_prototype_has_own_property_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "propertyIsEnumerable", - object_prototype_property_is_enumerable_thunk as *const u8, + crate::fn_info!(object_prototype_property_is_enumerable_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_object_prototype_dunder_proto(proto_obj); @@ -677,13 +743,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "apply", - function_prototype_apply_thunk as *const u8, + crate::fn_info!(function_prototype_apply_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); install_proto_method_rest( proto_obj, "bind", - function_prototype_bind_thunk as *const u8, + crate::fn_info!(function_prototype_bind_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); // #4101: dedicated toString thunk (source reconstruction + brand @@ -691,13 +757,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - function_prototype_to_string_thunk as *const u8, + crate::fn_info!(function_prototype_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method_rest( proto_obj, "call", - function_prototype_call_thunk as *const u8, + crate::fn_info!(function_prototype_call_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_noop_proto_methods(proto_obj, OBJECT_PROTO_METHODS); @@ -718,13 +784,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - primitive_proto_thunks::string_proto_to_string_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::string_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "valueOf", - primitive_proto_thunks::string_proto_value_of_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::string_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -748,13 +814,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "valueOf", - primitive_proto_thunks::number_proto_value_of_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::number_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "toLocaleString", - primitive_proto_thunks::number_proto_to_locale_string_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::number_proto_to_locale_string_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -765,13 +831,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - primitive_proto_thunks::boolean_proto_to_string_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::boolean_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "valueOf", - primitive_proto_thunks::boolean_proto_value_of_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::boolean_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -787,13 +853,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - primitive_proto_thunks::symbol_proto_to_string_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::symbol_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "valueOf", - primitive_proto_thunks::symbol_proto_value_of_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::symbol_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -805,7 +871,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: let to_string = install_proto_method( proto_obj, "toString", - primitive_proto_thunks::bigint_proto_to_string_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::bigint_proto_to_string_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); super::super::native_module::set_builtin_closure_length( @@ -815,7 +881,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "valueOf", - primitive_proto_thunks::bigint_proto_value_of_thunk as *const u8, + crate::fn_info!(primitive_proto_thunks::bigint_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -886,13 +952,13 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "isPrototypeOf", - object_prototype_is_prototype_of_thunk as *const u8, + crate::fn_info!(object_prototype_is_prototype_of_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "toString", - date_prototype_to_string_thunk as *const u8, + crate::fn_info!(date_prototype_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // `Date.prototype[Symbol.toPrimitive]` — a generic `OrdinaryToPrimitive` @@ -916,8 +982,18 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: } #[cfg(feature = "global-url")] "URLPattern" => { - install_proto_method_rest(proto_obj, "exec", url_pattern_exec_thunk as *const u8, 1); - install_proto_method_rest(proto_obj, "test", url_pattern_test_thunk as *const u8, 1); + install_proto_method_rest( + proto_obj, + "exec", + crate::fn_info!(url_pattern_exec_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + install_proto_method_rest( + proto_obj, + "test", + crate::fn_info!(url_pattern_test_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); for name in [ "hasRegExpGroups", "hash", @@ -993,19 +1069,19 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "catch", - crate::promise::promise_prototype_catch_thunk as *const u8, + crate::fn_info!(crate::promise::promise_prototype_catch_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "finally", - crate::promise::promise_prototype_finally_thunk as *const u8, + crate::fn_info!(crate::promise::promise_prototype_finally_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "then", - crate::promise::promise_prototype_then_thunk as *const u8, + crate::fn_info!(crate::promise::promise_prototype_then_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); set_intrinsic_to_string_tag(proto_obj, "Promise"); @@ -1021,19 +1097,19 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "encode", - crate::text::text_encoder_encode_thunk as *const u8, + crate::fn_info!(crate::text::text_encoder_encode_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "encodeInto", - crate::text::text_encoder_encode_into_thunk as *const u8, + crate::fn_info!(crate::text::text_encoder_encode_into_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); install_text_accessor( proto_obj, "encoding", - crate::text::text_encoder_encoding_getter as *const u8, + crate::fn_info!(crate::text::text_encoder_encoding_getter, 0; with_declared(0)), ); for name in ["encode", "encodeInto"] { make_proto_member_enumerable(proto_obj, name); @@ -1046,23 +1122,23 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "decode", - crate::text::text_decoder_decode_thunk as *const u8, + crate::fn_info!(crate::text::text_decoder_decode_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_text_accessor( proto_obj, "encoding", - crate::text::text_decoder_encoding_getter as *const u8, + crate::fn_info!(crate::text::text_decoder_encoding_getter, 0; with_declared(0)), ); install_text_accessor( proto_obj, "fatal", - crate::text::text_decoder_fatal_getter as *const u8, + crate::fn_info!(crate::text::text_decoder_fatal_getter, 0; with_declared(0)), ); install_text_accessor( proto_obj, "ignoreBOM", - crate::text::text_decoder_ignore_bom_getter as *const u8, + crate::fn_info!(crate::text::text_decoder_ignore_bom_getter, 0; with_declared(0)), ); make_proto_member_enumerable(proto_obj, "decode"); install_noop_proto_methods(proto_obj, OBJECT_PROTO_METHODS); @@ -1077,7 +1153,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: let entries_value = install_proto_method( proto_obj, "entries", - global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_builtin_iterator_symbol(proto_obj, entries_value); @@ -1153,13 +1229,9 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: ] }; unsafe { - crate::closure::js_register_closure_arity( - global_this_builtin_noop_thunk as *const u8, - 0, - ); for name in accessors { let getter = crate::closure::js_closure_alloc( - global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(0)), 0, ); if !getter.is_null() { @@ -1216,18 +1288,18 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_webcrypto_proto_getter( proto_obj, "subtle", - webcrypto_subtle_getter_thunk as *const u8, + crate::fn_info!(webcrypto_subtle_getter_thunk, 0; with_declared(0)), ); install_webcrypto_proto_method( proto_obj, "getRandomValues", - webcrypto_get_random_values_thunk as *const u8, + crate::fn_info!(webcrypto_get_random_values_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_webcrypto_proto_method( proto_obj, "randomUUID", - webcrypto_random_uuid_thunk as *const u8, + crate::fn_info!(webcrypto_random_uuid_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_noop_proto_methods(proto_obj, OBJECT_PROTO_METHODS); @@ -1235,13 +1307,22 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: #[cfg(feature = "global-webcrypto")] "CryptoKey" => { for (name, func_ptr) in [ - ("algorithm", cryptokey_algorithm_getter_thunk as *const u8), + ( + "algorithm", + crate::fn_info!(cryptokey_algorithm_getter_thunk, 0; with_declared(0)), + ), ( "extractable", - cryptokey_extractable_getter_thunk as *const u8, + crate::fn_info!(cryptokey_extractable_getter_thunk, 0; with_declared(0)), + ), + ( + "type", + crate::fn_info!(cryptokey_type_getter_thunk, 0; with_declared(0)), + ), + ( + "usages", + crate::fn_info!(cryptokey_usages_getter_thunk, 0; with_declared(0)), ), - ("type", cryptokey_type_getter_thunk as *const u8), - ("usages", cryptokey_usages_getter_thunk as *const u8), ] { install_webcrypto_proto_getter(proto_obj, name, func_ptr); } @@ -1252,22 +1333,22 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: for (name, func_ptr, length) in [ ( "encapsulateBits", - subtle_crypto_encapsulate_bits_thunk as *const u8, + crate::fn_info!(subtle_crypto_encapsulate_bits_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 2, ), ( "decapsulateBits", - subtle_crypto_decapsulate_bits_thunk as *const u8, + crate::fn_info!(subtle_crypto_decapsulate_bits_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 3, ), ( "encapsulateKey", - subtle_crypto_encapsulate_key_thunk as *const u8, + crate::fn_info!(subtle_crypto_encapsulate_key_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 5, ), ( "decapsulateKey", - subtle_crypto_decapsulate_key_thunk as *const u8, + crate::fn_info!(subtle_crypto_decapsulate_key_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 6, ), ] { @@ -1281,19 +1362,19 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto_obj, "toString", - error_prototype_to_string_thunk as *const u8, + crate::fn_info!(error_prototype_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); install_proto_method( proto_obj, "isPrototypeOf", - object_prototype_is_prototype_of_thunk as *const u8, + crate::fn_info!(object_prototype_is_prototype_of_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_proto_method( proto_obj, "hasOwnProperty", - object_prototype_has_own_property_thunk as *const u8, + crate::fn_info!(object_prototype_has_own_property_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); } @@ -1364,7 +1445,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto(), "toString", - url_prototype_href_thunk as *const u8, + crate::fn_info!(url_prototype_href_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); web_method_enumerable(proto(), "toString"); @@ -1373,7 +1454,12 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: // cannot reach this prototype links none of the accessors. #[cfg(feature = "global-url")] crate::url::prototype::install_url_prototype_accessors(proto()); - install_proto_method(proto(), "toJSON", url_prototype_href_thunk as *const u8, 0); + install_proto_method( + proto(), + "toJSON", + crate::fn_info!(url_prototype_href_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); web_method_enumerable(proto(), "toJSON"); } "AbortController" => { @@ -1388,7 +1474,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: let method = install_proto_method( proto(), "abort", - abort_controller_prototype_abort_thunk as *const u8, + crate::fn_info!(abort_controller_prototype_abort_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); if JSValue::from_bits(method.to_bits()).is_pointer() { @@ -1430,7 +1516,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: install_proto_method( proto(), "throwIfAborted", - abort_signal_prototype_throw_if_aborted_thunk as *const u8, + crate::fn_info!(abort_signal_prototype_throw_if_aborted_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); unsafe { install_web_builtin_to_string_tag(proto(), "AbortSignal") }; @@ -1486,10 +1572,10 @@ pub(crate) fn install_error_prototype_data_properties( fn install_webcrypto_proto_method( proto_obj: *mut ObjectHeader, method_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) { - install_proto_method(proto_obj, method_name, func_ptr, arity); + install_proto_method(proto_obj, method_name, info, arity); super::super::set_builtin_property_attrs( proto_obj as usize, method_name.to_string(), @@ -1500,10 +1586,10 @@ fn install_webcrypto_proto_method( fn install_webcrypto_proto_method_rest_with_length( proto_obj: *mut ObjectHeader, method_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, length: u32, ) { - install_proto_method_rest_with_length(proto_obj, method_name, func_ptr, length, 0); + install_proto_method_rest_with_length(proto_obj, method_name, info, length); super::super::set_builtin_property_attrs( proto_obj as usize, method_name.to_string(), @@ -1511,12 +1597,15 @@ fn install_webcrypto_proto_method_rest_with_length( ); } -fn install_webcrypto_proto_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) { +fn install_webcrypto_proto_getter( + proto_obj: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { if proto_obj.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); let value = if closure.is_null() { f64::from_bits(crate::value::TAG_UNDEFINED) } else { diff --git a/crates/perry-runtime/src/object/global_this/typed_array.rs b/crates/perry-runtime/src/object/global_this/typed_array.rs index 284c5d5b06..a42c1b8b25 100644 --- a/crates/perry-runtime/src/object/global_this/typed_array.rs +++ b/crates/perry-runtime/src/object/global_this/typed_array.rs @@ -519,8 +519,7 @@ fn install_typed_array_to_string_tag(proto_obj: *mut ObjectHeader) { return; } unsafe { - let f = typed_array_to_string_tag_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(f, 0); + let f = crate::fn_info!(typed_array_to_string_tag_getter_thunk, 0; with_declared(0)); let c = crate::closure::js_closure_alloc(f, 0); if c.is_null() { return; @@ -546,8 +545,7 @@ fn install_typed_array_to_string_tag(proto_obj: *mut ObjectHeader) { fn install_typed_array_proto_accessors(proto_obj: *mut ObjectHeader) { unsafe { // 0-arg getters: `.call(this)` forwards 0 user args. - let mk = |f: *const u8| -> u64 { - crate::closure::js_register_closure_arity(f, 0); + let mk = |f: *const crate::closure::JsFunctionInfo| -> u64 { let c = crate::closure::js_closure_alloc(f, 0); if c.is_null() { 0 @@ -558,22 +556,22 @@ fn install_typed_array_proto_accessors(proto_obj: *mut ObjectHeader) { install_builtin_getter( proto_obj, "length", - mk(typed_array_length_getter_thunk as *const u8), + mk(crate::fn_info!(typed_array_length_getter_thunk, 0; with_declared(0))), ); install_builtin_getter( proto_obj, "byteLength", - mk(typed_array_byte_length_getter_thunk as *const u8), + mk(crate::fn_info!(typed_array_byte_length_getter_thunk, 0; with_declared(0))), ); install_builtin_getter( proto_obj, "byteOffset", - mk(typed_array_byte_offset_getter_thunk as *const u8), + mk(crate::fn_info!(typed_array_byte_offset_getter_thunk, 0; with_declared(0))), ); install_builtin_getter( proto_obj, "buffer", - mk(typed_array_buffer_getter_thunk as *const u8), + mk(crate::fn_info!(typed_array_buffer_getter_thunk, 0; with_declared(0))), ); } } @@ -589,8 +587,7 @@ pub(crate) fn install_function_has_instance_symbol(proto_obj: *mut ObjectHeader) return; } unsafe { - let func_ptr = super::super::instanceof::function_prototype_has_instance_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); + let func_ptr = crate::fn_info!(super::super::instanceof::function_prototype_has_instance_thunk, 1; with_declared(1)); let closure = crate::closure::js_closure_alloc(func_ptr, 0); if closure.is_null() { return; @@ -670,12 +667,15 @@ pub(crate) fn ensure_typed_array_intrinsic( // `typedarray_props.rs:812` on the allocation-point route. See // `gc::tests::lazy_intrinsic_towers` for the gate. let _no_move = crate::gc::GcSuppressScope::new(); - let ctor = crate::closure::js_closure_alloc(typed_array_constructor_call_thunk as *const u8, 0); + let ctor = crate::closure::js_closure_alloc( + crate::fn_info!(typed_array_constructor_call_thunk, 1; with_declared(0)), + 0, + ); let proto = js_object_alloc(0, 0); if ctor.is_null() || proto.is_null() { return (std::ptr::null_mut(), std::ptr::null_mut()); } - crate::closure::js_register_closure_arity(typed_array_constructor_call_thunk as *const u8, 0); + super::super::native_module::set_bound_native_closure_name(ctor, "TypedArray"); super::super::native_module::set_builtin_closure_length(ctor as usize, 0); super::super::set_builtin_property_attrs( @@ -749,15 +749,18 @@ pub(crate) fn ensure_typed_array_intrinsic( // that it captures the final `values` closure (the `%TypedArray%.prototype // [@@iterator] === %TypedArray%.prototype.values` identity invariant). install_typed_array_iterator_symbol(proto); - install_constructor_static_with_call_arity( + install_constructor_static( ctor, "from", - typed_array_from_thunk as *const u8, + crate::fn_info!(typed_array_from_thunk, 3; with_declared(3)), 1, - 3, - false, ); - install_constructor_static(ctor, "of", typed_array_of_thunk as *const u8, 0, true); + install_constructor_static( + ctor, + "of", + crate::fn_info!(typed_array_of_thunk, 1; with_rest(0)), + 0, + ); crate::object::TYPED_ARRAY_INTRINSIC_PTR.store(ctor as i64, Ordering::Release); crate::object::TYPED_ARRAY_INTRINSIC_PROTO_PTR.store(proto as i64, Ordering::Release); (ctor, proto) diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index c25c0b1ad3..740a778deb 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -337,7 +337,7 @@ fn webassembly_error_ctor_expected_name(type_ref: f64) -> Option<&'static [u8]> if !crate::closure::closure_kind_probe(ptr as usize) { return None; } - let func_ptr = (*ptr).func_ptr as usize; + let func_ptr = (*ptr).code() as usize; if func_ptr == webassembly_compile_error_ctor_thunk as *const u8 as usize { Some(b"CompileError") } else if func_ptr == webassembly_link_error_ctor_thunk as *const u8 as usize { @@ -409,7 +409,7 @@ fn webassembly_value_ctor_expected_kind(type_ref: f64) -> Option<&'static [u8]> if !crate::closure::closure_kind_probe(ptr as usize) { return None; } - let func_ptr = (*ptr).func_ptr as usize; + let func_ptr = (*ptr).code() as usize; if func_ptr == webassembly_module_ctor_thunk as *const u8 as usize { Some(b"module") } else { @@ -984,27 +984,27 @@ pub(super) fn create_webassembly_namespace() -> f64 { let module_ctor = install_webassembly_constructor( ns_obj, "Module", - webassembly_module_ctor_thunk as *const u8, + crate::fn_info!(webassembly_module_ctor_thunk, 1; with_declared(1)), ); if !module_ctor.is_null() { install_webassembly_static_fn( module_ctor as *mut ObjectHeader, "exports", - webassembly_module_exports_thunk as *const u8, + crate::fn_info!(webassembly_module_exports_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( module_ctor as *mut ObjectHeader, "imports", - webassembly_module_imports_thunk as *const u8, + crate::fn_info!(webassembly_module_imports_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( module_ctor as *mut ObjectHeader, "customSections", - webassembly_module_custom_sections_thunk as *const u8, + crate::fn_info!(webassembly_module_custom_sections_thunk, 2; with_declared(2)), 2, true, ); @@ -1013,57 +1013,87 @@ pub(super) fn create_webassembly_namespace() -> f64 { let instance_ctor = install_webassembly_constructor( ns_obj, "Instance", - webassembly_instance_ctor_thunk as *const u8, + crate::fn_info!(webassembly_instance_ctor_thunk, 2; with_declared(2)), ); - // The optional imports object is a real second call argument, while the - // standard constructor metadata remains `WebAssembly.Instance.length === - // 1`. Keep the dispatch arity separate from the public length. - crate::closure::js_register_closure_arity(webassembly_instance_ctor_thunk as *const u8, 2); + // The optional imports object is a real second call argument (declared 2), + // while the standard constructor metadata remains + // `WebAssembly.Instance.length === 1`. install_webassembly_proto_data(instance_ctor, "exports", undefined()); let memory_ctor = install_webassembly_constructor( ns_obj, "Memory", - webassembly_memory_ctor_thunk as *const u8, + crate::fn_info!(webassembly_memory_ctor_thunk, 1; with_declared(1)), ); install_webassembly_proto_data(memory_ctor, "buffer", undefined()); install_webassembly_proto_fn( memory_ctor, "grow", - webassembly_memory_grow_thunk as *const u8, + crate::fn_info!(webassembly_memory_grow_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); - let table_ctor = - install_webassembly_constructor(ns_obj, "Table", webassembly_table_ctor_thunk as *const u8); - install_webassembly_proto_method(table_ctor, "get", 1); - install_webassembly_proto_method(table_ctor, "grow", 1); + let table_ctor = install_webassembly_constructor( + ns_obj, + "Table", + crate::fn_info!(webassembly_table_ctor_thunk, 1; with_declared(1)), + ); + install_webassembly_proto_fn( + table_ctor, + "get", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); + install_webassembly_proto_fn( + table_ctor, + "grow", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); install_webassembly_proto_data(table_ctor, "length", undefined()); - install_webassembly_proto_method(table_ctor, "set", 2); + install_webassembly_proto_fn( + table_ctor, + "set", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), + 2, + ); let global_ctor = install_webassembly_constructor( ns_obj, "Global", - webassembly_global_ctor_thunk as *const u8, + crate::fn_info!(webassembly_global_ctor_thunk, 2; with_declared(2)), ); - crate::closure::js_register_closure_arity(webassembly_global_ctor_thunk as *const u8, 2); install_webassembly_proto_data(global_ctor, "value", undefined()); - install_webassembly_proto_method(global_ctor, "valueOf", 0); + install_webassembly_proto_fn( + global_ctor, + "valueOf", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); - for (name, func_ptr) in [ + for (name, info) in [ ( "CompileError", - webassembly_compile_error_ctor_thunk as *const u8, + crate::fn_info!(webassembly_compile_error_ctor_thunk, 1; with_declared(1)), + ), + ( + "LinkError", + crate::fn_info!(webassembly_link_error_ctor_thunk, 1; with_declared(1)), ), - ("LinkError", webassembly_link_error_ctor_thunk as *const u8), ( "RuntimeError", - webassembly_runtime_error_ctor_thunk as *const u8, + crate::fn_info!(webassembly_runtime_error_ctor_thunk, 1; with_declared(1)), + ), + ( + "Exception", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1)), + ), + ( + "Tag", + crate::fn_info!(global_this_builtin_noop_thunk, 1; with_declared(1)), ), - ("Exception", global_this_builtin_noop_thunk as *const u8), - ("Tag", global_this_builtin_noop_thunk as *const u8), ] { - let ctor = install_webassembly_constructor(ns_obj, name, func_ptr); + let ctor = install_webassembly_constructor(ns_obj, name, info); if matches!(name, "CompileError" | "LinkError" | "RuntimeError") { install_webassembly_error_proto_data(ctor, name); } @@ -1078,45 +1108,42 @@ pub(super) fn create_webassembly_namespace() -> f64 { install_webassembly_static_fn( ns_obj, "compile", - webassembly_compile_thunk as *const u8, + crate::fn_info!(webassembly_compile_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( ns_obj, "validate", - webassembly_validate_thunk as *const u8, + crate::fn_info!(webassembly_validate_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( ns_obj, "instantiate", - webassembly_instantiate_thunk as *const u8, + crate::fn_info!(webassembly_instantiate_thunk, 2; with_declared(2)), 1, true, ); - // The optional imports object is a real second call argument, while - // `WebAssembly.instantiate.length` stays 1. - crate::closure::js_register_closure_arity(webassembly_instantiate_thunk as *const u8, 2); install_webassembly_static_fn( ns_obj, "compileStreaming", - webassembly_compile_streaming_thunk as *const u8, + crate::fn_info!(webassembly_compile_streaming_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( ns_obj, "instantiateStreaming", - webassembly_instantiate_streaming_thunk as *const u8, + crate::fn_info!(webassembly_instantiate_streaming_thunk, 1; with_declared(1)), 1, true, ); install_webassembly_static_fn( ns_obj, "promising", - webassembly_unsupported_static_thunk as *const u8, + crate::fn_info!(webassembly_unsupported_static_thunk, 1; with_declared(1)), 1, true, ); @@ -1127,16 +1154,15 @@ pub(super) fn create_webassembly_namespace() -> f64 { fn install_webassembly_constructor( ns_obj: *mut ObjectHeader, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, ) -> *mut crate::closure::ClosureHeader { if ns_obj.is_null() { return std::ptr::null_mut(); } - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return std::ptr::null_mut(); } - crate::closure::js_register_closure_arity(func_ptr, 1); super::super::native_module::set_bound_native_closure_name(closure, name); super::super::native_module::set_builtin_closure_length(closure as usize, 1); super::super::set_builtin_property_attrs( @@ -1188,18 +1214,17 @@ fn install_webassembly_constructor( fn install_webassembly_static_fn( obj: *mut ObjectHeader, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, enumerable: bool, ) { if obj.is_null() { return; } - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, arity); super::super::native_module::set_bound_native_closure_name(closure, name); super::super::native_module::set_builtin_closure_length(closure as usize, arity); super::super::set_builtin_property_attrs( @@ -1241,30 +1266,17 @@ fn webassembly_constructor_proto(ctor: *mut crate::closure::ClosureHeader) -> *m } } -fn install_webassembly_proto_method( - ctor: *mut crate::closure::ClosureHeader, - name: &str, - arity: u32, -) { - install_webassembly_proto_fn( - ctor, - name, - global_this_builtin_noop_thunk as *const u8, - arity, - ); -} - fn install_webassembly_proto_fn( ctor: *mut crate::closure::ClosureHeader, name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) { let proto = webassembly_constructor_proto(ctor); if proto.is_null() { return; } - install_proto_method(proto, name, func_ptr, arity); + install_proto_method(proto, name, info, arity); } fn install_webassembly_proto_data( @@ -1476,10 +1488,11 @@ mod tests { let grow = js_object_get_field_by_name_f64(memory_proto, named_key(b"grow")); let grow_jv = crate::value::JSValue::from_bits(grow.to_bits()); assert!(grow_jv.is_pointer(), "Memory.prototype.grow must exist"); + let instantiate = closure_ptr(ns_field(ns, b"instantiate")); assert_eq!( - crate::closure::lookup_closure_arity(webassembly_instantiate_thunk as *const u8), + crate::closure::closure_arity(instantiate), Some(2), - "the install helper must not overwrite instantiate's two-argument dispatch" + "instantiate's info must carry its two-argument declared arity" ); } diff --git a/crates/perry-runtime/src/object/inherited_read_cache_tests.rs b/crates/perry-runtime/src/object/inherited_read_cache_tests.rs index 8fdcf37f50..b83be0a8e9 100644 --- a/crates/perry-runtime/src/object/inherited_read_cache_tests.rs +++ b/crates/perry-runtime/src/object/inherited_read_cache_tests.rs @@ -829,7 +829,7 @@ extern "C" fn forty_two_raw_getter(_this: f64) -> f64 { /// A CLASS accessor on `proto`: its pair carries a compiled getter entry, as /// a ClassBody `get` does (`decl_accessors.rs`). unsafe fn install_class_getter(proto: *mut ObjectHeader, name: &str) { - let getter = crate::closure::js_closure_alloc(forty_two_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(forty_two_getter, 0), 0); crate::object::set_builtin_accessor_pair( proto as usize, name.to_string(), @@ -853,7 +853,7 @@ fn a_closure_only_accessor_is_not_primed() { unsafe { let proto = crate::object::js_object_alloc(0, 4); set(proto, "irc_closure", 7.0); - let getter = crate::closure::js_closure_alloc(forty_two_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(forty_two_getter, 0), 0); crate::object::descriptor_state::set_accessor_descriptor( proto as usize, "irc_closure".to_string(), diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index 0cfc095299..4dbc57f55e 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -354,17 +354,36 @@ fn build_iterator_prototypes() { } install_symbol_iterator(shared); - let array_proto = build_family_proto(array_iterator_next_thunk, "Array Iterator", shared); - let map_proto = build_family_proto(map_iterator_next_thunk, "Map Iterator", shared); - let set_proto = build_family_proto(set_iterator_next_thunk, "Set Iterator", shared); - let string_proto = build_family_proto(string_iterator_next_thunk, "String Iterator", shared); + let array_proto = build_family_proto( + crate::fn_info!(array_iterator_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + "Array Iterator", + shared, + ); + let map_proto = build_family_proto( + crate::fn_info!(map_iterator_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + "Map Iterator", + shared, + ); + let set_proto = build_family_proto( + crate::fn_info!(set_iterator_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + "Set Iterator", + shared, + ); + let string_proto = build_family_proto( + crate::fn_info!(string_iterator_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + "String Iterator", + shared, + ); let regexp_string_proto = build_family_proto( - regexp_string_iterator_next_thunk, + crate::fn_info!(regexp_string_iterator_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), "RegExp String Iterator", shared, ); - let iterator_helper_proto = - build_family_proto(iterator_helper_next_thunk, "Iterator Helper", shared); + let iterator_helper_proto = build_family_proto( + crate::fn_info!(iterator_helper_next_thunk, 1; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + "Iterator Helper", + shared, + ); ITERATOR_PROTOTYPE_PTR.store(shared as i64, Ordering::Release); ARRAY_ITERATOR_PROTOTYPE_PTR.store(array_proto as i64, Ordering::Release); @@ -378,12 +397,13 @@ fn build_iterator_prototypes() { /// Install `[Symbol.iterator]` on the shared parent as a real method whose /// `name`/`length` own props match the spec (`"[Symbol.iterator]"`, length 0). fn install_symbol_iterator(shared: *mut ObjectHeader) { - let func_ptr = iterator_proto_symbol_iterator_thunk as *const u8; - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(iterator_proto_symbol_iterator_thunk, 1; with_declared(0)), + 0, + ); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); super::native_module::set_bound_native_closure_name(closure, "[Symbol.iterator]"); super::native_module::set_builtin_closure_length(closure as usize, 0); set_builtin_property_attrs( @@ -417,7 +437,7 @@ fn install_symbol_iterator(shared: *mut ObjectHeader) { /// Allocate one family prototype with an own `next` method (spec descriptor), /// a `[Symbol.toStringTag]`, and `[[Prototype]] === shared %IteratorPrototype%`. fn build_family_proto( - next_thunk: crate::closure::body_call::js_body_fn_ty!(a), + next_info: *const crate::closure::JsFunctionInfo, tag: &str, shared: *mut ObjectHeader, ) -> *mut ObjectHeader { @@ -429,7 +449,7 @@ fn build_family_proto( // configurable:true }` and the closure's `name`/`length` as // `{ writable:false, enumerable:false, configurable:true }` — exactly the // spec descriptor shape test262 verifies. `.length` 0 (next takes no args). - install_proto_method(proto, "next", next_thunk as *const u8, 0); + install_proto_method(proto, "next", next_info, 0); set_to_string_tag(proto, tag); chain_to(proto, shared); proto @@ -808,8 +828,10 @@ mod override_probe_allocation_tests { "premise: the prototype carries an own `next` closure" ); - let patched = crate::closure::js_closure_alloc(patched_next_thunk as *const u8, 0); - crate::closure::js_register_closure_arity(patched_next_thunk as *const u8, 0); + let patched = crate::closure::js_closure_alloc( + crate::fn_info!(patched_next_thunk, 0; with_declared(0)), + 0, + ); let patched_h = scope.root_nanbox_f64(js_nanbox_pointer(patched as i64)); set_proto_next(patched_h.get_nanbox_f64()); assert!( @@ -851,8 +873,10 @@ mod override_probe_allocation_tests { "premise: unpatched prototype passes the proof" ); - let getter = crate::closure::js_closure_alloc(accessor_getter_thunk as *const u8, 0); - crate::closure::js_register_closure_arity(accessor_getter_thunk as *const u8, 0); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(accessor_getter_thunk, 0; with_declared(0)), + 0, + ); let getter_h = scope.root_nanbox_f64(js_nanbox_pointer(getter as i64)); let key = scope.root_string_ptr(crate::string::js_string_from_bytes(b"next".as_ptr(), 4)); diff --git a/crates/perry-runtime/src/object/literal_constructor.rs b/crates/perry-runtime/src/object/literal_constructor.rs index 3af5cffeb5..ddfca92fea 100644 --- a/crates/perry-runtime/src/object/literal_constructor.rs +++ b/crates/perry-runtime/src/object/literal_constructor.rs @@ -71,7 +71,7 @@ mod tests { ); let scope = crate::gc::RuntimeHandleScope::new(); let receiver = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); - let setter = crate::closure::js_closure_alloc(collecting_setter as *const u8, 0); + let setter = crate::closure::js_closure_alloc(crate::fn_info!(collecting_setter, 1), 0); let descriptor = crate::object::js_object_alloc(0, 0); let set_key = crate::js_string_from_bytes(b"set".as_ptr(), 3); crate::object::js_object_set_field_by_name( diff --git a/crates/perry-runtime/src/object/method_site.rs b/crates/perry-runtime/src/object/method_site.rs index 1625b0e997..148102db40 100644 --- a/crates/perry-runtime/src/object/method_site.rs +++ b/crates/perry-runtime/src/object/method_site.rs @@ -6,12 +6,12 @@ //! then, with no runtime call: //! //! * **own entry** — loads the receiver's inline slot [`MethodEntry::slot`], -//! proves the value is a closure whose code pointer equals -//! [`MethodEntry::func`], and calls that code pointer directly with the +//! proves the value is a closure whose body info equals +//! [`MethodEntry::info`], and calls [`MethodEntry::code`] directly with the //! receiver as `this`; //! * **inherited entry** ([`METHOD_SITE_INHERITED`] in `slot`) — compares //! [`MethodEntry::gen`] against `PERRY_PROTO_VALIDITY` and calls -//! [`MethodEntry::func`] on the memoized closure [`MethodEntry::closure`]. +//! [`MethodEntry::code`] on the memoized closure [`MethodEntry::closure`]. //! //! Everything else calls [`js_method_site_miss`], which primes the entry when //! the facts below hold and then performs the ordinary dispatch. @@ -27,7 +27,7 @@ //! re-stamps the receiver and the word stops matching. //! * An own entry re-loads the slot on every call and compares the value's //! code pointer, so a reassigned method (`o.m = other`, no shape change) is -//! seen at once. The code pointer, not the closure, is compared: a factory +//! seen at once. The body info, not the closure, is compared: a factory //! that returns fresh closures per object shares one body, and the call //! passes the LOADED closure, so each object's captures are its own. //! * An inherited entry holds the method closure itself. The chain it was @@ -112,20 +112,24 @@ pub struct MethodEntry { pub word: u64, /// Own entry: the inline slot. Inherited entry: [`METHOD_SITE_INHERITED`]. pub slot: u64, - /// The method body's code pointer. - pub func: u64, + /// The method body's `JsFunctionInfo` (the identity an own hit compares + /// the slot closure's info word with). + pub info: u64, /// Inherited entry: the method closure's address (a STRONG GC root). pub closure: usize, /// Inherited entry: `PERRY_PROTO_VALIDITY` when the entry was primed. pub gen: u64, + /// The method body's code address, the hit's call target. + pub code: u64, } const EMPTY_ENTRY: MethodEntry = MethodEntry { word: METHOD_SITE_EMPTY, slot: 0, - func: 0, + info: 0, closure: 0, gen: 0, + code: 0, }; /// Entries per site, all compared by the emitted code (the census: 97.5% of @@ -147,8 +151,8 @@ pub struct MethodSite { #[cfg(target_pointer_width = "64")] const _: () = { assert!( - std::mem::offset_of!(crate::closure::ClosureHeader, func_ptr) - == crate::codegen_abi::CLOSURE_FUNC_PTR_OFFSET + std::mem::offset_of!(crate::closure::ClosureHeader, info) + == crate::codegen_abi::CLOSURE_INFO_OFFSET ); assert!( std::mem::offset_of!(crate::closure::ClosureHeader, props) @@ -156,7 +160,8 @@ const _: () = { ); assert!(std::mem::offset_of!(MethodEntry, word) == crate::codegen_abi::METHOD_SITE_WORD_OFFSET); assert!(std::mem::offset_of!(MethodEntry, slot) == crate::codegen_abi::METHOD_SITE_SLOT_OFFSET); - assert!(std::mem::offset_of!(MethodEntry, func) == crate::codegen_abi::METHOD_SITE_FUNC_OFFSET); + assert!(std::mem::offset_of!(MethodEntry, info) == crate::codegen_abi::METHOD_SITE_INFO_OFFSET); + assert!(std::mem::offset_of!(MethodEntry, code) == crate::codegen_abi::METHOD_SITE_CODE_OFFSET); assert!( std::mem::offset_of!(MethodEntry, closure) == crate::codegen_abi::METHOD_SITE_CLOSURE_OFFSET @@ -449,7 +454,7 @@ unsafe fn publish(slot: *mut MethodSiteSlot, entry: MethodEntry) -> bool { && if inherited { e.slot == METHOD_SITE_INHERITED } else { - e.slot == entry.slot && e.func == entry.func + e.slot == entry.slot && e.info == entry.info } }) .or_else(|| { @@ -470,9 +475,10 @@ unsafe fn publish(slot: *mut MethodSiteSlot, entry: MethodEntry) -> bool { let e = &mut site.entries[idx]; e.word = METHOD_SITE_EMPTY; e.slot = entry.slot; - e.func = entry.func; + e.info = entry.info; e.closure = entry.closure; e.gen = entry.gen; + e.code = entry.code; e.word = entry.word; true } @@ -554,7 +560,7 @@ unsafe fn prime(slot: *mut MethodSiteSlot, recv: f64, name: &[u8], argc: usize) } } }; - let Some(func) = direct_callable(value, argc) else { + let Some(info) = direct_callable(value, argc) else { refuse(5); return; }; @@ -565,7 +571,8 @@ unsafe fn prime(slot: *mut MethodSiteSlot, recv: f64, name: &[u8], argc: usize) let entry = MethodEntry { word, slot: slot_word, - func: func as u64, + info: info as *const crate::closure::JsFunctionInfo as u64, + code: info.code as u64, closure: 0, gen: 0, }; @@ -620,7 +627,7 @@ unsafe fn prime_function(slot: *mut MethodSiteSlot, addr: usize, name: &[u8], ar return; } let value = field_bits(bag as usize, s); - let Some(func) = direct_callable(value, argc) else { + let Some(info) = direct_callable(value, argc) else { refuse(5); return; }; @@ -631,7 +638,8 @@ unsafe fn prime_function(slot: *mut MethodSiteSlot, addr: usize, name: &[u8], ar let entry = MethodEntry { word: std::ptr::read(addr as *const u64), slot: s as u64 | METHOD_SITE_FUNCTION_BAG, - func: func as u64, + info: info as *const crate::closure::JsFunctionInfo as u64, + code: info.code as u64, closure: 0, gen: 0, }; @@ -744,10 +752,13 @@ fn is_user_method(value_bits: u64, name: &[u8]) -> bool { } } -/// The code pointer a site may call for `value` with `argc` arguments, when -/// the call `js_native_call_value(value, args)` would reach -/// `func(closure, args...)` with nothing in between. -unsafe fn direct_callable(value_bits: u64, argc: usize) -> Option<*const u8> { +/// The body info whose code a site may call for `value` with `argc` +/// arguments, when the call `js_native_call_value(value, args)` would reach +/// `code(closure, this, args...)` with nothing in between. +unsafe fn direct_callable( + value_bits: u64, + argc: usize, +) -> Option<&'static crate::closure::JsFunctionInfo> { if value_bits & !crate::value::POINTER_MASK != crate::value::POINTER_TAG { refuse(12); return None; @@ -759,7 +770,13 @@ unsafe fn direct_callable(value_bits: u64, argc: usize) -> Option<*const u8> { } let value = f64::from_bits(value_bits); let header = addr as *const crate::closure::ClosureHeader; - let func = (*header).func_ptr; + // The cell is proven a live function object above; a bodiless one has a + // null info. + let Some(info) = (*header).info.as_ref() else { + refuse(17); + return None; + }; + let func = info.code; if func.is_null() || func == crate::closure::BOUND_METHOD_FUNC_PTR || func == crate::closure::BOUND_FUNCTION_FUNC_PTR @@ -776,7 +793,7 @@ unsafe fn direct_callable(value_bits: u64, argc: usize) -> Option<*const u8> { refuse(13); return None; } - if crate::closure::lookup_closure_rest_full(func).is_some() { + if crate::closure::info_rest(info).is_some() { refuse(14); return None; } @@ -790,12 +807,11 @@ unsafe fn direct_callable(value_bits: u64, argc: usize) -> Option<*const u8> { refuse(15); return None; } - match crate::closure::resolve_strategy(func).kind() { - crate::closure::DispatchKind::Direct => Some(func), + match crate::closure::resolve_strategy(info).kind() { crate::closure::DispatchKind::Arity(declared) if declared as usize <= crate::codegen_abi::method_site_padded_argc(argc) => { - Some(func) + Some(info) } crate::closure::DispatchKind::Arity(_) => { refuse(16); @@ -907,7 +923,7 @@ unsafe fn prime_inherited( return; } let value = field_bits(next_addr, s); - let Some(func) = direct_callable(value, argc) else { + let Some(info) = direct_callable(value, argc) else { refuse(10); return; }; @@ -918,7 +934,8 @@ unsafe fn prime_inherited( let entry = MethodEntry { word, slot: METHOD_SITE_INHERITED, - func: func as u64, + info: info as *const crate::closure::JsFunctionInfo as u64, + code: info.code as u64, closure: (value & crate::value::POINTER_MASK) as usize, gen, }; diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index d51fe6c8b5..9aa6857f2f 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -349,8 +349,10 @@ unsafe fn call_primitive_closure_value( // the ToObject wrapper — boxed ONCE up front so writes through `this` // land on the wrapper the body later observes. For a string receiver // that wrapper costs an own index property per UTF-16 code unit (#11509). - let func_ptr = crate::closure::get_valid_func_ptr(ptr as *const crate::closure::ClosureHeader); - let this_receiver = if crate::closure::body_receives_primitive_this(func_ptr) { + let receives_primitive = + crate::closure::closure_info(ptr as *const crate::closure::ClosureHeader) + .is_some_and(crate::closure::info_receives_primitive_this); + let this_receiver = if receives_primitive { receiver_h.get_nanbox_f64() } else { crate::object::js_object_coerce(receiver_h.get_nanbox_f64()) @@ -510,7 +512,7 @@ unsafe fn builtin_proto_user_value( if (value.bits() & crate::value::TAG_MASK) == crate::value::POINTER_TAG { let ptr = (value.bits() & crate::value::POINTER_MASK) as usize; if crate::closure::is_closure_ptr(ptr) - && (*(ptr as *const crate::closure::ClosureHeader)).func_ptr + && (*(ptr as *const crate::closure::ClosureHeader)).code() == super::global_this::global_this_builtin_noop_thunk as *const u8 { return None; diff --git a/crates/perry-runtime/src/object/native_call_method/closure_override_tests.rs b/crates/perry-runtime/src/object/native_call_method/closure_override_tests.rs index 85b26c8cc2..5edac47821 100644 --- a/crates/perry-runtime/src/object/native_call_method/closure_override_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/closure_override_tests.rs @@ -20,15 +20,14 @@ extern "C" fn own_method( fn check_own_method_dispatch(proxy: bool) { let _lock = crate::gc::global_side_table_test_lock(); let scope = RuntimeHandleScope::new(); - closure::js_register_closure_arity(original as *const u8, 1); - closure::js_register_closure_arity(own_method as *const u8, 1); + for method in ["bind", "call", "apply", "toString"] { let receiver = scope.root_nanbox_f64(value::js_nanbox_pointer(closure::js_closure_alloc( - original as *const u8, + crate::fn_info!(original, 1; with_declared(1)), 0, ) as i64)); let implementation = scope.root_nanbox_f64(value::js_nanbox_pointer( - closure::js_closure_alloc(own_method as *const u8, 0) as i64, + closure::js_closure_alloc(crate::fn_info!(own_method, 1; with_declared(1)), 0) as i64, )); if proxy { let handler = scope.root_nanbox_f64(value::js_nanbox_pointer( diff --git a/crates/perry-runtime/src/object/native_call_method/code_point_at_dispatch_tests.rs b/crates/perry-runtime/src/object/native_call_method/code_point_at_dispatch_tests.rs index e499930d35..a00f4776ff 100644 --- a/crates/perry-runtime/src/object/native_call_method/code_point_at_dispatch_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/code_point_at_dispatch_tests.rs @@ -100,7 +100,7 @@ fn builtin_callee_gets_the_primitive_receiver_and_a_user_callee_the_wrapper() { let method = crate::object::global_this::install_proto_method( proto, "codePointAt", - crate::object::string_proto_thunks::string_proto_code_point_at_thunk as *const u8, + crate::fn_info!(crate::object::string_proto_thunks::string_proto_code_point_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); let before = crate::builtins::test_boxed_primitive_payload_count(); @@ -127,7 +127,7 @@ fn builtin_callee_gets_the_primitive_receiver_and_a_user_callee_the_wrapper() { ) -> f64 { 0.0 } - let user = crate::closure::js_closure_alloc(sloppy_user_body as *const u8, 0); + let user = crate::closure::js_closure_alloc(crate::fn_info!(sloppy_user_body, 0), 0); let user_value = crate::value::js_nanbox_pointer(user as i64); let before = crate::builtins::test_boxed_primitive_payload_count(); let _ = super::call_primitive_closure_value( diff --git a/crates/perry-runtime/src/object/native_call_method/dispatch_arg_coercion_tests.rs b/crates/perry-runtime/src/object/native_call_method/dispatch_arg_coercion_tests.rs index b18754450b..710ce990dc 100644 --- a/crates/perry-runtime/src/object/native_call_method/dispatch_arg_coercion_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/dispatch_arg_coercion_tests.rs @@ -118,10 +118,11 @@ fn replace_fn_result_is_to_string_coerced() { // "gundefineduna" (test262 S15.5.4.11_A1_T5's shape; the old // `call_replace_callback` dropped every non-string result to ""). unsafe { - let func_ptr = undef_replacer as *const u8; - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(undef_replacer, 3; with_declared(3)), + 0, + ); assert!(!closure.is_null()); - crate::closure::js_register_closure_arity(func_ptr, 3); let cb = crate::value::js_nanbox_pointer(closure as i64); let r = call_string_method("gnulluna", "replace", &[short(b"null"), cb]); assert_string_result(r, "gundefineduna", "\"gnulluna\".replace(\"null\", fn)"); diff --git a/crates/perry-runtime/src/object/native_call_method/function_shape.rs b/crates/perry-runtime/src/object/native_call_method/function_shape.rs index 38c3c9186f..3bc574728c 100644 --- a/crates/perry-runtime/src/object/native_call_method/function_shape.rs +++ b/crates/perry-runtime/src/object/native_call_method/function_shape.rs @@ -105,7 +105,7 @@ unsafe fn function_shape_decline( args_ptr: *const f64, args_len: usize, ) -> Option { - if crate::closure::shape::is_class_code((*(addr as *const ClosureHeader)).func_ptr) { + if crate::closure::shape::is_class_info((*(addr as *const ClosureHeader)).info) { return super::primitive_methods::class_value_method_call(object, name, args_ptr, args_len); } None @@ -182,11 +182,14 @@ mod tests { crate::string::js_string_from_bytes(s.as_ptr(), s.len() as u32) } - /// A prototype object whose `bind` slot holds `func` (the intrinsic thunk - /// or anything else), published as this agent's Function.prototype. - unsafe fn install_proto(func: *const u8) -> *mut crate::object::ObjectHeader { + /// A prototype object whose `bind` slot holds a closure over `info` (the + /// intrinsic thunk or anything else), published as this agent's + /// Function.prototype. + unsafe fn install_proto( + info: *const crate::closure::JsFunctionInfo, + ) -> *mut crate::object::ObjectHeader { let proto = crate::object::js_object_alloc(0, 0); - let method = crate::closure::js_closure_alloc(func, 0); + let method = crate::closure::js_closure_alloc(info, 0); crate::object::js_object_set_field_by_name( proto, key("bind"), @@ -207,7 +210,7 @@ mod tests { unsafe { let saved = crate::closure::shape::FUNCTION_PROTOTYPE_PTR.load(Ordering::Acquire); install_proto(crate::object::global_this::function_prototype_bind_thunk_for_test()); - let target = crate::closure::js_closure_alloc(target_body as *const u8, 0); + let target = crate::closure::js_closure_alloc(crate::fn_info!(target_body, 0), 0); let target_v = crate::value::js_nanbox_pointer(target as i64); let this_arg = [f64::from_bits(crate::value::TAG_UNDEFINED)]; let before = hits(); @@ -215,10 +218,7 @@ mod tests { .expect("the shape path must answer bind on a base-shaped function"); assert_eq!(hits(), before + 1, "the path must FIRE, not only agree"); let bound_ptr = JSValue::from_bits(bound.to_bits()).as_pointer::(); - assert_eq!( - (*bound_ptr).func_ptr, - crate::closure::BOUND_FUNCTION_FUNC_PTR - ); + assert_eq!((*bound_ptr).code(), crate::closure::BOUND_FUNCTION_FUNC_PTR); assert_eq!( crate::closure::js_closure_call0(bound_ptr, crate::closure::plain_call_receiver()), 42.0 @@ -234,8 +234,8 @@ mod tests { unsafe { let saved = crate::closure::shape::FUNCTION_PROTOTYPE_PTR.load(Ordering::Acquire); // The slot holds something else: identity by VALUE says no. - install_proto(target_body as *const u8); - let target = crate::closure::js_closure_alloc(target_body as *const u8, 0); + install_proto(crate::fn_info!(target_body, 0)); + let target = crate::closure::js_closure_alloc(crate::fn_info!(target_body, 0), 0); let target_v = crate::value::js_nanbox_pointer(target as i64); let this_arg = [f64::from_bits(crate::value::TAG_UNDEFINED)]; let before = hits(); diff --git a/crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs b/crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs index 29da6b5d27..b4a8061177 100644 --- a/crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs +++ b/crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs @@ -27,7 +27,7 @@ pub(crate) unsafe fn try_dispatch_value_called_proto_method( if closure.is_null() { return None; } - if (*closure).func_ptr != super::global_this::global_this_builtin_noop_thunk as *const u8 { + if (*closure).code() != super::global_this::global_this_builtin_noop_thunk as *const u8 { return None; } super::native_module::builtin_closure_length(closure as usize)?; diff --git a/crates/perry-runtime/src/object/native_get/tests.rs b/crates/perry-runtime/src/object/native_get/tests.rs index 7a041ac2eb..ce92cb4f3a 100644 --- a/crates/perry-runtime/src/object/native_get/tests.rs +++ b/crates/perry-runtime/src/object/native_get/tests.rs @@ -104,7 +104,7 @@ extern "C" fn getter( } fn install_getter(object: *mut ObjectHeader, name: &str, has_getter: bool) { - let closure = crate::closure::js_closure_alloc(getter as *const u8, 0); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(getter, 0), 0); crate::object::descriptor_state::set_accessor_descriptor( object as usize, name.to_string(), @@ -159,7 +159,7 @@ fn unsupported_receivers_and_private_names_decline_and_preserve_results() { let array = crate::array::js_array_alloc(4); let array_value = crate::value::js_nanbox_pointer(array as i64); js_object_set_field_by_name(array as *mut ObjectHeader, key("value"), 59.0); - let closure = crate::closure::js_closure_alloc(getter as *const u8, 0); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(getter, 0), 0); js_object_set_field_by_name(closure as *mut ObjectHeader, key("value"), 59.0); for receiver in [ proxy, diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index 0d4a52c371..1bdd2ba35a 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -1302,7 +1302,7 @@ pub(crate) fn build_symbol_bound_method_closure( let scope = crate::gc::RuntimeHandleScope::new(); let receiver_handle = scope.root_nanbox_f64(receiver); let closure_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - crate::closure::BOUND_METHOD_FUNC_PTR, + &crate::closure::BOUND_METHOD_INFO, 5, )); if closure_handle.with_mut_ptr::(|c| c.is_null()) { @@ -1371,7 +1371,7 @@ pub(crate) fn is_static_bound_method_value(value: f64) -> bool { } let closure = raw as *const crate::closure::ClosureHeader; if !std::ptr::eq( - unsafe { (*closure).func_ptr }, + unsafe { (*closure).code() }, crate::closure::BOUND_METHOD_FUNC_PTR, ) { return false; diff --git a/crates/perry-runtime/src/object/native_module/async_hooks_exports.rs b/crates/perry-runtime/src/object/native_module/async_hooks_exports.rs index ab4d0fd093..4bab2454c0 100644 --- a/crates/perry-runtime/src/object/native_module/async_hooks_exports.rs +++ b/crates/perry-runtime/src/object/native_module/async_hooks_exports.rs @@ -124,8 +124,7 @@ fn attach_prototype(constructor_value: f64, methods: &[(&str, u32)]) -> f64 { super::super::PropertyAttrs::new(true, false, true), ); - let thunk = async_hooks_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_rest(thunk, 0); + let thunk = crate::fn_info!(async_hooks_prototype_method_thunk, 1; with_rest(0)); for &(name, length) in methods { let method = crate::closure::js_closure_alloc(thunk, 2); if method.is_null() { @@ -193,8 +192,7 @@ fn attach_prototype(constructor_value: f64, methods: &[(&str, u32)]) -> f64 { /// property reads whose static type was erased. Invocation observes the /// call-site receiver as its `this` argument, just like the real prototype. pub(crate) fn async_resource_prototype_method_value(name: &'static str, length: u32) -> f64 { - let thunk = async_hooks_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_rest(thunk, 0); + let thunk = crate::fn_info!(async_hooks_prototype_method_thunk, 1; with_rest(0)); let closure = crate::closure::js_closure_alloc(thunk, 2); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); diff --git a/crates/perry-runtime/src/object/native_module/callable_exports.rs b/crates/perry-runtime/src/object/native_module/callable_exports.rs index 854f263314..225d28cf18 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports.rs @@ -68,7 +68,7 @@ pub fn bound_native_callable_export_value(module_name: &str, property_name: &str callable_module_name.as_ptr(), callable_module_name.len(), )); - let closure = crate::closure::js_closure_alloc(crate::closure::BOUND_METHOD_FUNC_PTR, 3); + let closure = crate::closure::js_closure_alloc(&crate::closure::BOUND_METHOD_INFO, 3); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr(|c: *mut crate::closure::ClosureHeader| { crate::closure::js_closure_set_capture_f64(c, 0, ns.get_nanbox_f64()); @@ -192,15 +192,14 @@ fn attach_stream_promisify_custom(pipeline_value: f64, property_name: &str) -> f target.get_nanbox_f64() } +/// `info` is the rest body's (`with_rest(fixed)`); `.length` is `length`. fn async_hooks_static_method_value( - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, name: &str, - fixed_arity: u32, length: u32, ) -> f64 { - crate::closure::js_register_closure_rest(func_ptr, fixed_arity); let scope = crate::gc::RuntimeHandleScope::new(); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -244,8 +243,7 @@ pub(crate) fn fs_namespace_descriptor_getter_value(property_name: &str) -> f64 { } let property_bytes: &'static [u8] = property_name.as_bytes().to_vec().leak(); - let func_ptr = fs_namespace_descriptor_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); + let func_ptr = crate::fn_info!(fs_namespace_descriptor_getter_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(func_ptr, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, property_bytes.as_ptr() as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, property_bytes.len() as i64); @@ -270,8 +268,7 @@ pub(crate) fn fs_namespace_descriptor_setter_value(property_name: &str) -> f64 { return f64::from_bits(bits); } - let func_ptr = fs_namespace_descriptor_setter_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); + let func_ptr = crate::fn_info!(fs_namespace_descriptor_setter_thunk, 1; with_declared(1)); let closure = crate::closure::js_closure_alloc(func_ptr, 0); let name = format!("set {property_name}"); set_bound_native_closure_name(closure, &name); @@ -327,8 +324,8 @@ pub(crate) fn sqlite_statement_sync_constructor_value() -> f64 { return f64::from_bits(cached); } - let func_ptr = sqlite_statement_sync_constructor_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); + let func_ptr = + crate::fn_info!(sqlite_statement_sync_constructor_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc_singleton(func_ptr); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -347,8 +344,7 @@ pub(crate) fn sqlite_session_constructor_value() -> f64 { return f64::from_bits(cached); } - let func_ptr = sqlite_session_constructor_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); + let func_ptr = crate::fn_info!(sqlite_session_constructor_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc_singleton(func_ptr); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -482,12 +478,15 @@ extern "C" fn buffer_prototype_offset_getter_thunk( /// `ObjectDefineProperty(Buffer.prototype, name, { enumerable: true, get() /// {…} })` (no `configurable: true`, so it defaults false). Mirrors /// `install_webcrypto_proto_getter`'s shape for a `*mut ObjectHeader` proto. -fn install_buffer_prototype_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) { +fn install_buffer_prototype_getter( + proto_obj: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { if proto_obj.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); let value = if closure.is_null() { f64::from_bits(crate::value::TAG_UNDEFINED) } else { @@ -813,8 +812,8 @@ fn attach_sqlite_database_sync_prototype(constructor_value: f64) { super::PropertyAttrs::new(true, false, true), ); - let func_ptr = sqlite_database_sync_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 3); + let func_ptr = + crate::fn_info!(sqlite_database_sync_prototype_method_thunk, 3; with_declared(3)); for method in SQLITE_DATABASE_SYNC_PROTOTYPE_METHODS { let leaked: &'static [u8] = method.as_bytes().to_vec().leak(); let method_closure = crate::closure::js_closure_alloc(func_ptr, 2); @@ -860,8 +859,8 @@ fn attach_sqlite_session_prototype(constructor_value: f64) { return; } - let func_ptr = sqlite_database_sync_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 3); + let func_ptr = + crate::fn_info!(sqlite_database_sync_prototype_method_thunk, 3; with_declared(3)); for method in SQLITE_SESSION_PROTOTYPE_METHODS { let leaked: &'static [u8] = method.as_bytes().to_vec().leak(); let method_closure = crate::closure::js_closure_alloc(func_ptr, 2); @@ -948,14 +947,13 @@ pub(crate) fn buffer_constructor_value() -> f64 { // mint, mirroring `install_native_module_vtable()` above. super::super::native_module_registry::js_nm_install_buffer(); - let func_ptr = buffer_constructor_thunk as *const u8; + let func_ptr = crate::fn_info!(buffer_constructor_thunk, 3; with_declared(3)); let closure = crate::closure::js_closure_alloc(func_ptr, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } let scope = crate::gc::RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(closure); - crate::closure::js_register_closure_arity(func_ptr, 3); closure.with_mut_ptr::(|ptr| { set_bound_native_closure_name(ptr, "Buffer") }); @@ -1008,7 +1006,7 @@ pub(crate) fn buffer_constructor_value() -> f64 { }); for method in BUFFER_PROTOTYPE_METHODS { - let method_ptr = buffer_prototype_method_thunk as *const u8; + let method_ptr = crate::fn_info!(buffer_prototype_method_thunk, 0); let method_closure = crate::closure::js_closure_alloc(method_ptr, 0); if method_closure.is_null() { continue; @@ -1039,12 +1037,12 @@ pub(crate) fn buffer_constructor_value() -> f64 { install_buffer_prototype_getter( proto, "parent", - buffer_prototype_parent_getter_thunk as *const u8, + crate::fn_info!(buffer_prototype_parent_getter_thunk, 0; with_declared(0)), ); install_buffer_prototype_getter( proto, "offset", - buffer_prototype_offset_getter_thunk as *const u8, + crate::fn_info!(buffer_prototype_offset_getter_thunk, 0; with_declared(0)), ); }); let proto_value = proto.with_mut_ptr(|proto: *mut ObjectHeader| { @@ -1137,12 +1135,10 @@ fn attach_crypto_x509_certificate_shape(closure_addr: usize, constructor_value: ); unsafe { - crate::closure::js_register_closure_arity( - x509_issuer_certificate_getter_thunk as *const u8, + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(x509_issuer_certificate_getter_thunk, 0; with_declared(0)), 0, ); - let getter = - crate::closure::js_closure_alloc(x509_issuer_certificate_getter_thunk as *const u8, 0); if !getter.is_null() { let getter_bits = crate::value::js_nanbox_pointer(getter as i64).to_bits(); super::object_ops::install_builtin_getter(proto, "issuerCertificate", getter_bits); @@ -1409,8 +1405,7 @@ fn attach_tls_constructor_prototype(constructor_value: f64, constructor_name: &s super::super::PropertyAttrs::new(true, false, true), ); - let thunk = tls_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_rest(thunk, 0); + let thunk = crate::fn_info!(tls_prototype_method_thunk, 1; with_rest(0)); for &(name, length) in methods { let method = crate::closure::js_closure_alloc(thunk, 2); if method.is_null() { @@ -1522,7 +1517,7 @@ pub(crate) unsafe fn bound_native_callable_module_and_method( // host) before probing `CLOSURE_MAGIC`. Refs test262 // class/subclass/superclass-{arrow,async,generator,async-generator}-function. if !crate::closure::is_closure_ptr(closure as usize) - || (*closure).func_ptr != crate::closure::BOUND_METHOD_FUNC_PTR + || (*closure).code() != crate::closure::BOUND_METHOD_FUNC_PTR { return None; } @@ -1841,10 +1836,9 @@ pub(crate) unsafe fn nm_attach_async_hooks( ), ); let bind = scope.root_nanbox_f64(async_hooks_static_method_value( - crate::async_hooks::js_async_local_storage_static_bind_method as *const u8, + crate::fn_info!(crate::async_hooks::js_async_local_storage_static_bind_method, 2; with_rest(1)), "bind", 1, - 1, )); crate::closure::closure_set_dynamic_prop( crate::value::js_nanbox_get_pointer(constructor_handle.get_nanbox_f64()) as usize, @@ -1852,10 +1846,9 @@ pub(crate) unsafe fn nm_attach_async_hooks( bind.get_nanbox_f64(), ); let snapshot = scope.root_nanbox_f64(async_hooks_static_method_value( - crate::async_hooks::js_async_local_storage_static_snapshot_method as *const u8, + crate::fn_info!(crate::async_hooks::js_async_local_storage_static_snapshot_method, 1; with_rest(0)), "snapshot", 0, - 0, )); crate::closure::closure_set_dynamic_prop( crate::value::js_nanbox_get_pointer(constructor_handle.get_nanbox_f64()) as usize, @@ -1871,10 +1864,9 @@ pub(crate) unsafe fn nm_attach_async_hooks( ), ); let bind = scope.root_nanbox_f64(async_hooks_static_method_value( - crate::async_hooks::js_async_resource_static_bind_method as *const u8, + crate::fn_info!(crate::async_hooks::js_async_resource_static_bind_method, 4; with_rest(3)), "bind", 3, - 3, )); crate::closure::closure_set_dynamic_prop( crate::value::js_nanbox_get_pointer(constructor_handle.get_nanbox_f64()) as usize, diff --git a/crates/perry-runtime/src/object/native_module/callable_exports/buffer_species.rs b/crates/perry-runtime/src/object/native_module/callable_exports/buffer_species.rs index 2a6cdd1eff..47a8c0f632 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports/buffer_species.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports/buffer_species.rs @@ -73,9 +73,10 @@ pub(super) fn install_buffer_species(buffer_ctor: f64) { // FastBuffer itself: named, sharing Buffer.prototype. Node reports // `FastBuffer.length === 0`; the thunk still receives three arguments. - let fast_ptr = fast_buffer_constructor_thunk as *const u8; - crate::closure::js_register_closure_arity(fast_ptr, 3); - let fast = crate::closure::js_closure_alloc(fast_ptr, 0); + let fast = crate::closure::js_closure_alloc( + crate::fn_info!(fast_buffer_constructor_thunk, 3; with_declared(3)), + 0, + ); if fast.is_null() { return; } @@ -100,9 +101,10 @@ pub(super) fn install_buffer_species(buffer_ctor: f64) { } // The getter, holding FastBuffer in capture slot 0. - let getter_ptr = buffer_species_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(getter_ptr, 0); - let getter = crate::closure::js_closure_alloc(getter_ptr, 1); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(buffer_species_getter_thunk, 0; with_declared(0)), + 1, + ); if getter.is_null() { return; } diff --git a/crates/perry-runtime/src/object/native_module/callable_exports/module_cjs.rs b/crates/perry-runtime/src/object/native_module/callable_exports/module_cjs.rs index f37b0aa091..49228484d0 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports/module_cjs.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports/module_cjs.rs @@ -11,10 +11,10 @@ extern "C" fn module_cjs_extension_noop_thunk( } fn module_cjs_extension_function(name: &str) -> f64 { - let func_ptr = module_cjs_extension_noop_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - crate::closure::js_register_closure_length(func_ptr, 2); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(module_cjs_extension_noop_thunk, 2; with_declared(2), with_length(2)), + 0, + ); let scope = crate::gc::RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { @@ -202,10 +202,10 @@ extern "C" fn module_wrap_thunk( } fn module_wrap_value() -> f64 { - let func = module_wrap_thunk as *const u8; - crate::closure::js_register_closure_arity(func, 1); - crate::closure::js_register_closure_length(func, 1); - let closure = crate::closure::js_closure_alloc(func, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(module_wrap_thunk, 1; with_declared(1), with_length(1)), + 0, + ); let scope = crate::gc::RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { @@ -274,13 +274,12 @@ extern "C" fn module_prototype_require_thunk( } fn module_prototype_method(name: &str, length: u32) -> f64 { - let func = match name { - "load" => module_prototype_load_thunk as *const u8, - "require" => module_prototype_require_thunk as *const u8, - _ => module_prototype_method_thunk as *const u8, + let info = match name { + "load" => crate::fn_info!(module_prototype_load_thunk, 3; with_declared(3)), + "require" => crate::fn_info!(module_prototype_require_thunk, 3; with_declared(3)), + _ => crate::fn_info!(module_prototype_method_thunk, 3; with_declared(3)), }; - crate::closure::js_register_closure_arity(func, 3); - let closure = crate::closure::js_closure_alloc(func, 0); + let closure = crate::closure::js_closure_alloc(info, 0); let scope = crate::gc::RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { @@ -330,24 +329,21 @@ extern "C" fn module_prototype_parent_setter( } fn module_accessor( - get_func: *const u8, - set_func: Option<*const u8>, + get_info: *const crate::closure::JsFunctionInfo, + set_info: Option<*const crate::closure::JsFunctionInfo>, capture: Option, ) -> crate::object::AccessorDescriptor { let scope = crate::gc::RuntimeHandleScope::new(); let capture = capture.map(|value| scope.root_nanbox_f64(value)); - crate::closure::js_register_closure_arity(get_func, 0); - let getter = crate::closure::js_closure_alloc(get_func, capture.is_some() as u32); + let getter = crate::closure::js_closure_alloc(get_info, capture.is_some() as u32); let getter = scope.root_raw_mut_ptr(getter); if let Some(value) = capture.as_ref() { getter.with_mut_ptr(|getter: *mut crate::closure::ClosureHeader| { crate::closure::js_closure_set_capture_f64(getter, 0, value.get_nanbox_f64()); }); } - let setter = set_func.map(|func| { - crate::closure::js_register_closure_arity(func, 1); - scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(func, 0)) - }); + let setter = + set_info.map(|info| scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(info, 0))); crate::object::AccessorDescriptor { get: getter .with_mut_ptr(|getter: *mut crate::closure::ClosureHeader| { @@ -419,17 +415,25 @@ fn module_cjs_prototype_value(_module_value: f64) -> f64 { }; install_accessor( "constructor", - module_accessor(module_prototype_constructor_getter as *const u8, None, None), + module_accessor( + crate::fn_info!(module_prototype_constructor_getter, 0; with_declared(0)), + None, + None, + ), ); install_accessor( "isPreloading", - module_accessor(module_prototype_false_getter as *const u8, None, None), + module_accessor( + crate::fn_info!(module_prototype_false_getter, 0; with_declared(0)), + None, + None, + ), ); install_accessor( "parent", module_accessor( - module_prototype_parent_getter as *const u8, - Some(module_prototype_parent_setter as *const u8), + crate::fn_info!(module_prototype_parent_getter, 0; with_declared(0)), + Some(crate::fn_info!(module_prototype_parent_setter, 1; with_declared(1))), None, ), ); diff --git a/crates/perry-runtime/src/object/native_module/class_method_bind.rs b/crates/perry-runtime/src/object/native_module/class_method_bind.rs index a44e322f0b..8905a1cf2c 100644 --- a/crates/perry-runtime/src/object/native_module/class_method_bind.rs +++ b/crates/perry-runtime/src/object/native_module/class_method_bind.rs @@ -266,7 +266,7 @@ pub(super) fn build_bound_method_closure_with_private_brand( let instance_handle = scope.root_nanbox_f64(instance); let private_brand_handle = private_brand.map(|brand| scope.root_nanbox_f64(brand)); let closure_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - crate::closure::BOUND_METHOD_FUNC_PTR, + &crate::closure::BOUND_METHOD_INFO, if private_brand_handle.is_some() { 4 } else { 3 }, )); // Capture-slot writes are scoped arguments to non-allocating stores, so diff --git a/crates/perry-runtime/src/object/native_module/namespace_builders.rs b/crates/perry-runtime/src/object/native_module/namespace_builders.rs index 3746f55e92..b734176e95 100644 --- a/crates/perry-runtime/src/object/native_module/namespace_builders.rs +++ b/crates/perry-runtime/src/object/native_module/namespace_builders.rs @@ -499,14 +499,12 @@ extern "C" fn global_agent_add_request_thunk( fn global_agent_method_value( name: &str, - func_ptr: *const u8, - call_arity: u32, + info: *const crate::closure::JsFunctionInfo, exposed_length: u32, capture: Option, ) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, call_arity); let captures = if capture.is_some() { 1 } else { 0 }; - let closure = crate::closure::js_closure_alloc(func_ptr, captures); + let closure = crate::closure::js_closure_alloc(info, captures); if let Some(capture) = capture { crate::closure::js_closure_set_capture_ptr(closure, 0, capture); } @@ -517,8 +515,10 @@ fn global_agent_method_value( } fn global_agent_listener_value(is_https: bool, once: bool) -> f64 { - crate::closure::js_register_closure_arity(global_agent_listener_thunk as *const u8, 3); - let closure = crate::closure::js_closure_alloc(global_agent_listener_thunk as *const u8, 2); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(global_agent_listener_thunk, 3; with_declared(3)), + 2, + ); crate::closure::js_closure_set_capture_ptr(closure, 0, i64::from(is_https)); crate::closure::js_closure_set_capture_ptr(closure, 1, i64::from(once)); let name = if once { "once" } else { "on" }; @@ -537,8 +537,7 @@ unsafe fn global_agent_prototype(is_https: bool, agent: *mut ObjectHeader) -> f6 "keepSocketAlive", global_agent_method_value( "keepSocketAlive", - global_agent_keep_socket_alive_thunk as *const u8, - 1, + crate::fn_info!(global_agent_keep_socket_alive_thunk, 1; with_declared(1)), 1, None, ), @@ -547,8 +546,7 @@ unsafe fn global_agent_prototype(is_https: bool, agent: *mut ObjectHeader) -> f6 "reuseSocket", global_agent_method_value( "reuseSocket", - global_agent_reuse_socket_thunk as *const u8, - 2, + crate::fn_info!(global_agent_reuse_socket_thunk, 2; with_declared(2)), 2, None, ), @@ -557,8 +555,7 @@ unsafe fn global_agent_prototype(is_https: bool, agent: *mut ObjectHeader) -> f6 "getName", global_agent_method_value( "getName", - global_agent_get_name_thunk as *const u8, - 2, + crate::fn_info!(global_agent_get_name_thunk, 2; with_declared(2)), 0, Some(i64::from(is_https)), ), @@ -567,8 +564,7 @@ unsafe fn global_agent_prototype(is_https: bool, agent: *mut ObjectHeader) -> f6 "addRequest", global_agent_method_value( "addRequest", - global_agent_add_request_thunk as *const u8, - 5, + crate::fn_info!(global_agent_add_request_thunk, 5; with_declared(5)), 4, Some(agent as i64), ), @@ -577,8 +573,7 @@ unsafe fn global_agent_prototype(is_https: bool, agent: *mut ObjectHeader) -> f6 "destroy", global_agent_method_value( "destroy", - global_agent_destroy_thunk as *const u8, - 0, + crate::fn_info!(global_agent_destroy_thunk, 0; with_declared(0)), 0, None, ), diff --git a/crates/perry-runtime/src/object/native_module/web_locks.rs b/crates/perry-runtime/src/object/native_module/web_locks.rs index 94dd89a28c..c505da2a19 100644 --- a/crates/perry-runtime/src/object/native_module/web_locks.rs +++ b/crates/perry-runtime/src/object/native_module/web_locks.rs @@ -213,14 +213,14 @@ fn web_locks_signal_rejection(options: f64) -> Result, f64> { } } +/// `info` records the body's declared (call) arity; `.length` is +/// `exposed_length`. fn web_locks_make_function( name: &str, - func_ptr: *const u8, - call_arity: u32, + info: *const crate::closure::JsFunctionInfo, exposed_length: u32, ) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, call_arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); set_bound_native_closure_name(closure, name); set_builtin_closure_length(closure as usize, exposed_length); crate::value::js_nanbox_pointer(closure as i64) @@ -236,15 +236,22 @@ extern "C" fn worker_threads_lock_to_string_tag(_this: f64) -> f64 { fn worker_threads_locks_proto_value() -> f64 { let proto = crate::object::js_object_alloc(0, 0); - let request = - web_locks_make_function("request", worker_threads_locks_request as *const u8, 3, 2); + let request = web_locks_make_function( + "request", + crate::fn_info!(worker_threads_locks_request, 3; with_declared(3)), + 2, + ); crate::object::class_prototype_method_root_store( WORKER_THREADS_LOCK_MANAGER_CLASS_ID, "request".to_string(), request.to_bits(), ); web_locks_set_field(proto, "request", request); - let query = web_locks_make_function("query", worker_threads_locks_query as *const u8, 0, 0); + let query = web_locks_make_function( + "query", + crate::fn_info!(worker_threads_locks_query, 0; with_declared(0)), + 0, + ); crate::object::class_prototype_method_root_store( WORKER_THREADS_LOCK_MANAGER_CLASS_ID, "query".to_string(), @@ -391,11 +398,10 @@ fn web_locks_release_callback_value( reject: bool, ) -> *const crate::closure::ClosureHeader { let func_ptr = if reject { - worker_threads_locks_release_reject as *const u8 + crate::fn_info!(worker_threads_locks_release_reject, 1; with_declared(1)) } else { - worker_threads_locks_release_fulfill as *const u8 + crate::fn_info!(worker_threads_locks_release_fulfill, 1; with_declared(1)) }; - crate::closure::js_register_closure_arity(func_ptr, 1); let closure = crate::closure::js_closure_alloc(func_ptr, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, id as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, output_promise as i64); diff --git a/crates/perry-runtime/src/object/native_module_stream.rs b/crates/perry-runtime/src/object/native_module_stream.rs index 3c55cb71c0..80ac375353 100644 --- a/crates/perry-runtime/src/object/native_module_stream.rs +++ b/crates/perry-runtime/src/object/native_module_stream.rs @@ -246,9 +246,10 @@ extern "C" fn stream_static_method_thunk( } fn stream_static_method_value(method: &str, kind: f64, exposed_length: u32) -> f64 { - let func_ptr = stream_static_method_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - let closure = crate::closure::js_closure_alloc(func_ptr, 1); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(stream_static_method_thunk, 2; with_declared(2)), + 1, + ); crate::closure::js_closure_set_capture_f64(closure, 0, kind); set_bound_native_closure_name(closure, method); set_builtin_closure_length(closure as usize, exposed_length); diff --git a/crates/perry-runtime/src/object/native_this_alias.rs b/crates/perry-runtime/src/object/native_this_alias.rs index 6daee7f016..361fc3cf21 100644 --- a/crates/perry-runtime/src/object/native_this_alias.rs +++ b/crates/perry-runtime/src/object/native_this_alias.rs @@ -589,10 +589,9 @@ pub(crate) fn attach_http_server_response_prototype(constructor_value: f64) -> f PropertyAttrs::new(true, false, true), ); }); - let func_ptr = server_response_prototype_method_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 3); + let info = crate::fn_info!(server_response_prototype_method_thunk, 3; with_declared(3)); for method in SERVER_RESPONSE_PROTOTYPE_METHODS { - let method_closure = crate::closure::js_closure_alloc(func_ptr, 2); + let method_closure = crate::closure::js_closure_alloc(info, 2); if method_closure.is_null() { continue; } diff --git a/crates/perry-runtime/src/object/object_ops/define_property.rs b/crates/perry-runtime/src/object/object_ops/define_property.rs index 2bc2a3ef52..1ddbfaae08 100644 --- a/crates/perry-runtime/src/object/object_ops/define_property.rs +++ b/crates/perry-runtime/src/object/object_ops/define_property.rs @@ -45,7 +45,7 @@ unsafe fn define_class_prototype_method(target_cid: u32, name: &str, value_bits: // chain) onto `target_cid`. if crate::closure::is_closure_ptr(ptr) { let closure = ptr as *const ClosureHeader; - if (*closure).func_ptr == BOUND_METHOD_FUNC_PTR { + if (*closure).code() == BOUND_METHOD_FUNC_PTR { let recv = crate::closure::js_closure_get_capture_f64(closure, 0); let recv_value = crate::JSValue::from_bits(recv.to_bits()); let source_cid = super::super::class_ref_id(recv).or_else(|| { diff --git a/crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs b/crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs index 25ceb078cf..0d6bf80394 100644 --- a/crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs +++ b/crates/perry-runtime/src/object/object_ops/descriptor_helpers.rs @@ -703,11 +703,12 @@ pub(crate) unsafe fn validate_nonconfigurable_redefine( if let Some(acc) = cur_accessor { // Both accessor: `get`/`set` may not change. The stored closures are // clones rebound to the receiver (`clone_closure_rebind_this`) but keep - // the original `func_ptr`, so compare by underlying function pointer. + // the original body, so compare by underlying code address. let closure_func_ptr = |bits: u64| -> usize { let p = (bits & crate::value::POINTER_MASK) as usize; if p >= 0x1000 && crate::closure::is_closure_ptr(p) { - (*(p as *const crate::closure::ClosureHeader)).func_ptr as usize + crate::closure::get_valid_func_ptr(p as *const crate::closure::ClosureHeader) + as usize } else { 0 } diff --git a/crates/perry-runtime/src/object/object_ops/keys_array.rs b/crates/perry-runtime/src/object/object_ops/keys_array.rs index c56955be4f..ecdf83ae09 100644 --- a/crates/perry-runtime/src/object/object_ops/keys_array.rs +++ b/crates/perry-runtime/src/object/object_ops/keys_array.rs @@ -697,7 +697,7 @@ pub(crate) unsafe fn install_builtin_getter(proto: *mut ObjectHeader, key: &str, // without this the `.name` read returned `""`. let getter_ptr = (getter_bits & 0x0000_FFFF_FFFF_FFFF) as usize; if getter_ptr >= 0x1000 && crate::closure::is_closure_ptr(getter_ptr) { - let func_ptr = (*(getter_ptr as *const crate::closure::ClosureHeader)).func_ptr as usize; + let func_ptr = (*(getter_ptr as *const crate::closure::ClosureHeader)).code() as usize; crate::builtins::register_function_name_if_absent(func_ptr, &format!("get {key}")); } set_builtin_accessor_descriptor( diff --git a/crates/perry-runtime/src/object/own_override_push_tests.rs b/crates/perry-runtime/src/object/own_override_push_tests.rs index 25eb7e0fb9..fbc6ecf629 100644 --- a/crates/perry-runtime/src/object/own_override_push_tests.rs +++ b/crates/perry-runtime/src/object/own_override_push_tests.rs @@ -118,8 +118,11 @@ fn a_forwarded_alias_answers_from_the_live_head() { fn an_own_user_method_takes_the_own_exit_with_its_return_value() { let _lock = crate::gc::global_side_table_test_lock(); unsafe { - let method = crate::closure::js_closure_alloc(own_push_fixture as *const u8, 0); - crate::closure::js_register_closure_arity(own_push_fixture as *const u8, 1); + let method = crate::closure::js_closure_alloc( + crate::fn_info!(own_push_fixture, 1; with_declared(1)), + 0, + ); + let arr = set_named( pushed(&[1.0]), "push", diff --git a/crates/perry-runtime/src/object/primitive_proto_thunks.rs b/crates/perry-runtime/src/object/primitive_proto_thunks.rs index 76535331fb..ec95d2f004 100644 --- a/crates/perry-runtime/src/object/primitive_proto_thunks.rs +++ b/crates/perry-runtime/src/object/primitive_proto_thunks.rs @@ -24,13 +24,13 @@ pub(super) fn install_primitive_proto_methods( ipm( proto_obj, "toExponential", - number_proto_to_exponential_thunk as *const u8, + crate::fn_info!(number_proto_to_exponential_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); ipm( proto_obj, "toFixed", - number_proto_to_fixed_thunk as *const u8, + crate::fn_info!(number_proto_to_fixed_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); // `.length` is 0 (both params are optional), but the thunk needs @@ -40,26 +40,25 @@ pub(super) fn install_primitive_proto_methods( super::global_this::install_proto_method_rest_with_length( proto_obj, "toLocaleString", - number_proto_to_locale_string_thunk as *const u8, - 0, + crate::fn_info!(number_proto_to_locale_string_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); ipm( proto_obj, "toPrecision", - number_proto_to_precision_thunk as *const u8, + crate::fn_info!(number_proto_to_precision_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); ipm( proto_obj, "toString", - number_proto_to_string_thunk as *const u8, + crate::fn_info!(number_proto_to_string_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); ipm( proto_obj, "valueOf", - number_proto_value_of_thunk as *const u8, + crate::fn_info!(number_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -67,13 +66,13 @@ pub(super) fn install_primitive_proto_methods( ipm( proto_obj, "toString", - boolean_proto_to_string_thunk as *const u8, + crate::fn_info!(boolean_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); ipm( proto_obj, "valueOf", - boolean_proto_value_of_thunk as *const u8, + crate::fn_info!(boolean_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -81,13 +80,13 @@ pub(super) fn install_primitive_proto_methods( ipm( proto_obj, "toString", - symbol_proto_to_string_thunk as *const u8, + crate::fn_info!(symbol_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); ipm( proto_obj, "valueOf", - symbol_proto_value_of_thunk as *const u8, + crate::fn_info!(symbol_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -95,7 +94,7 @@ pub(super) fn install_primitive_proto_methods( let to_string = ipm( proto_obj, "toString", - bigint_proto_to_string_thunk as *const u8, + crate::fn_info!(bigint_proto_to_string_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); // The optional radix does not contribute to the observable @@ -108,7 +107,7 @@ pub(super) fn install_primitive_proto_methods( ipm( proto_obj, "valueOf", - bigint_proto_value_of_thunk as *const u8, + crate::fn_info!(bigint_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // `.length` is 0 despite the optional `(locales?, options?)` @@ -119,8 +118,7 @@ pub(super) fn install_primitive_proto_methods( super::global_this::install_proto_method_rest_with_length( proto_obj, "toLocaleString", - bigint_proto_to_locale_string_thunk as *const u8, - 0, + crate::fn_info!(bigint_proto_to_locale_string_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -130,30 +128,61 @@ pub(super) fn install_primitive_proto_methods( } pub(crate) fn primitive_proto_method_value(builtin_name: &str, method_name: &str) -> Option { - let (func_ptr, arity) = match (builtin_name, method_name) { - ("Number", "toExponential") => (number_proto_to_exponential_thunk as *const u8, 1), - ("Number", "toFixed") => (number_proto_to_fixed_thunk as *const u8, 1), + let (info, arity) = match (builtin_name, method_name) { + ("Number", "toExponential") => ( + crate::fn_info!(number_proto_to_exponential_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ("Number", "toFixed") => ( + crate::fn_info!(number_proto_to_fixed_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), // #9414: rest-based (see `install_primitive_proto_methods`), so the // reified `Number.prototype.toLocaleString` function value must be // registered the same way or its `(locales, options)` never arrive. - ("Number", "toLocaleString") => { - let func_ptr = number_proto_to_locale_string_thunk as *const u8; - let value = primitive_proto_method_closure_value(method_name, func_ptr, 0); - crate::closure::js_register_closure_rest(func_ptr, 0); - return Some(value); - } - ("Number", "toPrecision") => (number_proto_to_precision_thunk as *const u8, 1), - ("Number", "toString") => (number_proto_to_string_thunk as *const u8, 1), - ("Number", "valueOf") => (number_proto_value_of_thunk as *const u8, 0), - ("Boolean", "toString") => (boolean_proto_to_string_thunk as *const u8, 0), - ("Boolean", "valueOf") => (boolean_proto_value_of_thunk as *const u8, 0), - ("Symbol", "toString") => (symbol_proto_to_string_thunk as *const u8, 0), - ("Symbol", "valueOf") => (symbol_proto_value_of_thunk as *const u8, 0), - ("BigInt", "toString") => (bigint_proto_to_string_thunk as *const u8, 1), - ("BigInt", "valueOf") => (bigint_proto_value_of_thunk as *const u8, 0), + ("Number", "toLocaleString") => ( + crate::fn_info!(number_proto_to_locale_string_thunk, 1; with_declared(0), with_rest(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("Number", "toPrecision") => ( + crate::fn_info!(number_proto_to_precision_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ("Number", "toString") => ( + crate::fn_info!(number_proto_to_string_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ("Number", "valueOf") => ( + crate::fn_info!(number_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("Boolean", "toString") => ( + crate::fn_info!(boolean_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("Boolean", "valueOf") => ( + crate::fn_info!(boolean_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("Symbol", "toString") => ( + crate::fn_info!(symbol_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("Symbol", "valueOf") => ( + crate::fn_info!(symbol_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), + ("BigInt", "toString") => ( + crate::fn_info!(bigint_proto_to_string_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ), + ("BigInt", "valueOf") => ( + crate::fn_info!(bigint_proto_value_of_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ), _ => return None, }; - let value = primitive_proto_method_closure_value(method_name, func_ptr, arity); + let value = primitive_proto_method_closure_value(method_name, info, arity); if builtin_name == "BigInt" && method_name == "toString" { super::native_module::set_builtin_closure_length( crate::value::js_nanbox_get_pointer(value) as usize, @@ -163,13 +192,15 @@ pub(crate) fn primitive_proto_method_value(builtin_name: &str, method_name: &str Some(value) } -fn primitive_proto_method_closure_value(method_name: &str, func_ptr: *const u8, arity: u32) -> f64 { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +fn primitive_proto_method_closure_value( + method_name: &str, + info: *const crate::closure::JsFunctionInfo, + arity: u32, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - crate::closure::js_register_closure_arity(func_ptr, arity); - crate::closure::register_closure_body_builtin(func_ptr); super::native_module::set_bound_native_closure_name(closure, method_name); super::native_module::set_builtin_closure_length(closure as usize, arity); super::native_module::set_builtin_closure_non_constructable(closure as usize); diff --git a/crates/perry-runtime/src/object/property_key.rs b/crates/perry-runtime/src/object/property_key.rs index 171f853b05..f3f5e9bd5f 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -621,7 +621,7 @@ mod property_key_tests { let obj = js_object_alloc(0, 0); let obj_value = crate::value::js_nanbox_pointer(obj as i64); let sym = crate::symbol::js_symbol_new_empty(); - let getter = crate::closure::js_closure_alloc(accessor_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(accessor_getter, 0), 0); let getter_value = crate::value::js_nanbox_pointer(getter as i64); js_object_define_accessor( diff --git a/crates/perry-runtime/src/object/regex_proto_thunks.rs b/crates/perry-runtime/src/object/regex_proto_thunks.rs index d710fa33ab..ec5865e264 100644 --- a/crates/perry-runtime/src/object/regex_proto_thunks.rs +++ b/crates/perry-runtime/src/object/regex_proto_thunks.rs @@ -205,13 +205,16 @@ pub(super) extern "C" fn regex_proto_flags_getter( /// Install one accessor getter (`set: undefined`) onto `proto_obj` with the /// spec attributes (`enumerable: false`, `configurable: true`) and the proper /// getter `name` (`"get "`) / `length` (`0`). -fn install_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) { +fn install_getter( + proto_obj: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { if proto_obj.is_null() { return; } unsafe { - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } @@ -735,9 +738,19 @@ fn record_canonical_test_site(proto_obj: *mut ObjectHeader) { pub(super) fn install_regex_proto_methods(proto_obj: *mut ObjectHeader) { use super::global_this::install_proto_method as ipm; #[cfg(feature = "regex-engine")] - ipm(proto_obj, "exec", regex_proto_exec_thunk as *const u8, 1); + ipm( + proto_obj, + "exec", + crate::fn_info!(regex_proto_exec_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); #[cfg(feature = "regex-engine")] - ipm(proto_obj, "test", regex_proto_test_thunk as *const u8, 1); + ipm( + proto_obj, + "test", + crate::fn_info!(regex_proto_test_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); #[cfg(feature = "regex-engine")] record_canonical_test_site(proto_obj); // Annex B `compile` re-initializes the receiver in place. It needs a real @@ -747,13 +760,13 @@ pub(super) fn install_regex_proto_methods(proto_obj: *mut ObjectHeader) { ipm( proto_obj, "compile", - regex_proto_compile_thunk as *const u8, + crate::fn_info!(regex_proto_compile_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); ipm( proto_obj, "toString", - regex_proto_to_string_thunk as *const u8, + crate::fn_info!(regex_proto_to_string_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); #[cfg(feature = "regex-engine")] @@ -766,41 +779,40 @@ fn install_regex_symbol_methods(proto: *mut crate::object::ObjectHeader) { use crate::value::js_nanbox_pointer; let scope = RuntimeHandleScope::new(); let proto = scope.root_raw_mut_ptr(proto); - for (symbol, name, fp, arity) in [ + for (symbol, name, info, arity) in [ ( "match", "[Symbol.match]", - crate::regex::perex_match_search::match_thunk as *const u8, + crate::fn_info!(crate::regex::perex_match_search::match_thunk, 1; with_declared(1)), 1, ), ( "search", "[Symbol.search]", - crate::regex::perex_match_search::search_thunk as *const u8, + crate::fn_info!(crate::regex::perex_match_search::search_thunk, 1; with_declared(1)), 1, ), ( "matchAll", "[Symbol.matchAll]", - crate::regex::match_all::regexp_thunk as *const u8, + crate::fn_info!(crate::regex::match_all::regexp_thunk, 1; with_declared(1)), 1, ), ( "split", "[Symbol.split]", - crate::regex::perex_split::regexp_thunk as *const u8, + crate::fn_info!(crate::regex::perex_split::regexp_thunk, 2; with_declared(2)), 2, ), ( "replace", "[Symbol.replace]", - crate::regex::perex_replace::regexp_thunk as *const u8, + crate::fn_info!(crate::regex::perex_replace::regexp_thunk, 2; with_declared(2)), 2, ), ] { let iteration = RuntimeHandleScope::new(); - crate::closure::js_register_closure_arity(fp, arity); - let function = iteration.root_raw_mut_ptr(crate::closure::js_closure_alloc(fp, 0)); + let function = iteration.root_raw_mut_ptr(crate::closure::js_closure_alloc(info, 0)); function.with_mut_ptr(|function| { super::native_module::set_bound_native_closure_name(function, name) }); @@ -834,35 +846,55 @@ fn install_regex_symbol_methods(proto: *mut crate::object::ObjectHeader) { /// Install all RegExp.prototype accessor getters. pub(super) fn install_regex_proto_accessors(proto_obj: *mut ObjectHeader) { - install_getter(proto_obj, "flags", regex_proto_flags_getter as *const u8); - install_getter(proto_obj, "source", regex_proto_source_getter as *const u8); - install_getter(proto_obj, "global", regex_proto_global_getter as *const u8); + install_getter( + proto_obj, + "flags", + crate::fn_info!(regex_proto_flags_getter, 0; with_declared(0)), + ); + install_getter( + proto_obj, + "source", + crate::fn_info!(regex_proto_source_getter, 0; with_declared(0)), + ); + install_getter( + proto_obj, + "global", + crate::fn_info!(regex_proto_global_getter, 0; with_declared(0)), + ); install_getter( proto_obj, "ignoreCase", - regex_proto_ignore_case_getter as *const u8, + crate::fn_info!(regex_proto_ignore_case_getter, 0; with_declared(0)), ); install_getter( proto_obj, "multiline", - regex_proto_multiline_getter as *const u8, + crate::fn_info!(regex_proto_multiline_getter, 0; with_declared(0)), + ); + install_getter( + proto_obj, + "dotAll", + crate::fn_info!(regex_proto_dot_all_getter, 0; with_declared(0)), + ); + install_getter( + proto_obj, + "sticky", + crate::fn_info!(regex_proto_sticky_getter, 0; with_declared(0)), ); - install_getter(proto_obj, "dotAll", regex_proto_dot_all_getter as *const u8); - install_getter(proto_obj, "sticky", regex_proto_sticky_getter as *const u8); install_getter( proto_obj, "unicode", - regex_proto_unicode_getter as *const u8, + crate::fn_info!(regex_proto_unicode_getter, 0; with_declared(0)), ); install_getter( proto_obj, "unicodeSets", - regex_proto_unicode_sets_getter as *const u8, + crate::fn_info!(regex_proto_unicode_sets_getter, 0; with_declared(0)), ); install_getter( proto_obj, "hasIndices", - regex_proto_has_indices_getter as *const u8, + crate::fn_info!(regex_proto_has_indices_getter, 0; with_declared(0)), ); } diff --git a/crates/perry-runtime/src/object/reserved_floor.rs b/crates/perry-runtime/src/object/reserved_floor.rs index 92afc5443e..a0ef38f84d 100644 --- a/crates/perry-runtime/src/object/reserved_floor.rs +++ b/crates/perry-runtime/src/object/reserved_floor.rs @@ -251,9 +251,11 @@ mod tests { } unsafe { let iter = set_iter_with_10_20_30(); - let closure = crate::closure::js_closure_alloc(patched_next as *const u8, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(patched_next, 1; with_declared(0)), + 0, + ); assert!(!closure.is_null()); - crate::closure::js_register_closure_arity(patched_next as *const u8, 0); js_object_set_field_by_name( iter, key("next"), diff --git a/crates/perry-runtime/src/object/restricted_function_store_tests.rs b/crates/perry-runtime/src/object/restricted_function_store_tests.rs index 625efd6c1d..c86b2d749b 100644 --- a/crates/perry-runtime/src/object/restricted_function_store_tests.rs +++ b/crates/perry-runtime/src/object/restricted_function_store_tests.rs @@ -37,7 +37,10 @@ fn plain_function_restricted_stores_honor_put_value_throw() { 1, ); } - let plain = crate::closure::js_closure_alloc(plain_func, 0); + let plain = crate::closure::js_closure_alloc( + crate::fn_info!(non_strict_restricted_store_test_body, 0), + 0, + ); let plain_value = crate::value::js_nanbox_pointer(plain as i64); for name in ["caller", "arguments"] { @@ -84,8 +87,10 @@ fn plain_function_restricted_stores_honor_put_value_throw() { 0, ); } - crate::closure::js_register_closure_strict_function(strict_func); - let strict = crate::closure::js_closure_alloc(strict_func, 0); + let strict = crate::closure::js_closure_alloc( + crate::fn_info!(strict_restricted_store_test_body, 0; with_flags(crate::closure::FN_STRICT)), + 0, + ); let strict_value = crate::value::js_nanbox_pointer(strict as i64); let caller = crate::string::js_string_from_bytes(b"caller".as_ptr(), 6); let caller_value = f64::from_bits(crate::value::JSValue::string_ptr(caller).bits()); @@ -111,7 +116,8 @@ fn plain_function_restricted_stores_honor_put_value_throw() { 0, ); } - let method = crate::closure::js_closure_alloc(method_func, 0); + let method = + crate::closure::js_closure_alloc(crate::fn_info!(method_restricted_store_test_body, 0), 0); let method_value = crate::value::js_nanbox_pointer(method as i64); assert!( crate::exception::catch_js_throw(|| crate::proxy::js_put_value_set( diff --git a/crates/perry-runtime/src/object/string_proto_thunks.rs b/crates/perry-runtime/src/object/string_proto_thunks.rs index 7a46e0754d..380a2e8a2a 100644 --- a/crates/perry-runtime/src/object/string_proto_thunks.rs +++ b/crates/perry-runtime/src/object/string_proto_thunks.rs @@ -30,23 +30,28 @@ pub(super) fn install_string_proto_methods( return false; } use super::global_this::install_proto_method as ipm; - ipm(proto_obj, "at", string_proto_at_thunk as *const u8, 1); + ipm( + proto_obj, + "at", + crate::fn_info!(string_proto_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), + 1, + ); ipm( proto_obj, "charAt", - string_proto_char_at_thunk as *const u8, + crate::fn_info!(string_proto_char_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); ipm( proto_obj, "charCodeAt", - string_proto_char_code_at_thunk as *const u8, + crate::fn_info!(string_proto_char_code_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); ipm( proto_obj, "codePointAt", - string_proto_code_point_at_thunk as *const u8, + crate::fn_info!(string_proto_code_point_at_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); install_string_iterator_symbol(proto_obj); @@ -119,12 +124,24 @@ fn install_generic_string_proto_methods(proto_obj: *mut ObjectHeader) { "match" | "search" | "matchAll" | "replace" | "replaceAll" | "split" ) { let fp = match name { - "match" => string_proto_match_thunk as *const u8, - "matchAll" => string_proto_match_all_thunk as *const u8, - "replace" => string_proto_replace_thunk as *const u8, - "replaceAll" => string_proto_replace_all_thunk as *const u8, - "split" => string_proto_split_thunk as *const u8, - _ => string_proto_search_thunk as *const u8, + "match" => { + crate::fn_info!(string_proto_match_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)) + } + "matchAll" => { + crate::fn_info!(string_proto_match_all_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)) + } + "replace" => { + crate::fn_info!(string_proto_replace_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)) + } + "replaceAll" => { + crate::fn_info!(string_proto_replace_all_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)) + } + "split" => { + crate::fn_info!(string_proto_split_thunk, 2; with_declared(2), with_flags(crate::closure::FN_BUILTIN)) + } + _ => { + crate::fn_info!(string_proto_search_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)) + } }; super::global_this::install_proto_method(proto_obj, name, fp, spec_length); continue; @@ -134,9 +151,8 @@ fn install_generic_string_proto_methods(proto_obj: *mut ObjectHeader) { super::global_this::install_proto_method_rest_with_length( proto_obj, name, - string_proto_generic_thunk as *const u8, + crate::fn_info!(string_proto_generic_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), spec_length, - 0, ); } // Annex B: `trimLeft`/`trimRight` are the SAME function objects as @@ -206,12 +222,11 @@ fn install_string_iterator_symbol(proto_obj: *mut ObjectHeader) { if iter.is_null() { return; } - let func_ptr = string_proto_symbol_iterator_thunk as *const u8; + let func_ptr = crate::fn_info!(string_proto_symbol_iterator_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(func_ptr, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, 0); super::native_module::set_bound_native_closure_name(closure, "[Symbol.iterator]"); super::native_module::set_builtin_closure_length(closure as usize, 0); super::native_module::set_builtin_closure_non_constructable(closure as usize); diff --git a/crates/perry-runtime/src/object/temporal_proto.rs b/crates/perry-runtime/src/object/temporal_proto.rs index c3fc98909f..e4ec460583 100644 --- a/crates/perry-runtime/src/object/temporal_proto.rs +++ b/crates/perry-runtime/src/object/temporal_proto.rs @@ -83,10 +83,11 @@ fn install_getter(proto_obj: *mut ObjectHeader, name: &str) { if proto_obj.is_null() { return; } - let func_ptr = temporal_proto_getter_thunk as *const u8; unsafe { - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(temporal_proto_getter_thunk, 0; with_declared(0)), + 0, + ); if closure.is_null() { return; } @@ -117,9 +118,8 @@ fn install_method(proto_obj: *mut ObjectHeader, name: &str, spec_length: u32) { super::global_this::install_proto_method_rest_with_length( proto_obj, name, - temporal_proto_method_thunk as *const u8, + crate::fn_info!(temporal_proto_method_thunk, 1; with_rest(0), with_flags(crate::closure::FN_BUILTIN)), spec_length, - 0, ); } diff --git a/crates/perry-runtime/src/object/tests.rs b/crates/perry-runtime/src/object/tests.rs index c4ca9130fc..ef1b20df15 100644 --- a/crates/perry-runtime/src/object/tests.rs +++ b/crates/perry-runtime/src/object/tests.rs @@ -22,7 +22,7 @@ fn call_method_depth_drop_is_idempotent_after_exception_restore() { fn test_global_this_builtin_constructor_value(name: &str) -> f64 { let closure_ptr = crate::closure::js_closure_alloc( - crate::object::global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(crate::object::global_this_builtin_noop_thunk, 1; with_declared(0)), 0, ); if closure_ptr.is_null() { @@ -138,9 +138,10 @@ fn date_to_json_number_hint_honors_symbol_to_primitive() { let receiver = js_object_alloc(0, 0); let receiver_value = crate::value::js_nanbox_pointer(receiver as i64); - let to_primitive = - crate::closure::js_closure_alloc(symbol_to_primitive_nan as *const u8, 0); - crate::closure::js_register_closure_arity(symbol_to_primitive_nan as *const u8, 1); + let to_primitive = crate::closure::js_closure_alloc( + crate::fn_info!(symbol_to_primitive_nan, 1; with_declared(1)), + 0, + ); let sym = crate::symbol::well_known_symbol("toPrimitive"); let sym_value = f64::from_bits(crate::value::POINTER_TAG | (sym as u64 & crate::value::POINTER_MASK)); @@ -150,8 +151,10 @@ fn date_to_json_number_hint_honors_symbol_to_primitive() { crate::value::js_nanbox_pointer(to_primitive as i64), ); - let value_of = crate::closure::js_closure_alloc(value_of_finite as *const u8, 0); - crate::closure::js_register_closure_arity(value_of_finite as *const u8, 0); + let value_of = crate::closure::js_closure_alloc( + crate::fn_info!(value_of_finite, 0; with_declared(0)), + 0, + ); let value_of_key = crate::string::js_string_from_bytes(b"valueOf".as_ptr(), 7); js_object_set_field_by_name( receiver, @@ -179,9 +182,10 @@ fn date_to_json_symbol_to_primitive_object_result_throws() { let receiver = js_object_alloc(0, 0); let receiver_value = crate::value::js_nanbox_pointer(receiver as i64); - let to_primitive = - crate::closure::js_closure_alloc(symbol_to_primitive_this_object as *const u8, 0); - crate::closure::js_register_closure_arity(symbol_to_primitive_this_object as *const u8, 1); + let to_primitive = crate::closure::js_closure_alloc( + crate::fn_info!(symbol_to_primitive_this_object, 1; with_declared(1)), + 0, + ); let sym = crate::symbol::well_known_symbol("toPrimitive"); let sym_value = f64::from_bits(crate::value::POINTER_TAG | (sym as u64 & crate::value::POINTER_MASK)); @@ -191,8 +195,10 @@ fn date_to_json_symbol_to_primitive_object_result_throws() { crate::value::js_nanbox_pointer(to_primitive as i64), ); - let to_iso = crate::closure::js_closure_alloc(to_iso_string_sentinel as *const u8, 0); - crate::closure::js_register_closure_arity(to_iso_string_sentinel as *const u8, 0); + let to_iso = crate::closure::js_closure_alloc( + crate::fn_info!(to_iso_string_sentinel, 0; with_declared(0)), + 0, + ); let to_iso_key = crate::string::js_string_from_bytes(b"toISOString".as_ptr(), 11); js_object_set_field_by_name( receiver, @@ -246,8 +252,10 @@ fn builtin_prototype_methods_reject_dynamic_new() { ); } - let ordinary = crate::closure::js_closure_alloc(value_of_finite as *const u8, 0); - crate::closure::js_register_closure_arity(value_of_finite as *const u8, 0); + let ordinary = crate::closure::js_closure_alloc( + crate::fn_info!(value_of_finite, 0; with_declared(0)), + 0, + ); let ordinary_value = crate::value::js_nanbox_pointer(ordinary as i64); let result = catch_js(|| js_new_function_construct(ordinary_value, std::ptr::null(), 0)); assert!(result.is_ok(), "ordinary closures remain constructable"); @@ -316,7 +324,7 @@ fn closure_name_and_length_ignore_plain_assignment() { crate::closure::test_clear_closure_side_tables(); { let closure = crate::closure::js_closure_alloc( - crate::object::global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(crate::object::global_this_builtin_noop_thunk, 1; with_declared(0)), 0, ); assert!(!closure.is_null()); @@ -353,7 +361,7 @@ fn closure_name_can_be_redefined_with_define_property() { crate::closure::test_clear_closure_side_tables(); { let closure = crate::closure::js_closure_alloc( - crate::object::global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(crate::object::global_this_builtin_noop_thunk, 1; with_declared(0)), 0, ); assert!(!closure.is_null()); @@ -433,11 +441,11 @@ fn closure_accessor_define_property_is_own_and_invoked() { let _global = crate::gc::global_side_table_test_lock(); crate::closure::test_clear_closure_side_tables(); let closure = crate::closure::js_closure_alloc( - crate::object::global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(crate::object::global_this_builtin_noop_thunk, 1; with_declared(0)), 0, ); assert!(!closure.is_null()); - let getter = crate::closure::js_closure_alloc(closure_accessor_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc(crate::fn_info!(closure_accessor_getter, 0), 0); assert!(!getter.is_null()); let caller_key = crate::string::js_string_from_bytes(b"caller".as_ptr(), 6); @@ -596,7 +604,8 @@ fn symbol_keys_keep_creation_order_across_accessor_redefine() { .collect() }; let getter_descriptor = || -> f64 { - let getter = crate::closure::js_closure_alloc(closure_accessor_getter as *const u8, 0); + let getter = + crate::closure::js_closure_alloc(crate::fn_info!(closure_accessor_getter, 0), 0); assert!(!getter.is_null()); let get_key = crate::string::js_string_from_bytes(b"get".as_ptr(), 3); let descriptor = js_object_alloc(0, 0); diff --git a/crates/perry-runtime/src/object/typed_array_proto_thunks.rs b/crates/perry-runtime/src/object/typed_array_proto_thunks.rs index 026f9ec0ad..9dfe5ff900 100644 --- a/crates/perry-runtime/src/object/typed_array_proto_thunks.rs +++ b/crates/perry-runtime/src/object/typed_array_proto_thunks.rs @@ -76,30 +76,21 @@ pub(super) const TYPED_ARRAY_PROTO_METHODS: &[(&str, u32)] = &[ ]; /// Install the brand-checking `%TypedArray%.prototype` methods onto a per-kind -/// typed-array prototype object. Each method gets a DISTINCT thunk func_ptr so -/// the per-func-ptr arity registry (and the no-op-thunk filter in -/// `try_dispatch_value_called_proto_method`) can tell them apart — and, because +/// typed-array prototype object. Each method gets a DISTINCT thunk so the +/// no-op-thunk filter in `try_dispatch_value_called_proto_method` can tell +/// them apart — and, because /// these are not the shared no-op thunk, a `.call`/`.apply` on the value flows /// through the normal closure-dispatch path straight into the thunk, where the /// brand check runs. pub(super) fn install_typed_array_proto_methods(proto_obj: *mut ObjectHeader) { use super::global_this::install_proto_method as ipm; for &(name, arity) in TYPED_ARRAY_PROTO_METHODS { - let func_ptr = thunk_for(name); - ipm(proto_obj, name, func_ptr, arity); - // `install_proto_method` uses the visible spec `.length` as the call - // arity. Most of these thunks have a uniform 3-argument native - // signature; register that ABI width so omitted trailing arguments are - // padded with `undefined` instead of reading unset register slots. - // - // `lastIndexOf` and the reducers need to distinguish omitted optional - // arguments from an explicitly supplied `undefined`, so they use the - // rest-dispatch path with one fixed argument. - if matches!(name, "lastIndexOf" | "reduce" | "reduceRight") { - crate::closure::js_register_closure_rest(func_ptr, 1); - } else { - crate::closure::js_register_closure_arity(func_ptr, 3); - } + // The visible spec `.length` is `arity`; the body's info declares the + // uniform 3-argument ABI width, so omitted trailing arguments are + // padded with `undefined` — except `lastIndexOf` and the reducers, + // which tell omitted optional arguments from an explicit `undefined` + // and so take one fixed argument plus a rest array (`thunk_for`). + ipm(proto_obj, name, thunk_for(name), arity); } } @@ -107,39 +98,101 @@ pub(super) fn install_typed_array_proto_methods(proto_obj: *mut ObjectHeader) { /// back to a generic dispatcher keyed off the closure's recorded `.name` — but /// every entry in `TYPED_ARRAY_PROTO_METHODS` has a concrete thunk so the lookup /// is exhaustive in practice. -fn thunk_for(name: &str) -> *const u8 { +fn thunk_for(name: &str) -> *const crate::closure::JsFunctionInfo { match name { - "at" => ta_at_thunk as *const u8, - "copyWithin" => ta_copy_within_thunk as *const u8, - "entries" => ta_entries_thunk as *const u8, - "every" => ta_every_thunk as *const u8, - "fill" => ta_fill_thunk as *const u8, - "filter" => ta_filter_thunk as *const u8, - "find" => ta_find_thunk as *const u8, - "findIndex" => ta_find_index_thunk as *const u8, - "findLast" => ta_find_last_thunk as *const u8, - "findLastIndex" => ta_find_last_index_thunk as *const u8, - "forEach" => ta_for_each_thunk as *const u8, - "includes" => ta_includes_thunk as *const u8, - "indexOf" => ta_index_of_thunk as *const u8, - "join" => ta_join_thunk as *const u8, - "keys" => ta_keys_thunk as *const u8, - "lastIndexOf" => ta_last_index_of_thunk as *const u8, - "map" => ta_map_thunk as *const u8, - "reduce" => ta_reduce_thunk as *const u8, - "reduceRight" => ta_reduce_right_thunk as *const u8, - "reverse" => ta_reverse_thunk as *const u8, - "set" => ta_set_thunk as *const u8, - "slice" => ta_slice_thunk as *const u8, - "some" => ta_some_thunk as *const u8, - "sort" => ta_sort_thunk as *const u8, - "subarray" => ta_subarray_thunk as *const u8, - "toLocaleString" => ta_to_locale_string_thunk as *const u8, - "toReversed" => ta_to_reversed_thunk as *const u8, - "toSorted" => ta_to_sorted_thunk as *const u8, - "values" => ta_values_thunk as *const u8, - "with" => ta_with_thunk as *const u8, - _ => ta_generic_thunk as *const u8, + "at" => { + crate::fn_info!(ta_at_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "copyWithin" => { + crate::fn_info!(ta_copy_within_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "entries" => { + crate::fn_info!(ta_entries_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "every" => { + crate::fn_info!(ta_every_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "fill" => { + crate::fn_info!(ta_fill_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "filter" => { + crate::fn_info!(ta_filter_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "find" => { + crate::fn_info!(ta_find_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "findIndex" => { + crate::fn_info!(ta_find_index_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "findLast" => { + crate::fn_info!(ta_find_last_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "findLastIndex" => { + crate::fn_info!(ta_find_last_index_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "forEach" => { + crate::fn_info!(ta_for_each_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "includes" => { + crate::fn_info!(ta_includes_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "indexOf" => { + crate::fn_info!(ta_index_of_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "join" => { + crate::fn_info!(ta_join_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "keys" => { + crate::fn_info!(ta_keys_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "lastIndexOf" => { + crate::fn_info!(ta_last_index_of_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)) + } + "map" => { + crate::fn_info!(ta_map_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "reduce" => { + crate::fn_info!(ta_reduce_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)) + } + "reduceRight" => { + crate::fn_info!(ta_reduce_right_thunk, 2; with_rest(1), with_flags(crate::closure::FN_BUILTIN)) + } + "reverse" => { + crate::fn_info!(ta_reverse_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "set" => { + crate::fn_info!(ta_set_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "slice" => { + crate::fn_info!(ta_slice_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "some" => { + crate::fn_info!(ta_some_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "sort" => { + crate::fn_info!(ta_sort_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "subarray" => { + crate::fn_info!(ta_subarray_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "toLocaleString" => { + crate::fn_info!(ta_to_locale_string_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "toReversed" => { + crate::fn_info!(ta_to_reversed_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "toSorted" => { + crate::fn_info!(ta_to_sorted_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "values" => { + crate::fn_info!(ta_values_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + "with" => { + crate::fn_info!(ta_with_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } + _ => { + crate::fn_info!(ta_generic_thunk, 3; with_declared(3), with_flags(crate::closure::FN_BUILTIN)) + } } } @@ -1217,14 +1270,31 @@ pub(super) extern "C" fn u8_set_from_hex_thunk( /// — the same convention `install_typed_array_proto_methods` uses. pub(super) fn install_uint8array_base64_hex_proto_methods(proto_obj: *mut ObjectHeader) { use super::global_this::install_proto_method as ipm; - let methods: &[(&str, *const u8, u32)] = &[ - ("toBase64", u8_to_base64_thunk as *const u8, 0), - ("toHex", u8_to_hex_thunk as *const u8, 0), - ("setFromBase64", u8_set_from_base64_thunk as *const u8, 1), - ("setFromHex", u8_set_from_hex_thunk as *const u8, 1), + use crate::closure::FN_BUILTIN; + use crate::fn_info; + let methods: &[(&str, *const crate::closure::JsFunctionInfo, u32)] = &[ + ( + "toBase64", + fn_info!(u8_to_base64_thunk, 3; with_declared(3), with_flags(FN_BUILTIN)), + 0, + ), + ( + "toHex", + fn_info!(u8_to_hex_thunk, 3; with_declared(3), with_flags(FN_BUILTIN)), + 0, + ), + ( + "setFromBase64", + fn_info!(u8_set_from_base64_thunk, 3; with_declared(3), with_flags(FN_BUILTIN)), + 1, + ), + ( + "setFromHex", + fn_info!(u8_set_from_hex_thunk, 3; with_declared(3), with_flags(FN_BUILTIN)), + 1, + ), ]; - for &(name, func_ptr, spec_length) in methods { - ipm(proto_obj, name, func_ptr, spec_length); - crate::closure::js_register_closure_arity(func_ptr, 3); + for &(name, info, spec_length) in methods { + ipm(proto_obj, name, info, spec_length); } } diff --git a/crates/perry-runtime/src/object/util_types.rs b/crates/perry-runtime/src/object/util_types.rs index 14f64b05e3..21464d5659 100644 --- a/crates/perry-runtime/src/object/util_types.rs +++ b/crates/perry-runtime/src/object/util_types.rs @@ -290,7 +290,8 @@ pub extern "C" fn js_util_types_is_async_function(value: f64) -> f64 { return nanbox_bool(false); } let closure = ptr as *const crate::closure::ClosureHeader; - let is_async = unsafe { crate::closure::is_registered_async_function((*closure).func_ptr) }; + let is_async = crate::closure::closure_info(closure) + .is_some_and(|info| crate::closure::info_has(info, crate::closure::FN_ASYNC)); nanbox_bool(is_async) } @@ -301,8 +302,10 @@ pub extern "C" fn js_util_types_is_generator_function(value: f64) -> f64 { return nanbox_bool(false); } let closure = v.as_pointer::(); - let func_ptr = crate::closure::get_valid_func_ptr(closure); - nanbox_bool(!func_ptr.is_null() && crate::closure::is_registered_generator_function(func_ptr)) + nanbox_bool( + crate::closure::closure_info(closure) + .is_some_and(|info| crate::closure::info_has(info, crate::closure::FN_GENERATOR)), + ) } #[no_mangle] diff --git a/crates/perry-runtime/src/object/weakref_proto_thunks.rs b/crates/perry-runtime/src/object/weakref_proto_thunks.rs index 6b3f213803..e1ac2c50ad 100644 --- a/crates/perry-runtime/src/object/weakref_proto_thunks.rs +++ b/crates/perry-runtime/src/object/weakref_proto_thunks.rs @@ -314,7 +314,7 @@ pub(super) fn install_weakref_proto_methods( ipm( proto_obj, "deref", - weakref_proto_deref_thunk as *const u8, + crate::fn_info!(weakref_proto_deref_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -322,13 +322,13 @@ pub(super) fn install_weakref_proto_methods( ipm( proto_obj, "register", - finreg_proto_register_thunk as *const u8, + crate::fn_info!(finreg_proto_register_thunk, 3; with_declared(2), with_flags(crate::closure::FN_BUILTIN)), 2, ); ipm( proto_obj, "unregister", - finreg_proto_unregister_thunk as *const u8, + crate::fn_info!(finreg_proto_unregister_thunk, 1; with_declared(1), with_flags(crate::closure::FN_BUILTIN)), 1, ); } @@ -395,11 +395,13 @@ mod tests { /// shape a name-collided fold hands to the weak helpers (`{ deref: … }`, /// `class Cache { deref() {…} }`, an array with `.deref` attached, or a /// function parameter). - fn plain_object_with_method(method_name: &str, func_ptr: *const u8, arity: u32) -> f64 { + fn plain_object_with_method( + method_name: &str, + info: *const crate::closure::JsFunctionInfo, + ) -> f64 { let obj = crate::object::js_object_alloc(0, 0); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); assert!(!closure.is_null(), "closure alloc failed"); - crate::closure::js_register_closure_arity(func_ptr, arity); let key = crate::string::js_string_from_bytes(method_name.as_ptr(), method_name.len() as u32); let value = crate::value::js_nanbox_pointer(closure as i64); @@ -420,7 +422,10 @@ mod tests { fn folded_weak_helpers_delegate_a_foreign_receiver_to_its_own_method() { let sentinel = JSValue::int32(FOREIGN_SENTINEL).bits(); - let deref_recv = plain_object_with_method("deref", foreign_method_thunk as *const u8, 0); + let deref_recv = plain_object_with_method( + "deref", + crate::fn_info!(foreign_method_thunk, 0; with_declared(0)), + ); assert_eq!( crate::weakref::js_weakref_deref(deref_recv).to_bits(), sentinel, @@ -428,12 +433,11 @@ mod tests { ); let key = f64::from_bits(JSValue::int32(1).bits()); - for (name, ptr) in [ - ("get", foreign_method_thunk_1 as *const u8), - ("has", foreign_method_thunk_1 as *const u8), - ("delete", foreign_method_thunk_1 as *const u8), - ] { - let recv = plain_object_with_method(name, ptr, 1); + for name in ["get", "has", "delete"] { + let recv = plain_object_with_method( + name, + crate::fn_info!(foreign_method_thunk_1, 1; with_declared(1)), + ); let got = match name { "get" => crate::weakref::js_weakmap_get(recv, key), "has" => crate::weakref::js_weakmap_has(recv, key), @@ -446,7 +450,10 @@ mod tests { ); } - let add_recv = plain_object_with_method("add", foreign_method_thunk_1 as *const u8, 1); + let add_recv = plain_object_with_method( + "add", + crate::fn_info!(foreign_method_thunk_1, 1; with_declared(1)), + ); assert_eq!( crate::weakref::js_weakset_add(add_recv, key).to_bits(), sentinel, diff --git a/crates/perry-runtime/src/object/websocket_global.rs b/crates/perry-runtime/src/object/websocket_global.rs index 27a2322a20..3e1381c0bd 100644 --- a/crates/perry-runtime/src/object/websocket_global.rs +++ b/crates/perry-runtime/src/object/websocket_global.rs @@ -50,13 +50,13 @@ pub(super) fn install_proto_methods(proto_obj: *mut ObjectHeader) { install_proto_method( proto_obj, "close", - global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(global_this_builtin_noop_thunk, 1), 0, ); install_proto_method( proto_obj, "send", - global_this_builtin_noop_thunk as *const u8, + crate::fn_info!(global_this_builtin_noop_thunk, 1), 1, ); super::set_builtin_property_attrs( diff --git a/crates/perry-runtime/src/os/os_process_emitter.rs b/crates/perry-runtime/src/os/os_process_emitter.rs index a3f747065d..12ff61a490 100644 --- a/crates/perry-runtime/src/os/os_process_emitter.rs +++ b/crates/perry-runtime/src/os/os_process_emitter.rs @@ -203,8 +203,10 @@ extern "C" fn process_once_raw_wrapper( fn create_process_once_raw_wrapper( callback: *const crate::closure::ClosureHeader, ) -> *const crate::closure::ClosureHeader { - crate::closure::js_register_closure_rest(process_once_raw_wrapper as *const u8, 0); - let wrapper = crate::closure::js_closure_alloc(process_once_raw_wrapper as *const u8, 1); + let wrapper = crate::closure::js_closure_alloc( + crate::fn_info!(process_once_raw_wrapper, 1; with_rest(0)), + 1, + ); let callback_value = f64::from_bits(crate::value::JSValue::pointer(callback as *const u8).bits()); crate::closure::js_closure_set_capture_f64(wrapper, 0, callback_value); diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index d5fa0857eb..783da884de 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -870,6 +870,14 @@ extern "C" fn process_stdin_add_listener_once( } /// `process.stdin.removeListener(event, cb)` / `.off(...)`. +/// One info for `removeListener` and `off`: both install the same singleton +/// function object (`off === removeListener`). +static PROCESS_STDIN_REMOVE_LISTENER_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + process_stdin_remove_listener as crate::codegen_abi::JsBody2, + ) + .with_declared(2); + extern "C" fn process_stdin_remove_listener( _closure: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, @@ -1552,11 +1560,10 @@ fn maybe_fire_stdin_end() { } } -/// Make a native-method closure value with the given arity registered, so the -/// dispatch path forwards the right number of arguments. -fn stdin_native_method(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, arity); - let closure = crate::closure::js_closure_alloc_singleton(func_ptr); +/// Make a native-method closure value (`.length` `arity`) for the body +/// `info` describes. +fn stdin_native_method(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = crate::closure::js_closure_alloc_singleton(info); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) @@ -1604,7 +1611,7 @@ pub fn scan_process_stream_singleton_roots_mut(visitor: &mut crate::gc::RuntimeR /// Build a stream object with a `write` field bound to the given stub. fn build_stream_object_with_write( - write_stub: crate::closure::body_call::js_body_fn_ty!(a, a, a), + write_info: *const crate::closure::JsFunctionInfo, fd: f64, writable: f64, ) -> *mut crate::object::ObjectHeader { @@ -1691,15 +1698,14 @@ fn build_stream_object_with_write( ) }; // `write` takes up to three positional args — `write(chunk[, encoding][, - // callback])`. Register its arity so dispatch pads/truncates to exactly the - // three the stub declares (Direct dispatch would otherwise size the call to - // the call site, dropping the trailing callback — #6672). - crate::closure::js_register_closure_arity(write_stub as *const u8, 3); - let closure = js_closure_alloc(write_stub as *const u8, 0); + // callback])`. The caller's info declares all three, so dispatch + // pads/truncates to exactly the three the stub takes; sizing the call to + // the call site would drop the trailing callback (#6672). + let closure = js_closure_alloc(write_info, 0); let cval = JSValue::pointer(closure as *const u8); js_object_set_field(obj, 0, cval); js_object_set_field(obj, 1, JSValue::number(fd)); - let emit = js_closure_alloc(process_stream_emit_stub as *const u8, 0); + let emit = js_closure_alloc(crate::fn_info!(process_stream_emit_stub, 1), 0); js_object_set_field(obj, 2, JSValue::pointer(emit as *const u8)); if is_tty && fd_i != 0 { js_object_set_field( @@ -1715,16 +1721,24 @@ fn build_stream_object_with_write( } else if is_stdin { // Real `on(event, cb)` so `process.stdin.on("data"/"readable", …)` // registers a keyboard listener instead of dropping it (#input). - let on = stdin_native_method(process_stdin_on as *const u8, "on", 2); + let on = stdin_native_method( + crate::fn_info!(process_stdin_on, 2; with_declared(2)), + "on", + 2, + ); js_object_set_field(obj, 3, JSValue::from_bits(on.to_bits())); // `once` routes through the same registry as `on`/`addListener` so a // one-shot listener registered on an aliased binding is not dropped either. - let once = stdin_native_method(process_stdin_add_listener_once as *const u8, "once", 2); + let once = stdin_native_method( + crate::fn_info!(process_stdin_add_listener_once, 2; with_declared(2)), + "once", + 2, + ); js_object_set_field(obj, 4, JSValue::from_bits(once.to_bits())); } else { - let on = js_closure_alloc(process_stream_on_once_stub as *const u8, 0); + let on = js_closure_alloc(crate::fn_info!(process_stream_on_once_stub, 1), 0); js_object_set_field(obj, 3, JSValue::pointer(on as *const u8)); - let once = js_closure_alloc(process_stream_on_once_stub as *const u8, 0); + let once = js_closure_alloc(crate::fn_info!(process_stream_on_once_stub, 1), 0); js_object_set_field(obj, 4, JSValue::pointer(once as *const u8)); } js_object_set_field(obj, 5, JSValue::from_bits(writable.to_bits())); @@ -1778,54 +1792,54 @@ fn build_stream_object_with_write( // replaces the stream stubs below with its real listener/flow operations; // stdout and stderr retain the stubs. if let Some(start) = teardown_start { - let set_field_with_stub = |idx: u32, stub: crate::closure::body_call::js_body_fn_ty!(a)| { - let c = js_closure_alloc(stub as *const u8, 0); + let on_once = crate::fn_info!(process_stream_on_once_stub, 1); + let set_field_with_stub = |idx: u32, stub: *const crate::closure::JsFunctionInfo| { + let c = js_closure_alloc(stub, 0); js_object_set_field(obj, idx, JSValue::pointer(c as *const u8)); }; - let lifecycle: crate::closure::body_call::js_body_fn_ty!(a) = if is_stdin { - process_stdin_detach_stub + let lifecycle = if is_stdin { + crate::fn_info!(process_stdin_detach_stub, 1) } else { - process_stream_on_once_stub + on_once }; // On stdin these must be REAL: a TUI registers its keyboard through an // aliased binding (`stdin.addListener("readable", handler)`), which lands // here rather than on codegen's direct `process.stdin.on(...)` extern. As // no-op stubs they silently discarded the handler. if is_stdin { - let add = - stdin_native_method(process_stdin_add_listener as *const u8, "addListener", 2); - js_object_set_field(obj, start, JSValue::from_bits(add.to_bits())); - let rm = stdin_native_method( - process_stdin_remove_listener as *const u8, - "removeListener", + let add = stdin_native_method( + crate::fn_info!(process_stdin_add_listener, 2; with_declared(2)), + "addListener", 2, ); + js_object_set_field(obj, start, JSValue::from_bits(add.to_bits())); + let rm = stdin_native_method(&PROCESS_STDIN_REMOVE_LISTENER_INFO, "removeListener", 2); js_object_set_field(obj, start + 1, JSValue::from_bits(rm.to_bits())); - let off = stdin_native_method(process_stdin_remove_listener as *const u8, "off", 2); + let off = stdin_native_method(&PROCESS_STDIN_REMOVE_LISTENER_INFO, "off", 2); js_object_set_field(obj, start + 2, JSValue::from_bits(off.to_bits())); } else { - set_field_with_stub(start, process_stream_on_once_stub); // addListener - set_field_with_stub(start + 1, process_stream_on_once_stub); // removeListener - set_field_with_stub(start + 2, process_stream_on_once_stub); // off + set_field_with_stub(start, on_once); // addListener + set_field_with_stub(start + 1, on_once); // removeListener + set_field_with_stub(start + 2, on_once); // off } if is_stdin { let remove_all = stdin_native_method( - process_stdin_remove_all_listeners as *const u8, + crate::fn_info!(process_stdin_remove_all_listeners, 1; with_declared(1)), "removeAllListeners", 1, ); js_object_set_field(obj, start + 3, JSValue::from_bits(remove_all.to_bits())); } else { - set_field_with_stub(start + 3, process_stream_on_once_stub); + set_field_with_stub(start + 3, on_once); } set_field_with_stub(start + 4, lifecycle); // pause // resume: real flowing-mode start on stdin, no-op on stdout/stderr. set_field_with_stub( start + 5, if is_stdin { - process_stdin_resume + crate::fn_info!(process_stdin_resume, 1) } else { - process_stream_on_once_stub + on_once }, ); // resume // #9676: on stdin, `unref`/`ref` are a SYMMETRIC pair that only moves @@ -1833,21 +1847,32 @@ fn build_stream_object_with_write( set_field_with_stub( start + 6, if is_stdin { - process_stdin_unref_stub + crate::fn_info!(process_stdin_unref_stub, 1) } else { - process_stream_on_once_stub + on_once }, ); // unref if is_stdin { - set_field_with_stub(start + 7, process_stdin_ref_stub); // ref + set_field_with_stub(start + 7, crate::fn_info!(process_stdin_ref_stub, 1)); // ref set_field_with_stub(start + 8, lifecycle); // destroy - let se = stdin_native_method(process_stdin_set_encoding as *const u8, "setEncoding", 1); + let se = stdin_native_method( + crate::fn_info!(process_stdin_set_encoding, 1; with_declared(1)), + "setEncoding", + 1, + ); js_object_set_field(obj, start + 9, JSValue::from_bits(se.to_bits())); // field 22: Readable.read() returns buffered keyboard input. - let read = stdin_native_method(process_stdin_read as *const u8, "read", 1); + let read = stdin_native_method( + crate::fn_info!(process_stdin_read, 1; with_declared(1)), + "read", + 1, + ); js_object_set_field(obj, 22, JSValue::from_bits(read.to_bits())); - let listeners = - stdin_native_method(process_stdin_listeners as *const u8, "listeners", 1); + let listeners = stdin_native_method( + crate::fn_info!(process_stdin_listeners, 1; with_declared(1)), + "listeners", + 1, + ); js_object_set_field(obj, 23, JSValue::from_bits(listeners.to_bits())); } else { set_field_with_stub(start + 7, lifecycle); // destroy @@ -1865,7 +1890,7 @@ pub extern "C" fn js_process_stdin() -> f64 { let fresh = *slot == 0; if fresh { *slot = build_stream_object_with_write( - process_stdin_write_noop_stub, + crate::fn_info!(process_stdin_write_noop_stub, 3), 0.0, f64::from_bits(crate::value::TAG_UNDEFINED), ) as usize; @@ -1903,7 +1928,7 @@ pub extern "C" fn js_process_stdout() -> f64 { let mut slot = slot.borrow_mut(); if *slot == 0 { *slot = build_stream_object_with_write( - process_stdout_write_stub, + crate::fn_info!(process_stdout_write_stub, 3; with_declared(3)), 1.0, f64::from_bits(crate::value::TAG_TRUE), ) as usize; @@ -1921,7 +1946,7 @@ pub extern "C" fn js_process_stderr() -> f64 { let mut slot = slot.borrow_mut(); if *slot == 0 { *slot = build_stream_object_with_write( - process_stderr_write_stub, + crate::fn_info!(process_stderr_write_stub, 3; with_declared(3)), 2.0, f64::from_bits(crate::value::TAG_TRUE), ) as usize; diff --git a/crates/perry-runtime/src/perf_hooks.rs b/crates/perry-runtime/src/perf_hooks.rs index d5eb74eaad..78ab158d97 100644 --- a/crates/perry-runtime/src/perf_hooks.rs +++ b/crates/perry-runtime/src/perf_hooks.rs @@ -1680,8 +1680,10 @@ fn schedule_flush() { // the timer phase instead, i.e. before an immediate the test awaits — // which is why `await new Promise(r => setImmediate(r))` saw // "not delivered". - let closure = - crate::closure::js_closure_alloc_singleton(js_perf_observer_flush_all as *const u8); + let closure = crate::closure::js_closure_alloc_singleton(crate::fn_info!( + js_perf_observer_flush_all, + 0 + )); crate::timer::js_set_immediate_callback(closure as i64); } } diff --git a/crates/perry-runtime/src/perf_hooks/prototypes.rs b/crates/perry-runtime/src/perf_hooks/prototypes.rs index b2a3f3d430..b6fbbdab7e 100644 --- a/crates/perry-runtime/src/perf_hooks/prototypes.rs +++ b/crates/perry-runtime/src/perf_hooks/prototypes.rs @@ -160,9 +160,9 @@ extern "C" fn perf_list_get_by_name_thunk( unsafe { current_list_get_by_name(name) } } -fn perf_method_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +/// `info` records the method's declared `arity`, which is also its `.length`. +fn perf_method_value(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -226,8 +226,7 @@ unsafe fn install_perf_to_string_tag(proto: *mut crate::object::ObjectHeader, ta unsafe fn perf_field_getter(name: &str) -> f64 { let leaked: &'static [u8] = name.as_bytes().to_vec().leak(); - let func_ptr = perf_entry_field_getter_thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); + let func_ptr = crate::fn_info!(perf_entry_field_getter_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(func_ptr, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, leaked.as_ptr() as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, leaked.len() as i64); @@ -324,50 +323,80 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( match class_name { "Performance" => { let methods = [ - ("clearMarks", clear_marks as *const u8, 0, true), - ("clearMeasures", clear_measures as *const u8, 0, true), + ( + "clearMarks", + crate::fn_info!(clear_marks, 1; with_declared(0)), + 0, + true, + ), + ( + "clearMeasures", + crate::fn_info!(clear_measures, 1; with_declared(0)), + 0, + true, + ), ( "clearResourceTimings", - clear_resource_timings as *const u8, + crate::fn_info!(clear_resource_timings, 0; with_declared(0)), + 0, + true, + ), + ( + "getEntries", + crate::fn_info!(get_entries, 0; with_declared(0)), 0, true, ), - ("getEntries", get_entries as *const u8, 0, true), ( "getEntriesByName", - get_entries_by_name as *const u8, + crate::fn_info!(get_entries_by_name, 2; with_declared(1)), 1, true, ), ( "getEntriesByType", - get_entries_by_type as *const u8, + crate::fn_info!(get_entries_by_type, 1; with_declared(1)), + 1, + true, + ), + ("mark", crate::fn_info!(mark, 2; with_declared(1)), 1, true), + ( + "measure", + crate::fn_info!(measure, 3; with_declared(1)), 1, true, ), - ("mark", mark as *const u8, 1, true), - ("measure", measure as *const u8, 1, true), - ("now", now as *const u8, 0, true), + ("now", crate::fn_info!(now, 0; with_declared(0)), 0, true), ( "setResourceTimingBufferSize", - set_resource_timing_buffer_size as *const u8, + crate::fn_info!(set_resource_timing_buffer_size, 1; with_declared(1)), 1, true, ), - ("toJSON", to_json as *const u8, 0, true), + ( + "toJSON", + crate::fn_info!(to_json, 0; with_declared(0)), + 0, + true, + ), ( "eventLoopUtilization", - event_loop_utilization as *const u8, + crate::fn_info!(event_loop_utilization, 2; with_declared(2)), 2, false, ), ( "markResourceTiming", - mark_resource_timing as *const u8, + crate::fn_info!(mark_resource_timing, 8; with_declared(7)), 7, false, ), - ("timerify", timerify as *const u8, 1, false), + ( + "timerify", + crate::fn_info!(timerify, 2; with_declared(1)), + 1, + false, + ), ]; for (method, thunk, arity, enumerable) in methods { install_perf_method( @@ -378,7 +407,7 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( ); } let getter = perf_method_value( - perf_time_origin_getter_thunk as *const u8, + crate::fn_info!(perf_time_origin_getter_thunk, 0; with_declared(0)), "get timeOrigin", 0, ); @@ -390,7 +419,11 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( let getter = perf_field_getter(field); install_perf_getter(proto, field, getter, true); } - let to_json = perf_method_value(perf_entry_to_json_thunk as *const u8, "toJSON", 0); + let to_json = perf_method_value( + crate::fn_info!(perf_entry_to_json_thunk, 0; with_declared(0)), + "toJSON", + 0, + ); install_perf_method(proto, "toJSON", to_json, true); } "PerformanceMark" | "PerformanceMeasure" => { @@ -398,7 +431,11 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( link_perf_class_default_prototype(proto as usize, base.to_bits()); let getter = perf_field_getter("detail"); install_perf_getter(proto, "detail", getter, true); - let to_json = perf_method_value(perf_entry_to_json_thunk as *const u8, "toJSON", 0); + let to_json = perf_method_value( + crate::fn_info!(perf_entry_to_json_thunk, 0; with_declared(0)), + "toJSON", + 0, + ); install_perf_method(proto, "toJSON", to_json, false); install_perf_to_string_tag(proto, class_name); } @@ -406,20 +443,28 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( install_perf_method( proto, "observe", - perf_method_value(perf_observer_observe_thunk as *const u8, "observe", 1), + perf_method_value( + crate::fn_info!(perf_observer_observe_thunk, 1; with_declared(1)), + "observe", + 1, + ), true, ); install_perf_method( proto, "disconnect", - perf_method_value(perf_observer_disconnect_thunk as *const u8, "disconnect", 0), + perf_method_value( + crate::fn_info!(perf_observer_disconnect_thunk, 0; with_declared(0)), + "disconnect", + 0, + ), true, ); install_perf_method( proto, "takeRecords", perf_method_value( - perf_observer_take_records_thunk as *const u8, + crate::fn_info!(perf_observer_take_records_thunk, 0; with_declared(0)), "takeRecords", 0, ), @@ -431,14 +476,18 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( install_perf_method( proto, "getEntries", - perf_method_value(perf_list_get_entries_thunk as *const u8, "getEntries", 0), + perf_method_value( + crate::fn_info!(perf_list_get_entries_thunk, 0; with_declared(0)), + "getEntries", + 0, + ), true, ); install_perf_method( proto, "getEntriesByType", perf_method_value( - perf_list_get_by_type_thunk as *const u8, + crate::fn_info!(perf_list_get_by_type_thunk, 1; with_declared(1)), "getEntriesByType", 1, ), @@ -448,7 +497,7 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( proto, "getEntriesByName", perf_method_value( - perf_list_get_by_name_thunk as *const u8, + crate::fn_info!(perf_list_get_by_name_thunk, 1; with_declared(1)), "getEntriesByName", 1, ), @@ -483,7 +532,11 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( let getter = perf_field_getter(field); install_perf_getter(proto, field, getter, true); } - let to_json = perf_method_value(perf_entry_to_json_thunk as *const u8, "toJSON", 0); + let to_json = perf_method_value( + crate::fn_info!(perf_entry_to_json_thunk, 0; with_declared(0)), + "toJSON", + 0, + ); install_perf_method(proto, "toJSON", to_json, true); install_perf_to_string_tag(proto, class_name); } @@ -500,7 +553,7 @@ pub(crate) unsafe fn attach_perf_hooks_constructor( if class_name == "PerformanceObserver" { let getter = perf_method_value( - perf_supported_entry_types_getter_thunk as *const u8, + crate::fn_info!(perf_supported_entry_types_getter_thunk, 0; with_declared(0)), "get supportedEntryTypes", 0, ); diff --git a/crates/perry-runtime/src/perf_hooks/timerify.rs b/crates/perry-runtime/src/perf_hooks/timerify.rs index 9e810fa125..28afa1a519 100644 --- a/crates/perry-runtime/src/perf_hooks/timerify.rs +++ b/crates/perry-runtime/src/perf_hooks/timerify.rs @@ -14,7 +14,7 @@ unsafe fn function_value_name(value: f64) -> String { let Some(closure) = closure_ptr_from_value(value) else { return String::new(); }; - crate::builtins::function_name_for_ptr((*closure).func_ptr as usize) + crate::builtins::function_name_for_ptr((*closure).code() as usize) .or_else(|| { let name_value = crate::closure::closure_get_dynamic_prop(closure as usize, "name"); string_of(JSValue::from_bits(name_value.to_bits())) @@ -128,7 +128,10 @@ extern "C" fn perf_timerify_wrapper( let normalized = crate::promise::js_assimilate_thenable(result_handle.get_nanbox_f64()); let normalized_handle = scope.root_nanbox_f64(normalized); if crate::promise::js_value_is_promise(normalized_handle.get_nanbox_f64()) != 0 { - let listener = crate::closure::js_closure_alloc(perf_timerify_settle as *const u8, 4); + let listener = crate::closure::js_closure_alloc( + crate::fn_info!(perf_timerify_settle, 1; with_declared(1)), + 4, + ); crate::closure::js_closure_set_capture_f64(listener, 0, name_handle.get_nanbox_f64()); crate::closure::js_closure_set_capture_f64(listener, 1, start_time); crate::closure::js_closure_set_capture_f64( @@ -174,12 +177,12 @@ pub extern "C" fn js_perf_timerify(fn_value: f64, options: f64) -> f64 { histogram = candidate; } } - TIMERIFY_WRAPPER_REGISTERED.call_once(|| { - crate::closure::js_register_closure_rest(perf_timerify_wrapper as *const u8, 0); - crate::closure::js_register_closure_arity(perf_timerify_settle as *const u8, 1); - }); + TIMERIFY_WRAPPER_REGISTERED.call_once(|| {}); let name = function_value_name(fn_value); - let closure = crate::closure::js_closure_alloc(perf_timerify_wrapper as *const u8, 3); + let closure = crate::closure::js_closure_alloc( + crate::fn_info!(perf_timerify_wrapper, 1; with_rest(0)), + 3, + ); crate::closure::js_closure_set_capture_f64(closure, 0, fn_value); let name_value = str_value(&name); crate::closure::js_closure_set_capture_f64(closure, 1, f64::from_bits(name_value.bits())); diff --git a/crates/perry-runtime/src/process.rs b/crates/perry-runtime/src/process.rs index ad1c06e3bf..8508dfc47b 100644 --- a/crates/perry-runtime/src/process.rs +++ b/crates/perry-runtime/src/process.rs @@ -299,9 +299,6 @@ pub(crate) fn module_set_field_rooted( handle.with_mut_ptr(|obj: *mut crate::object::ObjectHeader| module_set_field(obj, name, value)); } -pub(crate) type ModuleFunction1 = crate::closure::body_call::js_body_fn_ty!(a); -pub(crate) type ModuleFunction2 = crate::closure::body_call::js_body_fn_ty!(a, a); - #[derive(Clone, Copy)] pub(crate) struct ModuleLoaderHookEntry { pub(crate) id: u64, @@ -350,11 +347,14 @@ thread_local! { const { Cell::new(std::ptr::null()) }; } -pub(crate) fn module_function1(name: &str, thunk: ModuleFunction1, length: u32) -> f64 { - let func_ptr = thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); - crate::closure::js_register_closure_length(func_ptr, length); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +/// A 1-parameter module function; `info` is its body's +/// (`fn_info!(thunk, 1; with_declared(1), with_length(length))`). +pub(crate) fn module_function1( + name: &str, + info: *const crate::closure::JsFunctionInfo, + length: u32, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); let scope = crate::gc::RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(closure); closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { @@ -368,11 +368,14 @@ pub(crate) fn module_function1(name: &str, thunk: ModuleFunction1, length: u32) }) } -pub(crate) fn module_function2(name: &str, thunk: ModuleFunction2, length: u32) -> f64 { - let func_ptr = thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - crate::closure::js_register_closure_length(func_ptr, length); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +/// A 2-parameter module function; `info` is its body's +/// (`fn_info!(thunk, 2; with_declared(2), with_length(length))`). +pub(crate) fn module_function2( + name: &str, + info: *const crate::closure::JsFunctionInfo, + length: u32, +) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, length); crate::value::js_nanbox_pointer(closure as i64) diff --git a/crates/perry-runtime/src/process/env_misc.rs b/crates/perry-runtime/src/process/env_misc.rs index 88a93b3e7b..d029956e9f 100644 --- a/crates/perry-runtime/src/process/env_misc.rs +++ b/crates/perry-runtime/src/process/env_misc.rs @@ -700,7 +700,7 @@ fn schedule_warning(warning: f64, label: &str, code: &str, msg: &str, detail: &s let detail_handle = scope.root_nanbox_f64(string_value(detail)); let hint_handle = scope.root_nanbox_f64(string_value(&hint)); - let callback = js_closure_alloc(process_warning_callback as *const u8, 4); + let callback = js_closure_alloc(crate::fn_info!(process_warning_callback, 0), 4); if callback.is_null() { return; } diff --git a/crates/perry-runtime/src/process/finalization.rs b/crates/perry-runtime/src/process/finalization.rs index 93b7acf947..e50578e07c 100644 --- a/crates/perry-runtime/src/process/finalization.rs +++ b/crates/perry-runtime/src/process/finalization.rs @@ -19,9 +19,7 @@ fn process_finalization_before_exit_listener_ptr() -> *const crate::closure::Clo if !existing.is_null() { return existing; } - let func_ptr = process_finalization_before_exit_listener as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 1); - crate::closure::js_register_closure_length(func_ptr, 1); + let func_ptr = crate::fn_info!(process_finalization_before_exit_listener, 1; with_declared(1), with_length(1)); let closure = crate::closure::js_closure_alloc(func_ptr, 0); crate::object::set_bound_native_closure_name(closure, "processFinalizationBeforeExit"); crate::object::set_builtin_closure_length(closure as usize, 1); @@ -243,21 +241,29 @@ pub(crate) fn process_finalization_value() -> f64 { module_set_field( obj, "register", - module_function2("register", process_finalization_register_function, 2), + module_function2( + "register", + crate::fn_info!(process_finalization_register_function, 2; with_declared(2), with_length(2)), + 2, + ), ); module_set_field( obj, "registerBeforeExit", module_function2( "registerBeforeExit", - process_finalization_register_before_exit_function, + crate::fn_info!(process_finalization_register_before_exit_function, 2; with_declared(2), with_length(2)), 2, ), ); module_set_field( obj, "unregister", - module_function1("unregister", process_finalization_unregister_function, 1), + module_function1( + "unregister", + crate::fn_info!(process_finalization_unregister_function, 1; with_declared(1), with_length(1)), + 1, + ), ); let value = module_object_value(obj); PROCESS_FINALIZATION_OBJECT.with(|c| c.set(value)); diff --git a/crates/perry-runtime/src/process/ipc.rs b/crates/perry-runtime/src/process/ipc.rs index 2afe84db9e..4f8ea606ae 100644 --- a/crates/perry-runtime/src/process/ipc.rs +++ b/crates/perry-runtime/src/process/ipc.rs @@ -9,7 +9,7 @@ use crate::closure::{ js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, js_native_call_value, - js_register_closure_arity, js_register_closure_length, ClosureHeader, + ClosureHeader, }; use crate::string::js_string_from_bytes; #[cfg(any(unix, windows))] @@ -98,24 +98,10 @@ fn set_field(obj: *mut crate::object::ObjectHeader, name: &str, value: f64) { crate::object::js_object_set_field_by_name(obj, key, value); } -type IpcFunction0 = crate::closure::body_call::js_body_fn_ty!(); -type IpcFunction4 = crate::closure::body_call::js_body_fn_ty!(a, a, a, a); - -fn ipc_function0(name: &str, thunk: IpcFunction0, length: u32) -> f64 { - let func_ptr = thunk as *const u8; - js_register_closure_arity(func_ptr, 0); - js_register_closure_length(func_ptr, length); - let closure = js_closure_alloc(func_ptr, 0); - crate::object::set_bound_native_closure_name(closure, name); - crate::object::set_builtin_closure_length(closure as usize, length); - crate::value::js_nanbox_pointer(closure as i64) -} - -fn ipc_function4(name: &str, thunk: IpcFunction4, length: u32) -> f64 { - let func_ptr = thunk as *const u8; - js_register_closure_arity(func_ptr, 4); - js_register_closure_length(func_ptr, length); - let closure = js_closure_alloc(func_ptr, 0); +/// A process IPC function value; `info` records the body's declared arity and +/// `.length`. +fn ipc_function(name: &str, info: *const crate::closure::JsFunctionInfo, length: u32) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, length); crate::value::js_nanbox_pointer(closure as i64) @@ -347,14 +333,22 @@ pub(crate) fn process_ipc_property(name: &str) -> Option { Some(match name { "send" => { if available { - ipc_function4("send", process_ipc_send_fn, 4) + ipc_function( + "send", + crate::fn_info!(process_ipc_send_fn, 4; with_declared(4), with_length(4)), + 4, + ) } else { undefined_value() } } "disconnect" => { if available { - ipc_function0("disconnect", process_ipc_disconnect_fn, 0) + ipc_function( + "disconnect", + crate::fn_info!(process_ipc_disconnect_fn, 0; with_declared(0), with_length(0)), + 0, + ) } else { undefined_value() } @@ -381,11 +375,23 @@ pub(crate) fn process_ipc_property(name: &str) -> Option { fn process_channel_value() -> f64 { let obj = crate::object::js_object_alloc(0, 2); - set_field(obj, "ref", ipc_function0("ref", process_channel_ref_fn, 0)); + set_field( + obj, + "ref", + ipc_function( + "ref", + crate::fn_info!(process_channel_ref_fn, 0; with_declared(0), with_length(0)), + 0, + ), + ); set_field( obj, "unref", - ipc_function0("unref", process_channel_unref_fn, 0), + ipc_function( + "unref", + crate::fn_info!(process_channel_unref_fn, 0; with_declared(0), with_length(0)), + 0, + ), ); object_value(obj) } @@ -562,9 +568,8 @@ fn json_frame(message: f64) -> Option> { } fn defer_send_callback(cb: f64, ok: bool) { - let func_ptr = process_ipc_send_callback_thunk as *const u8; - js_register_closure_arity(func_ptr, 0); - js_register_closure_length(func_ptr, 0); + let func_ptr = + crate::fn_info!(process_ipc_send_callback_thunk, 0; with_declared(0), with_length(0)); let deferred = js_closure_alloc(func_ptr, 2); js_closure_set_capture_ptr(deferred, 0, cb.to_bits() as i64); let flag = if ok { TAG_TRUE } else { TAG_FALSE }; diff --git a/crates/perry-runtime/src/process/node_module.rs b/crates/perry-runtime/src/process/node_module.rs index 65819a941d..fe64aaad69 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -170,7 +170,11 @@ pub extern "C" fn js_module_module_new(id: f64, parent: f64) -> f64 { module_null().to_bits() }), ); - let require = scope.root_nanbox_f64(module_function1("require", module_require_thunk, 1)); + let require = scope.root_nanbox_f64(module_function1( + "require", + crate::fn_info!(module_require_thunk, 1; with_declared(1), with_length(1)), + 1, + )); crate::object::js_object_set_field( obj_ptr(), 7, @@ -893,9 +897,7 @@ extern "C" fn module_hooks_deregister( } fn module_hooks_deregister_function(id: u64) -> f64 { - let func_ptr = module_hooks_deregister as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); - crate::closure::js_register_closure_length(func_ptr, 0); + let func_ptr = crate::fn_info!(module_hooks_deregister, 0; with_declared(0), with_length(0)); let closure = crate::closure::js_closure_alloc(func_ptr, 1); js_closure_set_capture_f64(closure, 0, id as f64); crate::object::set_bound_native_closure_name(closure, "deregister"); @@ -994,17 +996,14 @@ extern "C" fn module_loader_next_load( fn module_loader_callback( slot: &'static std::thread::LocalKey>, name: &str, - func: crate::closure::body_call::js_body_fn_ty!(a, a), + func: *const crate::closure::JsFunctionInfo, ) -> f64 { let ptr = slot.with(|cell| { let existing = cell.get(); if !existing.is_null() { return existing; } - let func_ptr = func as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - crate::closure::js_register_closure_length(func_ptr, 2); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(func, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 2); cell.set(closure); @@ -1060,9 +1059,7 @@ fn module_loader_hook_chain_function(callback: f64, next: f64, name: &str) -> f6 let scope = crate::gc::RuntimeHandleScope::new(); let callback = scope.root_nanbox_f64(callback); let next = scope.root_nanbox_f64(next); - let func_ptr = module_loader_hook_chain as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 2); - crate::closure::js_register_closure_length(func_ptr, 2); + let func_ptr = crate::fn_info!(module_loader_hook_chain, 2; with_declared(2), with_length(2)); let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(func_ptr, 2)); closure.with_mut_ptr(|c: *mut crate::closure::ClosureHeader| { js_closure_set_capture_f64(c, 0, callback.get_nanbox_f64()); @@ -1129,7 +1126,7 @@ pub extern "C" fn js_module_dynamic_import_apply_hooks(specifier: f64) -> f64 { let resolve_terminal = module_loader_callback( &MODULE_LOADER_NEXT_RESOLVE, "nextResolve", - module_loader_next_resolve, + crate::fn_info!(module_loader_next_resolve, 2; with_declared(2), with_length(2)), ); let resolve_chain = scope.root_nanbox_f64(module_loader_build_hook_chain( &entries, @@ -1154,7 +1151,7 @@ pub extern "C" fn js_module_dynamic_import_apply_hooks(specifier: f64) -> f64 { let load_terminal = module_loader_callback( &MODULE_LOADER_NEXT_LOAD, "nextLoad", - module_loader_next_load, + crate::fn_info!(module_loader_next_load, 2; with_declared(2), with_length(2)), ); let load_chain = scope.root_nanbox_f64(module_loader_build_hook_chain( &entries, diff --git a/crates/perry-runtime/src/process/node_module/source_map.rs b/crates/perry-runtime/src/process/node_module/source_map.rs index 79903ac512..c65cf29eb4 100644 --- a/crates/perry-runtime/src/process/node_module/source_map.rs +++ b/crates/perry-runtime/src/process/node_module/source_map.rs @@ -85,12 +85,8 @@ pub extern "C" fn js_module_source_map_new(payload: f64, options: f64) -> f64 { obj.with_mut_ptr(|obj: *mut crate::object::ObjectHeader| module_object_value(obj)) } -type SourceMapThunk = crate::closure::body_call::js_body_fn_ty!(a); - -fn source_map_method(name: &str, thunk: SourceMapThunk) -> f64 { - let func_ptr = thunk as *const u8; - let closure = js_closure_alloc(func_ptr, 0); - crate::closure::js_register_closure_rest(func_ptr, 0); +fn source_map_method(name: &str, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 2); crate::object::set_builtin_closure_non_constructable(closure as usize); @@ -134,10 +130,8 @@ extern "C" fn source_map_line_lengths_getter( f64::from_bits(JSValue::array_ptr(cloned).bits()) } -fn source_map_getter(name: &str, thunk: crate::closure::body_call::js_body_fn_ty!()) -> f64 { - let func_ptr = thunk as *const u8; - crate::closure::js_register_closure_arity(func_ptr, 0); - let closure = js_closure_alloc(func_ptr, 0); +fn source_map_getter(name: &str, info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, &format!("get {name}")); crate::object::set_builtin_closure_length(closure as usize, 0); crate::object::set_builtin_closure_non_constructable(closure as usize); @@ -173,19 +167,19 @@ fn source_map_prototype() -> f64 { 0, JSValue::from_bits(module_undefined().to_bits()), ); - for (index, name, thunk) in [ + for (index, name, info) in [ ( 1, "findEntry", - source_map_find_entry_thunk as SourceMapThunk, + crate::fn_info!(source_map_find_entry_thunk, 1; with_rest(0)), ), ( 2, "findOrigin", - source_map_find_origin_thunk as SourceMapThunk, + crate::fn_info!(source_map_find_origin_thunk, 1; with_rest(0)), ), ] { - let value = source_map_method(name, thunk); + let value = source_map_method(name, info); let proto = module_object_ptr(f64::from_bits(slot.get())).expect("SourceMap prototype"); crate::object::js_object_set_field( proto as *mut _, @@ -198,19 +192,19 @@ fn source_map_prototype() -> f64 { crate::object::PropertyAttrs::new(true, false, true), ); } - for (index, name, thunk) in [ + for (index, name, info) in [ ( 4, "payload", - source_map_payload_getter as crate::closure::body_call::js_body_fn_ty!(), + crate::fn_info!(source_map_payload_getter, 0; with_declared(0)), ), ( 3, "lineLengths", - source_map_line_lengths_getter as crate::closure::body_call::js_body_fn_ty!(), + crate::fn_info!(source_map_line_lengths_getter, 0; with_declared(0)), ), ] { - let getter = source_map_getter(name, thunk); + let getter = source_map_getter(name, info); let proto = module_object_ptr(f64::from_bits(slot.get())).expect("SourceMap prototype"); crate::object::js_object_set_field( proto as *mut _, diff --git a/crates/perry-runtime/src/process/permission.rs b/crates/perry-runtime/src/process/permission.rs index 4ec02294d2..e76b057b69 100644 --- a/crates/perry-runtime/src/process/permission.rs +++ b/crates/perry-runtime/src/process/permission.rs @@ -226,12 +226,20 @@ pub(crate) fn process_permission_value() -> Option { module_set_field( obj, "has", - module_function2("has", process_permission_has_thunk, 2), + module_function2( + "has", + crate::fn_info!(process_permission_has_thunk, 2; with_declared(2), with_length(2)), + 2, + ), ); module_set_field( obj, "drop", - module_function2("drop", process_permission_drop_thunk, 2), + module_function2( + "drop", + crate::fn_info!(process_permission_drop_thunk, 2; with_declared(2), with_length(2)), + 2, + ), ); let value = module_object_value(obj); CACHED_PERMISSION.with(|c| c.set(value)); diff --git a/crates/perry-runtime/src/process/report.rs b/crates/perry-runtime/src/process/report.rs index 1c5421c675..2bb0c97c8b 100644 --- a/crates/perry-runtime/src/process/report.rs +++ b/crates/perry-runtime/src/process/report.rs @@ -148,7 +148,11 @@ fn process_report_controller_object() -> f64 { module_set_field( obj, "getReport", - module_function1("getReport", process_report_function_get_report, 1), + module_function1( + "getReport", + crate::fn_info!(process_report_function_get_report, 1; with_declared(1), with_length(1)), + 1, + ), ); module_set_field(obj, "reportOnFatalError", bool_value(false)); module_set_field(obj, "reportOnSignal", bool_value(false)); @@ -157,7 +161,11 @@ fn process_report_controller_object() -> f64 { module_set_field( obj, "writeReport", - module_function2("writeReport", process_report_function_write_report, 2), + module_function2( + "writeReport", + crate::fn_info!(process_report_function_write_report, 2; with_declared(2), with_length(2)), + 2, + ), ); module_object_value(obj) } diff --git a/crates/perry-runtime/src/promise/assimilate.rs b/crates/perry-runtime/src/promise/assimilate.rs index 91438909e1..45c927d1bc 100644 --- a/crates/perry-runtime/src/promise/assimilate.rs +++ b/crates/perry-runtime/src/promise/assimilate.rs @@ -9,8 +9,7 @@ //! `assimilate_via_then_property` helper. use super::combinators::{ - callable_closure_value, combinator_catch_js, ensure_native_resolving_arity_registered, - promise_reject_fn, promise_resolve_fn, + callable_closure_value, combinator_catch_js, PROMISE_REJECT_FN_INFO, PROMISE_RESOLVE_FN_INFO, }; use super::*; @@ -132,7 +131,7 @@ pub(super) fn enqueue_thenable_job(promise: *mut Promise, thenable: f64, then_ac js_closure_alloc, js_closure_set_capture_f64, js_closure_set_capture_ptr, }; - let callback = js_closure_alloc(promise_resolve_thenable_job as *const u8, 3); + let callback = js_closure_alloc(crate::fn_info!(promise_resolve_thenable_job, 0), 3); js_closure_set_capture_ptr(callback, 0, promise as i64); js_closure_set_capture_f64(callback, 1, thenable); js_closure_set_capture_f64(callback, 2, then_action); @@ -225,7 +224,7 @@ pub(super) fn enqueue_native_adoption_job(outer: *mut Promise, inner: *mut Promi // rejections internally) and can crash it. crate::promise::mark_rejection_handled(inner); - let callback = js_closure_alloc(native_promise_adoption_job as *const u8, 2); + let callback = js_closure_alloc(crate::fn_info!(native_promise_adoption_job, 0), 2); js_closure_set_capture_ptr(callback, 0, outer as i64); js_closure_set_capture_ptr(callback, 1, inner as i64); @@ -318,6 +317,16 @@ fn thenable_job_take_guard(guard_arr: *mut crate::array::ArrayHeader) -> bool { true } +/// `thenable_job_resolve_fn`'s info: an anonymous built-in resolving function (ECMA-262 +/// 27.2.1.3) — declared 1, so a zero-argument call pads to `undefined`, and +/// no `[[Construct]]`. +pub(super) static THENABLE_JOB_RESOLVE_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + thenable_job_resolve_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + pub(super) extern "C" fn thenable_job_resolve_fn( closure: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, @@ -333,6 +342,16 @@ pub(super) extern "C" fn thenable_job_resolve_fn( 0.0 } +/// `thenable_job_reject_fn`'s info: an anonymous built-in resolving function (ECMA-262 +/// 27.2.1.3) — declared 1, so a zero-argument call pads to `undefined`, and +/// no `[[Construct]]`. +pub(super) static THENABLE_JOB_REJECT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + thenable_job_reject_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + pub(super) extern "C" fn thenable_job_reject_fn( closure: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, @@ -369,20 +388,18 @@ extern "C" fn promise_resolve_thenable_job( return 0.0; } // The resolve/reject closures below may be invoked with zero arguments by - // the thenable's `then`; ensure their dispatch arity is registered so the - // missing value pads to `undefined`. - ensure_native_resolving_arity_registered(); - + // the thenable's `then`; their infos declare 1, so the missing value pads + // to `undefined`. let guard_arr = js_array_alloc(1); unsafe { (*guard_arr).length = 1; } js_array_set_f64(guard_arr, 0, 0.0); - let resolve_closure = js_closure_alloc(thenable_job_resolve_fn as *const u8, 2); + let resolve_closure = js_closure_alloc(&THENABLE_JOB_RESOLVE_FN_INFO, 2); js_closure_set_capture_ptr(resolve_closure, 0, promise as i64); js_closure_set_capture_ptr(resolve_closure, 1, guard_arr as i64); - let reject_closure = js_closure_alloc(thenable_job_reject_fn as *const u8, 2); + let reject_closure = js_closure_alloc(&THENABLE_JOB_REJECT_FN_INFO, 2); js_closure_set_capture_ptr(reject_closure, 0, promise as i64); js_closure_set_capture_ptr(reject_closure, 1, guard_arr as i64); @@ -458,7 +475,7 @@ pub(super) fn assimilate_via_then_property(value: f64) -> f64 { let promise_handle = scope.root_raw_mut_ptr(js_promise_new()); let resolve_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - promise_resolve_fn as *const u8, + &PROMISE_RESOLVE_FN_INFO, 1, )); resolve_handle.with_mut_ptr(|resolve| { @@ -466,10 +483,8 @@ pub(super) fn assimilate_via_then_property(value: f64) -> f64 { crate::closure::js_closure_set_capture_ptr(resolve, 0, promise as i64); }) }); - let reject_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - promise_reject_fn as *const u8, - 1, - )); + let reject_handle = + scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(&PROMISE_REJECT_FN_INFO, 1)); reject_handle.with_mut_ptr(|reject| { promise_handle.with_mut_ptr::(|promise| { crate::closure::js_closure_set_capture_ptr(reject, 0, promise as i64); diff --git a/crates/perry-runtime/src/promise/async_step.rs b/crates/perry-runtime/src/promise/async_step.rs index 90e890fd11..f93896af64 100644 --- a/crates/perry-runtime/src/promise/async_step.rs +++ b/crates/perry-runtime/src/promise/async_step.rs @@ -1008,12 +1008,12 @@ fn build_async_step_thunks( // Capture layout: [step_closure_ptr, activation_trap_next_ptr, // stable_box_activation_ptr, generation]. Token storage is never freed; // pointer + generation rejects a capture after the token is recycled. - let fulfill = js_closure_alloc(async_step_fulfill_thunk as *const u8, 4); + let fulfill = js_closure_alloc(crate::fn_info!(async_step_fulfill_thunk, 1), 4); js_closure_set_capture_ptr(fulfill, 0, step_closure as i64); js_closure_set_capture_ptr(fulfill, 1, trap_next as i64); js_closure_set_capture_ptr(fulfill, 2, box_activation as i64); js_closure_set_capture_f64(fulfill, 3, box_activation_id as f64); - let reject = js_closure_alloc(async_step_reject_thunk as *const u8, 4); + let reject = js_closure_alloc(crate::fn_info!(async_step_reject_thunk, 1), 4); js_closure_set_capture_ptr(reject, 0, step_closure as i64); js_closure_set_capture_ptr(reject, 1, trap_next as i64); js_closure_set_capture_ptr(reject, 2, box_activation as i64); @@ -1314,10 +1314,13 @@ pub extern "C" fn js_array_from_async(input: f64, map_fn: f64, this_arg: f64) -> (*result_arr).length = 0; } - let state = js_closure_alloc(array_from_async_step as *const u8, AFA_STATE_CAPTURES); - let reject_closure = js_closure_alloc(array_from_async_reject as *const u8, 1); - let value_closure = js_closure_alloc(array_from_async_value_step as *const u8, 1); - let mapped_closure = js_closure_alloc(array_from_async_mapped_step as *const u8, 1); + let state = js_closure_alloc( + crate::fn_info!(array_from_async_step, 1), + AFA_STATE_CAPTURES, + ); + let reject_closure = js_closure_alloc(crate::fn_info!(array_from_async_reject, 1), 1); + let value_closure = js_closure_alloc(crate::fn_info!(array_from_async_value_step, 1), 1); + let mapped_closure = js_closure_alloc(crate::fn_info!(array_from_async_mapped_step, 1), 1); js_closure_set_capture_ptr(reject_closure, 0, result_promise as i64); js_closure_set_capture_ptr(value_closure, 0, state as i64); @@ -1674,12 +1677,13 @@ mod tests { let previous_from = js_promise_new(); let previous_to = copy_promise_to_old(previous_from); let previous_step_from = - crate::closure::js_closure_alloc(relocating_step as *const u8, 0); + crate::closure::js_closure_alloc(crate::fn_info!(relocating_step, 2), 0); let previous_step_to = copy_closure_to_old(previous_step_from); let rejected = js_promise_rejected(73.0); - let step = crate::closure::js_closure_alloc(relocating_step as *const u8, 0); - let thunk = crate::closure::js_closure_alloc(async_step_fulfill_thunk as *const u8, 4); + let step = crate::closure::js_closure_alloc(crate::fn_info!(relocating_step, 2), 0); + let thunk = + crate::closure::js_closure_alloc(crate::fn_info!(async_step_fulfill_thunk, 1), 4); crate::closure::js_closure_set_capture_ptr(thunk, 0, step as i64); crate::closure::js_closure_set_capture_ptr(thunk, 1, captured_from as i64); crate::closure::js_closure_set_capture_ptr(thunk, 2, 0); diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index cf2a9d8c49..a1b9d8b063 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -8,7 +8,7 @@ use super::keyed_table::PromiseKeyedTable; use super::assimilate::{ assimilate_via_then_property, enqueue_thenable_job, get_then_action, - promise_resolve_assimilating, thenable_job_reject_fn, thenable_job_resolve_fn, + promise_resolve_assimilating, }; #[derive(Clone, Copy)] @@ -771,43 +771,6 @@ fn take_already_resolved(guard: *mut crate::array::ArrayHeader) -> bool { true } -/// Register the dispatch arity (= 1) of the native resolving / thenable-job -/// functions, once per thread. Each is an `extern "C" fn(closure, value)`, so a -/// JS call that passes FEWER arguments — a thenable whose `then` does -/// `resolve()` with no args (27.2.1.3.2 step 9) — must pad the missing `value` -/// to `undefined`. Without a registered arity, `js_closure_call0` falls to the -/// zero-arg direct-call arm and the function reads a GARBAGE second register -/// (observed as the denormal `5e-324`, i.e. bits = 1), corrupting the -/// resolution value. (test262 exception-after-resolve-in-{executor,thenable-job}.) -/// -/// #10521: the same registration carries every spec-visible fact of these -/// anonymous built-in functions (27.2.1.3), keyed by function KIND rather than -/// by closure. The registered arity is the own `length` 1 (`closure_length` -/// falls back to it), the unregistered func-ptr name reads back as the own -/// `name` `""`, and the non-constructor bit makes `new resolve()` throw. The -/// descriptor defaults for an entry-less closure are already -/// `{writable: false, enumerable: false, configurable: true}` for both keys. -pub(super) fn ensure_native_resolving_arity_registered() { - crate::perry_thread_local! { - static DONE: std::cell::Cell = const { std::cell::Cell::new(false) }; - } - DONE.with(|d| { - if d.get() { - return; - } - d.set(true); - for f in [ - promise_resolve_fn as *const u8, - promise_reject_fn as *const u8, - thenable_job_resolve_fn as *const u8, - thenable_job_reject_fn as *const u8, - ] { - crate::closure::js_register_closure_arity(f, 1); - crate::closure::register_closure_body_non_constructor(f); - } - }); -} - /// Wire a resolve/reject closure pair to a promise with a shared /// `[[AlreadyResolved]]` guard. Returns `(resolve_closure, reject_closure)`. pub(super) fn make_resolving_functions( @@ -821,23 +784,17 @@ pub(super) fn make_resolving_functions( // capture slots after them. Pre-fix all three lived in bare Rust locals, so // a copying minor here wrote pre-collection addresses into the two closures // — the from-space-publishing shape, not merely a stale read. - // - // #9587: `ensure_native_resolving_arity_registered` registers four closure - // arities and so can allocate on its first call per thread — root `promise` - // BEFORE it, not after, or the very first `new Promise(executor)` on a - // thread can publish a pre-collection address into the capture slots. let scope = crate::gc::RuntimeHandleScope::new(); let promise_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(promise as i64)); - ensure_native_resolving_arity_registered(); let guard_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer( alloc_already_resolved_guard() as i64, )); let resolve_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - promise_resolve_fn as *const u8, + &PROMISE_RESOLVE_FN_INFO, 2, ) as i64)); let reject_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - promise_reject_fn as *const u8, + &PROMISE_REJECT_FN_INFO, 2, ) as i64)); let ptr_of = |h: &crate::gc::RuntimeHandle<'_>| -> i64 { @@ -853,8 +810,8 @@ pub(super) fn make_resolving_functions( // configurable), and NO `[[Construct]]` (`new resolve()` throws). test262 // `resolve-function-*` / `reject-function-*` assert all four. // - // #10521: all four come from the function kind registered by - // `ensure_native_resolving_arity_registered` above, so nothing is + // #10521: all four come from the bodies' static infos + // (`PROMISE_RESOLVE_FN_INFO` / `PROMISE_REJECT_FN_INFO`), so nothing is // installed per closure. Each pair used to get a `name` string, a closure // dynamic-prop entry, a descriptor-table entry (whose install bumped the // global property-plan epoch, invalidating the `then`-probe verdict and @@ -867,6 +824,16 @@ pub(super) fn make_resolving_functions( ) } +/// `promise_resolve_fn`'s info: an anonymous built-in resolving function (ECMA-262 +/// 27.2.1.3) — declared 1, so a zero-argument call pads to `undefined`, and +/// no `[[Construct]]`. +pub(super) static PROMISE_RESOLVE_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + promise_resolve_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Internal resolve function for Promise executor callbacks. /// Called when user calls resolve(value) inside the executor. /// @@ -911,6 +878,16 @@ pub(super) extern "C" fn promise_resolve_fn( undef // resolve returns undefined } +/// `promise_reject_fn`'s info: an anonymous built-in resolving function (ECMA-262 +/// 27.2.1.3) — declared 1, so a zero-argument call pads to `undefined`, and +/// no `[[Construct]]`. +pub(super) static PROMISE_REJECT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + promise_reject_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Internal reject function for Promise executor callbacks. /// Called when user calls reject(reason) inside the executor. pub(super) extern "C" fn promise_reject_fn( @@ -1074,11 +1051,11 @@ fn attach_promise_all_after_prior_reaction(promise: *mut Promise, state: Promise scope.root_nanbox_f64(crate::value::js_nanbox_pointer(state.results_arr as i64)); let state_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(state.state_arr as i64)); let fulfill_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - promise_all_ordered_fulfill_handler as *const u8, + crate::fn_info!(promise_all_ordered_fulfill_handler, 1), 4, ) as i64)); let reject_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - promise_all_ordered_reject_handler as *const u8, + crate::fn_info!(promise_all_ordered_reject_handler, 1), 2, ) as i64)); @@ -1195,9 +1172,9 @@ pub extern "C" fn js_promise_race(promises_arr: *const crate::array::ArrayHeader // Both handlers capture only `result_promise` and don't depend on // the input index — so allocate once and share across all N inputs. // Saves (N-1) × 2 closure allocs per Promise.race call. - let shared_resolve = js_closure_alloc(promise_race_resolve_handler as *const u8, 1); + let shared_resolve = js_closure_alloc(crate::fn_info!(promise_race_resolve_handler, 1), 1); js_closure_set_capture_ptr(shared_resolve, 0, result_promise as i64); - let shared_reject = js_closure_alloc(promise_race_reject_handler as *const u8, 1); + let shared_reject = js_closure_alloc(crate::fn_info!(promise_race_reject_handler, 1), 1); js_closure_set_capture_ptr(shared_reject, 0, result_promise as i64); // Normalize each input with PromiseResolve semantics before attaching handlers. @@ -1375,7 +1352,7 @@ pub extern "C" fn js_assimilate_thenable(value: f64) -> f64 { let promise_handle = scope.root_raw_mut_ptr(js_promise_new()); let resolve_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - promise_resolve_fn as *const u8, + &PROMISE_RESOLVE_FN_INFO, 1, )); resolve_handle.with_mut_ptr(|resolve| { @@ -1383,10 +1360,8 @@ pub extern "C" fn js_assimilate_thenable(value: f64) -> f64 { crate::closure::js_closure_set_capture_ptr(resolve, 0, promise as i64); }) }); - let reject_handle = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - promise_reject_fn as *const u8, - 1, - )); + let reject_handle = + scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(&PROMISE_REJECT_FN_INFO, 1)); reject_handle.with_mut_ptr(|reject| { promise_handle.with_mut_ptr::(|promise| { crate::closure::js_closure_set_capture_ptr(reject, 0, promise as i64); @@ -1533,14 +1508,16 @@ pub extern "C" fn js_promise_all_settled( }; // Fulfill: store {status:"fulfilled", value:v} - let fulfill_closure = js_closure_alloc(promise_all_settled_fulfill_handler as *const u8, 4); + let fulfill_closure = + js_closure_alloc(crate::fn_info!(promise_all_settled_fulfill_handler, 1), 4); js_closure_set_capture_ptr(fulfill_closure, 0, result_promise as i64); js_closure_set_capture_ptr(fulfill_closure, 1, results_arr as i64); js_closure_set_capture_ptr(fulfill_closure, 2, state_arr as i64); js_closure_set_capture_f64(fulfill_closure, 3, i as f64); // Reject: store {status:"rejected", reason:r} - let reject_closure = js_closure_alloc(promise_all_settled_reject_handler as *const u8, 4); + let reject_closure = + js_closure_alloc(crate::fn_info!(promise_all_settled_reject_handler, 1), 4); js_closure_set_capture_ptr(reject_closure, 0, result_promise as i64); js_closure_set_capture_ptr(reject_closure, 1, results_arr as i64); js_closure_set_capture_ptr(reject_closure, 2, state_arr as i64); @@ -1673,7 +1650,7 @@ pub extern "C" fn js_promise_any(promises_arr: *const crate::array::ArrayHeader) // the Promise.all reject-closure sharing in commit 7c89fcc6). // Reject still needs per-index since it must write its error into // the correct slot of `errors_arr` for the eventual AggregateError. - let shared_fulfill = js_closure_alloc(promise_any_fulfill_handler as *const u8, 2); + let shared_fulfill = js_closure_alloc(crate::fn_info!(promise_any_fulfill_handler, 1), 2); js_closure_set_capture_ptr(shared_fulfill, 0, result_promise as i64); js_closure_set_capture_ptr(shared_fulfill, 1, state_arr as i64); @@ -1683,7 +1660,7 @@ pub extern "C" fn js_promise_any(promises_arr: *const crate::array::ArrayHeader) Err(reason) => js_promise_rejected(reason), }; - let reject_closure = js_closure_alloc(promise_any_reject_handler as *const u8, 4); + let reject_closure = js_closure_alloc(crate::fn_info!(promise_any_reject_handler, 1), 4); js_closure_set_capture_ptr(reject_closure, 0, result_promise as i64); js_closure_set_capture_ptr(reject_closure, 1, errors_arr as i64); js_closure_set_capture_ptr(reject_closure, 2, state_arr as i64); diff --git a/crates/perry-runtime/src/promise/combinators_tests.rs b/crates/perry-runtime/src/promise/combinators_tests.rs index 718755fa76..1f981d4e48 100644 --- a/crates/perry-runtime/src/promise/combinators_tests.rs +++ b/crates/perry-runtime/src/promise/combinators_tests.rs @@ -10,7 +10,7 @@ fn reset_promise_test_state() { PROMISE_ALL_STATES.with(|s| s.borrow_mut().clear()); } -fn thenable_value(func: *const u8, captured: f64) -> f64 { +fn thenable_value(func: *const crate::closure::JsFunctionInfo, captured: f64) -> f64 { let obj = js_object_alloc(0, 0); let then = js_closure_alloc(func, 1); js_closure_set_capture_f64(then, 0, captured); @@ -114,7 +114,7 @@ fn promise_all_assimilates_thenable_and_guards_double_resolve() { js_array_set_f64( arr, 0, - thenable_value(test_thenable_resolve_twice as *const u8, 7.0), + thenable_value(crate::fn_info!(test_thenable_resolve_twice, 2), 7.0), ); let all = js_promise_all(arr); @@ -138,7 +138,7 @@ fn promise_all_rejects_from_thenable_job() { js_array_set_f64( arr, 0, - thenable_value(test_thenable_reject as *const u8, 13.0), + thenable_value(crate::fn_info!(test_thenable_reject, 2), 13.0), ); let all = js_promise_all(arr); @@ -160,12 +160,12 @@ fn promise_all_settled_assimilates_thenables_in_input_order() { js_array_set_f64( arr, 0, - thenable_value(test_thenable_reject as *const u8, 1.0), + thenable_value(crate::fn_info!(test_thenable_reject, 2), 1.0), ); js_array_set_f64( arr, 1, - thenable_value(test_thenable_resolve_twice as *const u8, 2.0), + thenable_value(crate::fn_info!(test_thenable_resolve_twice, 2), 2.0), ); let settled = js_promise_all_settled(arr); @@ -192,7 +192,7 @@ fn promise_result_resolution_assimilates_thenable_objects() { let promise = js_promise_new(); promise_resolve_assimilating( promise, - thenable_value(test_thenable_resolve_twice as *const u8, 21.0), + thenable_value(crate::fn_info!(test_thenable_resolve_twice, 2), 21.0), ); assert_eq!((*promise).state, PromiseState::Pending); diff --git a/crates/perry-runtime/src/promise/microtasks.rs b/crates/perry-runtime/src/promise/microtasks.rs index ad6dc7291e..1d25eeb610 100644 --- a/crates/perry-runtime/src/promise/microtasks.rs +++ b/crates/perry-runtime/src/promise/microtasks.rs @@ -919,7 +919,7 @@ fn pump_protected(mode: MicrotaskDrainMode, reentrant: bool, landed: bool, ran: // but AFTER those calls preserves an address that is already // stale, which is what the first attempt at this fix did: // the instrument still faulted at `call_async_step_direct`'s - // `(*step_closure).func_ptr`, on a value re-read from a + // `(*step_closure).code()`, on a value re-read from a // handle that had been seeded too late. let trap_scope = crate::gc::RuntimeHandleScope::new(); let step_handle = trap_scope.root_nanbox_f64(boxed_closure(step_closure)); @@ -1195,7 +1195,7 @@ fn call_async_step_direct( // closure strategy lookup for every await continuation; direct-call // the stored function pointer instead. unsafe { - let func_ptr = (*step_closure).func_ptr; + let func_ptr = (*step_closure).code(); crate::closure::body_call::js_body_call!( func_ptr, step_closure, diff --git a/crates/perry-runtime/src/promise/microtasks/empty.rs b/crates/perry-runtime/src/promise/microtasks/empty.rs index 0072a167a9..e6dd46c68e 100644 --- a/crates/perry-runtime/src/promise/microtasks/empty.rs +++ b/crates/perry-runtime/src/promise/microtasks/empty.rs @@ -86,8 +86,8 @@ mod tests { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } - crate::closure::js_register_closure_arity(callback as *const u8, 0); - let callback = crate::closure::js_closure_alloc(callback as *const u8, 0); + + let callback = crate::closure::js_closure_alloc(crate::fn_info!(callback, 0), 0); crate::builtins::js_queue_microtask(callback as i64); assert!(!can_skip_callback_phases()); assert!(js_promise_run_microtasks_event_loop() > 0); @@ -192,8 +192,8 @@ mod tests { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } - crate::closure::js_register_closure_arity(callback as *const u8, 1); - let callback = crate::closure::js_closure_alloc(callback as *const u8, 0); + + let callback = crate::closure::js_closure_alloc(crate::fn_info!(callback, 1), 0); crate::os::test_set_stdin_data_listener(Some(callback as i64)); crate::os::stdin_push_bytes(b"input"); assert!(!crate::timer::timer_phase_work_pending()); @@ -251,8 +251,8 @@ mod tests { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } - crate::closure::js_register_closure_arity(callback as *const u8, 0); - let callback = crate::closure::js_closure_alloc(callback as *const u8, 0); + + let callback = crate::closure::js_closure_alloc(crate::fn_info!(callback, 0), 0); crate::builtins::js_queue_next_tick(callback as i64); assert_eq!(js_promise_run_promise_jobs(), 0); assert_eq!(CALLED.load(Ordering::Relaxed), 0); diff --git a/crates/perry-runtime/src/promise/rejection_loop_tests.rs b/crates/perry-runtime/src/promise/rejection_loop_tests.rs index b3401d7ddc..9822e4210c 100644 --- a/crates/perry-runtime/src/promise/rejection_loop_tests.rs +++ b/crates/perry-runtime/src/promise/rejection_loop_tests.rs @@ -29,7 +29,7 @@ extern "C" fn catching_step( fn caught_rejected_awaits_have_no_iteration_limit() { let _lock = crate::gc::global_side_table_test_lock(); let scope = crate::gc::RuntimeHandleScope::new(); - let step = crate::closure::js_closure_alloc(catching_step as *const u8, 0); + let step = crate::closure::js_closure_alloc(crate::fn_info!(catching_step, 2), 0); let step_handle = scope.root_raw_const_ptr(step); let rejected = js_promise_new(); js_promise_reject(rejected, 12_050.0); diff --git a/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs b/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs index 537b58fbbf..40f26567f5 100644 --- a/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs +++ b/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs @@ -78,7 +78,7 @@ fn non_constructor_kind_is_keyed_by_body_not_blanket() { // Register the kinds, then show a closure over a different body and a // plain object are still not answered as non-constructors by it. let _ = make_resolving_functions(js_promise_new()); - let other = crate::closure::js_closure_alloc(unrelated_native_body as *const u8, 0); + let other = crate::closure::js_closure_alloc(crate::fn_info!(unrelated_native_body, 1), 0); assert!(!crate::object::builtin_closure_is_non_constructable( other as usize )); diff --git a/crates/perry-runtime/src/promise/scanners.rs b/crates/perry-runtime/src/promise/scanners.rs index ea74f91fe7..f1d1fefa2c 100644 --- a/crates/perry-runtime/src/promise/scanners.rs +++ b/crates/perry-runtime/src/promise/scanners.rs @@ -1005,14 +1005,14 @@ pub extern "C" fn js_promise_with_resolvers() -> *mut crate::object::ObjectHeade // Create resolve closure that resolves this promise. let resolve_fn = js_closure_alloc( - with_resolvers_resolve_handler as *const u8, + crate::fn_info!(with_resolvers_resolve_handler, 1), 1, // 1 capture: the promise pointer ); crate::closure::js_closure_set_capture_f64(resolve_fn, 0, promise_box); let resolve_box = crate::value::js_nanbox_pointer(resolve_fn as i64); // Create reject closure. - let reject_fn = js_closure_alloc(with_resolvers_reject_handler as *const u8, 1); + let reject_fn = js_closure_alloc(crate::fn_info!(with_resolvers_reject_handler, 1), 1); crate::closure::js_closure_set_capture_f64(reject_fn, 0, promise_box); let reject_box = crate::value::js_nanbox_pointer(reject_fn as i64); diff --git a/crates/perry-runtime/src/promise/spec_combinators.rs b/crates/perry-runtime/src/promise/spec_combinators.rs index a1712ae1c7..3d79fa861d 100644 --- a/crates/perry-runtime/src/promise/spec_combinators.rs +++ b/crates/perry-runtime/src/promise/spec_combinators.rs @@ -89,29 +89,6 @@ pub(super) struct Capability { // a per-instance `length` or non-constructable entry. // --------------------------------------------------------------------------- -crate::perry_thread_local! { - static ARITY_REGISTERED: std::cell::Cell = const { std::cell::Cell::new(false) }; -} - -fn ensure_arity_registered() { - ARITY_REGISTERED.with(|done| { - if done.get() { - return; - } - done.set(true); - for (f, arity) in [ - (capability_executor_fn as *const u8, 2), - (all_resolve_element_fn as *const u8, 1), - (settled_fulfill_element_fn as *const u8, 1), - (settled_reject_element_fn as *const u8, 1), - (any_reject_element_fn as *const u8, 1), - ] { - crate::closure::js_register_closure_arity(f, arity); - crate::closure::register_closure_body_non_constructor(f); - } - }); -} - // --------------------------------------------------------------------------- // TypeError helpers // --------------------------------------------------------------------------- @@ -162,6 +139,15 @@ fn is_callable(value: f64) -> bool { // NewPromiseCapability(C) // --------------------------------------------------------------------------- +/// `capability_executor_fn`'s info: the anonymous built-in `GetCapabilitiesExecutor` +/// function — declared 2, no `[[Construct]]` (#10521). +static CAPABILITY_EXECUTOR_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + capability_executor_fn as crate::codegen_abi::JsBody2, + ) + .with_declared(2) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// `GetCapabilitiesExecutor` function. Captures the 2-slot capability storage /// array (slot0 = resolve, slot1 = reject, both init `undefined`). Throws a /// TypeError if either slot is already set (executor called twice). @@ -198,9 +184,6 @@ pub(super) fn new_promise_capability(c: f64) -> Capability { // helper and re-read every address at its point of use. let scope = crate::gc::RuntimeHandleScope::new(); let ctor_h = scope.root_nanbox_f64(c); - // The generic path's executor takes its `length` and non-constructor bit - // from this registration (#10521), whichever entry point got here. - ensure_arity_registered(); if !crate::object::js_value_is_constructor(ctor_h.get_nanbox_f64()) { throw_type_error("Promise.all called on non-constructor"); @@ -232,10 +215,8 @@ pub(super) fn new_promise_capability(c: f64) -> Capability { js_array_set_f64(storage, 1, undef()); let storage_h = scope.root_nanbox_f64(boxed_ptr(storage)); - let executor_h = scope.root_nanbox_f64(boxed_ptr(js_closure_alloc( - capability_executor_fn as *const u8, - 1, - ))); + let executor_h = + scope.root_nanbox_f64(boxed_ptr(js_closure_alloc(&CAPABILITY_EXECUTOR_FN_INFO, 1))); js_closure_set_capture_ptr( unboxed_ptr(executor_h.get_nanbox_f64()), 0, @@ -342,7 +323,7 @@ fn take_already_called(guard: *mut crate::array::ArrayHeader) -> bool { #[inline] fn build_element_closure( - func: *const u8, + info: *const crate::closure::JsFunctionInfo, guard: *mut crate::array::ArrayHeader, index: u32, values: *mut crate::array::ArrayHeader, @@ -363,7 +344,7 @@ fn build_element_closure( let cap_resolve_h = scope.root_nanbox_f64(cap_resolve); let cap_reject_h = scope.root_nanbox_f64(cap_reject); - let closure_h = scope.root_nanbox_f64(boxed_ptr(js_closure_alloc(func, 6))); + let closure_h = scope.root_nanbox_f64(boxed_ptr(js_closure_alloc(info, 6))); let c: *mut crate::closure::ClosureHeader = unboxed_ptr(closure_h.get_nanbox_f64()); js_closure_set_capture_ptr(c, 0, unboxed_ptr::(guard_h.get_nanbox_f64()) as i64); js_closure_set_capture_f64(c, 1, index as f64); @@ -373,8 +354,8 @@ fn build_element_closure( js_closure_set_capture_f64(c, 5, cap_reject_h.get_nanbox_f64()); // Spec: the resolve/reject element functions are anonymous built-in // functions with `length` 1 and are NOT constructors, so - // `new resolveElement()` throws. Both facts come with the function kind - // (`ensure_arity_registered`, #10521), not a per-element entry. + // `new resolveElement()` throws. Both facts come with the body's static + // info (#10521), not a per-element entry. unboxed_ptr(closure_h.get_nanbox_f64()) } @@ -386,6 +367,15 @@ fn dec_remaining(state: *mut crate::array::ArrayHeader) -> bool { remaining == 0.0 } +/// `all_resolve_element_fn`'s info: an anonymous built-in element function — +/// declared 1, no `[[Construct]]` (#10521). +static ALL_RESOLVE_ELEMENT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + all_resolve_element_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Promise.all Resolve Element Function. extern "C" fn all_resolve_element_fn( closure: *const crate::closure::ClosureHeader, @@ -409,6 +399,15 @@ extern "C" fn all_resolve_element_fn( undef() } +/// `settled_fulfill_element_fn`'s info: an anonymous built-in element function — +/// declared 1, no `[[Construct]]` (#10521). +static SETTLED_FULFILL_ELEMENT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + settled_fulfill_element_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Promise.allSettled Resolve Element Function → `{status:"fulfilled", value}`. extern "C" fn settled_fulfill_element_fn( closure: *const crate::closure::ClosureHeader, @@ -418,6 +417,15 @@ extern "C" fn settled_fulfill_element_fn( settled_element(closure, value, true) } +/// `settled_reject_element_fn`'s info: an anonymous built-in element function — +/// declared 1, no `[[Construct]]` (#10521). +static SETTLED_REJECT_ELEMENT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + settled_reject_element_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Promise.allSettled Reject Element Function → `{status:"rejected", reason}`. extern "C" fn settled_reject_element_fn( closure: *const crate::closure::ClosureHeader, @@ -467,6 +475,15 @@ fn settled_element( undef() } +/// `any_reject_element_fn`'s info: an anonymous built-in element function — +/// declared 1, no `[[Construct]]` (#10521). +static ANY_REJECT_ELEMENT_FN_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + any_reject_element_fn as crate::codegen_abi::JsBody1, + ) + .with_declared(1) + .with_flags(crate::closure::FN_NON_CONSTRUCTOR); + /// Promise.any Reject Element Function → collect into errors array; reject with /// AggregateError once all reject. extern "C" fn any_reject_element_fn( @@ -618,7 +635,7 @@ fn is_intrinsic_promise_resolve(resolve_fn: f64) -> bool { if crate::value::addr_class::is_handle_band(raw) || !crate::closure::is_closure_ptr(raw) { return false; } - unsafe { (*(raw as *const crate::closure::ClosureHeader)).func_ptr == intrinsic } + unsafe { (*(raw as *const crate::closure::ClosureHeader)).code() == intrinsic } } /// True while any promise-lifecycle observer is installed. Re-checked per @@ -801,7 +818,7 @@ fn perform( } else { let guard_h = iter.root_nanbox_f64(boxed_ptr(new_guard())); let elem_h = iter.root_nanbox_f64(boxed_ptr(build_element_closure( - all_resolve_element_fn as *const u8, + &ALL_RESOLVE_ELEMENT_FN_INFO, unboxed_ptr(guard_h.get_nanbox_f64()), i, values_ptr(), @@ -819,7 +836,7 @@ fn perform( CombinatorKind::AllSettled => { let guard_h = iter.root_nanbox_f64(boxed_ptr(new_guard())); let on_ful_h = iter.root_nanbox_f64(boxed_ptr(build_element_closure( - settled_fulfill_element_fn as *const u8, + &SETTLED_FULFILL_ELEMENT_FN_INFO, unboxed_ptr(guard_h.get_nanbox_f64()), i, values_ptr(), @@ -828,7 +845,7 @@ fn perform( cap_reject_h.get_nanbox_f64(), ))); let on_rej_h = iter.root_nanbox_f64(boxed_ptr(build_element_closure( - settled_reject_element_fn as *const u8, + &SETTLED_REJECT_ELEMENT_FN_INFO, unboxed_ptr(guard_h.get_nanbox_f64()), i, values_ptr(), @@ -846,7 +863,7 @@ fn perform( CombinatorKind::Any => { let guard_h = iter.root_nanbox_f64(boxed_ptr(new_guard())); let on_rej_h = iter.root_nanbox_f64(boxed_ptr(build_element_closure( - any_reject_element_fn as *const u8, + &ANY_REJECT_ELEMENT_FN_INFO, unboxed_ptr(guard_h.get_nanbox_f64()), i, values_ptr(), @@ -924,8 +941,6 @@ fn new_guard() -> *mut crate::array::ArrayHeader { /// synchronously for `NewPromiseCapability` failures, otherwise returns the /// (possibly already-rejected) result promise. pub fn run_combinator(kind: CombinatorKind, c: f64, iterable: f64) -> f64 { - ensure_arity_registered(); - // #7497: `c`, `iterable` and all three capability slots stay live across // `get_promise_resolve` (a property read that can run a getter), // `combinator_iterable_to_array_caught` (a full iterator drain) and @@ -1034,7 +1049,6 @@ fn is_object_value(value: f64) -> bool { /// `Call(capability.[[Reject]], undefined, «r»)`. #[no_mangle] pub extern "C" fn js_promise_reject_spec(this_ctor: f64, reason: f64) -> f64 { - ensure_arity_registered(); // #7497: `is_default_promise_constructor` reaches `globalThis.Promise`, which // allocates a key string on every call — so neither argument may be carried // across it in a register. See `js_promise_resolve_spec`. @@ -1060,7 +1074,6 @@ pub extern "C" fn js_promise_reject_spec(this_ctor: f64, reason: f64) -> f64 { /// `Call(capability.[[Resolve]], undefined, «x»)`. #[no_mangle] pub extern "C" fn js_promise_resolve_spec(this_ctor: f64, value: f64) -> f64 { - ensure_arity_registered(); // #7497, and this is THE hot one: `Promise.all` calls this once per element, // and `is_default_promise_constructor` below reaches `globalThis.Promise` // through a lookup that allocates a fresh key string EVERY time. `value` — @@ -1108,7 +1121,6 @@ pub extern "C" fn js_promise_resolve_spec(this_ctor: f64, value: f64) -> f64 { /// capability flow (test262 `withResolvers/newpromisecapability-this-value`). #[no_mangle] pub extern "C" fn js_promise_with_resolvers_spec(this_ctor: f64) -> f64 { - ensure_arity_registered(); // #7497: see `js_promise_resolve_spec` — `is_default_promise_constructor` // allocates, and the three capability slots below outlive the record // allocation they are stored into. @@ -1154,7 +1166,6 @@ pub extern "C" fn js_promise_with_resolvers_spec(this_ctor: f64) -> f64 { /// custom constructor `this`; a non-object `this` throws. #[no_mangle] pub extern "C" fn js_promise_try_spec(this_ctor: f64, callback: f64, rest: f64) -> f64 { - ensure_arity_registered(); // #7497: see `js_promise_resolve_spec` — `is_default_promise_constructor` // allocates, and `callback` / `rest` are dereferenced after it. let scope = crate::gc::RuntimeHandleScope::new(); @@ -1249,10 +1260,18 @@ mod fast_arm_tests { crate::object::promise_static_function_spec("reject").expect("reject spec"); assert_ne!(resolve_thunk, reject_thunk); - let good = boxed_ptr(js_closure_alloc(resolve_thunk, 0)); + // The thunks are private to their module; the spec hands out only + // their code, so the test builds a bare info around each. + let info_of = |code: *const u8| -> *const crate::closure::JsFunctionInfo { + // SAFETY: both statics are one-parameter JS bodies. + Box::leak(Box::new(unsafe { + crate::closure::JsFunctionInfo::from_code(code, 1) + })) + }; + let good = boxed_ptr(js_closure_alloc(info_of(resolve_thunk), 0)); assert!(is_intrinsic_promise_resolve(good)); - let wrong_static = boxed_ptr(js_closure_alloc(reject_thunk, 0)); + let wrong_static = boxed_ptr(js_closure_alloc(info_of(reject_thunk), 0)); assert!(!is_intrinsic_promise_resolve(wrong_static)); // Non-closure values must not be probed as closures. @@ -1354,7 +1373,7 @@ mod fast_arm_tests { fn own_then_expando_forces_the_slow_arm_and_is_invoked() { reset(); let p = settled_promise(1.0); - let then = js_closure_alloc(own_then_fn as *const u8, 1); + let then = js_closure_alloc(crate::fn_info!(own_then_fn, 2), 1); js_closure_set_capture_f64(then, 0, 99.0); unsafe { crate::object::exotic_expando::exotic_set_property( diff --git a/crates/perry-runtime/src/promise/then.rs b/crates/perry-runtime/src/promise/then.rs index dbbc284cbb..fd1e8f5627 100644 --- a/crates/perry-runtime/src/promise/then.rs +++ b/crates/perry-runtime/src/promise/then.rs @@ -384,13 +384,15 @@ pub extern "C" fn js_promise_resolve_with_promise(outer: *mut Promise, inner: *m let outer_i64 = outer as i64; // Create a resolve forwarding closure - let resolve_closure = - crate::closure::js_closure_alloc(promise_forward_resolve as *const u8, 1); + let resolve_closure = crate::closure::js_closure_alloc( + crate::fn_info!(promise_forward_resolve, 1), + 1, + ); crate::closure::js_closure_set_capture_ptr(resolve_closure, 0, outer_i64); // Create a reject forwarding closure let reject_closure = - crate::closure::js_closure_alloc(promise_forward_reject as *const u8, 1); + crate::closure::js_closure_alloc(crate::fn_info!(promise_forward_reject, 1), 1); crate::closure::js_closure_set_capture_ptr(reject_closure, 0, outer_i64); // #5437 bug#2: add the forwarding callbacks as an OVERFLOW @@ -847,12 +849,12 @@ pub extern "C" fn js_promise_finally( let next_i64 = next as i64; // Build the fulfilled wrapper: captures [on_finally, next]. - let fulfill_wrap = js_closure_alloc(finally_fulfill_wrapper as *const u8, 2); + let fulfill_wrap = js_closure_alloc(crate::fn_info!(finally_fulfill_wrapper, 1), 2); js_closure_set_capture_ptr(fulfill_wrap, 0, on_finally as i64); js_closure_set_capture_ptr(fulfill_wrap, 1, next_i64); // Build the rejected wrapper: captures [on_finally, next]. - let reject_wrap = js_closure_alloc(finally_reject_wrapper as *const u8, 2); + let reject_wrap = js_closure_alloc(crate::fn_info!(finally_reject_wrapper, 1), 2); js_closure_set_capture_ptr(reject_wrap, 0, on_finally as i64); js_closure_set_capture_ptr(reject_wrap, 1, next_i64); @@ -1322,11 +1324,11 @@ fn perform_promise_then_with_cap( let cap_promise_h = scope.root_nanbox_f64(cap_promise); let ful_wrap_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - then_cap_fulfill_fn as *const u8, + crate::fn_info!(then_cap_fulfill_fn, 1; with_declared(1)), 3, ) as i64)); let rej_wrap_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_closure_alloc( - then_cap_reject_fn as *const u8, + crate::fn_info!(then_cap_reject_fn, 1; with_declared(1)), 3, ) as i64)); @@ -1405,7 +1407,10 @@ extern "C" fn spec_then_finally_fn( }; // Build valueThunk = () => value - let value_thunk_cl = js_closure_alloc(spec_value_return_fn as *const u8, 1); + let value_thunk_cl = js_closure_alloc( + crate::fn_info!(spec_value_return_fn, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(value_thunk_cl, 0, value); let value_thunk = f64::from_bits(crate::value::JSValue::pointer(value_thunk_cl as *const u8).bits()); @@ -1442,7 +1447,10 @@ extern "C" fn spec_catch_finally_fn( }; // Build thrower = () => { throw reason; } - let thrower_cl = js_closure_alloc(spec_reason_thrower_fn as *const u8, 1); + let thrower_cl = js_closure_alloc( + crate::fn_info!(spec_reason_thrower_fn, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(thrower_cl, 0, reason); let thrower = f64::from_bits(crate::value::JSValue::pointer(thrower_cl as *const u8).bits()); @@ -1452,25 +1460,6 @@ extern "C" fn spec_catch_finally_fn( call_receiver_then(c_resolved, &args) } -fn ensure_spec_finally_arities_registered() { - crate::perry_thread_local! { - static DONE: std::cell::Cell = const { std::cell::Cell::new(false) }; - } - DONE.with(|d| { - if d.get() { - return; - } - d.set(true); - use crate::closure::js_register_closure_arity; - js_register_closure_arity(then_cap_fulfill_fn as *const u8, 1); - js_register_closure_arity(then_cap_reject_fn as *const u8, 1); - js_register_closure_arity(spec_then_finally_fn as *const u8, 1); - js_register_closure_arity(spec_catch_finally_fn as *const u8, 1); - js_register_closure_arity(spec_value_return_fn as *const u8, 1); - js_register_closure_arity(spec_reason_thrower_fn as *const u8, 1); - }); -} - // --------------------------------------------------------------------------- // Promise.prototype.then (ECMA-262 §27.2.5.4) — SpeciesConstructor-aware // --------------------------------------------------------------------------- @@ -1481,7 +1470,6 @@ pub(crate) extern "C" fn promise_prototype_then_thunk( on_fulfilled: f64, on_rejected: f64, ) -> f64 { - ensure_spec_finally_arities_registered(); let receiver = this.as_f64(); let promise = if js_value_is_promise(receiver) != 0 { crate::value::js_nanbox_get_pointer(receiver) as *mut Promise @@ -1552,7 +1540,6 @@ pub(crate) extern "C" fn promise_prototype_finally_thunk( on_finally: f64, ) -> f64 { use crate::closure::{js_closure_alloc, js_closure_set_capture_f64}; - ensure_spec_finally_arities_registered(); let receiver = this.as_f64(); @@ -1569,11 +1556,17 @@ pub(crate) extern "C" fn promise_prototype_finally_thunk( (on_finally, on_finally) } else { // Build spec-compliant thenFinally and catchFinally closures. - let tf = js_closure_alloc(spec_then_finally_fn as *const u8, 2); + let tf = js_closure_alloc( + crate::fn_info!(spec_then_finally_fn, 1; with_declared(1)), + 2, + ); js_closure_set_capture_f64(tf, 0, c); js_closure_set_capture_f64(tf, 1, on_finally); - let cf = js_closure_alloc(spec_catch_finally_fn as *const u8, 2); + let cf = js_closure_alloc( + crate::fn_info!(spec_catch_finally_fn, 1; with_declared(1)), + 2, + ); js_closure_set_capture_f64(cf, 0, c); js_closure_set_capture_f64(cf, 1, on_finally); @@ -1711,7 +1704,7 @@ fn finally_wrapper_common( let inner = crate::value::js_nanbox_get_pointer(cleanup) as *mut Promise; if !inner.is_null() { // On cleanup fulfillment: settle `next` with the original outcome. - let on_ok = js_closure_alloc(finally_cleanup_fulfill as *const u8, 3); + let on_ok = js_closure_alloc(crate::fn_info!(finally_cleanup_fulfill, 1), 3); js_closure_set_capture_ptr(on_ok, 0, next as i64); js_closure_set_capture_f64(on_ok, 1, orig); js_closure_set_capture_f64( @@ -1724,7 +1717,7 @@ fn finally_wrapper_common( }), ); // On cleanup rejection: reject `next` with the cleanup reason. - let on_err = js_closure_alloc(finally_cleanup_reject as *const u8, 1); + let on_err = js_closure_alloc(crate::fn_info!(finally_cleanup_reject, 1), 1); js_closure_set_capture_ptr(on_err, 0, next as i64); // Use Invoke(cleanup, "then", …) to respect any user-installed own // `then` property on the cleanup promise (observable-then-calls tests). @@ -1764,9 +1757,9 @@ fn finally_settle_next_with_extra_hop(next: *mut Promise, orig: f64, is_fulfille return; } let pass_fn = if is_fulfilled { - finally_passthrough_fulfill as *const u8 + crate::fn_info!(finally_passthrough_fulfill, 1) } else { - finally_passthrough_reject as *const u8 + crate::fn_info!(finally_passthrough_reject, 1) }; let pass = js_closure_alloc(pass_fn, 2); js_closure_set_capture_ptr(pass, 0, next as i64); diff --git a/crates/perry-runtime/src/proxy.rs b/crates/perry-runtime/src/proxy.rs index f0f71f701b..49f4a54321 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -2545,11 +2545,9 @@ pub extern "C" fn js_proxy_revocable(target: f64, handler: f64) -> f64 { let proxy = js_proxy_new(target, handler); // Build the revoke closure capturing the proxy value. - let revoke_closure = - crate::closure::js_closure_alloc(reflect_misc::proxy_revoke_trampoline as *const u8, 1); - crate::closure::js_register_closure_arity( - reflect_misc::proxy_revoke_trampoline as *const u8, - 0, + let revoke_closure = crate::closure::js_closure_alloc( + crate::fn_info!(reflect_misc::proxy_revoke_trampoline, 0; with_declared(0)), + 1, ); crate::closure::js_closure_set_capture_f64(revoke_closure, 0, proxy); let revoke_boxed = f64::from_bits(POINTER_TAG | ((revoke_closure as u64) & POINTER_MASK)); @@ -2961,7 +2959,7 @@ mod tests { ) -> f64 { f64::from_bits(TAG_UNDEFINED) } - let f = crate::closure::js_closure_alloc(dummy_fn as *const u8, 0); + let f = crate::closure::js_closure_alloc(crate::fn_info!(dummy_fn, 0), 0); let f_val = f64::from_bits(POINTER_TAG | ((f as u64) & POINTER_MASK)); let handler = obj_value(); diff --git a/crates/perry-runtime/src/proxy/reflect_misc.rs b/crates/perry-runtime/src/proxy/reflect_misc.rs index ebc1a867ea..36fb6a88ae 100644 --- a/crates/perry-runtime/src/proxy/reflect_misc.rs +++ b/crates/perry-runtime/src/proxy/reflect_misc.rs @@ -395,7 +395,7 @@ mod tests { /// argument registers held. #[test] fn reflect_apply_forwards_every_argument() { - let closure = crate::closure::js_closure_alloc(weighted_six as *const u8, 0); + let closure = crate::closure::js_closure_alloc(crate::fn_info!(weighted_six, 6), 0); let f = crate::value::js_nanbox_pointer(closure as i64); let args = array_from_args(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); let result = js_reflect_apply(f, f64::from_bits(TAG_UNDEFINED), args); diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index 84aeb2b3b5..c6f2f76150 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -47,9 +47,9 @@ pub(crate) use platform_impl::pty_register; mod platform_impl { use super::{native, reactor}; use crate::child_process::{ - cp_array_ptr, cp_box_ptr, cp_box_string, cp_build_object, cp_cast0, cp_cast1, cp_cast2, - cp_get_field, cp_make_error, cp_object_ptr, cp_set_field, cp_this, cp_undefined, - cp_value_to_bytes, cp_value_to_string, CpFn, + cp_array_ptr, cp_box_ptr, cp_box_string, cp_build_object, cp_get_field, cp_make_error, + cp_object_ptr, cp_set_field, cp_this, cp_undefined, cp_value_to_bytes, cp_value_to_string, + CpFn, }; use crate::closure::{native_call_value_this, ClosureHeader, JsThis}; use crate::value::JSValue; @@ -127,7 +127,10 @@ mod platform_impl { /// subscription triple is stored as fields on the handle itself /// (GC-traced through the object), read back by the dispose body. fn pty_make_disposable(target: f64, event: &str, cb: f64) -> f64 { - let methods: [(&str, CpFn); 1] = [("dispose", cp_cast0(pty_disposable_dispose))]; + let methods: [(&str, CpFn); 1] = [( + "dispose", + crate::fn_info!(pty_disposable_dispose, 0; with_declared(0)), + )]; let obj = cp_build_object(&methods, PTY_DISPOSABLE_SHAPE_ID + methods.len() as u32); let val = cp_box_ptr(obj as *const u8); cp_set_field(val, b"__ptyTarget", target); @@ -350,31 +353,42 @@ mod platform_impl { } } - fn pty_register_arities() { - use crate::closure::js_register_closure_arity; - js_register_closure_arity(pty_method_on_data as *const u8, 1); - js_register_closure_arity(pty_method_on_exit as *const u8, 1); - js_register_closure_arity(pty_method_write as *const u8, 1); - js_register_closure_arity(pty_method_resize as *const u8, 2); - js_register_closure_arity(pty_method_kill as *const u8, 1); - js_register_closure_arity(pty_method_pause as *const u8, 0); - js_register_closure_arity(pty_method_resume as *const u8, 0); - js_register_closure_arity(pty_method_dispose as *const u8, 0); - js_register_closure_arity(pty_disposable_dispose as *const u8, 0); - } - /// Build the IPty object shell (methods only; live fields are set by /// `js_pty_spawn` after the fork succeeds). fn pty_build_ipty() -> f64 { let methods: [(&str, CpFn); 8] = [ - ("onData", cp_cast1(pty_method_on_data)), - ("onExit", cp_cast1(pty_method_on_exit)), - ("write", cp_cast1(pty_method_write)), - ("resize", cp_cast2(pty_method_resize)), - ("kill", cp_cast1(pty_method_kill)), - ("pause", cp_cast0(pty_method_pause)), - ("resume", cp_cast0(pty_method_resume)), - ("dispose", cp_cast0(pty_method_dispose)), + ( + "onData", + crate::fn_info!(pty_method_on_data, 1; with_declared(1)), + ), + ( + "onExit", + crate::fn_info!(pty_method_on_exit, 1; with_declared(1)), + ), + ( + "write", + crate::fn_info!(pty_method_write, 1; with_declared(1)), + ), + ( + "resize", + crate::fn_info!(pty_method_resize, 2; with_declared(2)), + ), + ( + "kill", + crate::fn_info!(pty_method_kill, 1; with_declared(1)), + ), + ( + "pause", + crate::fn_info!(pty_method_pause, 0; with_declared(0)), + ), + ( + "resume", + crate::fn_info!(pty_method_resume, 0; with_declared(0)), + ), + ( + "dispose", + crate::fn_info!(pty_method_dispose, 0; with_declared(0)), + ), ]; let obj = cp_build_object(&methods, PTY_SHAPE_ID + methods.len() as u32); cp_box_ptr(obj as *const u8) @@ -389,8 +403,6 @@ mod platform_impl { /// reader/waiter threads with the reactor. Returns the IPty object. #[no_mangle] pub extern "C" fn js_pty_spawn(file_bits: i64, args_bits: i64, opts_bits: i64) -> f64 { - pty_register_arities(); - let file_val = f64::from_bits(file_bits as u64); let Some(file) = cp_value_to_string(file_val).filter(|s| !s.is_empty()) else { crate::exception::js_throw(cp_make_error("spawn: file argument is required", &[])); @@ -482,9 +494,8 @@ mod platform_impl { cp_undefined() } - fn listener_value(f: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { - crate::closure::js_register_closure_arity(f as *const u8, 1); - let closure = crate::closure::js_closure_alloc(f as *const u8, 0); + fn listener_value(info: *const crate::closure::JsFunctionInfo) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); cp_box_ptr(closure as *const u8) } @@ -531,8 +542,16 @@ mod platform_impl { assert_eq!(cp_get_field(ipty, b"cols"), 80.0); assert_eq!(cp_get_field(ipty, b"rows"), 24.0); - pty_register(ipty, "data", listener_value(test_data_listener)); - pty_register(ipty, "exit", listener_value(test_exit_listener)); + pty_register( + ipty, + "data", + listener_value(crate::fn_info!(test_data_listener, 1; with_declared(1))), + ); + pty_register( + ipty, + "exit", + listener_value(crate::fn_info!(test_exit_listener, 1; with_declared(1))), + ); let handle = pty_handle_of(ipty).expect("__ptyHandle set"); // Output prints PTY_OK while the echoed command shows PTY_%s, so @@ -568,7 +587,11 @@ mod platform_impl { boxed_str("30").to_bits() as i64, cp_undefined().to_bits() as i64, ); - pty_register(ipty, "exit", listener_value(test_exit_listener)); + pty_register( + ipty, + "exit", + listener_value(crate::fn_info!(test_exit_listener, 1; with_declared(1))), + ); let handle = pty_handle_of(ipty).expect("__ptyHandle set"); std::thread::sleep(std::time::Duration::from_millis(150)); diff --git a/crates/perry-runtime/src/pty/tests.rs b/crates/perry-runtime/src/pty/tests.rs index fa6f6c9360..d3afa4f893 100644 --- a/crates/perry-runtime/src/pty/tests.rs +++ b/crates/perry-runtime/src/pty/tests.rs @@ -40,9 +40,8 @@ extern "C" fn exited( cp_undefined() } -fn callback(f: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { - crate::closure::js_register_closure_arity(f as *const u8, 1); - cp_box_ptr(crate::closure::js_closure_alloc(f as *const u8, 0).cast()) +fn callback(info: *const crate::closure::JsFunctionInfo) -> f64 { + cp_box_ptr(crate::closure::js_closure_alloc(info, 0).cast()) } fn call(target: f64, name: &[u8], args: &[f64]) -> f64 { @@ -113,10 +112,22 @@ fn aliases_share_native_spawn_and_paused_output_precedes_exit() { }); assert!(cp_get_field(term.get_nanbox_f64(), b"pid") > 0.0); let handle = pty_handle_of(term.get_nanbox_f64()).unwrap(); - let disposable = call(term.get_nanbox_f64(), b"onData", &[callback(removed)]); + let disposable = call( + term.get_nanbox_f64(), + b"onData", + &[callback(crate::fn_info!(removed, 1; with_declared(1)))], + ); call(disposable, b"dispose", &[]); - call(term.get_nanbox_f64(), b"onData", &[callback(data)]); - call(term.get_nanbox_f64(), b"onExit", &[callback(exited)]); + call( + term.get_nanbox_f64(), + b"onData", + &[callback(crate::fn_info!(data, 1; with_declared(1)))], + ); + call( + term.get_nanbox_f64(), + b"onExit", + &[callback(crate::fn_info!(exited, 1; with_declared(1)))], + ); call(term.get_nanbox_f64(), b"pause", &[]); call(term.get_nanbox_f64(), b"write", &[cp_box_string(script)]); let paused = Instant::now() + Duration::from_millis(150); diff --git a/crates/perry-runtime/src/readline_helpers.rs b/crates/perry-runtime/src/readline_helpers.rs index 88d3dd5ca0..45a09173e5 100644 --- a/crates/perry-runtime/src/readline_helpers.rs +++ b/crates/perry-runtime/src/readline_helpers.rs @@ -301,7 +301,7 @@ pub extern "C" fn js_readline_emit_keypress_events_args( if raw_ptr_from_value(stream.get_nanbox_f64()).is_none() { return undefined(); } - let listener = js_closure_alloc(emit_keypress_data as *const u8, 1); + let listener = js_closure_alloc(crate::fn_info!(emit_keypress_data, 1), 1); js_closure_set_capture_f64(listener, 0, stream.get_nanbox_f64()); let listener = scope.root_raw_const_ptr(listener as *const ClosureHeader); let Some(raw) = raw_ptr_from_value(stream.get_nanbox_f64()) else { diff --git a/crates/perry-runtime/src/regex/site_test.rs b/crates/perry-runtime/src/regex/site_test.rs index 3e379b4238..ffe3621dd2 100644 --- a/crates/perry-runtime/src/regex/site_test.rs +++ b/crates/perry-runtime/src/regex/site_test.rs @@ -669,7 +669,7 @@ mod tests { crate::object::js_object_set_field_by_name( object, string("test"), - closure_with_arity(patched_test as *const u8, 1), + closure(crate::fn_info!(patched_test, 1; with_declared(1))), ); crate::value::js_nanbox_pointer(object as i64) } @@ -685,13 +685,8 @@ mod tests { exact_factory(std::ptr::null(), this) } - fn closure(function: *const u8) -> f64 { - closure_with_arity(function, 0) - } - - fn closure_with_arity(function: *const u8, arity: usize) -> f64 { - crate::closure::js_register_closure_arity(function, arity as u32); - crate::value::js_nanbox_pointer(crate::closure::js_closure_alloc(function, 0) as i64) + fn closure(info: *const crate::closure::JsFunctionInfo) -> f64 { + crate::value::js_nanbox_pointer(crate::closure::js_closure_alloc(info, 0) as i64) } fn dispatch_test(site: i64, receiver: f64, input: &str) -> f64 { @@ -710,7 +705,7 @@ mod tests { test_reset(); ensure_regexp_builtins(); let site = key(&FACTORY_CALL); - let callee = closure(exact_factory as *const u8); + let callee = closure(crate::fn_info!(exact_factory, 0; with_declared(0))); let first = js_regexp_site_factory_call_value(site, callee); let second = js_regexp_site_factory_call_value(site, callee); assert_eq!( @@ -720,7 +715,7 @@ mod tests { ); assert_eq!(TEST_NO_ALLOC.with(std::cell::Cell::get), 1); - let replacement = closure(replacement_factory as *const u8); + let replacement = closure(crate::fn_info!(replacement_factory, 0; with_declared(0))); let receiver = js_regexp_site_factory_call_value(site, replacement); let result = dispatch_test(site, receiver, "does not contain the pattern"); assert_eq!(result.to_bits(), crate::value::TAG_TRUE); @@ -739,7 +734,7 @@ mod tests { ensure_regexp_builtins(); NESTED_WRAPPER_CALLS.store(0, std::sync::atomic::Ordering::Relaxed); let site = key(&NESTED_FACTORY_CALL); - let wrapper = closure(nested_factory_wrapper as *const u8); + let wrapper = closure(crate::fn_info!(nested_factory_wrapper, 0; with_declared(0))); let scope = crate::gc::RuntimeHandleScope::new(); let first = scope.root_nanbox_f64(js_regexp_site_factory_call_value(site, wrapper)); let second = js_regexp_site_factory_call_value(site, wrapper); @@ -786,7 +781,7 @@ mod tests { crate::object::js_object_set_field_by_name( namespace, name, - closure(exact_factory as *const u8), + closure(crate::fn_info!(exact_factory, 0; with_declared(0))), ); let namespace_object = namespace; let namespace = crate::value::js_nanbox_pointer(namespace_object as i64); @@ -803,7 +798,7 @@ mod tests { crate::object::js_object_set_field_by_name( namespace_object, name, - closure(replacement_factory as *const u8), + closure(crate::fn_info!(replacement_factory, 0; with_declared(0))), ); let replacement = unsafe { js_regexp_site_factory_call_method(site, namespace, member) }; assert_eq!( @@ -843,7 +838,7 @@ mod tests { crate::object::js_object_set_field_by_name( proto, test_key, - closure_with_arity(patched_test as *const u8, 1), + closure(crate::fn_info!(patched_test, 1; with_declared(1))), ); let receiver = js_regexp_site_test_new(string("x"), string("g"), site); diff --git a/crates/perry-runtime/src/set_tombstone_tests.rs b/crates/perry-runtime/src/set_tombstone_tests.rs index c75dfa17cc..4184e6fb0c 100644 --- a/crates/perry-runtime/src/set_tombstone_tests.rs +++ b/crates/perry-runtime/src/set_tombstone_tests.rs @@ -39,7 +39,7 @@ fn take_foreach_delete_visits() -> Vec { FOREACH_DELETE_VISITS.with(|visits| visits.borrow_mut().drain(..).map(f64::from_bits).collect()) } -fn foreach_callback(func: *const u8) -> f64 { +fn foreach_callback(func: *const crate::closure::JsFunctionInfo) -> f64 { FOREACH_DELETE_VISITS.with(|visits| visits.borrow_mut().clear()); let callback = crate::closure::js_closure_alloc(func, 0); crate::value::js_nanbox_pointer(callback as i64) @@ -54,7 +54,7 @@ fn foreach_survives_delete_compaction_threshold() { } js_set_foreach( set, - foreach_callback(delete_current_set_value as *const u8), + foreach_callback(crate::fn_info!(delete_current_set_value, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!(take_foreach_delete_visits(), expected); @@ -73,7 +73,7 @@ fn foreach_survives_delete_compaction_threshold() { } js_set_foreach( set, - foreach_callback(delete_earlier_set_value as *const u8), + foreach_callback(crate::fn_info!(delete_earlier_set_value, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!(take_foreach_delete_visits(), expected); @@ -435,7 +435,7 @@ fn a_live_index_read_inside_foreach_defers_the_squeeze_and_skips_nothing() { } js_set_foreach( set, - foreach_callback(delete_earlier_then_live_read as *const u8), + foreach_callback(crate::fn_info!(delete_earlier_then_live_read, 3)), f64::from_bits(crate::value::TAG_UNDEFINED), ); assert_eq!( diff --git a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs index 72f709d2b8..da77bba0f0 100644 --- a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs +++ b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs @@ -2,7 +2,7 @@ //! receiver as `this`, for `[[Get]]` and `[[Set]]` alike. use super::*; -use crate::closure::{js_closure_alloc, js_register_closure_arity, ClosureHeader}; +use crate::closure::{js_closure_alloc, ClosureHeader}; use std::cell::Cell; thread_local! { @@ -25,9 +25,8 @@ extern "C" fn recording_setter( f64::from_bits(TAG_UNDEFINED) } -unsafe fn closure_bits(f: *const u8, arity: u32) -> u64 { - js_register_closure_arity(f, arity); - crate::value::js_nanbox_pointer(js_closure_alloc(f, 0) as i64).to_bits() +unsafe fn closure_bits(info: *const crate::closure::JsFunctionInfo) -> u64 { + crate::value::js_nanbox_pointer(js_closure_alloc(info, 0) as i64).to_bits() } unsafe fn plain_object() -> f64 { @@ -42,8 +41,8 @@ unsafe fn fixture() -> (f64, f64, f64, f64) { set_symbol_accessor_property( proto, sym, - closure_bits(this_getter as *const u8, 0), - closure_bits(recording_setter as *const u8, 1), + closure_bits(crate::fn_info!(this_getter, 0; with_declared(0))), + closure_bits(crate::fn_info!(recording_setter, 1; with_declared(1))), ); let child = crate::object::js_object_create(proto); let grandchild = crate::object::js_object_create(child); @@ -194,7 +193,12 @@ fn symbol_may_have_accessor_is_false_until_an_accessor_is_installed() { let bits_before = super::accessors::test_symbol_accessor_id_bits_set(); let holder = plain_object(); - set_symbol_accessor_property(holder, sym, closure_bits(this_getter as *const u8, 0), 0); + set_symbol_accessor_property( + holder, + sym, + closure_bits(crate::fn_info!(this_getter, 0; with_declared(0))), + 0, + ); let after = super::accessors::symbol_may_have_accessor(sym_key); let bits_after = super::accessors::test_symbol_accessor_id_bits_set(); crate::gc::gc_unsuppress(); diff --git a/crates/perry-runtime/src/symbol/properties.rs b/crates/perry-runtime/src/symbol/properties.rs index 332e6edea4..3930bd4e5d 100644 --- a/crates/perry-runtime/src/symbol/properties.rs +++ b/crates/perry-runtime/src/symbol/properties.rs @@ -270,7 +270,11 @@ unsafe fn register_closure_name_if_absent(val_bits: u64, name: &str) { } let val_ptr = val_addr as *const u8; let closure_ptr = val_ptr as *const crate::closure::ClosureHeader; - let func_ptr = (*closure_ptr).func_ptr; + // A header whose info word is not yet written (null) names nothing. + if (*closure_ptr).info.is_null() { + return; + } + let func_ptr = (*closure_ptr).code(); if func_ptr.is_null() { return; } diff --git a/crates/perry-runtime/src/thread.rs b/crates/perry-runtime/src/thread.rs index ed71594593..044a18a1ac 100644 --- a/crates/perry-runtime/src/thread.rs +++ b/crates/perry-runtime/src/thread.rs @@ -244,9 +244,11 @@ pub enum SerializedValue { keys: Option>>, }, - /// A closure: function pointer (global code, safe to share) + serialized captures. + /// A closure: its body's static `JsFunctionInfo` (process-global, safe + /// to share: the receiving thread sees every fact about the body) + + /// serialized captures. Closure { - func_ptr: usize, + info: usize, capture_count: u32, // includes CAPTURES_THIS_FLAG captures: Vec, }, @@ -308,8 +310,8 @@ pub enum SerializedValue { } // Safety: SerializedValue contains no raw pointers to arena memory. -// func_ptr in Closure points to compiled code in the executable's text segment, -// which is process-global and immutable. +// `info` in Closure points to a body's static JsFunctionInfo, which is +// process-global and immutable. unsafe impl Send for SerializedValue {} unsafe impl Sync for SerializedValue {} @@ -762,7 +764,7 @@ unsafe fn serialize_closure(closure: *const ClosureHeader) -> SerializedValue { return SerializedValue::Inline(TAG_UNDEFINED); } - let func_ptr = (*closure).func_ptr as usize; + let info = (*closure).info as usize; let capture_count_raw = (*closure).capture_count; let actual_count = real_capture_count(capture_count_raw) as usize; @@ -775,7 +777,7 @@ unsafe fn serialize_closure(closure: *const ClosureHeader) -> SerializedValue { } SerializedValue::Closure { - func_ptr, + info, capture_count: capture_count_raw, captures, } @@ -919,11 +921,14 @@ pub unsafe fn deserialize_nanbox_on_current_thread(sv: &SerializedValue) -> u64 } SerializedValue::Closure { - func_ptr, + info, capture_count, captures, } => { - let closure = closure::js_closure_alloc(*func_ptr as *const u8, *capture_count); + let closure = closure::js_closure_alloc( + *info as *const crate::closure::JsFunctionInfo, + *capture_count, + ); for (i, cap) in captures.iter().enumerate() { let bits = deserialize_nanbox_on_current_thread(cap); crate::closure::js_closure_set_capture_f64(closure, i as u32, f64::from_bits(bits)); @@ -1042,7 +1047,7 @@ type ClosureCallFn = crate::closure::body_call::js_body_fn_ty!(argument); /// /// Both arguments are NaN-boxed f64 values as produced by the compiler: /// - `array_val`: POINTER_TAG'd ArrayHeader pointer -/// - `closure_val`: POINTER_TAG'd ClosureHeader pointer (contains func_ptr + captures) +/// - `closure_val`: POINTER_TAG'd ClosureHeader pointer (its body info + captures) /// /// Returns a POINTER_TAG'd ArrayHeader pointer to the result array. #[no_mangle] @@ -1053,7 +1058,7 @@ pub extern "C" fn js_thread_parallel_map(array_val: f64, closure_val: f64) -> f6 } unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { - // ── 1. Extract closure pointer and func_ptr, and root the closure ─ + // ── 1. Extract closure pointer and code, and root the closure ─ // The closure is validated and rooted BEFORE `clean_arr_ptr`: resolving // the array can force-materialize a lazy array — a GC point — and a // moving minor there would strand a raw closure pointer held in an @@ -1064,7 +1069,7 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { // No valid closure — can't call anything return crate::array::js_array_alloc(0) as i64; } - let func = (*closure).func_ptr; + let func = (*closure).code(); let scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = scope.root_raw_mut_ptr(closure as *mut ClosureHeader); @@ -1125,7 +1130,7 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { // ── 5. Serialize closure captures (shared across all threads) ──── let serialized_captures: Option<(usize, u32, Vec)> = { if !closure.is_null() && (closure as usize) >= 0x1000 { - let fp = (*closure).func_ptr as usize; + let fp = (*closure).info as usize; let cc = (*closure).capture_count; let actual = real_capture_count(cc) as usize; let base = @@ -1201,7 +1206,8 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { let gc_scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = if let Some(ref caps) = captures_ref { let (fp, cc, ref cap_vals) = **caps; - let c = closure::js_closure_alloc(fp as *const u8, cc); + let c = + closure::js_closure_alloc(fp as *const crate::closure::JsFunctionInfo, cc); let h = gc_scope.root_raw_mut_ptr(c); for (i, cap) in cap_vals.iter().enumerate() { let bits = deserialize_nanbox_on_current_thread(cap); @@ -1347,7 +1353,7 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { if closure.is_null() || (closure as usize) < 0x1000 { return crate::array::js_array_alloc(0) as i64; } - let func = (*closure).func_ptr; + let func = (*closure).code(); let scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = scope.root_raw_mut_ptr(closure as *mut ClosureHeader); @@ -1394,7 +1400,7 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { // Serialize closure captures let serialized_captures: Option<(usize, u32, Vec)> = { - let fp = (*closure).func_ptr as usize; + let fp = (*closure).info as usize; let cc = (*closure).capture_count; let actual = real_capture_count(cc) as usize; let base = (closure as *const u8).add(std::mem::size_of::()) as *const f64; @@ -1454,7 +1460,8 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { let gc_scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = if let Some(ref caps) = captures_ref { let (fp, cc, ref cap_vals) = **caps; - let c = closure::js_closure_alloc(fp as *const u8, cc); + let c = + closure::js_closure_alloc(fp as *const crate::closure::JsFunctionInfo, cc); let h = gc_scope.root_raw_mut_ptr(c); for (i, cap) in cap_vals.iter().enumerate() { let bits = deserialize_nanbox_on_current_thread(cap); @@ -1607,11 +1614,11 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { let err = crate::error::js_error_new_with_message(s); return crate::promise::js_promise_rejected(crate::value::js_nanbox_pointer(err as i64)); } - // ── 0. Extract closure pointer and func_ptr ────────────────────── + // ── 0. Extract closure pointer and body info ────────────────────── let closure_bits = closure_val.to_bits(); let closure = (closure_bits & POINTER_MASK) as *const ClosureHeader; - let func_usize = if !closure.is_null() && (closure as usize) >= 0x1000 { - (*closure).func_ptr as usize + let info_usize = if !closure.is_null() && (closure as usize) >= 0x1000 { + (*closure).info as usize } else { // No valid closure — return a resolved promise with undefined let promise = crate::promise::js_promise_new(); @@ -1675,7 +1682,8 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { // capture-deserialization allocations. let gc_scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = if let Some((cc, ref cap_vals)) = serialized_captures { - let c = closure::js_closure_alloc(func_usize as *const u8, cc); + let c = + closure::js_closure_alloc(info_usize as *const crate::closure::JsFunctionInfo, cc); let h = gc_scope.root_raw_mut_ptr(c); for (i, cap) in cap_vals.iter().enumerate() { unsafe { @@ -1690,13 +1698,16 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { h } else { // No captures — create a minimal closure header - gc_scope.root_raw_mut_ptr(closure::js_closure_alloc(func_usize as *const u8, 0)) + gc_scope.root_raw_mut_ptr(closure::js_closure_alloc( + info_usize as *const crate::closure::JsFunctionInfo, + 0, + )) }; // Call the function — catch panics to avoid aborting across FFI boundary let call_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let call_fn: ClosureCall0Fn = - unsafe { crate::closure::body_call::js_body_fn!(func_usize as *const u8;) }; + let code = unsafe { (*(info_usize as *const crate::closure::JsFunctionInfo)).code }; + let call_fn: ClosureCall0Fn = unsafe { crate::closure::body_call::js_body_fn!(code;) }; let local_closure = closure_handle.get_raw_mut_ptr::() as *const ClosureHeader; call_fn(local_closure, crate::closure::plain_call_receiver()) diff --git a/crates/perry-runtime/src/thread_transfer_guard_tests.rs b/crates/perry-runtime/src/thread_transfer_guard_tests.rs index 6ef10a0c3e..1301390fbc 100644 --- a/crates/perry-runtime/src/thread_transfer_guard_tests.rs +++ b/crates/perry-runtime/src/thread_transfer_guard_tests.rs @@ -59,7 +59,7 @@ fn first_unsupported_transfer_type_finds_nested_markers() { assert_eq!(first_unsupported_transfer_type(&obj), Some("Promise")); // Inside a closure capture. let clo = SerializedValue::Closure { - func_ptr: 0, + info: 0, capture_count: 1, captures: vec![SerializedValue::Unsupported("Error")], }; diff --git a/crates/perry-runtime/src/timer/handle_object.rs b/crates/perry-runtime/src/timer/handle_object.rs index 990a7637a0..6266ddd532 100644 --- a/crates/perry-runtime/src/timer/handle_object.rs +++ b/crates/perry-runtime/src/timer/handle_object.rs @@ -359,18 +359,17 @@ fn install_timer_symbol_method( proto: *mut crate::object::ObjectHeader, symbol_name: &str, display_name: &str, - func_ptr: *const u8, + info: *const crate::closure::JsFunctionInfo, arity: u32, ) { let sym = crate::symbol::well_known_symbol(symbol_name); if sym.is_null() { return; } - let closure = crate::closure::js_closure_alloc(func_ptr, 0); + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, arity); crate::object::native_module::set_bound_native_closure_name(closure, display_name); crate::object::native_module::set_builtin_closure_length(closure as usize, arity); crate::object::native_module::set_builtin_closure_non_constructable(closure as usize); @@ -389,11 +388,11 @@ fn install_timer_symbol_method( } fn install_timer_constructor(proto: *mut crate::object::ObjectHeader, name: &str) { - let closure = crate::closure::js_closure_alloc(timer_ctor_thunk as *const u8, 0); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(timer_ctor_thunk, 1; with_declared(0)), 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(timer_ctor_thunk as *const u8, 0); crate::object::native_module::set_bound_native_closure_name(closure, name); crate::object::native_module::set_builtin_closure_length(closure as usize, 0); let key = crate::string::js_string_from_bytes(b"constructor".as_ptr(), 11); @@ -425,17 +424,22 @@ fn build_timer_prototypes() { if proto.is_null() { return; } - crate::object::install_proto_method(proto, "ref", timer_proto_ref_thunk as *const u8, 0); + crate::object::install_proto_method( + proto, + "ref", + crate::fn_info!(timer_proto_ref_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), + 0, + ); crate::object::install_proto_method( proto, "unref", - timer_proto_unref_thunk as *const u8, + crate::fn_info!(timer_proto_unref_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); crate::object::install_proto_method( proto, "hasRef", - timer_proto_has_ref_thunk as *const u8, + crate::fn_info!(timer_proto_has_ref_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); // Measured against node 26.8.1: `Timeout.prototype` owns exactly @@ -448,13 +452,13 @@ fn build_timer_prototypes() { crate::object::install_proto_method( proto, "refresh", - timer_proto_refresh_thunk as *const u8, + crate::fn_info!(timer_proto_refresh_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); crate::object::install_proto_method( proto, "close", - timer_proto_close_thunk as *const u8, + crate::fn_info!(timer_proto_close_thunk, 0; with_declared(0), with_flags(crate::closure::FN_BUILTIN)), 0, ); } @@ -463,7 +467,7 @@ fn build_timer_prototypes() { proto, "dispose", "[Symbol.dispose]", - timer_proto_dispose_thunk as *const u8, + crate::fn_info!(timer_proto_dispose_thunk, 0; with_declared(0)), 0, ); if !is_immediate { @@ -471,7 +475,7 @@ fn build_timer_prototypes() { proto, "toPrimitive", "[Symbol.toPrimitive]", - timer_proto_to_primitive_thunk as *const u8, + crate::fn_info!(timer_proto_to_primitive_thunk, 1; with_declared(1)), 1, ); } diff --git a/crates/perry-runtime/src/timer/tests_inline.rs b/crates/perry-runtime/src/timer/tests_inline.rs index a631d49dbf..9ce254145f 100644 --- a/crates/perry-runtime/src/timer/tests_inline.rs +++ b/crates/perry-runtime/src/timer/tests_inline.rs @@ -185,7 +185,8 @@ mod mock_dispatch_own_pin_tests { js_mock_timers_reset(); js_mock_timers_enable(MOCK_TIMERS_API_SET_TIMEOUT, 0.0); - let closure = crate::closure::js_closure_alloc(churn_then_check_self as *const u8, 0); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(churn_then_check_self, 0), 0); let id = schedule_mock_callback_timer( closure as i64, 10.0, diff --git a/crates/perry-runtime/src/tty.rs b/crates/perry-runtime/src/tty.rs index 95ce31c1ea..773756fdfc 100644 --- a/crates/perry-runtime/src/tty.rs +++ b/crates/perry-runtime/src/tty.rs @@ -669,9 +669,8 @@ pub extern "C" fn js_tty_write_stream_has_colors( js_bool(has_colors_for_depth(count, color_depth_for_env(env))) } -fn closure_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, arity); - let closure = crate::closure::js_closure_alloc_singleton(func_ptr); +fn closure_value(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = crate::closure::js_closure_alloc_singleton(info); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) @@ -697,7 +696,7 @@ fn ensure_tty_prototypes() { 1, JSValue::from_bits( closure_value( - js_tty_read_stream_set_raw_mode as *const u8, + crate::fn_info!(js_tty_read_stream_set_raw_mode, 1; with_declared(1)), "setRawMode", 1, ) @@ -719,7 +718,7 @@ fn ensure_tty_prototypes() { 2, JSValue::from_bits( closure_value( - js_tty_write_stream_get_color_depth as *const u8, + crate::fn_info!(js_tty_write_stream_get_color_depth, 1; with_declared(1)), "getColorDepth", 1, ) @@ -730,7 +729,12 @@ fn ensure_tty_prototypes() { write_proto, 3, JSValue::from_bits( - closure_value(js_tty_write_stream_has_colors as *const u8, "hasColors", 2).to_bits(), + closure_value( + crate::fn_info!(js_tty_write_stream_has_colors, 2; with_declared(2)), + "hasColors", + 2, + ) + .to_bits(), ), ); crate::object::js_object_set_field( @@ -738,7 +742,7 @@ fn ensure_tty_prototypes() { 4, JSValue::from_bits( closure_value( - js_tty_write_stream_refresh_size as *const u8, + crate::fn_info!(js_tty_write_stream_refresh_size, 0; with_declared(0)), "_refreshSize", 0, ) @@ -749,7 +753,12 @@ fn ensure_tty_prototypes() { write_proto, 5, JSValue::from_bits( - closure_value(js_tty_write_stream_cursor_to as *const u8, "cursorTo", 3).to_bits(), + closure_value( + crate::fn_info!(js_tty_write_stream_cursor_to, 3; with_declared(3)), + "cursorTo", + 3, + ) + .to_bits(), ), ); crate::object::js_object_set_field( @@ -757,7 +766,7 @@ fn ensure_tty_prototypes() { 6, JSValue::from_bits( closure_value( - js_tty_write_stream_move_cursor as *const u8, + crate::fn_info!(js_tty_write_stream_move_cursor, 3; with_declared(3)), "moveCursor", 3, ) @@ -768,7 +777,12 @@ fn ensure_tty_prototypes() { write_proto, 7, JSValue::from_bits( - closure_value(js_tty_write_stream_clear_line as *const u8, "clearLine", 2).to_bits(), + closure_value( + crate::fn_info!(js_tty_write_stream_clear_line, 2; with_declared(2)), + "clearLine", + 2, + ) + .to_bits(), ), ); crate::object::js_object_set_field( @@ -776,7 +790,7 @@ fn ensure_tty_prototypes() { 8, JSValue::from_bits( closure_value( - js_tty_write_stream_clear_screen_down as *const u8, + crate::fn_info!(js_tty_write_stream_clear_screen_down, 1; with_declared(1)), "clearScreenDown", 1, ) @@ -788,7 +802,7 @@ fn ensure_tty_prototypes() { 9, JSValue::from_bits( closure_value( - js_tty_write_stream_get_window_size as *const u8, + crate::fn_info!(js_tty_write_stream_get_window_size, 0; with_declared(0)), "getWindowSize", 0, ) @@ -807,7 +821,7 @@ pub(crate) fn attach_tty_constructor_prototype(constructor_value: f64, name: &st CLASS_ID_TTY_READ_STREAM, Some(1), Some(closure_value( - js_tty_read_stream_set_raw_mode as *const u8, + crate::fn_info!(js_tty_read_stream_set_raw_mode, 1; with_declared(1)), "setRawMode", 1, )), @@ -847,12 +861,16 @@ fn add_write_stream_listener_fields(obj: *mut ObjectHeader) { } pub(crate) fn tty_listener_on_value() -> f64 { - closure_value(js_tty_write_stream_on as *const u8, "on", 2) + closure_value( + crate::fn_info!(js_tty_write_stream_on, 2; with_declared(2)), + "on", + 2, + ) } pub(crate) fn tty_listener_remove_value() -> f64 { closure_value( - js_tty_write_stream_remove_listener as *const u8, + crate::fn_info!(js_tty_write_stream_remove_listener, 2; with_declared(2)), "removeListener", 2, ) @@ -860,7 +878,7 @@ pub(crate) fn tty_listener_remove_value() -> f64 { pub(crate) fn tty_listener_remove_all_value() -> f64 { closure_value( - js_tty_write_stream_remove_all_listeners as *const u8, + crate::fn_info!(js_tty_write_stream_remove_all_listeners, 1; with_declared(1)), "removeAllListeners", 1, ) diff --git a/crates/perry-runtime/src/tui/handle_object.rs b/crates/perry-runtime/src/tui/handle_object.rs index 35cd7bbbf9..f6728e764c 100644 --- a/crates/perry-runtime/src/tui/handle_object.rs +++ b/crates/perry-runtime/src/tui/handle_object.rs @@ -382,36 +382,40 @@ fn build_prototype(kind: TuiKind) -> *mut crate::object::ObjectHeader { if proto.is_null() { return std::ptr::null_mut(); } - let methods: &[(&str, *const u8, u32)] = match kind { - TuiKind::State => &[ - ("get", state_proto_get_thunk as *const u8, 0), - ("set", state_proto_set_thunk as *const u8, 1), + let methods: Vec<(&str, *const crate::closure::JsFunctionInfo, u32)> = match kind { + TuiKind::State => vec![ + ("get", crate::fn_info!(state_proto_get_thunk, 0), 0), + ("set", crate::fn_info!(state_proto_set_thunk, 1), 1), ], - TuiKind::RefBox => &[ - ("get", ref_proto_get_thunk as *const u8, 0), - ("set", ref_proto_set_thunk as *const u8, 1), + TuiKind::RefBox => vec![ + ("get", crate::fn_info!(ref_proto_get_thunk, 0), 0), + ("set", crate::fn_info!(ref_proto_set_thunk, 1), 1), ], - TuiKind::App => &[ - ("exit", app_proto_exit_thunk as *const u8, 0), + TuiKind::App => vec![ + ("exit", crate::fn_info!(app_proto_exit_thunk, 0), 0), ( "waitUntilExit", - app_proto_wait_until_exit_thunk as *const u8, + crate::fn_info!(app_proto_wait_until_exit_thunk, 0), 0, ), ], - TuiKind::Stdout => &[ - ("write", stdout_proto_write_thunk as *const u8, 1), - ("columns", stdout_proto_columns_thunk as *const u8, 0), - ("rows", stdout_proto_rows_thunk as *const u8, 0), + TuiKind::Stdout => vec![ + ("write", crate::fn_info!(stdout_proto_write_thunk, 1), 1), + ("columns", crate::fn_info!(stdout_proto_columns_thunk, 0), 0), + ("rows", crate::fn_info!(stdout_proto_rows_thunk, 0), 0), ], - TuiKind::FocusManager => &[ - ("focusNext", focus_proto_next_thunk as *const u8, 0), - ("focusPrevious", focus_proto_previous_thunk as *const u8, 0), - ("focus", focus_proto_focus_thunk as *const u8, 1), + TuiKind::FocusManager => vec![ + ("focusNext", crate::fn_info!(focus_proto_next_thunk, 0), 0), + ( + "focusPrevious", + crate::fn_info!(focus_proto_previous_thunk, 0), + 0, + ), + ("focus", crate::fn_info!(focus_proto_focus_thunk, 1), 1), ], - TuiKind::Widget => &[], + TuiKind::Widget => vec![], }; - for (name, ptr, arity) in methods { + for (name, ptr, arity) in &methods { crate::object::install_proto_method(proto, name, *ptr, *arity); } slot.store(proto as i64, Ordering::Release); diff --git a/crates/perry-runtime/src/tui/hooks.rs b/crates/perry-runtime/src/tui/hooks.rs index 3b9d6987fb..dbd6fb05c2 100644 --- a/crates/perry-runtime/src/tui/hooks.rs +++ b/crates/perry-runtime/src/tui/hooks.rs @@ -456,8 +456,7 @@ pub extern "C" fn js_perry_tui_use_state_tuple(initial: f64) -> i64 { drop(s); // Allocate the setter closure with 1 capture (the slot index). - let trampoline = perry_tui_state_setter_trampoline as *const u8; - let setter = js_closure_alloc(trampoline, 1); + let setter = js_closure_alloc(crate::fn_info!(perry_tui_state_setter_trampoline, 1), 1); js_closure_set_capture_f64(setter, 0, idx as f64); // Build [value, setter_closure] array. diff --git a/crates/perry-runtime/src/typed_feedback/guards.rs b/crates/perry-runtime/src/typed_feedback/guards.rs index e95d61f333..2cafe97d22 100644 --- a/crates/perry-runtime/src/typed_feedback/guards.rs +++ b/crates/perry-runtime/src/typed_feedback/guards.rs @@ -1500,7 +1500,7 @@ pub unsafe extern "C" fn js_method_direct_shape_guard( pub extern "C" fn js_typed_feedback_closure_direct_call_guard( site_id: u64, closure_value: f64, - expected_func_ptr: *const u8, + expected_info: *const crate::closure::JsFunctionInfo, expected_arity: u32, call_arity: u32, ) -> i32 { @@ -1513,19 +1513,17 @@ pub extern "C" fn js_typed_feedback_closure_direct_call_guard( std::ptr::null() }; let closure_ptr = crate::closure::clean_closure_ptr(raw_ptr); - let func_ptr = crate::closure::get_valid_func_ptr(closure_ptr); - let has_rest = !func_ptr.is_null() && crate::closure::lookup_closure_rest(func_ptr).is_some(); - let declared = if func_ptr.is_null() { - None - } else { - crate::closure::lookup_closure_arity(func_ptr) - }; - let contract_valid = !expected_func_ptr.is_null() - && !func_ptr.is_null() - && func_ptr == expected_func_ptr - && func_ptr != crate::closure::BOUND_METHOD_FUNC_PTR - && !has_rest - && declared.unwrap_or(expected_arity) == expected_arity + let info = crate::closure::closure_info(closure_ptr); + let func_ptr = info.map_or(std::ptr::null(), |info| info.code); + // The body's own info is the identity; its rest kind and parameter count + // are the contract a direct call at `call_arity` relies on. + let contract_valid = !expected_info.is_null() + && info.is_some_and(|info| { + std::ptr::eq(info, expected_info) + && info.code != crate::closure::BOUND_METHOD_FUNC_PTR + && crate::closure::info_rest(info).is_none() + && u32::from(info.params) == expected_arity + }) && expected_arity == call_arity; let observation = Observation { source: ObservationSource::Closure, @@ -1557,7 +1555,7 @@ pub extern "C" fn js_typed_feedback_closure_direct_call_guard( /// direct call. /// /// Whole-program object-literal capabilities already prove the target's -/// arity/rest contract at compile time. Repeating the closure registry lookups +/// arity/rest contract at compile time. Repeating the closure info checks /// and recording a typed-feedback observation on every call therefore adds no /// safety. This smaller guard deliberately keeps the speculation-safe closure /// header validation used by the universal dispatcher: arbitrary replacement @@ -1577,14 +1575,14 @@ fn closure_ptr_from_value_bits(bits: u64) -> *const crate::closure::ClosureHeade #[no_mangle] pub extern "C" fn js_closure_exact_func_guard( closure_value: f64, - expected_func_ptr: *const u8, + expected_info: *const crate::closure::JsFunctionInfo, ) -> u64 { - if expected_func_ptr.is_null() { + if expected_info.is_null() { return 0; } let raw_ptr = closure_ptr_from_value_bits(closure_value.to_bits()); let closure_ptr = crate::closure::clean_closure_ptr(raw_ptr); - if crate::closure::get_valid_func_ptr(closure_ptr) == expected_func_ptr { + if std::ptr::eq(crate::closure::get_valid_info(closure_ptr), expected_info) { closure_ptr as u64 } else { 0 @@ -1626,7 +1624,7 @@ pub unsafe extern "C" fn js_object_own_method_cache_miss( field_index: u32, method_name_ptr: *const i8, method_name_len: usize, - expected_func_ptr: *const u8, + expected_info: *const crate::closure::JsFunctionInfo, cache_slot: *mut MethodPicCacheSlot, ) -> u64 { { @@ -1635,7 +1633,7 @@ pub unsafe extern "C" fn js_object_own_method_cache_miss( (*cache)[0] = 0; } } - if expected_class_id == 0 || expected_func_ptr.is_null() || cache_slot.is_null() { + if expected_class_id == 0 || expected_info.is_null() || cache_slot.is_null() { return 0; } let Some(method_bytes) = method_name_bytes(method_name_ptr, method_name_len) else { @@ -1689,7 +1687,7 @@ pub unsafe extern "C" fn js_object_own_method_cache_miss( let closure_bits = std::ptr::read(fields.add(field_index as usize)); let raw_ptr = closure_ptr_from_value_bits(closure_bits); let closure = crate::closure::clean_closure_ptr(raw_ptr); - if crate::closure::get_valid_func_ptr(closure) != expected_func_ptr { + if !std::ptr::eq(crate::closure::get_valid_info(closure), expected_info) { return 0; } @@ -1730,11 +1728,11 @@ mod keep_guard_symbols { #[cfg(feature = "keepalive-anchors")] #[used(compiler)] static G2: unsafe extern "C" fn(u64, f64, u32, u32, *const i8, usize, *const u8) -> i32 = js_typed_feedback_method_direct_call_guard; #[cfg(feature = "keepalive-anchors")] - #[used(compiler)] static G3: extern "C" fn(u64, f64, *const u8, u32, u32) -> i32 = js_typed_feedback_closure_direct_call_guard; + #[used(compiler)] static G3: extern "C" fn(u64, f64, *const crate::closure::JsFunctionInfo, u32, u32) -> i32 = js_typed_feedback_closure_direct_call_guard; #[cfg(feature = "keepalive-anchors")] - #[used(compiler)] static G3B: extern "C" fn(f64, *const u8) -> u64 = js_closure_exact_func_guard; + #[used(compiler)] static G3B: extern "C" fn(f64, *const crate::closure::JsFunctionInfo) -> u64 = js_closure_exact_func_guard; #[cfg(feature = "keepalive-anchors")] - #[used(compiler)] static G3C: unsafe extern "C" fn(f64, u32, u32, *const i8, usize, *const u8, *mut MethodPicCacheSlot) -> u64 = js_object_own_method_cache_miss; + #[used(compiler)] static G3C: unsafe extern "C" fn(f64, u32, u32, *const i8, usize, *const crate::closure::JsFunctionInfo, *mut MethodPicCacheSlot) -> u64 = js_object_own_method_cache_miss; #[cfg(feature = "keepalive-anchors")] #[used(compiler)] static G4: unsafe extern "C" fn(f64, u32, u32, u32) -> i32 = js_method_direct_shape_guard; #[cfg(feature = "keepalive-anchors")] diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index ec959d1790..9bdc91e7db 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -32,8 +32,20 @@ extern "C" fn test_direct_closure( arg } -fn test_direct_closure_ptr() -> *const u8 { - test_direct_closure as *const () as *const u8 +static TEST_DIRECT_CLOSURE_INFO: crate::closure::JsFunctionInfo = + crate::closure::JsFunctionInfo::of( + test_direct_closure as crate::codegen_abi::JsBody1, + ) + .with_declared(1); + +/// A second one-parameter body, for a function object that must differ from +/// `test_direct_closure`'s. +extern "C" fn test_replacement_closure( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { + arg } fn test_direct_method_ptr() -> *const u8 { @@ -1383,19 +1395,19 @@ fn representation_lowering_helpers_have_lto_keepalive_anchors() { ( guards, "static G3", - "static G3: extern \"C\" fn(u64, f64, *const u8, u32, u32) -> i32", + "static G3: extern \"C\" fn(u64, f64, *const crate::closure::JsFunctionInfo, u32, u32) -> i32", "js_typed_feedback_closure_direct_call_guard", ), ( guards, "static G3B", - "static G3B: extern \"C\" fn(f64, *const u8) -> u64", + "static G3B: extern \"C\" fn(f64, *const crate::closure::JsFunctionInfo) -> u64", "js_closure_exact_func_guard", ), ( guards, "static G3C", - "static G3C: unsafe extern \"C\" fn(f64, u32, u32, *const i8, usize, *const u8, *mut MethodPicCacheSlot) -> u64", + "static G3C: unsafe extern \"C\" fn(f64, u32, u32, *const i8, usize, *const crate::closure::JsFunctionInfo, *mut MethodPicCacheSlot) -> u64", "js_object_own_method_cache_miss", ), ( @@ -1770,6 +1782,7 @@ fn typed_feedback_class_field_set_guard_falls_back_for_class_setter() { b"x".as_ptr(), 1, test_class_field_setter as *const () as usize as i64, + 1, ); } @@ -1823,6 +1836,7 @@ fn typed_feedback_class_field_set_guard_falls_back_for_class_setter() { b"x".as_ptr(), 1, test_class_field_setter as *const () as usize as i64, + 1, ); } crate::object::js_object_set_field_by_name(bare, key, 7.0); @@ -2466,14 +2480,13 @@ fn typed_feedback_closure_direct_guard_passes_and_rejects_bound_sentinel() { reset_typed_feedback_for_tests(); register(66, TypedFeedbackSiteKind::ClosureCall, "cb()"); - let fn_ptr = test_direct_closure_ptr(); - crate::closure::js_register_closure_arity(fn_ptr, 1); - let closure = crate::closure::js_closure_alloc_singleton(fn_ptr); + let fn_ptr: *const crate::closure::JsFunctionInfo = &TEST_DIRECT_CLOSURE_INFO; + let closure = crate::closure::js_closure_alloc_singleton(&TEST_DIRECT_CLOSURE_INFO); let closure_value = crate::value::js_nanbox_pointer(closure as i64); let pass = js_typed_feedback_closure_direct_call_guard(66, closure_value, fn_ptr, 1, 1); assert_eq!(pass, 1); - let bound = crate::closure::js_closure_alloc(crate::closure::BOUND_METHOD_FUNC_PTR, 0); + let bound = crate::closure::js_closure_alloc(&crate::closure::BOUND_METHOD_INFO, 0); let bound_value = crate::value::js_nanbox_pointer(bound as i64); let fail = js_typed_feedback_closure_direct_call_guard(66, bound_value, fn_ptr, 1, 1); assert_eq!(fail, 0); @@ -2485,19 +2498,19 @@ fn typed_feedback_closure_direct_guard_passes_and_rejects_bound_sentinel() { #[test] fn exact_closure_func_guard_is_safe_and_identity_exact() { - let fn_ptr = test_direct_closure_ptr(); - let closure = crate::closure::js_closure_alloc_singleton(fn_ptr); + let fn_ptr: *const crate::closure::JsFunctionInfo = &TEST_DIRECT_CLOSURE_INFO; + let closure = crate::closure::js_closure_alloc_singleton(&TEST_DIRECT_CLOSURE_INFO); let closure_value = crate::value::js_nanbox_pointer(closure as i64); assert_eq!( js_closure_exact_func_guard(closure_value, fn_ptr), closure as u64 ); assert_eq!( - js_closure_exact_func_guard(closure_value, test_direct_method_ptr()), + js_closure_exact_func_guard(closure_value, crate::fn_info!(test_replacement_closure, 1),), 0 ); - let bound = crate::closure::js_closure_alloc(crate::closure::BOUND_METHOD_FUNC_PTR, 0); + let bound = crate::closure::js_closure_alloc(&crate::closure::BOUND_METHOD_INFO, 0); let bound_value = crate::value::js_nanbox_pointer(bound as i64); assert_eq!(js_closure_exact_func_guard(bound_value, fn_ptr), 0); assert_eq!(js_closure_exact_func_guard(42.0, fn_ptr), 0); @@ -2518,8 +2531,8 @@ fn own_method_cache_accepts_appends_and_rejects_live_method_mutation() { let method_key = crate::string::js_string_from_bytes(b"method".as_ptr(), 6); let extra_key = crate::string::js_string_from_bytes(b"state".as_ptr(), 5); let spilled_key = crate::string::js_string_from_bytes(b"spilled".as_ptr(), 7); - let fn_ptr = test_direct_closure_ptr(); - let closure = crate::closure::js_closure_alloc_singleton(fn_ptr); + let fn_ptr: *const crate::closure::JsFunctionInfo = &TEST_DIRECT_CLOSURE_INFO; + let closure = crate::closure::js_closure_alloc_singleton(&TEST_DIRECT_CLOSURE_INFO); let closure_value = crate::value::js_nanbox_pointer(closure as i64); crate::object::js_object_set_field_by_name(object, method_key, closure_value); let receiver = crate::value::js_nanbox_pointer(object as i64); @@ -2578,7 +2591,8 @@ fn own_method_cache_accepts_appends_and_rejects_live_method_mutation() { assert_eq!(after_spilled_append, closure as u64); assert_ne!(cache[0], 0); - let replacement = crate::closure::js_closure_alloc_singleton(test_direct_method_ptr()); + let replacement = + crate::closure::js_closure_alloc_singleton(crate::fn_info!(test_replacement_closure, 1)); crate::object::js_object_set_field_by_name( object, method_key, diff --git a/crates/perry-runtime/src/url/abort.rs b/crates/perry-runtime/src/url/abort.rs index 25c4465148..2678208446 100644 --- a/crates/perry-runtime/src/url/abort.rs +++ b/crates/perry-runtime/src/url/abort.rs @@ -90,11 +90,10 @@ extern "C" fn abort_signal_remove_event_listener_method( fn abort_signal_listener_method_value(signal: *mut ObjectHeader, add: bool) -> f64 { let func = if add { - abort_signal_add_event_listener_method as *const u8 + crate::fn_info!(abort_signal_add_event_listener_method, 2; with_declared(2)) } else { - abort_signal_remove_event_listener_method as *const u8 + crate::fn_info!(abort_signal_remove_event_listener_method, 2; with_declared(2)) }; - crate::closure::js_register_closure_arity(func, 2); let closure = crate::closure::js_closure_alloc(func, 1); crate::closure::js_closure_set_capture_ptr(closure, 0, signal as i64); crate::value::js_nanbox_pointer(closure as i64) @@ -113,8 +112,7 @@ extern "C" fn abort_controller_abort_method( } fn abort_controller_abort_value(controller: *mut ObjectHeader) -> f64 { - let func = abort_controller_abort_method as *const u8; - crate::closure::js_register_closure_arity(func, 1); + let func = crate::fn_info!(abort_controller_abort_method, 1; with_declared(1)); let closure = crate::closure::js_closure_alloc(func, 1); let controller_value = crate::value::js_nanbox_pointer(controller as i64); crate::closure::js_closure_set_capture_ptr(closure, 0, controller_value.to_bits() as i64); @@ -284,8 +282,7 @@ extern "C" fn abort_error_constructor_thunk( } fn abort_error_constructor_value() -> f64 { - let func = abort_error_constructor_thunk as *const u8; - crate::closure::js_register_closure_arity(func, 0); + let func = crate::fn_info!(abort_error_constructor_thunk, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(func, 0); crate::object::set_bound_native_closure_name(closure, "AbortError"); crate::value::js_nanbox_pointer(closure as i64) @@ -475,13 +472,18 @@ extern "C" fn abort_signal_throw_if_aborted_thunk( /// as its NaN-boxed bits (the `aborted_resolve_listener` idiom) so the GC's /// closure scan keeps it alive and relocates it. pub(crate) fn abort_signal_method_bind(signal: *mut ObjectHeader, name: &[u8]) -> Option { - let (fp, arity): (*const u8, u32) = match name { - b"addEventListener" => (abort_signal_add_event_listener_thunk as *const u8, 3), - b"removeEventListener" => (abort_signal_remove_event_listener_thunk as *const u8, 3), - b"throwIfAborted" => (abort_signal_throw_if_aborted_thunk as *const u8, 0), + let fp = match name { + b"addEventListener" => { + crate::fn_info!(abort_signal_add_event_listener_thunk, 3; with_declared(3)) + } + b"removeEventListener" => { + crate::fn_info!(abort_signal_remove_event_listener_thunk, 3; with_declared(3)) + } + b"throwIfAborted" => { + crate::fn_info!(abort_signal_throw_if_aborted_thunk, 0; with_declared(0)) + } _ => return None, }; - crate::closure::js_register_closure_arity(fp, arity); let closure = crate::closure::js_closure_alloc(fp, 1); let signal_f64 = f64::from_bits(crate::value::js_nanbox_pointer(signal as i64).to_bits()); crate::closure::js_closure_set_capture_ptr(closure, 0, signal_f64.to_bits() as i64); @@ -580,8 +582,7 @@ extern "C" fn abort_signal_timeout_fire( #[no_mangle] pub extern "C" fn js_abort_signal_timeout(ms: f64) -> *mut ObjectHeader { let signal = alloc_abort_signal(); - let func = abort_signal_timeout_fire as *const u8; - crate::closure::js_register_closure_arity(func, 0); + let func = crate::fn_info!(abort_signal_timeout_fire, 0; with_declared(0)); let closure = crate::closure::js_closure_alloc(func, 1); let signal_value = crate::value::js_nanbox_pointer(signal as i64); crate::closure::js_closure_set_capture_ptr(closure, 0, signal_value.to_bits() as i64); @@ -682,8 +683,7 @@ pub extern "C" fn js_abort_signal_any( } // Register a propagation listener that adopts this source's reason // when it later aborts. - let func = abort_any_propagate_thunk as *const u8; - crate::closure::js_register_closure_arity(func, 1); + let func = crate::fn_info!(abort_any_propagate_thunk, 1; with_declared(1)); let closure = crate::closure::js_closure_alloc(func, 2); crate::closure::js_closure_set_capture_ptr(closure, 0, combined_box.to_bits() as i64); crate::closure::js_closure_set_capture_ptr(closure, 1, elem.to_bits() as i64); diff --git a/crates/perry-runtime/src/url/prototype.rs b/crates/perry-runtime/src/url/prototype.rs index f633578ad9..66168ce7b7 100644 --- a/crates/perry-runtime/src/url/prototype.rs +++ b/crates/perry-runtime/src/url/prototype.rs @@ -72,49 +72,73 @@ url_setter!(set_username, "username", js_url_set_username); url_setter!(set_password, "password", js_url_set_password); pub(crate) fn install_url_prototype_accessors(proto: *mut ObjectHeader) { - let entries: &[(&str, *const u8, Option<*const u8>)] = &[ - ("href", get_href as *const u8, Some(set_href as *const u8)), - ("origin", get_origin as *const u8, None), + type Info = *const crate::closure::JsFunctionInfo; + let entries: &[(&str, Info, Option)] = &[ + ( + "href", + crate::fn_info!(get_href, 0; with_declared(0)), + Some(crate::fn_info!(set_href, 1; with_declared(1))), + ), + ( + "origin", + crate::fn_info!(get_origin, 0; with_declared(0)), + None, + ), ( "protocol", - get_protocol as *const u8, - Some(set_protocol as *const u8), + crate::fn_info!(get_protocol, 0; with_declared(0)), + Some(crate::fn_info!(set_protocol, 1; with_declared(1))), ), ( "username", - get_username as *const u8, - Some(set_username as *const u8), + crate::fn_info!(get_username, 0; with_declared(0)), + Some(crate::fn_info!(set_username, 1; with_declared(1))), ), ( "password", - get_password as *const u8, - Some(set_password as *const u8), + crate::fn_info!(get_password, 0; with_declared(0)), + Some(crate::fn_info!(set_password, 1; with_declared(1))), + ), + ( + "host", + crate::fn_info!(get_host, 0; with_declared(0)), + Some(crate::fn_info!(set_host, 1; with_declared(1))), ), - ("host", get_host as *const u8, Some(set_host as *const u8)), ( "hostname", - get_hostname as *const u8, - Some(set_hostname as *const u8), + crate::fn_info!(get_hostname, 0; with_declared(0)), + Some(crate::fn_info!(set_hostname, 1; with_declared(1))), + ), + ( + "port", + crate::fn_info!(get_port, 0; with_declared(0)), + Some(crate::fn_info!(set_port, 1; with_declared(1))), ), - ("port", get_port as *const u8, Some(set_port as *const u8)), ( "pathname", - get_pathname as *const u8, - Some(set_pathname as *const u8), + crate::fn_info!(get_pathname, 0; with_declared(0)), + Some(crate::fn_info!(set_pathname, 1; with_declared(1))), ), ( "search", - get_search as *const u8, - Some(set_search as *const u8), + crate::fn_info!(get_search, 0; with_declared(0)), + Some(crate::fn_info!(set_search, 1; with_declared(1))), + ), + ( + "searchParams", + crate::fn_info!(get_search_params, 0; with_declared(0)), + None, + ), + ( + "hash", + crate::fn_info!(get_hash, 0; with_declared(0)), + Some(crate::fn_info!(set_hash, 1; with_declared(1))), ), - ("searchParams", get_search_params as *const u8, None), - ("hash", get_hash as *const u8, Some(set_hash as *const u8)), ]; let scope = crate::gc::RuntimeHandleScope::new(); let proto_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(proto as i64)); for &(name, getter, setter) in entries { unsafe { - crate::closure::js_register_closure_arity(getter, 0); let get = crate::closure::js_closure_alloc(getter, 0); let get_h = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(get as i64)); crate::object::native_module::set_bound_native_closure_name( @@ -125,7 +149,6 @@ pub(crate) fn install_url_prototype_accessors(proto: *mut ObjectHeader) { crate::object::native_module::set_builtin_closure_length(get, 0); crate::object::native_module::set_builtin_closure_non_constructable(get); let set_h = setter.map(|func| { - crate::closure::js_register_closure_arity(func, 1); let set = crate::closure::js_closure_alloc(func, 0); let handle = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(set as i64)); crate::object::native_module::set_bound_native_closure_name( diff --git a/crates/perry-runtime/src/url/search_params.rs b/crates/perry-runtime/src/url/search_params.rs index a34aa602d9..a631c47bb5 100644 --- a/crates/perry-runtime/src/url/search_params.rs +++ b/crates/perry-runtime/src/url/search_params.rs @@ -1260,16 +1260,15 @@ pub(crate) fn url_search_params_dynamic_call( /// object. Returns `None` for names outside the covered surface so unknown /// properties still read as `undefined`. pub(crate) fn url_search_params_method_value(obj: *const ObjectHeader, name: &str) -> Option { - let (func_ptr, arity): (*const u8, u32) = match name { - "append" => (usp_append_thunk as *const u8, 2), - "set" => (usp_set_thunk as *const u8, 2), - "get" => (usp_get_thunk as *const u8, 1), - "has" => (usp_has_thunk as *const u8, 1), - "delete" => (usp_delete_thunk as *const u8, 1), + let info = match name { + "append" => crate::fn_info!(usp_append_thunk, 2; with_declared(2)), + "set" => crate::fn_info!(usp_set_thunk, 2; with_declared(2)), + "get" => crate::fn_info!(usp_get_thunk, 1; with_declared(1)), + "has" => crate::fn_info!(usp_has_thunk, 1; with_declared(1)), + "delete" => crate::fn_info!(usp_delete_thunk, 1; with_declared(1)), _ => return None, }; - crate::closure::js_register_closure_arity(func_ptr, arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 1); + let closure = crate::closure::js_closure_alloc(info, 1); if closure.is_null() { return Some(f64::from_bits(crate::value::TAG_UNDEFINED)); } diff --git a/crates/perry-runtime/src/util_abort.rs b/crates/perry-runtime/src/util_abort.rs index e24a8aa4d6..52a121cc04 100644 --- a/crates/perry-runtime/src/util_abort.rs +++ b/crates/perry-runtime/src/util_abort.rs @@ -1,8 +1,7 @@ //! `node:util` AbortSignal helper surface. use crate::closure::{ - js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, - js_register_closure_arity, ClosureHeader, + js_closure_alloc, js_closure_get_capture_ptr, js_closure_set_capture_ptr, ClosureHeader, }; use crate::object::{js_object_alloc, js_object_set_field_by_name}; use crate::string::js_string_from_bytes; @@ -104,8 +103,7 @@ pub extern "C" fn js_util_aborted(signal: f64, resource: f64) -> f64 { let promise = crate::promise::js_promise_new(); let promise_value = js_nanbox_pointer(promise as i64); - let listener_func = aborted_resolve_listener as *const u8; - js_register_closure_arity(listener_func, 0); + let listener_func = crate::fn_info!(aborted_resolve_listener, 0; with_declared(0)); let listener = js_closure_alloc(listener_func, 2); js_closure_set_capture_ptr(listener, 0, promise_value.to_bits() as i64); js_closure_set_capture_ptr(listener, 1, signal.to_bits() as i64); diff --git a/crates/perry-runtime/src/util_debuglog.rs b/crates/perry-runtime/src/util_debuglog.rs index fc1f082bb1..70a7b8ff82 100644 --- a/crates/perry-runtime/src/util_debuglog.rs +++ b/crates/perry-runtime/src/util_debuglog.rs @@ -9,7 +9,7 @@ use std::cell::Cell; use crate::array::{js_array_length, ArrayHeader}; use crate::closure::{ js_closure_alloc, js_closure_call1, js_closure_get_capture_f64, js_closure_set_capture_f64, - js_register_closure_rest, ClosureHeader, + ClosureHeader, }; use crate::string::StringHeader; use crate::value::{ @@ -75,7 +75,6 @@ fn register_thunks_once() { if flag.get() { return; } - js_register_closure_rest(logger_thunk as *const u8, 0); flag.set(true); }); } @@ -256,7 +255,7 @@ pub extern "C" fn js_util_debuglog(section: f64, callback: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let section_handle = scope.root_nanbox_f64(section_value); let callback_handle = scope.root_nanbox_f64(callback); - let closure = js_closure_alloc(logger_thunk as *const u8, 4); + let closure = js_closure_alloc(crate::fn_info!(logger_thunk, 1; with_rest(0)), 4); if closure.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/util_mime.rs b/crates/perry-runtime/src/util_mime.rs index c4dd45c6ef..345a6dbd98 100644 --- a/crates/perry-runtime/src/util_mime.rs +++ b/crates/perry-runtime/src/util_mime.rs @@ -543,6 +543,7 @@ fn register_setter(class_id: u32, name: &'static str, func_ptr: usize) { name.as_ptr(), name.len() as i64, func_ptr as i64, + 1, ); } } diff --git a/crates/perry-runtime/src/util_promisify.rs b/crates/perry-runtime/src/util_promisify.rs index e0d1dc6745..87c14a6b41 100644 --- a/crates/perry-runtime/src/util_promisify.rs +++ b/crates/perry-runtime/src/util_promisify.rs @@ -30,8 +30,7 @@ use std::cell::Cell; use crate::array::{js_array_alloc, js_array_length, js_array_push_f64, ArrayHeader}; use crate::closure::{ js_closure_alloc, js_closure_get_capture_f64, js_closure_get_capture_ptr, - js_closure_set_capture_f64, js_closure_set_capture_ptr, js_register_closure_arity, - js_register_closure_rest, ClosureHeader, + js_closure_set_capture_f64, js_closure_set_capture_ptr, ClosureHeader, }; use crate::promise::{ js_promise_attach_handlers, js_promise_new, js_promise_reject, js_promise_resolve, @@ -152,19 +151,11 @@ fn register_thunks_once() { // Outer thunk: fixed_arity = 0 with a rest param, so dispatch // bundles ALL forwarded args into one array and we receive them // as `(closure, rest_array_nanbox)`. - js_register_closure_rest(outer_thunk as *const u8, 0); // Inner callback: declared arity 2 — `(err, value)`. Registering // the arity lets dispatch pad with `undefined` when callers // invoke it as `cb(err)` only, matching Node's contract. - js_register_closure_arity(inner_callback_thunk as *const u8, 2); - js_register_closure_rest(callbackify_outer_thunk as *const u8, 0); - js_register_closure_arity(callbackify_fulfilled_thunk as *const u8, 1); - js_register_closure_arity(callbackify_rejected_thunk as *const u8, 1); - js_register_closure_rest(deprecate_outer_thunk as *const u8, 0); // crypto.generateKeyPair promisify wrapper: outer is a rest function; // inner callback takes `(err, publicKey, privateKey)`. - js_register_closure_rest(gkp_outer_thunk as *const u8, 0); - js_register_closure_arity(gkp_inner_callback_thunk as *const u8, 3); crate::builtins::register_function_name_if_absent( deprecate_outer_thunk as *const () as usize, "deprecated", @@ -203,7 +194,7 @@ pub extern "C" fn js_util_promisify(fn_value: f64) -> f64 { register_thunks_once(); let scope = crate::gc::RuntimeHandleScope::new(); let fn_handle = scope.root_nanbox_f64(fn_value); - let closure = js_closure_alloc(gkp_outer_thunk as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(gkp_outer_thunk, 1; with_rest(0)), 1); if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -221,7 +212,7 @@ pub extern "C" fn js_util_promisify(fn_value: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fn_handle = scope.root_nanbox_f64(fn_value); - let closure = js_closure_alloc(outer_thunk as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(outer_thunk, 1; with_rest(0)), 1); if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -264,7 +255,7 @@ pub extern "C" fn js_util_deprecate(fn_value: f64, msg: f64, code: f64) -> f64 { let fn_handle = scope.root_nanbox_f64(fn_value); let msg_handle = scope.root_nanbox_f64(msg); let code_handle = scope.root_nanbox_f64(code); - let closure = js_closure_alloc(deprecate_outer_thunk as *const u8, 4); + let closure = js_closure_alloc(crate::fn_info!(deprecate_outer_thunk, 1; with_rest(0)), 4); if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -301,7 +292,7 @@ pub extern "C" fn js_util_callbackify(fn_value: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fn_handle = scope.root_nanbox_f64(fn_value); - let closure = js_closure_alloc(callbackify_outer_thunk as *const u8, 1); + let closure = js_closure_alloc(crate::fn_info!(callbackify_outer_thunk, 1; with_rest(0)), 1); if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -345,8 +336,12 @@ extern "C" fn outer_thunk( // Allocate the inner (err, value) callback that captures the promise. // #7341: the alloc and the re-read are one step, so the pre-alloc promise // address is never nameable on the early-return path. - let (cb_closure, promise_after_alloc) = promise_handle - .across_mut::(|| js_closure_alloc(inner_callback_thunk as *const u8, 1)); + let (cb_closure, promise_after_alloc) = promise_handle.across_mut::(|| { + js_closure_alloc( + crate::fn_info!(inner_callback_thunk, 2; with_declared(2)), + 1, + ) + }); if cb_closure.is_null() { return nanbox_promise(promise_after_alloc); } @@ -488,8 +483,12 @@ extern "C" fn gkp_outer_thunk( // Inner `(err, publicKey, privateKey)` callback capturing the promise. // #7341: as above — alloc and re-read as one step. - let (cb_closure, promise_after_alloc) = promise_handle - .across_mut::(|| js_closure_alloc(gkp_inner_callback_thunk as *const u8, 1)); + let (cb_closure, promise_after_alloc) = promise_handle.across_mut::(|| { + js_closure_alloc( + crate::fn_info!(gkp_inner_callback_thunk, 3; with_declared(3)), + 1, + ) + }); if cb_closure.is_null() { return nanbox_promise(promise_after_alloc); } @@ -767,7 +766,10 @@ extern "C" fn callbackify_outer_thunk( let returned_handle = scope.root_nanbox_f64(returned); // Build the onFulfilled / onRejected closures (bound to the user callback). - let fulfilled = js_closure_alloc(callbackify_fulfilled_thunk as *const u8, 1); + let fulfilled = js_closure_alloc( + crate::fn_info!(callbackify_fulfilled_thunk, 1; with_declared(1)), + 1, + ); if fulfilled.is_null() { return TAG_UNDEFINED_F64; } @@ -778,7 +780,10 @@ extern "C" fn callbackify_outer_thunk( callback_handle.get_nanbox_f64(), ); - let rejected = js_closure_alloc(callbackify_rejected_thunk as *const u8, 1); + let rejected = js_closure_alloc( + crate::fn_info!(callbackify_rejected_thunk, 1; with_declared(1)), + 1, + ); if rejected.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/v8.rs b/crates/perry-runtime/src/v8.rs index 4f8b6ae8dd..6a30a82d52 100644 --- a/crates/perry-runtime/src/v8.rs +++ b/crates/perry-runtime/src/v8.rs @@ -14,7 +14,7 @@ use std::sync::{LazyLock, Mutex}; use crate::closure::{ js_closure_alloc, js_closure_call1, js_closure_call2, js_closure_get_capture_ptr, - js_closure_set_capture_ptr, js_register_closure_arity, ClosureHeader, + js_closure_set_capture_ptr, ClosureHeader, }; use crate::object::{js_object_get_field_by_name, ObjectHeader}; use crate::promise::Promise; @@ -163,7 +163,6 @@ fn register_stop_trampoline_once() { if registered.get() { return; } - js_register_closure_arity(promise_hook_stop_trampoline as *const u8, 0); registered.set(true); }); } @@ -182,7 +181,10 @@ extern "C" fn promise_hook_stop_trampoline( fn make_stop_function(index: usize) -> f64 { register_stop_trampoline_once(); - let closure = js_closure_alloc(promise_hook_stop_trampoline as *const u8, 1); + let closure = js_closure_alloc( + crate::fn_info!(promise_hook_stop_trampoline, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, index as i64); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } diff --git a/crates/perry-runtime/src/value/to_string_array.rs b/crates/perry-runtime/src/value/to_string_array.rs index 2031c95e14..0fd351441c 100644 --- a/crates/perry-runtime/src/value/to_string_array.rs +++ b/crates/perry-runtime/src/value/to_string_array.rs @@ -77,7 +77,7 @@ pub(crate) unsafe fn array_prototype_to_string_override(value: f64) -> ArrayToSt return ArrayToStringOutcome::UseDefaultJoin; } let closure = method_ptr as *const crate::closure::ClosureHeader; - if (*closure).func_ptr == crate::object::global_this_builtin_noop_thunk as *const u8 { + if (*closure).code() == crate::object::global_this_builtin_noop_thunk as *const u8 { return ArrayToStringOutcome::UseDefaultJoin; } let receiver = value_handle.get_nanbox_f64(); diff --git a/crates/perry-runtime/src/wasi.rs b/crates/perry-runtime/src/wasi.rs index 8827ffc69a..b2b50e2807 100644 --- a/crates/perry-runtime/src/wasi.rs +++ b/crates/perry-runtime/src/wasi.rs @@ -377,17 +377,15 @@ fn validate_options(options: f64) -> ValidatedOptions { } } -fn closure_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_arity(func_ptr, arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +fn closure_value(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) } -fn closure_rest_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { - crate::closure::js_register_closure_rest(func_ptr, arity); - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +fn closure_rest_value(info: *const crate::closure::JsFunctionInfo, name: &str, arity: u32) -> f64 { + let closure = crate::closure::js_closure_alloc(info, 0); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, arity); crate::value::js_nanbox_pointer(closure as i64) @@ -405,9 +403,8 @@ fn create_import_function(import_value: f64, name: &str) -> f64 { }; // The native thunk has four ABI arguments; keep dispatch from invoking it // through a shorter function pointer while exposing Node's public length. - crate::closure::js_register_closure_arity(js_wasi_import_stub as *const u8, 4); let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( - js_wasi_import_stub as *const u8, + crate::fn_info!(js_wasi_import_stub, 4; with_declared(4)), 2, )); let closure_ptr = closure.get_raw_mut_ptr::(); @@ -475,7 +472,7 @@ fn ensure_wasi_prototype() { crate::object::class_prototype_object_root_store(CLASS_ID_WASI, proto); let method_scope = crate::gc::RuntimeHandleScope::new(); let method = method_scope.root_nanbox_f64(closure_value( - js_wasi_get_import_object as *const u8, + crate::fn_info!(js_wasi_get_import_object, 0; with_declared(0)), "getImportObject", 0, )); @@ -484,14 +481,18 @@ fn ensure_wasi_prototype() { 1, JSValue::from_bits(method.get_nanbox_f64().to_bits()), ); - method.set_nanbox_f64(closure_value(js_wasi_start as *const u8, "start", 1)); + method.set_nanbox_f64(closure_value( + crate::fn_info!(js_wasi_start, 1; with_declared(1)), + "start", + 1, + )); crate::object::js_object_set_field( crate::object::class_prototype_object(CLASS_ID_WASI), 2, JSValue::from_bits(method.get_nanbox_f64().to_bits()), ); method.set_nanbox_f64(closure_value( - js_wasi_initialize as *const u8, + crate::fn_info!(js_wasi_initialize, 1; with_declared(1)), "initialize", 1, )); @@ -501,7 +502,7 @@ fn ensure_wasi_prototype() { JSValue::from_bits(method.get_nanbox_f64().to_bits()), ); method.set_nanbox_f64(closure_rest_value( - js_wasi_finalize_bindings as *const u8, + crate::fn_info!(js_wasi_finalize_bindings, 2; with_rest(1)), "finalizeBindings", 1, )); diff --git a/crates/perry-runtime/src/weakref/test_support.rs b/crates/perry-runtime/src/weakref/test_support.rs index a4f0022044..52dc59a116 100644 --- a/crates/perry-runtime/src/weakref/test_support.rs +++ b/crates/perry-runtime/src/weakref/test_support.rs @@ -91,8 +91,8 @@ fn empty_checkpoint_delivers_recorded_finalization_job() { 0.0 } crate::gc::js_gc_init(); - crate::closure::js_register_closure_arity(cleanup as *const u8, 1); - let closure = crate::closure::js_closure_alloc(cleanup as *const u8, 0); + let closure = + crate::closure::js_closure_alloc(crate::fn_info!(cleanup, 1; with_declared(1)), 0); let scope = crate::gc::RuntimeHandleScope::new(); let callback = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(closure as i64)); let registry = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_finreg_new( diff --git a/crates/perry-runtime/src/web_storage.rs b/crates/perry-runtime/src/web_storage.rs index 3d0eaaf541..41b0cffe4f 100644 --- a/crates/perry-runtime/src/web_storage.rs +++ b/crates/perry-runtime/src/web_storage.rs @@ -230,16 +230,31 @@ pub(crate) fn install_storage_globals( "Storage".to_string(), PropertyAttrs::new(true, false, true), ); - install_method(storage_proto, "clear", storage_clear as *const u8, 0); - install_method(storage_proto, "getItem", storage_get_item as *const u8, 1); - install_method(storage_proto, "key", storage_key as *const u8, 1); + install_method( + storage_proto, + "clear", + crate::fn_info!(storage_clear, 0; with_declared(0)), + ); + install_method( + storage_proto, + "getItem", + crate::fn_info!(storage_get_item, 1; with_declared(1)), + ); + install_method( + storage_proto, + "key", + crate::fn_info!(storage_key, 1; with_declared(1)), + ); install_method( storage_proto, "removeItem", - storage_remove_item as *const u8, - 1, + crate::fn_info!(storage_remove_item, 1; with_declared(1)), + ); + install_method( + storage_proto, + "setItem", + crate::fn_info!(storage_set_item, 2; with_declared(2)), ); - install_method(storage_proto, "setItem", storage_set_item as *const u8, 2); install_storage_length_accessor(storage_proto); let constructor_key = string("constructor"); @@ -264,12 +279,16 @@ pub(crate) fn install_storage_globals( set_global_storage_property(global, "sessionStorage", session); } -fn install_method(proto: *mut ObjectHeader, name: &str, func_ptr: *const u8, arity: u32) { - let closure = crate::closure::js_closure_alloc(func_ptr, 0); +/// `info` records the method's declared arity. +fn install_method( + proto: *mut ObjectHeader, + name: &str, + info: *const crate::closure::JsFunctionInfo, +) { + let closure = crate::closure::js_closure_alloc(info, 0); if closure.is_null() { return; } - crate::closure::js_register_closure_arity(func_ptr, arity); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 0); crate::object::define_builtin_data_property( @@ -292,11 +311,14 @@ fn install_method(proto: *mut ObjectHeader, name: &str, func_ptr: *const u8, ari } fn install_storage_length_accessor(proto: *mut ObjectHeader) { - let getter = crate::closure::js_closure_alloc(storage_length_getter as *const u8, 0); + let getter = crate::closure::js_closure_alloc( + crate::fn_info!(storage_length_getter, 0; with_declared(0)), + 0, + ); if getter.is_null() { return; } - crate::closure::js_register_closure_arity(storage_length_getter as *const u8, 0); + crate::object::set_bound_native_closure_name(getter, "get length"); crate::object::js_object_set_field_by_name( proto, @@ -322,23 +344,16 @@ fn make_storage_object(kind: StorageKind, proto: *mut ObjectHeader) -> *mut Obje let proto_bits = crate::value::js_nanbox_pointer(proto as i64).to_bits(); crate::object::prototype_chain::object_set_static_prototype(obj as usize, proto_bits); update_length_on_obj(obj, 0); - for (name, arity) in [ - ("clear", 0), - ("getItem", 1), - ("key", 1), - ("removeItem", 1), - ("setItem", 2), - ] { + for name in ["clear", "getItem", "key", "removeItem", "setItem"] { let func_ptr = match name { - "clear" => storage_clear as *const u8, - "getItem" => storage_get_item as *const u8, - "key" => storage_key as *const u8, - "removeItem" => storage_remove_item as *const u8, - _ => storage_set_item as *const u8, + "clear" => crate::fn_info!(storage_clear, 0; with_declared(0)), + "getItem" => crate::fn_info!(storage_get_item, 1; with_declared(1)), + "key" => crate::fn_info!(storage_key, 1; with_declared(1)), + "removeItem" => crate::fn_info!(storage_remove_item, 1; with_declared(1)), + _ => crate::fn_info!(storage_set_item, 2; with_declared(2)), }; let closure = crate::closure::js_closure_alloc(func_ptr, 0); if !closure.is_null() { - crate::closure::js_register_closure_arity(func_ptr, arity); crate::object::set_bound_native_closure_name(closure, name); crate::object::set_builtin_closure_length(closure as usize, 0); crate::object::define_builtin_data_property( @@ -367,14 +382,15 @@ fn set_global_storage_property(global: *mut ObjectHeader, name: &str, value: *mu ); let getter_fn = match name { - "localStorage" => storage_local_global_getter as *const u8, - _ => storage_session_global_getter as *const u8, + "localStorage" => crate::fn_info!(storage_local_global_getter, 0; with_declared(0)), + _ => crate::fn_info!(storage_session_global_getter, 0; with_declared(0)), }; let getter = crate::closure::js_closure_alloc(getter_fn, 0); - let setter = crate::closure::js_closure_alloc(storage_global_setter as *const u8, 0); + let setter = crate::closure::js_closure_alloc( + crate::fn_info!(storage_global_setter, 1; with_declared(1)), + 0, + ); if !getter.is_null() && !setter.is_null() { - crate::closure::js_register_closure_arity(getter_fn, 0); - crate::closure::js_register_closure_arity(storage_global_setter as *const u8, 1); crate::object::set_bound_native_closure_name(getter, name); crate::object::set_builtin_accessor_descriptor( global as usize, diff --git a/crates/perry-runtime/src/webassembly.rs b/crates/perry-runtime/src/webassembly.rs index 71ec7f104c..b58a284799 100644 --- a/crates/perry-runtime/src/webassembly.rs +++ b/crates/perry-runtime/src/webassembly.rs @@ -889,7 +889,7 @@ unsafe extern "C" fn resolve_wasm_import( return std::ptr::null_mut(); } let closure = pointer as *const crate::closure::ClosureHeader; - let fp = (*closure).func_ptr; + let fp = (*closure).code(); if is_wasm_export_call_shim(fp) { return crate::closure::js_closure_get_capture_f64(closure, 6) as usize as *mut c_void; } @@ -1043,7 +1043,7 @@ fn make_export_function( let memory = scope.root_nanbox_f64(memory); let instance = scope.root_nanbox_f64(instance); let imports = scope.root_nanbox_f64(imports); - let (func_ptr, declared_arity) = wasm_export_call_shim_for_arity(arity); + let (info, _declared_arity) = wasm_export_call_shim_for_arity(arity); // Resolve the export ONCE here rather than by name on every call: the // per-call `get_func` probe plus `FuncType` clone was the sub-microsecond // floor left under the linear-memory copy this binding removed (#9611). @@ -1053,7 +1053,7 @@ fn make_export_function( let external = unsafe { perry_wasm_host_instance_export_extern(inst, name.as_ptr() as *const c_char, name.len()) }; - let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(func_ptr, 7)); + let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(info, 7)); if closure .get_raw_mut_ptr::() .is_null() @@ -1061,7 +1061,6 @@ fn make_export_function( drop_host_extern_handle(external as usize); return nanbox_undefined(); } - crate::closure::js_register_closure_arity(func_ptr, declared_arity); let name_value = scope.root_nanbox_f64(string_value(name)); let closure_ptr = closure.get_raw_mut_ptr::(); crate::closure::js_closure_set_capture_f64(closure_ptr, 0, inst as usize as f64); @@ -1153,12 +1152,14 @@ fn make_global_object(external: *mut c_void, receiver: f64) -> f64 { placeholder.get_raw_const_ptr::(), nanbox_undefined(), ); - let getter_fp = js_wasm_global_get as *const u8; - let setter_fp = js_wasm_global_set as *const u8; - crate::closure::js_register_closure_arity(getter_fp, 0); - crate::closure::js_register_closure_arity(setter_fp, 1); - let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(getter_fp, 0)); - let setter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(setter_fp, 0)); + let getter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( + crate::fn_info!(js_wasm_global_get, 0; with_declared(0)), + 0, + )); + let setter = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( + crate::fn_info!(js_wasm_global_set, 1; with_declared(1)), + 0, + )); let object_ptr = object.get_raw_mut_ptr::(); crate::object::set_builtin_accessor_descriptor( object_ptr as usize, diff --git a/crates/perry-runtime/src/webassembly_calls.rs b/crates/perry-runtime/src/webassembly_calls.rs index 3d23bf1f5d..bfd6c66f8c 100644 --- a/crates/perry-runtime/src/webassembly_calls.rs +++ b/crates/perry-runtime/src/webassembly_calls.rs @@ -178,25 +178,78 @@ wasm_export_call_shim!( a15 ); -pub(super) fn wasm_export_call_shim_for_arity(arity: usize) -> (*const u8, u32) { +pub(super) fn wasm_export_call_shim_for_arity( + arity: usize, +) -> (*const crate::closure::JsFunctionInfo, u32) { match arity { - 0 => (js_wasm_export_call_0 as *const u8, 0), - 1 => (js_wasm_export_call_1 as *const u8, 1), - 2 => (js_wasm_export_call_2 as *const u8, 2), - 3 => (js_wasm_export_call_3 as *const u8, 3), - 4 => (js_wasm_export_call_4 as *const u8, 4), - 5 => (js_wasm_export_call_5 as *const u8, 5), - 6 => (js_wasm_export_call_6 as *const u8, 6), - 7 => (js_wasm_export_call_7 as *const u8, 7), - 8 => (js_wasm_export_call_8 as *const u8, 8), - 9 => (js_wasm_export_call_9 as *const u8, 9), - 10 => (js_wasm_export_call_10 as *const u8, 10), - 11 => (js_wasm_export_call_11 as *const u8, 11), - 12 => (js_wasm_export_call_12 as *const u8, 12), - 13 => (js_wasm_export_call_13 as *const u8, 13), - 14 => (js_wasm_export_call_14 as *const u8, 14), - 15 => (js_wasm_export_call_15 as *const u8, 15), - _ => (js_wasm_export_call_16 as *const u8, 16), + 0 => ( + crate::fn_info!(js_wasm_export_call_0, 0; with_declared(0)), + 0, + ), + 1 => ( + crate::fn_info!(js_wasm_export_call_1, 1; with_declared(1)), + 1, + ), + 2 => ( + crate::fn_info!(js_wasm_export_call_2, 2; with_declared(2)), + 2, + ), + 3 => ( + crate::fn_info!(js_wasm_export_call_3, 3; with_declared(3)), + 3, + ), + 4 => ( + crate::fn_info!(js_wasm_export_call_4, 4; with_declared(4)), + 4, + ), + 5 => ( + crate::fn_info!(js_wasm_export_call_5, 5; with_declared(5)), + 5, + ), + 6 => ( + crate::fn_info!(js_wasm_export_call_6, 6; with_declared(6)), + 6, + ), + 7 => ( + crate::fn_info!(js_wasm_export_call_7, 7; with_declared(7)), + 7, + ), + 8 => ( + crate::fn_info!(js_wasm_export_call_8, 8; with_declared(8)), + 8, + ), + 9 => ( + crate::fn_info!(js_wasm_export_call_9, 9; with_declared(9)), + 9, + ), + 10 => ( + crate::fn_info!(js_wasm_export_call_10, 10; with_declared(10)), + 10, + ), + 11 => ( + crate::fn_info!(js_wasm_export_call_11, 11; with_declared(11)), + 11, + ), + 12 => ( + crate::fn_info!(js_wasm_export_call_12, 12; with_declared(12)), + 12, + ), + 13 => ( + crate::fn_info!(js_wasm_export_call_13, 13; with_declared(13)), + 13, + ), + 14 => ( + crate::fn_info!(js_wasm_export_call_14, 14; with_declared(14)), + 14, + ), + 15 => ( + crate::fn_info!(js_wasm_export_call_15, 15; with_declared(15)), + 15, + ), + _ => ( + crate::fn_info!(js_wasm_export_call_16, 16; with_declared(16)), + 16, + ), } } @@ -244,9 +297,10 @@ pub(super) fn make_instance_result(module: *mut c_void, inst: *mut c_void, impor module.get_nanbox_f64(), instance.get_nanbox_f64(), )); - let function = js_wasm_instance_result_then as *const u8; - crate::closure::js_register_closure_arity(function, 2); - let then = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(function, 1)); + let then = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc( + crate::fn_info!(js_wasm_instance_result_then, 2; with_declared(2)), + 1, + )); then.with_mut_ptr(|t: *mut crate::closure::ClosureHeader| { crate::closure::js_closure_set_capture_f64(t, 0, settlement.get_nanbox_f64()) }); diff --git a/crates/perry-runtime/src/webassembly_table.rs b/crates/perry-runtime/src/webassembly_table.rs index f7157f5adc..caa653cb0c 100644 --- a/crates/perry-runtime/src/webassembly_table.rs +++ b/crates/perry-runtime/src/webassembly_table.rs @@ -50,7 +50,7 @@ pub(super) fn wasm_function_external(value: f64) -> *mut c_void { if header.obj_type != crate::gc::GC_TYPE_CLOSURE { return std::ptr::null_mut(); } - let fp = unsafe { (*closure).func_ptr }; + let fp = unsafe { (*closure).code() }; if !is_wasm_export_call_shim(fp) { return std::ptr::null_mut(); } @@ -238,18 +238,16 @@ pub(super) fn make_table_method( inst: *mut c_void, name: f64, table: f64, - func_ptr: *const u8, - arity: u32, + info: *const crate::closure::JsFunctionInfo, display_name: &str, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let name = scope.root_nanbox_f64(name); let table = scope.root_nanbox_f64(table); - let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(func_ptr, 4)); + let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(info, 4)); if closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| closure.is_null()) { return nanbox_undefined(); } - crate::closure::js_register_closure_arity(func_ptr, arity); closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { crate::closure::js_closure_set_capture_f64(closure, 0, external as usize as f64) }); @@ -276,14 +274,13 @@ pub(super) fn make_table_function(external: *mut c_void) -> f64 { drop_host_extern_handle(external as usize); return nanbox_undefined(); } - let (func_ptr, declared_arity) = wasm_export_call_shim_for_arity(arity); + let (info, _declared_arity) = wasm_export_call_shim_for_arity(arity); let scope = crate::gc::RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(func_ptr, 7)); + let closure = scope.root_raw_mut_ptr(crate::closure::js_closure_alloc(info, 7)); if closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| closure.is_null()) { drop_host_extern_handle(external as usize); return nanbox_undefined(); } - crate::closure::js_register_closure_arity(func_ptr, declared_arity); for index in 0..6 { closure.with_mut_ptr(|closure: *mut crate::closure::ClosureHeader| { crate::closure::js_closure_set_capture_f64(closure, index, nanbox_undefined()) @@ -349,18 +346,26 @@ pub(super) fn make_table_object( as *mut crate::object::ObjectHeader; let _ = object_set(table_ptr, b"__wasmValues", values.get_nanbox_f64()); let methods = [ - ("get", js_wasm_table_get as *const u8, 1u32), - ("grow", js_wasm_table_grow as *const u8, 2u32), - ("set", js_wasm_table_set as *const u8, 2u32), + ( + "get", + crate::fn_info!(js_wasm_table_get, 1; with_declared(1)), + ), + ( + "grow", + crate::fn_info!(js_wasm_table_grow, 2; with_declared(2)), + ), + ( + "set", + crate::fn_info!(js_wasm_table_set, 2; with_declared(2)), + ), ]; - for (method_name, func_ptr, arity) in methods { + for (method_name, info) in methods { let method = scope.root_nanbox_f64(make_table_method( external, inst, name.get_nanbox_f64(), table.get_nanbox_f64(), - func_ptr, - arity, + info, method_name, )); let table_ptr = JSValue::from_bits(table.get_nanbox_f64().to_bits()) diff --git a/crates/perry-stdlib/src/bun_sql.rs b/crates/perry-stdlib/src/bun_sql.rs index bb2289b2f9..6f733c600e 100644 --- a/crates/perry-stdlib/src/bun_sql.rs +++ b/crates/perry-stdlib/src/bun_sql.rs @@ -10,8 +10,7 @@ use crate::sqlite::*; use perry_runtime::{ closure::{ closure_set_dynamic_prop, js_closure_alloc, js_closure_call1, js_closure_call2, - js_closure_get_capture_f64, js_closure_set_capture_f64, js_register_closure_arity, - js_register_closure_rest, ClosureHeader, + js_closure_get_capture_f64, js_closure_set_capture_f64, ClosureHeader, }, exception::{js_call_catching, js_throw}, gc::RuntimeHandleScope, @@ -19,7 +18,6 @@ use perry_runtime::{ promise::{js_promise_reject, js_promise_resolved_catching, js_promise_then}, ArrayHeader, JSValue, Promise, }; -use std::sync::Once; const CLIENT_HANDLE_CAPTURE: u32 = 0; const METHOD_HANDLE_CAPTURE: u32 = 0; @@ -249,7 +247,7 @@ extern "C" fn bun_sql_begin( let callback_promise = scope.root_raw_mut_ptr(callback_promise); let fulfilled = scope.root_raw_mut_ptr(js_closure_alloc( - bun_sql_transaction_fulfilled as *const u8, + perry_runtime::fn_info!(bun_sql_transaction_fulfilled, 1; with_declared(1)), 2, )); js_closure_set_capture_f64(fulfilled.get_raw_mut_ptr(), 0, db_handle as f64); @@ -259,7 +257,7 @@ extern "C" fn bun_sql_begin( if nested { 1.0 } else { 0.0 }, ); let rejected = scope.root_raw_mut_ptr(js_closure_alloc( - bun_sql_transaction_rejected as *const u8, + perry_runtime::fn_info!(bun_sql_transaction_rejected, 1; with_declared(1)), 2, )); js_closure_set_capture_f64(rejected.get_raw_mut_ptr(), 0, db_handle as f64); @@ -347,25 +345,13 @@ extern "C" fn bun_sql_close( } } -static BUN_SQL_CLOSURES_REGISTERED: Once = Once::new(); - -fn register_closure_shapes() { - BUN_SQL_CLOSURES_REGISTERED.call_once(|| { - js_register_closure_rest(bun_sql_query_tag as *const u8, 1); - js_register_closure_arity(bun_sql_begin as *const u8, 1); - js_register_closure_arity(bun_sql_reserve as *const u8, 1); - js_register_closure_arity(bun_sql_release as *const u8, 0); - js_register_closure_arity(bun_sql_close as *const u8, 1); - js_register_closure_arity(bun_sql_transaction_fulfilled as *const u8, 1); - js_register_closure_arity(bun_sql_transaction_rejected as *const u8, 1); - }); -} - unsafe fn make_client(handle: Handle, onclose: f64) -> f64 { - register_closure_shapes(); let scope = RuntimeHandleScope::new(); let onclose = scope.root_nanbox_f64(onclose); - let client = scope.root_raw_mut_ptr(js_closure_alloc(bun_sql_query_tag as *const u8, 1)); + let client = scope.root_raw_mut_ptr(js_closure_alloc( + perry_runtime::fn_info!(bun_sql_query_tag, 2; with_rest(1)), + 1, + )); js_closure_set_capture_f64( client.get_raw_mut_ptr(), CLIENT_HANDLE_CAPTURE, @@ -373,10 +359,22 @@ unsafe fn make_client(handle: Handle, onclose: f64) -> f64 { ); for (name, function) in [ - ("begin", bun_sql_begin as *const u8), - ("reserve", bun_sql_reserve as *const u8), - ("release", bun_sql_release as *const u8), - ("close", bun_sql_close as *const u8), + ( + "begin", + perry_runtime::fn_info!(bun_sql_begin, 1; with_declared(1)), + ), + ( + "reserve", + perry_runtime::fn_info!(bun_sql_reserve, 1; with_declared(1)), + ), + ( + "release", + perry_runtime::fn_info!(bun_sql_release, 0; with_declared(0)), + ), + ( + "close", + perry_runtime::fn_info!(bun_sql_close, 1; with_declared(1)), + ), ] { // Allocate first, then re-read every rooted pointer/value. The method // allocation itself can move the client and the onclose callback. diff --git a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs index d29886b708..46cf51bf9d 100644 --- a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs +++ b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs @@ -122,12 +122,8 @@ extern "C" fn async_local_storage_unbound_method_thunk( } pub(crate) fn unbound_async_local_storage_method(method: &'static [u8]) -> f64 { - perry_runtime::closure::js_register_closure_rest( - async_local_storage_unbound_method_thunk as *const u8, - 0, - ); let closure = perry_runtime::closure::js_closure_alloc( - async_local_storage_unbound_method_thunk as *const u8, + perry_runtime::fn_info!(async_local_storage_unbound_method_thunk, 1; with_rest(0)), 2, ); if closure.is_null() { diff --git a/crates/perry-stdlib/src/crypto/random.rs b/crates/perry-stdlib/src/crypto/random.rs index 66c200cc71..d4d788d2a6 100644 --- a/crates/perry-stdlib/src/crypto/random.rs +++ b/crates/perry-stdlib/src/crypto/random.rs @@ -982,8 +982,10 @@ mod tests { } fn make_record_callback() -> f64 { - perry_runtime::closure::js_register_closure_arity(record_cb_thunk as *const u8, 2); - let closure = perry_runtime::closure::js_closure_alloc(record_cb_thunk as *const u8, 0); + let closure = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(record_cb_thunk, 2; with_declared(2)), + 0, + ); boxed_ptr(closure as *const u8) } diff --git a/crates/perry-stdlib/src/domain.rs b/crates/perry-stdlib/src/domain.rs index 89f5474af7..f3e5f2cad6 100644 --- a/crates/perry-stdlib/src/domain.rs +++ b/crates/perry-stdlib/src/domain.rs @@ -128,10 +128,7 @@ fn ensure_gc_scanner_registered() { } fn ensure_wrapper_closures_registered() { - DOMAIN_WRAPPERS_REGISTERED.call_once(|| { - perry_runtime::closure::js_register_closure_rest(domain_bound_wrapper as *const u8, 0); - perry_runtime::closure::js_register_closure_rest(domain_intercept_wrapper as *const u8, 0); - }); + DOMAIN_WRAPPERS_REGISTERED.call_once(|| {}); } /// `HANDLES` payload releaser (#11471): see `register_handle_payload_releaser`. @@ -397,7 +394,10 @@ pub unsafe extern "C" fn js_domain_run( #[no_mangle] pub unsafe extern "C" fn js_domain_bind(handle: Handle, callback: f64) -> f64 { ensure_wrapper_closures_registered(); - let closure = perry_runtime::closure::js_closure_alloc(domain_bound_wrapper as *const u8, 2); + let closure = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(domain_bound_wrapper, 1; with_rest(0)), + 2, + ); perry_runtime::closure::js_closure_set_capture_ptr(closure, 0, handle); perry_runtime::closure::js_closure_set_capture_f64(closure, 1, callback); js_nanbox_pointer(closure as i64) @@ -406,8 +406,10 @@ pub unsafe extern "C" fn js_domain_bind(handle: Handle, callback: f64) -> f64 { #[no_mangle] pub unsafe extern "C" fn js_domain_intercept(handle: Handle, callback: f64) -> f64 { ensure_wrapper_closures_registered(); - let closure = - perry_runtime::closure::js_closure_alloc(domain_intercept_wrapper as *const u8, 2); + let closure = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(domain_intercept_wrapper, 1; with_rest(0)), + 2, + ); perry_runtime::closure::js_closure_set_capture_ptr(closure, 0, handle); perry_runtime::closure::js_closure_set_capture_f64(closure, 1, callback); js_nanbox_pointer(closure as i64) diff --git a/crates/perry-stdlib/src/events.rs b/crates/perry-stdlib/src/events.rs index 8d5653af33..7fd535a192 100644 --- a/crates/perry-stdlib/src/events.rs +++ b/crates/perry-stdlib/src/events.rs @@ -654,12 +654,7 @@ fn validate_event_listener(listener_bits: i64) -> i64 { static RAW_ONCE_WRAPPER_REST_REGISTERED: std::sync::Once = std::sync::Once::new(); fn ensure_raw_once_wrapper_rest_registered() { - RAW_ONCE_WRAPPER_REST_REGISTERED.call_once(|| { - perry_runtime::closure::js_register_closure_rest( - event_emitter_once_wrapper as *const u8, - 0, - ); - }); + RAW_ONCE_WRAPPER_REST_REGISTERED.call_once(|| {}); } unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i64) -> i64 { @@ -668,8 +663,10 @@ unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i6 } ensure_raw_once_wrapper_rest_registered(); - let wrapper = - perry_runtime::closure::js_closure_alloc(event_emitter_once_wrapper as *const u8, 4); + let wrapper = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(event_emitter_once_wrapper, 1; with_rest(0)), + 4, + ); let event_ptr = js_string_from_bytes(event_name.as_ptr(), event_name.len() as u32); perry_runtime::closure::js_closure_set_capture_ptr(wrapper, 0, handle); perry_runtime::closure::js_closure_set_capture_ptr(wrapper, 1, event_ptr as i64); @@ -1089,7 +1086,10 @@ unsafe fn capture_listener_rejection(handle: Handle, result: f64) { if promise.is_null() { return; } - let on_rejected = js_closure_alloc(events_capture_rejection_handler as *const u8, 1); + let on_rejected = js_closure_alloc( + perry_runtime::fn_info!(events_capture_rejection_handler, 1), + 1, + ); js_closure_set_capture_ptr(on_rejected, 0, handle); perry_runtime::promise::js_promise_then(promise, std::ptr::null(), on_rejected); } diff --git a/crates/perry-stdlib/src/events/events_on.rs b/crates/perry-stdlib/src/events/events_on.rs index d842790751..cd47ec8293 100644 --- a/crates/perry-stdlib/src/events/events_on.rs +++ b/crates/perry-stdlib/src/events/events_on.rs @@ -74,12 +74,7 @@ fn events_resolved_iter_promise(value: f64, done: bool) -> f64 { f64::from_bits(JSValue::pointer(p as *const u8).bits()) } -fn register_events_on_arities() { - perry_runtime::closure::js_register_closure_arity(events_on_next as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(events_on_return as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(events_on_aiter_self as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(events_on_async_iterator as *const u8, 0); -} +fn register_events_on_arities() {} /// The queue listener fired for each emitted event. Resolves a blocked `next()` /// Promise immediately if one is waiting, otherwise buffers the `[arg]` array. @@ -226,17 +221,26 @@ extern "C" fn events_on_async_iterator( packed.as_ptr(), packed.len() as u32, ); - let next_cl = js_closure_alloc(events_on_next as *const u8, 1); + let next_cl = js_closure_alloc( + perry_runtime::fn_info!(events_on_next, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(next_cl, 0, state as i64); perry_runtime::object::js_object_set_field(obj, 0, JSValue::pointer(next_cl as *const u8)); - let ret_cl = js_closure_alloc(events_on_return as *const u8, 1); + let ret_cl = js_closure_alloc( + perry_runtime::fn_info!(events_on_return, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(ret_cl, 0, state as i64); perry_runtime::object::js_object_set_field(obj, 1, JSValue::pointer(ret_cl as *const u8)); let iter_val = f64::from_bits(JSValue::pointer(obj as *const u8).bits()); let async_iterator = perry_runtime::symbol::well_known_symbol("asyncIterator"); if !async_iterator.is_null() { - let self_cl = js_closure_alloc(events_on_aiter_self as *const u8, 1); + let self_cl = js_closure_alloc( + perry_runtime::fn_info!(events_on_aiter_self, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_f64(self_cl, 0, iter_val); unsafe { perry_runtime::symbol::js_object_set_symbol_property( @@ -257,7 +261,10 @@ unsafe fn install_events_on_async_iterator(queue: *mut ArrayHeader, state: *mut if async_iterator.is_null() { return; } - let closure = js_closure_alloc(events_on_async_iterator as *const u8, 1); + let closure = js_closure_alloc( + perry_runtime::fn_info!(events_on_async_iterator, 0; with_declared(0)), + 1, + ); js_closure_set_capture_ptr(closure, 0, state as i64); perry_runtime::symbol::js_object_set_symbol_property( js_nanbox_pointer(queue as i64), @@ -370,7 +377,7 @@ pub unsafe extern "C" fn js_events_on( perry_runtime::exception::js_throw(perry_runtime::url::js_abort_error_value()); } - let listener = js_closure_alloc(events_on_queue_listener as *const u8, 1); + let listener = js_closure_alloc(perry_runtime::fn_info!(events_on_queue_listener, 1), 1); js_closure_set_capture_ptr(listener, 0, state as i64); let handle = match target { @@ -418,7 +425,8 @@ pub unsafe extern "C" fn js_events_on( if let Some(signal) = signal { if let Some(signal_ptr) = object_ptr_from_value(signal) { - let abort_listener = js_closure_alloc(events_on_abort_listener as *const u8, 5); + let abort_listener = + js_closure_alloc(perry_runtime::fn_info!(events_on_abort_listener, 0), 5); js_closure_set_capture_ptr(abort_listener, 0, handle); js_closure_set_capture_ptr(abort_listener, 1, listener as i64); js_closure_set_capture_ptr(abort_listener, 2, signal_ptr as i64); diff --git a/crates/perry-stdlib/src/events/module_helpers.rs b/crates/perry-stdlib/src/events/module_helpers.rs index 1888a17f11..d2f1be9ffb 100644 --- a/crates/perry-stdlib/src/events/module_helpers.rs +++ b/crates/perry-stdlib/src/events/module_helpers.rs @@ -78,7 +78,8 @@ pub unsafe extern "C" fn js_events_add_abort_listener(signal: f64, listener: f64 let listener_val = js_nanbox_pointer(callback_ptr); perry_runtime::url::js_abort_signal_add_listener(signal_ptr, event_val, listener_val); - let dispose_closure = js_closure_alloc(events_abort_listener_dispose as *const u8, 2); + let dispose_closure = + js_closure_alloc(perry_runtime::fn_info!(events_abort_listener_dispose, 0), 2); js_closure_set_capture_ptr(dispose_closure, 0, signal_ptr as i64); js_closure_set_capture_ptr(dispose_closure, 1, callback_ptr); let dispose_val = js_nanbox_pointer(dispose_closure as i64); diff --git a/crates/perry-stdlib/src/events/once_helpers.rs b/crates/perry-stdlib/src/events/once_helpers.rs index 7bccf9dd6b..b02fbfaf6f 100644 --- a/crates/perry-stdlib/src/events/once_helpers.rs +++ b/crates/perry-stdlib/src/events/once_helpers.rs @@ -214,7 +214,8 @@ pub unsafe extern "C" fn js_events_once( }; if let Some(signal) = &signal { if object_ptr_from_value(signal.get_nanbox_f64()).is_some() { - let abort_listener = js_closure_alloc(events_once_abort_listener as *const u8, 2); + let abort_listener = + js_closure_alloc(perry_runtime::fn_info!(events_once_abort_listener, 0), 2); let abort_listener = scope.root_raw_mut_ptr(abort_listener); js_closure_set_capture_ptr(abort_listener.get_raw_mut_ptr(), 0, handle); js_closure_set_capture_ptr( @@ -243,7 +244,10 @@ pub unsafe extern "C" fn js_events_once( return promise.get_raw_mut_ptr(); } if let EventHelperTarget::EventTarget(_) = target { - let listener = js_closure_alloc(events_once_event_target_listener as *const u8, 3); + let listener = js_closure_alloc( + perry_runtime::fn_info!(events_once_event_target_listener, 1), + 3, + ); let listener = scope.root_raw_mut_ptr(listener); let target = object_ptr_from_value(target_value.get_nanbox_f64()).unwrap_or(std::ptr::null_mut()); @@ -267,15 +271,10 @@ pub unsafe extern "C" fn js_events_once( } if let EventHelperTarget::NetSocket(handle) = target { let listens_for_error = event_name == "error"; - perry_runtime::closure::js_register_closure_rest( - events_once_stream_resolve_listener as *const u8, - 0, + let listener = js_closure_alloc( + perry_runtime::fn_info!(events_once_stream_resolve_listener, 1; with_rest(0)), + 4, ); - perry_runtime::closure::js_register_closure_rest( - events_once_stream_reject_listener as *const u8, - 0, - ); - let listener = js_closure_alloc(events_once_stream_resolve_listener as *const u8, 4); let listener = scope.root_raw_mut_ptr(listener); js_closure_set_capture_ptr( listener.get_raw_mut_ptr(), @@ -288,8 +287,10 @@ pub unsafe extern "C" fn js_events_once( if !listens_for_error { let error_event_ptr = js_string_from_bytes(b"error".as_ptr(), 5); let error_event = scope.root_string_ptr(error_event_ptr); - let reject_listener = - js_closure_alloc(events_once_stream_reject_listener as *const u8, 4); + let reject_listener = js_closure_alloc( + perry_runtime::fn_info!(events_once_stream_reject_listener, 1; with_rest(0)), + 4, + ); let reject_listener = scope.root_raw_mut_ptr(reject_listener); js_closure_set_capture_ptr( reject_listener.get_raw_mut_ptr(), @@ -337,15 +338,10 @@ pub unsafe extern "C" fn js_events_once( return promise.get_raw_mut_ptr(); } if let EventHelperTarget::Stream(handle) = target { - perry_runtime::closure::js_register_closure_rest( - events_once_stream_resolve_listener as *const u8, - 0, - ); - perry_runtime::closure::js_register_closure_rest( - events_once_stream_reject_listener as *const u8, - 0, + let listener = js_closure_alloc( + perry_runtime::fn_info!(events_once_stream_resolve_listener, 1; with_rest(0)), + 4, ); - let listener = js_closure_alloc(events_once_stream_resolve_listener as *const u8, 4); let listener = scope.root_raw_mut_ptr(listener); js_closure_set_capture_ptr( listener.get_raw_mut_ptr(), @@ -360,8 +356,10 @@ pub unsafe extern "C" fn js_events_once( let error_event_ptr = js_string_from_bytes(error_event_name.as_ptr(), error_event_name.len() as u32); let error_event = scope.root_string_ptr(error_event_ptr); - let reject_listener = - js_closure_alloc(events_once_stream_reject_listener as *const u8, 4); + let reject_listener = js_closure_alloc( + perry_runtime::fn_info!(events_once_stream_reject_listener, 1; with_rest(0)), + 4, + ); let reject_listener = scope.root_raw_mut_ptr(reject_listener); js_closure_set_capture_ptr( reject_listener.get_raw_mut_ptr(), diff --git a/crates/perry-stdlib/src/fetch/dispatch.rs b/crates/perry-stdlib/src/fetch/dispatch.rs index 3b04b057a7..e283760165 100644 --- a/crates/perry-stdlib/src/fetch/dispatch.rs +++ b/crates/perry-stdlib/src/fetch/dispatch.rs @@ -278,7 +278,7 @@ fn form_data_bound_method_value(form_id: usize, method_name: &'static str) -> f6 // Register before allocating — see `headers_bound_method_value`. super::gc::ensure_gc_registered(); let closure = - perry_runtime::closure::js_closure_alloc(perry_runtime::closure::BOUND_METHOD_FUNC_PTR, 3); + perry_runtime::closure::js_closure_alloc(&perry_runtime::closure::BOUND_METHOD_INFO, 3); perry_runtime::closure::js_closure_set_capture_f64(closure, 0, handle_to_f64(form_id)); perry_runtime::closure::js_closure_set_capture_ptr(closure, 1, method_name.as_ptr() as i64); perry_runtime::closure::js_closure_set_capture_ptr(closure, 2, method_name.len() as i64); diff --git a/crates/perry-stdlib/src/fetch/headers_method_value.rs b/crates/perry-stdlib/src/fetch/headers_method_value.rs index bafab3effe..b46a5ac5ac 100644 --- a/crates/perry-stdlib/src/fetch/headers_method_value.rs +++ b/crates/perry-stdlib/src/fetch/headers_method_value.rs @@ -20,7 +20,7 @@ use super::*; extern "C" { #[link_name = "js_closure_alloc"] fn provider_js_closure_alloc( - function: *const u8, + info: *const perry_runtime::closure::JsFunctionInfo, capture_count: u32, ) -> *mut perry_runtime::closure::ClosureHeader; #[link_name = "js_closure_set_capture_f64"] @@ -60,7 +60,7 @@ pub(crate) fn headers_bound_method_value(headers_id: usize, method_name: &'stati // a live root by the first collection that can run after the insert. super::gc::ensure_gc_registered(); let closure = - unsafe { provider_js_closure_alloc(perry_runtime::closure::BOUND_METHOD_FUNC_PTR, 3) }; + unsafe { provider_js_closure_alloc(&perry_runtime::closure::BOUND_METHOD_INFO, 3) }; unsafe { provider_js_closure_set_capture_f64(closure, 0, handle_to_f64(headers_id)); provider_js_closure_set_capture_ptr(closure, 1, method_name.as_ptr() as i64); diff --git a/crates/perry-stdlib/src/readline/mod.rs b/crates/perry-stdlib/src/readline/mod.rs index ac2f1f2211..48dd77aa30 100644 --- a/crates/perry-stdlib/src/readline/mod.rs +++ b/crates/perry-stdlib/src/readline/mod.rs @@ -1033,11 +1033,7 @@ fn resolve_pending_next(promise: usize, value: f64, done: bool) { } } -fn register_aiter_arities() { - perry_runtime::closure::js_register_closure_arity(readline_aiter_next as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(readline_aiter_return as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(readline_aiter_self as *const u8, 0); -} +fn register_aiter_arities() {} enum NextAction { Line(String), @@ -1127,14 +1123,20 @@ pub extern "C" fn js_readline_iterator(handle: i64) -> i64 { packed.len() as u32, ); let obj_handle = scope.root_raw_mut_ptr(obj); - let next_cl = js_closure_alloc(readline_aiter_next as *const u8, 1); + let next_cl = js_closure_alloc( + perry_runtime::fn_info!(readline_aiter_next, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(next_cl, 0, handle as f64); js_object_set_field( obj_handle.get_raw_mut_ptr::(), 0, JSValue::pointer(next_cl as *const u8), ); - let ret_cl = js_closure_alloc(readline_aiter_return as *const u8, 1); + let ret_cl = js_closure_alloc( + perry_runtime::fn_info!(readline_aiter_return, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(ret_cl, 0, handle as f64); js_object_set_field( obj_handle.get_raw_mut_ptr::(), @@ -1148,7 +1150,10 @@ pub extern "C" fn js_readline_iterator(handle: i64) -> i64 { JSValue::pointer(obj_handle.get_raw_mut_ptr::() as *const u8).bits(), ); let iter_handle = scope.root_nanbox_f64(iter_val); - let self_cl = js_closure_alloc(readline_aiter_self as *const u8, 1); + let self_cl = js_closure_alloc( + perry_runtime::fn_info!(readline_aiter_self, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(self_cl, 0, iter_handle.get_nanbox_f64()); let self_val = f64::from_bits(JSValue::pointer(self_cl as *const u8).bits()); // Re-fetch the interned symbol: the closure allocation above may @@ -1194,18 +1199,22 @@ fn attach_custom_input(handle: i64, input: f64) { // registration Readable.from-backed interfaces retained the callbacks but // never delivered `data`/`end`, leaving the top-level readline Promise // unsettled. - perry_runtime::closure::js_register_closure_arity(custom_input_data as *const u8, 1); - perry_runtime::closure::js_register_closure_arity(custom_input_close as *const u8, 0); // Root every value built here: each later closure/string allocation (and // the JS `.on` calls below) can trigger a moving minor GC, leaving an // unrooted listener pointer in from-space. Re-read handles at each use. let scope = perry_runtime::gc::RuntimeHandleScope::new(); let input_handle = scope.root_nanbox_f64(input); - let data = js_closure_alloc(custom_input_data as *const u8, 1); + let data = js_closure_alloc( + perry_runtime::fn_info!(custom_input_data, 1; with_declared(1)), + 1, + ); js_closure_set_capture_f64(data, 0, handle as f64); let data_handle = scope.root_nanbox_f64(f64::from_bits(JSValue::pointer(data as *const u8).bits())); - let close = js_closure_alloc(custom_input_close as *const u8, 1); + let close = js_closure_alloc( + perry_runtime::fn_info!(custom_input_close, 0; with_declared(0)), + 1, + ); js_closure_set_capture_f64(close, 0, handle as f64); let close_handle = scope.root_nanbox_f64(f64::from_bits(JSValue::pointer(close as *const u8).bits())); diff --git a/crates/perry-stdlib/src/readline/test_support.rs b/crates/perry-stdlib/src/readline/test_support.rs index 52a80e0b46..14d8599b34 100644 --- a/crates/perry-stdlib/src/readline/test_support.rs +++ b/crates/perry-stdlib/src/readline/test_support.rs @@ -25,7 +25,7 @@ extern "C" fn count_data_callback( } pub(super) fn data_counter_callback() -> i64 { - js_closure_alloc(count_data_callback as *const u8, 0) as i64 + js_closure_alloc(perry_runtime::fn_info!(count_data_callback, 1), 0) as i64 } extern "C" fn count_readable_callback( @@ -37,7 +37,7 @@ extern "C" fn count_readable_callback( } pub(super) fn readable_counter_callback() -> i64 { - js_closure_alloc(count_readable_callback as *const u8, 0) as i64 + js_closure_alloc(perry_runtime::fn_info!(count_readable_callback, 0), 0) as i64 } extern "C" fn record_keypress_callback( @@ -54,7 +54,7 @@ extern "C" fn record_keypress_callback( } pub(super) fn keypress_recorder_callback() -> i64 { - js_closure_alloc(record_keypress_callback as *const u8, 0) as i64 + js_closure_alloc(perry_runtime::fn_info!(record_keypress_callback, 2), 0) as i64 } pub(super) fn event_name(name: &str) -> *mut StringHeader { diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests.rs index ebc6b38ff3..0b758a8032 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests.rs @@ -25,7 +25,10 @@ fn thread_exit_releases_the_threads_closure_side_table_entries() { let (owner, set_while_alive) = std::thread::spawn(|| { use perry_runtime::closure as c; let scope = perry_runtime::gc::RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(c::js_closure_alloc(probe_thunk as *const u8, 0)); + let closure = scope.root_raw_mut_ptr(c::js_closure_alloc( + perry_runtime::fn_info!(probe_thunk, 0), + 0, + )); let proto = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); // Re-read through the handles: the allocation above may have moved the // closure, and the side tables follow a moved owner to its new key. diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/listeners_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/listeners_tests.rs index e851e51cbf..317fc34fd7 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/listeners_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/listeners_tests.rs @@ -38,7 +38,7 @@ fn thread_exit_disables_the_threads_v8_promise_hooks() { let (callback, live) = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( - thunk1 as *const u8, + perry_runtime::fn_info!(thunk1, 1), 0, )); let addr = || closure.get_raw_mut_ptr::() as usize; @@ -61,7 +61,7 @@ fn thread_exit_disables_the_threads_async_hooks_create_hook_record() { let (callback, live) = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( - thunk1 as *const u8, + perry_runtime::fn_info!(thunk1, 1), 0, )); let options = scope.root_raw_mut_ptr(perry_runtime::js_object_alloc(0, 1)); @@ -219,7 +219,7 @@ fn thread_exit_releases_the_threads_tls_client_records() { let live = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); let closure = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( - thunk2 as *const u8, + perry_runtime::fn_info!(thunk2, 2), 0, )); let options = scope.root_raw_mut_ptr(perry_runtime::js_object_alloc(0, 1)); diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/stdlib_misc_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/stdlib_misc_tests.rs index 91b6eab6df..51f7ff54e2 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/stdlib_misc_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/stdlib_misc_tests.rs @@ -16,7 +16,7 @@ extern "C" fn probe_thunk( /// A closure allocated in the calling thread's arena, as a raw address. fn closure_here() -> i64 { - perry_runtime::closure::js_closure_alloc(probe_thunk as *const u8, 0) as i64 + perry_runtime::closure::js_closure_alloc(perry_runtime::fn_info!(probe_thunk, 0), 0) as i64 } #[cfg(all( diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/streams_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/streams_tests.rs index 2128666c87..4e3510a6df 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/streams_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/streams_tests.rs @@ -19,7 +19,8 @@ fn undef() -> f64 { /// A fresh closure on the calling thread's heap, NaN-boxed. fn closure_value() -> f64 { - let closure = perry_runtime::closure::js_closure_alloc(probe_thunk as *const u8, 0); + let closure = + perry_runtime::closure::js_closure_alloc(perry_runtime::fn_info!(probe_thunk, 0), 0); f64::from_bits(perry_runtime::JSValue::pointer(closure as *const u8).bits()) } diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs index 1bb12f28bf..e0d895fec8 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/symbols_tests.rs @@ -89,7 +89,7 @@ fn thread_exit_releases_the_threads_symbol_side_table_entries() { // Object.defineProperty(obj, sym3, { get }) (SYMBOL_ACCESSOR_PROPERTIES). let sym3 = scope.root_nanbox_f64(unsafe { s::js_symbol_new(string_value("t11471c")) }); let getter = scope.root_raw_mut_ptr(perry_runtime::closure::js_closure_alloc( - probe_thunk as *const u8, + perry_runtime::fn_info!(probe_thunk, 0), 0, )); let accessor = scope.root_raw_mut_ptr(perry_runtime::object::js_object_alloc(0, 0)); diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests/ui_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests/ui_tests.rs index 92f6084ca5..fb24c9cc58 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests/ui_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests/ui_tests.rs @@ -48,7 +48,8 @@ fn thread_exit_releases_the_threads_geisterhand_callbacks_and_queued_actions() { let (queued_arg, alive) = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(thunk0 as *const u8, 0)); + let closure = + scope.root_raw_mut_ptr(js_closure_alloc(perry_runtime::fn_info!(thunk0, 0), 0)); let arg = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); let closure_f64 = f64::from_bits(closure_bits(closure.get_raw_mut_ptr::())); let arg_f64 = f64::from_bits(array_bits(arg.get_raw_mut_ptr::())); @@ -88,7 +89,8 @@ fn thread_exit_releases_the_threads_ui_state_values_and_foreach_bindings() { let alive = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(thunk0 as *const u8, 0)); + let closure = + scope.root_raw_mut_ptr(js_closure_alloc(perry_runtime::fn_info!(thunk0, 0), 0)); let value = scope.root_raw_mut_ptr(perry_runtime::js_array_alloc(0)); let value_bits = array_bits(value.get_raw_mut_ptr::()); let closure_f64 = f64::from_bits(closure_bits(closure.get_raw_mut_ptr::())); @@ -120,7 +122,8 @@ fn thread_exit_releases_the_threads_frame_callbacks_and_last_fire_entries() { let (id, closure_addr, alive) = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(thunk2 as *const u8, 0)); + let closure = + scope.root_raw_mut_ptr(js_closure_alloc(perry_runtime::fn_info!(thunk2, 2), 0)); let addr = || closure.get_raw_mut_ptr::() as i64; // Fire once so LAST_FIRE_BY_CLOSURE records this closure, then queue // it again so a pending callback is left behind. @@ -151,7 +154,8 @@ fn thread_exit_clears_the_threads_tui_input_handler() { let (handler, alive) = std::thread::spawn(|| { let scope = RuntimeHandleScope::new(); - let closure = scope.root_raw_mut_ptr(js_closure_alloc(thunk0 as *const u8, 0)); + let closure = + scope.root_raw_mut_ptr(js_closure_alloc(perry_runtime::fn_info!(thunk0, 0), 0)); let handler = closure.get_raw_mut_ptr::() as i64; i::js_perry_tui_use_input(handler); (handler, i::tui_input_handler_for_test() == handler) diff --git a/crates/perry-stdlib/src/sqlite/better.rs b/crates/perry-stdlib/src/sqlite/better.rs index c5057c2918..1bcab0ccdb 100644 --- a/crates/perry-stdlib/src/sqlite/better.rs +++ b/crates/perry-stdlib/src/sqlite/better.rs @@ -453,7 +453,7 @@ pub unsafe extern "C" fn js_sqlite_transaction( js_closure_alloc, js_closure_set_capture_f64, js_closure_set_capture_ptr, }; - let wrapper = js_closure_alloc(sqlite_tx_wrapper as *const u8, 2); + let wrapper = js_closure_alloc(perry_runtime::fn_info!(sqlite_tx_wrapper, 1), 2); js_closure_set_capture_f64(wrapper, 0, db_handle as f64); js_closure_set_capture_ptr(wrapper, 1, closure_ptr); diff --git a/crates/perry-stdlib/src/sqlite/bun.rs b/crates/perry-stdlib/src/sqlite/bun.rs index 17c80825a1..de168f4f10 100644 --- a/crates/perry-stdlib/src/sqlite/bun.rs +++ b/crates/perry-stdlib/src/sqlite/bun.rs @@ -4,13 +4,13 @@ use perry_runtime::{ closure::{ js_closure_alloc, js_closure_call_array, js_closure_get_capture_f64, js_closure_get_capture_ptr, js_closure_set_capture_f64, js_closure_set_capture_ptr, - js_register_closure_rest, ClosureHeader, + ClosureHeader, }, js_array_alloc, js_array_get, js_array_length, js_array_push, js_nanbox_get_pointer, js_nanbox_pointer, js_string_from_bytes, ArrayHeader, JSValue, ObjectHeader, StringHeader, }; use rusqlite::ffi; -use std::sync::{atomic::Ordering, Once}; +use std::sync::atomic::Ordering; #[no_mangle] pub unsafe extern "C" fn js_bun_sqlite_database_call( @@ -270,8 +270,6 @@ unsafe extern "C" fn bun_sqlite_transaction_wrapper( } } -static BUN_SQLITE_TRANSACTION_WRAPPER_REGISTERED: Once = Once::new(); - #[no_mangle] pub unsafe extern "C" fn js_bun_sqlite_database_transaction( db_handle: Handle, @@ -279,13 +277,13 @@ pub unsafe extern "C" fn js_bun_sqlite_database_transaction( ) -> *mut ClosureHeader { let callback = closure_ptr_from_value(callback_value) .unwrap_or_else(|| throw_plain_type("Expected a function")); - BUN_SQLITE_TRANSACTION_WRAPPER_REGISTERED.call_once(|| { - // The wrapper has no fixed arguments and receives every invocation - // argument in the synthetic rest array. This preserves Bun's - // `transaction(fn)(...args)` forwarding for arbitrary callback arity. - js_register_closure_rest(bun_sqlite_transaction_wrapper as *const u8, 0); - }); - let wrapper = js_closure_alloc(bun_sqlite_transaction_wrapper as *const u8, 2); + // The wrapper has no fixed arguments and receives every invocation + // argument in its rest array (its info). This preserves Bun's + // `transaction(fn)(...args)` forwarding for arbitrary callback arity. + let wrapper = js_closure_alloc( + perry_runtime::fn_info!(bun_sqlite_transaction_wrapper, 1; with_rest(0)), + 2, + ); js_closure_set_capture_f64(wrapper, 0, db_handle as f64); js_closure_set_capture_ptr(wrapper, 1, callback as i64); wrapper diff --git a/crates/perry-stdlib/src/sqlite/dispatch.rs b/crates/perry-stdlib/src/sqlite/dispatch.rs index 8ecc085b05..b9f4044c78 100644 --- a/crates/perry-stdlib/src/sqlite/dispatch.rs +++ b/crates/perry-stdlib/src/sqlite/dispatch.rs @@ -208,9 +208,11 @@ pub(crate) extern "C" fn sql_tag_store_constructor_thunk( } pub(crate) unsafe fn sql_tag_store_constructor_value() -> f64 { - let func_ptr = sql_tag_store_constructor_thunk as *const u8; - perry_runtime::closure::js_register_closure_arity(func_ptr, 0); - let closure = perry_runtime::closure::js_closure_alloc_singleton(func_ptr); + let closure = perry_runtime::closure::js_closure_alloc_singleton(perry_runtime::fn_info!( + sql_tag_store_constructor_thunk, + 0; + with_declared(0) + )); if closure.is_null() { return undefined_f64(); } diff --git a/crates/perry-stdlib/src/streams.rs b/crates/perry-stdlib/src/streams.rs index 0e4479a9e5..fcd9364302 100644 --- a/crates/perry-stdlib/src/streams.rs +++ b/crates/perry-stdlib/src/streams.rs @@ -25,7 +25,7 @@ //! into `desiredSize`) live in `streams/byob.rs` and the queue helpers on //! `ReadableStreamData` (#4915). -use perry_runtime::closure::JsThis; +use perry_runtime::closure::{JsFunctionInfo, JsThis}; use perry_runtime::{ArrayHeader, ClosureHeader, JSValue, ObjectHeader, Promise, StringHeader}; use std::collections::{HashMap, VecDeque}; use std::sync::Mutex; @@ -52,7 +52,10 @@ extern "C" { #[link_name = "js_assimilate_thenable"] fn provider_js_assimilate_thenable(value: f64) -> f64; #[link_name = "js_closure_alloc"] - fn provider_js_closure_alloc(function: *const u8, capture_count: u32) -> *mut ClosureHeader; + fn provider_js_closure_alloc( + info: *const JsFunctionInfo, + capture_count: u32, + ) -> *mut ClosureHeader; #[link_name = "js_closure_call0"] fn provider_js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64; #[link_name = "js_closure_call1"] @@ -128,8 +131,6 @@ extern "C" { ) -> *mut Promise; #[link_name = "js_promise_value"] fn provider_js_promise_value(promise: *mut Promise) -> f64; - #[link_name = "js_register_closure_arity"] - fn provider_js_register_closure_arity(function: *const u8, arity: u32); #[link_name = "js_string_from_bytes"] fn provider_js_string_from_bytes(data: *const u8, len: u32) -> *mut StringHeader; #[link_name = "js_value_is_promise"] @@ -174,8 +175,8 @@ fn js_assimilate_thenable(value: f64) -> f64 { provider_call!(provider_js_assimilate_thenable(value)) } -fn js_closure_alloc(function: *const u8, capture_count: u32) -> *mut ClosureHeader { - provider_call!(provider_js_closure_alloc(function, capture_count)) +fn js_closure_alloc(info: *const JsFunctionInfo, capture_count: u32) -> *mut ClosureHeader { + provider_call!(provider_js_closure_alloc(info, capture_count)) } fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { @@ -286,10 +287,6 @@ fn js_promise_value(promise: *mut Promise) -> f64 { provider_call!(provider_js_promise_value(promise)) } -fn js_register_closure_arity(function: *const u8, arity: u32) { - provider_call!(provider_js_register_closure_arity(function, arity)) -} - fn js_string_from_bytes(data: *const u8, len: u32) -> *mut StringHeader { provider_call!(provider_js_string_from_bytes(data, len)) } @@ -1134,11 +1131,11 @@ extern "C" fn readable_pull_microtask( perry_runtime::value::js_nanbox_get_pointer(result) as *mut Promise; if !promise.is_null() { let fulfilled = readable_pull_settled_closure( - readable_pull_fulfilled as *const u8, + perry_runtime::fn_info!(readable_pull_fulfilled, 1; with_declared(1)), stream_id, ); let rejected = readable_pull_settled_closure( - readable_pull_rejected as *const u8, + perry_runtime::fn_info!(readable_pull_rejected, 1; with_declared(1)), stream_id, ); let _ = js_promise_then(promise, fulfilled, rejected); @@ -1161,9 +1158,11 @@ extern "C" fn readable_pull_microtask( f64::from_bits(TAG_UNDEFINED) } -fn readable_pull_settled_closure(func: *const u8, stream_id: usize) -> *mut ClosureHeader { - js_register_closure_arity(func, 1); - let closure = js_closure_alloc(func, 1); +fn readable_pull_settled_closure( + info: *const JsFunctionInfo, + stream_id: usize, +) -> *mut ClosureHeader { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, stream_id as i64); closure } @@ -1262,9 +1261,10 @@ unsafe fn maybe_pull_inner(stream_id: usize, force: bool) { if !should_pull { return; } - let pull_fn = readable_pull_microtask as *const u8; - js_register_closure_arity(pull_fn, 0); - let pull = js_closure_alloc(pull_fn, 3); + let pull = js_closure_alloc( + perry_runtime::fn_info!(readable_pull_microtask, 0; with_declared(0)), + 3, + ); js_closure_set_capture_ptr(pull, 0, controller.to_bits() as i64); js_closure_set_capture_ptr(pull, 1, cb); js_closure_set_capture_ptr(pull, 2, if pull_returns_byte_chunk { 1 } else { 0 }); @@ -2249,8 +2249,10 @@ unsafe fn resolve_reader_read_value(promise: *mut Promise, value_bits: u64) { js_promise_reject(promise, js_promise_reason(inner)); } _ => { - let fulfill = js_closure_alloc(readable_from_chunk_fulfilled as *const u8, 1); - let reject = js_closure_alloc(readable_from_chunk_rejected as *const u8, 1); + let fulfill = + js_closure_alloc(perry_runtime::fn_info!(readable_from_chunk_fulfilled, 1), 1); + let reject = + js_closure_alloc(perry_runtime::fn_info!(readable_from_chunk_rejected, 1), 1); js_closure_set_capture_ptr(fulfill, 0, promise as i64); js_closure_set_capture_ptr(reject, 0, promise as i64); let _ = js_promise_then(inner, fulfill, reject); @@ -2366,13 +2368,8 @@ fn resolved_done_promise() -> f64 { } } -fn closure_capture_value( - func: extern "C" fn(*const ClosureHeader, perry_runtime::closure::JsThis) -> f64, - value: f64, -) -> *mut ClosureHeader { - let fn_ptr = func as *const u8; - js_register_closure_arity(fn_ptr, 0); - let closure = js_closure_alloc(fn_ptr, 1); +fn closure_capture_value(info: *const JsFunctionInfo, value: f64) -> *mut ClosureHeader { + let closure = js_closure_alloc(info, 1); js_closure_set_capture_ptr(closure, 0, value.to_bits() as i64); closure } @@ -2427,21 +2424,28 @@ unsafe fn build_readable_stream_iterator(stream_handle: f64) -> f64 { js_object_set_field( obj, 0, - JSValue::pointer(closure_capture_value(readable_stream_iterator_next, reader) as *const u8), + JSValue::pointer(closure_capture_value( + perry_runtime::fn_info!(readable_stream_iterator_next, 0; with_declared(0)), + reader, + ) as *const u8), ); js_object_set_field( obj, 1, - JSValue::pointer( - closure_capture_value(readable_stream_iterator_return, reader) as *const u8, - ), + JSValue::pointer(closure_capture_value( + perry_runtime::fn_info!(readable_stream_iterator_return, 0; with_declared(0)), + reader, + ) as *const u8), ); js_object_set_keys(obj, keys); let iterator = f64::from_bits(JSValue::object_ptr(obj as *mut u8).bits()); let async_iterator = perry_runtime::symbol::well_known_symbol("asyncIterator"); if !async_iterator.is_null() { - let self_closure = closure_capture_value(readable_stream_iterator_self, iterator); + let self_closure = closure_capture_value( + perry_runtime::fn_info!(readable_stream_iterator_self, 0; with_declared(0)), + iterator, + ); let symbol_value = f64::from_bits(JSValue::pointer(async_iterator as *const u8).bits()); let closure_value = f64::from_bits(JSValue::pointer(self_closure as *const u8).bits()); perry_runtime::symbol::js_object_set_symbol_property(iterator, symbol_value, closure_value); diff --git a/crates/perry-stdlib/src/streams/byob.rs b/crates/perry-stdlib/src/streams/byob.rs index b5209da248..ce3f8d397a 100644 --- a/crates/perry-stdlib/src/streams/byob.rs +++ b/crates/perry-stdlib/src/streams/byob.rs @@ -416,15 +416,17 @@ pub unsafe extern "C" fn js_readable_stream_controller_byob_request(stream_handl js_array_push(keys, JSValue::string_ptr(k_rwnv)); js_object_set_field(obj, 0, JSValue::from_bits(view_bits)); - let respond_fn = byob_request_respond as *const u8; - perry_runtime::closure::js_register_closure_arity(respond_fn, 1); - let respond = perry_runtime::closure::js_closure_alloc(respond_fn, 1); + let respond = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(byob_request_respond, 1; with_declared(1)), + 1, + ); perry_runtime::closure::js_closure_set_capture_f64(respond, 0, id as f64); js_object_set_field(obj, 1, JSValue::pointer(respond as *const u8)); - let rwnv_fn = byob_request_respond_with_new_view as *const u8; - perry_runtime::closure::js_register_closure_arity(rwnv_fn, 1); - let rwnv = perry_runtime::closure::js_closure_alloc(rwnv_fn, 1); + let rwnv = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(byob_request_respond_with_new_view, 1; with_declared(1)), + 1, + ); perry_runtime::closure::js_closure_set_capture_f64(rwnv, 0, id as f64); js_object_set_field(obj, 2, JSValue::pointer(rwnv as *const u8)); diff --git a/crates/perry-stdlib/src/streams/pipe.rs b/crates/perry-stdlib/src/streams/pipe.rs index 1217e9addf..9cc429203e 100644 --- a/crates/perry-stdlib/src/streams/pipe.rs +++ b/crates/perry-stdlib/src/streams/pipe.rs @@ -365,27 +365,19 @@ unsafe fn finish_pipe( js_writable_stream_close(writable_id as f64) }; let fulfilled = pipe_closure( - readable_stream_pipe_to_close_fulfilled as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_close_fulfilled, 1; with_declared(1)), readable_id, writable_id, promise, state, ); let rejected = pipe_closure( - readable_stream_pipe_to_write_rejected as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_write_rejected, 1; with_declared(1)), readable_id, writable_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_close_fulfilled as *const u8, - 1, - ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_write_rejected as *const u8, - 1, - ); let _ = perry_runtime::promise::js_promise_then(close_promise, fulfilled, rejected); } @@ -500,7 +492,7 @@ unsafe fn wait_for_shutdown_actions( perry_runtime::promise::js_promise_all(values) }; let fulfilled = pipe_closure_with_reason( - readable_stream_pipe_to_shutdown_fulfilled as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_shutdown_fulfilled, 1; with_declared(1)), readable_id, writable_id, promise, @@ -508,21 +500,13 @@ unsafe fn wait_for_shutdown_actions( reason, ); let rejected = pipe_closure_with_reason( - readable_stream_pipe_to_shutdown_rejected as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_shutdown_rejected, 1; with_declared(1)), readable_id, writable_id, promise, state, reason, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_shutdown_fulfilled as *const u8, - 1, - ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_shutdown_rejected as *const u8, - 1, - ); let _ = perry_runtime::promise::js_promise_then(action, fulfilled, rejected); } @@ -573,16 +557,12 @@ unsafe fn schedule_pipe_step( state: PipeState, ) { let closure = pipe_closure( - readable_stream_pipe_to_microtask as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_microtask, 0; with_declared(0)), readable_id, writable_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_microtask as *const u8, - 0, - ); perry_runtime::builtins::js_queue_microtask(closure as i64); } @@ -625,27 +605,19 @@ unsafe fn pipe_write_then_continue( return; } let fulfilled = pipe_closure( - readable_stream_pipe_to_write_fulfilled as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_write_fulfilled, 1; with_declared(1)), readable_id, writable_id, promise, state, ); let rejected = pipe_closure( - readable_stream_pipe_to_write_rejected as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_write_rejected, 1; with_declared(1)), readable_id, writable_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_write_fulfilled as *const u8, - 1, - ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_write_rejected as *const u8, - 1, - ); let _ = perry_runtime::promise::js_promise_then(write_promise, fulfilled, rejected); } @@ -672,27 +644,19 @@ unsafe fn wait_for_next_read( maybe_pull(readable_id); let fulfilled = pipe_closure( - readable_stream_pipe_to_read_fulfilled as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_read_fulfilled, 1; with_declared(1)), readable_id, writable_id, promise, state, ); let rejected = pipe_closure( - readable_stream_pipe_to_read_rejected as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_read_rejected, 1; with_declared(1)), readable_id, writable_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_read_fulfilled as *const u8, - 1, - ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_read_rejected as *const u8, - 1, - ); let _ = perry_runtime::promise::js_promise_then(read_promise, fulfilled, rejected); } @@ -733,28 +697,28 @@ unsafe fn pipe_iter_result_object(value_bits: u64, done: bool) -> u64 { } fn pipe_closure( - func: *const u8, + info: *const perry_runtime::closure::JsFunctionInfo, readable_id: usize, writable_id: usize, promise: *mut Promise, state: PipeState, ) -> *mut perry_runtime::ClosureHeader { - pipe_closure_with_extra(func, readable_id, writable_id, promise, state, None) + pipe_closure_with_extra(info, readable_id, writable_id, promise, state, None) } fn pipe_closure_with_reason( - func: *const u8, + info: *const perry_runtime::closure::JsFunctionInfo, readable_id: usize, writable_id: usize, promise: *mut Promise, state: PipeState, reason: u64, ) -> *mut perry_runtime::ClosureHeader { - pipe_closure_with_extra(func, readable_id, writable_id, promise, state, Some(reason)) + pipe_closure_with_extra(info, readable_id, writable_id, promise, state, Some(reason)) } fn pipe_closure_with_extra( - func: *const u8, + info: *const perry_runtime::closure::JsFunctionInfo, readable_id: usize, writable_id: usize, promise: *mut Promise, @@ -762,7 +726,7 @@ fn pipe_closure_with_extra( reason: Option, ) -> *mut perry_runtime::ClosureHeader { let closure = - perry_runtime::closure::js_closure_alloc(func, if reason.is_some() { 11 } else { 10 }); + perry_runtime::closure::js_closure_alloc(info, if reason.is_some() { 11 } else { 10 }); perry_runtime::closure::js_closure_set_capture_ptr( closure, 0, @@ -858,16 +822,12 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( if let Some(signal_ptr) = pipe_signal_ptr(signal) { let listener = pipe_closure( - readable_stream_pipe_to_aborted as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_aborted, 0; with_declared(0)), r_id, w_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_aborted as *const u8, - 0, - ); state.abort_listener = f64::from_bits(JSValue::pointer(listener as *const u8).bits()); perry_runtime::closure::js_closure_set_capture_ptr( listener, @@ -888,16 +848,12 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( } let closure = pipe_closure( - readable_stream_pipe_to_microtask as *const u8, + perry_runtime::fn_info!(readable_stream_pipe_to_microtask, 0; with_declared(0)), r_id, w_id, promise, state, ); - perry_runtime::closure::js_register_closure_arity( - readable_stream_pipe_to_microtask as *const u8, - 0, - ); perry_runtime::builtins::js_queue_microtask(closure as i64); promise @@ -1035,9 +991,10 @@ mod tests { fn pipe_keeps_locks_until_async_abort_settles() { let _serial = crate::streams::tests::serial_guard(); let action = js_promise_new(); - let callback = - perry_runtime::closure::js_closure_alloc(pending_abort_action as *const u8, 1); - perry_runtime::closure::js_register_closure_arity(pending_abort_action as *const u8, 1); + let callback = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(pending_abort_action, 1; with_declared(1)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(callback, 0, action as i64); let readable = alloc_readable(0, 0, 0, 1.0); let writable = alloc_writable(0, 0, callback as i64, 1.0); diff --git a/crates/perry-stdlib/src/streams/strategy.rs b/crates/perry-stdlib/src/streams/strategy.rs index b8284fe6ba..960731bb08 100644 --- a/crates/perry-stdlib/src/streams/strategy.rs +++ b/crates/perry-stdlib/src/streams/strategy.rs @@ -39,7 +39,7 @@ extern "C" fn byte_length_queuing_strategy_size( /// is the raw JSValue bits read from the caller's options object. unsafe fn build_queuing_strategy( hwm_bits: u64, - size_fn: extern "C" fn(*const ClosureHeader, perry_runtime::closure::JsThis, f64) -> f64, + size_info: *const perry_runtime::closure::JsFunctionInfo, ) -> f64 { let obj = js_object_alloc(0, 2); let keys = js_array_alloc(2); @@ -48,11 +48,9 @@ unsafe fn build_queuing_strategy( js_array_push(keys, JSValue::string_ptr(k_hwm)); js_array_push(keys, JSValue::string_ptr(k_size)); js_object_set_field(obj, 0, JSValue::from_bits(hwm_bits)); - // `size` is a 1-arg native function value. Register the arity so closure - // dispatch pads/forwards the single `chunk` argument correctly. - let fn_ptr = size_fn as *const u8; - perry_runtime::closure::js_register_closure_arity(fn_ptr, 1); - let closure = perry_runtime::closure::js_closure_alloc(fn_ptr, 0); + // `size` is a 1-arg native function value (its info declares the one + // `chunk` parameter). + let closure = perry_runtime::closure::js_closure_alloc(size_info, 0); js_object_set_field(obj, 1, JSValue::pointer(closure as *const u8)); js_object_set_keys(obj, keys); f64::from_bits(JSValue::object_ptr(obj as *mut u8).bits()) @@ -100,12 +98,18 @@ pub unsafe extern "C" fn js_streams_strategy_high_water_mark(strategy: f64) -> f #[no_mangle] pub unsafe extern "C" fn js_count_queuing_strategy_new(opts: f64) -> f64 { let hwm = read_high_water_mark(opts); - build_queuing_strategy(hwm, count_queuing_strategy_size) + build_queuing_strategy( + hwm, + perry_runtime::fn_info!(count_queuing_strategy_size, 1; with_declared(1)), + ) } /// `new ByteLengthQueuingStrategy({ highWaterMark })`. #[no_mangle] pub unsafe extern "C" fn js_byte_length_queuing_strategy_new(opts: f64) -> f64 { let hwm = read_high_water_mark(opts); - build_queuing_strategy(hwm, byte_length_queuing_strategy_size) + build_queuing_strategy( + hwm, + perry_runtime::fn_info!(byte_length_queuing_strategy_size, 1; with_declared(1)), + ) } diff --git a/crates/perry-stdlib/src/streams/tee.rs b/crates/perry-stdlib/src/streams/tee.rs index b8bc0353af..521850a6ca 100644 --- a/crates/perry-stdlib/src/streams/tee.rs +++ b/crates/perry-stdlib/src/streams/tee.rs @@ -274,9 +274,10 @@ pub(super) unsafe fn tee_schedule_pull(source: usize) { if !TEE_PULLING.lock().unwrap().insert(source) { return; } - let f = tee_pull_microtask as *const u8; - perry_runtime::closure::js_register_closure_arity(f, 0); - let job = perry_runtime::closure::js_closure_alloc(f, 1); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(tee_pull_microtask, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(job, 0, source as i64); perry_runtime::builtins::js_queue_microtask(job as i64); } @@ -316,9 +317,10 @@ pub(super) unsafe fn tee_schedule_pull_demand(source: usize) { if !TEE_PULLING.lock().unwrap().insert(source) { return; } - let f = tee_demand_hop as *const u8; - perry_runtime::closure::js_register_closure_arity(f, 0); - let job = perry_runtime::closure::js_closure_alloc(f, 1); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(tee_demand_hop, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(job, 0, source as i64); perry_runtime::builtins::js_queue_microtask(job as i64); } @@ -440,9 +442,10 @@ extern "C" fn tee_pull_microtask( // the final delivery pair). Each extra hop cedes a // task-generation to racing promise cascades (the // Next.js module-require chain must win that race). - let f = tee_byte_chain_hop as *const u8; - perry_runtime::closure::js_register_closure_arity(f, 0); - let job = perry_runtime::closure::js_closure_alloc(f, 1); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(tee_byte_chain_hop, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(job, 0, source as i64); perry_runtime::builtins::js_queue_microtask(job as i64); } else { @@ -464,9 +467,10 @@ extern "C" fn tee_pull_microtask( // 6/12/20-tick chains), i.e. it travels the nextTick queue, // which runs when the microtask queue exhausts. A plain // (non-tee) stream's close stays prompt. Defer via nextTick. - let f = tee_close_tick as *const u8; - perry_runtime::closure::js_register_closure_arity(f, 0); - let job = perry_runtime::closure::js_closure_alloc(f, 1); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(tee_close_tick, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(job, 0, source as i64); perry_runtime::builtins::js_queue_next_tick(job as i64); } diff --git a/crates/perry-stdlib/src/streams/tests.rs b/crates/perry-stdlib/src/streams/tests.rs index a75a121d35..75daa975a6 100644 --- a/crates/perry-stdlib/src/streams/tests.rs +++ b/crates/perry-stdlib/src/streams/tests.rs @@ -447,9 +447,10 @@ extern "C" fn next_recording_this( #[test] fn iterator_next_is_called_with_the_iterator_as_its_receiver() { let scope = perry_runtime::gc::RuntimeHandleScope::new(); - let fn_ptr = next_recording_this as *const u8; - js_register_closure_arity(fn_ptr, 0); - let next = scope.root_raw_mut_ptr(js_closure_alloc(fn_ptr, 1)); + let next = scope.root_raw_mut_ptr(js_closure_alloc( + perry_runtime::fn_info!(next_recording_this, 0; with_declared(0)), + 1, + )); perry_runtime::closure::js_closure_set_capture_f64( next.get_raw_mut_ptr::(), 0, diff --git a/crates/perry-stdlib/src/streams/transform.rs b/crates/perry-stdlib/src/streams/transform.rs index dace897148..ff34f5b0ab 100644 --- a/crates/perry-stdlib/src/streams/transform.rs +++ b/crates/perry-stdlib/src/streams/transform.rs @@ -253,9 +253,10 @@ pub(super) unsafe fn transform_write(writable_id: usize, chunk: f64) -> *mut Pro // cadence (teepipe2.js) — which let a tee sibling's reader outrun the // pipeline (Next.js cold-start head reorder). Queue the invocation; the // write promise resolves in the job, after the transform enqueues. - let job_fn = transform_write_job as *const u8; - perry_runtime::closure::js_register_closure_arity(job_fn, 0); - let job = perry_runtime::closure::js_closure_alloc(job_fn, 5); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(transform_write_job, 0; with_declared(0)), + 5, + ); perry_runtime::closure::js_closure_set_capture_ptr(job, 0, transform_cb); perry_runtime::closure::js_closure_set_capture_ptr(job, 1, readable_id as i64); perry_runtime::closure::js_closure_set_capture_ptr(job, 2, chunk.to_bits() as i64); @@ -290,9 +291,10 @@ extern "C" fn transform_write_job( _this: perry_runtime::closure::JsThis, ) -> f64 { { - let job_fn = transform_write_job2 as *const u8; - perry_runtime::closure::js_register_closure_arity(job_fn, 0); - let job = perry_runtime::closure::js_closure_alloc(job_fn, 5); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(transform_write_job2, 0; with_declared(0)), + 5, + ); for i in 0..5 { perry_runtime::closure::js_closure_set_capture_ptr( job, @@ -332,15 +334,17 @@ extern "C" fn transform_write_job2( if perry_runtime::promise::js_value_is_promise(ret) != 0 { let inner = perry_runtime::value::js_nanbox_get_pointer(ret) as *mut Promise; if !inner.is_null() { - let f = transform_write_settle_fulfilled as *const u8; - let r = transform_write_settle_rejected as *const u8; - perry_runtime::closure::js_register_closure_arity(f, 1); - perry_runtime::closure::js_register_closure_arity(r, 1); - let fc = perry_runtime::closure::js_closure_alloc(f, 3); + let fc = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(transform_write_settle_fulfilled, 1; with_declared(1)), + 3, + ); perry_runtime::closure::js_closure_set_capture_ptr(fc, 0, readable_id as i64); perry_runtime::closure::js_closure_set_capture_ptr(fc, 1, promise as i64); perry_runtime::closure::js_closure_set_capture_ptr(fc, 2, writable_id as i64); - let rc = perry_runtime::closure::js_closure_alloc(r, 3); + let rc = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(transform_write_settle_rejected, 1; with_declared(1)), + 3, + ); perry_runtime::closure::js_closure_set_capture_ptr(rc, 0, readable_id as i64); perry_runtime::closure::js_closure_set_capture_ptr(rc, 1, promise as i64); perry_runtime::closure::js_closure_set_capture_ptr(rc, 2, writable_id as i64); @@ -702,8 +706,10 @@ mod backpressure_tests { fn initial_backpressure_defers_transform_until_read_demand() { let _serial = crate::streams::tests::serial_guard(); TRANSFORM_CALLS.store(0, Ordering::Relaxed); - let transform = perry_runtime::closure::js_closure_alloc(capture_transform as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(capture_transform as *const u8, 2); + let transform = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(capture_transform, 2; with_declared(2)), + 0, + ); let transform_value = f64::from_bits(JSValue::pointer(transform as *const u8).bits()); let undefined = f64::from_bits(TAG_UNDEFINED); let stream = unsafe { @@ -727,8 +733,10 @@ mod backpressure_tests { fn abort_rejects_transform_waiting_for_read_demand() { let _serial = crate::streams::tests::serial_guard(); TRANSFORM_CALLS.store(0, Ordering::Relaxed); - let transform = perry_runtime::closure::js_closure_alloc(capture_transform as *const u8, 0); - perry_runtime::closure::js_register_closure_arity(capture_transform as *const u8, 2); + let transform = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(capture_transform, 2; with_declared(2)), + 0, + ); let transform_value = f64::from_bits(JSValue::pointer(transform as *const u8).bits()); let undefined = f64::from_bits(TAG_UNDEFINED); let stream = unsafe { @@ -860,11 +868,11 @@ unsafe fn perform_transform_close(writable_id: usize, promise: *mut Promise) { // deferred enqueue lands on the still-open readable first. _ => { let fulfill = perry_runtime::closure::js_closure_alloc( - transform_flush_fulfilled as *const u8, + perry_runtime::fn_info!(transform_flush_fulfilled, 1), 3, ); let reject = perry_runtime::closure::js_closure_alloc( - transform_flush_rejected as *const u8, + perry_runtime::fn_info!(transform_flush_rejected, 1), 3, ); perry_runtime::closure::js_closure_set_capture_ptr( diff --git a/crates/perry-stdlib/src/streams/writable.rs b/crates/perry-stdlib/src/streams/writable.rs index 459e54c894..f3bb3bd444 100644 --- a/crates/perry-stdlib/src/streams/writable.rs +++ b/crates/perry-stdlib/src/streams/writable.rs @@ -335,12 +335,10 @@ unsafe fn attach_writable_write_handlers( write_promise: *mut Promise, sink_promise: *mut Promise, ) { - let fulfilled_fn = writable_write_fulfilled as *const u8; - let rejected_fn = writable_write_rejected as *const u8; - perry_runtime::closure::js_register_closure_arity(fulfilled_fn, 1); - perry_runtime::closure::js_register_closure_arity(rejected_fn, 1); - - let on_fulfilled = perry_runtime::closure::js_closure_alloc(fulfilled_fn, 3); + let on_fulfilled = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(writable_write_fulfilled, 1; with_declared(1)), + 3, + ); perry_runtime::closure::js_closure_set_capture_ptr( on_fulfilled, 0, @@ -353,7 +351,10 @@ unsafe fn attach_writable_write_handlers( ); perry_runtime::closure::js_closure_set_capture_ptr(on_fulfilled, 2, write_promise as i64); - let on_rejected = perry_runtime::closure::js_closure_alloc(rejected_fn, 3); + let on_rejected = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(writable_write_rejected, 1; with_declared(1)), + 3, + ); perry_runtime::closure::js_closure_set_capture_ptr( on_rejected, 0, @@ -421,12 +422,10 @@ unsafe fn attach_writable_close_handlers( close_promise: *mut Promise, sink_promise: *mut Promise, ) { - let fulfilled_fn = writable_close_fulfilled as *const u8; - let rejected_fn = writable_close_rejected as *const u8; - perry_runtime::closure::js_register_closure_arity(fulfilled_fn, 1); - perry_runtime::closure::js_register_closure_arity(rejected_fn, 1); - - let on_fulfilled = perry_runtime::closure::js_closure_alloc(fulfilled_fn, 2); + let on_fulfilled = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(writable_close_fulfilled, 1; with_declared(1)), + 2, + ); perry_runtime::closure::js_closure_set_capture_ptr( on_fulfilled, 0, @@ -434,7 +433,10 @@ unsafe fn attach_writable_close_handlers( ); perry_runtime::closure::js_closure_set_capture_ptr(on_fulfilled, 1, close_promise as i64); - let on_rejected = perry_runtime::closure::js_closure_alloc(rejected_fn, 2); + let on_rejected = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(writable_close_rejected, 1; with_declared(1)), + 2, + ); perry_runtime::closure::js_closure_set_capture_ptr( on_rejected, 0, @@ -627,9 +629,10 @@ pub(super) unsafe fn writable_stream_write( install_writable_backpressure_ready(stream_id, writer_id); } if let Some((cb, chunk, write_promise)) = start_write { - let job_fn = writable_write_start_microtask as *const u8; - perry_runtime::closure::js_register_closure_arity(job_fn, 0); - let job = perry_runtime::closure::js_closure_alloc(job_fn, 5); + let job = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(writable_write_start_microtask, 0; with_declared(0)), + 5, + ); perry_runtime::closure::js_closure_set_capture_ptr( job, 0, diff --git a/crates/perry-stdlib/src/string_decoder.rs b/crates/perry-stdlib/src/string_decoder.rs index 7bcfc41a9f..762547b6c0 100644 --- a/crates/perry-stdlib/src/string_decoder.rs +++ b/crates/perry-stdlib/src/string_decoder.rs @@ -173,9 +173,8 @@ unsafe fn define_accessor_property( ); } -unsafe fn function_value(func: *const u8, arity: u32, name: &str) -> f64 { - let closure = perry_runtime::closure::js_closure_alloc(func, 0); - perry_runtime::closure::js_register_closure_arity(func, arity); +unsafe fn function_value(info: *const perry_runtime::closure::JsFunctionInfo, name: &str) -> f64 { + let closure = perry_runtime::closure::js_closure_alloc(info, 0); perry_runtime::closure::closure_set_dynamic_prop(closure as usize, "name", string_value(name)); boxed_ptr(closure as *const u8) } @@ -273,21 +272,30 @@ pub unsafe fn string_decoder_prototype_value() -> f64 { define_data_property( proto, "write", - function_value(string_decoder_proto_write as *const u8, 1, "write"), + function_value( + perry_runtime::fn_info!(string_decoder_proto_write, 1; with_declared(1)), + "write", + ), true, true, ); define_data_property( proto, "end", - function_value(string_decoder_proto_end as *const u8, 1, "end"), + function_value( + perry_runtime::fn_info!(string_decoder_proto_end, 1; with_declared(1)), + "end", + ), true, true, ); define_data_property( proto, "text", - function_value(string_decoder_proto_text as *const u8, 2, "text"), + function_value( + perry_runtime::fn_info!(string_decoder_proto_text, 2; with_declared(2)), + "text", + ), true, true, ); @@ -295,8 +303,7 @@ pub unsafe fn string_decoder_prototype_value() -> f64 { proto, "lastChar", function_value( - string_decoder_last_char_getter as *const u8, - 0, + perry_runtime::fn_info!(string_decoder_last_char_getter, 0; with_declared(0)), "get lastChar", ), true, @@ -305,8 +312,7 @@ pub unsafe fn string_decoder_prototype_value() -> f64 { proto, "lastNeed", function_value( - string_decoder_last_need_getter as *const u8, - 0, + perry_runtime::fn_info!(string_decoder_last_need_getter, 0; with_declared(0)), "get lastNeed", ), true, @@ -315,8 +321,7 @@ pub unsafe fn string_decoder_prototype_value() -> f64 { proto, "lastTotal", function_value( - string_decoder_last_total_getter as *const u8, - 0, + perry_runtime::fn_info!(string_decoder_last_total_getter, 0; with_declared(0)), "get lastTotal", ), true, diff --git a/crates/perry-stdlib/src/tls.rs b/crates/perry-stdlib/src/tls.rs index 681b8c0592..b2aa3c2ea4 100644 --- a/crates/perry-stdlib/src/tls.rs +++ b/crates/perry-stdlib/src/tls.rs @@ -1249,9 +1249,10 @@ pub unsafe extern "C" fn js_tls_client_preflight( .unwrap_or_default(); if sni_callback != 0 && !servername.is_empty() { - perry_runtime::closure::js_register_closure_arity(tls_sni_completion as *const u8, 2); - let completion = - perry_runtime::closure::js_closure_alloc(tls_sni_completion as *const u8, 2); + let completion = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(tls_sni_completion, 2; with_declared(2)), + 2, + ); perry_runtime::closure::js_closure_set_capture_ptr(completion, 0, server_handle); perry_runtime::closure::js_closure_set_capture_f64(completion, 1, nanbox_str(&servername)); js_closure_call2( diff --git a/crates/perry-stdlib/src/webcrypto/digest.rs b/crates/perry-stdlib/src/webcrypto/digest.rs index 2674a9cf50..59ac94388e 100644 --- a/crates/perry-stdlib/src/webcrypto/digest.rs +++ b/crates/perry-stdlib/src/webcrypto/digest.rs @@ -19,7 +19,10 @@ extern "C" fn webcrypto_digest_settle( // completion), so re-arm one more time before resolving. let remaining = perry_runtime::closure::js_closure_get_capture_ptr(closure, 2); if remaining > 1 { - let cl = perry_runtime::closure::js_closure_alloc(webcrypto_digest_settle as *const u8, 3); + let cl = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(webcrypto_digest_settle, 0), + 3, + ); perry_runtime::closure::js_closure_set_capture_ptr(cl, 0, promise_bits as i64); perry_runtime::closure::js_closure_set_capture_ptr(cl, 1, value_bits as i64); perry_runtime::closure::js_closure_set_capture_ptr(cl, 2, remaining - 1); @@ -58,7 +61,10 @@ pub unsafe extern "C" fn js_webcrypto_digest(algo_bits: f64, data_bits: f64) -> let scope = perry_runtime::gc::RuntimeHandleScope::new(); let value = scope.root_nanbox_f64(f64::from_bits(JSValue::pointer(buf as *const u8).bits())); let promise = scope.root_raw_mut_ptr(perry_runtime::promise::js_promise_new()); - let cl = perry_runtime::closure::js_closure_alloc(webcrypto_digest_settle as *const u8, 3); + let cl = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(webcrypto_digest_settle, 0), + 3, + ); let cl = scope.root_raw_mut_ptr(cl); let promise_val = promise.with_mut_ptr(|promise: *mut Promise| { f64::from_bits(JSValue::pointer(promise as *const u8).bits()) diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index 57d67544fb..d92fb46a5f 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -547,15 +547,16 @@ fn set_object_prototype(obj: *mut perry_runtime::object::ObjectHeader, prototype } } -fn closure_value(func_ptr: *const u8, arity: u32) -> f64 { - perry_runtime::closure::js_register_closure_arity(func_ptr, arity); - let closure = perry_runtime::closure::js_closure_alloc(func_ptr, 0); +fn closure_value(info: *const perry_runtime::closure::JsFunctionInfo) -> f64 { + let closure = perry_runtime::closure::js_closure_alloc(info, 0); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -fn closure_value_with_worker_id(func_ptr: *const u8, arity: u32, worker_id: u64) -> f64 { - perry_runtime::closure::js_register_closure_arity(func_ptr, arity); - let closure = perry_runtime::closure::js_closure_alloc(func_ptr, 1); +fn closure_value_with_worker_id( + info: *const perry_runtime::closure::JsFunctionInfo, + worker_id: u64, +) -> f64 { + let closure = perry_runtime::closure::js_closure_alloc(info, 1); perry_runtime::closure::js_closure_set_capture_ptr(closure, 0, worker_id as i64); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } @@ -650,9 +651,8 @@ extern "C" fn worker_threads_noop0( /// Build a closure that captures a single f64 (the port id) in capture slot 0. /// The bound extern fn reads it back via `js_closure_get_capture_f64`. -fn port_bound_closure(func_ptr: *const u8, arity: u32, port_id: u64) -> f64 { - perry_runtime::closure::js_register_closure_arity(func_ptr, arity); - let closure = perry_runtime::closure::js_closure_alloc(func_ptr, 1); +fn port_bound_closure(info: *const perry_runtime::closure::JsFunctionInfo, port_id: u64) -> f64 { + let closure = perry_runtime::closure::js_closure_alloc(info, 1); perry_runtime::closure::js_closure_set_capture_f64(closure, 0, f64::from_bits(port_id)); f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } @@ -747,12 +747,10 @@ fn worker_wait_budget() -> Option { } fn queue_worker_threads_microtask() { - perry_runtime::closure::js_register_closure_arity( - worker_threads_channels_microtask as *const u8, + let closure = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(worker_threads_channels_microtask, 0; with_declared(0)), 0, ); - let closure = - perry_runtime::closure::js_closure_alloc(worker_threads_channels_microtask as *const u8, 0); perry_runtime::builtins::js_queue_microtask(closure as i64); perry_runtime::event_pump::js_notify_main_thread(); } diff --git a/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs b/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs index d42b588904..a9d14bf12d 100644 --- a/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs +++ b/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs @@ -142,32 +142,50 @@ pub extern "C" fn js_worker_threads_broadcast_channel_new(name: f64) -> f64 { set_object_field( obj, "postMessage", - port_bound_closure(broadcast_post_message as *const u8, 1, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_post_message, 1; with_declared(1)), + id, + ), ); set_object_field( obj, "close", - port_bound_closure(broadcast_close as *const u8, 0, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_close, 0; with_declared(0)), + id, + ), ); set_object_field( obj, "ref", - port_bound_closure(broadcast_ref_or_unref as *const u8, 0, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_ref_or_unref, 0; with_declared(0)), + id, + ), ); set_object_field( obj, "unref", - port_bound_closure(broadcast_ref_or_unref as *const u8, 0, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_ref_or_unref, 0; with_declared(0)), + id, + ), ); set_object_field( obj, "addEventListener", - port_bound_closure(broadcast_add_event_listener as *const u8, 3, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_add_event_listener, 3; with_declared(3)), + id, + ), ); set_object_field( obj, "removeEventListener", - port_bound_closure(broadcast_remove_event_listener as *const u8, 2, id), + port_bound_closure( + perry_runtime::fn_info!(broadcast_remove_event_listener, 2; with_declared(2)), + id, + ), ); set_object_field(obj, "onmessage", js_null()); set_object_field(obj, "onmessageerror", js_null()); diff --git a/crates/perry-stdlib/src/worker_threads/message_port.rs b/crates/perry-stdlib/src/worker_threads/message_port.rs index ea3afd3a40..6f0f1a4b69 100644 --- a/crates/perry-stdlib/src/worker_threads/message_port.rs +++ b/crates/perry-stdlib/src/worker_threads/message_port.rs @@ -18,72 +18,114 @@ pub(super) fn message_port_object(port_id: u64) -> *mut perry_runtime::object::O set_object_field( obj, "postMessage", - port_bound_closure(port_post_message as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_post_message, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "on", - port_bound_closure(port_on as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_on, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "addListener", - port_bound_closure(port_on as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_on, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "once", - port_bound_closure(port_once as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_once, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "off", - port_bound_closure(port_off as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_off, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "removeListener", - port_bound_closure(port_off as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_off, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "listenerCount", - port_bound_closure(port_listener_count as *const u8, 1, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_listener_count, 1; with_declared(1)), + port_id, + ), ); set_object_field( obj, "addEventListener", - port_bound_closure(port_add_event_listener as *const u8, 3, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_add_event_listener, 3; with_declared(3)), + port_id, + ), ); set_object_field( obj, "removeEventListener", - port_bound_closure(port_remove_event_listener as *const u8, 2, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_remove_event_listener, 2; with_declared(2)), + port_id, + ), ); set_object_field( obj, "close", - port_bound_closure(port_close as *const u8, 0, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_close, 0; with_declared(0)), + port_id, + ), ); set_object_field( obj, "start", - port_bound_closure(port_start as *const u8, 0, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_start, 0; with_declared(0)), + port_id, + ), ); set_object_field( obj, "ref", - port_bound_closure(port_ref as *const u8, 0, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_ref, 0; with_declared(0)), + port_id, + ), ); set_object_field( obj, "unref", - port_bound_closure(port_unref as *const u8, 0, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_unref, 0; with_declared(0)), + port_id, + ), ); set_object_field( obj, "hasRef", - port_bound_closure(port_has_ref as *const u8, 0, port_id), + port_bound_closure( + perry_runtime::fn_info!(port_has_ref, 0; with_declared(0)), + port_id, + ), ); set_object_field(obj, "__perryPortId", f64::from_bits(port_id)); set_object_field(obj, "onmessage", js_null()); diff --git a/crates/perry-stdlib/src/worker_threads/parent_port.rs b/crates/perry-stdlib/src/worker_threads/parent_port.rs index b9b98c4dba..72474fc040 100644 --- a/crates/perry-stdlib/src/worker_threads/parent_port.rs +++ b/crates/perry-stdlib/src/worker_threads/parent_port.rs @@ -11,52 +11,58 @@ pub(super) fn worker_parent_port_object() -> *mut perry_runtime::object::ObjectH set_object_field( obj, "postMessage", - closure_value(worker_parent_port_post_message as *const u8, 1), + closure_value( + perry_runtime::fn_info!(worker_parent_port_post_message, 1; with_declared(1)), + ), ); set_object_field( obj, "on", - closure_value(worker_parent_port_on as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_parent_port_on, 2; with_declared(2))), ); set_object_field( obj, "addListener", - closure_value(worker_parent_port_on as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_parent_port_on, 2; with_declared(2))), ); set_object_field( obj, "once", - closure_value(worker_parent_port_on as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_parent_port_on, 2; with_declared(2))), ); set_object_field( obj, "off", - closure_value(worker_parent_port_off as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_parent_port_off, 2; with_declared(2))), ); set_object_field( obj, "removeListener", - closure_value(worker_parent_port_off as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_parent_port_off, 2; with_declared(2))), ); set_object_field( obj, "addEventListener", - closure_value(worker_parent_port_add_event_listener as *const u8, 2), + closure_value( + perry_runtime::fn_info!(worker_parent_port_add_event_listener, 2; with_declared(2)), + ), ); set_object_field( obj, "removeEventListener", - closure_value(worker_parent_port_remove_event_listener as *const u8, 2), + closure_value( + perry_runtime::fn_info!(worker_parent_port_remove_event_listener, 2; with_declared(2)), + ), ); set_object_field( obj, "ref", - closure_value(worker_threads_noop0 as *const u8, 0), + closure_value(perry_runtime::fn_info!(worker_threads_noop0, 0; with_declared(0))), ); set_object_field( obj, "unref", - closure_value(worker_threads_noop0 as *const u8, 0), + closure_value(perry_runtime::fn_info!(worker_threads_noop0, 0; with_declared(0))), ); obj } diff --git a/crates/perry-stdlib/src/worker_threads/worker_surface.rs b/crates/perry-stdlib/src/worker_threads/worker_surface.rs index 1bdc334a72..a384a6e480 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_surface.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_surface.rs @@ -158,77 +158,122 @@ pub(super) fn worker_object( set_object_field( obj, "getHeapStatistics", - closure_value_with_worker_id(worker_get_heap_statistics as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_get_heap_statistics, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "cpuUsage", - closure_value_with_worker_id(worker_cpu_usage as *const u8, 1, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_cpu_usage, 1; with_declared(1)), + worker_id, + ), ); set_object_field( obj, "getHeapSnapshot", - closure_value_with_worker_id(worker_get_heap_snapshot as *const u8, 1, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_get_heap_snapshot, 1; with_declared(1)), + worker_id, + ), ); set_object_field( obj, "startCpuProfile", - closure_value_with_worker_id(worker_start_cpu_profile as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_start_cpu_profile, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "startHeapProfile", - closure_value_with_worker_id(worker_start_heap_profile as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_start_heap_profile, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "postMessage", - closure_value_with_worker_id(worker_post_message as *const u8, 1, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_post_message, 1; with_declared(1)), + worker_id, + ), ); set_object_field( obj, "terminate", - closure_value_with_worker_id(worker_terminate as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_terminate, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "reload", - closure_value_with_worker_id(worker_reload as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_reload, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "ref", - closure_value_with_worker_id(worker_ref as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_ref, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "unref", - closure_value_with_worker_id(worker_unref as *const u8, 0, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_unref, 0; with_declared(0)), + worker_id, + ), ); set_object_field( obj, "on", - closure_value_with_worker_id(worker_on as *const u8, 2, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_on, 2; with_declared(2)), + worker_id, + ), ); set_object_field( obj, "once", - closure_value_with_worker_id(worker_once as *const u8, 2, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_once, 2; with_declared(2)), + worker_id, + ), ); set_object_field( obj, "off", - closure_value_with_worker_id(worker_off as *const u8, 2, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_off, 2; with_declared(2)), + worker_id, + ), ); set_object_field( obj, "addEventListener", - closure_value_with_worker_id(worker_add_event_listener as *const u8, 2, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_add_event_listener, 2; with_declared(2)), + worker_id, + ), ); set_object_field( obj, "removeEventListener", - closure_value_with_worker_id(worker_remove_event_listener as *const u8, 2, worker_id), + closure_value_with_worker_id( + perry_runtime::fn_info!(worker_remove_event_listener, 2; with_declared(2)), + worker_id, + ), ); set_object_field(obj, "onmessage", js_null()); set_object_field(obj, "onmessageerror", js_null()); @@ -251,26 +296,29 @@ pub(super) fn install_web_worker_globals() { return; } set_object_field(global(), "self", global_h.get_nanbox_f64()); - let post_message_h = - scope.root_nanbox_f64(closure_value(web_worker_post_message as *const u8, 2)); + let post_message_h = scope.root_nanbox_f64(closure_value( + perry_runtime::fn_info!(web_worker_post_message, 2; with_declared(2)), + )); set_object_field(global(), "postMessage", post_message_h.get_nanbox_f64()); - let add_event_listener_h = - scope.root_nanbox_f64(closure_value(web_worker_add_event_listener as *const u8, 3)); + let add_event_listener_h = scope.root_nanbox_f64(closure_value( + perry_runtime::fn_info!(web_worker_add_event_listener, 3; with_declared(3)), + )); set_object_field( global(), "addEventListener", add_event_listener_h.get_nanbox_f64(), ); let remove_event_listener_h = scope.root_nanbox_f64(closure_value( - web_worker_remove_event_listener as *const u8, - 2, + perry_runtime::fn_info!(web_worker_remove_event_listener, 2; with_declared(2)), )); set_object_field( global(), "removeEventListener", remove_event_listener_h.get_nanbox_f64(), ); - let close_h = scope.root_nanbox_f64(closure_value(web_worker_close as *const u8, 0)); + let close_h = scope.root_nanbox_f64(closure_value( + perry_runtime::fn_info!(web_worker_close, 0; with_declared(0)), + )); set_object_field(global(), "close", close_h.get_nanbox_f64()); set_object_field(global(), "onmessage", js_null()); set_object_field(global(), "onmessageerror", js_null()); @@ -352,7 +400,7 @@ fn worker_performance_object() -> *mut perry_runtime::object::ObjectHeader { set_object_field( obj, "eventLoopUtilization", - closure_value(worker_event_loop_utilization as *const u8, 2), + closure_value(perry_runtime::fn_info!(worker_event_loop_utilization, 2; with_declared(2))), ); obj } @@ -530,31 +578,71 @@ pub(super) fn worker_readable_stream_object() -> f64 { let obj = perry_runtime::object::js_object_alloc(0, 0); set_object_field(obj, "readable", js_bool(true)); set_object_field(obj, "destroyed", js_bool(false)); - set_object_field(obj, "on", closure_value(stream_on as *const u8, 2)); - set_object_field(obj, "once", closure_value(stream_on as *const u8, 2)); - set_object_field(obj, "addListener", closure_value(stream_on as *const u8, 2)); + set_object_field( + obj, + "on", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "once", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "addListener", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); set_object_field( obj, "prependListener", - closure_value(stream_on as *const u8, 2), + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "off", + closure_value(perry_runtime::fn_info!(stream_off, 2; with_declared(2))), ); - set_object_field(obj, "off", closure_value(stream_off as *const u8, 2)); set_object_field( obj, "removeListener", - closure_value(stream_off as *const u8, 2), + closure_value(perry_runtime::fn_info!(stream_off, 2; with_declared(2))), + ); + set_object_field( + obj, + "emit", + closure_value(perry_runtime::fn_info!(stream_emit, 2; with_declared(2))), + ); + set_object_field( + obj, + "pause", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), + ); + set_object_field( + obj, + "resume", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), + ); + set_object_field( + obj, + "destroy", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), ); - set_object_field(obj, "emit", closure_value(stream_emit as *const u8, 2)); - set_object_field(obj, "pause", closure_value(stream_this0 as *const u8, 0)); - set_object_field(obj, "resume", closure_value(stream_this0 as *const u8, 0)); - set_object_field(obj, "destroy", closure_value(stream_this0 as *const u8, 0)); set_object_field( obj, "setEncoding", - closure_value(stream_this1 as *const u8, 1), + closure_value(perry_runtime::fn_info!(stream_this1, 1; with_declared(1))), + ); + set_object_field( + obj, + "read", + closure_value(perry_runtime::fn_info!(stream_read, 1; with_declared(1))), + ); + set_object_field( + obj, + "pipe", + closure_value(perry_runtime::fn_info!(stream_pipe, 1; with_declared(1))), ); - set_object_field(obj, "read", closure_value(stream_read as *const u8, 1)); - set_object_field(obj, "pipe", closure_value(stream_pipe as *const u8, 1)); object_value(obj) } @@ -562,21 +650,61 @@ fn worker_writable_stream_object() -> f64 { let obj = perry_runtime::object::js_object_alloc(0, 0); set_object_field(obj, "writable", js_bool(true)); set_object_field(obj, "destroyed", js_bool(false)); - set_object_field(obj, "on", closure_value(stream_on as *const u8, 2)); - set_object_field(obj, "once", closure_value(stream_on as *const u8, 2)); - set_object_field(obj, "addListener", closure_value(stream_on as *const u8, 2)); - set_object_field(obj, "off", closure_value(stream_off as *const u8, 2)); + set_object_field( + obj, + "on", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "once", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "addListener", + closure_value(perry_runtime::fn_info!(stream_on, 2; with_declared(2))), + ); + set_object_field( + obj, + "off", + closure_value(perry_runtime::fn_info!(stream_off, 2; with_declared(2))), + ); set_object_field( obj, "removeListener", - closure_value(stream_off as *const u8, 2), - ); - set_object_field(obj, "emit", closure_value(stream_emit as *const u8, 2)); - set_object_field(obj, "write", closure_value(stream_write as *const u8, 2)); - set_object_field(obj, "end", closure_value(stream_this1 as *const u8, 1)); - set_object_field(obj, "destroy", closure_value(stream_this0 as *const u8, 0)); - set_object_field(obj, "cork", closure_value(stream_this0 as *const u8, 0)); - set_object_field(obj, "uncork", closure_value(stream_this0 as *const u8, 0)); + closure_value(perry_runtime::fn_info!(stream_off, 2; with_declared(2))), + ); + set_object_field( + obj, + "emit", + closure_value(perry_runtime::fn_info!(stream_emit, 2; with_declared(2))), + ); + set_object_field( + obj, + "write", + closure_value(perry_runtime::fn_info!(stream_write, 2; with_declared(2))), + ); + set_object_field( + obj, + "end", + closure_value(perry_runtime::fn_info!(stream_this1, 1; with_declared(1))), + ); + set_object_field( + obj, + "destroy", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), + ); + set_object_field( + obj, + "cork", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), + ); + set_object_field( + obj, + "uncork", + closure_value(perry_runtime::fn_info!(stream_this0, 0; with_declared(0))), + ); object_value(obj) } @@ -603,8 +731,10 @@ extern "C" fn worker_profile_stop( } pub(super) fn worker_profile_handle(kind_bits: i64) -> f64 { - perry_runtime::closure::js_register_closure_arity(worker_profile_stop as *const u8, 0); - let closure = perry_runtime::closure::js_closure_alloc(worker_profile_stop as *const u8, 1); + let closure = perry_runtime::closure::js_closure_alloc( + perry_runtime::fn_info!(worker_profile_stop, 0; with_declared(0)), + 1, + ); perry_runtime::closure::js_closure_set_capture_ptr(closure, 0, kind_bits); let obj = perry_runtime::object::js_object_alloc(0, 0); set_object_field( diff --git a/crates/perry-ui-ios/src/notifications.rs b/crates/perry-ui-ios/src/notifications.rs index 5bd0addad2..6ab782d106 100644 --- a/crates/perry-ui-ios/src/notifications.rs +++ b/crates/perry-ui-ios/src/notifications.rs @@ -282,12 +282,12 @@ fn invoke_pending_completion(handle: i64, result_code: u64) { } } -/// Allocate a Perry closure whose func_ptr is the trampoline above and whose +/// Allocate a Perry closure running the trampoline above, whose /// captures are `(handle, result_code)`. The returned pointer is what /// `js_promise_then` expects (a `*const ClosureHeader`). unsafe fn make_completion_closure(handle: i64, result_code: u64) -> *const u8 { let closure = perry_runtime::closure::js_closure_alloc( - perry_ios_notification_completion_trampoline as *const u8, + perry_runtime::fn_info!(perry_ios_notification_completion_trampoline, 1), 2, ); perry_runtime::closure::js_closure_set_capture_ptr(closure, 0, handle); diff --git a/crates/perry/tests/source_graph_export_regressions.rs b/crates/perry/tests/source_graph_export_regressions.rs index 49f6e12062..0cec4ede31 100644 --- a/crates/perry/tests/source_graph_export_regressions.rs +++ b/crates/perry/tests/source_graph_export_regressions.rs @@ -342,9 +342,10 @@ fn cross_module_arrow_callback_dispatch_is_resolved_once_and_fails_closed() { "the direct arrow with a mutable capture must get a bounded private body" ); assert!( - all_ir.contains("call void @js_register_closure_trusted_direct(") - && all_ir.contains("i32 1, i64 1)"), - "module init must register the exact one-box capture layout" + all_ir.lines().any(|line| line.contains("$info = ") + && line.contains("$trusted_boxes, i64 1, ptr") + && line.contains("i32 1, ptr @")), + "the arrow's info must name its trusted body with the exact one-box capture layout" ); let trusted_callback = llvm_function_body_containing(&all_ir, "$trusted_boxes", "trusted_box.tdz"); diff --git a/crates/perry/tests/this_param_abi.rs b/crates/perry/tests/this_param_abi.rs index e3969e084a..9642994506 100644 --- a/crates/perry/tests/this_param_abi.rs +++ b/crates/perry/tests/this_param_abi.rs @@ -137,27 +137,38 @@ fn read_ir(dir: &Path) -> String { ir } -/// Symbols passed as `ptr @X` to `js_closure_alloc*` / `js_register_closure_*`. +/// The bodies function objects run: every `JsFunctionInfo` the module +/// defines names its body as its first field (`@X$info = ... { ptr @X, ...`), +/// and every `js_closure_alloc*` names an info (`ptr @X$info`). fn installed_bodies(ir: &str) -> Vec { let mut out: Vec = Vec::new(); - for call in ["@js_closure_alloc", "@js_register_closure_"] { - for (at, _) in ir.match_indices(call) { - let rest = &ir[at..]; - let Some(open) = rest.find('(') else { continue }; - let Some(close) = rest[open..].find(')') else { - continue; - }; - for arg in rest[open + 1..open + close].split(',') { - let Some(sym) = arg.trim().strip_prefix("ptr @") else { - continue; - }; - let sym = sym.trim_matches('"').to_string(); - if !out.contains(&sym) { - out.push(sym); + let mut push = |sym: &str| { + let sym = sym.trim_matches('"').to_string(); + if !out.contains(&sym) { + out.push(sym); + } + }; + for line in ir.lines() { + if let Some(at) = line.find("$info = ") { + if line.starts_with('@') { + if let Some(code) = line[at..].split("{ ptr @").nth(1) { + push(code.split(',').next().unwrap_or_default()); } } } } + for (at, _) in ir.match_indices("@js_closure_alloc") { + let rest = &ir[at..]; + let Some(open) = rest.find('(') else { continue }; + let Some(close) = rest[open..].find(')') else { + continue; + }; + for arg in rest[open + 1..open + close].split(',') { + if let Some(sym) = arg.trim().strip_prefix("ptr @") { + push(sym.trim_matches('"').trim_end_matches("$info")); + } + } + } out } diff --git a/scripts/check_js_body_call_funnel.py b/scripts/check_js_body_call_funnel.py index afa3a10cac..20a1a8f18e 100755 --- a/scripts/check_js_body_call_funnel.py +++ b/scripts/check_js_body_call_funnel.py @@ -25,8 +25,10 @@ declares the receiver (`JsThis`) as its second parameter, unless it is a call ENTRY (`perry_abi::JS_CALL_ENTRIES`: it takes a closure as an ordinary argument) or says `NOT-A-JS-BODY: ` on one of the three lines above; -* a function whose address is handed straight to a closure allocator - (`js_closure_alloc*`, `js_register_closure_*`) declares the receiver. +* a closure allocator (`js_closure_alloc*`) takes the body's static + `JsFunctionInfo`, built by `fn_info!` / `JsFunctionInfo::of` from the + body's TYPED pointer; handing an allocator a bare `NAME as *const u8` is + refused (it can only be a code address where an info is expected). Every call ENTRY (`perry_abi::JS_CALL_ENTRIES`) takes the receiver after the function, and three rules keep native code outside the runtime on that ABI: @@ -35,11 +37,20 @@ (a `#[link_name]` alias included), declares `JsThis` as its second parameter — a stale declaration is otherwise invisible to the compiler; * outside the runtime, stdlib and perry-ffi no crate declares a closure - allocator or registrar (`js_closure_alloc*`, `js_register_closure_*`): - addons register bodies through perry-ffi, whose API is typed; -* perry-ffi's registration functions (`alloc_closure`, `register_closure_*`) - take no `*const u8`: a body is a `JsBody` pointer type, so a wrong - signature does not compile. + allocator (`js_closure_alloc*`): addons allocate through perry-ffi, whose + API is typed; and every declaration of one that does exist takes + `*const JsFunctionInfo` first (an extern declaration taking `*const u8` + would link and pass a code address as the info); +* perry-ffi's `alloc_closure` takes `&'static JsFunctionInfo`, never a + `*const u8`: a body's info is built from its `JsBody` pointer type, so a + wrong signature does not compile. + +Every fact about a body lives in its static `JsFunctionInfo`, which the +function object points to. Nothing is registered by code address: no crate +may name a closure-body registrar (`js_register_closure_*`) or one of the +deleted code-keyed tables (`CLOSURE_BODY_REGISTRY`, `TRUSTED_TARGETS`, +`DISPATCH_RECENT`) — not as a definition, a declaration, a codegen call +string, or a comment that would send a reader looking for one. """ from __future__ import annotations @@ -142,8 +153,8 @@ def fn_defs(text: str): ) JS_THIS_RE = re.compile(r"^(?:[\w:]*::)?JsThis$") ALLOC_RE = re.compile( - r"\b(?:js_closure_alloc(?:_singleton|_with_captures_singleton)?|js_register_closure_\w+" - r"|alloc_closure|register_closure_arity)\s*\(\s*(\w+)\s+as\s+\*const", + r"\b(?:js_closure_alloc(?:_init|_singleton|_with_captures_singleton)?|alloc_closure)" + r"\s*\(\s*(\w+)\s+as\s+\*const\s+u8\b", re.S, ) @@ -205,16 +216,10 @@ def definition_violations(root: Path): ) for rel, text in texts: for m in ALLOC_RE.finditer(text): - name = m.group(1) - found = defs.get(name) - if not found: - continue # a typed local; the compiler checked its type - if any(len(t) > 1 and JS_THIS_RE.match(t[1]) for _, _, t in found): - continue line = text.count("\n", 0, m.start()) + 1 out.append( - f"{rel}:{line}: `{name}` is installed as a JS body but does not declare " - "the callee and the receiver (perry_abi::JS_BODY_*)" + f"{rel}:{line}: `{m.group(1)}` is handed to a closure allocator as a raw " + "pointer; allocators take the body's JsFunctionInfo (`fn_info!`)" ) return out @@ -225,7 +230,8 @@ def definition_violations(root: Path): # `#[link_name = "..."]` a few lines above. DECL_RE = re.compile(r"\bfn\s+(\w+)\s*\(") LINK_NAME_RE = re.compile(r'#\[link_name\s*=\s*"(\w+)"\]') -REGISTRAR_RE = re.compile(r"^(?:js_closure_alloc\w*|js_register_closure_\w+)$") +REGISTRAR_RE = re.compile(r"^js_closure_alloc\w*$") +INFO_PARAM_RE = re.compile(r"^\*const\s+(?:[\w:]*::)?JsFunctionInfo$") REGISTRAR_CRATES_ALLOWED = ("crates/perry-runtime/", "crates/perry-stdlib/", "crates/perry-ffi/") FFI_CLOSURE = "crates/perry-ffi/src" @@ -266,24 +272,27 @@ def entry_abi_violations(root: Path): f"{rel}:{line}: `{name}` {what} the call entry `{symbol}` without the " "receiver (`this: JsThis` after the function; perry_abi::JS_CALL_ENTRIES)" ) - if ( - is_decl - and REGISTRAR_RE.match(symbol) - and not rel.startswith(REGISTRAR_CRATES_ALLOWED) - ): - out.append( - f"{rel}:{line}: declares the closure registrar `{symbol}`; register a body " - "through perry-ffi (`alloc_closure` / `register_closure_*`, typed `JsBody`)" - ) + if is_decl and REGISTRAR_RE.match(symbol): + if not rel.startswith(REGISTRAR_CRATES_ALLOWED): + out.append( + f"{rel}:{line}: declares the closure allocator `{symbol}`; allocate " + "through perry-ffi (`alloc_closure(js_function_info!(..), n)`)" + ) + types = param_types(params) + if not types or not INFO_PARAM_RE.match(types[0]): + out.append( + f"{rel}:{line}: `{name}` declares the allocator `{symbol}` without " + "`*const JsFunctionInfo` first; it would pass a code address as the info" + ) if ( rel.startswith(FFI_CLOSURE) and not is_decl - and re.match(r"^(?:alloc_closure|register_closure_\w+)$", name) - and "*const u8" in params + and name == "alloc_closure" + and "JsFunctionInfo" not in params ): out.append( - f"{rel}:{line}: perry-ffi's `{name}` takes an untyped `*const u8` body; " - "take `F: JsBody`" + f"{rel}:{line}: perry-ffi's `alloc_closure` must take the body's " + "`&'static JsFunctionInfo`" ) return out @@ -329,6 +338,27 @@ def codegen_violations(root: Path): return out +REGISTRY_RE = re.compile( + r"\b(?:js_register_closure_\w+|CLOSURE_BODY_REGISTRY|TRUSTED_TARGETS|DISPATCH_RECENT)\b" +) + + +def registry_violations(root: Path): + """No closure-body registrar or code-keyed body table anywhere in the crates.""" + out = [] + for path in sorted((root / "crates").rglob("*.rs")): + rel = path.relative_to(root).as_posix() + text = path.read_text(encoding="utf-8", errors="replace") + for m in REGISTRY_RE.finditer(text): + line = text.count("\n", 0, m.start()) + 1 + out.append( + f"{rel}:{line}: `{m.group(0)}` — a body's facts live in its static " + "JsFunctionInfo (the function object points to it); nothing is registered " + "by code address" + ) + return out + + def self_test() -> int: import tempfile @@ -367,10 +397,22 @@ def self_test() -> int: 'macro_rules! m { ($n:ident) => { extern "C" fn $n(c: *const ClosureHeader, _this: JsThis $(, $a: f64)*) -> f64 { 0.0 } }; }', False), "def_ffi_no_this": ("crates/perry-ext-zz/src/x.rs", 'extern "C" fn cb(_c: *const RawClosureHeader, a: f64) -> f64 { a }', True), - "alloc_receiverless": ("crates/perry-runtime/src/x.rs", - 'extern "C" fn noop() -> f64 { 0.0 }\nfn f() { js_closure_alloc(noop as *const u8, 0); }', True), - "alloc_ok": ("crates/perry-runtime/src/x.rs", - 'extern "C" fn b(_c: *const ClosureHeader, _this: JsThis) -> f64 { 0.0 }\nfn f() { js_closure_alloc(b as *const u8, 0); }', False), + "alloc_raw_pointer": ("crates/perry-runtime/src/x.rs", + 'extern "C" fn b(_c: *const ClosureHeader, _this: JsThis) -> f64 { 0.0 }\nfn f() { js_closure_alloc(b as *const u8, 0); }', True), + "alloc_info_ok": ("crates/perry-runtime/src/x.rs", + 'extern "C" fn b(_c: *const ClosureHeader, _this: JsThis) -> f64 { 0.0 }\nfn f() { js_closure_alloc(fn_info!(b, 0), 0); }', False), + "alloc_decl_code_ptr": ("crates/perry-stdlib/src/x.rs", + 'extern "C" {\n #[link_name = "js_closure_alloc"]\n fn provider_alloc(f: *const u8, n: u32) -> *mut u8;\n}', True), + "alloc_decl_info_ok": ("crates/perry-stdlib/src/x.rs", + 'extern "C" {\n fn js_closure_alloc(info: *const JsFunctionInfo, n: u32) -> *mut u8;\n}', False), + "registrar_def": ("crates/perry-runtime/src/x.rs", + 'pub extern "C" fn js_register_closure_arity(f: *const u8, n: u32) {}', True), + "registrar_codegen_call": ("crates/perry-codegen/src/x.rs", + 'fn f() { blk.call(VOID, "js_register_closure_rest", &[]); }', True), + "registry_table": ("crates/perry-runtime/src/x.rs", + "static CLOSURE_BODY_REGISTRY: u8 = 0;", True), + "info_alloc_no_registry_ok": ("crates/perry-codegen/src/x.rs", + 'fn f() { blk.call(I64, "js_closure_alloc_singleton", &[]); }', False), "cg_funnel_ok": ("crates/perry-codegen/src/expr/body_call.rs", "fn f() { blk.call_indirect(DOUBLE, &p, &a); }", False), "entry_def_no_this": ("crates/perry-runtime/src/x.rs", 'pub extern "C" fn js_closure_call2(c: *const ClosureHeader, a: f64, b: f64) -> f64 { a }', True), @@ -380,12 +422,12 @@ def self_test() -> int: 'extern "C" {\n fn js_closure_call2(c: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64;\n}', False), "entry_link_name_no_this": ("crates/perry-stdlib/src/x.rs", 'extern "C" {\n #[link_name = "js_closure_call2"]\n fn provider_call2(c: *const ClosureHeader, a: f64, b: f64) -> f64;\n}', True), - "ext_registrar_decl": ("crates/perry-ext-zz/src/x.rs", - 'extern "C" {\n fn js_closure_alloc(f: *const u8, n: u32) -> *mut u8;\n}', True), + "ext_allocator_decl": ("crates/perry-ext-zz/src/x.rs", + 'extern "C" {\n fn js_closure_alloc(info: *const JsFunctionInfo, n: u32) -> *mut u8;\n}', True), "ffi_untyped_alloc": ("crates/perry-ffi/src/closure.rs", 'pub fn alloc_closure(func: *const u8, capture_count: u32) -> *mut ClosureHeader { todo!() }', True), - "ffi_typed_alloc_ok": ("crates/perry-ffi/src/closure.rs", - 'pub fn alloc_closure>(func: F, capture_count: u32) -> *mut ClosureHeader { todo!() }', False), + "ffi_info_alloc_ok": ("crates/perry-ffi/src/closure.rs", + "pub fn alloc_closure(info: &'static JsFunctionInfo, capture_count: u32) -> *mut ClosureHeader { todo!() }", False), } failed = 0 for name, (rel, text, expect_red) in cases.items(): @@ -404,6 +446,7 @@ def self_test() -> int: or codegen_violations(root) or definition_violations(root) or entry_abi_violations(root) + or registry_violations(root) ) if red != expect_red: print(f"self-test {name}: expected {'red' if expect_red else 'green'}, got {'red' if red else 'green'}") @@ -425,6 +468,7 @@ def main() -> int: + codegen_violations(REPO) + definition_violations(REPO) + entry_abi_violations(REPO) + + registry_violations(REPO) ) if violations: print("\n".join(violations)) diff --git a/scripts/check_runtime_symbols.sh b/scripts/check_runtime_symbols.sh index 35d9bb81c6..c1d7acb532 100755 --- a/scripts/check_runtime_symbols.sh +++ b/scripts/check_runtime_symbols.sh @@ -55,7 +55,6 @@ SENTINELS=( js_closure_set_capture_bits js_closure_set_box_capture_ptr js_closure_resolve_plain_direct_call - js_register_closure_trusted_direct # #9188: codegen switched module init from the copying spellings to these. # A runtime archive predating the split defines neither, and every program # with a named function would fail to link on a build worker instead of here. diff --git a/scripts/gc_root_dominance_check.py b/scripts/gc_root_dominance_check.py index 9765207f48..3cae04b988 100755 --- a/scripts/gc_root_dominance_check.py +++ b/scripts/gc_root_dominance_check.py @@ -944,6 +944,9 @@ def dead_poll_capable(symbols): "assert", "assert_eq", "assert_ne", "debug_assert", "debug_assert_eq", "debug_assert_ne", "matches", "println", "eprintln", "print", "eprint", "write", "writeln", "format", "panic", "vec", "dbg", "todo", "unimplemented", + # `fn_info!(body, n)` names a body to build its private `static INFO` + # (a JsFunctionInfo); it defines no export. + "fn_info", )) _INDENTED_MACRO_RE = re.compile( r'^[ \t]+(?:\w+::)*(\w+)!\s*[({]\s*(js_\w+)\s*(?:=>|[,)])') diff --git a/scripts/gc_runtime_root_holders.json b/scripts/gc_runtime_root_holders.json index 616868aa4d..2ae7b3edf0 100644 --- a/scripts/gc_runtime_root_holders.json +++ b/scripts/gc_runtime_root_holders.json @@ -39,12 +39,6 @@ "verdict": "not_a_gc_pointer", "why": "This agent's base Function ShapeIds ([u32; 5]): shape-table ids, not addresses. The records they name are pinned by RECORD_FLAG_EXTERNAL_CARRIER at mint." }, - { - "file": "crates/perry-runtime/src/closure/shape.rs", - "name": "LAST_BODY", - "verdict": "not_a_gc_pointer", - "why": "One-entry closure-birth cache: (func_ptr code address, ShapeId). A code address and an id; nothing for the collector." - }, { "file": "crates/perry-runtime/src/closure/shape.rs", "name": "VERDICT_CACHE", @@ -286,30 +280,6 @@ "verdict": "not_a_gc_pointer", "why": "Cache miss counter (telemetry). Holds no address." }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "BODY_RECORD_LOOKUPS", - "verdict": "test_only", - "why": "#10127's call/apply rest-dispatch cache counts how often a dispatch resolves through the body-record lookup instead of the cache, so the tests can assert the cache is actually taken rather than that nothing threw. It is a `Cell` count inside a `#[cfg(test)]` `std::thread_local!` block \u2014 absent from production builds, and it never stores an address. Its sibling RESOLVE_STRATEGY_SLOW_CALLS in the same block is pinned on the frontier as debt; this one takes a researched verdict instead, which is the stronger record." - }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "CLOSURE_BODY_REGISTRY", - "verdict": "not_a_gc_pointer", - "why": "#9707: the unified per-body record table. Keyed by a closure body `func_ptr` (a CODE address the collector neither moves nor traces); the value is `ClosureBodyRecord` = three plain integers (arity, rest arity, `.length`), a flags word, and a 1-based index into TRUSTED_TARGETS. No field can hold a heap pointer, so the table cannot hold a GC root. It replaced the ten `CLOSURE_*_REGISTRY` / `DISPATCH_CACHE` maps that carried the same verdict." - }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "TRUSTED_TARGETS", - "verdict": "not_a_gc_pointer", - "why": "#9707: dense side array of `TrustedTargets`, two `Option` per eligible arrow. TrustedDirectTarget{func_ptr:*const u8, capture_count:u32, boxed_capture_mask:u64} is a static code pointer plus plain integers \u2014 the same payload the deleted CLOSURE_ARROW_FUNCTION_REGISTRY / CLOSURE_VERSIONED_LOOP_REGISTRY held under this verdict. Nothing for the collector." - }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "PARAMS", - "verdict": "not_a_gc_pointer", - "why": "WASI only (cfg(target_os = \"wasi\")): memo of each closure body's real parameter count, keyed by the wasm function-table index of its function pointer (a code address, never a heap address) and valued by an i32 count. Nothing for the collector." - }, { "file": "crates/perry-runtime/src/dyn_eval/registry_lifetime.rs", "name": "ACTIVE_FUNCTIONS", @@ -392,7 +362,7 @@ "file": "crates/perry-runtime/src/gc/census.rs", "name": "PASS1_MARKED", "verdict": "non_moving_snapshot", - "why": "Real GC header addresses, deliberately untraced so the diagnostic does not keep its observed objects alive. Populated only at the end of mark propagation of a synchronous full cycle; consumed at sweep entry in the same run_to_completion invocation. The intervening full-cycle phases do not relocate or run JS callbacks. The Vec is used for membership comparisons and dropped with the census before sweep. Budgeted and minor cycles skip both boundaries. Pin re-audited 2026-09-05 after #9760 touched `gc/mod.rs`: that change is `mod heap_stats;` plus a `pub(crate) use` re-export and alters no mark/sweep control flow. `heap_stats()` is reached only from `js_bun_jsc_heap_stats` (the JS-facing `bun:jsc.heapStats()`), i.e. from mutator code, never inside a cycle, and its own module contract forbids allocation or collection during its walk. The mark-complete \u2192 sweep-entry window is unchanged. Re-audited 2026-09-05 (train125) after #9769 and #9771 touched pinned files. #9769 adds one `reg_scanner!` registration to `gc/mod.rs`; #9771 adds a feature-gated `alloc_census_init()` there and a feature-gated Rust-heap dump inside `take_census`. `alloc-census` is not in the default feature set, and decisively: `census_take_if_armed_at_full_sweep_start` does `PASS1_MARKED.with(|p| p.borrow_mut().take())` BEFORE calling `take_census`, so the snapshot has already left the thread-local by the time #9771's code runs \u2014 it cannot affect the window. Neither change alters mark/sweep control flow. Re-audited 2026-09-06 after #9831 touched `gc/policy.rs`. Its hunks are (a) the tiny-parse pressure guard's pricing (`tiny_parse_pressure_headroom_bytes`, `tiny_parse_pressure_due*`, a `Cell` byte-count base) consulted from JSON.parse's mutator-side boundaries (`gc_bump_malloc_trigger`, `gc_collect_pending_suppressed_parse`, `gc_schedule_parse_boundary_collection_if_pressure`), none of which is reachable from inside a cycle, and (b) one extra `Cell` store in `note_collection_finished_arena_occupancy`, which runs from `publish_reclaim_outcome` in the Publish subphase \u2014 after `step_sweep` has already consumed the snapshot. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Re-audited 2026-09-05 (train126) after #9755 restructured `gc/cycle.rs`. Its hunks are all root-scan machinery (`RootScanSubphase`, `RootScanCycleState`, the mutable-scanner iteration state), which runs BEFORE mark propagation completes; `gc/mod.rs` gains only a `mod young_log;` declaration. The bracketing is unchanged \u2014 `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep` \u2014 and a synchronous full mark-sweep still moves nothing between them. Re-pinned 2026-09-05 for the #9740 hot-TLS conversion of this file: the sole change is `thread_local!` \u2192 `crate::perry_thread_local!`, a macro-name swap with identical declaration syntax and `.with()` call sites. No control flow, no phase boundary, and no storage semantics change. Re-audited 2026-09-06 (train128) after #9794's GC diagnostics touched `gc/mod.rs` and `gc/policy.rs`: both gain diagnostic module declarations and counters only \u2014 no mark/sweep control flow, and the census bracketing in `step_mark_propagation` / `step_sweep` is unchanged. Re-audited for #9794's GC diagnostics: `gc/mod.rs` gains `mod diag_sites;` / `mod survival_diag;`, a re-export, a `diag_sites::full_started(...)` call at TRIGGER time (before mark propagation begins), and exit-time reporting. Nothing executes between mark-complete and sweep-entry, so the window is unchanged. Re-audited 2026-09-06 for the retained array-growth verifier fix: the cycle.rs change passes the existing non-copying evacuation verifier an explicit all-forwarded policy. That call remains in minor finalization, outside the synchronous full-cycle census window; its root and heap reads do not allocate GC objects, move objects, or invoke JS callbacks. The mark-complete and sweep-entry boundaries are unchanged. Re-audited 2026-09-05 after #9830 touched `gc/policy.rs`. That change is (a) six `thread_local! {` blocks rewritten as `crate::perry_thread_local! {` and (b) one `#[cfg(test)]` accessor listing the trigger path's hot-slot indices. The macro keeps the same storage, the same `.with()` at every read and write, and the same destructor registration (the teardown guard exists exactly when `needs_drop` holds, which is what `std::thread_local!` already decided); no value, predicate or branch in the file changes, so no mark or sweep control flow does. The one new behaviour is on a declaration's FIRST read: `HotKey::resolve_and_cache` takes a mutex and allocates a key through the GLOBAL allocator. Even if a first read landed inside this window it would be sound \u2014 the window's contract is that nothing relocates and no JS callback runs, and a mimalloc allocation does neither. `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`; the bracketing is untouched. Re-audited 2026-09-06 (train132) after #9860 and #9845 touched `gc/mod.rs`. Both hunks are re-export lists and nothing else: #9860 adds `idle_reclaim_elapsed_starts` / `IDLE_RECLAIM_REARM_MS`, and #9845 adds `owner_is_dead_copied_minor_from_space_of_type`. No mark or sweep control flow changes. #9845's substantive work sits in `gc/oldgen.rs` and `gc/copying.rs`, neither pinned: the copying-minor arm (`finalize_dead_copied_minor_from_space_regexps`) runs on a MINOR, which skips both census boundaries; the full-cycle arm (`collect_dead_registered_regexps_post_trace`, from `with_dead_collection_finalize`) walks the RegExp registry building a Vec of addresses \u2014 no GC allocation, no JS callback, so it cannot relocate the snapshot's subjects \u2014 and it is reached from the sweep body, i.e. AFTER `census_take_if_armed_at_full_sweep_start` has already `take()`n the snapshot out of the thread-local. The mark-complete -> sweep-entry window is unchanged. Re-audited 2026-09-07 for #9965 after 1ec9e0e8a touched `gc/cycle.rs` and `gc/mod.rs`: `gc/mod.rs:216-217` only declares and imports the failure-attribution module, while `gc/cycle.rs:1414-1417` reads the trigger and diagnostic counters immediately before evacuation verification inside `atomic_finalize_minor_prelude`. Full cycles bypass `MinorPrelude` at `gc/cycle.rs:1192-1196`; evacuation remains guarded by the minor-only context at `gc/cycle.rs:1330-1372`. The snapshot store remains at `gc/cycle.rs:963-964` after synchronous full marking, and its take remains at `gc/cycle.rs:1454-1457` before sweep. No new write, relocation, collection, or JS callback was added to that full-cycle interval, so the PASS1_MARKED window is unaffected. Re-audited 2026-09-07 for the regex census rows: all new work is in `take_census` after `census_take_if_armed_at_full_sweep_start` has taken PASS1_MARKED out of TLS; neither boundary nor the intervening cycle control flow changed. Re-audited 2026-09-08 (train144) after #9976 and #9977 touched pinned files. `gc/mod.rs` gains exactly three lines: `mod copying_phase;` and `mod regex_census;` (declarations) and one `reg_scanner!(regex::site_test::scan_roots_mut)` registration. A scanner registration adds a root SOURCE for the mutable-root walks; it does not move either census boundary and runs nowhere between them. `gc/census.rs` widens `side_tables()` to `pub(super)`, extends it with regex rows and adds a test module \u2014 all census REPORTING, which runs from the diagnostic dump, not inside a cycle. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Re-audited 2026-09-08 for #9849 JSON construction deferral. `gc/mod.rs` adds the `json_defer` module/re-export and a trusted-header layout helper used only by already-validated JSON emitters; neither changes or runs in collector phase control flow. `gc/policy.rs` adds JSON completion scheduling, construction-grace checks, and safepoint deferral predicates. These are called from mutator-side JSON allocation/output boundaries and ordinary safepoint entry; they do not alter `step_mark_propagation`, `step_sweep`, or invoke callbacks or relocation between the census boundaries. The mark-complete to sweep-entry window is unchanged. The follow-up adds a cfg(test)-only one-shot boolean for deterministic explicit-pressure fixtures; it is absent from production builds and cannot affect the census window. The first predicate read consumes it, so post-parse accounting exercises normal pricing. Re-audited 2026-09-09 for bounded tiny-JSON completion polling. The policy.rs changes split the mutator-side pending-parse check into an inlined empty fast path plus an outlined debt-service path, and amortize the mutator-side arena-pressure read across 64 bounded parse completions. Neither function is reachable from step_mark_propagation or step_sweep; neither census boundary nor the synchronous full-cycle interval between them changes. Re-audited 2026-09-09 for lazy JSON record batches: policy.rs only widens gc_budgeted_cycle_active visibility from pub(super) to pub(crate). Its body remains a read-only Cell query. The new caller is lazy_get materialization in the mutator; run_to_completion, step_mark_propagation, census snapshot consumption at step_sweep, and the synchronous non-moving window are unchanged. Re-audited 2026-09-09 for completed JSON-output debt: the added gc_service_json_output_sweep function calls the existing trigger check from a rooted mutator boundary and reports whether its malloc-count request remains due. It is not called from any census or collector phase; the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-09 for the JSON byte-debt carry: the same mutator-only service helper now distinguishes requests satisfied before its call from those satisfied by its trigger check. The added enum contains no payload, both count reads are scalar, and no census boundary or collector phase changed. Re-audited 2026-09-11 for #10055: gc/mod.rs only registers the weak UTF-16 index scanner during gc_init. It neither marks strings nor allocates GC objects or runs JS; offset vectors use the Rust allocator. The mark-complete to sweep-entry census window and cycle control flow are unchanged. Re-audited 2026-09-11 for #10054: gc/mod.rs adds only the trim-cache mutable-root scanner registration in gc_init. Its scanner visits two existing string slots without allocating or invoking JS. Root scanning still precedes mark completion, and neither census boundary nor the synchronous mark-complete to sweep-entry window changes. Re-audited 2026-09-11 for #10060: the census array classifier now reads the logical element start and bounds its scan by the remaining capacity. The helper only reads the existing GC/header words and performs pointer arithmetic; it cannot allocate, collect, or call JS. This classifier runs in take_census after PASS1_MARKED has been taken out of TLS. Neither census boundary nor the mark-complete to sweep-entry control flow changed. Re-audited for #8512: gc/mod.rs only enables the existing PTY mutable-root scanner on Windows; it changes no mark/sweep phase or census boundary. The scanner visits NaN-boxed slots without running JS callbacks. Re-audited 2026-09-12 for the single regular-expression engine: `gc/mod.rs` changes `mod prefetch;` to `pub(crate) mod prefetch;` so the RegExp owner-table walks can prefetch headers, a visibility change with no new call in collector control flow; `gc/census.rs` changes only its `#[cfg(test)]` `regex_census_tests` module, dropping assertions for the previous engine's cache rows. Neither boundary (`census_pass1_if_armed` in `step_mark_propagation`, `census_take_if_armed_at_full_sweep_start` in `step_sweep`) nor the synchronous mark-complete to sweep-entry interval changes. Re-audited 2026-09-13 after the #10169 fix touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` gains only `pub(crate) use` re-exports (`policy::note_young_leaf_born_old`, `policy::young_generation_holds_a_nursery`, `promote_in_place::{young_generation_measured_dying, young_generation_measured_retained}`, and cfg(test) survival seeders). `gc/policy.rs` gains a `Cell` thread-local (`GC_YOUNG_LEAF_BORN_OLD`, no pointer), its setter, a pure predicate over `copying_from_space_in_use_bytes` vs the base nursery cap, and a consumed-once branch at the top of `gc_budgeted_due_trigger` that may answer `YoungScavengeCap` ahead of `OldReclaim`. That branch decides WHICH collection a safepoint starts (a minor instead of a full); it runs before any cycle begins and never inside one, so the mark-complete \u2192 sweep-entry window of a synchronous full \u2014 where PASS1_MARKED is populated and consumed within one `run_to_completion` \u2014 is unchanged, and neither hunk adds an allocation, a JS callback, or a relocation to it. Re-audited 2026-09-13 for the heap generation (#10164 cross-call search positions): `gc/mod.rs` only declares `pub(crate) mod heap_generation;`. `gc/cycle.rs` wraps the `Sweep` and `Reclaim` arms of `GcCycleState::step` in a `HeapChange` scope and opens one inside `atomic_finalize_minor_prelude`'s evacuation branch (with a nested one around old-page defrag). Opening and closing a scope only increments two thread-local integer cells (`HEAP_GENERATION`, `OPEN_HEAP_CHANGES`); a first thread-local read may allocate a key through the global allocator, which neither relocates nor runs JS. The `Sweep` scope opens immediately before `step_sweep`, i.e. before `census_take_if_armed_at_full_sweep_start` takes PASS1_MARKED out of TLS, and adds no relocation, collection or JS callback to the synchronous mark-complete to sweep-entry window; the minor-prelude scope is unreachable from a full cycle, which bypasses `MinorPrelude`. Neither boundary nor the intervening control flow changed. Re-audited 2026-09-13 for #10182 block-granular reclamation, which touched `gc/cycle.rs`. Two hunks: (a) in the `RememberedSetRebuild` subphase of AtomicFinalize \u2014 INSIDE the window \u2014 the require-marked old-to-young rebuild is now constructed with `OldToYoungRememberedRebuildState::new_skipping`, whose cursor never enters blocks the census recorded as holding no reached, pinned or pre-marked object (`BlockCensus::unmarked_blocks`); computing that list reads `arena_block_snapshots()` and allocates one `Vec` through the global allocator. It visits a subset of the same objects the rebuild already walked (every skipped object would have been rejected as unmarked), and it neither allocates a GC object, relocates anything, nor runs a JS callback. (b) In `step_sweep`, `IncrementalSweepState::with_block_skip` runs after `census_take_if_armed_at_full_sweep_start` has already taken PASS1_MARKED out of TLS. Neither boundary moved and the synchronous mark-complete to sweep-entry interval gains no relocation, collection or callback. Re-audited 2026-09-11 for the startup memory profile: gc/mod.rs only retains the pre-main allocator-policy constructor in js_gc_init. The constructor applies process allocation options, without invoking GC or JS. No census boundary, collector phase, or mark-complete to sweep-entry control flow changed. Re-audited 2026-09-13 for #10179: census.rs only adds a native regex cache metadata row and its unit assertion; snapshot consumption and the full-cycle window are unchanged. Re-audited 2026-09-14 for the GC due-check fast path, which touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` only changes the safepoint re-exports: `gc_runtime_safepoint` becomes cfg(test) and `gc_runtime_safepoint_poll` is added. `gc/policy.rs`: the budgeted step returns a debt-free `GcStepReport` (debt is attached by the FFI and test entry points after the step returns) and moves cycle start/step into an out-of-line `gc_budgeted_start_or_step`; `gc_check_trigger` reuses a repeatable due-trigger answer through `DueTriggerMemo`, placed after its `GC_FLAG_IN_ALLOC` and suppression early returns; the young scavenge cap reuses the old-gen pressure value the due trigger already read and checks the census-seeded flag first. All of it runs from mutator safepoints, allocation-point trigger checks and the host step API, before a cycle starts or between budgeted steps. None of it is reachable between `census_pass1_if_armed` in `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` in `step_sweep` of a synchronous full: an allocation inside that window reaches `gc_check_trigger` with `GC_FLAG_IN_ALLOC` set and returns before the changed code. No allocation, relocation, collection or JS callback is added to the window. Re-audited 2026-09-14 for the tiny-parse nursery-cap boundary, which touched `gc/policy.rs`. It adds `tiny_parse_generational_collection_due`, a pure predicate (the existing `tiny_parse_pressure_due` OR the existing `young_scavenge_cap_due` read), and calls it instead of `tiny_parse_pressure_due` from `gc_bump_malloc_trigger_inner` and `gc_collect_pending_suppressed_parse_slow` (generational branch only) and from `gc_schedule_parse_boundary_collection_if_pressure`. All three are JSON.parse mutator-side boundaries, none reachable from `step_mark_propagation` or `step_sweep`; the predicate reads counters and allocates nothing. Neither census boundary nor the synchronous mark-complete to sweep-entry interval changed. Re-audited 2026-09-13 for #10182's full-collection throughput follow-up, which touched `gc/cycle.rs` in one hunk, INSIDE the window: the `RememberedSetRebuild` subphase of a synchronous full now first asks `verify::full_remembered_rebuild_provably_empty` and, when it holds, installs `OldToYoungRememberedRebuildState::provably_empty()` (an empty sticky set, no walk) instead of the require-marked rebuild. The predicate reads `arena_block_snapshots()` (one `Vec` through the global allocator), the census's per-block reached/pre-marked facts and the malloc registry's length; the constructor bumps a `Cell` counter and prints one line under `PERRY_GC_DIAG`. None of it allocates a GC object, relocates anything, collects, or runs a JS callback, and both census boundaries stay where they were. Re-audited 2026-09-14 for #10182's pacing-full work, which touched `gc/cycle.rs`, `gc/mod.rs` and `gc/policy.rs`. `gc/cycle.rs`: `GcCycleState::new_full` no longer calls `materialize_all_promoted_page_runs`; that call ran in the constructor, before the census and far before `census_pass1_if_armed`, and removing it adds nothing to the window. `gc/mod.rs`: one `mod promoted_cohort;` declaration. `gc/policy.rs`: (a) `credit_promoted_bytes_to_old_baseline` also credits a `Cell` cohort counter (it runs after a copying minor completes); (b) `finish_full_old_reclaim_baseline` also records the verified old live bytes and resets that counter (Publish, after `step_sweep` consumed the snapshot); (c) `gc_safepoint_moving_minor` arms and disarms the promotion-census record around its nursery minor and calls `run_promoted_cohort_full_if_due`, which starts a synchronous full through the same `gc_collect_full_mark_sweep_with_trigger` entry and reads byte counters before and after it. All of it runs before a cycle starts or after it completes; none of it runs between mark completion and sweep entry, allocates a GC object, relocates anything, or calls into JS. The census the promoted-cohort full may adopt from the promotion walk is built in `BuildValidPointerSet`, before either boundary. Both boundaries are unchanged. Re-audited 2026-09-14 for the #10182 dead-stack scrub in `gc/cycle.rs`: `step_build_valid_pointer_set` now calls `scrub_dead_stack_below`, which zeroes a local array in its own frame (dead stack below the caller), right after the census finishes \u2014 in `BuildValidPointerSet`, before the root scan and far before `census_pass1_if_armed`. It writes no heap memory, allocates nothing, relocates nothing and calls no JS; both boundaries are unchanged. Re-audited 2026-09-14 for #10241 (cohort survival), which touched `gc/cycle.rs` and `gc/policy.rs`. `gc/cycle.rs`: one call, `promoted_cohort::survival::check_minor_view_at_full_sweep_start()`, in `step_sweep` immediately AFTER `census_take_if_armed_at_full_sweep_start` has taken PASS1_MARKED out of TLS, i.e. outside the window. It is a no-op unless a promoted-cohort full armed its survival probe; when armed it walks the old page index over the preceding minor's dirty pages (`old_arena_walk_objects_on_pages`, Rust-allocator Vecs), reads GC headers' mark flags and the slots of unmarked ones, and records one enum. It writes no heap memory, allocates no GC object, relocates nothing and calls no JS. `gc/policy.rs`: `run_promoted_cohort_full_if_due` arms the probe before `gc_collect_full_mark_sweep_with_trigger` and takes it after the full returns (feeding `note_full_measured_promotion_survival` and one diagnostic line); both run before a cycle starts or after it completes. Both boundaries are unchanged. Re-audited 2026-09-14 for #10241's in-place-only cohort: `gc/policy.rs` drops the `promoted_cohort::note_promoted` call from `credit_promoted_bytes_to_old_baseline` (the copying minor now calls `promoted_cohort::note_minor_promotion` itself, after the credit). Both run at the end of a copying minor, outside any full cycle; the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-14 for the parse-boundary side-allocation band (medium-parse pacing), which touched `gc/policy.rs`. Three hunks: (a) a `Cell` thread-local (`GC_LAST_COLLECTION_EXTERNAL_SIDE_BYTES`, a byte COUNT, no pointer) plus three pure predicates over it and `external_side_live_bytes()`; (b) that predicate added as a third disjunct of `tiny_parse_generational_collection_due`, which is read only from the three JSON.parse mutator-side boundaries (`gc_bump_malloc_trigger_inner`, `gc_collect_pending_suppressed_parse_slow`, `gc_schedule_parse_boundary_collection_if_pressure`), none of them reachable from `step_mark_propagation` or `step_sweep`; and (c) one extra `Cell` store in `note_collection_finished_arena_occupancy` plus two extra reads in the `PERRY_GC_DIAG` tiny-parse line. `note_collection_finished_arena_occupancy` runs from `publish_reclaim_outcome` in the Publish subphase, i.e. AFTER `step_sweep` has already `take()`n the snapshot out of the thread-local, exactly as #9831's store on the same line does. Nothing added allocates a GC object, relocates anything, or runs a JS callback, and neither census boundary moved. Re-audited 2026-09-14 for the drained-bytes counterweight to that band, which touched `gc/policy.rs` again. Four hunks: a second `Cell` thread-local (`GC_EXTERNAL_SIDE_DRAINED_SINCE_FULL`, a byte COUNT); one increment of it inside `gc_note_external_side_free`; a pure read (`external_side_old_reclaim_pressure_bytes`) substituted for `external_side_live_bytes()` at the four old-reclaim pressure sites; and one `Cell` store at the top of `finish_full_old_reclaim_baseline`. None of it can run between the census boundaries. `gc_note_external_side_free` is also reached by mutator-side tape materialization, regex scratch teardown, native-addon adjustments and buffer replacement. Its added operation is only a saturating increment of a scalar Cell, with no GC allocation, relocation, collection or JS callback, so this wider caller set does not invalidate the census window. `finish_full_old_reclaim_baseline` runs from `publish_reclaim_outcome` in the Publish subphase, the same place #9831's store already sits. The pressure reads happen at trigger decisions, before a cycle starts. No allocation, relocation, collection or JS callback is added to the mark-complete -> sweep-entry window, and neither boundary moved. Re-audited 2026-09-15 for turnloop P0, which touched `gc/mod.rs` with one added call: `crate::event_pump::shutdown_wait_driver()` inside `js_gc_release_current_thread_collection_side_allocations`, the process-exit funnel. That function runs once no more JavaScript can run on the thread, never from inside a collection cycle; the added call drops the thread's turnloop wait loop (closing its kqueue/epoll descriptor) and may print a diagnostic line. It allocates no GC object, relocates nothing, starts no collection and runs no JS callback. The census boundaries and the mark-complete -> sweep-entry window are untouched.. Re-audited 2026-09-16 for the copying minor's per-parent weak-holder fact: `gc/mod.rs` gains exactly one line, `mod copying_parent_facts;`, a module declaration. The module it declares holds `weak_holder_fact` (a read of the parent's `obj_type`/`class_id` via `weakref::is_weak_holder_header`) and the copying minor's `visit_slot_with_parent`, moved verbatim out of `gc/copying.rs` for the 2000-line lint. Both run only inside a COPYING MINOR, which skips both census boundaries (`census_pass1_if_armed` in `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` in `step_sweep` are synchronous-full only). Nothing was added to any full-cycle phase, and the declaration itself executes no code. Neither boundary moved and the synchronous mark-complete to sweep-entry window gains no allocation, relocation, collection or JS callback. Re-audited 2026-09-18 for the #10532 follow-up argument-list rooting fix, which touched `gc/mod.rs`. The only change there is `mod collection_points;` plus a `pub(crate) use collection_points::collection_point;` re-export (and, under `#[cfg(test)]`, `arm_collection_point`). `collection_point` is an inline no-op outside `cfg(test)`; under test it only runs a copying minor when called from ordinary MUTATOR code (`proxy.rs`'s `Reflect.apply` rebind path and `registry.rs`'s rest-array bundler), never from inside `step_mark_propagation` or `step_sweep`. Neither `census_pass1_if_armed` nor `census_take_if_armed_at_full_sweep_start` is reachable from it, so the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-18 (same PR, round 2) for the added `arm_collection_point_after` re-export in `gc/mod.rs`: another pure re-export line, same as the `collection_point`/`arm_collection_point` one already covered above. `arm_collection_point_after` only changes test-only arming state in `collection_points.rs` (which named site fires and on which hit); it still runs no mark/sweep control flow. Re-audited 2026-09-19 for #10735 (require.main threading): gc/mod.rs gains exactly one line, `reg_scanner!(crate::module_require::scan_cjs_main_module_root_mut);`, registering the new CJS_MAIN_MODULE thread-local's mutable-root scanner beside the existing `scan_module_path_roots_mut` registration. A scanner registration adds a root SOURCE for the mutable-root walks; it runs during root scanning, before mark propagation completes, and does not execute between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start`. Neither census boundary moved and the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-20 for #10834 (inherited-property read cache). `gc/mod.rs` gains exactly one line: `reg_scanner!(crate::object::inherited_read_cache::scan_inherited_read_cache_roots_mut);` in `gc_init()`. A scanner registration adds a root SOURCE for the mutable-root walks. The walk runs inside `RootScanCycleState::step_current_subphase`, i.e. entirely within the RootScan phase: `step_root_scan` only sets `self.phase = GcCyclePhase::MarkPropagation` once that loop reports done (`gc/cycle.rs:958-961`), and `census_pass1_if_armed()` fires at the END of `step_mark_propagation` (`gc/cycle.rs:982`). The scanner therefore runs strictly BEFORE the window opens and can never execute between the boundaries. Its body is a bounded walk of a fixed 512-entry thread-local array calling `visit_tagged_usize_slot` / `visit_usize_slot`; it allocates nothing, relocates nothing and runs no JS callback. Same shape as #9769, #9976/#9977, #10054, #10055 and #10735, all previously cleared. The PR also adds an `INHERITED_READ_CACHE` entry to `DEAD_KEY_PRUNES` in `gc/dead_owner.rs` (not a pinned source). That registry is consumed by `IncrementalSweepState::with_dead_collection_finalize` at `gc/cycle.rs:1548`, which is AFTER `census_take_if_armed_at_full_sweep_start` at `gc/cycle.rs:1505` has already `take()`n the snapshot out of the thread-local -- the same argument that cleared #9845's `collect_dead_registered_regexps_post_trace`. The prune reads addresses and zeroes entries; no GC allocation, relocation or callback. Both additions sit outside the window, on opposite sides of it. Neither boundary moved and the synchronous mark-complete to sweep-entry interval is unchanged. Re-audited 2026-09-22 for #10399 (per-thread module init), which touched `gc/mod.rs`. Two hunks, both init-time: a new free function `raise_default_thread_stack_floor()` and one call to it at the top of `js_gc_init`, before `enter_current_thread_image`'s successor statements. The function reads `RUST_MIN_STACK` from the environment and, only when it is unset, sets it to 32 MiB so a thread spawned against a multi-megabyte static TLS block still has usable stack (glibc carves static TLS out of the thread's stack mapping). It touches no heap object, allocates no GC object, relocates nothing and runs no JS callback. `js_gc_init` is the first runtime call of a compiled `main`, so it runs once before any cycle exists, and it is not reachable from `step_mark_propagation` or `step_sweep`. Same shape as the 2026-09-11 startup-memory-profile re-audit, which cleared the pre-main allocator-policy constructor in the same function. Neither census boundary moved and the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-23 (size/runtime-decouple, #11135) after the binary-size branch touched `gc/census.rs`, `gc/mod.rs` and `gc/policy.rs`. census.rs: `census_pass1_if_armed` / `census_take_if_armed_at_full_sweep_start` keep their bodies verbatim, moved into `_impl` functions compiled only with the new `gc-instruments` feature (without it both are empty and `census_path()` is `None`, so nothing is ever armed); the take still empties PASS1_MARKED before `take_census`. gc/mod.rs: `gc_init` gains a startup env check that aborts when an instrument knob is set without the feature, before any cycle exists. gc/policy.rs: env-knob OnceLock caches now initialize through `crate::once_init::get_or_init` (same closures, same values). No mark/sweep control flow between the two census boundaries changed; the window is unchanged. Re-audited for Fetch handle reclamation: cycle.rs only redirects the incomplete-cycle Drop cancellation hook to also cancel the Fetch trace. The full-trace finish hook removes native records and cached slots without allocating GC objects or invoking JS; it cannot relocate the census addresses before sweep entry. Re-audited 2026-09-22 for #10928 (one proportional old-reclaim rule), which touched `gc/policy.rs`. Six hunks. (a) Two new thread-locals, `GC_OLD_RECLAIM_PRE_IN_USE_BYTES` (`Cell`) and `GC_OLD_RECLAIM_BACKOFF_SHIFT` (`Cell`): both are byte/shift COUNTS, neither holds a pointer. (b) `gc_old_reclaim_growth_band_bytes` gains a `Cell` read and a left shift -- pure arithmetic over byte counts. (c) `old_reclaim_pressure_due` loses the #7937 absolute first-crossing arm, splits its pure form out as `old_reclaim_pressure_due_inner`, and calls `note_old_reclaim_cycle_started()` when the answer is true. That predicate is read at TRIGGER decisions only -- the allocation-point `gc_check_trigger` and `gc_budgeted_due_trigger` at safepoints -- i.e. before a cycle starts, never between the boundaries; an allocation inside the window reaches `gc_check_trigger` with `GC_FLAG_IN_ALLOC` set and returns before this code, the same argument the 2026-09-14 due-check fast-path re-audit made for the same function. Even if it did run there it would be sound: `note_old_reclaim_cycle_started` stores one scalar `Cell` from `pacing_arena_in_use_bytes()` (a read of `arena_live_allocated_bytes`), which allocates no GC object, relocates nothing and runs no JS callback -- the window's contract. (d) `update_old_reclaim_backoff` is called only from `finish_full_old_reclaim_baseline`, which runs from `publish_reclaim_outcome` in the Publish subphase, AFTER `step_sweep` has already `take()`n the snapshot out of the thread-local -- exactly where #9831's store and the medium-parse pacing store already sit. (e) `gc_old_reclaim_debt_bytes` drops the absolute arm it mirrored; it remains pure arithmetic read at debt/trigger decisions. (f) `#[cfg(test)]` seams, absent from production builds. `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback between them; the change alters only WHEN a collection is scheduled, never what runs inside one. Neither boundary moved and the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-24 for #10960 (growth-aware old-reclaim backoff), which touched `gc/policy.rs` again. One new thread-local, `GC_OLD_RECLAIM_LAST_POST_IN_USE_BYTES` (`Cell`), a byte COUNT that holds no pointer. It is written only by `update_old_reclaim_backoff`, which runs from `finish_full_old_reclaim_baseline` in the Publish subphase, after `step_sweep` has already taken the snapshot out of the thread-local; the change there is pure integer arithmetic deciding whether to widen the band. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback, and neither window boundary moved. Re-audited 2026-09-24 after the class-capture environment added one `reg_scanner!` registration (`scan_class_env_roots_mut`, visiting each guarded class environment's owner class object) to `gc/mod.rs`: a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-25 after the inherited-access lane touched `gc/mod.rs`: the change is one `reg_scanner!` registration for `object::chain_store::scan_chain_store_roots_mut`, a root scanner that visits store-site chain verdicts (one interned key pointer each) during root scanning. It runs at the start of a cycle, never between mark completion and sweep entry, relocates nothing and runs no JS callback. The synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-25 for #10698 (allocation-point trigger watermark), which touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` gains `pub(crate) mod trigger_watermark;` and a `pub(crate) use` re-export -- no control flow. In `gc/policy.rs`: (a) `gc_check_trigger` becomes an out-of-line wrapper over an inlined fast path that reads the watermark cell, the malloc registry's length through an unguarded shared borrow and the inline allocator's offset, then either returns or runs the previous body unchanged (`gc_check_trigger_evaluate`). An allocation inside the window reaches it with `GC_FLAG_IN_ALLOC` set: the fast path returns without acting, which is the outcome of the `GC_FLAG_IN_ALLOC` early return it would otherwise reach, and the slow path still takes that early return. (b) The due-trigger evaluation also returns a watermark -- integer arithmetic over values it already read -- published only on the no-action return past every entry guard. (c) The ladder's thresholds and flags are retyped `Cell` -> `TriggerInput`, whose writes add one store retiring the watermark; heap-generation advances and `Arena::set_current` retire it too. (d) `#[cfg(test)]` seams. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback; `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`, so the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-25 for #10498 (class-accessor cache), which adds one `reg_scanner!` registration (`scan_class_accessor_cache_roots_mut`, marking and rewriting the cache's key strings) to `gc/mod.rs`: again a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-26 for #10572: `gc/mod.rs` gains one `#[cfg(not(feature = \"hot-diag\"))]` call to `hot_diag::refuse_knobs_without_hot_diag()` in `gc_init`, a startup-only knob check that runs before any cycle and alters no mark/sweep control flow; the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-27 for #11416: `gc/mod.rs` loses the `reg_scanner!` registration for `class_accessor_cache::scan_class_accessor_cache_roots_mut` (the cache is deleted); removing a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 for the method-calls lane, which adds one `reg_scanner!` registration (`scan_method_site_roots_mut`, marking and rewriting the method closures inherited method-site entries hold) to `gc/mod.rs`: a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-28 (charter step 5, P0): the field-representation census adds `mod census_field_repr;` to `gc/mod.rs` and, in `census.rs`, one Rust-owned accumulator fed from `visit_object` inside `take_census`, which runs after `census_take_if_armed_at_full_sweep_start` has already taken PASS1_MARKED; it reads slots and the per-object layout maps (try_borrow), allocates nothing on the JS heap and alters no mark/sweep control flow. The window is unchanged. Re-audited 2026-09-27 for #11416: `gc/mod.rs` loses the `reg_scanner!` registration for `class_accessor_cache::scan_class_accessor_cache_roots_mut` (the cache is deleted); removing a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 for the method-calls lane, which adds one `reg_scanner!` registration (`scan_method_site_roots_mut`, marking and rewriting the method closures inherited method-site entries hold) to `gc/mod.rs`: a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 (class constructors as function objects, #11414): `gc/mod.rs` gains one `reg_scanner!` registration (`object::class_value::scan_class_value_roots_mut`, the per-agent class function-object table, visited and rewritten) \u2014 a root-scanner registration that alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-29 after #11659 added `verify::verify_array_hole_tails_at_collection()` to `gc/mod.rs` and `gc/policy.rs`. Every call sits at a collection or budgeted-cycle ENTRY, beside `roots::ensure_stack_maps_built()`, i.e. before mark begins and so before `census_pass1_if_armed` opens the window. The call is a read-only walk of array headers (debug builds, or release with PERRY_GC_VERIFY_ARRAY_HOLES): it neither allocates GC memory, relocates, runs JS, nor reads or writes PASS1_MARKED, and it panics rather than continuing on a violation. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Pin re-audited 2026-09-29 after PR #11646 added one call inside that window, in `cycle.rs` beside `census_take_if_armed_at_full_sweep_start`: `object::shapes::store_kind::audit_heap_at_full_sweep_start()`. It compiles to nothing unless the `shape-fact-audit` feature is on; when on it walks the arena read-only (`gc::for_each_live_object_at_sweep_start`, in `gc/verify.rs`) and reads each marked object header and its shape record. It allocates no GC object, moves nothing, runs no JS callback and holds no address past the walk, so PASS1_MARKED stays valid across it; a disagreement panics rather than continuing. Re-audited 2026-09-29 after the pinned-roots fix touched `gc/cycle.rs` and `gc/mod.rs`: the block-persistence live-block predicate now reads GC_FLAG_MARKED alone (pinned objects are marked as roots, so the set of live blocks is unchanged), and `gc/mod.rs` adds one `reg_scanner!` registration. Neither relocates, runs JS, or moves the mark-complete to sweep-entry window. Re-audited again 2026-09-29 (same fix, copying-minor follow-up): `BlockPersistCycleState`'s force-mark in `gc/cycle.rs` no longer skips an unmarked pinned header, so it marks and pushes it like any other object of a live block. That is marking inside the budgeted cycle's persistence step: nothing relocates, no JS runs, and the budgeted path still skips both census boundaries. The window is unchanged. Re-audited 2026-09-29 for this-as-a-parameter stage 3: `gc/mod.rs` registers `scan_dispatch_binding_roots_mut` in place of `scan_implicit_this_roots_mut` (the implicit-`this` cell is deleted; the same scanner body keeps `new.target`, the static-`this` override and the static private-owner stack) and rewords its comment; a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window.", + "why": "Real GC header addresses, deliberately untraced so the diagnostic does not keep its observed objects alive. Populated only at the end of mark propagation of a synchronous full cycle; consumed at sweep entry in the same run_to_completion invocation. The intervening full-cycle phases do not relocate or run JS callbacks. The Vec is used for membership comparisons and dropped with the census before sweep. Budgeted and minor cycles skip both boundaries. Pin re-audited 2026-09-05 after #9760 touched `gc/mod.rs`: that change is `mod heap_stats;` plus a `pub(crate) use` re-export and alters no mark/sweep control flow. `heap_stats()` is reached only from `js_bun_jsc_heap_stats` (the JS-facing `bun:jsc.heapStats()`), i.e. from mutator code, never inside a cycle, and its own module contract forbids allocation or collection during its walk. The mark-complete \u2192 sweep-entry window is unchanged. Re-audited 2026-09-05 (train125) after #9769 and #9771 touched pinned files. #9769 adds one `reg_scanner!` registration to `gc/mod.rs`; #9771 adds a feature-gated `alloc_census_init()` there and a feature-gated Rust-heap dump inside `take_census`. `alloc-census` is not in the default feature set, and decisively: `census_take_if_armed_at_full_sweep_start` does `PASS1_MARKED.with(|p| p.borrow_mut().take())` BEFORE calling `take_census`, so the snapshot has already left the thread-local by the time #9771's code runs \u2014 it cannot affect the window. Neither change alters mark/sweep control flow. Re-audited 2026-09-06 after #9831 touched `gc/policy.rs`. Its hunks are (a) the tiny-parse pressure guard's pricing (`tiny_parse_pressure_headroom_bytes`, `tiny_parse_pressure_due*`, a `Cell` byte-count base) consulted from JSON.parse's mutator-side boundaries (`gc_bump_malloc_trigger`, `gc_collect_pending_suppressed_parse`, `gc_schedule_parse_boundary_collection_if_pressure`), none of which is reachable from inside a cycle, and (b) one extra `Cell` store in `note_collection_finished_arena_occupancy`, which runs from `publish_reclaim_outcome` in the Publish subphase \u2014 after `step_sweep` has already consumed the snapshot. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Re-audited 2026-09-05 (train126) after #9755 restructured `gc/cycle.rs`. Its hunks are all root-scan machinery (`RootScanSubphase`, `RootScanCycleState`, the mutable-scanner iteration state), which runs BEFORE mark propagation completes; `gc/mod.rs` gains only a `mod young_log;` declaration. The bracketing is unchanged \u2014 `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep` \u2014 and a synchronous full mark-sweep still moves nothing between them. Re-pinned 2026-09-05 for the #9740 hot-TLS conversion of this file: the sole change is `thread_local!` \u2192 `crate::perry_thread_local!`, a macro-name swap with identical declaration syntax and `.with()` call sites. No control flow, no phase boundary, and no storage semantics change. Re-audited 2026-09-06 (train128) after #9794's GC diagnostics touched `gc/mod.rs` and `gc/policy.rs`: both gain diagnostic module declarations and counters only \u2014 no mark/sweep control flow, and the census bracketing in `step_mark_propagation` / `step_sweep` is unchanged. Re-audited for #9794's GC diagnostics: `gc/mod.rs` gains `mod diag_sites;` / `mod survival_diag;`, a re-export, a `diag_sites::full_started(...)` call at TRIGGER time (before mark propagation begins), and exit-time reporting. Nothing executes between mark-complete and sweep-entry, so the window is unchanged. Re-audited 2026-09-06 for the retained array-growth verifier fix: the cycle.rs change passes the existing non-copying evacuation verifier an explicit all-forwarded policy. That call remains in minor finalization, outside the synchronous full-cycle census window; its root and heap reads do not allocate GC objects, move objects, or invoke JS callbacks. The mark-complete and sweep-entry boundaries are unchanged. Re-audited 2026-09-05 after #9830 touched `gc/policy.rs`. That change is (a) six `thread_local! {` blocks rewritten as `crate::perry_thread_local! {` and (b) one `#[cfg(test)]` accessor listing the trigger path's hot-slot indices. The macro keeps the same storage, the same `.with()` at every read and write, and the same destructor registration (the teardown guard exists exactly when `needs_drop` holds, which is what `std::thread_local!` already decided); no value, predicate or branch in the file changes, so no mark or sweep control flow does. The one new behaviour is on a declaration's FIRST read: `HotKey::resolve_and_cache` takes a mutex and allocates a key through the GLOBAL allocator. Even if a first read landed inside this window it would be sound \u2014 the window's contract is that nothing relocates and no JS callback runs, and a mimalloc allocation does neither. `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`; the bracketing is untouched. Re-audited 2026-09-06 (train132) after #9860 and #9845 touched `gc/mod.rs`. Both hunks are re-export lists and nothing else: #9860 adds `idle_reclaim_elapsed_starts` / `IDLE_RECLAIM_REARM_MS`, and #9845 adds `owner_is_dead_copied_minor_from_space_of_type`. No mark or sweep control flow changes. #9845's substantive work sits in `gc/oldgen.rs` and `gc/copying.rs`, neither pinned: the copying-minor arm (`finalize_dead_copied_minor_from_space_regexps`) runs on a MINOR, which skips both census boundaries; the full-cycle arm (`collect_dead_registered_regexps_post_trace`, from `with_dead_collection_finalize`) walks the RegExp registry building a Vec of addresses \u2014 no GC allocation, no JS callback, so it cannot relocate the snapshot's subjects \u2014 and it is reached from the sweep body, i.e. AFTER `census_take_if_armed_at_full_sweep_start` has already `take()`n the snapshot out of the thread-local. The mark-complete -> sweep-entry window is unchanged. Re-audited 2026-09-07 for #9965 after 1ec9e0e8a touched `gc/cycle.rs` and `gc/mod.rs`: `gc/mod.rs:216-217` only declares and imports the failure-attribution module, while `gc/cycle.rs:1414-1417` reads the trigger and diagnostic counters immediately before evacuation verification inside `atomic_finalize_minor_prelude`. Full cycles bypass `MinorPrelude` at `gc/cycle.rs:1192-1196`; evacuation remains guarded by the minor-only context at `gc/cycle.rs:1330-1372`. The snapshot store remains at `gc/cycle.rs:963-964` after synchronous full marking, and its take remains at `gc/cycle.rs:1454-1457` before sweep. No new write, relocation, collection, or JS callback was added to that full-cycle interval, so the PASS1_MARKED window is unaffected. Re-audited 2026-09-07 for the regex census rows: all new work is in `take_census` after `census_take_if_armed_at_full_sweep_start` has taken PASS1_MARKED out of TLS; neither boundary nor the intervening cycle control flow changed. Re-audited 2026-09-08 (train144) after #9976 and #9977 touched pinned files. `gc/mod.rs` gains exactly three lines: `mod copying_phase;` and `mod regex_census;` (declarations) and one `reg_scanner!(regex::site_test::scan_roots_mut)` registration. A scanner registration adds a root SOURCE for the mutable-root walks; it does not move either census boundary and runs nowhere between them. `gc/census.rs` widens `side_tables()` to `pub(super)`, extends it with regex rows and adds a test module \u2014 all census REPORTING, which runs from the diagnostic dump, not inside a cycle. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Re-audited 2026-09-08 for #9849 JSON construction deferral. `gc/mod.rs` adds the `json_defer` module/re-export and a trusted-header layout helper used only by already-validated JSON emitters; neither changes or runs in collector phase control flow. `gc/policy.rs` adds JSON completion scheduling, construction-grace checks, and safepoint deferral predicates. These are called from mutator-side JSON allocation/output boundaries and ordinary safepoint entry; they do not alter `step_mark_propagation`, `step_sweep`, or invoke callbacks or relocation between the census boundaries. The mark-complete to sweep-entry window is unchanged. The follow-up adds a cfg(test)-only one-shot boolean for deterministic explicit-pressure fixtures; it is absent from production builds and cannot affect the census window. The first predicate read consumes it, so post-parse accounting exercises normal pricing. Re-audited 2026-09-09 for bounded tiny-JSON completion polling. The policy.rs changes split the mutator-side pending-parse check into an inlined empty fast path plus an outlined debt-service path, and amortize the mutator-side arena-pressure read across 64 bounded parse completions. Neither function is reachable from step_mark_propagation or step_sweep; neither census boundary nor the synchronous full-cycle interval between them changes. Re-audited 2026-09-09 for lazy JSON record batches: policy.rs only widens gc_budgeted_cycle_active visibility from pub(super) to pub(crate). Its body remains a read-only Cell query. The new caller is lazy_get materialization in the mutator; run_to_completion, step_mark_propagation, census snapshot consumption at step_sweep, and the synchronous non-moving window are unchanged. Re-audited 2026-09-09 for completed JSON-output debt: the added gc_service_json_output_sweep function calls the existing trigger check from a rooted mutator boundary and reports whether its malloc-count request remains due. It is not called from any census or collector phase; the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-09 for the JSON byte-debt carry: the same mutator-only service helper now distinguishes requests satisfied before its call from those satisfied by its trigger check. The added enum contains no payload, both count reads are scalar, and no census boundary or collector phase changed. Re-audited 2026-09-11 for #10055: gc/mod.rs only registers the weak UTF-16 index scanner during gc_init. It neither marks strings nor allocates GC objects or runs JS; offset vectors use the Rust allocator. The mark-complete to sweep-entry census window and cycle control flow are unchanged. Re-audited 2026-09-11 for #10054: gc/mod.rs adds only the trim-cache mutable-root scanner registration in gc_init. Its scanner visits two existing string slots without allocating or invoking JS. Root scanning still precedes mark completion, and neither census boundary nor the synchronous mark-complete to sweep-entry window changes. Re-audited 2026-09-11 for #10060: the census array classifier now reads the logical element start and bounds its scan by the remaining capacity. The helper only reads the existing GC/header words and performs pointer arithmetic; it cannot allocate, collect, or call JS. This classifier runs in take_census after PASS1_MARKED has been taken out of TLS. Neither census boundary nor the mark-complete to sweep-entry control flow changed. Re-audited for #8512: gc/mod.rs only enables the existing PTY mutable-root scanner on Windows; it changes no mark/sweep phase or census boundary. The scanner visits NaN-boxed slots without running JS callbacks. Re-audited 2026-09-12 for the single regular-expression engine: `gc/mod.rs` changes `mod prefetch;` to `pub(crate) mod prefetch;` so the RegExp owner-table walks can prefetch headers, a visibility change with no new call in collector control flow; `gc/census.rs` changes only its `#[cfg(test)]` `regex_census_tests` module, dropping assertions for the previous engine's cache rows. Neither boundary (`census_pass1_if_armed` in `step_mark_propagation`, `census_take_if_armed_at_full_sweep_start` in `step_sweep`) nor the synchronous mark-complete to sweep-entry interval changes. Re-audited 2026-09-13 after the #10169 fix touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` gains only `pub(crate) use` re-exports (`policy::note_young_leaf_born_old`, `policy::young_generation_holds_a_nursery`, `promote_in_place::{young_generation_measured_dying, young_generation_measured_retained}`, and cfg(test) survival seeders). `gc/policy.rs` gains a `Cell` thread-local (`GC_YOUNG_LEAF_BORN_OLD`, no pointer), its setter, a pure predicate over `copying_from_space_in_use_bytes` vs the base nursery cap, and a consumed-once branch at the top of `gc_budgeted_due_trigger` that may answer `YoungScavengeCap` ahead of `OldReclaim`. That branch decides WHICH collection a safepoint starts (a minor instead of a full); it runs before any cycle begins and never inside one, so the mark-complete \u2192 sweep-entry window of a synchronous full \u2014 where PASS1_MARKED is populated and consumed within one `run_to_completion` \u2014 is unchanged, and neither hunk adds an allocation, a JS callback, or a relocation to it. Re-audited 2026-09-13 for the heap generation (#10164 cross-call search positions): `gc/mod.rs` only declares `pub(crate) mod heap_generation;`. `gc/cycle.rs` wraps the `Sweep` and `Reclaim` arms of `GcCycleState::step` in a `HeapChange` scope and opens one inside `atomic_finalize_minor_prelude`'s evacuation branch (with a nested one around old-page defrag). Opening and closing a scope only increments two thread-local integer cells (`HEAP_GENERATION`, `OPEN_HEAP_CHANGES`); a first thread-local read may allocate a key through the global allocator, which neither relocates nor runs JS. The `Sweep` scope opens immediately before `step_sweep`, i.e. before `census_take_if_armed_at_full_sweep_start` takes PASS1_MARKED out of TLS, and adds no relocation, collection or JS callback to the synchronous mark-complete to sweep-entry window; the minor-prelude scope is unreachable from a full cycle, which bypasses `MinorPrelude`. Neither boundary nor the intervening control flow changed. Re-audited 2026-09-13 for #10182 block-granular reclamation, which touched `gc/cycle.rs`. Two hunks: (a) in the `RememberedSetRebuild` subphase of AtomicFinalize \u2014 INSIDE the window \u2014 the require-marked old-to-young rebuild is now constructed with `OldToYoungRememberedRebuildState::new_skipping`, whose cursor never enters blocks the census recorded as holding no reached, pinned or pre-marked object (`BlockCensus::unmarked_blocks`); computing that list reads `arena_block_snapshots()` and allocates one `Vec` through the global allocator. It visits a subset of the same objects the rebuild already walked (every skipped object would have been rejected as unmarked), and it neither allocates a GC object, relocates anything, nor runs a JS callback. (b) In `step_sweep`, `IncrementalSweepState::with_block_skip` runs after `census_take_if_armed_at_full_sweep_start` has already taken PASS1_MARKED out of TLS. Neither boundary moved and the synchronous mark-complete to sweep-entry interval gains no relocation, collection or callback. Re-audited 2026-09-11 for the startup memory profile: gc/mod.rs only retains the pre-main allocator-policy constructor in js_gc_init. The constructor applies process allocation options, without invoking GC or JS. No census boundary, collector phase, or mark-complete to sweep-entry control flow changed. Re-audited 2026-09-13 for #10179: census.rs only adds a native regex cache metadata row and its unit assertion; snapshot consumption and the full-cycle window are unchanged. Re-audited 2026-09-14 for the GC due-check fast path, which touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` only changes the safepoint re-exports: `gc_runtime_safepoint` becomes cfg(test) and `gc_runtime_safepoint_poll` is added. `gc/policy.rs`: the budgeted step returns a debt-free `GcStepReport` (debt is attached by the FFI and test entry points after the step returns) and moves cycle start/step into an out-of-line `gc_budgeted_start_or_step`; `gc_check_trigger` reuses a repeatable due-trigger answer through `DueTriggerMemo`, placed after its `GC_FLAG_IN_ALLOC` and suppression early returns; the young scavenge cap reuses the old-gen pressure value the due trigger already read and checks the census-seeded flag first. All of it runs from mutator safepoints, allocation-point trigger checks and the host step API, before a cycle starts or between budgeted steps. None of it is reachable between `census_pass1_if_armed` in `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` in `step_sweep` of a synchronous full: an allocation inside that window reaches `gc_check_trigger` with `GC_FLAG_IN_ALLOC` set and returns before the changed code. No allocation, relocation, collection or JS callback is added to the window. Re-audited 2026-09-14 for the tiny-parse nursery-cap boundary, which touched `gc/policy.rs`. It adds `tiny_parse_generational_collection_due`, a pure predicate (the existing `tiny_parse_pressure_due` OR the existing `young_scavenge_cap_due` read), and calls it instead of `tiny_parse_pressure_due` from `gc_bump_malloc_trigger_inner` and `gc_collect_pending_suppressed_parse_slow` (generational branch only) and from `gc_schedule_parse_boundary_collection_if_pressure`. All three are JSON.parse mutator-side boundaries, none reachable from `step_mark_propagation` or `step_sweep`; the predicate reads counters and allocates nothing. Neither census boundary nor the synchronous mark-complete to sweep-entry interval changed. Re-audited 2026-09-13 for #10182's full-collection throughput follow-up, which touched `gc/cycle.rs` in one hunk, INSIDE the window: the `RememberedSetRebuild` subphase of a synchronous full now first asks `verify::full_remembered_rebuild_provably_empty` and, when it holds, installs `OldToYoungRememberedRebuildState::provably_empty()` (an empty sticky set, no walk) instead of the require-marked rebuild. The predicate reads `arena_block_snapshots()` (one `Vec` through the global allocator), the census's per-block reached/pre-marked facts and the malloc registry's length; the constructor bumps a `Cell` counter and prints one line under `PERRY_GC_DIAG`. None of it allocates a GC object, relocates anything, collects, or runs a JS callback, and both census boundaries stay where they were. Re-audited 2026-09-14 for #10182's pacing-full work, which touched `gc/cycle.rs`, `gc/mod.rs` and `gc/policy.rs`. `gc/cycle.rs`: `GcCycleState::new_full` no longer calls `materialize_all_promoted_page_runs`; that call ran in the constructor, before the census and far before `census_pass1_if_armed`, and removing it adds nothing to the window. `gc/mod.rs`: one `mod promoted_cohort;` declaration. `gc/policy.rs`: (a) `credit_promoted_bytes_to_old_baseline` also credits a `Cell` cohort counter (it runs after a copying minor completes); (b) `finish_full_old_reclaim_baseline` also records the verified old live bytes and resets that counter (Publish, after `step_sweep` consumed the snapshot); (c) `gc_safepoint_moving_minor` arms and disarms the promotion-census record around its nursery minor and calls `run_promoted_cohort_full_if_due`, which starts a synchronous full through the same `gc_collect_full_mark_sweep_with_trigger` entry and reads byte counters before and after it. All of it runs before a cycle starts or after it completes; none of it runs between mark completion and sweep entry, allocates a GC object, relocates anything, or calls into JS. The census the promoted-cohort full may adopt from the promotion walk is built in `BuildValidPointerSet`, before either boundary. Both boundaries are unchanged. Re-audited 2026-09-14 for the #10182 dead-stack scrub in `gc/cycle.rs`: `step_build_valid_pointer_set` now calls `scrub_dead_stack_below`, which zeroes a local array in its own frame (dead stack below the caller), right after the census finishes \u2014 in `BuildValidPointerSet`, before the root scan and far before `census_pass1_if_armed`. It writes no heap memory, allocates nothing, relocates nothing and calls no JS; both boundaries are unchanged. Re-audited 2026-09-14 for #10241 (cohort survival), which touched `gc/cycle.rs` and `gc/policy.rs`. `gc/cycle.rs`: one call, `promoted_cohort::survival::check_minor_view_at_full_sweep_start()`, in `step_sweep` immediately AFTER `census_take_if_armed_at_full_sweep_start` has taken PASS1_MARKED out of TLS, i.e. outside the window. It is a no-op unless a promoted-cohort full armed its survival probe; when armed it walks the old page index over the preceding minor's dirty pages (`old_arena_walk_objects_on_pages`, Rust-allocator Vecs), reads GC headers' mark flags and the slots of unmarked ones, and records one enum. It writes no heap memory, allocates no GC object, relocates nothing and calls no JS. `gc/policy.rs`: `run_promoted_cohort_full_if_due` arms the probe before `gc_collect_full_mark_sweep_with_trigger` and takes it after the full returns (feeding `note_full_measured_promotion_survival` and one diagnostic line); both run before a cycle starts or after it completes. Both boundaries are unchanged. Re-audited 2026-09-14 for #10241's in-place-only cohort: `gc/policy.rs` drops the `promoted_cohort::note_promoted` call from `credit_promoted_bytes_to_old_baseline` (the copying minor now calls `promoted_cohort::note_minor_promotion` itself, after the credit). Both run at the end of a copying minor, outside any full cycle; the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-14 for the parse-boundary side-allocation band (medium-parse pacing), which touched `gc/policy.rs`. Three hunks: (a) a `Cell` thread-local (`GC_LAST_COLLECTION_EXTERNAL_SIDE_BYTES`, a byte COUNT, no pointer) plus three pure predicates over it and `external_side_live_bytes()`; (b) that predicate added as a third disjunct of `tiny_parse_generational_collection_due`, which is read only from the three JSON.parse mutator-side boundaries (`gc_bump_malloc_trigger_inner`, `gc_collect_pending_suppressed_parse_slow`, `gc_schedule_parse_boundary_collection_if_pressure`), none of them reachable from `step_mark_propagation` or `step_sweep`; and (c) one extra `Cell` store in `note_collection_finished_arena_occupancy` plus two extra reads in the `PERRY_GC_DIAG` tiny-parse line. `note_collection_finished_arena_occupancy` runs from `publish_reclaim_outcome` in the Publish subphase, i.e. AFTER `step_sweep` has already `take()`n the snapshot out of the thread-local, exactly as #9831's store on the same line does. Nothing added allocates a GC object, relocates anything, or runs a JS callback, and neither census boundary moved. Re-audited 2026-09-14 for the drained-bytes counterweight to that band, which touched `gc/policy.rs` again. Four hunks: a second `Cell` thread-local (`GC_EXTERNAL_SIDE_DRAINED_SINCE_FULL`, a byte COUNT); one increment of it inside `gc_note_external_side_free`; a pure read (`external_side_old_reclaim_pressure_bytes`) substituted for `external_side_live_bytes()` at the four old-reclaim pressure sites; and one `Cell` store at the top of `finish_full_old_reclaim_baseline`. None of it can run between the census boundaries. `gc_note_external_side_free` is also reached by mutator-side tape materialization, regex scratch teardown, native-addon adjustments and buffer replacement. Its added operation is only a saturating increment of a scalar Cell, with no GC allocation, relocation, collection or JS callback, so this wider caller set does not invalidate the census window. `finish_full_old_reclaim_baseline` runs from `publish_reclaim_outcome` in the Publish subphase, the same place #9831's store already sits. The pressure reads happen at trigger decisions, before a cycle starts. No allocation, relocation, collection or JS callback is added to the mark-complete -> sweep-entry window, and neither boundary moved. Re-audited 2026-09-15 for turnloop P0, which touched `gc/mod.rs` with one added call: `crate::event_pump::shutdown_wait_driver()` inside `js_gc_release_current_thread_collection_side_allocations`, the process-exit funnel. That function runs once no more JavaScript can run on the thread, never from inside a collection cycle; the added call drops the thread's turnloop wait loop (closing its kqueue/epoll descriptor) and may print a diagnostic line. It allocates no GC object, relocates nothing, starts no collection and runs no JS callback. The census boundaries and the mark-complete -> sweep-entry window are untouched.. Re-audited 2026-09-16 for the copying minor's per-parent weak-holder fact: `gc/mod.rs` gains exactly one line, `mod copying_parent_facts;`, a module declaration. The module it declares holds `weak_holder_fact` (a read of the parent's `obj_type`/`class_id` via `weakref::is_weak_holder_header`) and the copying minor's `visit_slot_with_parent`, moved verbatim out of `gc/copying.rs` for the 2000-line lint. Both run only inside a COPYING MINOR, which skips both census boundaries (`census_pass1_if_armed` in `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` in `step_sweep` are synchronous-full only). Nothing was added to any full-cycle phase, and the declaration itself executes no code. Neither boundary moved and the synchronous mark-complete to sweep-entry window gains no allocation, relocation, collection or JS callback. Re-audited 2026-09-18 for the #10532 follow-up argument-list rooting fix, which touched `gc/mod.rs`. The only change there is `mod collection_points;` plus a `pub(crate) use collection_points::collection_point;` re-export (and, under `#[cfg(test)]`, `arm_collection_point`). `collection_point` is an inline no-op outside `cfg(test)`; under test it only runs a copying minor when called from ordinary MUTATOR code (`proxy.rs`'s `Reflect.apply` rebind path and `registry.rs`'s rest-array bundler), never from inside `step_mark_propagation` or `step_sweep`. Neither `census_pass1_if_armed` nor `census_take_if_armed_at_full_sweep_start` is reachable from it, so the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-18 (same PR, round 2) for the added `arm_collection_point_after` re-export in `gc/mod.rs`: another pure re-export line, same as the `collection_point`/`arm_collection_point` one already covered above. `arm_collection_point_after` only changes test-only arming state in `collection_points.rs` (which named site fires and on which hit); it still runs no mark/sweep control flow. Re-audited 2026-09-19 for #10735 (require.main threading): gc/mod.rs gains exactly one line, `reg_scanner!(crate::module_require::scan_cjs_main_module_root_mut);`, registering the new CJS_MAIN_MODULE thread-local's mutable-root scanner beside the existing `scan_module_path_roots_mut` registration. A scanner registration adds a root SOURCE for the mutable-root walks; it runs during root scanning, before mark propagation completes, and does not execute between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start`. Neither census boundary moved and the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-20 for #10834 (inherited-property read cache). `gc/mod.rs` gains exactly one line: `reg_scanner!(crate::object::inherited_read_cache::scan_inherited_read_cache_roots_mut);` in `gc_init()`. A scanner registration adds a root SOURCE for the mutable-root walks. The walk runs inside `RootScanCycleState::step_current_subphase`, i.e. entirely within the RootScan phase: `step_root_scan` only sets `self.phase = GcCyclePhase::MarkPropagation` once that loop reports done (`gc/cycle.rs:958-961`), and `census_pass1_if_armed()` fires at the END of `step_mark_propagation` (`gc/cycle.rs:982`). The scanner therefore runs strictly BEFORE the window opens and can never execute between the boundaries. Its body is a bounded walk of a fixed 512-entry thread-local array calling `visit_tagged_usize_slot` / `visit_usize_slot`; it allocates nothing, relocates nothing and runs no JS callback. Same shape as #9769, #9976/#9977, #10054, #10055 and #10735, all previously cleared. The PR also adds an `INHERITED_READ_CACHE` entry to `DEAD_KEY_PRUNES` in `gc/dead_owner.rs` (not a pinned source). That registry is consumed by `IncrementalSweepState::with_dead_collection_finalize` at `gc/cycle.rs:1548`, which is AFTER `census_take_if_armed_at_full_sweep_start` at `gc/cycle.rs:1505` has already `take()`n the snapshot out of the thread-local -- the same argument that cleared #9845's `collect_dead_registered_regexps_post_trace`. The prune reads addresses and zeroes entries; no GC allocation, relocation or callback. Both additions sit outside the window, on opposite sides of it. Neither boundary moved and the synchronous mark-complete to sweep-entry interval is unchanged. Re-audited 2026-09-22 for #10399 (per-thread module init), which touched `gc/mod.rs`. Two hunks, both init-time: a new free function `raise_default_thread_stack_floor()` and one call to it at the top of `js_gc_init`, before `enter_current_thread_image`'s successor statements. The function reads `RUST_MIN_STACK` from the environment and, only when it is unset, sets it to 32 MiB so a thread spawned against a multi-megabyte static TLS block still has usable stack (glibc carves static TLS out of the thread's stack mapping). It touches no heap object, allocates no GC object, relocates nothing and runs no JS callback. `js_gc_init` is the first runtime call of a compiled `main`, so it runs once before any cycle exists, and it is not reachable from `step_mark_propagation` or `step_sweep`. Same shape as the 2026-09-11 startup-memory-profile re-audit, which cleared the pre-main allocator-policy constructor in the same function. Neither census boundary moved and the synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-23 (size/runtime-decouple, #11135) after the binary-size branch touched `gc/census.rs`, `gc/mod.rs` and `gc/policy.rs`. census.rs: `census_pass1_if_armed` / `census_take_if_armed_at_full_sweep_start` keep their bodies verbatim, moved into `_impl` functions compiled only with the new `gc-instruments` feature (without it both are empty and `census_path()` is `None`, so nothing is ever armed); the take still empties PASS1_MARKED before `take_census`. gc/mod.rs: `gc_init` gains a startup env check that aborts when an instrument knob is set without the feature, before any cycle exists. gc/policy.rs: env-knob OnceLock caches now initialize through `crate::once_init::get_or_init` (same closures, same values). No mark/sweep control flow between the two census boundaries changed; the window is unchanged. Re-audited for Fetch handle reclamation: cycle.rs only redirects the incomplete-cycle Drop cancellation hook to also cancel the Fetch trace. The full-trace finish hook removes native records and cached slots without allocating GC objects or invoking JS; it cannot relocate the census addresses before sweep entry. Re-audited 2026-09-22 for #10928 (one proportional old-reclaim rule), which touched `gc/policy.rs`. Six hunks. (a) Two new thread-locals, `GC_OLD_RECLAIM_PRE_IN_USE_BYTES` (`Cell`) and `GC_OLD_RECLAIM_BACKOFF_SHIFT` (`Cell`): both are byte/shift COUNTS, neither holds a pointer. (b) `gc_old_reclaim_growth_band_bytes` gains a `Cell` read and a left shift -- pure arithmetic over byte counts. (c) `old_reclaim_pressure_due` loses the #7937 absolute first-crossing arm, splits its pure form out as `old_reclaim_pressure_due_inner`, and calls `note_old_reclaim_cycle_started()` when the answer is true. That predicate is read at TRIGGER decisions only -- the allocation-point `gc_check_trigger` and `gc_budgeted_due_trigger` at safepoints -- i.e. before a cycle starts, never between the boundaries; an allocation inside the window reaches `gc_check_trigger` with `GC_FLAG_IN_ALLOC` set and returns before this code, the same argument the 2026-09-14 due-check fast-path re-audit made for the same function. Even if it did run there it would be sound: `note_old_reclaim_cycle_started` stores one scalar `Cell` from `pacing_arena_in_use_bytes()` (a read of `arena_live_allocated_bytes`), which allocates no GC object, relocates nothing and runs no JS callback -- the window's contract. (d) `update_old_reclaim_backoff` is called only from `finish_full_old_reclaim_baseline`, which runs from `publish_reclaim_outcome` in the Publish subphase, AFTER `step_sweep` has already `take()`n the snapshot out of the thread-local -- exactly where #9831's store and the medium-parse pacing store already sit. (e) `gc_old_reclaim_debt_bytes` drops the absolute arm it mirrored; it remains pure arithmetic read at debt/trigger decisions. (f) `#[cfg(test)]` seams, absent from production builds. `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback between them; the change alters only WHEN a collection is scheduled, never what runs inside one. Neither boundary moved and the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-24 for #10960 (growth-aware old-reclaim backoff), which touched `gc/policy.rs` again. One new thread-local, `GC_OLD_RECLAIM_LAST_POST_IN_USE_BYTES` (`Cell`), a byte COUNT that holds no pointer. It is written only by `update_old_reclaim_backoff`, which runs from `finish_full_old_reclaim_baseline` in the Publish subphase, after `step_sweep` has already taken the snapshot out of the thread-local; the change there is pure integer arithmetic deciding whether to widen the band. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback, and neither window boundary moved. Re-audited 2026-09-24 after the class-capture environment added one `reg_scanner!` registration (`scan_class_env_roots_mut`, visiting each guarded class environment's owner class object) to `gc/mod.rs`: a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-25 after the inherited-access lane touched `gc/mod.rs`: the change is one `reg_scanner!` registration for `object::chain_store::scan_chain_store_roots_mut`, a root scanner that visits store-site chain verdicts (one interned key pointer each) during root scanning. It runs at the start of a cycle, never between mark completion and sweep entry, relocates nothing and runs no JS callback. The synchronous mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-25 for #10698 (allocation-point trigger watermark), which touched `gc/mod.rs` and `gc/policy.rs`. `gc/mod.rs` gains `pub(crate) mod trigger_watermark;` and a `pub(crate) use` re-export -- no control flow. In `gc/policy.rs`: (a) `gc_check_trigger` becomes an out-of-line wrapper over an inlined fast path that reads the watermark cell, the malloc registry's length through an unguarded shared borrow and the inline allocator's offset, then either returns or runs the previous body unchanged (`gc_check_trigger_evaluate`). An allocation inside the window reaches it with `GC_FLAG_IN_ALLOC` set: the fast path returns without acting, which is the outcome of the `GC_FLAG_IN_ALLOC` early return it would otherwise reach, and the slow path still takes that early return. (b) The due-trigger evaluation also returns a watermark -- integer arithmetic over values it already read -- published only on the no-action return past every entry guard. (c) The ladder's thresholds and flags are retyped `Cell` -> `TriggerInput`, whose writes add one store retiring the watermark; heap-generation advances and `Arena::set_current` retire it too. (d) `#[cfg(test)]` seams. Nothing added allocates a GC object, relocates anything, collects, or invokes a JS callback; `census_pass1_if_armed` is still inside `step_mark_propagation` and `census_take_if_armed_at_full_sweep_start` inside `step_sweep`, so the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-25 for #10498 (class-accessor cache), which adds one `reg_scanner!` registration (`scan_class_accessor_cache_roots_mut`, marking and rewriting the cache's key strings) to `gc/mod.rs`: again a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-26 for #10572: `gc/mod.rs` gains one `#[cfg(not(feature = \"hot-diag\"))]` call to `hot_diag::refuse_knobs_without_hot_diag()` in `gc_init`, a startup-only knob check that runs before any cycle and alters no mark/sweep control flow; the mark-complete to sweep-entry window is unchanged. Re-audited 2026-09-27 for #11416: `gc/mod.rs` loses the `reg_scanner!` registration for `class_accessor_cache::scan_class_accessor_cache_roots_mut` (the cache is deleted); removing a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 for the method-calls lane, which adds one `reg_scanner!` registration (`scan_method_site_roots_mut`, marking and rewriting the method closures inherited method-site entries hold) to `gc/mod.rs`: a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-28 (charter step 5, P0): the field-representation census adds `mod census_field_repr;` to `gc/mod.rs` and, in `census.rs`, one Rust-owned accumulator fed from `visit_object` inside `take_census`, which runs after `census_take_if_armed_at_full_sweep_start` has already taken PASS1_MARKED; it reads slots and the per-object layout maps (try_borrow), allocates nothing on the JS heap and alters no mark/sweep control flow. The window is unchanged. Re-audited 2026-09-27 for #11416: `gc/mod.rs` loses the `reg_scanner!` registration for `class_accessor_cache::scan_class_accessor_cache_roots_mut` (the cache is deleted); removing a root-scanner registration alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 for the method-calls lane, which adds one `reg_scanner!` registration (`scan_method_site_roots_mut`, marking and rewriting the method closures inherited method-site entries hold) to `gc/mod.rs`: a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-27 (class constructors as function objects, #11414): `gc/mod.rs` gains one `reg_scanner!` registration (`object::class_value::scan_class_value_roots_mut`, the per-agent class function-object table, visited and rewritten) \u2014 a root-scanner registration that alters no mark/sweep control flow and runs nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-29 after #11659 added `verify::verify_array_hole_tails_at_collection()` to `gc/mod.rs` and `gc/policy.rs`. Every call sits at a collection or budgeted-cycle ENTRY, beside `roots::ensure_stack_maps_built()`, i.e. before mark begins and so before `census_pass1_if_armed` opens the window. The call is a read-only walk of array headers (debug builds, or release with PERRY_GC_VERIFY_ARRAY_HOLES): it neither allocates GC memory, relocates, runs JS, nor reads or writes PASS1_MARKED, and it panics rather than continuing on a violation. Mark/sweep control flow between `census_pass1_if_armed` and `census_take_if_armed_at_full_sweep_start` is untouched. Pin re-audited 2026-09-29 after PR #11646 added one call inside that window, in `cycle.rs` beside `census_take_if_armed_at_full_sweep_start`: `object::shapes::store_kind::audit_heap_at_full_sweep_start()`. It compiles to nothing unless the `shape-fact-audit` feature is on; when on it walks the arena read-only (`gc::for_each_live_object_at_sweep_start`, in `gc/verify.rs`) and reads each marked object header and its shape record. It allocates no GC object, moves nothing, runs no JS callback and holds no address past the walk, so PASS1_MARKED stays valid across it; a disagreement panics rather than continuing. Re-audited 2026-09-29 after the pinned-roots fix touched `gc/cycle.rs` and `gc/mod.rs`: the block-persistence live-block predicate now reads GC_FLAG_MARKED alone (pinned objects are marked as roots, so the set of live blocks is unchanged), and `gc/mod.rs` adds one `reg_scanner!` registration. Neither relocates, runs JS, or moves the mark-complete to sweep-entry window. Re-audited again 2026-09-29 (same fix, copying-minor follow-up): `BlockPersistCycleState`'s force-mark in `gc/cycle.rs` no longer skips an unmarked pinned header, so it marks and pushes it like any other object of a live block. That is marking inside the budgeted cycle's persistence step: nothing relocates, no JS runs, and the budgeted path still skips both census boundaries. The window is unchanged. Re-audited 2026-09-29 for this-as-a-parameter stage 3: `gc/mod.rs` registers `scan_dispatch_binding_roots_mut` in place of `scan_implicit_this_roots_mut` (the implicit-`this` cell is deleted; the same scanner body keeps `new.target`, the static-`this` override and the static private-owner stack) and rewords its comment; a root-scanner registration, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window. Re-audited 2026-09-28 for the JsFunctionInfo lane: `gc/census.rs`'s `side_tables()` loses its `closure_registry_census()` row (the closure-body registry is deleted); a census report row, altering no mark/sweep control flow and running nothing inside the mark-complete to sweep-entry window.", "window": { "start": { "file": "crates/perry-runtime/src/gc/census.rs", @@ -407,7 +377,7 @@ "function": "run_to_completion" }, "sources": { - "crates/perry-runtime/src/gc/census.rs": "b2b60a124ea0f1131108940a8e675dae92f24192ecb482f87b6183e7641a2677", + "crates/perry-runtime/src/gc/census.rs": "aed9e85c2c5cf17869f2e50408ca2dcaddb052aedea6d019431a9b57904dbda7", "crates/perry-runtime/src/gc/cycle.rs": "9b992d27700382c84758f6f4a5574115a9862234bed2e18e0c5a3cca25c25236", "crates/perry-runtime/src/gc/mod.rs": "ee122498f4071588eca2ea07199a5c60dd85184a485a430a688f379f41cd81d5", "crates/perry-runtime/src/gc/policy.rs": "84a869e0aa09e932a4d2b6601186260129ba851b4482f29579047713c60fc95e", @@ -2863,10 +2833,6 @@ "file": "crates/perry-runtime/src/array/indexing_support.rs", "name": "KEYS_ARRAY_SLOT_FALLBACKS" }, - { - "file": "crates/perry-runtime/src/array/iterator.rs", - "name": "REGISTERED" - }, { "file": "crates/perry-runtime/src/async_context.rs", "name": "HANDLE_GENERATIONS" @@ -3007,14 +2973,6 @@ "file": "crates/perry-runtime/src/closure/dynamic_props.rs", "name": "IN_FN_PROTO_FALLBACK" }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "DISPATCH_RECENT" - }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "RESOLVE_STRATEGY_SLOW_CALLS" - }, { "file": "crates/perry-runtime/src/cluster.rs", "name": "CLUSTER_GC_REGISTERED" @@ -3604,10 +3562,6 @@ "file": "crates/perry-runtime/src/node_stream_constructors.rs", "name": "DEFAULT_HWM_OBJECT" }, - { - "file": "crates/perry-runtime/src/node_stream_constructors.rs", - "name": "ITER_HELPER_ARITIES_REGISTERED" - }, { "file": "crates/perry-runtime/src/node_submodules/blob.rs", "name": "FILE_BLOBS" @@ -3688,10 +3642,6 @@ "file": "crates/perry-runtime/src/object/arguments.rs", "name": "TEST_ARGUMENTS_REGISTRY_PROBES" }, - { - "file": "crates/perry-runtime/src/object/async_generator_queue.rs", - "name": "REGISTERED" - }, { "file": "crates/perry-runtime/src/object/class_registry/dispatch.rs", "name": "OBJ_DISPATCH_IC" @@ -4007,10 +3957,6 @@ "file": "crates/perry-runtime/src/promise/async_step.rs", "name": "TRACE_ASYNC_SEQ" }, - { - "file": "crates/perry-runtime/src/promise/combinators.rs", - "name": "DONE" - }, { "file": "crates/perry-runtime/src/promise/combinators.rs", "name": "SCHEDULED_RESOLVES" @@ -4071,14 +4017,6 @@ "file": "crates/perry-runtime/src/promise/rejection.rs", "name": "PROCESSING_REJECTIONS" }, - { - "file": "crates/perry-runtime/src/promise/spec_combinators.rs", - "name": "ARITY_REGISTERED" - }, - { - "file": "crates/perry-runtime/src/promise/then.rs", - "name": "DONE" - }, { "file": "crates/perry-runtime/src/promise/then_probe.rs", "name": "ADMISSIBLE_MEMO" diff --git a/scripts/registry_lifetime_allowlist.json b/scripts/registry_lifetime_allowlist.json index 459574f9ce..a882c62260 100644 --- a/scripts/registry_lifetime_allowlist.json +++ b/scripts/registry_lifetime_allowlist.json @@ -136,12 +136,6 @@ "verdict": "bounded_by_program", "why": "Keyed by func_ptr of non-capturing closures: one cached closure per compiled function" }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "CLOSURE_BODY_REGISTRY", - "verdict": "bounded_by_program", - "why": "One ClosureBodyRecord per closure body keyed by func_ptr, filled at module init from codegen registrations" - }, { "file": "crates/perry-runtime/src/dyn_eval/bridge.rs", "name": "MEMBER_KEY_CACHE", @@ -572,13 +566,6 @@ "name": "UNTRANSFERABLE_OBJECTS", "verdict": "open_leak", "why": "NaN-box bits of each object passed to markAsUntransferable: one per marked object, never unmarked" - }, - { - "file": "crates/perry-runtime/src/closure/registry.rs", - "name": "PARAMS", - "fn": "wasi_body_params", - "verdict": "bounded_by_program", - "why": "WASI-only per-thread memo keyed by a wasm function-table index (a function pointer, never a heap address): one entry per distinct compiled closure body ever called, so growth stops at the program's function count" } ] } diff --git a/scripts/wasi_toolchain.sh b/scripts/wasi_toolchain.sh index e56dacb62e..e300b787b7 100755 --- a/scripts/wasi_toolchain.sh +++ b/scripts/wasi_toolchain.sh @@ -7,9 +7,7 @@ # # wasi-sdk clang + wasm-ld + the wasm32-wasip2 sysroot: compiles the # runtime's C for WASI and links `perry compile --target wasi`. -# wasmtime the WASI host the smoke test runs programs under. Perry's -# closure dispatch uses `ref.test` (the GC proposal) on WASI; -# wasmtime enables it by default. +# wasmtime the WASI host the smoke test runs programs under. # # Versions are adopted only once they are older than the repo's soak window # (scripts/soak/constants.mts): wasi-sdk 34 was published 2026-08-25 and diff --git a/test-files/test_gap_fn_info_facts.ts b/test-files/test_gap_fn_info_facts.ts new file mode 100644 index 0000000000..b065742d62 --- /dev/null +++ b/test-files/test_gap_fn_info_facts.ts @@ -0,0 +1,127 @@ +// Every fact a function object's JsFunctionInfo carries, observed through +// JS: the parameter count callers pad to, the rest kind and its fixed +// prefix, `.length` and its declared fallback, the arrow / async / +// generator / async-generator / non-constructor bits, and the compiled +// direct-call clones (a captured arrow called from a loop and from an +// array method). Each fact is read through a GENERIC call (the function +// travels as a value) so the runtime reads it from the info, not from a +// statically known callee. + +const out: string[] = []; +const show = (label: string, v: unknown) => out.push(label + " " + JSON.stringify(v)); +const tryNew = (f: any) => { + try { + new f(); + return "constructed"; + } catch (e) { + return (e as Error).constructor.name; + } +}; + +const values: any[] = []; +function keep(f: T): T { + values.push(f); + return values[values.length - 1]; +} + +// params: a caller passing fewer pads with undefined to the declared count. +const three = keep(function three(a: any, b: any, c: any) { + return [a, b === undefined, c === undefined]; +}); +show("pad", three(1)); +show("pad.call", three.call(null, 1, 2)); +show("pad.apply", three.apply(null, [1])); +show("pad.map", [7].map(three as any)); + +// rest: the fixed prefix and the bundled tail. +const rest = keep(function rest(a: any, b: any, ...xs: any[]) { + return [a, b, xs]; +}); +show("rest0", rest()); +show("rest1", rest(1)); +show("rest4", rest(1, 2, 3, 4)); +show("rest.apply", rest.apply(null, [1, 2, 3])); +const restOnly = keep((...xs: any[]) => xs.length); +show("restOnly", [restOnly(), restOnly(1, 2, 3), restOnly.call(null, 1)]); + +// arguments: every argument, fixed parameters included. +const args = keep(function args(a: any) { + return [a, arguments.length, Array.prototype.slice.call(arguments)]; +}); +show("arguments", args(1, 2, 3)); +show("arguments0", args()); +const both = keep(function both(a: any, ...r: any[]) { + return [arguments.length, r.length, a]; +}); +show("rest+arguments", both(1, 2, 3, 4)); + +// .length and its declared fallback. +show("length", [ + three.length, + rest.length, + restOnly.length, + args.length, + keep(function (a: any, b = 1, c?: any) {}).length, + keep((x: any, y: any) => 0).length, + keep(async (x: any) => 0).length, + keep(function* (p: any, q: any) {}).length, + three.bind(null, 1).length, + rest.bind(null).length, +]); +show("builtin length", [Math.max.length, [].map.length, Array.prototype.push.length, JSON.stringify.length, Object.keys.length]); + +// kind bits: arrow, async, generator, async generator, builtin. (A concise +// method's missing [[Construct]] and the async/generator toStringTag are +// separate gaps on main and not read from here.) +const arrow = keep(() => 1); +const asyncFn = keep(async function () {}); +const gen = keep(function* () {}); +const asyncGen = keep(async function* () {}); +show("new", [ + tryNew(keep(function F(this: any) { this.x = 1; })), + tryNew(arrow), + tryNew(asyncFn), + tryNew(gen), + tryNew(asyncGen), + tryNew(Math.max), +]); +show("prototype", [ + typeof (three as any).prototype, + typeof (arrow as any).prototype, + typeof (asyncFn as any).prototype, + typeof (gen as any).prototype, +]); +show("gen", Array.from((gen as any)()).length); + +// strict receiver: a plain call's `this` is undefined, a primitive +// receiver stays primitive. +const recv = keep(function (this: any) { + return typeof this; +}); +show("this", [recv(), recv.call(5), recv.call("s"), recv.call({})]); +// arrows keep the lexical receiver whatever the caller passes. +const holder = { + tag: "h", + make() { + return keep(() => (this as any).tag); + }, +}; +const lexical = holder.make(); +show("arrow this", [lexical(), lexical.call({ tag: "other" })]); + +// the direct-call clones: a capturing arrow run from a loop and from array +// methods, with a boxed (reassigned) capture and a plain one. +let boxed = 0; +const step = 3; +const add = keep((x: number) => { + boxed += x * step; + return boxed; +}); +let last = 0; +for (let i = 0; i < 1000; i++) last = add(i); +show("loop clone", [last, boxed]); +show("map clone", [1, 2, 3].map(add)); +show("reduce clone", [1, 2, 3, 4].reduce((acc, x) => acc + x * step, 0)); +show("filter clone", [1, 2, 3, 4, 5, 6].filter((x) => x % step === 0)); + +for (const line of out) console.log(line);