From 533f5ce163a46c56c4256d42e653f47779ef56ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 26 Sep 2026 17:57:32 +0000 Subject: [PATCH 01/17] refactor: one funnel per side for every call of a JS function body (this-as-a-parameter, stage 0) No behaviour change. Every place that turns a function body's code pointer into a call now goes through one funnel, so the body ABI can change (stage 1: the receiver becomes a parameter) in one place, with the compiler finding every caller. Runtime: `closure/body_call.rs` owns the body types and the only transmutes to them: `js_body_fn_ty!` / `js_body_fn!` / `js_body_call!` / `js_body_call_unwind!` for closure-ABI bodies `(callee, a0..)`, `js_method_body_fn!` / `js_method_body_call!` for class method, constructor and accessor bodies `(this, a0..)`, and `js_bare_body_fn!` for static methods, static accessors and the lifted well-known hooks `(a0..)`. Routed through it: the 17 direct arms of `js_closure_call0..16`, `dispatch_with_arity` (33 arms), `dispatch_rest_bundled` (16 arms, now one macro), the wide ladder, `DirectCall1..4`, the captureless `some` callback, the async-step microtask, five `thread.rs` workers, 31 class accessor / static / hook calls, and the `then(this, ..)` thenable call. The 57 typed native-body type positions (install helpers, erasure casts) use `js_body_fn_ty!`. Production (`panic = "abort"`) signatures are unchanged; in unwinding test builds rest arms 1-6 now use the unwind-capable pointer type arms 0 and 7-15 already used. Codegen: `expr/body_call.rs::emit_js_body_call` is the only builder of body calls: the method-site hit, the versioned-loop and resolved-arrow indirect calls, the known-closure and `$generic` direct calls, imported-object method bodies and the public typed trampoline's generic fallback. perry-abi: `JS_BODY_CALLEE_PARAM` / `JS_BODY_FIRST_ARG_PARAM`, `JS_CLOSURE_CALL_ENTRIES` (the codegen `js_closure_call{N}` declarations are generated from it; a runtime test checks each entry names the function taking that many JS arguments) and `JS_CALL_ENTRIES`, from which `gc_root_dominance_check.py` now reads its JS-call poll-capable set. That adds `js_closure_call1_receiverless`, which the hand list lacked. Gate: `scripts/check_js_body_call_funnel.py` (+ `--self-test`, wired into the lint job) refuses a transmute to a body type outside the runtime funnel and an indirect call built outside the codegen funnel. Native Rust helpers with a body-shaped type carry `NOT-A-JS-BODY:`. Also: delete `js_closure_unbind_this`, which was dead (no caller) and wrong (it cleared capture slot 0; `this` lives in the LAST slot), and correct four comments that stated false facts: `CAPTURES_THIS_FLAG` (last slot, not slot 0), the bind-result capture layout (5 slots, not 3), the `js_closure_alloc` flag note, and the `new.rs` claim that a throw leaves `new.target` set (the catch savepoint replays it at the nearest `try`). --- .github/workflows/test.yml | 10 + crates/perry-abi/src/lib.rs | 66 ++++ crates/perry-codegen/src/codegen/closure.rs | 12 +- crates/perry-codegen/src/expr/body_call.rs | 61 ++++ crates/perry-codegen/src/expr/closure.rs | 3 +- crates/perry-codegen/src/expr/method_site.rs | 16 +- crates/perry-codegen/src/expr/mod.rs | 1 + .../src/lower_call/early_branches.rs | 98 +++--- crates/perry-codegen/src/lower_call/new.rs | 7 +- .../property_get/imported_object.rs | 22 +- .../src/runtime_decls/strings.rs | 103 +----- .../perry-codegen/src/wasm32/runtime_abi.tsv | 1 - .../perry-runtime/src/array/iter_methods.rs | 5 +- crates/perry-runtime/src/array/iterator.rs | 2 +- .../perry-runtime/src/bun_compat/cli_utils.rs | 10 +- crates/perry-runtime/src/bun_compat/mod.rs | 4 +- crates/perry-runtime/src/bun_compat/plugin.rs | 2 +- .../src/child_process/builder.rs | 14 +- crates/perry-runtime/src/closure/alloc.rs | 8 +- crates/perry-runtime/src/closure/body_call.rs | 131 ++++++++ .../src/closure/dispatch/calln.rs | 316 +++++++----------- .../src/closure/dispatch/direct.rs | 10 +- .../src/closure/dispatch/validate.rs | 4 +- .../src/closure/dynamic_props.rs | 66 ---- crates/perry-runtime/src/closure/mod.rs | 4 +- crates/perry-runtime/src/closure/registry.rs | 266 ++------------- crates/perry-runtime/src/closure/shape.rs | 2 +- crates/perry-runtime/src/closure/wide_call.rs | 13 +- crates/perry-runtime/src/dgram.rs | 2 +- crates/perry-runtime/src/event_target.rs | 2 +- .../src/fs/dir_glob_watch/watch.rs | 2 +- crates/perry-runtime/src/fs/filehandle.rs | 1 + .../src/fs/mkdtemp_disposable.rs | 6 +- crates/perry-runtime/src/fs/stream.rs | 153 ++++----- .../src/iterator_helpers/tests.rs | 4 +- crates/perry-runtime/src/lib.rs | 1 + .../src/node_stream/async_iterator.rs | 2 +- .../perry-runtime/src/node_stream_dispatch.rs | 8 +- .../src/node_submodules/consumers.rs | 1 + .../src/node_submodules/diagnostics.rs | 12 +- .../src/node_submodules/fs_promises.rs | 6 +- .../src/node_submodules/hono_jsx.rs | 1 + .../perry-runtime/src/node_submodules/mod.rs | 6 +- crates/perry-runtime/src/node_vm.rs | 2 +- crates/perry-runtime/src/node_vm/modules.rs | 2 +- .../src/object/async_generator_queue.rs | 2 +- .../object/class_registry/decl_accessors.rs | 4 +- .../object/class_registry/parent_static.rs | 75 ++--- .../parent_static/private_and_dynamic.rs | 4 +- .../src/object/class_registry/registration.rs | 4 +- .../perry-runtime/src/object/global_fetch.rs | 6 + .../src/object/global_this/proto_methods.rs | 2 +- .../src/object/inherited_read_cache.rs | 4 +- .../src/object/instanceof/static_dispatch.rs | 3 +- .../src/object/iterator_prototypes.rs | 2 +- .../perry-runtime/src/object/native_module.rs | 1 + .../perry-runtime/src/object/property_key.rs | 4 +- .../perry-runtime/src/object/to_string_tag.rs | 2 +- .../perry-runtime/src/os_process_streams.rs | 14 +- crates/perry-runtime/src/process.rs | 5 +- crates/perry-runtime/src/process/ipc.rs | 4 +- .../perry-runtime/src/process/node_module.rs | 2 +- .../src/process/node_module/source_map.rs | 8 +- .../perry-runtime/src/promise/combinators.rs | 21 +- .../perry-runtime/src/promise/microtasks.rs | 4 +- crates/perry-runtime/src/promise/mod.rs | 1 + crates/perry-runtime/src/pty/mod.rs | 2 +- crates/perry-runtime/src/pty/tests.rs | 2 +- crates/perry-runtime/src/thread.rs | 21 +- scripts/check_js_body_call_funnel.py | 198 +++++++++++ scripts/gc_root_dominance_check.py | 28 +- test-files/test_compat_buffers_typed.ts | 1 - 72 files changed, 971 insertions(+), 921 deletions(-) create mode 100644 crates/perry-codegen/src/expr/body_call.rs create mode 100644 crates/perry-runtime/src/closure/body_call.rs create mode 100755 scripts/check_js_body_call_funnel.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c6ec589234..0e99934e71 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -624,6 +624,16 @@ jobs: python3 scripts/check_gc_env_knobs.py --self-test python3 scripts/check_gc_env_knobs.py + # `this`-as-a-parameter lane, stage 0: every call of a JS function body + # goes through one funnel per side (runtime `closure/body_call.rs`, + # codegen `expr/body_call.rs`), so a body-ABI change is made in one place + # and the compiler finds every caller. + - name: JS body-call funnel + if: ${{ !cancelled() }} + run: | + python3 scripts/check_js_body_call_funnel.py --self-test + python3 scripts/check_js_body_call_funnel.py + # #7982. The in-process LLVM reader's unit gate builds three tracked `.ll` # corpora and asserts they RAN — which proves the tests ran, not that they # test today's IR. All three froze on 2026-08-03 and carried zero diff --git a/crates/perry-abi/src/lib.rs b/crates/perry-abi/src/lib.rs index 0cb02c6c2a..ee0b89e10c 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -37,6 +37,72 @@ pub const GC_FLAG_FORWARDED: u8 = 0x80; /// `gc::GC_HEADER_SIZE`. pub const GC_HEADER_SIZE: usize = 8; + +/// The JS BODY calling convention. Every native body a function object runs — +/// a compiled closure body, a value wrapper, a native builtin installed as a +/// function object — is +/// +/// ```text +/// double body(i64 callee, double a0, double a1, ...) +/// ``` +/// +/// where `callee` is the function object (its captures follow the header). +/// Passing more JS arguments than a body declares is safe (the caller owns +/// the stack argument area); fewer is padded with `undefined` by the caller. +/// The runtime calls bodies only through `closure/body_call.rs`; emitted code +/// only through `expr::body_call::emit_js_body_call`. +pub const JS_BODY_CALLEE_PARAM: usize = 0; +/// Native parameter index of the first JS argument. +pub const JS_BODY_FIRST_ARG_PARAM: usize = 1; +/// `js_closure_call{N}(callee, a0..aN-1)` exists for `N <= JS_CLOSURE_CALL_MAX_ARGS`; +/// wider calls use `js_closure_call_array`. +pub const JS_CLOSURE_CALL_MAX_ARGS: usize = 16; +/// The fixed-arity entries, indexed by JS argument count. +pub const JS_CLOSURE_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 1] = [ + "js_closure_call0", + "js_closure_call1", + "js_closure_call2", + "js_closure_call3", + "js_closure_call4", + "js_closure_call5", + "js_closure_call6", + "js_closure_call7", + "js_closure_call8", + "js_closure_call9", + "js_closure_call10", + "js_closure_call11", + "js_closure_call12", + "js_closure_call13", + "js_closure_call14", + "js_closure_call15", + "js_closure_call16", +]; +/// Every runtime entry point emitted code calls to run a JS function. Each can +/// run arbitrary JS and therefore collect: `scripts/gc_root_dominance_check.py` +/// reads its poll-capable set from THIS list. +pub const JS_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 5] = [ + "js_closure_call0", + "js_closure_call1", + "js_closure_call2", + "js_closure_call3", + "js_closure_call4", + "js_closure_call5", + "js_closure_call6", + "js_closure_call7", + "js_closure_call8", + "js_closure_call9", + "js_closure_call10", + "js_closure_call11", + "js_closure_call12", + "js_closure_call13", + "js_closure_call14", + "js_closure_call15", + "js_closure_call16", + "js_closure_call1_receiverless", + "js_closure_call_array", + "js_closure_call_apply_with_spread", + "js_native_call_value", +]; /// `object::method_site::MethodEntry` — the words the emitted method-call site /// reads (`perry-codegen/src/expr/method_site.rs`). pub const METHOD_SITE_WORD_OFFSET: usize = 0; diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index 83e1a3de40..dfbae21935 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -169,12 +169,12 @@ fn emit_public_typed_closure_trampoline( emit_typed_closure_trampoline_fast_value(blk, kind, &typed_name, values, &arg_reps) }, &mut |blk| { - let mut call_args: Vec<(LlvmType, &str)> = Vec::with_capacity(arg_names.len() + 1); - call_args.push((I64, "%this_closure")); - for arg in &arg_names { - call_args.push((DOUBLE, arg.as_str())); - } - blk.call(DOUBLE, generic_body_name, &call_args) + crate::expr::body_call::emit_js_body_call( + blk, + crate::expr::body_call::JsBody::Symbol(generic_body_name), + "%this_closure", + &arg_names, + ) }, ); Ok(()) diff --git a/crates/perry-codegen/src/expr/body_call.rs b/crates/perry-codegen/src/expr/body_call.rs new file mode 100644 index 0000000000..c7f3fab2b6 --- /dev/null +++ b/crates/perry-codegen/src/expr/body_call.rs @@ -0,0 +1,61 @@ +//! The only builder of calls to a JS BODY (`perry_abi::JS_BODY_*`): +//! `double body(i64 callee, double a0, ...)` — a compiled closure body +//! (`perry_closure_*`, its `$generic` / `$trusted_boxes` / versioned-loop +//! clones) or any body reached through a function object's code pointer. +//! +//! Every emitted call of such a body goes through [`emit_js_body_call`] so +//! that a change to the body ABI (the receiver becoming a parameter) is made +//! here and nowhere else. Private typed clones (`$typed_*`, raw-rep +//! parameters) are not JS bodies and are called by their own emitters. +//! `scripts/check_js_body_call_funnel.sh` refuses an indirect call built +//! anywhere else. + +use crate::block::LlBlock; +use crate::types::{LlvmType, DOUBLE, I64}; + +/// What is being called. +#[derive(Clone, Copy)] +pub(crate) enum JsBody<'a> { + /// A body symbol, without the leading `@`. + Symbol(&'a str), + /// A code pointer value (e.g. loaded from a function object or a site). + Pointer(&'a str), +} + +/// The native argument list of a JS body call: the callee, then the JS +/// arguments, in `perry_abi::JS_BODY_*` order. +fn js_body_args<'a>(callee: &'a str, args: &'a [String]) -> Vec<(LlvmType, &'a str)> { + const _: () = assert!(crate::runtime_abi::JS_BODY_CALLEE_PARAM == 0); + const _: () = assert!(crate::runtime_abi::JS_BODY_FIRST_ARG_PARAM == 1); + let mut out: Vec<(LlvmType, &str)> = Vec::with_capacity(args.len() + 1); + out.push((I64, callee)); + out.extend(args.iter().map(|a| (DOUBLE, a.as_str()))); + out +} + +/// Emit `body(callee, args...)` and return the result value. +pub(crate) fn emit_js_body_call( + blk: &mut LlBlock, + body: JsBody<'_>, + callee: &str, + args: &[String], +) -> String { + let native = js_body_args(callee, args); + match body { + JsBody::Symbol(name) => blk.call(DOUBLE, name, &native), + JsBody::Pointer(ptr) => blk.call_indirect(DOUBLE, ptr, &native), + } +} + +/// [`emit_js_body_call`] through a code pointer whose caller-side native GC +/// values need not be relocated across the call (`LlBlock::call_indirect_gc_leaf` +/// states when that is sound). +pub(crate) fn emit_js_body_call_gc_leaf( + blk: &mut LlBlock, + code_ptr: &str, + callee: &str, + args: &[String], +) -> String { + let native = js_body_args(callee, args); + blk.call_indirect_gc_leaf(DOUBLE, code_ptr, &native) +} diff --git a/crates/perry-codegen/src/expr/closure.rs b/crates/perry-codegen/src/expr/closure.rs index d48d56df2e..b1d735834e 100644 --- a/crates/perry-codegen/src/expr/closure.rs +++ b/crates/perry-codegen/src/expr/closure.rs @@ -232,8 +232,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // size (real_capture_count) but preserves it in the stored // `capture_count` field. Used by `clone_closure_rebind_this` at // `Object.defineProperty(obj, k, { get(){}, set(){} })` time so - // accessor invocation sees `this === obj` per spec, and by - // `js_closure_unbind_this` for detached method references. + // accessor invocation sees `this === obj` per spec. let cap_count_val = if *captures_this { (total_caps as u32) | 0x8000_0000u32 } else { diff --git a/crates/perry-codegen/src/expr/method_site.rs b/crates/perry-codegen/src/expr/method_site.rs index 7bdf07f658..f53479186e 100644 --- a/crates/perry-codegen/src/expr/method_site.rs +++ b/crates/perry-codegen/src/expr/method_site.rs @@ -411,20 +411,20 @@ pub(crate) fn emit_method_site( Some(cell) => crate::rooting::implicit_this_save_at(ctx, cell, recv_box), None => crate::rooting::implicit_this_save(ctx, recv_box), }; - let mut call_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - call_args.push((I64, &handle)); - call_args.extend(lowered_args.iter().map(|a| (DOUBLE, a.as_str()))); + let mut call_args: Vec = lowered_args.to_vec(); // Pad with `undefined` up to the arity the prime admits, so a body that // declares a few more parameters than this call passes is entered // directly (`dispatch_with_arity` pads the same way). A body declaring // fewer ignores the extra registers. let undefined = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); let pad = crate::runtime_abi::method_site_padded_argc(lowered_args.len()) - lowered_args.len(); - for _ in 0..pad { - call_args.push((DOUBLE, undefined.as_str())); - } - let hit_value = ctx.block().call_indirect(DOUBLE, &fptr, &call_args); + call_args.extend(std::iter::repeat_n(undefined, pad)); + let hit_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Pointer(&fptr), + &handle, + &call_args, + ); match &cell { Some(cell) => crate::rooting::implicit_this_restore_at(ctx, cell, saved), None => crate::rooting::implicit_this_restore(ctx, saved), diff --git a/crates/perry-codegen/src/expr/mod.rs b/crates/perry-codegen/src/expr/mod.rs index e2eae9837a..54e9bd367d 100644 --- a/crates/perry-codegen/src/expr/mod.rs +++ b/crates/perry-codegen/src/expr/mod.rs @@ -36,6 +36,7 @@ pub(crate) mod array_length; mod array_literal; pub(crate) mod array_proto_guard; mod bitset_test; +pub(crate) mod body_call; pub(crate) mod folded_builtin_override; pub(crate) mod hot_tls; mod literal_descriptor; diff --git a/crates/perry-codegen/src/lower_call/early_branches.rs b/crates/perry-codegen/src/lower_call/early_branches.rs index 51c85cc201..444a8b31a7 100644 --- a/crates/perry-codegen/src/lower_call/early_branches.rs +++ b/crates/perry-codegen/src/lower_call/early_branches.rs @@ -473,23 +473,20 @@ pub fn try_lower_closure_typed_local_call( if callback_local_id == *id && callback_arity == lowered_args.len() { let context_bits = ctx.block().ptrtoint(&context, I64); let context_box = ctx.block().bitcast_i64_to_double(&context_bits); - let mut direct_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - direct_args.push((I64, &closure_handle)); - direct_args.extend(lowered_args.iter().enumerate().map(|(index, value)| { - if index == 0 { - (DOUBLE, context_box.as_str()) - } else { - (DOUBLE, value.as_str()) - } - })); + // The versioned-loop clone repurposes JS argument 0 for + // the caller's deopt context (see `codegen/closure.rs`). + let mut direct_args: Vec = lowered_args.to_vec(); + direct_args[0] = context_box; // The exact clone marks the caller's counter for an // immediate side exit before any cold arm that can run // user code or collect. Hot returns cannot collect; cold // returns never observe caller-side cached heap handles. - let value = ctx - .block() - .call_indirect_gc_leaf(DOUBLE, &target, &direct_args); + let value = crate::expr::body_call::emit_js_body_call_gc_leaf( + ctx.block(), + &target, + &closure_handle, + &direct_args, + ); callee_group.release(ctx); return Ok(Some(value)); } @@ -509,11 +506,12 @@ pub fn try_lower_closure_typed_local_call( ctx.block().cond_br(&fast_ok, &fast_label, &fallback_label); ctx.current_block = fast_idx; - let mut direct_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - direct_args.push((I64, &closure_handle)); - direct_args.extend(lowered_args.iter().map(|value| (DOUBLE, value.as_str()))); - let fast_value = ctx.block().call_indirect(DOUBLE, &target, &direct_args); + let fast_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Pointer(&target), + &closure_handle, + &lowered_args, + ); let after_fast = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); @@ -800,13 +798,12 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = generic_idx; - let mut generic_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - generic_args.push((DOUBLE, v.as_str())); - } - let generic_value = - ctx.block().call(DOUBLE, &generic_closure_fn, &generic_args); + let generic_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), + &closure_handle, + &lowered_args, + ); let after_generic = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&typed_merge_label); @@ -901,13 +898,12 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = generic_idx; - let mut generic_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - generic_args.push((DOUBLE, v.as_str())); - } - let generic_value = - ctx.block().call(DOUBLE, &generic_closure_fn, &generic_args); + let generic_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), + &closure_handle, + &lowered_args, + ); let after_generic = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&typed_merge_label); @@ -1005,13 +1001,12 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = generic_idx; - let mut generic_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - generic_args.push((DOUBLE, v.as_str())); - } - let generic_value = - ctx.block().call(DOUBLE, &generic_closure_fn, &generic_args); + let generic_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), + &closure_handle, + &lowered_args, + ); let after_generic = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&typed_merge_label); @@ -1112,13 +1107,12 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = generic_idx; - let mut generic_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - generic_args.push((DOUBLE, v.as_str())); - } - let generic_value = - ctx.block().call(DOUBLE, &generic_closure_fn, &generic_args); + let generic_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), + &closure_handle, + &lowered_args, + ); let after_generic = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&typed_merge_label); @@ -1152,12 +1146,12 @@ pub fn try_lower_closure_typed_local_call( ); result } else { - let mut direct_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - direct_args.push((DOUBLE, v.as_str())); - } - ctx.block().call(DOUBLE, &closure_fn, &direct_args) + crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&closure_fn), + &closure_handle, + &lowered_args, + ) }; let after_fast = ctx.block().label.clone(); if !ctx.block().is_terminated() { diff --git a/crates/perry-codegen/src/lower_call/new.rs b/crates/perry-codegen/src/lower_call/new.rs index 942918adde..2739cf17a4 100644 --- a/crates/perry-codegen/src/lower_call/new.rs +++ b/crates/perry-codegen/src/lower_call/new.rs @@ -692,9 +692,10 @@ fn lower_new_impl_inner<'a>( // observes the same cell — `new Child()` where only `Base` reads // `new.target` would otherwise see `undefined` instead of `Child`. // A runtime parent is an unknown reader, so it also needs the cell. - // ponytail: a throw inside the ctor skips the restore, leaving the cell - // set — same edge case the runtime construct paths already have; fix - // holistically if it bites. + // A throw inside the ctor skips this restore; the `new.target` catch + // savepoint (`exception/savepoints.rs`) replays the cell at the + // nearest enclosing JS `try`, so only a throw that no `try` catches + // leaves it set. // #7664: `prev` is saved across the WHOLE constructor body, and the // cell it comes out of is a registered mutable root that evacuation // rewrites — so it goes in a temp root, not a bare register. 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 925e5e5076..744661b7b6 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 @@ -244,11 +244,12 @@ pub(super) fn try_lower_imported_object_method_call( &closure_symbol, &fallback_label, ); - let mut direct_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - direct_args.push((I64, &closure_handle)); - direct_args.extend(lowered_args.iter().map(|arg| (DOUBLE, arg.as_str()))); - let direct_value = ctx.block().call(DOUBLE, &closure_symbol, &direct_args); + let direct_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&closure_symbol), + &closure_handle, + &lowered_args, + ); let direct_end = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); @@ -396,11 +397,12 @@ pub(super) fn try_lower_dynamic_object_method_call( &closure_symbol, &miss_label, ); - let mut direct_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - direct_args.push((I64, &closure_handle)); - direct_args.extend(lowered_args.iter().map(|arg| (DOUBLE, arg.as_str()))); - let direct_value = ctx.block().call(DOUBLE, &closure_symbol, &direct_args); + let direct_value = crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(&closure_symbol), + &closure_handle, + &lowered_args, + ); let direct_end = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index a6c701ef65..b4344c54a9 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -276,101 +276,18 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { I64, &[DOUBLE, DOUBLE], ); - module.declare_function("js_closure_call0", DOUBLE, &[I64]); - module.declare_function("js_closure_call1", DOUBLE, &[I64, DOUBLE]); + // `js_closure_call{N}(callee, a0..aN-1)`, generated from the ABI table so + // the declarations cannot drift from `perry_abi::JS_BODY_*`. + for (argc, name) in crate::runtime_abi::JS_CLOSURE_CALL_ENTRIES + .iter() + .enumerate() + { + let mut params = vec![I64]; + params.extend(std::iter::repeat_n(DOUBLE, argc)); + module.declare_function(name, DOUBLE, ¶ms); + } module.declare_function("js_closure_call1_receiverless", DOUBLE, &[I64, DOUBLE]); - module.declare_function("js_closure_call2", DOUBLE, &[I64, DOUBLE, DOUBLE]); - module.declare_function("js_closure_call3", DOUBLE, &[I64, DOUBLE, DOUBLE, DOUBLE]); module.declare_function("js_closure_resolve_arrow_direct_call", PTR, &[I64, I32]); - module.declare_function( - "js_closure_call4", - DOUBLE, - &[I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE], - ); - module.declare_function( - "js_closure_call5", - DOUBLE, - &[I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE], - ); - module.declare_function( - "js_closure_call6", - DOUBLE, - &[I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE], - ); - module.declare_function( - "js_closure_call7", - DOUBLE, - &[I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE], - ); - module.declare_function( - "js_closure_call8", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call9", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call10", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call11", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, - ], - ); - module.declare_function( - "js_closure_call12", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call13", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call14", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call15", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); - module.declare_function( - "js_closure_call16", - DOUBLE, - &[ - I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE, - ], - ); // Phase B.16 / D follow-ups: more runtime functions discovered // by the test-files sweep histogram. diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index d678846968..3596c4e100 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -648,7 +648,6 @@ js_closure_set_box_capture_ptr void ptr,i32u,i64 js_closure_set_capture_bits void ptr,i32u,i64 js_closure_set_capture_f64 void ptr,i32u,f64 js_closure_set_capture_ptr void ptr,i32u,i64 -js_closure_unbind_this f64 f64 js_closure_unbox_callee_checked i64 f64 js_closure_unbox_callee_checked_rebind i64 f64,f64 js_cluster_disconnect f64 f64 diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index 0e704d1f5c..bcb3414ec7 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -977,8 +977,9 @@ pub extern "C" fn js_array_some_captureless( return js_array_some(original_arr, callback); } - let callback: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(callback_func) }; + // `callback_func` is a captureless compiled closure body. + 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 { let length = (*arr).length; // SAFETY: `normalize_array_receiver` returned this live plain-array diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index 9a48155987..5eaf92dd52 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -700,7 +700,7 @@ fn register_async_from_sync_thunks_once() { fn install_async_from_sync_method( obj: *mut crate::object::ObjectHeader, name: &[u8], - func: extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a), iter: f64, ) -> f64 { // `obj`, `iter` and the freshly-allocated closure are live young objects diff --git a/crates/perry-runtime/src/bun_compat/cli_utils.rs b/crates/perry-runtime/src/bun_compat/cli_utils.rs index 1a4f1c9a48..ea2a0f5dea 100644 --- a/crates/perry-runtime/src/bun_compat/cli_utils.rs +++ b/crates/perry-runtime/src/bun_compat/cli_utils.rs @@ -53,7 +53,7 @@ fn number_arg(value: f64) -> Option { } } -fn closure1(name: &str, func: extern "C" fn(*const ClosureHeader, f64) -> f64) -> f64 { +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); crate::object::set_bound_native_closure_name(closure, name); @@ -61,11 +61,7 @@ fn closure1(name: &str, func: extern "C" fn(*const ClosureHeader, f64) -> f64) - f64::from_bits(JSValue::pointer(closure as *const u8).bits()) } -fn closure2( - name: &str, - func: extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - length: u32, -) -> f64 { +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); crate::object::set_bound_native_closure_name(closure, name); @@ -75,7 +71,7 @@ fn closure2( fn closure3( name: &str, - func: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a, a, a), length: u32, ) -> f64 { js_register_closure_arity(func as *const u8, 3); diff --git a/crates/perry-runtime/src/bun_compat/mod.rs b/crates/perry-runtime/src/bun_compat/mod.rs index 1e21a017f2..b59bbc3f89 100644 --- a/crates/perry-runtime/src/bun_compat/mod.rs +++ b/crates/perry-runtime/src/bun_compat/mod.rs @@ -159,14 +159,14 @@ 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: extern "C" fn(*const ClosureHeader) -> f64, capture: f64) -> f64 { +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: extern "C" fn(*const ClosureHeader, f64) -> f64, capture: f64) -> f64 { +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); js_closure_set_capture_f64(closure, 0, capture); diff --git a/crates/perry-runtime/src/bun_compat/plugin.rs b/crates/perry-runtime/src/bun_compat/plugin.rs index 2b265d5ee6..b1a89dc01b 100644 --- a/crates/perry-runtime/src/bun_compat/plugin.rs +++ b/crates/perry-runtime/src/bun_compat/plugin.rs @@ -127,7 +127,7 @@ mod tests { static SETUP_CALLS: AtomicU32 = AtomicU32::new(0); static LOADER_CALLS: AtomicU32 = AtomicU32::new(0); - fn closure(func: extern "C" fn(*const ClosureHeader, f64) -> f64) -> f64 { + 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) } diff --git a/crates/perry-runtime/src/child_process/builder.rs b/crates/perry-runtime/src/child_process/builder.rs index caf1ca631e..159a1dbc9e 100644 --- a/crates/perry-runtime/src/child_process/builder.rs +++ b/crates/perry-runtime/src/child_process/builder.rs @@ -1,8 +1,6 @@ use super::*; -use crate::closure::{ - js_closure_alloc, js_closure_set_capture_ptr, js_register_closure_arity, ClosureHeader, -}; +use crate::closure::{js_closure_alloc, js_closure_set_capture_ptr, js_register_closure_arity}; use crate::object::{js_object_alloc_with_shape, js_object_set_field, ObjectHeader}; use crate::value::JSValue; @@ -16,24 +14,24 @@ pub(crate) const CP_WRITABLE_SHAPE_ID: u32 = 0x7FFF_FD80; pub(crate) type CpFn = unsafe extern "C" fn(); #[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cp_cast0(f: extern "C" fn(*const ClosureHeader) -> f64) -> CpFn { +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: extern "C" fn(*const ClosureHeader, f64) -> f64) -> CpFn { +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: extern "C" fn(*const ClosureHeader, f64, f64) -> f64) -> CpFn { +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: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64) -> CpFn { +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: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64) -> CpFn { +pub(crate) fn cp_cast4(f: crate::closure::body_call::js_body_fn_ty!(a, a, a, a)) -> CpFn { unsafe { std::mem::transmute(f) } } diff --git a/crates/perry-runtime/src/closure/alloc.rs b/crates/perry-runtime/src/closure/alloc.rs index 1e4695af69..4f0568f730 100644 --- a/crates/perry-runtime/src/closure/alloc.rs +++ b/crates/perry-runtime/src/closure/alloc.rs @@ -600,10 +600,10 @@ pub(crate) unsafe fn gc_capture_slot_range( } /// Allocate a closure with space for captured values. -/// The high bit of `capture_count` may contain CAPTURES_THIS_FLAG to indicate -/// that slot 0 is reserved for `this`. The flag is preserved in the header -/// for later use by `js_closure_unbind_this`, but the actual allocation size -/// uses only the lower 31 bits. +/// The two high bits of `capture_count` may carry `CAPTURES_THIS_FLAG` (the +/// LAST capture slot holds `this`) and `NO_THIS_REBIND_FLAG`. Both are +/// preserved in the stored header; the allocation size uses only the count +/// (`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 { diff --git a/crates/perry-runtime/src/closure/body_call.rs b/crates/perry-runtime/src/closure/body_call.rs new file mode 100644 index 0000000000..f8690f1fb5 --- /dev/null +++ b/crates/perry-runtime/src/closure/body_call.rs @@ -0,0 +1,131 @@ +//! THE funnel: the only Rust code that turns a JS function body's code +//! pointer into something callable. +//! +//! A *JS body* is native code a function object runs: a compiled closure +//! body, a value wrapper, or a native builtin installed as a function +//! object. Its native signature is +//! +//! ```text +//! double body(i64 callee, double a0, double a1, ...) +//! ``` +//! +//! (`perry_abi::JS_BODY_*` names the parameter positions). Two other body +//! kinds exist until they join the one body ABI, and have their own macros: +//! +//! * METHOD bodies — class instance methods, constructors, instance +//! accessors: `double m(double this, double a0, ...)` +//! ([`js_method_body_call!`]); +//! * BARE bodies — static methods and static accessors, and the top-level +//! functions HIR lifts well-known hooks into (`static [Symbol.hasInstance]`, +//! `get [Symbol.toStringTag]`, whose receiver is an explicit parameter): +//! `double f(double a0, ...)` ([`js_bare_body_fn!`]). +//! +//! Every call site states the JS arguments it passes and nothing else, so +//! when the body ABI changes (the receiver becomes a parameter) this file is +//! where the change is made, and the compiler finds every caller. A +//! `mem::transmute` to a body type anywhere else is refused by +//! `scripts/check_js_body_call_funnel.sh`. +//! +//! Over-application is safe and relied on: every supported calling +//! convention leaves the stack argument area to the caller, so a body that +//! declares N parameters reads only the first N (`wide_call.rs`). + +/// The native type of a JS body taking one `f64` per token: +/// `js_body_fn_ty!(a, b)` is `extern "C" fn(*const ClosureHeader, f64, f64) -> f64`. +macro_rules! js_body_fn_ty { + (@f64 $x:tt) => { f64 }; + ($($x:tt),* $(,)?) => { + extern "C" fn(*const $crate::closure::ClosureHeader $(, $crate::closure::body_call::js_body_fn_ty!(@f64 $x))*) -> f64 + }; +} + +/// Reinterpret a validated, non-sentinel body code pointer as a callable +/// taking one `f64` per token (`js_body_fn!(code; a, b)`). +/// +/// # Safety +/// `code` must be a JS body whose declared JS parameter count is at most the +/// number of tokens (over-application is safe, see the module docs). +macro_rules! js_body_fn { + ($code:expr; $($x:tt),* $(,)?) => { + ::std::mem::transmute::<*const u8, $crate::closure::body_call::js_body_fn_ty!($($x),*)>($code) + }; +} + +/// Call a JS body: `js_body_call!(code, callee, a0, a1, ...)`. +/// +/// # Safety +/// As [`js_body_fn!`]; `callee` is the function object whose body `code` is. +macro_rules! js_body_call { + ($code:expr, $callee:expr $(, $a:expr)* $(,)?) => {{ + let f = $crate::closure::body_call::js_body_fn!($code; $($a),*); + f($callee $(, $a)*) + }}; +} + +/// [`js_body_call!`] through an unwind-capable pointer type in builds that +/// unwind Rust panics (test builds), for callers that must let a JS +/// exception travel through them there. Production (`panic = "abort"`) is +/// identical to [`js_body_call!`]. +macro_rules! js_body_call_unwind { + (@f64 $x:tt) => { f64 }; + ($code:expr, $callee:expr $(, $a:expr)* $(,)?) => {{ + #[cfg(panic = "abort")] + let f = $crate::closure::body_call::js_body_fn!($code; $($a),*); + #[cfg(not(panic = "abort"))] + let f = ::std::mem::transmute::< + *const u8, + extern "C-unwind" fn( + *const $crate::closure::ClosureHeader + $(, $crate::closure::body_call::js_body_call_unwind!(@f64 $a))* + ) -> f64, + >($code); + f($callee $(, $a)*) + }}; +} + +/// Reinterpret a METHOD body (`double m(double this, double a0, ...)`) as a +/// callable taking the receiver plus one `f64` per token: +/// `js_method_body_fn!(code; value)` is `extern "C" fn(f64, f64) -> f64`. +/// +/// # Safety +/// `code` must be a method body declaring at most the passed argument count. +macro_rules! js_method_body_fn { + (@f64 $x:tt) => { f64 }; + ($code:expr; $($x:tt),* $(,)?) => { + ::std::mem::transmute::< + *const u8, + extern "C" fn(f64 $(, $crate::closure::body_call::js_method_body_fn!(@f64 $x))*) -> f64, + >($code) + }; +} + +/// Call a METHOD body: `js_method_body_call!(code, this, a0, ...)`. +/// +/// # Safety +/// As [`js_method_body_fn!`]. +macro_rules! js_method_body_call { + ($code:expr, $this:expr $(, $a:expr)* $(,)?) => {{ + let f = $crate::closure::body_call::js_method_body_fn!($code; $($a),*); + f($this $(, $a)*) + }}; +} + +/// Reinterpret a BARE body (`double f(double a0, ...)`: no callee, no +/// implicit receiver) as a callable taking one `f64` per token. +/// +/// # Safety +/// `code` must be a bare body declaring at most the passed argument count. +macro_rules! js_bare_body_fn { + (@f64 $x:tt) => { f64 }; + ($code:expr; $($x:tt),* $(,)?) => { + ::std::mem::transmute::< + *const u8, + extern "C" fn($($crate::closure::body_call::js_bare_body_fn!(@f64 $x)),*) -> f64, + >($code) + }; +} + +pub(crate) use { + js_bare_body_fn, js_body_call, js_body_call_unwind, js_body_fn, js_body_fn_ty, + js_method_body_call, js_method_body_fn, +}; diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index c4988925fe..d600b61aa0 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -42,11 +42,7 @@ fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 0) => unsafe { dispatch_with_arity(closure, func_ptr, &[], declared) }, - _ => { - let func: extern "C" fn(*const ClosureHeader) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure) - } + _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure) }, } } @@ -87,11 +83,7 @@ fn dispatch_call1_resolved( DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 1) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0], declared) }, - _ => { - let func: extern "C" fn(*const ClosureHeader, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0) - } + _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, arg0) }, } } @@ -155,11 +147,7 @@ pub extern "C" fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 2) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0, arg1], declared) }, - _ => { - let func: extern "C" fn(*const ClosureHeader, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1) - } + _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1) }, } } @@ -186,11 +174,9 @@ pub extern "C" fn js_closure_call3( DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 3) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2], declared) }, - _ => { - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2) - } + _ => unsafe { + crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2) + }, } } @@ -226,11 +212,9 @@ pub extern "C" fn js_closure_call4( DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 4) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2, arg3], declared) }, - _ => { - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2, arg3) - } + _ => unsafe { + crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2, arg3) + }, } } @@ -272,9 +256,9 @@ pub extern "C" fn js_closure_call5( }; } } - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2, arg3, arg4) + unsafe { + crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2, arg3, arg4) + } } /// Call a closure with 6 arguments, returning f64 @@ -321,9 +305,11 @@ pub extern "C" fn js_closure_call6( }; } } - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2, arg3, arg4, arg5) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5 + ) + } } #[inline] @@ -386,9 +372,11 @@ pub extern "C" fn js_closure_call7( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 7) { return result; } - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6 + ) + } } /// Call a closure with 8 arguments, returning f64 @@ -419,9 +407,11 @@ pub extern "C" fn js_closure_call8( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 8) { return result; } - let func: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64, f64, f64) -> f64 = - unsafe { std::mem::transmute(func_ptr) }; - func(closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 + ) + } } /// Call a closure with 9 arguments, returning f64 @@ -453,21 +443,11 @@ pub extern "C" fn js_closure_call9( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 9) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 + ) + } } /// Call a closure with 10 arguments, returning f64 @@ -500,22 +480,11 @@ pub extern "C" fn js_closure_call10( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 10) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + ) + } } /// Call a closure with 11 arguments, returning f64 @@ -555,23 +524,11 @@ pub extern "C" fn js_closure_call11( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 11) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 + ) + } } /// Call a closure with 12 arguments, returning f64 @@ -612,24 +569,12 @@ pub extern "C" fn js_closure_call12( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 12) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11 + ) + } } /// Call a closure with 13 arguments, returning f64 @@ -671,25 +616,12 @@ pub extern "C" fn js_closure_call13( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 13) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12 + ) + } } /// Call a closure with 14 arguments, returning f64 @@ -733,27 +665,12 @@ pub extern "C" fn js_closure_call14( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 14) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13 + ) + } } /// Call a closure with 15 arguments, returning f64 @@ -800,28 +717,12 @@ pub extern "C" fn js_closure_call15( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 15) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, arg14, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13, arg14 + ) + } } /// Call a closure with 16 arguments, returning f64 @@ -869,29 +770,12 @@ pub extern "C" fn js_closure_call16( if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 16) { return result; } - let func: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = unsafe { std::mem::transmute(func_ptr) }; - func( - closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, arg14, arg15, - ) + unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + arg11, arg12, arg13, arg14, arg15 + ) + } } #[cfg(test)] @@ -921,3 +805,59 @@ mod receiverless_tests { crate::object::js_implicit_this_set(original); } } + +/// `perry_abi::JS_CLOSURE_CALL_ENTRIES` is what codegen DECLARES; these are +/// the functions it links to. Each entry must name the function taking +/// exactly its index's JS argument count (the coercion below fails to compile +/// otherwise), in order. +#[cfg(test)] +mod abi_table_tests { + use super::*; + + macro_rules! entry { + ($f:ident; $($x:tt),*) => {{ + let f: crate::closure::body_call::js_body_fn_ty!($($x),*) = $f; + (stringify!($f), f as *const u8) + }}; + } + + #[test] + fn closure_call_entries_match_the_abi_table() { + // `js_closure_call0` is `extern "C-unwind"` in unwinding (test) builds. + let call0 = { + let f: extern "C-unwind" fn(*const ClosureHeader) -> f64 = js_closure_call0; + ("js_closure_call0", f as *const u8) + }; + let real = [ + call0, + entry!(js_closure_call1; a), + entry!(js_closure_call2; a, a), + entry!(js_closure_call3; a, a, a), + entry!(js_closure_call4; a, a, a, a), + entry!(js_closure_call5; a, a, a, a, a), + entry!(js_closure_call6; a, a, a, a, a, a), + entry!(js_closure_call7; a, a, a, a, a, a, a), + entry!(js_closure_call8; a, a, a, a, a, a, a, a), + entry!(js_closure_call9; a, a, a, a, a, a, a, a, a), + entry!(js_closure_call10; a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call11; a, a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call12; a, a, a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call13; a, a, a, a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call14; a, a, a, a, a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call15; a, a, a, a, a, a, a, a, a, a, a, a, a, a, a), + entry!(js_closure_call16; a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a), + ]; + let table = crate::codegen_abi::JS_CLOSURE_CALL_ENTRIES; + assert_eq!(real.len(), table.len()); + for (argc, ((name, ptr), declared)) in real.iter().zip(table.iter()).enumerate() { + assert_eq!(name, declared, "JS_CLOSURE_CALL_ENTRIES[{argc}]"); + assert!(!ptr.is_null()); + } + for name in table { + assert!( + crate::codegen_abi::JS_CALL_ENTRIES.contains(&name), + "{name} is a JS-call entry but not in JS_CALL_ENTRIES" + ); + } + } +} diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index c434844351..31021ec649 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -157,17 +157,14 @@ macro_rules! define_direct_call_site { ) => { $(#[$meta])* #[derive(Clone, Copy)] - pub struct $site(extern "C" fn(*const ClosureHeader, $(define_direct_call_site!(@f64 $arg)),+) -> f64); + pub struct $site(crate::closure::body_call::js_body_fn_ty!($($arg),+)); impl $site { /// Resolve `closure` once, before the loop. #[inline] pub fn resolve(closure: *const ClosureHeader) -> Self { $site(resolve_direct_func_ptr(closure, $arity).map_or($slow, |func_ptr| unsafe { - std::mem::transmute::< - *const u8, - extern "C" fn(*const ClosureHeader, $(define_direct_call_site!(@f64 $arg)),+) -> f64, - >(func_ptr) + crate::closure::body_call::js_body_fn!(func_ptr; $($arg),+) })) } @@ -186,12 +183,11 @@ macro_rules! define_direct_call_site { pub(crate) fn is_direct(&self) -> bool { !std::ptr::fn_addr_eq( self.0, - $slow as extern "C" fn(*const ClosureHeader, $(define_direct_call_site!(@f64 $arg)),+) -> f64, + $slow as crate::closure::body_call::js_body_fn_ty!($($arg),+), ) } } }; - (@f64 $arg:ident) => { f64 }; } define_direct_call_site!( diff --git a/crates/perry-runtime/src/closure/dispatch/validate.rs b/crates/perry-runtime/src/closure/dispatch/validate.rs index aace5ac249..a81b67b436 100644 --- a/crates/perry-runtime/src/closure/dispatch/validate.rs +++ b/crates/perry-runtime/src/closure/dispatch/validate.rs @@ -141,8 +141,8 @@ pub fn get_valid_func_ptr(closure: *const ClosureHeader) -> *const u8 { /// checked`) — so a proxy callee arrives here as its bare registry id /// (`PROXY_ID_BAND_START + id`). The compiler emits a `ProxyApply` node only /// when it can statically prove the callee is a proxy; a proxy read out of a -/// dynamically-typed slot (`const g = obj.m` / `arr[0]` / `map.get(k)`, or a -/// method detached by `js_closure_unbind_this`) reaches this generic path with +/// dynamically-typed slot (`const g = obj.m` / `arr[0]` / `map.get(k)`) +/// reaches this generic path with /// no static hint, and `get_valid_func_ptr` correctly refuses to dereference the /// id (#5976/#6321) — which turned the SIGSEGV into a spurious TypeError. Node /// calls the proxy. Re-boxing the id and asking the proxy registry costs nothing diff --git a/crates/perry-runtime/src/closure/dynamic_props.rs b/crates/perry-runtime/src/closure/dynamic_props.rs index af3f8d8100..a07162789d 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -717,72 +717,6 @@ pub fn closure_dynamic_props_snapshot(ptr: usize) -> Vec<(String, f64)> { unsafe { super::props::bag_snapshot(ptr) } } -/// Unbind `this` from a detached method closure. -/// -/// When a method is read from an object via PropertyGet (e.g., `const fn = holder.getX`), -/// this function is called on the result. If the value is a closure whose capture_count -/// has CAPTURES_THIS_FLAG set (indicating slot 0 is `this`), it allocates a new closure -/// with the same func_ptr and captures but slot 0 set to undefined. -/// -/// For non-closure values (numbers, strings, objects, arrays), this is a no-op. -#[no_mangle] -pub extern "C" fn js_closure_unbind_this(val: f64) -> f64 { - let bits = val.to_bits(); - let tag = bits & 0xFFFF_0000_0000_0000; - // Only process POINTER_TAG values (closures are NaN-boxed with POINTER_TAG) - if tag != 0x7FFD_0000_0000_0000 { - return val; - } - let ptr = (bits & 0x0000_FFFF_FFFF_FFFF) as usize; - // #6320: the old `< 0x10000` floor is an order of magnitude below - // `HANDLE_BAND_MAX`, so a registry handle NaN-boxed under POINTER_TAG — most - // sharply a revocable-Proxy id at `0xF0000 + id` — passed it and the - // CLOSURE_MAGIC probe below dereferenced unmapped low memory. Detaching a - // proxy-valued method (`const g = obj.m` where `obj.m = new Proxy(fn, {})`) - // reaches exactly here. `is_closure_ptr` subsumes the band, heap-range, - // alignment and magic checks; a non-closure value has no `this` slot to - // unbind, so it flows through untouched. - if !is_closure_ptr(ptr) { - return val; - } - unsafe { - let header = ptr as *const ClosureHeader; - let raw_count = (*header).capture_count; - // Only unbind if the closure has the CAPTURES_THIS_FLAG - if raw_count & CAPTURES_THIS_FLAG == 0 { - return val; - } - let count = real_capture_count(raw_count) as usize; - if count == 0 { - return val; - } - // Clone the closure with slot 0 set to undefined - let scope = crate::gc::RuntimeHandleScope::new(); - let val_handle = scope.root_nanbox_f64(val); - let func_ptr = (*header).func_ptr; - let new_closure = js_closure_alloc(func_ptr, raw_count); - let source_bits = val_handle.get_nanbox_f64().to_bits(); - let source_ptr = (source_bits & 0x0000_FFFF_FFFF_FFFF) as usize; - if !closure_kind_probe(source_ptr) { - return val_handle.get_nanbox_f64(); - } - let src_captures = closure_capture_slots_mut(source_ptr as *mut ClosureHeader); - let dst_captures = closure_capture_slots_mut(new_closure); - // Set slot 0 to undefined - // GC_STORE_AUDIT(BARRIERED): cloned closure capture stores are followed by layout/barrier rebuild. - *dst_captures = crate::value::TAG_UNDEFINED; - // Copy remaining captures (slots 1..count) - for i in 1..count { - *dst_captures.add(i) = *src_captures.add(i); - } - rebuild_closure_layout_and_barriers(new_closure, count); - super::clone_closure_box_captures(source_ptr as *const ClosureHeader, new_closure); - // NaN-box the new closure pointer - let new_ptr = new_closure as u64; - f64::from_bits(0x7FFD_0000_0000_0000 | (new_ptr & 0x0000_FFFF_FFFF_FFFF)) - } -} - #[cfg(test)] mod tests_1802 { use super::*; diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index 7dac269eb0..aea174e750 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -6,6 +6,7 @@ //! - Followed by captured values (as f64 or i64 pointers) mod alloc; +pub(crate) mod body_call; mod box_captures; mod dispatch; mod dynamic_props; @@ -101,8 +102,7 @@ pub use dynamic_props::{ closure_delete_own_dynamic_prop, closure_dynamic_props_snapshot, closure_get_dynamic_prop, closure_get_own_dynamic_prop, closure_has_own_dynamic_prop, closure_is_key_deleted, closure_mark_key_deleted, closure_set_dynamic_prop, closure_set_static_prototype, - closure_static_prototype, is_closure_ptr, js_closure_unbind_this, - scan_closure_dynamic_props_roots_mut, + closure_static_prototype, is_closure_ptr, scan_closure_dynamic_props_roots_mut, }; // v8_stubs re-exports the AOT stubs + non-macOS Rust V8-interop stubs. diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index b2b702feed..2aa2589a2c 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -1180,229 +1180,31 @@ pub unsafe fn dispatch_rest_bundled( // Use a macro for higher rest arities so this stays in sync with the // documented 0..=15 support and the generated closure-call ceiling. + // One arm per fixed arity: the body takes `(callee, fixed..., rest + // [, arguments])`. Every arm goes through the body-call funnel. macro_rules! rest_arm { - (@ty $i:tt) => { - f64 - }; ($($i:tt),* $(,)?) => {{ if let Some(arguments_double) = all_arguments_double { - #[cfg(panic = "abort")] - let f: extern "C" fn(*const ClosureHeader $(, rest_arm!(@ty $i))*, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - #[cfg(not(panic = "abort"))] - let f: extern "C-unwind" fn(*const ClosureHeader $(, rest_arm!(@ty $i))*, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure $(, a!($i))*, rest_double, arguments_double) + crate::closure::body_call::js_body_call_unwind!( + func_ptr, closure $(, a!($i))*, rest_double, arguments_double + ) } else { - #[cfg(panic = "abort")] - let f: extern "C" fn(*const ClosureHeader $(, rest_arm!(@ty $i))*, f64) -> f64 = - std::mem::transmute(func_ptr); - #[cfg(not(panic = "abort"))] - let f: extern "C-unwind" fn(*const ClosureHeader $(, rest_arm!(@ty $i))*, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure $(, a!($i))*, rest_double) + crate::closure::body_call::js_body_call_unwind!( + func_ptr, closure $(, a!($i))*, rest_double + ) } }}; } match k { - 0 => { - if let Some(arguments_double) = all_arguments_double { - #[cfg(panic = "abort")] - let f: extern "C" fn(*const ClosureHeader, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - #[cfg(not(panic = "abort"))] - let f: extern "C-unwind" fn(*const ClosureHeader, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, rest_double, arguments_double) - } else { - #[cfg(panic = "abort")] - let f: extern "C" fn(*const ClosureHeader, f64) -> f64 = - std::mem::transmute(func_ptr); - #[cfg(not(panic = "abort"))] - let f: extern "C-unwind" fn(*const ClosureHeader, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, rest_double) - } - } - 1 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), rest_double, arguments_double) - } else { - let f: extern "C" fn(*const ClosureHeader, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), rest_double) - } - } - 2 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), rest_double, arguments_double) - } else { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), rest_double) - } - } - 3 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), a!(2), rest_double, arguments_double) - } else { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), a!(2), rest_double) - } - } - 4 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - rest_double, - arguments_double, - ) - } else { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), a!(2), a!(3), rest_double) - } - } - 5 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - a!(4), - rest_double, - arguments_double, - ) - } else { - let f: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - f(closure, a!(0), a!(1), a!(2), a!(3), a!(4), rest_double) - } - } - 6 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - a!(4), - a!(5), - rest_double, - arguments_double, - ) - } else { - let f: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - a!(4), - a!(5), - rest_double, - ) - } - } - 7 => { - if let Some(arguments_double) = all_arguments_double { - let f: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - a!(4), - a!(5), - a!(6), - rest_double, - arguments_double, - ) - } else { - let f: extern "C" fn( - *const ClosureHeader, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - f64, - ) -> f64 = std::mem::transmute(func_ptr); - f( - closure, - a!(0), - a!(1), - a!(2), - a!(3), - a!(4), - a!(5), - a!(6), - rest_double, - ) - } - } + 0 => rest_arm!(), + 1 => rest_arm!(0), + 2 => rest_arm!(0, 1), + 3 => rest_arm!(0, 1, 2), + 4 => rest_arm!(0, 1, 2, 3), + 5 => rest_arm!(0, 1, 2, 3, 4), + 6 => rest_arm!(0, 1, 2, 3, 4, 5), + 7 => rest_arm!(0, 1, 2, 3, 4, 5, 6), 8 => rest_arm!(0, 1, 2, 3, 4, 5, 6, 7), 9 => rest_arm!(0, 1, 2, 3, 4, 5, 6, 7, 8), 10 => rest_arm!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), @@ -1462,28 +1264,20 @@ pub unsafe fn dispatch_with_arity( } }; } - // One match arm per declared arity. Each arm transmutes `func_ptr` to - // the concrete `(closure, f64 x N)` signature and forwards the (padded) - // args. Arities up to 32 have exact arms so high-arity closures dispatched + // One match arm per declared arity. Each arm calls the body through the + // body-call funnel with exactly N (padded) JS arguments. Arities up to 32 have exact arms so high-arity closures dispatched // dynamically — e.g. qs's recursive `stringify`, which declares 18 // params and self-calls with 18 args (#3527) — call their body // correctly instead of mis-calling and corrupting registers; wider // bodies take the padded ladder in `wide_call` (#10420). The - // `arm!` macro builds the fn type and the (padded) call args from the - // arg-index token list; `arm!(@ty $i)` maps any index token to `f64`. + // `arm!` macro builds the (padded) call args from the arg-index token list. macro_rules! arm { - (@ty $i:tt) => { f64 }; - ($($i:tt),* $(,)?) => {{ - let f: extern "C" fn(*const ClosureHeader $(, arm!(@ty $i))*) -> f64 = - std::mem::transmute(func_ptr); - f(closure $(, a!($i))*) - }}; + ($($i:tt),* $(,)?) => { + crate::closure::body_call::js_body_call!(func_ptr, closure $(, a!($i))*) + }; } match k { - 0 => { - let f: extern "C" fn(*const ClosureHeader) -> f64 = std::mem::transmute(func_ptr); - f(closure) - } + 0 => arm!(), 1 => arm!(0), 2 => arm!(0, 1), 3 => arm!(0, 1, 2), @@ -1562,16 +1356,20 @@ pub const BOUND_METHOD_FUNC_PTR: *const u8 = 0xBADD_DEAD_u64 as *const u8; /// prepends the bound args, sets `IMPLICIT_THIS` to the bound receiver, and calls /// the target closure. /// -/// Captures layout: +/// Captures layout (`js_function_bind`, `dispatch/bound.rs`): /// [0] = target closure value (f64, NaN-boxed) /// [1] = bound `this` value (f64) /// [2] = bound-args JS Array pointer (i64; 0 when no partial args) +/// [3] = target name snapshot (for the `bound ` name) +/// [4] = `length` snapshot pub const BOUND_FUNCTION_FUNC_PTR: *const u8 = 0xBADD_B12D_u64 as *const u8; -/// Flag stored in the high bit of capture_count to indicate that capture slot 0 -/// holds `this` (i.e., this closure is an object literal method that captures `this`). -/// When the closure is detached from the object (assigned to a variable via PropertyGet), -/// `js_closure_unbind_this` clones it and clears slot 0 so `this` becomes undefined. +/// 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 +/// by `lower_object_literal`). `clone_closure_rebind_this` rewrites that slot +/// for `call`/`apply`/borrowed-method dispatch unless `NO_THIS_REBIND_FLAG` is +/// also set. pub const CAPTURES_THIS_FLAG: u32 = 0x8000_0000; /// Flag stored in bit 30 of `capture_count` marking a closure whose captured diff --git a/crates/perry-runtime/src/closure/shape.rs b/crates/perry-runtime/src/closure/shape.rs index 6413f116b9..59b47c254c 100644 --- a/crates/perry-runtime/src/closure/shape.rs +++ b/crates/perry-runtime/src/closure/shape.rs @@ -433,7 +433,7 @@ mod tests { shapes::shape_object_kind_by_id(unsafe { (*c).shape_id }) } - fn fresh(body: extern "C" fn(*const ClosureHeader) -> f64) -> *mut ClosureHeader { + fn fresh(body: crate::closure::body_call::js_body_fn_ty!()) -> *mut ClosureHeader { js_closure_alloc(body as *const u8, 0) } diff --git a/crates/perry-runtime/src/closure/wide_call.rs b/crates/perry-runtime/src/closure/wide_call.rs index 8104e8001d..9433c2bf02 100644 --- a/crates/perry-runtime/src/closure/wide_call.rs +++ b/crates/perry-runtime/src/closure/wide_call.rs @@ -56,22 +56,15 @@ pub(crate) unsafe fn dispatch_wide_abi( let provided = args.len().min(width); // `padded!(slots, [0], 1, + + …)` doubles the index list once per `+` - // (six doublings = 64 indices) and emits the transmuted call with one - // `f64` parameter per index. The indices are constant expressions into a + // (six doublings = 64 indices) and emits the body call with one + // `f64` argument per index. The indices are constant expressions into a // fixed-size array, so the loads carry no bounds checks. macro_rules! padded { - (@f64 $i:expr) => { f64 }; ($slots:ident, [$($i:expr),+], $step:expr, + $($more:tt)*) => { padded!($slots, [$($i,)+ $($i + $step),+], $step * 2, $($more)*) }; ($slots:ident, [$($i:expr),+], $step:expr,) => {{ - #[cfg(panic = "abort")] - let f: extern "C" fn(*const ClosureHeader $(, padded!(@f64 $i))+) -> f64 = - std::mem::transmute(func_ptr); - #[cfg(not(panic = "abort"))] - let f: extern "C-unwind" fn(*const ClosureHeader $(, padded!(@f64 $i))+) -> f64 = - std::mem::transmute(func_ptr); - f(closure $(, $slots[$i])+) + crate::closure::body_call::js_body_call_unwind!(func_ptr, closure $(, $slots[$i])+) }}; } macro_rules! fill { diff --git a/crates/perry-runtime/src/dgram.rs b/crates/perry-runtime/src/dgram.rs index de64128961..2e3d21f73d 100644 --- a/crates/perry-runtime/src/dgram.rs +++ b/crates/perry-runtime/src/dgram.rs @@ -112,7 +112,7 @@ pub(crate) const KEY_REACTOR_ID: &[u8] = b"__perryDgramReactorId"; pub(crate) const KEY_ASYNC_ID: &[u8] = b"__perryDgramAsyncId"; pub(crate) const KEY_TRIGGER_ASYNC_ID: &[u8] = b"__perryDgramTriggerAsyncId"; -type MethodThunk = extern "C" fn(*const ClosureHeader, f64) -> f64; +type MethodThunk = crate::closure::body_call::js_body_fn_ty!(a); struct MethodSpec { name: &'static str, diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index d68d5143cf..99198bf612 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -200,7 +200,7 @@ extern "C" fn event_stop_immediate_propagation_thunk( fn install_event_method( event: *mut ObjectHeader, name: &str, - func: extern "C" fn(*const crate::closure::ClosureHeader) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(), ) { let func_ptr = func as *const u8; let closure = crate::closure::js_closure_alloc(func_ptr, 0); 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 68e127ee70..0eafaa264f 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -231,7 +231,7 @@ fn signal_abort_reason(signal: f64) -> f64 { fn add_abort_listener( signal: f64, id: usize, - func: extern "C" fn(*const ClosureHeader) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(), ) -> f64 { let Some(signal_ptr) = crate::url::abort::abort_signal_ptr_from_value(signal) else { return undefined_value(); diff --git a/crates/perry-runtime/src/fs/filehandle.rs b/crates/perry-runtime/src/fs/filehandle.rs index d01cef4e87..9e928540f8 100644 --- a/crates/perry-runtime/src/fs/filehandle.rs +++ b/crates/perry-runtime/src/fs/filehandle.rs @@ -470,6 +470,7 @@ pub(crate) extern "C" fn filehandle_readable_web_stream_impl( auto_close, filehandle_webstream_cancel_impl as *const u8, ); + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let factory: ReadableWebStreamFactory = unsafe { std::mem::transmute(raw) }; unsafe { factory(pull, cancel) } } diff --git a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs index f1c4ee5fc5..b9730a46b3 100644 --- a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs +++ b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs @@ -56,7 +56,7 @@ extern "C" fn mkdtemp_disposable_async_remove_impl(closure: *const ClosureHeader fn mkdtemp_disposable_method( path_value: f64, - func: extern "C" fn(*const ClosureHeader) -> 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); @@ -80,9 +80,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 extern "C" fn(*const ClosureHeader) -> f64 + mkdtemp_disposable_async_remove_impl as crate::closure::body_call::js_body_fn_ty!() } else { - mkdtemp_disposable_remove_impl as extern "C" fn(*const ClosureHeader) -> f64 + mkdtemp_disposable_remove_impl as crate::closure::body_call::js_body_fn_ty!() }; 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/stream.rs b/crates/perry-runtime/src/fs/stream.rs index 0ed1721d77..28c6143479 100644 --- a/crates/perry-runtime/src/fs/stream.rs +++ b/crates/perry-runtime/src/fs/stream.rs @@ -1647,50 +1647,44 @@ fn create_write_stream_with_state(state: StreamState) -> f64 { let id = alloc_stream(state); let method_funcs: [(&str, extern "C" fn()); 8] = [ ("write", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64, - extern "C" fn(), - >(write_stream_write_impl) + std::mem::transmute::( + write_stream_write_impl, + ) }), ("end", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64, - extern "C" fn(), - >(write_stream_end_impl) + std::mem::transmute::( + write_stream_end_impl, + ) }), ("on", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(write_stream_on_impl) + std::mem::transmute::( + write_stream_on_impl, + ) }), ("once", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(write_stream_once_impl) + std::mem::transmute::( + write_stream_once_impl, + ) }), ("addListener", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(write_stream_on_impl) + std::mem::transmute::( + write_stream_on_impl, + ) }), ("close", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( write_stream_close_impl, ) }), ("destroy", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( write_stream_close_impl, ) }), ("emit", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(stream_emit_impl) + std::mem::transmute::( + stream_emit_impl, + ) }), ]; let obj = build_stream_object(id, CLASS_ID_FS_WRITE_STREAM, &method_funcs); @@ -1720,59 +1714,54 @@ fn create_read_stream_with_state(state: StreamState) -> f64 { store_open_failure(id); let method_funcs: [(&str, extern "C" fn()); 10] = [ ("on", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(read_stream_on_impl) + std::mem::transmute::( + read_stream_on_impl, + ) }), ("once", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(read_stream_once_impl) + std::mem::transmute::( + read_stream_once_impl, + ) }), ("addListener", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(read_stream_on_impl) + std::mem::transmute::( + read_stream_on_impl, + ) }), ("pipe", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(read_stream_pipe_impl) + std::mem::transmute::( + read_stream_pipe_impl, + ) }), ("pause", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( read_stream_pause_impl, ) }), ("resume", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( read_stream_resume_impl, ) }), ("isPaused", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( read_stream_is_paused_impl, ) }), ("close", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( read_stream_close_impl, ) }), ("destroy", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( read_stream_close_impl, ) }), ("emit", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(stream_emit_impl) + std::mem::transmute::( + stream_emit_impl, + ) }), ]; let obj = build_stream_object(id, CLASS_ID_FS_READ_STREAM, &method_funcs); @@ -1819,88 +1808,82 @@ fn create_utf8_stream_with_state(state: Utf8StreamState) -> f64 { let id = alloc_utf8_stream(state); let method_funcs: [(&str, extern "C" fn()); 16] = [ ("write", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_write_impl, ) }), ("flush", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_flush_impl, ) }), ("flushSync", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_flush_sync_impl, ) }), ("end", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_end_impl, ) }), ("destroy", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_destroy_impl, ) }), ("reopen", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_reopen_impl, ) }), ("on", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_on_impl) + std::mem::transmute::( + utf8_stream_on_impl, + ) }), ("once", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_once_impl) + std::mem::transmute::( + utf8_stream_once_impl, + ) }), ("addListener", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_on_impl) + std::mem::transmute::( + utf8_stream_on_impl, + ) }), ("off", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_off_impl) + std::mem::transmute::( + utf8_stream_off_impl, + ) }), ("removeListener", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_off_impl) + std::mem::transmute::( + utf8_stream_off_impl, + ) }), ("removeAllListeners", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_remove_all_impl, ) }), ("listenerCount", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_listener_count_impl, ) }), ("emit", unsafe { - std::mem::transmute::< - extern "C" fn(*const ClosureHeader, f64, f64) -> f64, - extern "C" fn(), - >(utf8_stream_emit_impl) + std::mem::transmute::( + utf8_stream_emit_impl, + ) }), ("close", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_destroy_impl, ) }), ("@@__perry_wk_dispose", unsafe { - std::mem::transmute:: f64, extern "C" fn()>( + std::mem::transmute::( utf8_stream_destroy_impl, ) }), diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index cb96a7af71..5578a87d49 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -132,13 +132,13 @@ extern "C" fn add(_c: *const crate::closure::ClosureHeader, a: f64, b: f64) -> f f64::from_bits(JSValue::number(sum).bits()) } -unsafe fn closure1(f: extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64) -> f64 { +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: extern "C" fn(*const crate::closure::ClosureHeader, f64, f64) -> f64) -> f64 { +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) diff --git a/crates/perry-runtime/src/lib.rs b/crates/perry-runtime/src/lib.rs index 9a51e9ea40..fadafdf6a5 100644 --- a/crates/perry-runtime/src/lib.rs +++ b/crates/perry-runtime/src/lib.rs @@ -757,6 +757,7 @@ pub(crate) mod stdlib_pump { } // SAFETY: `js_register_stdlib_next_wake` only stores callbacks with // this exact ABI and signature. + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: extern "C" fn() -> f64 = unsafe { std::mem::transmute(f) }; func() } diff --git a/crates/perry-runtime/src/node_stream/async_iterator.rs b/crates/perry-runtime/src/node_stream/async_iterator.rs index 2160a0a1e7..d46ac575a1 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator.rs @@ -704,7 +704,7 @@ pub(super) extern "C" fn ns_iterator1(closure: *const ClosureHeader, opts: f64) build_readable_async_iterator(this_value(closure), destroy_on_return_from_options(opts)) } -fn install_async_iterator_symbol(target: f64, func: extern "C" fn(*const ClosureHeader) -> f64) { +fn install_async_iterator_symbol(target: f64, func: crate::closure::body_call::js_body_fn_ty!()) { let scope = crate::gc::RuntimeHandleScope::new(); let target = scope.root_nanbox_f64(target); let async_iterator = crate::symbol::well_known_symbol("asyncIterator"); diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index bb883745b1..0bf15920e9 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -12,19 +12,19 @@ use crate::value::JSValue; pub(crate) type StubFn = unsafe extern "C" fn(); #[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast0(f: extern "C" fn(*const ClosureHeader) -> f64) -> StubFn { +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: extern "C" fn(*const ClosureHeader, f64) -> f64) -> StubFn { +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: extern "C" fn(*const ClosureHeader, f64, f64) -> f64) -> StubFn { +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: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64) -> StubFn { +pub(super) fn cast3(f: crate::closure::body_call::js_body_fn_ty!(a, a, a)) -> StubFn { unsafe { std::mem::transmute(f) } } diff --git a/crates/perry-runtime/src/node_submodules/consumers.rs b/crates/perry-runtime/src/node_submodules/consumers.rs index 33a7f0dcea..ad44e03cd7 100644 --- a/crates/perry-runtime/src/node_submodules/consumers.rs +++ b/crates/perry-runtime/src/node_submodules/consumers.rs @@ -427,6 +427,7 @@ fn registered_readable_stream_reader(stream: f64) -> Option { return None; } let reader = unsafe { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: StreamGetReaderFn = std::mem::transmute(f); func(stream) }; diff --git a/crates/perry-runtime/src/node_submodules/diagnostics.rs b/crates/perry-runtime/src/node_submodules/diagnostics.rs index 5da13e6024..f293c558f9 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics.rs @@ -813,25 +813,23 @@ pub(crate) fn get_field_value(obj: *mut ObjectHeader, name: &str) -> f64 { } #[allow(clippy::missing_transmute_annotations)] -pub(crate) fn cast0(f: extern "C" fn(*const ClosureHeader) -> f64) -> *const u8 { +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: extern "C" fn(*const ClosureHeader, f64) -> f64) -> *const u8 { +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: extern "C" fn(*const ClosureHeader, f64, f64) -> f64) -> *const u8 { +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: extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64) -> *const u8 { +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: extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64, -) -> *const u8 { +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 { diff --git a/crates/perry-runtime/src/node_submodules/fs_promises.rs b/crates/perry-runtime/src/node_submodules/fs_promises.rs index 96da1cd208..ccf79ce01f 100644 --- a/crates/perry-runtime/src/node_submodules/fs_promises.rs +++ b/crates/perry-runtime/src/node_submodules/fs_promises.rs @@ -599,7 +599,7 @@ fn promise_ptr_from_value(value: f64) -> Option<*mut crate::promise::Promise> { } fn readline_bound_method0( - func: extern "C" fn(*const ClosureHeader) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(), this_value: f64, ) -> f64 { js_register_closure_arity(func as *const u8, 0); @@ -609,7 +609,7 @@ fn readline_bound_method0( } fn readline_bound_method1( - func: extern "C" fn(*const ClosureHeader, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a), this_value: f64, ) -> f64 { js_register_closure_arity(func as *const u8, 1); @@ -619,7 +619,7 @@ fn readline_bound_method1( } fn readline_bound_method2( - func: extern "C" fn(*const ClosureHeader, f64, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a, a), this_value: f64, ) -> f64 { js_register_closure_arity(func as *const u8, 2); diff --git a/crates/perry-runtime/src/node_submodules/hono_jsx.rs b/crates/perry-runtime/src/node_submodules/hono_jsx.rs index 3991b95b17..20c52a96cd 100644 --- a/crates/perry-runtime/src/node_submodules/hono_jsx.rs +++ b/crates/perry-runtime/src/node_submodules/hono_jsx.rs @@ -105,6 +105,7 @@ pub(crate) extern "C" fn thunk_hono_render_to_readable_stream( // value is at least usable (degraded vs. a real ReadableStream). return html_value; } + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let f: RenderStreamFn = unsafe { std::mem::transmute(raw) }; unsafe { f(html_value) } } diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 92561cd327..09abf6d87a 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -48,9 +48,9 @@ struct ExportSpec { } enum ExportThunk { - Fn1(extern "C" fn(*const ClosureHeader, f64) -> f64), - Fn2(extern "C" fn(*const ClosureHeader, f64, f64) -> f64), - Fn3(extern "C" fn(*const ClosureHeader, f64, f64, f64) -> f64), + 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 { diff --git a/crates/perry-runtime/src/node_vm.rs b/crates/perry-runtime/src/node_vm.rs index c5f871b535..edfeb0c332 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -1293,7 +1293,7 @@ fn script_metadata(script_value: f64) -> Option { fn install_script_method( obj: *mut ObjectHeader, name: &str, - func: extern "C" fn(*const ClosureHeader, f64, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a, a), arity: u32, ) { let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index 9d27ad49fd..2edf414b53 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -170,7 +170,7 @@ extern "C" fn module_error_getter(closure: *const ClosureHeader) -> f64 { fn install_module_accessor( module: *mut ObjectHeader, name: &str, - getter: extern "C" fn(*const ClosureHeader) -> f64, + getter: crate::closure::body_call::js_body_fn_ty!(), ) { let scope = crate::gc::RuntimeHandleScope::new(); let module = scope.root_raw_mut_ptr(module); diff --git a/crates/perry-runtime/src/object/async_generator_queue.rs b/crates/perry-runtime/src/object/async_generator_queue.rs index 16c8b30a67..982a941b83 100644 --- a/crates/perry-runtime/src/object/async_generator_queue.rs +++ b/crates/perry-runtime/src/object/async_generator_queue.rs @@ -120,7 +120,7 @@ pub(crate) fn wrap_async_generator_instance(obj: *mut ObjectHeader) { ( b"next".as_slice(), &next_h, - async_generator_next_wrapper as extern "C" fn(*const ClosureHeader, f64) -> f64, + 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), 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 b126b10078..38cbfc4383 100644 --- a/crates/perry-runtime/src/object/class_registry/decl_accessors.rs +++ b/crates/perry-runtime/src/object/class_registry/decl_accessors.rs @@ -180,7 +180,7 @@ pub(crate) unsafe fn class_chain_getter_value( let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this.get_nanbox_f64())); let _boundary = crate::object::prototype_chain::UserCodeResolutionBoundary::enter(); - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(acc.raw_get); + let f = crate::closure::body_call::js_method_body_fn!(acc.raw_get as *const u8;); let v = f(this.get_nanbox_f64()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return Some((crate::JSValue::from_bits(v.to_bits()), acc.raw_get)); @@ -217,7 +217,7 @@ pub(crate) unsafe fn class_chain_setter_apply( if acc.raw_set != 0 { let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_h.get_nanbox_f64())); - let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(acc.raw_set); + let f = crate::closure::body_call::js_method_body_fn!(acc.raw_set as *const u8; value); let _ = f(this_h.get_nanbox_f64(), value_h.get_nanbox_f64()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return Some(true); 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 73ade58983..7e26e4d905 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static.rs @@ -1053,13 +1053,13 @@ pub(crate) unsafe fn class_symbol_getter_value( let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::object::static_private_owner_push(receiver); - let f: extern "C" fn() -> f64 = std::mem::transmute(getter); + let f = crate::closure::body_call::js_bare_body_fn!(getter as *const u8;); let result = f(); crate::object::static_private_owner_pop(); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } else { - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(getter); + let f = crate::closure::body_call::js_method_body_fn!(getter as *const u8;); f(receiver) }; return Some(result); @@ -1101,12 +1101,14 @@ pub(crate) unsafe fn class_symbol_setter_apply( let prev_this = this_scope .root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::object::static_private_owner_push(receiver); - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(setter); + let f = + crate::closure::body_call::js_bare_body_fn!(setter as *const u8; a0); let _ = f(value); crate::object::static_private_owner_pop(); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } else { - let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(setter); + let f = + crate::closure::body_call::js_method_body_fn!(setter as *const u8; a0); let _ = f(receiver, value); } } @@ -1174,7 +1176,7 @@ pub(crate) unsafe fn class_static_accessor_getter_value( // `this`, not the shared compile-time ClassRef. crate::object::static_this_arm_if_unarmed(receiver); crate::object::static_private_owner_push(owner); - let f: extern "C" fn() -> f64 = std::mem::transmute(getter); + let f = crate::closure::body_call::js_bare_body_fn!(getter as *const u8;); let result = f(); crate::object::static_private_owner_pop(); crate::object::static_this_disarm(); @@ -1216,7 +1218,7 @@ pub(crate) unsafe fn class_static_accessor_setter_apply( // actual constructor value for this evaluation. crate::object::static_this_arm_if_unarmed(receiver); crate::object::static_private_owner_push(receiver); - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(setter); + let f = crate::closure::body_call::js_bare_body_fn!(setter as *const u8; a0); let _ = f(value); crate::object::static_private_owner_pop(); crate::object::static_this_disarm(); @@ -1420,27 +1422,27 @@ pub(crate) unsafe fn call_static_method( } } match param_count { - 0 => (std::mem::transmute:: f64>(func_ptr))(), - 1 => (std::mem::transmute:: f64>(func_ptr))(a( + 0 => (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8;))(), + 1 => (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0))(a( args_ptr, args_len, 0, )), - 2 => (std::mem::transmute:: f64>(func_ptr))( + 2 => (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1))( a(args_ptr, args_len, 0), a(args_ptr, args_len, 1), ), - 3 => (std::mem::transmute:: f64>(func_ptr))( + 3 => (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2))( a(args_ptr, args_len, 0), a(args_ptr, args_len, 1), a(args_ptr, args_len, 2), ), - 4 => (std::mem::transmute:: f64>(func_ptr))( + 4 => (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2, a3))( a(args_ptr, args_len, 0), a(args_ptr, args_len, 1), a(args_ptr, args_len, 2), a(args_ptr, args_len, 3), ), 5 => { - (std::mem::transmute:: f64>(func_ptr))( + (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2, a3, a4))( a(args_ptr, args_len, 0), a(args_ptr, args_len, 1), a(args_ptr, args_len, 2), @@ -1448,20 +1450,18 @@ pub(crate) unsafe fn call_static_method( a(args_ptr, args_len, 4), ) } - 6 => (std::mem::transmute:: f64>( - func_ptr, - ))( - a(args_ptr, args_len, 0), - a(args_ptr, args_len, 1), - a(args_ptr, args_len, 2), - a(args_ptr, args_len, 3), - a(args_ptr, args_len, 4), - a(args_ptr, args_len, 5), - ), + 6 => { + (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2, a3, a4, a5))( + a(args_ptr, args_len, 0), + a(args_ptr, args_len, 1), + a(args_ptr, args_len, 2), + a(args_ptr, args_len, 3), + a(args_ptr, args_len, 4), + a(args_ptr, args_len, 5), + ) + } 7 => { - (std::mem::transmute:: f64>( - func_ptr, - ))( + (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2, a3, a4, a5, a6))( a(args_ptr, args_len, 0), a(args_ptr, args_len, 1), a(args_ptr, args_len, 2), @@ -1471,19 +1471,18 @@ pub(crate) unsafe fn call_static_method( a(args_ptr, args_len, 6), ) } - _ => (std::mem::transmute::< - usize, - extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64) -> f64, - >(func_ptr))( - a(args_ptr, args_len, 0), - a(args_ptr, args_len, 1), - a(args_ptr, args_len, 2), - a(args_ptr, args_len, 3), - a(args_ptr, args_len, 4), - a(args_ptr, args_len, 5), - a(args_ptr, args_len, 6), - a(args_ptr, args_len, 7), - ), + _ => { + (crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0, a1, a2, a3, a4, a5, a6, a7))( + a(args_ptr, args_len, 0), + a(args_ptr, args_len, 1), + a(args_ptr, args_len, 2), + a(args_ptr, args_len, 3), + a(args_ptr, args_len, 4), + a(args_ptr, args_len, 5), + a(args_ptr, args_len, 6), + a(args_ptr, args_len, 7), + ) + } } } diff --git a/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs b/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs index 41a481238e..d83c907494 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs @@ -260,7 +260,7 @@ pub(crate) unsafe fn class_private_instance_getter_value( if getter == 0 { return None; } - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(getter); + let f = crate::closure::body_call::js_method_body_fn!(getter as *const u8;); Some(f(receiver)) } @@ -278,7 +278,7 @@ pub(crate) unsafe fn class_private_instance_setter_apply( if decl.set == 0 { return false; } - let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(decl.set); + let f = crate::closure::body_call::js_method_body_fn!(decl.set as *const u8; value); let _ = f(receiver, value); true } diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index 41685eed3a..6c6cd60d86 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -212,7 +212,7 @@ extern "C" fn class_accessor_getter_thunk(closure: *const crate::closure::Closur return f64::from_bits(crate::value::TAG_UNDEFINED); } let this = crate::object::js_implicit_this_get(); - let f: extern "C" fn(f64) -> f64 = unsafe { std::mem::transmute(raw) }; + let f = unsafe { crate::closure::body_call::js_method_body_fn!(raw as *const u8;) }; f(this) } @@ -226,7 +226,7 @@ extern "C" fn class_accessor_setter_thunk( return f64::from_bits(crate::value::TAG_UNDEFINED); } let this = crate::object::js_implicit_this_get(); - let f: extern "C" fn(f64, f64) -> f64 = unsafe { std::mem::transmute(raw) }; + let f = unsafe { crate::closure::body_call::js_method_body_fn!(raw as *const u8; a0) }; f(this, value) } diff --git a/crates/perry-runtime/src/object/global_fetch.rs b/crates/perry-runtime/src/object/global_fetch.rs index f741efbfb5..84f95a2c3d 100644 --- a/crates/perry-runtime/src/object/global_fetch.rs +++ b/crates/perry-runtime/src/object/global_fetch.rs @@ -393,6 +393,7 @@ pub(super) fn call_global_blob_new(parts: f64, type_value: f64) -> f64 { pub(super) fn call_global_blob_new(parts: f64, type_value: f64) -> f64 { let f = GLOBAL_FETCH_BLOB_NEW.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: FetchBlobNewFn = unsafe { std::mem::transmute(f) }; return unsafe { func(parts, type_value) }; } @@ -418,6 +419,7 @@ pub(super) fn call_global_file_new( ) -> f64 { let f = GLOBAL_FETCH_FILE_NEW.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: FetchFileNewFn = unsafe { std::mem::transmute(f) }; return unsafe { func(parts, name, type_value, last_modified) }; } @@ -433,6 +435,7 @@ pub(super) fn call_global_headers_new() -> f64 { pub(super) fn call_global_headers_new() -> f64 { let f = GLOBAL_FETCH_HEADERS_NEW.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: FetchHeadersNewFn = unsafe { std::mem::transmute(f) }; return func(); } @@ -448,6 +451,7 @@ pub(super) fn call_global_headers_init_from_value(handle: f64, init: f64) -> f64 pub(super) fn call_global_headers_init_from_value(handle: f64, init: f64) -> f64 { let f = GLOBAL_FETCH_HEADERS_INIT_FROM_VALUE.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: FetchHeadersInitFromValueFn = unsafe { std::mem::transmute(f) }; return unsafe { func(handle, init) }; } @@ -573,6 +577,7 @@ pub(super) fn call_global_request_new(input: f64, init: f64) -> f64 { pub(super) fn call_global_request_new(input: f64, init: f64) -> f64 { let f = GLOBAL_FETCH_REQUEST_NEW.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: perry-ext-fetch registers its Rust `Request` constructor. let func: FetchRequestNewFn = unsafe { std::mem::transmute(f) }; return unsafe { func(input, init) }; } @@ -659,6 +664,7 @@ pub(super) fn call_global_response_static_error() -> f64 { pub(super) fn call_global_response_static_error() -> f64 { let f = GLOBAL_FETCH_RESPONSE_STATIC_ERROR.load(Ordering::Acquire); if !f.is_null() { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: FetchResponseStaticErrorFn = unsafe { std::mem::transmute(f) }; return func(); } 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 0d21242c22..40bade6aac 100644 --- a/crates/perry-runtime/src/object/global_this/proto_methods.rs +++ b/crates/perry-runtime/src/object/global_this/proto_methods.rs @@ -466,7 +466,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: ); // Iteration / search methods: real generic-engine thunks (rest // shape — spec `.length` recorded separately below). - type RestThunk = extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64; + 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), diff --git a/crates/perry-runtime/src/object/inherited_read_cache.rs b/crates/perry-runtime/src/object/inherited_read_cache.rs index 76d0e63752..db22e9e538 100644 --- a/crates/perry-runtime/src/object/inherited_read_cache.rs +++ b/crates/perry-runtime/src/object/inherited_read_cache.rs @@ -632,7 +632,7 @@ unsafe fn accessor_hit(obj: *const ObjectHeader, pair_bits: u64) -> Lookup { if crate::object::prototype_chain::resolution_stack_savepoint() != 0 { return Lookup::Unknown; } - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(acc.raw_get); + let f = crate::closure::body_call::js_method_body_fn!(acc.raw_get as *const u8;); return Lookup::Hit(JSValue::from_bits(f(this).to_bits())); } if acc.get != 0 { @@ -739,7 +739,7 @@ unsafe fn accessor_set(obj: *const ObjectHeader, pair_bits: u64, value: f64) -> // A compiled class setter is called directly with the receiver as its // `this` parameter, exactly as the class-setter arm of the generic // `[[Set]]` calls it (that arm opens no resolution boundary either). - let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(acc.raw_set); + let f = crate::closure::body_call::js_method_body_fn!(acc.raw_set as *const u8; value); let _ = f(this, value); return true; } diff --git a/crates/perry-runtime/src/object/instanceof/static_dispatch.rs b/crates/perry-runtime/src/object/instanceof/static_dispatch.rs index 96b7360886..bc6fe0b3b2 100644 --- a/crates/perry-runtime/src/object/instanceof/static_dispatch.rs +++ b/crates/perry-runtime/src/object/instanceof/static_dispatch.rs @@ -66,7 +66,8 @@ pub extern "C" fn js_instanceof(value: f64, class_id: u32) -> f64 { // function `__perry_wk_hasinstance_` and the LLVM backend registers a // pointer to it against the class id at module init. if let Some(func_ptr) = lookup_has_instance_hook(class_id) { - let hook: extern "C" fn(f64) -> f64 = unsafe { std::mem::transmute(func_ptr as *const u8) }; + let hook = + unsafe { crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0) }; let result = hook(value); // Normalize: any truthy NaN-boxed bool stays as the TAG_TRUE/FALSE // sentinel. User-written `return typeof v === "number" && ...` diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index 6c197b35a1..5b59c9b17c 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -404,7 +404,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: extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64, + next_thunk: crate::closure::body_call::js_body_fn_ty!(a), tag: &str, shared: *mut ObjectHeader, ) -> *mut ObjectHeader { diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index a544a6a808..c01e1ec0cd 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -439,6 +439,7 @@ pub(crate) fn call_worker_threads_getter( if ptr.is_null() { return fallback(); } + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let getter: WorkerThreadsValueGetter = unsafe { std::mem::transmute(ptr) }; getter() } diff --git a/crates/perry-runtime/src/object/property_key.rs b/crates/perry-runtime/src/object/property_key.rs index 1c71c8da4c..1f96d52d19 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -372,7 +372,9 @@ pub unsafe extern "C" fn js_super_accessor_get( reg.get(&cid).and_then(|m| m.get(key_name)).map(|&(g, _)| g) { if getter_ptr != 0 { - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(getter_ptr); + // A static getter is a BARE body declaring no parameters; the + // receiver passed below is ignored by it (over-application is safe). + let f = crate::closure::body_call::js_bare_body_fn!(getter_ptr as *const u8; a0); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope .root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); diff --git a/crates/perry-runtime/src/object/to_string_tag.rs b/crates/perry-runtime/src/object/to_string_tag.rs index 16b18a1df9..3052f57ac9 100644 --- a/crates/perry-runtime/src/object/to_string_tag.rs +++ b/crates/perry-runtime/src/object/to_string_tag.rs @@ -482,7 +482,7 @@ pub unsafe extern "C" fn js_object_to_string(value: f64) -> f64 { tag_str = Some("Error".to_string()); } if let Some(func_ptr) = lookup_to_string_tag_hook(class_id) { - let getter: extern "C" fn(f64) -> f64 = std::mem::transmute(func_ptr as *const u8); + let getter = crate::closure::body_call::js_bare_body_fn!(func_ptr as *const u8; a0); let result_f64 = getter(value); let rbits = result_f64.to_bits(); if (rbits & 0xFFFF_0000_0000_0000) == STRING_TAG { diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index 6cedbe5cdb..fdf6dece4f 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -1673,7 +1673,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: extern "C" fn(*const crate::closure::ClosureHeader, f64, f64, f64) -> f64, + write_stub: crate::closure::body_call::js_body_fn_ty!(a, a, a), fd: f64, writable: f64, ) -> *mut crate::object::ObjectHeader { @@ -1833,13 +1833,11 @@ 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: extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64| { - let c = js_closure_alloc(stub as *const u8, 0); - js_object_set_field(obj, idx, JSValue::pointer(c as *const u8)); - }; - let lifecycle: extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64 = if is_stdin - { + 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); + 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 } else { process_stream_on_once_stub diff --git a/crates/perry-runtime/src/process.rs b/crates/perry-runtime/src/process.rs index b6e2027528..ad1c06e3bf 100644 --- a/crates/perry-runtime/src/process.rs +++ b/crates/perry-runtime/src/process.rs @@ -299,9 +299,8 @@ 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 = extern "C" fn(*const crate::closure::ClosureHeader, f64) -> f64; -pub(crate) type ModuleFunction2 = - extern "C" fn(*const crate::closure::ClosureHeader, f64, f64) -> f64; +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 { diff --git a/crates/perry-runtime/src/process/ipc.rs b/crates/perry-runtime/src/process/ipc.rs index 5b278d0f18..4d0d8a661e 100644 --- a/crates/perry-runtime/src/process/ipc.rs +++ b/crates/perry-runtime/src/process/ipc.rs @@ -98,8 +98,8 @@ 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 = extern "C" fn(*const ClosureHeader) -> f64; -type IpcFunction4 = extern "C" fn(*const ClosureHeader, f64, f64, f64, f64) -> f64; +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; diff --git a/crates/perry-runtime/src/process/node_module.rs b/crates/perry-runtime/src/process/node_module.rs index 85c34f039d..6a87ae504a 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -985,7 +985,7 @@ extern "C" fn module_loader_next_load( fn module_loader_callback( slot: &'static std::thread::LocalKey>, name: &str, - func: extern "C" fn(*const crate::closure::ClosureHeader, f64, f64) -> f64, + func: crate::closure::body_call::js_body_fn_ty!(a, a), ) -> f64 { let ptr = slot.with(|cell| { let existing = cell.get(); 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 4bc09d197d..1c15add93b 100644 --- a/crates/perry-runtime/src/process/node_module/source_map.rs +++ b/crates/perry-runtime/src/process/node_module/source_map.rs @@ -85,7 +85,7 @@ 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 = extern "C" fn(*const ClosureHeader, f64) -> f64; +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; @@ -128,7 +128,7 @@ extern "C" fn source_map_line_lengths_getter(_closure: *const ClosureHeader) -> f64::from_bits(JSValue::array_ptr(cloned).bits()) } -fn source_map_getter(name: &str, thunk: extern "C" fn(*const ClosureHeader) -> f64) -> f64 { +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); @@ -196,12 +196,12 @@ fn source_map_prototype() -> f64 { ( 4, "payload", - source_map_payload_getter as extern "C" fn(*const ClosureHeader) -> f64, + source_map_payload_getter as crate::closure::body_call::js_body_fn_ty!(), ), ( 3, "lineLengths", - source_map_line_lengths_getter as extern "C" fn(*const ClosureHeader) -> f64, + source_map_line_lengths_getter as crate::closure::body_call::js_body_fn_ty!(), ), ] { let getter = source_map_getter(name, thunk); diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index 3a610abeda..f12c6e8189 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -1385,17 +1385,26 @@ pub extern "C" fn js_assimilate_thenable(value: f64) -> f64 { unsafe { match then_param_count { 0 => { - let f: extern "C" fn(f64) -> f64 = std::mem::transmute(then_func_ptr); - f(this_f64); + crate::closure::body_call::js_method_body_call!( + then_func_ptr as *const u8, + this_f64 + ); } 1 => { - let f: extern "C" fn(f64, f64) -> f64 = std::mem::transmute(then_func_ptr); - f(this_f64, resolve_f64); + crate::closure::body_call::js_method_body_call!( + then_func_ptr as *const u8, + this_f64, + resolve_f64 + ); } _ => { // 2+ params: pass resolve/reject; any extra slots arrive as NaN. - let f: extern "C" fn(f64, f64, f64) -> f64 = std::mem::transmute(then_func_ptr); - f(this_f64, resolve_f64, reject_f64); + crate::closure::body_call::js_method_body_call!( + then_func_ptr as *const u8, + this_f64, + resolve_f64, + reject_f64 + ); } } } diff --git a/crates/perry-runtime/src/promise/microtasks.rs b/crates/perry-runtime/src/promise/microtasks.rs index 6d5eab92c2..8e2bb62df0 100644 --- a/crates/perry-runtime/src/promise/microtasks.rs +++ b/crates/perry-runtime/src/promise/microtasks.rs @@ -1189,9 +1189,7 @@ fn call_async_step_direct( // the stored function pointer instead. unsafe { let func_ptr = (*step_closure).func_ptr; - let func: extern "C" fn(*const crate::closure::ClosureHeader, f64, f64) -> f64 = - std::mem::transmute(func_ptr); - func(step_closure, value, is_error_bits) + crate::closure::body_call::js_body_call!(func_ptr, step_closure, value, is_error_bits) } } diff --git a/crates/perry-runtime/src/promise/mod.rs b/crates/perry-runtime/src/promise/mod.rs index 3b799dc472..7e0f416680 100644 --- a/crates/perry-runtime/src/promise/mod.rs +++ b/crates/perry-runtime/src/promise/mod.rs @@ -206,6 +206,7 @@ pub(crate) fn adapt_foreign_promise_value(value: f64) -> f64 { } unsafe { + // NOT-A-JS-BODY: a native Rust helper registered by another crate. let func: ForeignPromiseAdapterFn = std::mem::transmute(f); func(value) } diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index 5f120e0518..fe5fcd1ae6 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -444,7 +444,7 @@ mod platform_impl { cp_undefined() } - fn listener_value(f: extern "C" fn(*const ClosureHeader, f64) -> f64) -> f64 { + 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); cp_box_ptr(closure as *const u8) diff --git a/crates/perry-runtime/src/pty/tests.rs b/crates/perry-runtime/src/pty/tests.rs index 39fdccd902..6fc2129be6 100644 --- a/crates/perry-runtime/src/pty/tests.rs +++ b/crates/perry-runtime/src/pty/tests.rs @@ -28,7 +28,7 @@ extern "C" fn exited(_closure: *const ClosureHeader, event: f64) -> f64 { cp_undefined() } -fn callback(f: extern "C" fn(*const ClosureHeader, f64) -> f64) -> f64 { +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()) } diff --git a/crates/perry-runtime/src/thread.rs b/crates/perry-runtime/src/thread.rs index 8f4ce017d1..490e0da36a 100644 --- a/crates/perry-runtime/src/thread.rs +++ b/crates/perry-runtime/src/thread.rs @@ -998,7 +998,7 @@ pub(crate) unsafe fn test_deserialize_bigint_limbs(limbs: [u64; BIGINT_LIMBS]) - /// This matches Perry's closure calling convention where the first parameter /// is a pointer to the ClosureHeader (for accessing captures) and the second /// is the f64 argument. -type ClosureCallFn = unsafe extern "C" fn(*const ClosureHeader, f64) -> f64; +type ClosureCallFn = crate::closure::body_call::js_body_fn_ty!(argument); /// Process an array in parallel across multiple OS threads. /// @@ -1201,7 +1201,8 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { None }; - let call_fn: ClosureCallFn = std::mem::transmute(func_usize); + let call_fn: ClosureCallFn = + crate::closure::body_call::js_body_fn!(func_usize as *const u8; argument); for elem_sv in &chunk { let arg = f64::from_bits(deserialize_nanbox_on_current_thread(elem_sv)); @@ -1281,7 +1282,8 @@ unsafe fn single_thread_map( let result_arr = crate::array::js_array_alloc(len as u32); let result_handle = scope.root_raw_mut_ptr(result_arr); - let call_fn: ClosureCallFn = std::mem::transmute(func as usize); + let call_fn: ClosureCallFn = + crate::closure::body_call::js_body_fn!(func as *const u8; argument); for i in 0..len { // Sparse-safe element read (see `parallel_map_impl`); re-derived from @@ -1452,7 +1454,8 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { None }; - let call_fn: ClosureCallFn = std::mem::transmute(func_usize); + let call_fn: ClosureCallFn = + crate::closure::body_call::js_body_fn!(func_usize as *const u8; argument); for elem_sv in &chunk { let arg = f64::from_bits(deserialize_nanbox_on_current_thread(elem_sv)); @@ -1526,7 +1529,8 @@ unsafe fn single_thread_filter( let result_arr = crate::array::js_array_alloc(len as u32); let result_handle = scope.root_raw_mut_ptr(result_arr); - let call_fn: ClosureCallFn = std::mem::transmute(func as usize); + let call_fn: ClosureCallFn = + crate::closure::body_call::js_body_fn!(func as *const u8; argument); let mut count = 0u32; for i in 0..len { @@ -1562,7 +1566,7 @@ static ACTIVE_THREAD_JOBS: AtomicUsize = AtomicUsize::new(0); /// The compiled closure function signature for zero-argument closures. /// Takes only the closure header pointer, returns f64 result. -type ClosureCall0Fn = unsafe extern "C" fn(*const ClosureHeader) -> f64; +type ClosureCall0Fn = crate::closure::body_call::js_body_fn_ty!(); /// FFI entry point for `spawn(closure)`. /// @@ -1676,10 +1680,11 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { // 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 { std::mem::transmute(func_usize) }; + let call_fn: ClosureCall0Fn = + unsafe { crate::closure::body_call::js_body_fn!(func_usize as *const u8;) }; let local_closure = closure_handle.get_raw_mut_ptr::() as *const ClosureHeader; - unsafe { call_fn(local_closure) } + call_fn(local_closure) })); match call_result { diff --git a/scripts/check_js_body_call_funnel.py b/scripts/check_js_body_call_funnel.py new file mode 100755 index 0000000000..57a700d4b0 --- /dev/null +++ b/scripts/check_js_body_call_funnel.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +"""Every call of a JS function body goes through ONE funnel per side. + +A JS body is the native code a function object runs (a compiled closure body, +a value wrapper, a native builtin), and the class-method / static-method +bodies the runtime calls by raw pointer. Their calling convention is about to +change (the receiver becomes a parameter), so the calls are funneled: + +* runtime: only `crates/perry-runtime/src/closure/body_call.rs` may turn a + code pointer into a callable body type (`mem::transmute` to an + `fn(...) -> f64` whose parameters are all `f64` / `*const ClosureHeader`); +* codegen: only `crates/perry-codegen/src/expr/body_call.rs` (and the block + builder that defines them) may emit an indirect call. + +A transmute FROM a body type to an erased pointer (installing a native body) +is not a call and is not this gate's business. +""" + +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent + +RUNTIME_ROOTS = ("crates/perry-runtime/src", "crates/perry-stdlib/src") # + crates/perry-ext-*/src +RUNTIME_FUNNEL = "crates/perry-runtime/src/closure/body_call.rs" +CODEGEN_ROOT = "crates/perry-codegen/src" +CODEGEN_ALLOWED = { + "crates/perry-codegen/src/expr/body_call.rs", # the funnel + "crates/perry-codegen/src/block.rs", # defines call_indirect (+ its unit tests) + "crates/perry-codegen/src/gc_call_effects.rs", # effect-classifier unit tests only +} + +TRANSMUTE_RE = re.compile(r"\btransmute\b") +# A callable JS/method/static body type: returns f64, and every parameter is +# f64 or a closure-header pointer. +PARAM = r"\s*(?:f64|\*const\s+(?:crate::closure::|super::|perry_runtime::closure::)?ClosureHeader)\s*" +BODY_TYPE_RE = re.compile( + r'(?:unsafe\s+)?extern\s+"C(?:-unwind)?"\s+fn\s*\((?:' + PARAM + r"(?:," + PARAM + r")*,?)?\s*\)\s*->\s*f64" +) +TURBOFISH_RE = re.compile(r"transmute\s*::\s*<(.*)>\s*\(", re.S) +LET_TYPE_RE = re.compile(r"let\s+(?:mut\s+)?\w+\s*:\s*([^=]+)=\s*(?:unsafe\s*\{\s*)?(?:std::mem::|::std::mem::|mem::)?transmute\b", re.S) +ALIAS_RE = re.compile(r"type\s+(\w+)\s*=\s*([^;]+);") + + +def split_turbofish(args: str) -> str: + """Return the target (second) type of `transmute::`.""" + depth = 0 + for i, ch in enumerate(args): + if ch in "(<[": + depth += 1 + elif ch in ")>]": + depth -= 1 + elif ch == "," and depth == 0: + return args[i + 1 :] + return "" + + +def transmute_targets(text: str): + """Yield (line, target_type_text) for every transmute call in `text`.""" + aliases = {m.group(1): m.group(2) for m in ALIAS_RE.finditer(text)} + for m in TRANSMUTE_RE.finditer(text): + start = m.start() + line = text.count("\n", 0, start) + 1 + tail = text[start : start + 600] + target = "" + tf = TURBOFISH_RE.match(tail) + if tf: + inner = tf.group(1) + # cut at the matching `>` of the turbofish + depth = 0 + for i, ch in enumerate(inner): + if ch == "<": + depth += 1 + elif ch == ">" and not (i > 0 and inner[i - 1] == "-"): + if depth == 0: + inner = inner[:i] + break + depth -= 1 + target = split_turbofish(inner) + else: + window = text[max(0, start - 400) : start + len("transmute")] + stmt = window[window.rfind(";") + 1 :] + lt = LET_TYPE_RE.search(stmt) + if lt: + target = lt.group(1) + target = aliases.get(target.strip(), target) + yield line, target + + +NOT_A_BODY = "NOT-A-JS-BODY:" + + +def exempt(lines, line: int) -> bool: + """A native (non-JS) callback with a body-shaped type — a Rust helper + another crate registers — is exempt when one of the three lines above the + transmute says `NOT-A-JS-BODY: `.""" + return any(NOT_A_BODY in lines[i] for i in range(max(0, line - 4), line)) + + +def runtime_violations(root: Path): + out = [] + bases = list(RUNTIME_ROOTS) + sorted( + p.relative_to(root).as_posix() for p in root.glob("crates/perry-ext-*/src") + ) + for base in bases: + for path in sorted((root / base).rglob("*.rs")): + rel = path.relative_to(root).as_posix() + if rel == RUNTIME_FUNNEL: + continue + text = path.read_text(encoding="utf-8") + lines = text.split("\n") + for line, target in transmute_targets(text): + if BODY_TYPE_RE.search(target) and not exempt(lines, line): + out.append(f"{rel}:{line}: transmute to a JS body type outside {RUNTIME_FUNNEL}") + return out + + +INDIRECT_RE = re.compile(r"\.call_indirect(?:_gc_leaf)?\s*\(") + + +def codegen_violations(root: Path): + out = [] + for path in sorted((root / CODEGEN_ROOT).rglob("*.rs")): + rel = path.relative_to(root).as_posix() + if rel in CODEGEN_ALLOWED or rel.endswith("_tests.rs"): + continue + text = path.read_text(encoding="utf-8") + for m in INDIRECT_RE.finditer(text): + line = text.count("\n", 0, m.start()) + 1 + out.append(f"{rel}:{line}: indirect call built outside crates/perry-codegen/src/expr/body_call.rs") + return out + + +def self_test() -> int: + import tempfile + + cases = { + # (file, text, expect_red) + "rt_let": ("crates/perry-runtime/src/x.rs", + 'fn f(p: *const u8) { let g: extern "C" fn(*const ClosureHeader, f64) -> f64 = unsafe { std::mem::transmute(p) }; }', True), + "rt_turbofish": ("crates/perry-runtime/src/x.rs", + 'fn f(p: usize) { let _ = unsafe { std::mem::transmute:: f64>(p) }; }', True), + "rt_alias": ("crates/perry-runtime/src/x.rs", + 'type Cb = unsafe extern "C" fn(*const ClosureHeader) -> f64;\nfn f(p: usize) { let c: Cb = std::mem::transmute(p); }', True), + "rt_erase_ok": ("crates/perry-runtime/src/x.rs", + 'fn f() { let _ = unsafe { std::mem::transmute:: f64, extern "C" fn()>(g) }; }', False), + "rt_foreign_ok": ("crates/perry-runtime/src/x.rs", + 'fn f(p: usize) { let g: extern "C" fn(f64) -> i64 = unsafe { std::mem::transmute(p) }; }', False), + "rt_exempt_ok": ("crates/perry-runtime/src/x.rs", + 'fn f(p: usize) {\n // NOT-A-JS-BODY: a registered Rust helper.\n let g: extern "C" fn(f64) -> f64 = unsafe { std::mem::transmute(p) };\n}', False), + "rt_static_getter": ("crates/perry-runtime/src/x.rs", + 'fn f(p: usize) { let g: extern "C" fn() -> f64 = std::mem::transmute(p); }', True), + "rt_funnel_ok": (RUNTIME_FUNNEL, + 'fn f(p: *const u8) { let g: extern "C" fn(f64) -> f64 = unsafe { std::mem::transmute(p) }; }', False), + "cg_indirect": ("crates/perry-codegen/src/lower_call/x.rs", "fn f() { blk.call_indirect(DOUBLE, &p, &a); }", True), + "cg_funnel_ok": ("crates/perry-codegen/src/expr/body_call.rs", "fn f() { blk.call_indirect(DOUBLE, &p, &a); }", False), + } + failed = 0 + for name, (rel, text, expect_red) in cases.items(): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + for d in (*RUNTIME_ROOTS, CODEGEN_ROOT): + (root / d).mkdir(parents=True, exist_ok=True) + p = root / rel + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(text, encoding="utf-8") + red = bool(runtime_violations(root) or codegen_violations(root)) + if red != expect_red: + print(f"self-test {name}: expected {'red' if expect_red else 'green'}, got {'red' if red else 'green'}") + failed += 1 + if failed: + return 1 + print(f"check_js_body_call_funnel self-test: {len(cases)} cases OK") + return 0 + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) + ap.add_argument("--self-test", action="store_true") + args = ap.parse_args() + if args.self_test: + return self_test() + violations = runtime_violations(REPO) + codegen_violations(REPO) + if violations: + print("\n".join(violations)) + print(f"\n{len(violations)} JS body call(s) outside the funnels " + "(runtime: closure/body_call.rs macros; codegen: expr::body_call::emit_js_body_call)") + return 1 + print("check_js_body_call_funnel: every JS body call goes through its funnel") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/gc_root_dominance_check.py b/scripts/gc_root_dominance_check.py index 8852f5b6e5..123305dda7 100755 --- a/scripts/gc_root_dominance_check.py +++ b/scripts/gc_root_dominance_check.py @@ -1487,14 +1487,8 @@ def is_collecting(callee): # Calling a JS closure. The four names this replaces # (`js_call_closure`, `js_invoke_closure`, `js_function_call`, # `js_apply_function`) were not symbols; these are. - "js_closure_call0", "js_closure_call1", "js_closure_call2", - "js_closure_call3", "js_closure_call4", "js_closure_call5", - "js_closure_call6", "js_closure_call7", "js_closure_call8", - "js_closure_call9", "js_closure_call10", "js_closure_call11", - "js_closure_call12", "js_closure_call13", "js_closure_call14", - "js_closure_call15", "js_closure_call16", - "js_closure_call_array", "js_closure_call_apply_with_spread", - "js_native_call_value", + # ... every JS-call entry point, read from `perry_abi::JS_CALL_ENTRIES` + # (added below) so a new entry cannot be missing here. "js_object_get_property_key", "js_object_set_property_key", "js_object_set_property_key_method", "js_object_get_field_by_name", "js_object_set_field_by_name", @@ -1691,6 +1685,24 @@ def is_collecting(callee): } +def _js_call_entries(): + """`perry_abi::JS_CALL_ENTRIES` — the runtime entry points emitted code + calls to run a JS function. Read from the ABI crate, the one list both + sides compile against; an empty read is an error, never an empty set.""" + abi = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", + "crates", "perry-abi", "src", "lib.rs") + with open(abi, encoding="utf-8") as fh: + text = fh.read() + m = re.search(r"pub const JS_CALL_ENTRIES:[^=]*=\s*\[(.*?)\];", text, re.S) + names = re.findall(r'"([A-Za-z0-9_]+)"', m.group(1)) if m else [] + if not names: + raise SystemExit("gc_root_dominance_check: perry_abi::JS_CALL_ENTRIES not found") + return set(names) + + +POLL_CAPABLE_RUNTIME |= _js_call_entries() + + def compute_poll_reaching(all_funcs): """Names of compiled functions that can transitively reach a moving-minor safepoint (`js_gc_loop_safepoint`) or a runtime helper that re-enters JS.""" diff --git a/test-files/test_compat_buffers_typed.ts b/test-files/test_compat_buffers_typed.ts index e2661f1d0b..11dffd012a 100644 --- a/test-files/test_compat_buffers_typed.ts +++ b/test-files/test_compat_buffers_typed.ts @@ -340,7 +340,6 @@ crates/perry-runtime/src/closure.rs: - js_closure_get_func - js_closure_set_capture_f64 - js_closure_set_capture_ptr - - js_closure_unbind_this - js_create_callback - js_native_call_value - js_new_instance From 43db28b2f4f86b5c85741df472b787d68cbb3728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sun, 27 Sep 2026 23:05:52 +0000 Subject: [PATCH 02/17] =?UTF-8?q?perf:=20this-as-a-parameter=20stage=201?= =?UTF-8?q?=20=E2=80=94=20every=20JS=20body=20takes=20its=20receiver=20as?= =?UTF-8?q?=20a=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every JS body — a compiled closure body, a function's value wrapper, a class method's value wrapper, the namespace/no-op/unknown-function wrappers and every native builtin installed as a function object — is now double body(i64 callee, i64 this, double a0, ...) (perry_abi::JS_BODY_THIS_PARAM = 1, FIRST_ARG = 2). The receiver is NaN-boxed bits in an integer register (owner decision D1). Behavior-neutral by construction: every caller passes exactly the receiver the implicit-`this` cell holds for the call, and bodies still read the cell. Runtime: closure::JsThis (repr(transparent) u64). The body-call funnel macros take the receiver; funnel callers pass JsThis::current(), read at the call through the per-agent pointer block (PERRY_AGENT_PTRS slot 1), or the receiver they know (js_closure_call1_receiverless). DirectCallN's fallback is a body-typed adapter over js_closure_callN (call ENTRIES keep their (callee, args) signature in this stage). 1,923 native body definitions declare `_this: JsThis`; 137 direct Rust calls of native bodies pass the cell's receiver. perry-ffi exports an ABI-identical JsThis. Codegen: expr::body_call owns js_body_params / js_body_param_types / emit_js_body_call(this_bits) / current_this_bits. The method-site hit passes its receiver; arrow-only direct calls pass undefined; the closure-direct call passes undefined when it bound the cell to undefined, else the cell's bits; imported-object and Atomics direct calls pass the cell's bits. Stage-0 miss closed: the inline `some` loop called its captureless callback directly. Witness: PERRY_THIS_WITNESS=1 (compile time, in the object-cache key) emits js_this_param_witness(%js_this, name) at the entry of every body that reads the cell and reports `PERRY_THIS_WITNESS checks=N mismatches=M` at exit. Gate: check_js_body_call_funnel.py refuses a body-shaped extern fn without the receiver and a direct closure-allocator install of one (20 self-test cases). Tests: closure/receiver_param_tests.rs (exact, padded, rest, wide, hoisted routes; witness counts a disagreement); perry tests/this_param_abi.rs (route fixture == node with checks >= 3000 and 0 mismatches; every installed body defined with the ABI in the IR). Five runtime/stdlib files pushed over the 2,000-line gate by the added parameter are split (pipe listeners, Error user props, prototype-method lookups, a test module, termios impls). --- changelog.d/this-as-a-parameter-stage-1.md | 37 ++ crates/perry-abi/src/lib.rs | 18 +- crates/perry-codegen/src/codegen/artifacts.rs | 32 +- crates/perry-codegen/src/codegen/closure.rs | 54 ++- .../src/codegen/export_value_wrappers.rs | 92 ++--- crates/perry-codegen/src/codegen/helpers.rs | 3 +- .../src/codegen/namespace_value_getters.rs | 9 +- crates/perry-codegen/src/codegen/opts.rs | 2 +- .../src/expr/array_callback_shape_tests.rs | 2 +- crates/perry-codegen/src/expr/arrays_finds.rs | 6 +- crates/perry-codegen/src/expr/body_call.rs | 135 ++++++- .../perry-codegen/src/expr/dyn_extern_i18n.rs | 12 +- crates/perry-codegen/src/expr/js_runtime.rs | 2 +- .../src/expr/logical_collections.rs | 17 +- crates/perry-codegen/src/expr/method_site.rs | 4 + crates/perry-codegen/src/expr/property_get.rs | 6 +- crates/perry-codegen/src/expr/super_method.rs | 13 +- crates/perry-codegen/src/gc_call_effects.rs | 4 + .../perry-codegen/src/lower_call/atomics.rs | 17 +- .../src/lower_call/early_branches.rs | 19 + .../property_get/imported_object.rs | 16 +- .../runtime_decls/stdlib_ffi/language_core.rs | 50 ++- .../src/runtime_decls/strings_part2.rs | 4 + crates/perry-codegen/src/stubs.rs | 22 +- .../perry-codegen/src/wasm32/closure_abi.rs | 11 +- .../perry-codegen/src/wasm32/runtime_abi.tsv | 67 ++-- crates/perry-ext-events/src/dispatch_ext.rs | 2 +- crates/perry-ext-events/src/lib.rs | 7 +- .../perry-ext-events/src/module_iterators.rs | 33 +- crates/perry-ext-events/src/module_on.rs | 5 +- crates/perry-ext-events/src/tests.rs | 2 +- crates/perry-ext-http/src/agent.rs | 5 +- crates/perry-ext-http/src/client_abort.rs | 5 +- .../src/client_request_surface.rs | 6 +- crates/perry-ext-http/src/lib.rs | 1 + .../src/server/server/upgrade_tests.rs | 8 +- crates/perry-ext-http/src/server/types.rs | 5 +- crates/perry-ext-net/src/pipe.rs | 8 +- crates/perry-ext-typescript/src/bun.rs | 19 +- crates/perry-ext-ws/src/tests.rs | 22 +- crates/perry-ext-zlib/src/lib.rs | 8 +- crates/perry-ffi/src/closure.rs | 27 +- crates/perry-ffi/src/lib.rs | 2 +- crates/perry-runtime/src/agent_ptrs.rs | 15 + .../src/array/collection_tag_tests.rs | 1 + .../perry-runtime/src/array/iter_methods.rs | 1 + crates/perry-runtime/src/array/iterator.rs | 10 +- crates/perry-runtime/src/array/tests.rs | 2 + .../src/array/typed_array_receiver_tests.rs | 6 + crates/perry-runtime/src/async_hooks.rs | 11 +- .../src/async_hooks/provider_ffi.rs | 5 +- .../src/async_hooks/test_support.rs | 6 +- crates/perry-runtime/src/atomics.rs | 24 +- crates/perry-runtime/src/builtins/console.rs | 12 +- .../perry-runtime/src/bun_compat/cli_utils.rs | 29 +- crates/perry-runtime/src/bun_compat/glob.rs | 18 +- crates/perry-runtime/src/bun_compat/mod.rs | 22 +- crates/perry-runtime/src/bun_compat/plugin.rs | 24 +- .../src/bun_compat/self_global.rs | 10 +- crates/perry-runtime/src/bun_compat/spawn.rs | 102 ++++- crates/perry-runtime/src/bun_ffi/callback.rs | 5 +- crates/perry-runtime/src/bun_ffi/dlopen.rs | 20 +- crates/perry-runtime/src/bun_ffi/read.rs | 7 +- .../src/child_process/emitter.rs | 97 ++++- .../child_process/emitter/relocation_tests.rs | 12 +- .../perry-runtime/src/child_process/exec.rs | 9 +- .../src/child_process/failed_spawn.rs | 7 +- .../src/child_process/reactor.rs | 20 +- .../child_process/reactor/lifecycle_tests.rs | 13 +- crates/perry-runtime/src/closure/body_call.rs | 101 ++++- .../src/closure/dispatch/bound.rs | 10 +- .../bound/function_method_value_tests.rs | 5 +- .../src/closure/dispatch/calln.rs | 259 +++++++++++-- .../src/closure/dispatch/direct.rs | 79 +++- .../src/closure/dispatch/value_call.rs | 3 +- crates/perry-runtime/src/closure/mod.rs | 3 + .../src/closure/receiver_param_tests.rs | 152 ++++++++ crates/perry-runtime/src/closure/registry.rs | 43 ++- crates/perry-runtime/src/closure/shape.rs | 12 +- crates/perry-runtime/src/closure/tests.rs | 5 +- crates/perry-runtime/src/closure/wide_call.rs | 4 +- crates/perry-runtime/src/cluster.rs | 98 ++++- crates/perry-runtime/src/dgram.rs | 5 +- crates/perry-runtime/src/dgram/net.rs | 5 +- crates/perry-runtime/src/dgram/ops.rs | 16 +- crates/perry-runtime/src/dgram/thunks.rs | 105 +++++- crates/perry-runtime/src/disposable.rs | 10 +- crates/perry-runtime/src/dns.rs | 15 +- crates/perry-runtime/src/dyn_eval/interp.rs | 7 +- crates/perry-runtime/src/dyn_eval/tests.rs | 6 +- crates/perry-runtime/src/error.rs | 35 +- .../perry-runtime/src/error_subclass_stack.rs | 6 +- crates/perry-runtime/src/event_target.rs | 26 +- crates/perry-runtime/src/fs/deferred.rs | 5 +- .../src/fs/dir_glob_watch/watch.rs | 91 ++++- crates/perry-runtime/src/fs/fd_ops.rs | 67 +++- crates/perry-runtime/src/fs/filehandle.rs | 153 ++++++-- .../src/fs/mkdtemp_disposable.rs | 10 +- crates/perry-runtime/src/fs/stats.rs | 1 + crates/perry-runtime/src/fs/stream.rs | 45 ++- .../perry-runtime/src/fs/stream/read_turn.rs | 5 +- .../src/fs/stream/utf8_stream.rs | 51 ++- .../tests/array_pointer_slot_enumeration.rs | 6 +- .../gc/tests/copying/pointer_publish_7154.rs | 5 +- .../gc/tests/copying/weak_holder_registry.rs | 1 + .../src/gc/tests/copying/weak_semantics.rs | 1 + .../layout_trace/object_closure_slots.rs | 5 +- .../src/gc/tests/rooted_container_values.rs | 1 + .../src/gc/tests/rooted_define_property.rs | 10 +- .../src/gc/tests/rooted_for_in.rs | 13 +- .../src/gc/tests/runtime_roots.rs | 15 +- .../runtime_roots/arraylike_callbacks.rs | 1 + .../runtime_roots/call_argument_lists.rs | 3 + .../tests/runtime_roots/callback_scanners.rs | 8 +- .../generator_attach_prototype.rs | 6 +- .../runtime_roots/hook_dispatch_handles.rs | 5 +- .../runtime_roots/implicit_this_scope.rs | 6 +- .../tests/runtime_roots/json_record_output.rs | 5 +- .../tests/runtime_roots/perex_construction.rs | 10 +- .../gc/tests/runtime_roots/perex_dispatch.rs | 62 +++- .../src/gc/tests/runtime_roots/perex_glob.rs | 12 +- .../gc/tests/runtime_roots/perex_match_all.rs | 81 +++- .../tests/runtime_roots/perex_match_search.rs | 34 +- .../gc/tests/runtime_roots/perex_public.rs | 5 +- .../gc/tests/runtime_roots/perex_replace.rs | 31 +- .../runtime_roots/perex_replace_direct.rs | 14 +- .../src/gc/tests/runtime_roots/perex_split.rs | 117 ++++-- .../tests/runtime_roots/regexp_last_index.rs | 5 +- .../gc/tests/runtime_roots/sort_collection.rs | 24 +- .../runtime_roots/string_normalize_form.rs | 5 +- .../runtime_roots/thenable_assimilation.rs | 1 + .../tests/runtime_roots/transient_handles.rs | 5 +- .../perry-runtime/src/gc/tests/step_bounds.rs | 1 + crates/perry-runtime/src/gc/tests/support.rs | 2 + crates/perry-runtime/src/intl.rs | 13 +- .../perry-runtime/src/intl/date_collator.rs | 18 +- .../src/intl/date_collator/range.rs | 4 + .../perry-runtime/src/intl/display_names.rs | 28 +- .../perry-runtime/src/intl/duration_format.rs | 23 +- .../src/intl/list_relative_plural.rs | 29 +- crates/perry-runtime/src/intl/locale.rs | 134 +++++-- crates/perry-runtime/src/intl/locales.rs | 2 + .../perry-runtime/src/intl/number_format.rs | 12 +- crates/perry-runtime/src/intl/segmenter.rs | 14 +- .../perry-runtime/src/intl/segments_view.rs | 5 +- .../src/iterator_helpers/tests.rs | 31 +- .../src/json/replacer_hidden_fields_tests.rs | 7 +- .../src/json_tape/cached_read.rs | 5 +- crates/perry-runtime/src/jsx.rs | 6 +- crates/perry-runtime/src/lib.rs | 2 +- .../perry-runtime/src/map_tombstone_tests.rs | 3 + crates/perry-runtime/src/messaging.rs | 50 ++- crates/perry-runtime/src/module_require.rs | 21 +- .../src/module_require/import_meta_resolve.rs | 7 +- .../src/node_api_host/functions.rs | 1 + .../src/node_api_host/v10_tests.rs | 11 +- crates/perry-runtime/src/node_inspector.rs | 55 ++- crates/perry-runtime/src/node_repl.rs | 29 +- crates/perry-runtime/src/node_stream.rs | 350 +++++++++--------- .../src/node_stream/async_iterator.rs | 64 +++- .../src/node_stream/pipe_listeners.rs | 158 ++++++++ .../src/node_stream/readable_from_promises.rs | 2 + .../src/node_stream_constructors/builders.rs | 1 + .../src/node_stream_constructors/pipeline.rs | 9 +- .../node_stream_constructors/web_adapter.rs | 137 +++++-- .../src/node_stream_destroy_state.rs | 17 +- .../perry-runtime/src/node_stream_dispatch.rs | 13 +- .../src/node_stream_event_emitter.rs | 67 +++- .../src/node_stream_iter_helpers.rs | 97 ++++- .../perry-runtime/src/node_stream_pipeline.rs | 32 +- .../src/node_stream_state_view.rs | 11 +- crates/perry-runtime/src/node_stream_tests.rs | 127 +++++-- .../perry-runtime/src/node_submodules/blob.rs | 79 +++- .../src/node_submodules/consumers.rs | 40 +- .../src/node_submodules/diagnostics.rs | 185 +++------ .../node_submodules/diagnostics/user_props.rs | 127 +++++++ .../src/node_submodules/diagnostics_tail.rs | 21 +- .../src/node_submodules/fs_promises.rs | 134 ++++++- .../src/node_submodules/hono_jsx.rs | 27 +- .../perry-runtime/src/node_submodules/mod.rs | 2 + .../src/node_submodules/stream_promises.rs | 19 +- .../perry-runtime/src/node_submodules/test.rs | 157 ++++++-- .../test_metadata_unit_tests.rs | 7 +- .../node_submodules/test_once_unit_tests.rs | 9 +- .../src/node_submodules/test_property.rs | 42 ++- .../src/node_submodules/test_reporters.rs | 31 +- .../src/node_submodules/test_runner.rs | 105 +++++- .../src/node_submodules/test_snapshot.rs | 4 + .../src/node_submodules/test_unit_tests.rs | 5 +- .../src/node_submodules/tests.rs | 114 +++++- .../src/node_submodules/timers.rs | 54 ++- .../src/node_submodules/trace_events.rs | 28 +- crates/perry-runtime/src/node_test.rs | 18 +- crates/perry-runtime/src/node_v8.rs | 11 +- crates/perry-runtime/src/node_vm.rs | 4 + crates/perry-runtime/src/node_vm/modules.rs | 10 +- .../src/object/accessor_pair_tests.rs | 5 +- crates/perry-runtime/src/object/arguments.rs | 9 +- crates/perry-runtime/src/object/assert.rs | 4 + .../src/object/async_generator_queue.rs | 37 +- .../src/object/class_registry/construct.rs | 80 ++-- .../construct/prototype_methods.rs | 44 +++ .../src/object/class_registry/registration.rs | 6 +- .../src/object/collection_proto_thunks.rs | 26 ++ .../collection_proto_thunks/subclass_tests.rs | 58 ++- .../src/object/dataview_proto_thunks.rs | 9 +- .../src/object/date_proto_thunks.rs | 69 +++- .../src/object/disposable_proto_thunks.rs | 24 +- .../has_property/function_presence_tests.rs | 10 +- .../perry-runtime/src/object/global_fetch.rs | 1 + .../src/object/global_this/array_error.rs | 57 ++- .../array_error/apply_args_tests.rs | 7 +- .../src/object/global_this/bigint_promise.rs | 74 +++- .../src/object/global_this/builtin_thunks.rs | 48 ++- .../src/object/global_this/ctor_thunks.rs | 27 ++ .../src/object/global_this/fetch_globals.rs | 58 ++- .../src/object/global_this/generator.rs | 8 + .../src/object/global_this/install_static.rs | 12 + .../src/object/global_this/math_temporal.rs | 39 +- .../src/object/global_this/proto_methods.rs | 9 +- .../src/object/global_this/typed_array.rs | 16 + .../src/object/global_this_webassembly.rs | 81 +++- .../src/object/inherited_read_cache_tests.rs | 5 +- crates/perry-runtime/src/object/instanceof.rs | 1 + .../src/object/iterator_prototypes.rs | 27 +- .../src/object/literal_constructor.rs | 6 +- crates/perry-runtime/src/object/mod.rs | 10 +- .../closure_override_tests.rs | 12 +- .../code_point_at_dispatch_tests.rs | 5 +- .../dispatch_arg_coercion_tests.rs | 1 + .../native_call_method/function_shape.rs | 2 +- .../to_locale_string_tests.rs | 6 +- .../src/object/native_get/tests.rs | 5 +- .../native_module/async_hooks_exports.rs | 1 + .../object/native_module/callable_exports.rs | 15 +- .../callable_exports/buffer_species.rs | 14 +- .../callable_exports/module_cjs.rs | 13 +- .../native_module/namespace_builders.rs | 10 +- .../src/object/native_module/web_locks.rs | 11 +- .../src/object/native_module_stream.rs | 1 + .../src/object/own_override_push_tests.rs | 6 +- .../src/object/primitive_proto_thunks.rs | 15 + .../perry-runtime/src/object/property_key.rs | 5 +- .../src/object/regex_proto_thunks.rs | 18 +- .../src/object/reserved_floor.rs | 6 +- .../object/restricted_function_store_tests.rs | 3 + .../src/object/string_proto_thunks.rs | 22 +- .../src/object/temporal_proto.rs | 2 + crates/perry-runtime/src/object/tests.rs | 17 +- .../perry-runtime/src/object/this_binding.rs | 90 ++++- .../src/object/typed_array_proto_thunks.rs | 9 + crates/perry-runtime/src/object/util_types.rs | 8 +- .../src/object/view_brand_tests.rs | 6 +- .../src/object/weakref_proto_thunks.rs | 14 +- .../src/os/os_process_emitter.rs | 1 + .../perry-runtime/src/os_process_streams.rs | 110 ++---- .../reader_lifecycle_tests.rs | 78 ++++ crates/perry-runtime/src/perf_hooks.rs | 1 + .../src/perf_hooks/prototypes.rs | 26 +- .../prototypes/performance_methods.rs | 26 +- .../perry-runtime/src/perf_hooks/timerify.rs | 2 + crates/perry-runtime/src/process/env_misc.rs | 5 +- .../perry-runtime/src/process/finalization.rs | 4 + crates/perry-runtime/src/process/ipc.rs | 21 +- .../perry-runtime/src/process/node_module.rs | 9 +- .../src/process/node_module/source_map.rs | 22 +- .../perry-runtime/src/process/permission.rs | 2 + crates/perry-runtime/src/process/report.rs | 2 + .../perry-runtime/src/promise/assimilate.rs | 12 +- .../perry-runtime/src/promise/async_step.rs | 15 +- .../src/promise/checked_dispatch.rs | 19 +- .../perry-runtime/src/promise/combinators.rs | 12 + .../perry-runtime/src/promise/microtasks.rs | 8 +- .../src/promise/microtasks/empty.rs | 16 +- .../src/promise/rejection_loop_tests.rs | 7 +- .../promise/resolving_function_kind_tests.rs | 6 +- crates/perry-runtime/src/promise/scanners.rs | 2 + .../src/promise/spec_combinators.rs | 6 + crates/perry-runtime/src/promise/then.rs | 17 + crates/perry-runtime/src/proxy.rs | 5 +- .../perry-runtime/src/proxy/reflect_misc.rs | 2 + crates/perry-runtime/src/pty/mod.rs | 67 +++- crates/perry-runtime/src/pty/tests.rs | 18 +- crates/perry-runtime/src/readline_helpers.rs | 6 +- crates/perry-runtime/src/regex/match_all.rs | 1 + .../src/regex/perex_match_search.rs | 12 +- .../perry-runtime/src/regex/perex_replace.rs | 1 + crates/perry-runtime/src/regex/perex_split.rs | 1 + crates/perry-runtime/src/regex/site_test.rs | 23 +- .../perry-runtime/src/set_tombstone_tests.rs | 3 + .../src/symbol/inherited_accessor_tests.rs | 8 +- crates/perry-runtime/src/text.rs | 7 + crates/perry-runtime/src/thread.rs | 10 +- .../perry-runtime/src/timer/handle_object.rs | 37 +- .../perry-runtime/src/timer/tests_inline.rs | 5 +- crates/perry-runtime/src/tty.rs | 20 +- crates/perry-runtime/src/tui/handle_object.rs | 59 ++- crates/perry-runtime/src/tui/hooks.rs | 1 + .../perry-runtime/src/typed_feedback/tests.rs | 6 +- crates/perry-runtime/src/url/abort.rs | 17 +- crates/perry-runtime/src/url/prototype.rs | 11 +- crates/perry-runtime/src/url/search_params.rs | 20 +- crates/perry-runtime/src/util_abort.rs | 5 +- crates/perry-runtime/src/util_debuglog.rs | 6 +- crates/perry-runtime/src/util_promisify.rs | 44 ++- crates/perry-runtime/src/v8.rs | 5 +- crates/perry-runtime/src/wasi.rs | 19 +- .../perry-runtime/src/weakref/test_support.rs | 6 +- crates/perry-runtime/src/web_storage.rs | 53 ++- crates/perry-runtime/src/webassembly.rs | 8 +- crates/perry-runtime/src/webassembly_calls.rs | 2 + crates/perry-runtime/src/webassembly_table.rs | 8 +- crates/perry-stdlib/src/bun_sql.rs | 36 +- .../src/common/dispatch/emitter_als.rs | 1 + crates/perry-stdlib/src/crypto/random.rs | 1 + crates/perry-stdlib/src/domain.rs | 12 +- crates/perry-stdlib/src/events.rs | 12 +- crates/perry-stdlib/src/events/events_on.rs | 31 +- .../perry-stdlib/src/events/module_helpers.rs | 5 +- .../perry-stdlib/src/events/once_helpers.rs | 23 +- crates/perry-stdlib/src/readline/mod.rs | 175 ++------- .../perry-stdlib/src/readline/termios_unix.rs | 52 +++ .../src/readline/termios_windows.rs | 89 +++++ .../perry-stdlib/src/readline/test_support.rs | 12 +- .../src/runtime_thread_exit_tests.rs | 5 +- .../listeners_tests.rs | 13 +- .../stdlib_misc_tests.rs | 5 +- .../streams_tests.rs | 5 +- .../symbols_tests.rs | 5 +- .../src/runtime_thread_exit_tests/ui_tests.rs | 9 +- crates/perry-stdlib/src/sqlite/better.rs | 1 + crates/perry-stdlib/src/sqlite/bun.rs | 1 + crates/perry-stdlib/src/sqlite/dispatch.rs | 5 +- crates/perry-stdlib/src/streams.rs | 46 ++- crates/perry-stdlib/src/streams/byob.rs | 12 +- crates/perry-stdlib/src/streams/pipe.rs | 25 +- crates/perry-stdlib/src/streams/strategy.rs | 14 +- crates/perry-stdlib/src/streams/tee.rs | 20 +- crates/perry-stdlib/src/streams/transform.rs | 35 +- crates/perry-stdlib/src/streams/writable.rs | 29 +- crates/perry-stdlib/src/string_decoder.rs | 6 + crates/perry-stdlib/src/tls.rs | 7 +- crates/perry-stdlib/src/webcrypto/digest.rs | 5 +- crates/perry-stdlib/src/worker_threads.rs | 93 ++++- .../src/worker_threads/broadcast_channel.rs | 18 +- .../src/worker_threads/message_port.rs | 61 ++- .../src/worker_threads/parent_port.rs | 10 +- .../src/worker_threads/worker_surface.rs | 65 +++- crates/perry-ui-ios/src/notifications.rs | 1 + .../src/commands/compile/object_cache.rs | 6 + .../object_cache/object_cache_tests.rs | 1 + .../perry/tests/fixtures/this_param_routes.ts | 72 ++++ crates/perry/tests/this_param_abi.rs | 206 +++++++++++ scripts/check_js_body_call_funnel.py | 153 +++++++- scripts/gc_root_dominance_check.py | 3 + 355 files changed, 7668 insertions(+), 1900 deletions(-) create mode 100644 changelog.d/this-as-a-parameter-stage-1.md create mode 100644 crates/perry-runtime/src/closure/receiver_param_tests.rs create mode 100644 crates/perry-runtime/src/node_stream/pipe_listeners.rs create mode 100644 crates/perry-runtime/src/node_submodules/diagnostics/user_props.rs create mode 100644 crates/perry-runtime/src/object/class_registry/construct/prototype_methods.rs create mode 100644 crates/perry-runtime/src/os_process_streams/reader_lifecycle_tests.rs create mode 100644 crates/perry-stdlib/src/readline/termios_unix.rs create mode 100644 crates/perry-stdlib/src/readline/termios_windows.rs create mode 100644 crates/perry/tests/fixtures/this_param_routes.ts create mode 100644 crates/perry/tests/this_param_abi.rs diff --git a/changelog.d/this-as-a-parameter-stage-1.md b/changelog.d/this-as-a-parameter-stage-1.md new file mode 100644 index 0000000000..ccb27dd9e6 --- /dev/null +++ b/changelog.d/this-as-a-parameter-stage-1.md @@ -0,0 +1,37 @@ +Every JS body now takes its receiver as a parameter: a compiled closure body, +a function's value wrapper, a class method's value wrapper and every native +builtin installed as a function object are `double body(i64 callee, i64 this, +double a0, ...)` (`perry_abi::JS_BODY_*`; the receiver is NaN-boxed bits in an +integer register, so every floating-point argument register stays free for +JS arguments). This is stage 1 of passing `this` as a parameter instead of +through the per-agent implicit-`this` cell, and it changes no behavior: +every caller passes exactly the receiver the cell holds for the call — the +method-call site its receiver, a call that bound the cell to `undefined` +`undefined`, the runtime's dispatch paths the cell's current value — and +bodies still read the cell. + +- The runtime's native bodies declare the receiver (`_this: JsThis`, + `closure::JsThis`, `repr(transparent)` over `u64`); perry-ffi exports an + ABI-identical `JsThis` for wrapper crates. The body-call funnel + (`closure/body_call.rs`) passes it on every route: exact arity, padded + arity, rest bundling, the wide ladder, hoisted `DirectCallN` sites, + microtask steps and worker threads. +- Stage 0 (the preceding commit) made one funnel per side for every call of + a body's code pointer: `closure/body_call.rs` in the runtime, + `expr::body_call` in codegen, and deleted the dead, wrong + `js_closure_unbind_this`. +- Codegen spells the ABI once (`expr::body_call::js_body_params` / + `emit_js_body_call`); a stage-0 miss is closed — the inline + `Array.prototype.some` loop called its captureless callback body directly. +- `PERRY_THIS_WITNESS=1` (compile-time, in the object-cache key) makes the + entry of every compiled body that reads the cell compare its `this` + parameter with the cell and report `PERRY_THIS_WITNESS checks=N + mismatches=M` at exit. The gap corpus, tsc and the call-route fixture run + with zero mismatches. +- `scripts/check_js_body_call_funnel.py` now also refuses a body-shaped + `extern "C" fn` that does not declare the receiver, and a function handed + straight to a closure allocator that does not. + +Cost (instructions per call, LTO-off fast build, same host): a closure value +call through the runtime +9 (the dispatcher reads the cell to pass it), a +method-site hit +3. Stage 3 removes the cell and these reads with it. diff --git a/crates/perry-abi/src/lib.rs b/crates/perry-abi/src/lib.rs index ee0b89e10c..b8bd1fbd33 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -37,23 +37,33 @@ pub const GC_FLAG_FORWARDED: u8 = 0x80; /// `gc::GC_HEADER_SIZE`. pub const GC_HEADER_SIZE: usize = 8; - /// The JS BODY calling convention. Every native body a function object runs — /// a compiled closure body, a value wrapper, a native builtin installed as a /// function object — is /// /// ```text -/// double body(i64 callee, double a0, double a1, ...) +/// double body(i64 callee, i64 this, double a0, double a1, ...) /// ``` /// -/// where `callee` is the function object (its captures follow the header). +/// where `callee` is the function object (its captures follow the header) and +/// `this` is the NaN-boxed receiver bits, passed in an INTEGER register so +/// every floating-point argument register stays free for JS arguments (SysV +/// x86-64 / AAPCS64; Win64 assigns positionally, which is equally correct). /// Passing more JS arguments than a body declares is safe (the caller owns /// the stack argument area); fewer is padded with `undefined` by the caller. /// The runtime calls bodies only through `closure/body_call.rs`; emitted code /// only through `expr::body_call::emit_js_body_call`. +/// +/// While the implicit-`this` cell still exists (this-as-a-parameter stage 1), +/// a caller passes exactly the receiver the cell holds for the call, so the +/// parameter and the cell never disagree; bodies still read the cell. pub const JS_BODY_CALLEE_PARAM: usize = 0; +/// Native parameter index of the receiver (`this`) bits. +pub const JS_BODY_THIS_PARAM: usize = 1; /// Native parameter index of the first JS argument. -pub const JS_BODY_FIRST_ARG_PARAM: usize = 1; +pub const JS_BODY_FIRST_ARG_PARAM: usize = 2; +/// Native parameters every JS body declares before its JS arguments. +pub const JS_BODY_FIXED_PARAMS: usize = 2; /// `js_closure_call{N}(callee, a0..aN-1)` exists for `N <= JS_CLOSURE_CALL_MAX_ARGS`; /// wider calls use `js_closure_call_array`. pub const JS_CLOSURE_CALL_MAX_ARGS: usize = 16; diff --git a/crates/perry-codegen/src/codegen/artifacts.rs b/crates/perry-codegen/src/codegen/artifacts.rs index 1d755e5116..d9ad91e4e6 100644 --- a/crates/perry-codegen/src/codegen/artifacts.rs +++ b/crates/perry-codegen/src/codegen/artifacts.rs @@ -7,7 +7,7 @@ use std::time::Instant; use anyhow::{Context, Result}; -use crate::types::{LlvmType, DOUBLE, I64, VOID}; +use crate::types::{LlvmType, DOUBLE, VOID}; use super::artifact_context::{ModuleArtifactsCtx, OptsView}; use super::class_artifacts::{emit_class_artifacts, ClassArtifactsCtx}; @@ -304,9 +304,10 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { // so `Expr::SuperPropertyGet` (value-form `super.`) can // materialize them via `js_closure_alloc_singleton(@__perry_wrap_)`. // Methods have signature `perry_method_<...>(this_box, args...)`; - // the closure-call ABI is `(i64 closure, double a0, ...)` and - // doesn't carry a separate `this`. The receiver therefore comes from - // IMPLICIT_THIS, set by the dispatcher right before the call: + // the JS body ABI is `(i64 closure, i64 this, double a0, ...)`. The + // receiver still comes from IMPLICIT_THIS (stage 1 of this-as-a-parameter: + // the `this` parameter carries the same value, which witness builds + // check), set by the dispatcher right before the call: // * A method-style invocation of a stored super-method value // (`this._complete = super._complete; obj._complete()` — rxjs's // `OperatorSubscriber` forwarding `complete`/`error`/`next` to the @@ -331,13 +332,13 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { continue; } let arity = method.params.len(); - let mut wrap_params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; - for i in 0..arity { - wrap_params.push((DOUBLE, format!("%a{}", i))); - } + let wrap_params = + crate::expr::body_call::js_body_params((0..arity).map(|i| format!("%a{}", i))); + let witness_site = crate::expr::body_call::this_witness_site(llmod, &wrap_name); let wf = llmod.define_function(&wrap_name, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); + crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); // Forward the call-site receiver (IMPLICIT_THIS) as `this`, // then the args. See the block comment above (#5138). let this_box = blk.call(DOUBLE, "js_implicit_this_get", &[]); @@ -380,14 +381,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let wf = llmod.define_function( &wrap_name, DOUBLE, - vec![ - (I64, "%this_closure".to_string()), - (DOUBLE, "%a0".to_string()), - (DOUBLE, "%a1".to_string()), - (DOUBLE, "%a2".to_string()), - (DOUBLE, "%a3".to_string()), - (DOUBLE, "%a4".to_string()), - ], + crate::expr::body_call::js_body_params((0..5).map(|i| format!("%a{i}"))), ); // Fix #420 (v0.5.576): internal linkage keeps unsplit modules' copies // dead-code-eliminable. Split units promote the definition so calls @@ -448,7 +442,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let wrapper = llmod.define_function( &wrapper_name, DOUBLE, - vec![(I64, "%this_closure".to_string())], + crate::expr::body_call::js_body_params(std::iter::empty::()), ); let _ = wrapper.create_block("entry"); let blk = wrapper.block_mut(0).unwrap(); @@ -465,7 +459,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let wrapper = llmod.define_function( &wrapper_name, DOUBLE, - vec![(I64, "%this_closure".to_string())], + crate::expr::body_call::js_body_params(std::iter::empty::()), ); let _ = wrapper.create_block("entry"); let blk = wrapper.block_mut(0).unwrap(); @@ -489,7 +483,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let wrapper = llmod.define_function( &wrapper_name, DOUBLE, - vec![(I64, "%this_closure".to_string())], + crate::expr::body_call::js_body_params(std::iter::empty::()), ); let _ = wrapper.create_block("entry"); let blk = wrapper.block_mut(0).unwrap(); diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index dfbae21935..ace6be5cd5 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -140,12 +140,8 @@ fn emit_public_typed_closure_trampoline( TypedFunctionTrampolineKind::StringRef => typed_param_reps_for_params(params) .unwrap_or_else(|| vec![TypedParamRep::StringRef; params.len()]), }; - let mut llvm_params: Vec<(LlvmType, String)> = Vec::with_capacity(params.len() + 1); - llvm_params.push((I64, "%this_closure".to_string())); - for p in params { - llvm_params.push((DOUBLE, format!("%arg{}", p.id))); - } let arg_names: Vec = params.iter().map(|p| format!("%arg{}", p.id)).collect(); + let llvm_params = crate::expr::body_call::js_body_params(arg_names.iter().cloned()); let wf = llmod.define_function(&public_name, DOUBLE, llvm_params); let _ = wf.create_block("entry"); @@ -169,10 +165,13 @@ fn emit_public_typed_closure_trampoline( emit_typed_closure_trampoline_fast_value(blk, kind, &typed_name, values, &arg_reps) }, &mut |blk| { + // The generic body is this entry's own continuation: it gets the + // receiver this entry was given. crate::expr::body_call::emit_js_body_call( blk, crate::expr::body_call::JsBody::Symbol(generic_body_name), - "%this_closure", + crate::expr::body_call::JS_BODY_CALLEE, + crate::expr::body_call::JS_BODY_THIS, &arg_names, ) }, @@ -407,10 +406,12 @@ pub(super) fn compile_typed_i32_closure( /// Compile a closure body as a top-level LLVM function. /// -/// Signature: `double perry_closure___(i64 this_closure, -/// double arg0, double arg1, …)`. The first parameter is the closure -/// pointer (raw i64); the remaining params are the closure's own -/// declared parameters. +/// Signature: the JS body ABI (`expr::body_call::js_body_params`), +/// `double perry_closure___(i64 this_closure, i64 +/// js_this, double arg0, double arg1, …)`. The first parameter is the +/// closure pointer (raw i64), the second the receiver bits (still read from +/// the implicit-`this` cell, stage 1); the remaining params are the +/// closure's own declared parameters. /// /// Inside the body, captured variables (`closure.captures`) are mapped /// to capture indices and accessed via the runtime @@ -544,15 +545,26 @@ pub(super) fn compile_closure( ordinary_body_name }; - // Param list: i64 this_closure, then each param as double. The private - // versioned-loop clone reuses its proven-unused first callback parameter - // for the caller's stack context, so its ABI and register footprint stay - // identical to the ordinary trusted clone. - let mut llvm_params: Vec<(LlvmType, String)> = Vec::with_capacity(params.len() + 1); - llvm_params.push((I64, "%this_closure".to_string())); - for p in params { - llvm_params.push((DOUBLE, format!("%arg{}", p.id))); - } + // Param list: the JS body ABI (`i64 %this_closure, i64 %js_this`), then + // each param as double. The private versioned-loop clone reuses its + // proven-unused first callback parameter for the caller's stack context, + // so its ABI and register footprint stay identical to the ordinary + // trusted clone. + let llvm_params = + crate::expr::body_call::js_body_params(params.iter().map(|p| format!("%arg{}", p.id))); + + // Stage-1 witness: a body that reads the implicit-`this` cell (it binds + // no lexical `this` of its own) checks, at entry, that its caller passed + // the cell's receiver as `%js_this`. Witness builds only. + let reads_this_cell = !captures_this + && enclosing_class.is_none() + && !is_arrow + && perry_hir::analysis::body_reads_dynamic_this(body); + let witness_site = if reads_this_cell { + crate::expr::body_call::this_witness_site(llmod, &llvm_name) + } else { + None + }; let ic_base = llmod.ic_counter; let buffer_alias_base = llmod.buffer_alias_counter; @@ -631,6 +643,10 @@ pub(super) fn compile_closure( crate::collectors::collect_shadow_slot_clear_points(body, &shadow_slot_map); let _ = lf.create_block("entry"); + crate::expr::body_call::emit_this_param_witness( + lf.block_mut(0).expect("closure body has an entry block"), + witness_site.as_ref(), + ); let versioned_loop_deopt_context = versioned_loop_callback.then(|| { let scratch_param = params diff --git a/crates/perry-codegen/src/codegen/export_value_wrappers.rs b/crates/perry-codegen/src/codegen/export_value_wrappers.rs index fbe768d3c8..f650f35ca5 100644 --- a/crates/perry-codegen/src/codegen/export_value_wrappers.rs +++ b/crates/perry-codegen/src/codegen/export_value_wrappers.rs @@ -87,11 +87,12 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { } // Emit FuncRef-as-value wrappers. For each user function, generate - // a thin wrapper `__perry_wrap_` whose signature matches the - // closure-call ABI: `double(i64 this_closure, double arg0, double - // arg1, ...)`. Most wrappers discard the closure pointer and forward the - // args to the underlying function; generator wrappers reuse it to link the - // returned iterator to the closure-cached `prototype`. + // a thin wrapper `__perry_wrap_` whose signature is the JS body + // ABI: `double(i64 this_closure, i64 js_this, double arg0, double arg1, + // ...)`. Most wrappers discard the closure pointer and forward the args + // to the underlying function; generator wrappers reuse it to link the + // returned iterator to the closure-cached `prototype`. The underlying + // function still reads its receiver from the implicit-`this` cell. // // The wrapper exists so that `apply(add, 3, 4)` can pass `add` as // a value and have `apply` call it via `js_closure_call2`. Without @@ -114,14 +115,20 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { // `js_closure_call_array`. let arity = f.params.len(); let arg_names: Vec = (0..arity).map(|i| format!("%a{}", i)).collect(); - let mut wrap_params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; - for name in &arg_names { - wrap_params.push((DOUBLE, name.clone())); - } + let wrap_params = crate::expr::body_call::js_body_params(arg_names.iter().cloned()); let wrap_name = format!("__perry_wrap_{}", original_name); + // Stage-1 witness: the function body reads its receiver from the + // implicit-`this` cell; its value wrapper is the entry a function + // object calls, so it checks the receiver it was passed. + let witness_site = if perry_hir::analysis::body_reads_dynamic_this(&f.body) { + crate::expr::body_call::this_witness_site(llmod, &wrap_name) + } else { + None + }; let wf = llmod.define_function(&wrap_name, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); + crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); // Call the underlying function with just the arg doubles. let call_args: Vec<(LlvmType, &str)> = arg_names.iter().map(|n| (DOUBLE, n.as_str())).collect(); @@ -166,14 +173,13 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { } let arity = f.params.len(); - let mut params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; - params.extend((0..arity).map(|i| (DOUBLE, format!("%a{}", i)))); - let alias = llmod.define_function(&alias_wrap, DOUBLE, params); + let params = + crate::expr::body_call::js_body_params((0..arity).map(|i| format!("%a{}", i))); + let alias = llmod.define_function(&alias_wrap, DOUBLE, params.clone()); let _ = alias.create_block("entry"); let blk = alias.block_mut(0).unwrap(); - let mut args: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; - args.extend((0..arity).map(|i| (DOUBLE, format!("%a{}", i)))); - let arg_refs: Vec<(LlvmType, &str)> = args + // Forward every parameter, the receiver included. + let arg_refs: Vec<(LlvmType, &str)> = params .iter() .map(|(ty, value)| (*ty, value.as_str())) .collect(); @@ -245,11 +251,8 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { // fall back to a no-op (variable/class/type rename). if let Some(f) = func_by_local_name.get(local.as_str()) { let arity = f.params.len().min(32); - let mut wrap_params: Vec<(LlvmType, String)> = - vec![(I64, "%this_closure".to_string())]; - for i in 0..arity { - wrap_params.push((DOUBLE, format!("%a{}", i))); - } + let wrap_params = + crate::expr::body_call::js_body_params((0..arity).map(|i| format!("%a{}", i))); let wf = llmod.define_function(&exported_wrap, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); @@ -279,14 +282,7 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let wf = llmod.define_function( &exported_wrap, DOUBLE, - vec![ - (I64, "%this_closure".to_string()), - (DOUBLE, "%a0".to_string()), - (DOUBLE, "%a1".to_string()), - (DOUBLE, "%a2".to_string()), - (DOUBLE, "%a3".to_string()), - (DOUBLE, "%a4".to_string()), - ], + crate::expr::body_call::js_body_params((0..5).map(|i| format!("%a{i}"))), ); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); @@ -390,11 +386,13 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let target_wrap = format!("__perry_wrap_{}", target); if !llmod.has_function(&raw_wrap) && emitted_aliases.insert(raw_wrap.clone()) { if llmod.has_function(&target_wrap) { - // Match the canonical wrapper's closure-call ABI (one - // double per declared param), including renamed exports. + // Match the canonical wrapper's JS body ABI (callee, + // receiver, one double per declared param), including + // renamed exports. let arity = function.map(|f| f.params.len()).unwrap_or(5); - let mut params = vec![(I64, "%this_closure".to_string())]; - params.extend((0..arity).map(|i| (DOUBLE, format!("%a{}", i)))); + let params = crate::expr::body_call::js_body_params( + (0..arity).map(|i| format!("%a{}", i)), + ); let wf = llmod.define_function(&raw_wrap, DOUBLE, params.clone()); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); @@ -411,14 +409,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let wf = llmod.define_function( &raw_wrap, DOUBLE, - vec![ - (I64, "%this_closure".to_string()), - (DOUBLE, "%a0".to_string()), - (DOUBLE, "%a1".to_string()), - (DOUBLE, "%a2".to_string()), - (DOUBLE, "%a3".to_string()), - (DOUBLE, "%a4".to_string()), - ], + crate::expr::body_call::js_body_params( + (0..5).map(|i| format!("%a{i}")), + ), ); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); @@ -488,11 +481,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { .and_then(|(_, fid)| hir.functions.iter().find(|f| f.id == *fid)); if let Some(f) = aliased_func { let arity = f.params.len().min(32); - let mut wrap_params: Vec<(LlvmType, String)> = - vec![(I64, "%this_closure".to_string())]; - for i in 0..arity { - wrap_params.push((DOUBLE, format!("%a{}", i))); - } + let wrap_params = crate::expr::body_call::js_body_params( + (0..arity).map(|i| format!("%a{}", i)), + ); let wf = llmod.define_function(&exported_wrap, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); @@ -510,14 +501,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let wf = llmod.define_function( &exported_wrap, DOUBLE, - vec![ - (I64, "%this_closure".to_string()), - (DOUBLE, "%a0".to_string()), - (DOUBLE, "%a1".to_string()), - (DOUBLE, "%a2".to_string()), - (DOUBLE, "%a3".to_string()), - (DOUBLE, "%a4".to_string()), - ], + crate::expr::body_call::js_body_params( + (0..5).map(|i| format!("%a{i}")), + ), ); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); diff --git a/crates/perry-codegen/src/codegen/helpers.rs b/crates/perry-codegen/src/codegen/helpers.rs index 7ac542ae00..05fd2c8f89 100644 --- a/crates/perry-codegen/src/codegen/helpers.rs +++ b/crates/perry-codegen/src/codegen/helpers.rs @@ -1574,8 +1574,7 @@ pub(super) fn emit_namespace_populator( sanitize_member(source_local) ); let arity = *param_count; - let mut wrapper_params: Vec = vec![I64]; - wrapper_params.extend(std::iter::repeat_n(DOUBLE, arity)); + let wrapper_params = crate::expr::body_call::js_body_param_types(arity); ctx.pending_declares .push((wrapper_name.clone(), DOUBLE, wrapper_params)); let blk = ctx.block(); diff --git a/crates/perry-codegen/src/codegen/namespace_value_getters.rs b/crates/perry-codegen/src/codegen/namespace_value_getters.rs index 4b5d14dd0d..6ba5602c04 100644 --- a/crates/perry-codegen/src/codegen/namespace_value_getters.rs +++ b/crates/perry-codegen/src/codegen/namespace_value_getters.rs @@ -3,7 +3,7 @@ use super::helpers::sanitize_member; use super::opts::CrossModuleCtx; use crate::module::LlModule; -use crate::types::{DOUBLE, I64}; +use crate::types::DOUBLE; pub(crate) fn symbol(module_prefix: &str, namespace: &str, member: &str) -> String { format!( @@ -31,8 +31,11 @@ pub(super) fn emit(llmod: &mut LlModule, module_prefix: &str, imports: &CrossMod ); let getter_name = format!("perry_fn_{source_prefix}__{origin}"); llmod.declare_function(&getter_name, DOUBLE, &[]); - let wrapper = - llmod.define_function(wrapper_name, DOUBLE, vec![(I64, "%closure".to_string())]); + let wrapper = llmod.define_function( + wrapper_name, + DOUBLE, + crate::expr::body_call::js_body_params(std::iter::empty::()), + ); wrapper.create_block("entry"); let block = wrapper.block_mut(0).unwrap(); let value = block.call(DOUBLE, &getter_name, &[]); diff --git a/crates/perry-codegen/src/codegen/opts.rs b/crates/perry-codegen/src/codegen/opts.rs index 9abe9d0304..d262602e68 100644 --- a/crates/perry-codegen/src/codegen/opts.rs +++ b/crates/perry-codegen/src/codegen/opts.rs @@ -742,7 +742,7 @@ pub struct ShortSpreadMethodCandidate { pub struct ImportedObjectLiteralMethod { pub name: String, pub func_id: u32, - /// Exact closure-call ABI function stored in the live own slot. This is a + /// Exact JS body (`perry_abi::JS_BODY_*`) stored in the live own slot. This is a /// `perry_closure_*` body for a `this`-capturing method and a /// `__perry_wrap_perry_fn_*` wrapper for HIR's lifted no-`this` method. pub target: String, diff --git a/crates/perry-codegen/src/expr/array_callback_shape_tests.rs b/crates/perry-codegen/src/expr/array_callback_shape_tests.rs index 13ad7f81ca..f4e9040c96 100644 --- a/crates/perry-codegen/src/expr/array_callback_shape_tests.rs +++ b/crates/perry-codegen/src/expr/array_callback_shape_tests.rs @@ -228,7 +228,7 @@ fn captureless_inline_some_passes_the_callback_body_directly() { assert!( ir.contains("some.inline.loop") && ir.contains( - "call double @perry_closure_array_some_captureless_ts__99(i64 0, double " + "call double @perry_closure_array_some_captureless_ts__99(i64 0, i64 9222246136947933185, double " ) && ir.contains("call i64 @js_array_live_head(") && ir.contains("call i32 @js_is_truthy("), diff --git a/crates/perry-codegen/src/expr/arrays_finds.rs b/crates/perry-codegen/src/expr/arrays_finds.rs index 6993c12ed7..1259cb2ee1 100644 --- a/crates/perry-codegen/src/expr/arrays_finds.rs +++ b/crates/perry-codegen/src/expr/arrays_finds.rs @@ -673,9 +673,9 @@ pub(crate) fn lower( // 3, 4)`), wrap it in a heap closure so the receiver can call // it via `js_closure_callN`. The wrapper function // `__perry_wrap_` is emitted by `compile_module` for - // every user function and has the closure-call ABI: it takes - // `(closure_ptr, arg0, arg1, ...)` and forwards to the - // underlying function. + // every user function and has the JS body ABI: it takes + // `(closure_ptr, this, arg0, arg1, ...)` and forwards the + // arguments to the underlying function. Expr::FuncRef(id) => { let wrap_name = ctx.func_names.get(id).map_or_else( || crate::codegen::helpers::unknown_func_wrapper_name(ctx.strings.module_prefix()), diff --git a/crates/perry-codegen/src/expr/body_call.rs b/crates/perry-codegen/src/expr/body_call.rs index c7f3fab2b6..2e606b4646 100644 --- a/crates/perry-codegen/src/expr/body_call.rs +++ b/crates/perry-codegen/src/expr/body_call.rs @@ -1,18 +1,59 @@ //! The only builder of calls to a JS BODY (`perry_abi::JS_BODY_*`): -//! `double body(i64 callee, double a0, ...)` — a compiled closure body -//! (`perry_closure_*`, its `$generic` / `$trusted_boxes` / versioned-loop -//! clones) or any body reached through a function object's code pointer. +//! `double body(i64 callee, i64 this, double a0, ...)` — a compiled closure +//! body (`perry_closure_*`, its `$generic` / `$trusted_boxes` / +//! versioned-loop clones), a value wrapper (`__perry_wrap_*`), or any body +//! reached through a function object's code pointer. //! -//! Every emitted call of such a body goes through [`emit_js_body_call`] so -//! that a change to the body ABI (the receiver becoming a parameter) is made -//! here and nowhere else. Private typed clones (`$typed_*`, raw-rep -//! parameters) are not JS bodies and are called by their own emitters. -//! `scripts/check_js_body_call_funnel.sh` refuses an indirect call built -//! anywhere else. +//! Every emitted call of such a body goes through [`emit_js_body_call`], and +//! every definition of one declares its parameters through +//! [`js_body_params`], so the body ABI is spelled here and nowhere else. +//! Private typed clones (`$typed_*`, raw-rep parameters) are not JS bodies +//! and are called by their own emitters. `scripts/check_js_body_call_funnel.py` +//! refuses an indirect call built anywhere else. +//! +//! The receiver (`this`) is passed as NaN-boxed bits in an INTEGER register +//! (owner decision D1), so every floating-point argument register stays free +//! for JS arguments. Stage 1 of this-as-a-parameter: a caller passes exactly +//! the receiver the implicit-`this` cell holds for the call, and bodies still +//! read the cell (`PERRY_THIS_WITNESS=1` builds check that the two agree). use crate::block::LlBlock; use crate::types::{LlvmType, DOUBLE, I64}; +/// The callee parameter of every JS body definition (`perry_abi::JS_BODY_CALLEE_PARAM`). +pub(crate) const JS_BODY_CALLEE: &str = "%this_closure"; +/// The receiver parameter of every JS body definition +/// (`perry_abi::JS_BODY_THIS_PARAM`): NaN-boxed `this` bits, `i64`. +pub(crate) const JS_BODY_THIS: &str = "%js_this"; + +const _: () = assert!(crate::runtime_abi::JS_BODY_CALLEE_PARAM == 0); +const _: () = assert!(crate::runtime_abi::JS_BODY_THIS_PARAM == 1); +const _: () = assert!(crate::runtime_abi::JS_BODY_FIRST_ARG_PARAM == 2); +const _: () = assert!(crate::runtime_abi::JS_BODY_FIXED_PARAMS == 2); + +/// The native parameter list of a JS body DEFINITION: the callee, the +/// receiver, then one `double` per JS parameter name given. +pub(crate) fn js_body_params(js_params: I) -> Vec<(LlvmType, String)> +where + I: IntoIterator, + S: Into, +{ + let mut out = vec![ + (I64, JS_BODY_CALLEE.to_string()), + (I64, JS_BODY_THIS.to_string()), + ]; + out.extend(js_params.into_iter().map(|name| (DOUBLE, name.into()))); + out +} + +/// The native parameter TYPES of a JS body taking `js_arity` JS arguments, +/// for a declaration of a body defined elsewhere. +pub(crate) fn js_body_param_types(js_arity: usize) -> Vec { + let mut out = vec![I64, I64]; + out.extend(std::iter::repeat_n(DOUBLE, js_arity)); + out +} + /// What is being called. #[derive(Clone, Copy)] pub(crate) enum JsBody<'a> { @@ -22,25 +63,30 @@ pub(crate) enum JsBody<'a> { Pointer(&'a str), } -/// The native argument list of a JS body call: the callee, then the JS -/// arguments, in `perry_abi::JS_BODY_*` order. -fn js_body_args<'a>(callee: &'a str, args: &'a [String]) -> Vec<(LlvmType, &'a str)> { - const _: () = assert!(crate::runtime_abi::JS_BODY_CALLEE_PARAM == 0); - const _: () = assert!(crate::runtime_abi::JS_BODY_FIRST_ARG_PARAM == 1); - let mut out: Vec<(LlvmType, &str)> = Vec::with_capacity(args.len() + 1); +/// The native argument list of a JS body call: the callee, the receiver +/// bits, then the JS arguments, in `perry_abi::JS_BODY_*` order. +fn js_body_args<'a>( + callee: &'a str, + this_bits: &'a str, + args: &'a [String], +) -> Vec<(LlvmType, &'a str)> { + let mut out: Vec<(LlvmType, &str)> = Vec::with_capacity(args.len() + 2); out.push((I64, callee)); + out.push((I64, this_bits)); out.extend(args.iter().map(|a| (DOUBLE, a.as_str()))); out } -/// Emit `body(callee, args...)` and return the result value. +/// Emit `body(callee, this, args...)` and return the result value. +/// `this_bits` is an `i64` value (NaN-boxed receiver bits). pub(crate) fn emit_js_body_call( blk: &mut LlBlock, body: JsBody<'_>, callee: &str, + this_bits: &str, args: &[String], ) -> String { - let native = js_body_args(callee, args); + let native = js_body_args(callee, this_bits, args); match body { JsBody::Symbol(name) => blk.call(DOUBLE, name, &native), JsBody::Pointer(ptr) => blk.call_indirect(DOUBLE, ptr, &native), @@ -54,8 +100,61 @@ pub(crate) fn emit_js_body_call_gc_leaf( blk: &mut LlBlock, code_ptr: &str, callee: &str, + this_bits: &str, args: &[String], ) -> String { - let native = js_body_args(callee, args); + let native = js_body_args(callee, this_bits, args); blk.call_indirect_gc_leaf(DOUBLE, code_ptr, &native) } + +/// The receiver bits for a call whose callee binds `this` to `undefined` +/// (and whose implicit-`this` cell the caller set to `undefined`, or whose +/// callee is an arrow and never reads it). +pub(crate) const JS_THIS_UNDEFINED: &str = crate::nanbox::TAG_UNDEFINED_I64; + +/// The receiver bits the implicit-`this` cell holds right now: what a callee +/// entered here would read from the cell. For a direct call whose caller +/// does not bind the cell (stage 1 passes the cell's value unchanged). +pub(crate) fn current_this_bits(ctx: &mut crate::expr::FnCtx<'_>) -> String { + if let Some(cell) = crate::rooting::implicit_this_cell_ptr(ctx) { + return ctx.block().load(I64, &cell); + } + let blk = ctx.block(); + let value = blk.call(DOUBLE, "js_implicit_this_get", &[]); + blk.bitcast_double_to_i64(&value) +} + +/// Whether this compile emits the stage-1 witness (`PERRY_THIS_WITNESS=1`, a +/// COMPILE-time knob — a product build emits nothing and pays nothing). +pub(crate) fn this_witness_enabled() -> bool { + std::env::var_os("PERRY_THIS_WITNESS").is_some_and(|v| v == "1") +} + +/// The site-name constant a witness call names its body by, added to +/// `llmod` BEFORE the body is defined (a definition borrows the module): +/// `Some((global, byte_len))` in a witness build, `None` otherwise. +pub(crate) fn this_witness_site( + llmod: &mut crate::module::LlModule, + site: &str, +) -> Option<(String, usize)> { + this_witness_enabled().then(|| llmod.add_string_constant(site)) +} + +/// In a witness build (`site` from [`this_witness_site`]), emit +/// `js_this_param_witness(%js_this, site, len)` at the entry of a body that +/// reads the implicit-`this` cell: the runtime compares the parameter with +/// the cell and names every body whose caller passed a different receiver. +/// A GC leaf. Nothing when `site` is `None`. +pub(crate) fn emit_this_param_witness(blk: &mut LlBlock, site: Option<&(String, usize)>) { + let Some((global, len)) = site else { + return; + }; + blk.call_void( + "js_this_param_witness", + &[ + (I64, JS_BODY_THIS), + (crate::types::PTR, &format!("@{global}")), + (I64, &len.to_string()), + ], + ); +} diff --git a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs index 5d25349d74..36c6d7d799 100644 --- a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs +++ b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs @@ -477,8 +477,11 @@ fn materialize_compiled_namespace(ctx: &mut FnCtx<'_>, name: &str) -> Result, expr: &Expr) -> Result { .copied() .unwrap_or(0) .min(5); - let mut wrap_param_types: Vec = vec![I64]; - for _ in 0..param_count { - wrap_param_types.push(DOUBLE); - } + let wrap_param_types = crate::expr::body_call::js_body_param_types(param_count); ctx.pending_declares .push((wrap_name.clone(), DOUBLE, wrap_param_types)); let blk = ctx.block(); diff --git a/crates/perry-codegen/src/expr/js_runtime.rs b/crates/perry-codegen/src/expr/js_runtime.rs index fa6edfea2b..b252a59b7c 100644 --- a/crates/perry-codegen/src/expr/js_runtime.rs +++ b/crates/perry-codegen/src/expr/js_runtime.rs @@ -248,7 +248,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // a JS function whose trampoline (perry-jsruntime/src/interop.rs:993, // `native_callback_trampoline`) calls // `func_ptr(closure_env, args_ptr, args_len)` — but Perry closure - // bodies expect `(closure_ptr, arg0, arg1, ...)` per arity. Bridge + // bodies expect `(callee, this, arg0, arg1, ...)` per arity. Bridge // is the `js_closure_call_array` runtime helper added alongside // (`crates/perry-runtime/src/closure.rs`) which takes the i64 // closure pointer and dispatches to the right `js_closure_callN` diff --git a/crates/perry-codegen/src/expr/logical_collections.rs b/crates/perry-codegen/src/expr/logical_collections.rs index 779407509c..c27e09e0ba 100644 --- a/crates/perry-codegen/src/expr/logical_collections.rs +++ b/crates/perry-codegen/src/expr/logical_collections.rs @@ -315,15 +315,22 @@ fn lower_captureless_some_inline( let elem = blk.bitcast_i64_to_double(&elem_bits); let i_double = blk.uitofp(I32, &i, DOUBLE); let recv = nanbox_pointer_inline(blk, &raw); - let mut args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, "0"), (DOUBLE, elem.as_str())]; + let mut args: Vec = vec![elem.clone()]; if param_count >= 2 { - args.push((DOUBLE, i_double.as_str())); + args.push(i_double.clone()); } if param_count >= 3 { - args.push((DOUBLE, recv.as_str())); + args.push(recv.clone()); } - let result = blk.call(DOUBLE, callback_func.trim_start_matches('@'), &args); + // A captureless arrow: no environment (callee 0) and lexical + // `this`, so the receiver parameter is unread. + let result = crate::expr::body_call::emit_js_body_call( + blk, + crate::expr::body_call::JsBody::Symbol(callback_func.trim_start_matches('@')), + "0", + crate::expr::body_call::JS_THIS_UNDEFINED, + &args, + ); let bits = blk.bitcast_double_to_i64(&result); let is_true = blk.icmp_eq(I64, &bits, TAG_TRUE_I64); blk.cond_br(&is_true, &found_l, &slow_l); diff --git a/crates/perry-codegen/src/expr/method_site.rs b/crates/perry-codegen/src/expr/method_site.rs index f53479186e..1aa1253802 100644 --- a/crates/perry-codegen/src/expr/method_site.rs +++ b/crates/perry-codegen/src/expr/method_site.rs @@ -419,10 +419,14 @@ pub(crate) fn emit_method_site( let undefined = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); let pad = crate::runtime_abi::method_site_padded_argc(lowered_args.len()) - lowered_args.len(); call_args.extend(std::iter::repeat_n(undefined, pad)); + // The body gets the receiver as its `this` parameter — the same value + // bound in the cell above, which bodies still read (stage 1). + let recv_bits = ctx.block().bitcast_double_to_i64(recv_box); let hit_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Pointer(&fptr), &handle, + &recv_bits, &call_args, ); match &cell { diff --git a/crates/perry-codegen/src/expr/property_get.rs b/crates/perry-codegen/src/expr/property_get.rs index f906cd9769..8de29180c1 100644 --- a/crates/perry-codegen/src/expr/property_get.rs +++ b/crates/perry-codegen/src/expr/property_get.rs @@ -1242,10 +1242,8 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { .copied() .unwrap_or(0) .min(5); - let mut wrap_param_types: Vec = vec![I64]; - for _ in 0..param_count { - wrap_param_types.push(DOUBLE); - } + let wrap_param_types = + crate::expr::body_call::js_body_param_types(param_count); ctx.pending_declares .push((wrap_name.clone(), DOUBLE, wrap_param_types)); let blk = ctx.block(); diff --git a/crates/perry-codegen/src/expr/super_method.rs b/crates/perry-codegen/src/expr/super_method.rs index e59b29af56..b9fa7f041f 100644 --- a/crates/perry-codegen/src/expr/super_method.rs +++ b/crates/perry-codegen/src/expr/super_method.rs @@ -340,12 +340,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // 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 same `(i64)` + 0 doubles - // shape the imported-function-ref site at `expr/mod.rs:12331` - // uses for unknown-arity imports. + // ClosureHeader's func_ptr); 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); - ctx.pending_declares - .push((wrap_name.clone(), DOUBLE, vec![I64])); + ctx.pending_declares.push(( + wrap_name.clone(), + DOUBLE, + 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)]); diff --git a/crates/perry-codegen/src/gc_call_effects.rs b/crates/perry-codegen/src/gc_call_effects.rs index 99df4be74c..bdbb5f0950 100644 --- a/crates/perry-codegen/src/gc_call_effects.rs +++ b/crates/perry-codegen/src/gc_call_effects.rs @@ -276,6 +276,10 @@ pub(crate) fn classify_direct_callee(name: &str) -> GcCallEffect { // and reads globalThis, which allocates. | "js_implicit_this_set" | "js_implicit_this_get" + // Witness builds only (`PERRY_THIS_WITNESS=1`): compares its argument + // with the same cell `_get` reads, bumps two atomics and may write + // stderr. No Perry allocation, no re-entry into generated code. + | "js_this_param_witness" | "js_new_target_get" | "js_new_target_set" // #8596: TDZ-suppression window depth (`box.rs`). Each is a single diff --git a/crates/perry-codegen/src/lower_call/atomics.rs b/crates/perry-codegen/src/lower_call/atomics.rs index 84a5654cee..b01a101c82 100644 --- a/crates/perry-codegen/src/lower_call/atomics.rs +++ b/crates/perry-codegen/src/lower_call/atomics.rs @@ -5,7 +5,6 @@ use perry_hir::Expr; use crate::expr::{lower_expr, FnCtx}; use crate::nanbox::double_literal; -use crate::types::{DOUBLE, PTR}; fn is_global_this_atomics_expr(e: &Expr) -> bool { matches!( @@ -60,9 +59,15 @@ pub fn try_lower_atomics_static_call( let _ = lower_expr(ctx, arg)?; } - let mut call_args: Vec<(crate::types::LlvmType, &str)> = vec![(PTR, "null")]; - for value in &lowered { - call_args.push((DOUBLE, value.as_str())); - } - Ok(Some(ctx.block().call(DOUBLE, runtime_fn, &call_args))) + // The `js_atomics_*` natives are JS bodies (the function objects + // `Atomics.load` &c. run): no environment, and this direct call does not + // bind the implicit-`this` cell, so they get the cell's receiver. + let this_bits = crate::expr::body_call::current_this_bits(ctx); + Ok(Some(crate::expr::body_call::emit_js_body_call( + ctx.block(), + crate::expr::body_call::JsBody::Symbol(runtime_fn), + "0", + &this_bits, + &lowered, + ))) } diff --git a/crates/perry-codegen/src/lower_call/early_branches.rs b/crates/perry-codegen/src/lower_call/early_branches.rs index 444a8b31a7..769a7bfbf5 100644 --- a/crates/perry-codegen/src/lower_call/early_branches.rs +++ b/crates/perry-codegen/src/lower_call/early_branches.rs @@ -481,10 +481,13 @@ pub fn try_lower_closure_typed_local_call( // immediate side exit before any cold arm that can run // user code or collect. Hot returns cannot collect; cold // returns never observe caller-side cached heap handles. + // An arrow (the versioned-loop clone admits only arrows): + // lexical `this`, so the receiver parameter is unread. let value = crate::expr::body_call::emit_js_body_call_gc_leaf( ctx.block(), &target, &closure_handle, + crate::expr::body_call::JS_THIS_UNDEFINED, &direct_args, ); callee_group.release(ctx); @@ -506,10 +509,13 @@ pub fn try_lower_closure_typed_local_call( ctx.block().cond_br(&fast_ok, &fast_label, &fallback_label); ctx.current_block = fast_idx; + // The resolved target is an arrow (lexical `this`): the + // receiver parameter is unread. let fast_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Pointer(&target), &closure_handle, + crate::expr::body_call::JS_THIS_UNDEFINED, &lowered_args, ); let after_fast = ctx.block().label.clone(); @@ -688,6 +694,14 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = fast_idx; + // The body is handed the receiver the implicit-`this` cell + // holds for this call (stage 1): `undefined` when this site + // bound it above, else whatever the cell already holds. + let direct_this_bits = if prev_this.is_some() { + crate::expr::body_call::JS_THIS_UNDEFINED.to_string() + } else { + crate::expr::body_call::current_this_bits(ctx) + }; let typed_f64_param_reps = if ctx.typed_f64_closures.contains(&func_id) { ctx.typed_i1_closure_param_reps .get(&func_id) @@ -802,6 +816,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, + &direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -902,6 +917,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, + &direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1005,6 +1021,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, + &direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1111,6 +1128,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, + &direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1150,6 +1168,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_fn), &closure_handle, + &direct_this_bits, &lowered_args, ) }; 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 744661b7b6..4583b096e4 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 @@ -207,9 +207,7 @@ pub(super) fn try_lower_imported_object_method_call( let key_index = ctx.strings.intern(property); let dispatch_global = ctx.strings.static_dispatch_global(key_index); let closure_symbol = method.target.clone(); - let mut closure_params = Vec::with_capacity(method.param_count + 1); - closure_params.push(I64); - closure_params.extend(std::iter::repeat_n(DOUBLE, method.param_count)); + let closure_params = crate::expr::body_call::js_body_param_types(method.param_count); ctx.pending_declares .push((closure_symbol.clone(), DOUBLE, closure_params)); @@ -244,10 +242,14 @@ pub(super) fn try_lower_imported_object_method_call( &closure_symbol, &fallback_label, ); + // This direct call does not bind the implicit-`this` cell, so the body + // is handed the receiver the cell already holds (stage 1). + let this_bits = crate::expr::body_call::current_this_bits(ctx); let direct_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_symbol), &closure_handle, + &this_bits, &lowered_args, ); let direct_end = ctx.block().label.clone(); @@ -381,9 +383,8 @@ pub(super) fn try_lower_dynamic_object_method_call( .cond_br(&receiver_matches, &cache_label, &miss_label); let closure_symbol = candidate.method.target.clone(); - let mut closure_params = Vec::with_capacity(candidate.method.param_count + 1); - closure_params.push(I64); - closure_params.extend(std::iter::repeat_n(DOUBLE, candidate.method.param_count)); + let closure_params = + crate::expr::body_call::js_body_param_types(candidate.method.param_count); ctx.pending_declares .push((closure_symbol.clone(), DOUBLE, closure_params)); let closure_handle = emit_cached_own_method_guard( @@ -397,10 +398,13 @@ pub(super) fn try_lower_dynamic_object_method_call( &closure_symbol, &miss_label, ); + // As above: the cell is not bound here, the body gets its value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); let direct_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_symbol), &closure_handle, + &this_bits, &lowered_args, ); let direct_end = ctx.block().label.clone(); 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 d981b3fd89..f6bab8ca1f 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 @@ -45,34 +45,58 @@ pub(crate) fn declare_core(module: &mut LlModule) { module.declare_function("js_math_tan", DOUBLE, &[DOUBLE]); // ========== Atomics ========== - module.declare_function("js_atomics_load", DOUBLE, &[PTR, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_is_lock_free", DOUBLE, &[PTR, DOUBLE]); - module.declare_function("js_atomics_store", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_add", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_sub", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_and", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_or", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); - module.declare_function("js_atomics_xor", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); + module.declare_function("js_atomics_load", DOUBLE, &[I64, I64, DOUBLE, DOUBLE]); + module.declare_function("js_atomics_is_lock_free", DOUBLE, &[I64, I64, DOUBLE]); + module.declare_function( + "js_atomics_store", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], + ); + module.declare_function( + "js_atomics_add", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], + ); + module.declare_function( + "js_atomics_sub", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], + ); + module.declare_function( + "js_atomics_and", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], + ); + module.declare_function("js_atomics_or", DOUBLE, &[I64, I64, DOUBLE, DOUBLE, DOUBLE]); + module.declare_function( + "js_atomics_xor", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], + ); module.declare_function( "js_atomics_exchange", DOUBLE, - &[PTR, DOUBLE, DOUBLE, DOUBLE], + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], ); module.declare_function( "js_atomics_compare_exchange", DOUBLE, - &[PTR, DOUBLE, DOUBLE, DOUBLE, DOUBLE], + &[I64, I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE], + ); + module.declare_function( + "js_atomics_notify", + DOUBLE, + &[I64, I64, DOUBLE, DOUBLE, DOUBLE], ); - module.declare_function("js_atomics_notify", DOUBLE, &[PTR, DOUBLE, DOUBLE, DOUBLE]); module.declare_function( "js_atomics_wait", DOUBLE, - &[PTR, DOUBLE, DOUBLE, DOUBLE, DOUBLE], + &[I64, I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE], ); module.declare_function( "js_atomics_wait_async", DOUBLE, - &[PTR, DOUBLE, DOUBLE, DOUBLE, DOUBLE], + &[I64, I64, DOUBLE, DOUBLE, DOUBLE, DOUBLE], ); // ========== Number ========== diff --git a/crates/perry-codegen/src/runtime_decls/strings_part2.rs b/crates/perry-codegen/src/runtime_decls/strings_part2.rs index 623fea225f..568f069485 100644 --- a/crates/perry-codegen/src/runtime_decls/strings_part2.rs +++ b/crates/perry-codegen/src/runtime_decls/strings_part2.rs @@ -11,6 +11,10 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) { // by a `PERRY_RECV_ROUTE_COUNT=1` build; declared unconditionally so the // declaration set does not depend on a diagnostic knob. module.declare_function("js_recv_route_note", VOID, &[I32]); + // This-as-a-parameter stage-1 witness (`expr/body_call.rs`): emitted only + // by a `PERRY_THIS_WITNESS=1` build; declared unconditionally for the same + // reason. + module.declare_function("js_this_param_witness", VOID, &[I64, PTR, I64]); // RegExp exec module.declare_function("js_regexp_exec", I64, &[I64, I64]); module.declare_function("js_number_to_precision", I64, &[DOUBLE, DOUBLE]); diff --git a/crates/perry-codegen/src/stubs.rs b/crates/perry-codegen/src/stubs.rs index 72528f98e8..14d000fd94 100644 --- a/crates/perry-codegen/src/stubs.rs +++ b/crates/perry-codegen/src/stubs.rs @@ -27,10 +27,11 @@ use crate::nanbox::TAG_UNDEFINED; /// 3. **Identity functions** — `double(double)` functions that pass their /// argument through unchanged. Used for `js_await_any_promise` and /// similar pass-through points where the V8 runtime is not present. -/// 4. **Closure-wrapper symbols** — `double(i64, double, double, double, +/// 4. **Closure-wrapper symbols** — `double(i64, i64, double, double, /// double, double, double)` functions returning NaN-boxed `TAG_UNDEFINED`. -/// Match the closure-call ABI emitted by `expr.rs:~11783` for cross- -/// module function-VALUE references (`__perry_wrap_perry_fn___`). +/// The JS body ABI (`perry_abi::JS_BODY_*`: callee, receiver, arguments) +/// of the wrappers cross-module function-VALUE references name +/// (`__perry_wrap_perry_fn___`). /// Used when a non-entry module fails to compile under `--allow-unimplemented` /// so the link still succeeds; consumers that read the import as a closure /// handle observe a singleton wrapping the undefined-return stub. @@ -105,13 +106,12 @@ pub fn generate_stub_object_full( )); } - // 4. Closure-wrapper stubs — match the closure-call ABI emitted at - // `crates/perry-codegen/src/expr.rs:~11783` for cross-module - // function-VALUE references: `double name(i64 closure, double a0, - // double a1, double a2, double a3, double a4)`. Return NaN-boxed - // TAG_UNDEFINED. The 5-double arg list covers the max-arity 5 - // that `js_closure_call0..5` dispatches through; signature shape - // matches the no-op variable/class branch at codegen.rs:~2750. + // 4. Closure-wrapper stubs — the JS body ABI of the wrappers + // cross-module function-VALUE references name: `double name(i64 + // callee, i64 this, double a0, double a1, double a2, double a3, + // double a4)`. Return NaN-boxed TAG_UNDEFINED. The stub reads no + // argument, so its declared width is informational; it matches the + // no-op variable/class wrappers in `codegen/export_value_wrappers.rs`. // // Used by the failed-module stub block at // `crates/perry/src/commands/compile.rs:~5518` so consumers that @@ -126,7 +126,7 @@ pub fn generate_stub_object_full( // Refs #903 / uuid `__perry_wrap_perry_fn_...sha1_js__default`. for name in wrapper_func_symbols { ll.push_str(&format!( - "define double @{}(i64 %0, double %1, double %2, double %3, double %4, double %5) {{\n ret double {}\n}}\n\n", + "define double @{}(i64 %0, i64 %1, double %2, double %3, double %4, double %5, double %6) {{\n ret double {}\n}}\n\n", name, undef_hex )); } diff --git a/crates/perry-codegen/src/wasm32/closure_abi.rs b/crates/perry-codegen/src/wasm32/closure_abi.rs index f5276f510d..d9d99a163f 100644 --- a/crates/perry-codegen/src/wasm32/closure_abi.rs +++ b/crates/perry-codegen/src/wasm32/closure_abi.rs @@ -1,10 +1,11 @@ //! Give every closure body the runtime's view of its first parameter. //! -//! Codegen defines a closure body as `(i64 %this_closure, double …)`: the -//! closure pointer travels as an `i64` like every other address it carries. -//! The runtime invokes those bodies through `extern "C" fn(*const -//! ClosureHeader, f64 …)` (and defines its own native-closure bodies that -//! way), so on wasm32 the first slot is an `i32` pointer. A native C ABI +//! Codegen defines a closure body as `(i64 %this_closure, i64 %js_this, +//! double …)` (`perry_abi::JS_BODY_*`): the closure pointer travels as an +//! `i64` like every other address it carries. The runtime invokes those +//! bodies through `extern "C" fn(*const ClosureHeader, JsThis, f64 …)` (and +//! defines its own native-closure bodies that way), so on wasm32 the first +//! slot is an `i32` pointer; the receiver is a `u64` on both sides. A native C ABI //! does not care; a wasm `call_indirect` must match exactly, and traps. //! //! This pass retypes each such definition to take `ptr %this_closure.ptr` diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index 3596c4e100..10767fb42c 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -5,7 +5,7 @@ clearImmediate void i64 clearInterval void i64 clearTimeout void i64 gc_check_trigger_export void -global_this_builtin_noop_thunk f64 ptr,f64 +global_this_builtin_noop_thunk f64 ptr,i64,f64 js_abort_controller_abort void ptr js_abort_controller_abort_reason void ptr,f64 js_abort_controller_new ptr @@ -289,19 +289,19 @@ js_async_resource_trigger_async_id f64 i64 js_async_step_chain ptr f64,ptr js_async_step_done ptr f64,ptr js_atob ptr f64 -js_atomics_add f64 ptr,f64,f64,f64 -js_atomics_and f64 ptr,f64,f64,f64 -js_atomics_compare_exchange f64 ptr,f64,f64,f64,f64 -js_atomics_exchange f64 ptr,f64,f64,f64 -js_atomics_is_lock_free f64 ptr,f64 -js_atomics_load f64 ptr,f64,f64 -js_atomics_notify f64 ptr,f64,f64,f64 -js_atomics_or f64 ptr,f64,f64,f64 -js_atomics_store f64 ptr,f64,f64,f64 -js_atomics_sub f64 ptr,f64,f64,f64 -js_atomics_wait f64 ptr,f64,f64,f64,f64 -js_atomics_wait_async f64 ptr,f64,f64,f64,f64 -js_atomics_xor f64 ptr,f64,f64,f64 +js_atomics_add f64 ptr,i64,f64,f64,f64 +js_atomics_and f64 ptr,i64,f64,f64,f64 +js_atomics_compare_exchange f64 ptr,i64,f64,f64,f64,f64 +js_atomics_exchange f64 ptr,i64,f64,f64,f64 +js_atomics_is_lock_free f64 ptr,i64,f64 +js_atomics_load f64 ptr,i64,f64,f64 +js_atomics_notify f64 ptr,i64,f64,f64,f64 +js_atomics_or f64 ptr,i64,f64,f64,f64 +js_atomics_store f64 ptr,i64,f64,f64,f64 +js_atomics_sub f64 ptr,i64,f64,f64,f64 +js_atomics_wait f64 ptr,i64,f64,f64,f64,f64 +js_atomics_wait_async f64 ptr,i64,f64,f64,f64,f64 +js_atomics_xor f64 ptr,i64,f64,f64,f64 js_aux_has_active i32s js_await_any_promise f64 f64 js_await_js_promise f64 f64 @@ -2759,7 +2759,7 @@ js_perf_measure f64 f64,f64,f64 js_perf_monitor_event_loop_delay f64 f64 js_perf_node_timing f64 js_perf_observer_disconnect f64 f64 -js_perf_observer_flush_all f64 ptr +js_perf_observer_flush_all f64 ptr,i64 js_perf_observer_new f64 f64 js_perf_observer_observe f64 f64,f64 js_perf_observer_take_records f64 f64 @@ -3699,6 +3699,7 @@ js_text_encoder_encode_llvm i64 f64 js_text_encoder_new i64 js_text_encoder_stream_new f64 js_text_encoding_stream_new f64 +js_this_param_witness void i64,ptr,i64 js_thread_has_pending i32s js_thread_parallel_filter f64 f64,f64 js_thread_parallel_map f64 f64,f64 @@ -3808,20 +3809,20 @@ js_try_end void js_try_push ptr js_tty_isatty f64 f64 js_tty_read_stream_new f64 f64 -js_tty_read_stream_set_raw_mode f64 ptr,f64 +js_tty_read_stream_set_raw_mode f64 ptr,i64,f64 js_tty_resize_drain i32s -js_tty_write_stream_clear_line f64 ptr,f64,f64 -js_tty_write_stream_clear_screen_down f64 ptr,f64 -js_tty_write_stream_cursor_to f64 ptr,f64,f64,f64 -js_tty_write_stream_get_color_depth f64 ptr,f64 -js_tty_write_stream_get_window_size f64 ptr -js_tty_write_stream_has_colors f64 ptr,f64,f64 -js_tty_write_stream_move_cursor f64 ptr,f64,f64,f64 +js_tty_write_stream_clear_line f64 ptr,i64,f64,f64 +js_tty_write_stream_clear_screen_down f64 ptr,i64,f64 +js_tty_write_stream_cursor_to f64 ptr,i64,f64,f64,f64 +js_tty_write_stream_get_color_depth f64 ptr,i64,f64 +js_tty_write_stream_get_window_size f64 ptr,i64 +js_tty_write_stream_has_colors f64 ptr,i64,f64,f64 +js_tty_write_stream_move_cursor f64 ptr,i64,f64,f64,f64 js_tty_write_stream_new f64 f64 -js_tty_write_stream_on f64 ptr,f64,f64 -js_tty_write_stream_refresh_size f64 ptr -js_tty_write_stream_remove_all_listeners f64 ptr,f64 -js_tty_write_stream_remove_listener f64 ptr,f64,f64 +js_tty_write_stream_on f64 ptr,i64,f64,f64 +js_tty_write_stream_refresh_size f64 ptr,i64 +js_tty_write_stream_remove_all_listeners f64 ptr,i64,f64 +js_tty_write_stream_remove_listener f64 ptr,i64,f64,f64 js_typed_array_at f64 ptr,f64 js_typed_array_copy_within ptr ptr,f64,f64,f64 js_typed_array_every f64 ptr,ptr @@ -4153,12 +4154,12 @@ js_vm_synthetic_module_set_export f64 i64,f64,f64 js_wait_for_event void js_wasi_constructor_call f64 f64 js_wasi_emit_warning f64 -js_wasi_finalize_bindings f64 ptr,f64,f64 -js_wasi_get_import_object f64 ptr -js_wasi_import_stub f64 ptr,f64,f64,f64,f64 -js_wasi_initialize f64 ptr,f64 +js_wasi_finalize_bindings f64 ptr,i64,f64,f64 +js_wasi_get_import_object f64 ptr,i64 +js_wasi_import_stub f64 ptr,i64,f64,f64,f64,f64 +js_wasi_initialize f64 ptr,i64,f64 js_wasi_new f64 f64 -js_wasi_start f64 ptr,f64 +js_wasi_start f64 ptr,i64,f64 js_weak_collection_subclass_init f64 f64,i32s,f64 js_weak_throw_primitive f64 js_weakmap_delete f64 f64,f64 @@ -4673,7 +4674,7 @@ perry_string_header_abi_revision i32u perry_stub_warn_ffi void ptr,ptr,ptr perry_thread_exit_probe_object_prototype_recorded bool usize perry_transition_cache_base ptr -perry_tui_state_setter_trampoline f64 ptr,f64 +perry_tui_state_setter_trampoline f64 ptr,i64,f64 perry_update_notify_startup void ptr,i32s perry_updater_check_headers ptr f64 perry_updater_check_url ptr f64 diff --git a/crates/perry-ext-events/src/dispatch_ext.rs b/crates/perry-ext-events/src/dispatch_ext.rs index 482a6330be..9aa471a6ed 100644 --- a/crates/perry-ext-events/src/dispatch_ext.rs +++ b/crates/perry-ext-events/src/dispatch_ext.rs @@ -193,7 +193,7 @@ mod tests { use super::*; use perry_ffi::alloc_string; - extern "C" fn noop_listener(_c: *const RawClosureHeader) -> f64 { + extern "C" fn noop_listener(_c: *const RawClosureHeader, _this: perry_ffi::JsThis) -> f64 { undefined_value() } diff --git a/crates/perry-ext-events/src/lib.rs b/crates/perry-ext-events/src/lib.rs index 5b6f443e53..ad03ac4319 100644 --- a/crates/perry-ext-events/src/lib.rs +++ b/crates/perry-ext-events/src/lib.rs @@ -891,7 +891,11 @@ unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i6 wrapper as i64 } -extern "C" fn event_emitter_once_wrapper(closure: *const RawClosureHeader, rest: f64) -> f64 { +extern "C" fn event_emitter_once_wrapper( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + rest: f64, +) -> f64 { unsafe { let handle = js_closure_get_capture_ptr(closure, 0) as Handle; let event_name_ptr = js_closure_get_capture_ptr(closure, 1) as *const StringHeader; @@ -1158,6 +1162,7 @@ unsafe fn call_emitter_listener(handle: Handle, callback: i64, args: &[f64]) -> extern "C" fn events_capture_rejection_handler( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, reason: f64, ) -> f64 { unsafe { diff --git a/crates/perry-ext-events/src/module_iterators.rs b/crates/perry-ext-events/src/module_iterators.rs index e7584299df..ced03a4fde 100644 --- a/crates/perry-ext-events/src/module_iterators.rs +++ b/crates/perry-ext-events/src/module_iterators.rs @@ -2,6 +2,7 @@ use super::*; pub(super) extern "C" fn events_once_event_target_listener( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, arg0: f64, ) -> f64 { unsafe { @@ -21,7 +22,10 @@ pub(super) extern "C" fn events_once_event_target_listener( undefined_value() } -pub(super) extern "C" fn events_once_abort_listener(closure: *const RawClosureHeader) -> f64 { +pub(super) extern "C" fn events_once_abort_listener( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { let handle = js_closure_get_capture_ptr(closure, 0) as Handle; let promise = js_closure_get_capture_ptr(closure, 1) as *mut Promise; @@ -40,6 +44,7 @@ pub(super) extern "C" fn events_once_abort_listener(closure: *const RawClosureHe pub(super) extern "C" fn events_once_stream_resolve_listener( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, rest: f64, ) -> f64 { unsafe { @@ -79,6 +84,7 @@ pub(super) extern "C" fn events_once_stream_resolve_listener( pub(super) extern "C" fn events_once_stream_reject_listener( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, rest: f64, ) -> f64 { unsafe { @@ -251,6 +257,7 @@ fn events_on_finish_pending(state: *mut ArrayHeader, reason: Option) { /// immediately, otherwise retain the argument tuple in FIFO order. pub(super) extern "C" fn events_on_queue_listener( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, arg0: f64, ) -> f64 { unsafe { @@ -289,7 +296,7 @@ pub(super) extern "C" fn events_on_queue_listener( f64::from_bits(TAG_UNDEFINED_F64_BITS) } -extern "C" fn events_on_next(closure: *const RawClosureHeader) -> f64 { +extern "C" fn events_on_next(closure: *const RawClosureHeader, _this: perry_ffi::JsThis) -> f64 { unsafe { let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; if state.is_null() { @@ -318,7 +325,7 @@ extern "C" fn events_on_next(closure: *const RawClosureHeader) -> f64 { } } -extern "C" fn events_on_return(closure: *const RawClosureHeader) -> f64 { +extern "C" fn events_on_return(closure: *const RawClosureHeader, _this: perry_ffi::JsThis) -> f64 { unsafe { let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; if state.is_null() { @@ -366,11 +373,17 @@ extern "C" fn events_on_return(closure: *const RawClosureHeader) -> f64 { } } -extern "C" fn events_on_iterator_self(closure: *const RawClosureHeader) -> f64 { +extern "C" fn events_on_iterator_self( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { js_closure_get_capture_f64(closure, 0) } } -extern "C" fn events_on_async_iterator(closure: *const RawClosureHeader) -> f64 { +extern "C" fn events_on_async_iterator( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; let scope = TransientRootScope::enter(); @@ -443,7 +456,10 @@ pub(super) unsafe fn events_on_install_async_iterator( } /// A configured close event ends the iterator after already-buffered events. -pub(super) extern "C" fn events_on_close_listener(closure: *const RawClosureHeader) -> f64 { +pub(super) extern "C" fn events_on_close_listener( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; if !state.is_null() { @@ -454,7 +470,10 @@ pub(super) extern "C" fn events_on_close_listener(closure: *const RawClosureHead undefined_value() } -pub(super) extern "C" fn events_on_abort_listener(closure: *const RawClosureHeader) -> f64 { +pub(super) extern "C" fn events_on_abort_listener( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { let handle = js_closure_get_capture_ptr(closure, 0) as Handle; let data_listener = js_closure_get_capture_ptr(closure, 1); diff --git a/crates/perry-ext-events/src/module_on.rs b/crates/perry-ext-events/src/module_on.rs index a583696c80..5f58d453f3 100644 --- a/crates/perry-ext-events/src/module_on.rs +++ b/crates/perry-ext-events/src/module_on.rs @@ -156,7 +156,10 @@ pub unsafe extern "C" fn js_events_on( (queue_root.get().to_bits() & POINTER_MASK) as *mut ArrayHeader } -extern "C" fn events_abort_listener_dispose(closure: *const RawClosureHeader) -> f64 { +extern "C" fn events_abort_listener_dispose( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { unsafe { let signal_ptr = js_closure_get_capture_ptr(closure, 0); let callback_ptr = js_closure_get_capture_ptr(closure, 1); diff --git a/crates/perry-ext-events/src/tests.rs b/crates/perry-ext-events/src/tests.rs index d65797ac45..53975ccf39 100644 --- a/crates/perry-ext-events/src/tests.rs +++ b/crates/perry-ext-events/src/tests.rs @@ -58,7 +58,7 @@ fn young_promise_root() -> *mut Promise { /// The listener-registration FFIs take raw NaN-box bits and validate that the /// value is a closure. Allocate a real closure and return its NaN-boxed bits /// as i64; the sentinel function pointer is never invoked in these tests. -extern "C" fn noop_listener(_c: *const RawClosureHeader) -> f64 { +extern "C" fn noop_listener(_c: *const RawClosureHeader, _this: perry_ffi::JsThis) -> f64 { f64::from_bits(TAG_UNDEFINED_F64_BITS) } diff --git a/crates/perry-ext-http/src/agent.rs b/crates/perry-ext-http/src/agent.rs index 4ffc0f4c07..02b9c588c6 100644 --- a/crates/perry-ext-http/src/agent.rs +++ b/crates/perry-ext-http/src/agent.rs @@ -321,7 +321,10 @@ unsafe fn read_string_field(obj_f64: f64, field: &str) -> Option { perry_ffi::read_string(js).map(String::from) } -extern "C" fn agent_connection_abort_listener(closure: *const RawClosureHeader) -> f64 { +extern "C" fn agent_connection_abort_listener( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { let socket = unsafe { perry_ffi::closure_capture_f64(closure, 0) } as i64; let signal_value = unsafe { perry_ffi::closure_capture_f64(closure, 1) }; unsafe { diff --git a/crates/perry-ext-http/src/client_abort.rs b/crates/perry-ext-http/src/client_abort.rs index e9f08a177e..95b6c1caee 100644 --- a/crates/perry-ext-http/src/client_abort.rs +++ b/crates/perry-ext-http/src/client_abort.rs @@ -14,7 +14,10 @@ fn abort_event_value() -> f64 { f64::from_bits(JsValue::from_string_ptr(alloc_string("abort").as_raw()).bits()) } -extern "C" fn request_signal_listener(closure: *const RawClosureHeader) -> f64 { +extern "C" fn request_signal_listener( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { let request_handle = unsafe { perry_ffi::closure_capture_f64(closure, 0) } as i64; push_event(PendingHttpEvent::SignalAbort { request_handle }); f64::from_bits(TAG_UNDEFINED) diff --git a/crates/perry-ext-http/src/client_request_surface.rs b/crates/perry-ext-http/src/client_request_surface.rs index fc903c409d..1415e32166 100644 --- a/crates/perry-ext-http/src/client_request_surface.rs +++ b/crates/perry-ext-http/src/client_request_surface.rs @@ -72,7 +72,11 @@ pub(crate) fn create_client_once_wrapper( wrapper.get() } -extern "C" fn client_once_wrapper(closure: *const RawClosureHeader, rest: f64) -> f64 { +extern "C" fn client_once_wrapper( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + rest: f64, +) -> f64 { unsafe { let handle = perry_ffi::closure_capture_f64(closure, 0) as Handle; let event_value = perry_ffi::closure_capture_f64(closure, 1); diff --git a/crates/perry-ext-http/src/lib.rs b/crates/perry-ext-http/src/lib.rs index bc7a9d6151..e0a9f74caf 100644 --- a/crates/perry-ext-http/src/lib.rs +++ b/crates/perry-ext-http/src/lib.rs @@ -956,6 +956,7 @@ unsafe fn invoke_create_socket( /// small handle on some codegen paths). unsafe extern "C" fn http_create_socket_cb( closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, err: f64, socket: f64, ) -> f64 { 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 3db7633719..e6a2e08252 100644 --- a/crates/perry-ext-http/src/server/server/upgrade_tests.rs +++ b/crates/perry-ext-http/src/server/server/upgrade_tests.rs @@ -2,7 +2,13 @@ use super::*; use perry_ffi::{alloc_closure, closure_capture_f64, set_closure_capture_f64, RawClosureHeader}; -extern "C" fn observe(closure: *const RawClosureHeader, req: f64, socket: f64, head: f64) -> f64 { +extern "C" fn observe( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + req: f64, + socket: f64, + head: f64, +) -> f64 { unsafe { let count = closure_capture_f64(closure, 0); set_closure_capture_f64(closure as *mut _, 0, count + 1.0); diff --git a/crates/perry-ext-http/src/server/types.rs b/crates/perry-ext-http/src/server/types.rs index e545aeb9ff..9cd8393961 100644 --- a/crates/perry-ext-http/src/server/types.rs +++ b/crates/perry-ext-http/src/server/types.rs @@ -410,7 +410,10 @@ mod tests { ); } - extern "C" fn listen_test_callback() -> f64 { + extern "C" fn listen_test_callback( + _closure: *const perry_ffi::RawClosureHeader, + _this: perry_ffi::JsThis, + ) -> f64 { f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-ext-net/src/pipe.rs b/crates/perry-ext-net/src/pipe.rs index 13518d7010..491a9b226c 100644 --- a/crates/perry-ext-net/src/pipe.rs +++ b/crates/perry-ext-net/src/pipe.rs @@ -105,7 +105,11 @@ fn generic_end(dest: f64) { } } -extern "C" fn pipe_data_forward(closure: *const RawClosureHeader, chunk: f64) -> f64 { +extern "C" fn pipe_data_forward( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + chunk: f64, +) -> f64 { if !closure.is_null() { let dest = unsafe { closure_capture_f64(closure, 0) }; generic_write(dest, chunk); @@ -113,7 +117,7 @@ extern "C" fn pipe_data_forward(closure: *const RawClosureHeader, chunk: f64) -> f64::from_bits(TAG_UNDEFINED_BITS) } -extern "C" fn pipe_end_forward(closure: *const RawClosureHeader) -> f64 { +extern "C" fn pipe_end_forward(closure: *const RawClosureHeader, _this: perry_ffi::JsThis) -> f64 { if !closure.is_null() { let dest = unsafe { closure_capture_f64(closure, 0) }; let end_on_finish = unsafe { closure_capture_f64(closure, 1) }; diff --git a/crates/perry-ext-typescript/src/bun.rs b/crates/perry-ext-typescript/src/bun.rs index 9a47cd891e..c3bd20acf3 100644 --- a/crates/perry-ext-typescript/src/bun.rs +++ b/crates/perry-ext-typescript/src/bun.rs @@ -779,11 +779,21 @@ fn register_plugin_hook( f64::from_bits(JsValue::UNDEFINED.bits()) } -extern "C" fn bun_on_resolve(closure: *const RawClosureHeader, options: f64, callback: f64) -> f64 { +extern "C" fn bun_on_resolve( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + options: f64, + callback: f64, +) -> f64 { register_plugin_hook(closure, options, callback, true) } -extern "C" fn bun_on_load(closure: *const RawClosureHeader, options: f64, callback: f64) -> f64 { +extern "C" fn bun_on_load( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + options: f64, + callback: f64, +) -> f64 { register_plugin_hook(closure, options, callback, false) } @@ -1284,7 +1294,10 @@ fn array_from_values(values: impl IntoIterator) -> JsValue { JsValue::from_object_ptr(array.get() as *mut ArrayHeader) } -extern "C" fn bun_build_output_text(closure: *const RawClosureHeader) -> f64 { +extern "C" fn bun_build_output_text( + closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, +) -> f64 { let scope = TransientRootScope::enter(); let contents = scope.root_nanbox(unsafe { closure_capture_f64(closure, 0) }); let promise = JsPromise::new(); diff --git a/crates/perry-ext-ws/src/tests.rs b/crates/perry-ext-ws/src/tests.rs index fd93e45173..36ed5750d3 100644 --- a/crates/perry-ext-ws/src/tests.rs +++ b/crates/perry-ext-ws/src/tests.rs @@ -198,19 +198,28 @@ fn server_clients_is_a_stable_set_that_tracks_connections() { static FIRST_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); static SECOND_CALLS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); -extern "C" fn count_second(_c: *const perry_runtime::closure::ClosureHeader) -> f64 { +extern "C" fn count_second( + _c: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { SECOND_CALLS.fetch_add(1, Ordering::SeqCst); f64::from_bits(JsValue::UNDEFINED.bits()) } -extern "C" fn count_first(_c: *const perry_runtime::closure::ClosureHeader) -> f64 { +extern "C" fn count_first( + _c: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { FIRST_CALLS.fetch_add(1, Ordering::SeqCst); f64::from_bits(JsValue::UNDEFINED.bits()) } /// Counts its calls and registers a `count_second` listener, from inside /// this callback, on the server handle in its capture slot 0. -extern "C" fn count_first_and_register(c: *const perry_runtime::closure::ClosureHeader) -> f64 { +extern "C" fn count_first_and_register( + c: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> 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)); @@ -230,7 +239,12 @@ 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) -> f64) -> i64 { +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 } diff --git a/crates/perry-ext-zlib/src/lib.rs b/crates/perry-ext-zlib/src/lib.rs index 1f7308b970..1ce6214788 100644 --- a/crates/perry-ext-zlib/src/lib.rs +++ b/crates/perry-ext-zlib/src/lib.rs @@ -492,6 +492,7 @@ mod tests { extern "C" fn record_dispatch_callback( _closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, err: f64, value: f64, ) -> f64 { @@ -593,7 +594,12 @@ mod tests { #[test] fn external_dispatch_accepts_options_and_honors_level_zero() { - extern "C" fn callback(_closure: *const RawClosureHeader, err: f64, output: f64) -> f64 { + extern "C" fn callback( + _closure: *const RawClosureHeader, + _this: perry_ffi::JsThis, + err: f64, + output: f64, + ) -> f64 { assert_eq!(err.to_bits(), JsValue::NULL.bits()); let bytes = read_buffer_bytes( JsValue::from_bits(output.to_bits()).as_pointer::(), diff --git a/crates/perry-ffi/src/closure.rs b/crates/perry-ffi/src/closure.rs index 06e4b450f8..004e32aa2a 100644 --- a/crates/perry-ffi/src/closure.rs +++ b/crates/perry-ffi/src/closure.rs @@ -59,12 +59,35 @@ extern "C" { fn js_closure_set_capture_f64(closure: *mut ClosureHeader, index: u32, value: f64); } +/// The receiver every native closure body takes as its SECOND parameter: +/// the NaN-boxed `this` bits, passed in an integer register. A body a +/// wrapper hands to [`alloc_closure`] must be declared +/// +/// ```ignore +/// extern "C" fn body(closure: *const RawClosureHeader, this: JsThis, a0: f64, ...) -> f64 +/// ``` +/// +/// (perry's JS body ABI, `perry_abi::JS_BODY_*`). `repr(transparent)` over +/// `u64`, so it is ABI-identical to perry-runtime's `closure::JsThis`. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct JsThis(pub u64); + +impl JsThis { + /// The receiver bits. + pub const fn bits(self) -> u64 { + self.0 + } +} + /// Register the arity the runtime uses when dispatching a native closure. pub fn register_closure_arity(func: *const u8, arity: u32) { unsafe { js_register_closure_arity(func, arity) } } -/// Allocate a native closure with `capture_count` f64 capture slots. +/// Allocate a native closure with `capture_count` f64 capture slots. `func` +/// is a body declared with the receiver as its second parameter (see +/// [`JsThis`]). pub fn alloc_closure(func: *const u8, capture_count: u32) -> *mut ClosureHeader { unsafe { js_closure_alloc(func, capture_count) } } @@ -176,7 +199,7 @@ mod tests { #[cfg(feature = "runtime-link")] #[test] fn native_closure_retains_capture() { - unsafe extern "C" fn callback(_: *const ClosureHeader) -> f64 { + unsafe extern "C" fn callback(_: *const ClosureHeader, _this: crate::JsThis) -> f64 { 0.0 } register_closure_arity(callback as *const u8, 0); diff --git a/crates/perry-ffi/src/lib.rs b/crates/perry-ffi/src/lib.rs index 1c2daad4a9..e5e462c094 100644 --- a/crates/perry-ffi/src/lib.rs +++ b/crates/perry-ffi/src/lib.rs @@ -107,7 +107,7 @@ mod closure; mod transient_roots; pub use closure::{ alloc_closure, closure_capture_f64, register_closure_arity, set_closure_capture_f64, JsClosure, - RawClosureHeader, + JsThis, RawClosureHeader, }; pub use transient_roots::{TransientRootScope, TransientRootedAddr, TransientRootedNanbox}; diff --git a/crates/perry-runtime/src/agent_ptrs.rs b/crates/perry-runtime/src/agent_ptrs.rs index d994e55873..dea7597c77 100644 --- a/crates/perry-runtime/src/agent_ptrs.rs +++ b/crates/perry-runtime/src/agent_ptrs.rs @@ -60,6 +60,21 @@ pub(crate) fn hot_addr() -> *mut u8 { &PERRY_AGENT_PTRS as *const AgentPtrs as *mut u8 } +/// This agent's implicit-`this` bits, read through the published slot — a +/// direct `#[thread_local]` access and one load, no `thread_local!` accessor — +/// publishing the slot on this agent's first read. +#[inline(always)] +pub(crate) fn implicit_this_bits() -> u64 { + let mut cell = PERRY_AGENT_PTRS.0[AGENT_PTR_IMPLICIT_THIS].get(); + if cell.is_null() { + cell = perry_implicit_this_cell(); + } + // SAFETY: a published slot points at this agent's live `HotTls` cell (a + // slot is cleared before the state it points into is freed); `Cell` + // has `u64`'s layout. + unsafe { *(cell as *const u64) } +} + /// The address of this agent's implicit-`this` cell, published into slot /// [`AGENT_PTR_IMPLICIT_THIS`] for emitted code that binds `this` around a /// direct method call. A leaf: one TLS read and one store. diff --git a/crates/perry-runtime/src/array/collection_tag_tests.rs b/crates/perry-runtime/src/array/collection_tag_tests.rs index a403e34e04..33fb2e6ba1 100644 --- a/crates/perry-runtime/src/array/collection_tag_tests.rs +++ b/crates/perry-runtime/src/array/collection_tag_tests.rs @@ -355,6 +355,7 @@ thread_local! { extern "C" fn record_first_arg( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, _index: f64, _receiver: f64, diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index bcb3414ec7..1884349430 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -1014,6 +1014,7 @@ pub extern "C" fn js_array_some_captureless( }; let result = callback( std::ptr::null(), + crate::closure::JsThis::current(), element, i as f64, array_receiver_value(arr), diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index 5eaf92dd52..ccf6e7872f 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -307,6 +307,7 @@ fn async_from_sync_iter_result(value: f64, done: bool) -> f64 { extern "C" fn async_from_sync_fulfilled( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { // `async_from_sync_iter_result` allocates and can move the nursery `outer` @@ -328,6 +329,7 @@ extern "C" fn async_from_sync_fulfilled( extern "C" fn async_from_sync_rejected_value( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { // `async_from_sync_close` calls back into JS and allocates, which can move @@ -598,6 +600,7 @@ fn async_from_sync_call(iter: f64, method: &[u8], args: &[f64], close_on_rejecti extern "C" fn async_from_sync_next( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { // Root the captured sync iterator + its `[[NextMethod]]` across the sync @@ -633,6 +636,7 @@ extern "C" fn async_from_sync_next( extern "C" fn async_from_sync_return( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -659,6 +663,7 @@ extern "C" fn async_from_sync_return( extern "C" fn async_from_sync_throw( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -677,7 +682,10 @@ extern "C" fn async_from_sync_throw( } } -extern "C" fn async_from_sync_async_iterator(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn async_from_sync_async_iterator( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::closure::js_closure_get_capture_f64(closure, 0) } diff --git a/crates/perry-runtime/src/array/tests.rs b/crates/perry-runtime/src/array/tests.rs index d42c0924b6..49edb111b2 100644 --- a/crates/perry-runtime/src/array/tests.rs +++ b/crates/perry-runtime/src/array/tests.rs @@ -10,6 +10,7 @@ mod strict_dense; extern "C" fn test_map_to_string( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _element: f64, _index: f64, ) -> f64 { @@ -21,6 +22,7 @@ static DIRECT_SOME_CALLS: AtomicUsize = AtomicUsize::new(0); extern "C" fn direct_some_is_two( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, element: f64, index: f64, _array: f64, 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 ca09b9a58a..3ef32f96ae 100644 --- a/crates/perry-runtime/src/array/typed_array_receiver_tests.rs +++ b/crates/perry-runtime/src/array/typed_array_receiver_tests.rs @@ -242,6 +242,7 @@ fn typed_read_back(ta: *mut ArrayHeader, len: usize) -> Vec { /// `ClosureHeader`, which is what `DirectCall2::resolve` expects. extern "C" fn descending_cmp( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, ) -> f64 { @@ -773,6 +774,7 @@ fn record(value: f64, index: f64, receiver: f64) { /// `(element, index, receiver) -> element * 2` — records, then doubles. extern "C" fn cb_double( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, index: f64, receiver: f64, @@ -785,6 +787,7 @@ extern "C" fn cb_double( /// answer "true" for the garbage reads, so it must not be used. extern "C" fn cb_is_three_or_two( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, index: f64, receiver: f64, @@ -796,6 +799,7 @@ extern "C" fn cb_is_three_or_two( /// Truthy only for the literal `1`. extern "C" fn cb_is_one( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, index: f64, receiver: f64, @@ -808,6 +812,7 @@ extern "C" fn cb_is_one( /// predicate. A `x > 0` predicate here is VACUOUS (see the header comment). extern "C" fn cb_is_a_source_byte( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, index: f64, receiver: f64, @@ -819,6 +824,7 @@ extern "C" fn cb_is_a_source_byte( /// `(accumulator, element, index, receiver) -> accumulator + element`. extern "C" fn cb_sum( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, accumulator: f64, value: f64, index: f64, diff --git a/crates/perry-runtime/src/async_hooks.rs b/crates/perry-runtime/src/async_hooks.rs index c32b5860ea..0e64e3a434 100644 --- a/crates/perry-runtime/src/async_hooks.rs +++ b/crates/perry-runtime/src/async_hooks.rs @@ -1339,6 +1339,7 @@ pub extern "C" fn js_async_resource_set_event_emitter(handle: i64, event_emitter /// the call-site `this` for a detached function. extern "C" fn async_resource_bind_method_trampoline( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { if closure.is_null() { @@ -1626,7 +1627,11 @@ pub extern "C" fn js_async_resource_run_in_async_scope( /// synthesized closure; receives the rest array of forwarded args and replays /// the call through `runInAsyncScope` so init/before/after/destroy fire with /// the bound resource's async id active. -extern "C" fn async_resource_bind_trampoline(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn async_resource_bind_trampoline( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -1782,6 +1787,7 @@ pub extern "C" fn js_async_resource_static_bind_direct( pub extern "C" fn js_async_resource_static_bind_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback_value: f64, type_value: f64, this_arg: f64, @@ -1792,6 +1798,7 @@ pub extern "C" fn js_async_resource_static_bind_method( pub extern "C" fn js_async_local_storage_static_bind_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback_value: f64, _rest: f64, ) -> f64 { @@ -1875,6 +1882,7 @@ fn call_callback_with_rest(callback_value: f64, this_arg: f64, rest: f64) -> f64 extern "C" fn async_local_storage_snapshot_trampoline( closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback_value: f64, rest: f64, ) -> f64 { @@ -1914,6 +1922,7 @@ fn async_local_storage_static_snapshot_value() -> f64 { pub extern "C" fn js_async_local_storage_static_snapshot_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _rest: f64, ) -> f64 { async_local_storage_static_snapshot_value() diff --git a/crates/perry-runtime/src/async_hooks/provider_ffi.rs b/crates/perry-runtime/src/async_hooks/provider_ffi.rs index 7570dc316a..967c0a027f 100644 --- a/crates/perry-runtime/src/async_hooks/provider_ffi.rs +++ b/crates/perry-runtime/src/async_hooks/provider_ffi.rs @@ -5,7 +5,10 @@ use super::{ try_leave_resource_scope, AsyncResourceIds, RESOURCES, }; -extern "C" fn deferred_destroy_step(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn deferred_destroy_step( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let async_id = crate::closure::js_closure_get_capture_f64(closure, 0) as u64; let remaining = crate::closure::js_closure_get_capture_f64(closure, 1) as u32; if remaining == 0 { diff --git a/crates/perry-runtime/src/async_hooks/test_support.rs b/crates/perry-runtime/src/async_hooks/test_support.rs index 93fc9330bb..a264f1b335 100644 --- a/crates/perry-runtime/src/async_hooks/test_support.rs +++ b/crates/perry-runtime/src/async_hooks/test_support.rs @@ -63,7 +63,11 @@ pub(crate) fn test_async_hooks_scanner_snapshot() -> (usize, u64) { mod tests { use super::*; - extern "C" fn throwing_lifecycle_hook(_closure: *const ClosureHeader, _async_id: f64) -> f64 { + extern "C" fn throwing_lifecycle_hook( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _async_id: f64, + ) -> f64 { crate::exception::js_throw(73.0) } diff --git a/crates/perry-runtime/src/atomics.rs b/crates/perry-runtime/src/atomics.rs index d2abd66ba1..b2f47eeebd 100644 --- a/crates/perry-runtime/src/atomics.rs +++ b/crates/perry-runtime/src/atomics.rs @@ -465,7 +465,12 @@ fn atomics_bitwise(view: f64, index: f64, value: f64, op: impl FnOnce(u64, u64) } #[no_mangle] -pub extern "C" fn js_atomics_load(_closure: *const ClosureHeader, view: f64, index: f64) -> f64 { +pub extern "C" fn js_atomics_load( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + view: f64, + index: f64, +) -> f64 { let (view, idx) = slot(view, index); if view.is_bigint() { return bigint_result_for_kind(view.kind(), view.get_bigint_bits(idx)); @@ -474,7 +479,11 @@ pub extern "C" fn js_atomics_load(_closure: *const ClosureHeader, view: f64, ind } #[no_mangle] -pub extern "C" fn js_atomics_is_lock_free(_closure: *const ClosureHeader, size: f64) -> f64 { +pub extern "C" fn js_atomics_is_lock_free( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + size: f64, +) -> f64 { // ToNumber(size) (runs `valueOf`/`toString`, so `'4'`→4 behaves like Node), // then an EXACT membership test over the lock-free element widths. Node/V8 // compares the raw number, so `4.9` is NOT floored to 4 — it is simply not a @@ -486,6 +495,7 @@ pub extern "C" fn js_atomics_is_lock_free(_closure: *const ClosureHeader, size: #[no_mangle] pub extern "C" fn js_atomics_store( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -509,6 +519,7 @@ pub extern "C" fn js_atomics_store( #[no_mangle] pub extern "C" fn js_atomics_add( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -531,6 +542,7 @@ pub extern "C" fn js_atomics_add( #[no_mangle] pub extern "C" fn js_atomics_sub( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -553,6 +565,7 @@ pub extern "C" fn js_atomics_sub( #[no_mangle] pub extern "C" fn js_atomics_and( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -563,6 +576,7 @@ pub extern "C" fn js_atomics_and( #[no_mangle] pub extern "C" fn js_atomics_or( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -573,6 +587,7 @@ pub extern "C" fn js_atomics_or( #[no_mangle] pub extern "C" fn js_atomics_xor( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -583,6 +598,7 @@ pub extern "C" fn js_atomics_xor( #[no_mangle] pub extern "C" fn js_atomics_exchange( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, value: f64, @@ -602,6 +618,7 @@ pub extern "C" fn js_atomics_exchange( #[no_mangle] pub extern "C" fn js_atomics_compare_exchange( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, expected: f64, @@ -630,6 +647,7 @@ pub extern "C" fn js_atomics_compare_exchange( #[no_mangle] pub extern "C" fn js_atomics_notify( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, count: f64, @@ -652,6 +670,7 @@ pub extern "C" fn js_atomics_notify( #[no_mangle] pub extern "C" fn js_atomics_wait( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, expected: f64, @@ -706,6 +725,7 @@ pub extern "C" fn js_atomics_wait( #[no_mangle] pub extern "C" fn js_atomics_wait_async( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, view: f64, index: f64, expected: f64, diff --git a/crates/perry-runtime/src/builtins/console.rs b/crates/perry-runtime/src/builtins/console.rs index 4aa7183396..00874feed2 100644 --- a/crates/perry-runtime/src/builtins/console.rs +++ b/crates/perry-runtime/src/builtins/console.rs @@ -117,11 +117,11 @@ pub extern "C" fn js_console_log_dynamic(value: f64) { /// Thunk for `console.log` exposed as a real callable closure value /// (#236). Lets `Promise.resolve(x).then(console.log)` actually call into -/// `js_console_log_dynamic` instead of being a no-op sentinel; the call -/// signature `extern "C" fn(*const ClosureHeader, f64) -> f64` matches -/// what `js_closure_call1` invokes through. +/// `js_console_log_dynamic` instead of being a no-op sentinel; it is a JS +/// body (`perry_abi::JS_BODY_*`: callee, receiver, one argument). extern "C" fn console_log_callable_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { js_console_log_dynamic(value); @@ -552,12 +552,16 @@ fn console_set_field(obj: *mut crate::object::ObjectHeader, name: &str, value: f crate::object::js_object_set_field_by_name(obj, key, value); } -extern "C" fn console_context_method_noop(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn console_context_method_noop( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { console_undefined() } extern "C" fn console_task_run( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, ) -> f64 { if !console_is_callable(callback) { diff --git a/crates/perry-runtime/src/bun_compat/cli_utils.rs b/crates/perry-runtime/src/bun_compat/cli_utils.rs index ea2a0f5dea..3bbd1a6036 100644 --- a/crates/perry-runtime/src/bun_compat/cli_utils.rs +++ b/crates/perry-runtime/src/bun_compat/cli_utils.rs @@ -114,12 +114,17 @@ fn namespace_object(fields: &[(&[u8], f64)]) -> f64 { // Object-valued exports // --------------------------------------------------------------------------- -extern "C" fn yaml_parse_closure(_closure: *const ClosureHeader, input: f64) -> f64 { +extern "C" fn yaml_parse_closure( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + input: f64, +) -> f64 { yaml_parse(input) } extern "C" fn yaml_stringify_closure( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, input: f64, replacer: f64, space: f64, @@ -137,7 +142,11 @@ pub fn js_bun_yaml() -> f64 { ]) } -extern "C" fn toml_parse_closure(_closure: *const ClosureHeader, input: f64) -> f64 { +extern "C" fn toml_parse_closure( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + input: f64, +) -> f64 { let source = value_to_string(input); match toml_parse_result(&source) { Ok(value) => value, @@ -185,7 +194,12 @@ fn normalize_semver_version(input: &str) -> &str { .unwrap_or_else(|| input.trim()) } -extern "C" fn semver_order_closure(_closure: *const ClosureHeader, left: f64, right: f64) -> f64 { +extern "C" fn semver_order_closure( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + left: f64, + right: f64, +) -> f64 { let left_source = value_to_string(left); let right_source = value_to_string(right); let left = match node_semver::Version::parse(normalize_semver_version(&left_source)) { @@ -211,6 +225,7 @@ extern "C" fn semver_order_closure(_closure: *const ClosureHeader, left: f64, ri extern "C" fn semver_satisfies_closure( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, version: f64, range: f64, ) -> f64 { @@ -241,6 +256,7 @@ pub fn js_bun_semver() -> f64 { extern "C" fn jsonl_parse_chunk_closure( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, input: f64, start: f64, end: f64, @@ -563,7 +579,12 @@ pub extern "C" fn js_bun_zstd_decompress(input: f64) -> f64 { } } -extern "C" fn xxhash64_closure(_closure: *const ClosureHeader, input: f64, seed: f64) -> f64 { +extern "C" fn xxhash64_closure( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + input: f64, + seed: f64, +) -> f64 { let bytes = payload_bytes(input).unwrap_or_default(); let hash = xxhash_rust::xxh64::xxh64(&bytes, super::hash_seed(seed)); let bigint = crate::bigint::js_bigint_from_u64(hash); diff --git a/crates/perry-runtime/src/bun_compat/glob.rs b/crates/perry-runtime/src/bun_compat/glob.rs index 9c903779a1..f37c3bda30 100644 --- a/crates/perry-runtime/src/bun_compat/glob.rs +++ b/crates/perry-runtime/src/bun_compat/glob.rs @@ -2,15 +2,27 @@ use super::*; -extern "C" fn bun_glob_scan(closure: *const ClosureHeader, options: f64) -> f64 { +extern "C" fn bun_glob_scan( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { crate::fs::js_bun_glob_async_iterator(captured(closure), options) } -extern "C" fn bun_glob_scan_sync(closure: *const ClosureHeader, options: f64) -> f64 { +extern "C" fn bun_glob_scan_sync( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { crate::fs::js_bun_glob_sync_iterator(captured(closure), options) } -extern "C" fn bun_glob_match(closure: *const ClosureHeader, path: f64) -> f64 { +extern "C" fn bun_glob_match( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + path: f64, +) -> f64 { match crate::fs::bun_glob_matches(captured(closure), path) { Ok(matches) => bool_value(matches), Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/bun_compat/mod.rs b/crates/perry-runtime/src/bun_compat/mod.rs index b59bbc3f89..f1ccb6f760 100644 --- a/crates/perry-runtime/src/bun_compat/mod.rs +++ b/crates/perry-runtime/src/bun_compat/mod.rs @@ -311,7 +311,7 @@ fn read_file_or_reject(path: &str) -> Result, f64> { .map_err(|err| unsafe { crate::fs::build_fs_error_value(&err, "open", path) }) } -extern "C" fn bun_file_text(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_file_text(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let path = value_to_string(captured(closure)); match read_file_or_reject(&path) { Ok(bytes) => promise_value(boxed_str(String::from_utf8_lossy(&bytes).as_bytes())), @@ -319,7 +319,7 @@ extern "C" fn bun_file_text(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_file_json(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_file_json(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let path = value_to_string(captured(closure)); match read_file_or_reject(&path) { Ok(bytes) => json_parse_promise(&bytes), @@ -327,7 +327,10 @@ extern "C" fn bun_file_json(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_file_array_buffer(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_file_array_buffer( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let path = value_to_string(captured(closure)); match read_file_or_reject(&path) { Ok(bytes) => promise_value(array_buffer_from_bytes(&bytes)), @@ -335,7 +338,7 @@ extern "C" fn bun_file_array_buffer(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_file_bytes(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_file_bytes(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let path = value_to_string(captured(closure)); match read_file_or_reject(&path) { Ok(bytes) => promise_value(uint8_array_from_bytes(&bytes)), @@ -343,7 +346,7 @@ extern "C" fn bun_file_bytes(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_file_exists(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_file_exists(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let path = value_to_string(captured(closure)); let exists = if crate::embedded::lookup(&path).is_some() { true @@ -487,7 +490,7 @@ pub extern "C" fn js_bun_write(dest: f64, data: f64) -> f64 { // Bun.stdin / Bun.stdout / Bun.stderr // --------------------------------------------------------------------------- -extern "C" fn bun_stdin_text(_closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_stdin_text(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let mut buf = Vec::new(); match std::io::stdin().lock().read_to_end(&mut buf) { Ok(_) => promise_value(boxed_str(String::from_utf8_lossy(&buf).as_bytes())), @@ -495,7 +498,7 @@ extern "C" fn bun_stdin_text(_closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_stdin_json(_closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_stdin_json(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let mut buf = Vec::new(); match std::io::stdin().lock().read_to_end(&mut buf) { Ok(_) => json_parse_promise(&buf), @@ -503,7 +506,10 @@ extern "C" fn bun_stdin_json(_closure: *const ClosureHeader) -> f64 { } } -extern "C" fn bun_stdin_array_buffer(_closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_stdin_array_buffer( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let mut buf = Vec::new(); match std::io::stdin().lock().read_to_end(&mut buf) { Ok(_) => promise_value(array_buffer_from_bytes(&buf)), diff --git a/crates/perry-runtime/src/bun_compat/plugin.rs b/crates/perry-runtime/src/bun_compat/plugin.rs index b1a89dc01b..d591aa594c 100644 --- a/crates/perry-runtime/src/bun_compat/plugin.rs +++ b/crates/perry-runtime/src/bun_compat/plugin.rs @@ -7,7 +7,11 @@ use crate::gc::{RuntimeHandle, RuntimeHandleScope}; use crate::object::{js_object_alloc, js_object_set_field_by_name}; use crate::value::{js_nanbox_pointer, JSValue}; -extern "C" fn ignore(_closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn ignore( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, +) -> f64 { undefined() } @@ -132,12 +136,20 @@ mod tests { js_nanbox_pointer(js_closure_alloc(func as *const u8, 0) as i64) } - extern "C" fn loader(_closure: *const ClosureHeader, _args: f64) -> f64 { + extern "C" fn loader( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _args: f64, + ) -> f64 { LOADER_CALLS.fetch_add(1, Ordering::SeqCst); undefined() } - extern "C" fn setup(_closure: *const ClosureHeader, build: f64) -> f64 { + extern "C" fn setup( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + build: f64, + ) -> f64 { SETUP_CALLS.fetch_add(1, Ordering::SeqCst); let scope = RuntimeHandleScope::new(); let build = scope.root_nanbox_f64(build); @@ -193,7 +205,11 @@ mod tests { assert_eq!(LOADER_CALLS.load(Ordering::SeqCst), 0); } - extern "C" fn async_setup(_closure: *const ClosureHeader, _build: f64) -> f64 { + extern "C" fn async_setup( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _build: f64, + ) -> f64 { js_nanbox_pointer(crate::promise::js_promise_resolved(42.0) as i64) } diff --git a/crates/perry-runtime/src/bun_compat/self_global.rs b/crates/perry-runtime/src/bun_compat/self_global.rs index 40490a5d00..317b63b60c 100644 --- a/crates/perry-runtime/src/bun_compat/self_global.rs +++ b/crates/perry-runtime/src/bun_compat/self_global.rs @@ -49,11 +49,15 @@ pub(super) fn install_once() { ); } -extern "C" fn get_self(_closure: *const ClosureHeader) -> f64 { +extern "C" fn get_self(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { crate::object::js_get_global_this() } -extern "C" fn set_self(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn set_self( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { // Like Bun, even a borrowed setter replaces the current realm's property. // Once replaced, subsequent assignments are ordinary writable data stores. let _no_move = crate::gc::GcSuppressScope::new(); @@ -96,7 +100,7 @@ mod tests { global_value.to_bits() ); - set_self(std::ptr::null(), 42.0); + set_self(std::ptr::null(), crate::closure::JsThis::current(), 42.0); assert!(crate::object::get_accessor_descriptor(global as usize, "self").is_none()); super::super::js_set_bun_platform(); assert_eq!( diff --git a/crates/perry-runtime/src/bun_compat/spawn.rs b/crates/perry-runtime/src/bun_compat/spawn.rs index 2ba472cecf..0d28a26c04 100644 --- a/crates/perry-runtime/src/bun_compat/spawn.rs +++ b/crates/perry-runtime/src/bun_compat/spawn.rs @@ -179,19 +179,31 @@ fn clone_options(options: f64) -> f64 { // Readable / writable stream facade // ------------------------------------------------------------------------- -extern "C" fn bun_readable_text(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_readable_text( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::node_submodules::consume_text(captured_at(closure, 0)) } -extern "C" fn bun_readable_json(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_readable_json( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::node_submodules::consume_json(captured_at(closure, 0)) } -extern "C" fn bun_readable_array_buffer(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_readable_array_buffer( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::node_submodules::consume_array_buffer(captured_at(closure, 0)) } -extern "C" fn bun_readable_bytes(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_readable_bytes( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::node_submodules::consume_bytes(captured_at(closure, 0)) } @@ -220,7 +232,11 @@ fn decorate_readable(stream: f64) -> f64 { stream.get_nanbox_f64() } -extern "C" fn bun_sink_flush(_closure: *const ClosureHeader, _wait: f64) -> f64 { +extern "C" fn bun_sink_flush( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _wait: f64, +) -> f64 { 0.0 } @@ -394,7 +410,12 @@ fn normalized_options(options: f64) -> Result<(f64, Vec), f64> { // Non-PTY Subprocess facade // ------------------------------------------------------------------------- -extern "C" fn bun_subprocess_exit(closure: *const ClosureHeader, code: f64, signal: f64) -> f64 { +extern "C" fn bun_subprocess_exit( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + code: f64, + signal: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let subprocess = scope.root_nanbox_f64(captured_at(closure, 0)); let code = scope.root_nanbox_f64(code); @@ -570,7 +591,11 @@ fn terminal_set_refed(terminal: f64, refed: bool) { ); } -extern "C" fn bun_terminal_write(closure: *const ClosureHeader, data: f64) -> f64 { +extern "C" fn bun_terminal_write( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); let data = scope.root_nanbox_f64(data); @@ -589,7 +614,12 @@ extern "C" fn bun_terminal_write(closure: *const ClosureHeader, data: f64) -> f6 0.0 } -extern "C" fn bun_terminal_resize(closure: *const ClosureHeader, columns: f64, rows: f64) -> f64 { +extern "C" fn bun_terminal_resize( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + columns: f64, + rows: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); let columns = number_i32(columns).unwrap_or(0); @@ -608,7 +638,11 @@ extern "C" fn bun_terminal_resize(closure: *const ClosureHeader, columns: f64, r cp_undefined() } -extern "C" fn bun_terminal_set_raw_mode(closure: *const ClosureHeader, enabled: f64) -> f64 { +extern "C" fn bun_terminal_set_raw_mode( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + enabled: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); let enabled = crate::value::js_is_truthy(enabled) != 0; @@ -626,7 +660,10 @@ extern "C" fn bun_terminal_set_raw_mode(closure: *const ClosureHeader, enabled: cp_undefined() } -extern "C" fn bun_terminal_ref(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_terminal_ref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); #[cfg(unix)] @@ -634,7 +671,10 @@ extern "C" fn bun_terminal_ref(closure: *const ClosureHeader) -> f64 { terminal.get_nanbox_f64() } -extern "C" fn bun_terminal_unref(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_terminal_unref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); #[cfg(unix)] @@ -642,7 +682,10 @@ extern "C" fn bun_terminal_unref(closure: *const ClosureHeader) -> f64 { terminal.get_nanbox_f64() } -extern "C" fn bun_terminal_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_terminal_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); if bool_field(terminal.get_nanbox_f64(), b"closed") { @@ -740,7 +783,11 @@ pub extern "C" fn js_bun_terminal_new(options: f64) -> f64 { } #[cfg(unix)] -extern "C" fn bun_terminal_data_bridge(closure: *const ClosureHeader, text: f64) -> f64 { +extern "C" fn bun_terminal_data_bridge( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + text: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let terminal = scope.root_nanbox_f64(captured_at(closure, 0)); let text = scope.root_nanbox_f64(text); @@ -758,7 +805,11 @@ extern "C" fn bun_terminal_data_bridge(closure: *const ClosureHeader, text: f64) } #[cfg(unix)] -extern "C" fn bun_pty_subprocess_exit(closure: *const ClosureHeader, payload: f64) -> f64 { +extern "C" fn bun_pty_subprocess_exit( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + payload: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let subprocess = scope.root_nanbox_f64(captured_at(closure, 0)); let terminal = scope.root_nanbox_f64(captured_at(closure, 1)); @@ -816,7 +867,11 @@ extern "C" fn bun_pty_subprocess_exit(closure: *const ClosureHeader, payload: f6 } #[cfg(unix)] -extern "C" fn bun_pty_subprocess_kill(closure: *const ClosureHeader, signal: f64) -> f64 { +extern "C" fn bun_pty_subprocess_kill( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + signal: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let subprocess = scope.root_nanbox_f64(captured_at(closure, 0)); let terminal = scope.root_nanbox_f64(cp_get_field(subprocess.get_nanbox_f64(), b"terminal")); @@ -841,7 +896,10 @@ extern "C" fn bun_pty_subprocess_kill(closure: *const ClosureHeader, signal: f64 } #[cfg(unix)] -extern "C" fn bun_pty_subprocess_ref(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_pty_subprocess_ref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let subprocess = scope.root_nanbox_f64(captured_at(closure, 0)); terminal_set_refed(cp_get_field(subprocess.get_nanbox_f64(), b"terminal"), true); @@ -849,7 +907,10 @@ extern "C" fn bun_pty_subprocess_ref(closure: *const ClosureHeader) -> f64 { } #[cfg(unix)] -extern "C" fn bun_pty_subprocess_unref(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_pty_subprocess_unref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let subprocess = scope.root_nanbox_f64(captured_at(closure, 0)); terminal_set_refed( @@ -860,8 +921,11 @@ extern "C" fn bun_pty_subprocess_unref(closure: *const ClosureHeader) -> f64 { } #[cfg(unix)] -extern "C" fn bun_pty_subprocess_dispose(closure: *const ClosureHeader) -> f64 { - let _ = bun_pty_subprocess_kill(closure, cp_undefined()); +extern "C" fn bun_pty_subprocess_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + let _ = bun_pty_subprocess_kill(closure, crate::closure::JsThis::current(), cp_undefined()); cp_undefined() } diff --git a/crates/perry-runtime/src/bun_ffi/callback.rs b/crates/perry-runtime/src/bun_ffi/callback.rs index f57f0e21d6..076084642b 100644 --- a/crates/perry-runtime/src/bun_ffi/callback.rs +++ b/crates/perry-runtime/src/bun_ffi/callback.rs @@ -311,7 +311,10 @@ fn validate_callback_types(ret: u8, args: &[u8]) { } } -extern "C" fn callback_close_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn callback_close_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let index = crate::closure::js_closure_get_capture_bits(closure, 0) as usize; if let Some(record) = CALLBACKS.lock().unwrap().get_mut(index) { close_record(record); diff --git a/crates/perry-runtime/src/bun_ffi/dlopen.rs b/crates/perry-runtime/src/bun_ffi/dlopen.rs index 55ebca122b..a285c2810b 100644 --- a/crates/perry-runtime/src/bun_ffi/dlopen.rs +++ b/crates/perry-runtime/src/bun_ffi/dlopen.rs @@ -214,7 +214,11 @@ unsafe fn invoke_from_closure(closure: *const ClosureHeader, js_args: &[f64]) -> macro_rules! sym_thunk { ($name:ident $(, $a:ident)*) => { - extern "C" fn $name(closure: *const ClosureHeader $(, $a: f64)*) -> f64 { + extern "C" fn $name( + closure: *const ClosureHeader, + _this: crate::closure::JsThis + $(, $a: f64)* + ) -> f64 { let args = [$($a),*]; unsafe { invoke_from_closure(closure, &args) } } @@ -341,7 +345,7 @@ fn sym_thunk_for(arity: usize) -> *const u8 { } } -extern "C" fn close_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn close_thunk(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let lib_index = crate::closure::js_closure_get_capture_bits(closure, 0) as usize; close_library_index(lib_index); super::undefined() @@ -374,7 +378,10 @@ fn index_closure(func: *const u8, index: usize, arity: u32, name: &str) -> f64 { crate::value::js_nanbox_pointer(closure as i64) } -extern "C" fn noop_close_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn noop_close_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { super::undefined() } @@ -1041,6 +1048,7 @@ unsafe fn parse_node_definition( extern "C" fn node_register_callback_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, signature: f64, callback: f64, ) -> f64 { @@ -1048,7 +1056,11 @@ extern "C" fn node_register_callback_thunk( unsafe { super::callback::node_register_callback_value(lib, signature, callback) } } -extern "C" fn node_unregister_callback_thunk(_closure: *const ClosureHeader, pointer: f64) -> f64 { +extern "C" fn node_unregister_callback_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + pointer: f64, +) -> f64 { unsafe { super::callback::node_unregister_callback_value(pointer) } } diff --git a/crates/perry-runtime/src/bun_ffi/read.rs b/crates/perry-runtime/src/bun_ffi/read.rs index 27602374da..b695ed73bc 100644 --- a/crates/perry-runtime/src/bun_ffi/read.rs +++ b/crates/perry-runtime/src/bun_ffi/read.rs @@ -104,7 +104,12 @@ unsafe fn read_value(kind: u8, pointer: f64, offset: f64) -> f64 { macro_rules! reader { ($name:ident, $kind:expr) => { - extern "C" fn $name(_closure: *const ClosureHeader, pointer: f64, offset: f64) -> f64 { + extern "C" fn $name( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + pointer: f64, + offset: f64, + ) -> f64 { unsafe { read_value($kind, pointer, offset) } } }; diff --git a/crates/perry-runtime/src/child_process/emitter.rs b/crates/perry-runtime/src/child_process/emitter.rs index 581a9b59ce..55efd49a7d 100644 --- a/crates/perry-runtime/src/child_process/emitter.rs +++ b/crates/perry-runtime/src/child_process/emitter.rs @@ -111,13 +111,19 @@ pub(crate) fn cp_readable_end(stream: f64) { // ----- method bodies (each receives the closure; slot 0 = host `this`) ----- -pub(crate) extern "C" fn cp_method_on(closure: *const ClosureHeader, event: f64, cb: f64) -> f64 { +pub(crate) extern "C" fn cp_method_on( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + cb: f64, +) -> f64 { let this = cp_this(closure); cp_register(this, event, cb); this } pub(crate) extern "C" fn cp_method_emit( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, arg: f64, ) -> f64 { @@ -132,16 +138,26 @@ pub(crate) extern "C" fn cp_method_emit( TAG_FALSE_F64 } } -pub(crate) extern "C" fn cp_method_this0(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_method_this0( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { cp_this(closure) } -pub(crate) extern "C" fn cp_method_this1(closure: *const ClosureHeader, _a: f64) -> f64 { +pub(crate) extern "C" fn cp_method_this1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, +) -> f64 { cp_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(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_method_ref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = cp_this(closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_live_set_refed(handle, true); @@ -152,7 +168,10 @@ pub(crate) extern "C" fn cp_method_ref(closure: *const ClosureHeader) -> f64 { /// Let the program terminate while this child continues running. The reactor /// still pumps and roots the child whenever another handle keeps the loop /// alive; only the active-handle accounting changes. -pub(crate) extern "C" fn cp_method_unref(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_method_unref( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = cp_this(closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_live_set_refed(handle, false); @@ -163,7 +182,11 @@ pub(crate) extern "C" fn cp_method_unref(closure: *const ClosureHeader) -> f64 { /// Low-level `new ChildProcess().spawn(options)` validation boundary. Node's /// constructor is public even though normal callers use `spawn()`; keep the /// constructed idle object inert after its setup checks. -pub(crate) extern "C" fn cp_method_child_spawn(closure: *const ClosureHeader, options: f64) -> f64 { +pub(crate) extern "C" fn cp_method_child_spawn( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { crate::child_process::validate::cp_validate_child_process_spawn(options); cp_this(closure) } @@ -171,13 +194,18 @@ pub(crate) extern "C" fn cp_method_child_spawn(closure: *const ClosureHeader, op /// to decoded strings. The reactor reads this field when it delivers data. pub(crate) extern "C" fn cp_method_set_encoding( closure: *const ClosureHeader, + _this: crate::closure::JsThis, encoding: f64, ) -> f64 { let this = cp_this(closure); cp_set_field(this, b"__cpEncoding", encoding); this } -pub(crate) extern "C" fn cp_method_kill(closure: *const ClosureHeader, signal: f64) -> f64 { +pub(crate) extern "C" fn cp_method_kill( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + signal: f64, +) -> f64 { cp_validate_signal(signal); let this = cp_this(closure); cp_set_field(this, b"killed", TAG_TRUE_F64); @@ -194,8 +222,11 @@ pub(crate) extern "C" fn cp_method_kill(closure: *const ClosureHeader, signal: f /// `child[Symbol.dispose]()` — Node aliases this to `kill()` and returns /// `undefined`, so `using child = spawn(...)` terminates the subprocess on /// scope exit. #2556. -pub(crate) extern "C" fn cp_method_dispose(closure: *const ClosureHeader) -> f64 { - let _ = cp_method_kill(closure, cp_undefined()); +pub(crate) extern "C" fn cp_method_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + let _ = cp_method_kill(closure, crate::closure::JsThis::current(), cp_undefined()); cp_undefined() } pub(crate) fn js_fork_child(args_len: usize) -> f64 { @@ -211,6 +242,7 @@ pub(crate) fn js_fork_child(args_len: usize) -> f64 { /// #1780. pub(crate) extern "C" fn cp_method_remove_listener( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -236,6 +268,7 @@ pub(crate) extern "C" fn cp_method_remove_listener( /// `__cpL_*` list when called with no event. #1780. pub(crate) extern "C" fn cp_method_remove_all_listeners( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let this = cp_this(closure); @@ -263,7 +296,11 @@ pub(crate) extern "C" fn cp_method_remove_all_listeners( this } -pub(crate) extern "C" fn cp_method_read(_closure: *const ClosureHeader, _n: f64) -> f64 { +pub(crate) extern "C" fn cp_method_read( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _n: f64, +) -> f64 { TAG_NULL_F64 } @@ -272,7 +309,11 @@ pub(crate) extern "C" fn cp_method_read(_closure: *const ClosureHeader, _n: f64) /// `process.stdout`/`process.stderr`; those stream objects expose no `end` /// method, so the lookup-miss skip below matches that naturally. Returns /// `dest` (Node returns the destination for chaining). -pub(crate) extern "C" fn cp_method_pipe(closure: *const ClosureHeader, dest: f64) -> f64 { +pub(crate) extern "C" fn cp_method_pipe( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + dest: f64, +) -> f64 { let this = cp_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); @@ -297,7 +338,11 @@ pub(crate) extern "C" fn cp_method_pipe(closure: *const ClosureHeader, dest: f64 } /// Pipe `data` forwarder: slot 0 = the destination; call `dest.write(chunk)`. -pub(crate) extern "C" fn cp_pipe_data_thunk(closure: *const ClosureHeader, chunk: f64) -> f64 { +pub(crate) extern "C" fn cp_pipe_data_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { let dest = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let write = cp_get_field(dest, b"write"); if !crate::fs::extract_closure_ptr(write).is_null() { @@ -315,7 +360,10 @@ pub(crate) extern "C" fn cp_pipe_data_thunk(closure: *const ClosureHeader, chunk /// Pipe `end` forwarder: slot 0 = the destination; call `dest.end()` when the /// destination has one (`process.stdout`/`process.stderr` do not — matching /// Node's doEnd exclusion for them). -pub(crate) extern "C" fn cp_pipe_end_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_pipe_end_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let dest = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let end = cp_get_field(dest, b"end"); if !crate::fs::extract_closure_ptr(end).is_null() { @@ -355,7 +403,10 @@ fn cp_defer_stream_callback(callback: f64) { } /// Deliver a deferred child-stdin completion callback captured in slot zero. -pub(crate) extern "C" fn cp_stream_callback_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_stream_callback_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let callback = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let args: [f64; 0] = []; unsafe { @@ -379,6 +430,7 @@ pub(crate) extern "C" fn cp_stream_callback_thunk(closure: *const ClosureHeader) /// deferred to the next turn as before. pub(crate) extern "C" fn cp_method_stdin_write( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, arg2: f64, arg3: f64, @@ -433,6 +485,7 @@ pub(crate) extern "C" fn cp_method_stdin_write( /// option forms are otherwise no-ops here, matching the prior behavior). pub(crate) extern "C" fn cp_method_send( closure: *const ClosureHeader, + _this: crate::closure::JsThis, message: f64, a2: f64, a3: f64, @@ -502,7 +555,10 @@ pub(crate) fn cp_defer_send_callback(cb: f64, ok: bool) { /// Deferred `send` callback body. Slot 0 = the user callback; slot 1 = the /// success flag. Invokes `callback(null)` on success or `callback(err)` with a /// Node-shaped `ERR_IPC_CHANNEL_CLOSED` error on failure. -pub(crate) extern "C" fn cp_send_callback_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_send_callback_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let cb = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); if crate::fs::extract_closure_ptr(cb).is_null() { return cp_undefined(); @@ -530,7 +586,10 @@ pub(crate) fn cp_channel_closed_error() -> f64 { /// `child.disconnect()` — close the IPC channel (#1933). Flips `connected` to /// `false`, `channel` to `null`, and emits a `disconnect` event. -pub(crate) extern "C" fn cp_method_disconnect(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn cp_method_disconnect( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = cp_this(closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_ipc_disconnect(handle); @@ -544,7 +603,10 @@ pub(crate) extern "C" fn cp_method_disconnect(closure: *const ClosureHeader) -> cp_undefined() } -extern "C" fn cp_disconnect_emit_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cp_disconnect_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let child = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); cp_emit(child, "disconnect", &[]); cp_undefined() @@ -555,6 +617,7 @@ extern "C" fn cp_disconnect_emit_thunk(closure: *const ClosureHeader) -> f64 { /// optional callback asynchronously (#1934 / #8512). pub(crate) extern "C" fn cp_method_stdin_end( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, arg2: f64, arg3: f64, 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 475a197c24..dcdeaedd4e 100644 --- a/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs +++ b/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs @@ -2,7 +2,11 @@ use super::*; /// Simulate the runtime-root rewrite performed by a moving collection inside /// the first listener, without requiring a native stack map in a Rust test. -extern "C" fn relocate(closure: *const ClosureHeader, _arg: f64) -> f64 { +extern "C" fn relocate( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { for pair in 0..2 { let source = js_closure_get_capture_ptr(closure, pair * 2) as *mut u8; let destination = js_closure_get_capture_ptr(closure, pair * 2 + 1) as *mut u8; @@ -16,7 +20,11 @@ extern "C" fn relocate(closure: *const ClosureHeader, _arg: f64) -> f64 { cp_undefined() } -extern "C" fn observe(_closure: *const ClosureHeader, arg: f64) -> f64 { +extern "C" fn observe( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { let target = crate::object::js_implicit_this_get(); cp_set_field(target, b"seen", arg); cp_undefined() diff --git a/crates/perry-runtime/src/child_process/exec.rs b/crates/perry-runtime/src/child_process/exec.rs index 96f8e15768..2aeca9dea7 100644 --- a/crates/perry-runtime/src/child_process/exec.rs +++ b/crates/perry-runtime/src/child_process/exec.rs @@ -459,6 +459,7 @@ pub extern "C" fn js_child_process_exec_file_sync( /// the error and reject with it. Arity 3. #4912/#1857. extern "C" fn cp_promise_settle_cb( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, stdout: f64, stderr: f64, @@ -517,7 +518,12 @@ fn cp_promisified_run( crate::value::js_nanbox_pointer(promise as i64) } -extern "C" fn cp_promisified_exec(_closure: *const ClosureHeader, cmd_val: f64, opts: f64) -> f64 { +extern "C" fn cp_promisified_exec( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cmd_val: f64, + opts: f64, +) -> f64 { validate::cp_validate_command(cmd_val, "command"); let cmd = cp_value_to_string(cmd_val).unwrap_or_default(); #[cfg(unix)] @@ -549,6 +555,7 @@ extern "C" fn cp_promisified_exec(_closure: *const ClosureHeader, cmd_val: f64, extern "C" fn cp_promisified_exec_file( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, file_val: f64, args_val: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/child_process/failed_spawn.rs b/crates/perry-runtime/src/child_process/failed_spawn.rs index d15ec5e5c7..27b12e1b0b 100644 --- a/crates/perry-runtime/src/child_process/failed_spawn.rs +++ b/crates/perry-runtime/src/child_process/failed_spawn.rs @@ -5,10 +5,13 @@ use super::*; /// Schedule close only after delivering the error. A close timer armed at spawn /// time can already be overdue before the error's setImmediate callback runs. /// Keep fork's separate failure contract unchanged by using this for spawn only. -pub(super) extern "C" fn emit_error_then_close(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn emit_error_then_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let cp = scope.root_nanbox_f64(cp_this(closure)); - reactor::cp_emit_spawn_error(closure); + reactor::cp_emit_spawn_error(closure, crate::closure::JsThis::current()); let close = crate::closure::js_closure_alloc(reactor::cp_emit_spawn_close as *const u8, 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); diff --git a/crates/perry-runtime/src/child_process/reactor.rs b/crates/perry-runtime/src/child_process/reactor.rs index b26aeed3a4..d6760692ce 100644 --- a/crates/perry-runtime/src/child_process/reactor.rs +++ b/crates/perry-runtime/src/child_process/reactor.rs @@ -606,7 +606,10 @@ fn cp_register_live_child_parts( handle } -extern "C" fn cp_abort_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn cp_abort_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let handle = js_closure_get_capture_ptr(closure, 0) as u64; cp_push_event(CpEvent::Abort { handle }); cp_undefined() @@ -1018,7 +1021,10 @@ pub extern "C" fn js_child_process_spawn_streams( /// Deferred single-`error` emit for the spawn/fork failure path. Slot 0 /// captures the ChildProcess value. -pub(super) extern "C" fn cp_emit_spawn_error(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn cp_emit_spawn_error( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let cp = scope.root_nanbox_f64(cp_this(closure)); let err = scope.root_nanbox_f64(cp_get_field(cp.get_nanbox_f64(), b"__cpError")); @@ -1029,7 +1035,10 @@ pub(super) extern "C" fn cp_emit_spawn_error(closure: *const ClosureHeader) -> f cp_undefined() } -pub(super) extern "C" fn cp_emit_spawn_close(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn cp_emit_spawn_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let cp = scope.root_nanbox_f64(cp_this(closure)); super::failed_spawn::finish_outputs(cp.get_nanbox_f64()); @@ -1332,7 +1341,10 @@ pub(super) fn cp_defer_exec_callback(cb_val: f64, err: f64, stdout: f64, stderr: /// The deferred-callback thunk: slots 0..4 capture `cb`, `err`, `stdout`, /// `stderr`. Takes no JS args. -extern "C" fn cp_exec_cb_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cp_exec_cb_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let cb = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let err = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); let out = f64::from_bits(js_closure_get_capture_ptr(closure, 2) as u64); 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 18713532da..cfb6fb1b52 100644 --- a/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs +++ b/crates/perry-runtime/src/child_process/reactor/lifecycle_tests.rs @@ -50,7 +50,12 @@ fn native_process_fixture() { assert_eq!(tail, b"exit"); } -extern "C" fn event(closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { +extern "C" fn event( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + _b: f64, +) -> f64 { let id = crate::closure::js_closure_get_capture_f64(closure, 0) as usize; EVENTS.with(|e| { e.borrow_mut() @@ -59,7 +64,11 @@ extern "C" fn event(closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { cp_undefined() } -extern "C" fn data(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn data( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { OUTPUT.with(|o| o.borrow_mut().extend(cp_value_to_bytes(value))); cp_undefined() } diff --git a/crates/perry-runtime/src/closure/body_call.rs b/crates/perry-runtime/src/closure/body_call.rs index f8690f1fb5..16848bb974 100644 --- a/crates/perry-runtime/src/closure/body_call.rs +++ b/crates/perry-runtime/src/closure/body_call.rs @@ -6,11 +6,12 @@ //! object. Its native signature is //! //! ```text -//! double body(i64 callee, double a0, double a1, ...) +//! double body(i64 callee, i64 this, double a0, double a1, ...) //! ``` //! -//! (`perry_abi::JS_BODY_*` names the parameter positions). Two other body -//! kinds exist until they join the one body ABI, and have their own macros: +//! (`perry_abi::JS_BODY_*` names the parameter positions; the receiver is a +//! [`JsThis`]). Two other body kinds exist until they join the one body ABI, +//! and have their own macros: //! //! * METHOD bodies — class instance methods, constructors, instance //! accessors: `double m(double this, double a0, ...)` @@ -20,27 +21,89 @@ //! `get [Symbol.toStringTag]`, whose receiver is an explicit parameter): //! `double f(double a0, ...)` ([`js_bare_body_fn!`]). //! -//! Every call site states the JS arguments it passes and nothing else, so -//! when the body ABI changes (the receiver becomes a parameter) this file is -//! where the change is made, and the compiler finds every caller. A -//! `mem::transmute` to a body type anywhere else is refused by -//! `scripts/check_js_body_call_funnel.sh`. +//! Every call site states the receiver and the JS arguments it passes and +//! nothing else, so the compiler finds every caller when the body ABI +//! changes. A `mem::transmute` to a body type anywhere else is refused by +//! `scripts/check_js_body_call_funnel.py`, and so is a native body definition +//! that does not declare the receiver. //! //! Over-application is safe and relied on: every supported calling //! convention leaves the stack argument area to the caller, so a body that //! declares N parameters reads only the first N (`wide_call.rs`). -/// The native type of a JS body taking one `f64` per token: -/// `js_body_fn_ty!(a, b)` is `extern "C" fn(*const ClosureHeader, f64, f64) -> f64`. +/// The receiver a JS body takes as its second native parameter +/// (`perry_abi::JS_BODY_THIS_PARAM`): the NaN-boxed `this` bits, passed in an +/// integer register (`repr(transparent)` over `u64`, so the ABI is exactly +/// that of a `u64`). +/// +/// Stage 1 of this-as-a-parameter: every caller passes the receiver the +/// implicit-`this` cell holds for the call (a runtime caller reads it with +/// [`JsThis::current`]), and bodies still read the cell. A native body +/// declares the parameter and must not assume anything about it yet. +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct JsThis(pub u64); + +impl JsThis { + /// `undefined` as a receiver. + pub const UNDEFINED: JsThis = JsThis(crate::value::TAG_UNDEFINED); + + /// The receiver bits. + #[inline(always)] + pub const fn bits(self) -> u64 { + self.0 + } + + /// The receiver as a NaN-boxed `f64`. + #[inline(always)] + pub fn as_f64(self) -> f64 { + f64::from_bits(self.0) + } + + /// A NaN-boxed `f64` receiver. + #[inline(always)] + pub fn from_f64(value: f64) -> Self { + JsThis(value.to_bits()) + } + + /// The receiver the implicit-`this` cell holds right now: what a body + /// called at this point would read from the cell. Read through the + /// per-agent pointer block (`agent_ptrs`), the cheapest path to the cell + /// on every target but Apple aarch64, where `HotTls` is the fast path. + #[inline(always)] + pub fn current() -> Self { + #[cfg(not(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" + )))] + return JsThis(crate::agent_ptrs::implicit_this_bits()); + #[cfg(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" + ))] + return JsThis(crate::object::implicit_this_bits()); + } +} + +/// The native type of a JS body taking the receiver and one `f64` per +/// token: `js_body_fn_ty!(a, b)` is +/// `extern "C" fn(*const ClosureHeader, JsThis, f64, f64) -> f64`. macro_rules! js_body_fn_ty { (@f64 $x:tt) => { f64 }; ($($x:tt),* $(,)?) => { - extern "C" fn(*const $crate::closure::ClosureHeader $(, $crate::closure::body_call::js_body_fn_ty!(@f64 $x))*) -> f64 + extern "C" fn( + *const $crate::closure::ClosureHeader, + $crate::closure::body_call::JsThis + $(, $crate::closure::body_call::js_body_fn_ty!(@f64 $x))* + ) -> f64 }; } /// Reinterpret a validated, non-sentinel body code pointer as a callable -/// taking one `f64` per token (`js_body_fn!(code; a, b)`). +/// taking the callee, the receiver and one `f64` per token +/// (`js_body_fn!(code; a, b)`). /// /// # Safety /// `code` must be a JS body whose declared JS parameter count is at most the @@ -51,14 +114,15 @@ macro_rules! js_body_fn { }; } -/// Call a JS body: `js_body_call!(code, callee, a0, a1, ...)`. +/// Call a JS body: `js_body_call!(code, callee, this, a0, a1, ...)`, where +/// `this` is a [`JsThis`]. /// /// # Safety /// As [`js_body_fn!`]; `callee` is the function object whose body `code` is. macro_rules! js_body_call { - ($code:expr, $callee:expr $(, $a:expr)* $(,)?) => {{ + ($code:expr, $callee:expr, $this:expr $(, $a:expr)* $(,)?) => {{ let f = $crate::closure::body_call::js_body_fn!($code; $($a),*); - f($callee $(, $a)*) + f($callee, $this $(, $a)*) }}; } @@ -68,18 +132,19 @@ macro_rules! js_body_call { /// identical to [`js_body_call!`]. macro_rules! js_body_call_unwind { (@f64 $x:tt) => { f64 }; - ($code:expr, $callee:expr $(, $a:expr)* $(,)?) => {{ + ($code:expr, $callee:expr, $this:expr $(, $a:expr)* $(,)?) => {{ #[cfg(panic = "abort")] let f = $crate::closure::body_call::js_body_fn!($code; $($a),*); #[cfg(not(panic = "abort"))] let f = ::std::mem::transmute::< *const u8, extern "C-unwind" fn( - *const $crate::closure::ClosureHeader + *const $crate::closure::ClosureHeader, + $crate::closure::body_call::JsThis $(, $crate::closure::body_call::js_body_call_unwind!(@f64 $a))* ) -> f64, >($code); - f($callee $(, $a)*) + f($callee, $this $(, $a)*) }}; } diff --git a/crates/perry-runtime/src/closure/dispatch/bound.rs b/crates/perry-runtime/src/closure/dispatch/bound.rs index 46f33ad5ec..f306a8355f 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound.rs @@ -878,11 +878,17 @@ mod rebind_predicate_tests { // 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. - extern "C" fn arrow_probe(_closure: *const ClosureHeader) -> f64 { + extern "C" fn arrow_probe( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 1.0 } - extern "C" fn method_probe(_closure: *const ClosureHeader) -> f64 { + extern "C" fn method_probe( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 2.0 } 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 9c69d9b475..07b0496a25 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 @@ -37,7 +37,10 @@ fn function_method_reads_share_the_prototype_function() { } } -extern "C" fn detached_function_fixture(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn detached_function_fixture( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 42.0 } diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index d600b61aa0..ad3171206b 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -42,7 +42,13 @@ fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 0) => unsafe { dispatch_with_arity(closure, func_ptr, &[], declared) }, - _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure) }, + _ => unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, + closure, + crate::closure::JsThis::current() + ) + }, } } @@ -64,15 +70,24 @@ pub extern "C" fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> if func_ptr.is_null() { return dispatch_proxy_callee_or_throw(closure, &[arg0]); } - dispatch_call1_resolved(closure, func_ptr, arg0, resolve_strategy(func_ptr)) + dispatch_call1_resolved( + closure, + func_ptr, + arg0, + resolve_strategy(func_ptr), + crate::closure::JsThis::current(), + ) } +/// `this` is the receiver the implicit-`this` cell holds for the call — the +/// caller knows it, so the direct arm does not re-read the cell. #[inline(always)] fn dispatch_call1_resolved( closure: *const ClosureHeader, func_ptr: *const u8, arg0: f64, strategy: DispatchStrategy, + this: crate::closure::JsThis, ) -> f64 { match strategy.kind() { DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, &[arg0]) }, @@ -83,7 +98,7 @@ fn dispatch_call1_resolved( DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 1) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0], declared) }, - _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, arg0) }, + _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, this, arg0) }, } } @@ -114,7 +129,15 @@ fn js_closure_call1_receiverless_impl(closure: *const ClosureHeader, arg0: f64) let func_ptr = get_valid_func_ptr(closure); let strategy = (!func_ptr.is_null()).then(|| resolve_strategy(func_ptr)); if let Some(arrow_strategy) = strategy.filter(|strategy| strategy.is_arrow()) { - return dispatch_call1_resolved(closure, func_ptr, arg0, arrow_strategy); + // An arrow's `this` is lexical: it reads neither the cell nor the + // parameter, so the receiver passed is unobservable. + return dispatch_call1_resolved( + closure, + func_ptr, + arg0, + arrow_strategy, + crate::closure::JsThis::UNDEFINED, + ); } let scope = crate::gc::RuntimeHandleScope::new(); @@ -122,7 +145,14 @@ fn js_closure_call1_receiverless_impl(closure: *const ClosureHeader, arg0: f64) let previous_this = crate::object::js_implicit_this_set(undefined); let previous_this_handle = scope.root_nanbox_f64(previous_this); let result = match strategy { - Some(strategy) => dispatch_call1_resolved(closure, func_ptr, arg0, strategy), + // The cell was bound to `undefined` just above. + Some(strategy) => dispatch_call1_resolved( + closure, + func_ptr, + arg0, + strategy, + crate::closure::JsThis::UNDEFINED, + ), None => dispatch_proxy_callee_or_throw(closure, &[arg0]), }; crate::object::js_implicit_this_set(previous_this_handle.get_nanbox_f64()); @@ -147,7 +177,15 @@ pub extern "C" fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 2) => unsafe { dispatch_with_arity(closure, func_ptr, &[arg0, arg1], declared) }, - _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1) }, + _ => unsafe { + crate::closure::body_call::js_body_call!( + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1 + ) + }, } } @@ -175,7 +213,14 @@ pub extern "C" fn js_closure_call3( dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2], declared) }, _ => unsafe { - crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2) + crate::closure::body_call::js_body_call!( + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2 + ) }, } } @@ -213,7 +258,15 @@ pub extern "C" fn js_closure_call4( dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2, arg3], declared) }, _ => unsafe { - crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2, arg3) + crate::closure::body_call::js_body_call!( + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3 + ) }, } } @@ -257,7 +310,16 @@ pub extern "C" fn js_closure_call5( } } unsafe { - crate::closure::body_call::js_body_call!(func_ptr, closure, arg0, arg1, arg2, arg3, arg4) + crate::closure::body_call::js_body_call!( + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4 + ) } } @@ -307,7 +369,15 @@ pub extern "C" fn js_closure_call6( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5 ) } } @@ -374,7 +444,16 @@ pub extern "C" fn js_closure_call7( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6 ) } } @@ -409,7 +488,17 @@ pub extern "C" fn js_closure_call8( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7 ) } } @@ -445,7 +534,18 @@ pub extern "C" fn js_closure_call9( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8 ) } } @@ -482,7 +582,19 @@ pub extern "C" fn js_closure_call10( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9 ) } } @@ -526,7 +638,20 @@ pub extern "C" fn js_closure_call11( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10 ) } } @@ -571,7 +696,20 @@ pub extern "C" fn js_closure_call12( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, arg11 ) } @@ -618,8 +756,22 @@ pub extern "C" fn js_closure_call13( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - arg11, arg12 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, + arg11, + arg12 ) } } @@ -667,8 +819,23 @@ pub extern "C" fn js_closure_call14( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - arg11, arg12, arg13 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, + arg11, + arg12, + arg13 ) } } @@ -719,8 +886,24 @@ pub extern "C" fn js_closure_call15( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - arg11, arg12, arg13, arg14 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, + arg11, + arg12, + arg13, + arg14 ) } } @@ -772,8 +955,25 @@ pub extern "C" fn js_closure_call16( } unsafe { crate::closure::body_call::js_body_call!( - func_ptr, closure, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - arg11, arg12, arg13, arg14, arg15 + func_ptr, + closure, + crate::closure::JsThis::current(), + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8, + arg9, + arg10, + arg11, + arg12, + arg13, + arg14, + arg15 ) } } @@ -782,7 +982,11 @@ pub extern "C" fn js_closure_call16( mod receiverless_tests { use super::*; - extern "C" fn observe_dynamic_this(_: *const ClosureHeader, _: f64) -> f64 { + extern "C" fn observe_dynamic_this( + _: *const ClosureHeader, + _this: crate::closure::JsThis, + _: f64, + ) -> f64 { crate::object::js_implicit_this_get() } @@ -814,9 +1018,12 @@ mod receiverless_tests { mod abi_table_tests { use super::*; + // An ENTRY takes the callee and the JS arguments; unlike a body it does + // not take the receiver (it reads it from the implicit-`this` cell). macro_rules! entry { + (@f64 $x:tt) => { f64 }; ($f:ident; $($x:tt),*) => {{ - let f: crate::closure::body_call::js_body_fn_ty!($($x),*) = $f; + let f: extern "C" fn(*const ClosureHeader $(, entry!(@f64 $x))*) -> f64 = $f; (stringify!($f), f as *const u8) }}; } diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index 31021ec649..39f2bf2ac5 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -153,8 +153,20 @@ pub extern "C" fn js_closure_resolve_versioned_loop_direct_call( macro_rules! define_direct_call_site { ( $(#[$meta:meta])* - $site:ident, $arity:literal, $slow:ident, $($arg:ident),+ + $site:ident, $arity:literal, $entry:ident, $slow:ident, $($arg:ident),+ ) => { + /// The fallback as a JS body: `$entry` is a call ENTRY (it takes no + /// receiver and reads the implicit-`this` cell itself), so the site + /// stores this adapter, which drops the receiver it is handed — the + /// same one the cell holds (stage 1 of this-as-a-parameter). + extern "C" fn $slow( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + $($arg: f64),+ + ) -> f64 { + $entry(closure, $($arg),+) + } + $(#[$meta])* #[derive(Clone, Copy)] pub struct $site(crate::closure::body_call::js_body_fn_ty!($($arg),+)); @@ -170,10 +182,11 @@ macro_rules! define_direct_call_site { /// Invoke with the CURRENT closure address (see the module docs on /// rooting). Falls back to the full dispatcher when the closure - /// did not resolve. + /// did not resolve. The body receives the receiver the + /// implicit-`this` cell holds, read at the call. #[inline] pub fn call(&self, closure: *const ClosureHeader, $($arg: f64),+) -> f64 { - (self.0)(closure, $($arg),+) + (self.0)(closure, crate::closure::JsThis::current(), $($arg),+) } /// Whether the direct target was resolved. Test-only: a "fast @@ -195,6 +208,7 @@ define_direct_call_site!( DirectCall1, 1, js_closure_call1, + direct_call1_fallback, arg0 ); @@ -204,6 +218,7 @@ define_direct_call_site!( DirectCall2, 2, js_closure_call2, + direct_call2_fallback, arg0, arg1 ); @@ -215,6 +230,7 @@ define_direct_call_site!( DirectCall3, 3, js_closure_call3, + direct_call3_fallback, arg0, arg1, arg2 @@ -226,6 +242,7 @@ define_direct_call_site!( DirectCall4, 4, js_closure_call4, + direct_call4_fallback, arg0, arg1, arg2, @@ -238,35 +255,75 @@ mod tests { // A capture-less body behind a real `ClosureHeader`, the same way // `array/tests.rs` and `array/typed_array_receiver_tests.rs` build theirs. - extern "C" fn add3(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { + extern "C" fn add3( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, + c: f64, + ) -> f64 { a * 100.0 + b * 10.0 + c } - extern "C" fn sum2(_c: *const ClosureHeader, a: f64, b: f64) -> f64 { + extern "C" fn sum2( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, + ) -> f64 { a + b } - extern "C" fn ordinary3(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { + extern "C" fn ordinary3( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, + c: f64, + ) -> f64 { a + b + c } - extern "C" fn rest_body(_c: *const ClosureHeader, _a: f64, _rest: f64) -> f64 { + extern "C" fn rest_body( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + _rest: f64, + ) -> f64 { 0.0 } - extern "C" fn trusted_add3(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { + extern "C" fn trusted_add3( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, + c: f64, + ) -> f64 { a * 100.0 + b * 10.0 + c } - extern "C" fn boxed1(_c: *const ClosureHeader, value: f64) -> f64 { + extern "C" fn boxed1( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, + ) -> f64 { value } - extern "C" fn trusted_boxed1(_c: *const ClosureHeader, value: f64) -> f64 { + extern "C" fn trusted_boxed1( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, + ) -> f64 { value } - extern "C" fn versioned_source(_c: *const ClosureHeader, value: f64) -> f64 { + extern "C" fn versioned_source( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, + ) -> f64 { value } diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index d86caf37b3..3ea50825fe 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -413,7 +413,8 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ /// `js_create_callback(func_ptr, closure_env, param_count)` registers a JS /// callable whose trampoline invokes `func_ptr(closure_env, args_ptr, /// args_len)`. Perry closure bodies have signature -/// `(closure_ptr, arg0, arg1, ...)` per arity instead, so the codegen +/// `(callee, this, arg0, arg1, ...)` per arity instead +/// (`perry_abi::JS_BODY_*`), so the codegen /// arm for `Expr::JsCreateCallback` (issue #248 Phase 2B) passes /// `js_closure_call_array` as the trampoline `func_ptr` and the raw /// `*const ClosureHeader` (NaN-boxing stripped) as `closure_env`. The diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index aea174e750..40defc0c08 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -17,6 +17,8 @@ mod unbox; mod v8_stubs; mod wide_call; +#[cfg(test)] +mod receiver_param_tests; #[cfg(test)] mod tests; @@ -33,6 +35,7 @@ pub use alloc::{ pub(crate) use alloc::{ closure_install_boxed_captures, gc_capture_slot_range, singleton_closure_if_cached, }; +pub use body_call::JsThis; pub use shape::closure_kind_probe; pub(crate) use registry::closure_registry_census; diff --git a/crates/perry-runtime/src/closure/receiver_param_tests.rs b/crates/perry-runtime/src/closure/receiver_param_tests.rs new file mode 100644 index 0000000000..8023153ec6 --- /dev/null +++ b/crates/perry-runtime/src/closure/receiver_param_tests.rs @@ -0,0 +1,152 @@ +//! This-as-a-parameter stage 1: a JS body is +//! `body(callee, this, a0, ...)` (`perry_abi::JS_BODY_*`), and every runtime +//! route into it passes, as `this`, the receiver the implicit-`this` cell +//! holds — with the JS arguments still in their own positions after it. +//! +//! Each test binds a receiver in the cell, calls a probe body through one +//! dispatch route (exact arity, padded arity, rest bundling, the padded wide +//! ladder, a hoisted `DirectCallN`), and checks what the probe received. +//! Sabotage: the funnel passes anything but `JsThis::current()` -> the +//! receiver assertions fail; the funnel drops the receiver argument (the +//! arguments shift one slot) -> the argument assertions fail. + +use super::*; +use std::cell::{Cell, RefCell}; + +thread_local! { + static SEEN_THIS: Cell = const { Cell::new(0) }; + static SEEN_ARGS: RefCell> = const { RefCell::new(Vec::new()) }; +} + +fn seen() -> (u64, Vec) { + ( + SEEN_THIS.with(Cell::get), + SEEN_ARGS.with(|a| a.borrow().clone()), + ) +} + +fn record(this: JsThis, args: &[f64]) { + SEEN_THIS.with(|c| c.set(this.bits())); + SEEN_ARGS.with(|a| *a.borrow_mut() = args.iter().map(|v| v.to_bits()).collect()); +} + +extern "C" fn probe3(_c: *const ClosureHeader, this: JsThis, a: f64, b: f64, d: f64) -> f64 { + record(this, &[a, b, d]); + a + b +} + +extern "C" fn probe_rest(_c: *const ClosureHeader, this: JsThis, a: f64, rest: f64) -> f64 { + let rest_len = crate::array::js_array_length( + crate::value::js_nanbox_get_pointer(rest) as *const crate::array::ArrayHeader + ); + record(this, &[a, rest_len as f64]); + 0.0 +} + +macro_rules! wide_probe { + ($($p:ident),+) => { + extern "C" fn probe_wide(_c: *const ClosureHeader, this: JsThis, $($p: f64),+) -> f64 { + record(this, &[$($p),+]); + 0.0 + } + }; +} +wide_probe!( + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, + a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35 +); + +const UNDEF: u64 = crate::value::TAG_UNDEFINED; + +/// Bind `receiver` in the cell for the duration of `f`, as a method dispatch does. +fn with_receiver(receiver: f64, f: impl FnOnce() -> R) -> R { + let scope = crate::gc::RuntimeHandleScope::new(); + let _bind = crate::object::ImplicitThisScope::bind(&scope, receiver); + f() +} + +fn receiver() -> f64 { + f64::from_bits(crate::value::JSValue::int32(4242).bits()) +} + +#[test] +fn an_exact_arity_call_passes_the_cell_receiver_then_the_arguments() { + let closure = js_closure_alloc(probe3 as *const u8, 0); + js_register_closure_arity(probe3 as *const u8, 3); + let r = with_receiver(receiver(), || js_closure_call3(closure, 1.0, 2.0, 3.0)); + assert_eq!(r, 3.0); + let (this, args) = seen(); + assert_eq!(this, receiver().to_bits(), "the body's `this` parameter"); + assert_eq!(args, vec![1f64.to_bits(), 2f64.to_bits(), 3f64.to_bits()]); +} + +#[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); + with_receiver(receiver(), || js_closure_call1(closure, 9.0)); + let (this, args) = seen(); + assert_eq!(this, receiver().to_bits()); + assert_eq!(args, vec![9f64.to_bits(), UNDEF, UNDEF]); +} + +#[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); + with_receiver(receiver(), || js_closure_call4(closure, 5.0, 6.0, 7.0, 8.0)); + let (this, args) = seen(); + assert_eq!(this, receiver().to_bits()); + assert_eq!( + args, + vec![5f64.to_bits(), 3f64.to_bits()], + "fixed arg, then rest length" + ); +} + +#[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 args: Vec = (0..36).map(f64::from).collect(); + let r = with_receiver(receiver(), || unsafe { + js_closure_call_array(closure as i64, args.as_ptr(), args.len() as i64) + }); + assert_eq!(r, 0.0); + let (this, seen_args) = seen(); + assert_eq!(this, receiver().to_bits()); + assert_eq!( + seen_args, + args.iter().map(|v| v.to_bits()).collect::>() + ); +} + +#[test] +fn a_hoisted_direct_call_passes_the_cell_receiver() { + let closure = js_closure_alloc(probe3 as *const u8, 0); + js_register_closure_arity(probe3 as *const u8, 3); + let site = DirectCall3::resolve(closure); + assert!(site.is_direct(), "the probe resolves to a direct call"); + with_receiver(receiver(), || site.call(closure, 1.0, 2.0, 3.0)); + let (this, args) = seen(); + assert_eq!(this, receiver().to_bits()); + assert_eq!(args, vec![1f64.to_bits(), 2f64.to_bits(), 3f64.to_bits()]); +} + +#[test] +fn the_witness_counts_a_parameter_that_disagrees_with_the_cell() { + let (checks0, mismatches0) = crate::object::this_witness_counts(); + let receiver_bits = receiver().to_bits(); + with_receiver(receiver(), || { + crate::object::js_this_param_witness(receiver_bits, std::ptr::null(), 0); + crate::object::js_this_param_witness(UNDEF, std::ptr::null(), 0); + }); + let (checks1, mismatches1) = crate::object::this_witness_counts(); + // Counters are process-wide; other tests may witness concurrently, so + // only lower bounds are exact. + assert!(checks1 >= checks0 + 2); + assert!( + mismatches1 > mismatches0, + "a disagreeing `this` was not counted" + ); +} diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index 2aa2589a2c..eaf7eecabf 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -484,24 +484,33 @@ fn invalidate_dispatch_strategy(func_ptr: *const u8) { mod dispatch_recent_tests { use super::*; - extern "C" fn body_a(_: *const ClosureHeader) -> f64 { + extern "C" fn body_a(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 1.0 } - extern "C" fn body_b(_: *const ClosureHeader) -> f64 { + extern "C" fn body_b(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 2.0 } - extern "C" fn body_c(_: *const ClosureHeader) -> f64 { + extern "C" fn body_c(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 3.0 } - extern "C" fn body_d(_: *const ClosureHeader) -> f64 { + extern "C" fn body_d(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 4.0 } - extern "C" fn add_two(_: *const ClosureHeader, left: f64, right: f64) -> f64 { + 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, value: f64) -> f64 { + 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 @@ -1186,11 +1195,11 @@ pub unsafe fn dispatch_rest_bundled( ($($i:tt),* $(,)?) => {{ if let Some(arguments_double) = all_arguments_double { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure $(, a!($i))*, rest_double, arguments_double + func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*, rest_double, arguments_double ) } else { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure $(, a!($i))*, rest_double + func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*, rest_double ) } }}; @@ -1273,7 +1282,7 @@ pub unsafe fn dispatch_with_arity( // `arm!` macro builds the (padded) call args from the arg-index token list. macro_rules! arm { ($($i:tt),* $(,)?) => { - crate::closure::body_call::js_body_call!(func_ptr, closure $(, a!($i))*) + crate::closure::body_call::js_body_call!(func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*) }; } match k { @@ -1413,28 +1422,28 @@ pub(crate) fn closure_registry_census() -> Vec mod body_record_tests { use super::*; - extern "C" fn rec_a(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_a(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 1.0 } - extern "C" fn rec_b(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_b(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 2.0 } - extern "C" fn rec_c(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_c(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 3.0 } - extern "C" fn rec_d(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_d(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 4.0 } - extern "C" fn rec_trusted(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_trusted(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 5.0 } - extern "C" fn rec_versioned(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_versioned(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 6.0 } - extern "C" fn rec_e(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_e(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 7.0 } - extern "C" fn rec_f(_: *const ClosureHeader) -> f64 { + extern "C" fn rec_f(_: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 8.0 } diff --git a/crates/perry-runtime/src/closure/shape.rs b/crates/perry-runtime/src/closure/shape.rs index 59b47c254c..07ddc49fae 100644 --- a/crates/perry-runtime/src/closure/shape.rs +++ b/crates/perry-runtime/src/closure/shape.rs @@ -422,10 +422,10 @@ mod tests { closure_set_dynamic_prop, closure_set_static_prototype, js_closure_alloc, }; - extern "C" fn plain_body(_c: *const ClosureHeader) -> f64 { + extern "C" fn plain_body(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 1.0 } - extern "C" fn async_body(_c: *const ClosureHeader) -> f64 { + extern "C" fn async_body(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 2.0 } @@ -571,7 +571,13 @@ mod tests { assert!(crate::closure::is_closure_ptr(fresh(plain_body) as usize)); } - extern "C" fn three_arg_body(_c: *const ClosureHeader, a: f64, b: f64, c: f64) -> f64 { + extern "C" fn three_arg_body( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, + c: f64, + ) -> f64 { a + b + c } diff --git a/crates/perry-runtime/src/closure/tests.rs b/crates/perry-runtime/src/closure/tests.rs index da8448cd1e..70e4f7e7f9 100644 --- a/crates/perry-runtime/src/closure/tests.rs +++ b/crates/perry-runtime/src/closure/tests.rs @@ -1,6 +1,9 @@ use super::*; -extern "C" fn test_closure_func(closure: *const ClosureHeader) -> f64 { +extern "C" fn test_closure_func( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let captured = js_closure_get_capture_f64(closure, 0); captured * 2.0 } diff --git a/crates/perry-runtime/src/closure/wide_call.rs b/crates/perry-runtime/src/closure/wide_call.rs index 9433c2bf02..6dc56a979e 100644 --- a/crates/perry-runtime/src/closure/wide_call.rs +++ b/crates/perry-runtime/src/closure/wide_call.rs @@ -64,7 +64,7 @@ pub(crate) unsafe fn dispatch_wide_abi( padded!($slots, [$($i,)+ $($i + $step),+], $step * 2, $($more)*) }; ($slots:ident, [$($i:expr),+], $step:expr,) => {{ - crate::closure::body_call::js_body_call_unwind!(func_ptr, closure $(, $slots[$i])+) + crate::closure::body_call::js_body_call_unwind!(func_ptr, closure, crate::closure::JsThis::current() $(, $slots[$i])+) }}; } macro_rules! fill { @@ -126,6 +126,7 @@ mod tests { extern "C" fn body_40( _: *const ClosureHeader, + _this: crate::closure::JsThis, a0: f64, a1: f64, a2: f64, @@ -231,6 +232,7 @@ mod tests { extern "C" fn rest_after_16( _: *const ClosureHeader, + _this: crate::closure::JsThis, a0: f64, a1: f64, a2: f64, diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 48e4c0fb90..33772178f1 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -809,6 +809,7 @@ pub(crate) fn is_worker_instance_value(value: f64) -> bool { extern "C" fn cluster_worker_send( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, message: f64, a2: f64, a3: f64, @@ -817,34 +818,62 @@ extern "C" fn cluster_worker_send( if is_self_worker_value(crate::object::js_implicit_this_get()) { return crate::process::process_ipc_send_call(message, a2, a3, a4); } - crate::child_process::cp_method_send(std::ptr::null(), message, a2, a3, a4) + crate::child_process::cp_method_send( + std::ptr::null(), + crate::closure::JsThis::current(), + message, + a2, + a3, + a4, + ) } -extern "C" fn cluster_worker_kill(_closure: *const ClosureHeader, signal: f64) -> f64 { +extern "C" fn cluster_worker_kill( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + signal: f64, +) -> f64 { if is_self_worker_value(crate::object::js_implicit_this_get()) { let _ = crate::os::js_process_kill(crate::os::js_process_pid(), signal); return TAG_UNDEFINED_F64; } - let _ = crate::child_process::cp_method_kill(std::ptr::null(), signal); + let _ = crate::child_process::cp_method_kill( + std::ptr::null(), + crate::closure::JsThis::current(), + signal, + ); TAG_UNDEFINED_F64 } -extern "C" fn cluster_worker_destroy(_closure: *const ClosureHeader, signal: f64) -> f64 { - cluster_worker_kill(std::ptr::null(), signal) +extern "C" fn cluster_worker_destroy( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + signal: f64, +) -> f64 { + cluster_worker_kill(std::ptr::null(), crate::closure::JsThis::current(), signal) } -extern "C" fn cluster_worker_disconnect(_closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_worker_disconnect( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let worker = crate::object::js_implicit_this_get(); set_field(worker, b"exitedAfterDisconnect", TAG_TRUE_F64); if is_self_worker_value(worker) { let _ = crate::process::process_ipc_disconnect_call(); } else { - let _ = crate::child_process::cp_method_disconnect(std::ptr::null()); + let _ = crate::child_process::cp_method_disconnect( + std::ptr::null(), + crate::closure::JsThis::current(), + ); } worker } -extern "C" fn cluster_worker_is_connected(_closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_worker_is_connected( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let worker = crate::object::js_implicit_this_get(); let connected = if is_self_worker_value(worker) { crate::process::ipc::process_ipc_property("connected").unwrap_or(TAG_FALSE_F64) @@ -858,7 +887,10 @@ extern "C" fn cluster_worker_is_connected(_closure: *const ClosureHeader) -> f64 } } -extern "C" fn cluster_worker_is_dead(_closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_worker_is_dead( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if is_worker_dead(crate::object::js_implicit_this_get()) { TAG_TRUE_F64 } else { @@ -950,7 +982,10 @@ fn apply_setup_primary(settings_arg: f64) { crate::timer::js_set_immediate_callback(deferred as i64); } -extern "C" fn cluster_setup_emit_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_setup_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let settings = js_closure_get_capture_f64(closure, 0); cluster_emit_event("setup", &[settings]); TAG_UNDEFINED_F64 @@ -962,7 +997,10 @@ fn defer_cluster_fork_event(worker: f64) { crate::timer::js_set_immediate_callback(deferred as i64); } -extern "C" fn cluster_fork_emit_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_fork_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let worker = js_closure_get_capture_f64(closure, 0); cluster_emit_event("fork", &[worker]); TAG_UNDEFINED_F64 @@ -1316,7 +1354,10 @@ fn drain_disconnect_callbacks_if_idle() { } } -extern "C" fn cluster_callback_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_callback_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let callback = f64::from_bits(crate::closure::js_closure_get_capture_ptr(closure, 0) as u64); unsafe { let _ = crate::closure::js_native_call_value(callback, std::ptr::null(), 0); @@ -1324,17 +1365,27 @@ extern "C" fn cluster_callback_thunk(closure: *const ClosureHeader) -> f64 { TAG_UNDEFINED_F64 } -extern "C" fn cluster_internal_online(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_internal_online( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { mark_worker_online(closure_this(closure)); TAG_UNDEFINED_F64 } -extern "C" fn cluster_internal_disconnect(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_internal_disconnect( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { mark_worker_disconnected(closure_this(closure)); TAG_UNDEFINED_F64 } -extern "C" fn cluster_internal_message(closure: *const ClosureHeader, message: f64) -> f64 { +extern "C" fn cluster_internal_message( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + message: f64, +) -> f64 { cluster_emit_event("message", &[closure_this(closure), message]); TAG_UNDEFINED_F64 } @@ -1355,13 +1406,21 @@ fn mark_worker_disconnected(worker: f64) { crate::timer::js_set_immediate_callback(deferred as i64); } -extern "C" fn cluster_disconnect_emit_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_disconnect_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let worker = js_closure_get_capture_f64(closure, 0); cluster_emit_event("disconnect", &[worker]); TAG_UNDEFINED_F64 } -extern "C" fn cluster_internal_exit(closure: *const ClosureHeader, code: f64, signal: f64) -> f64 { +extern "C" fn cluster_internal_exit( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + code: f64, + signal: f64, +) -> f64 { let worker = closure_this(closure); if JSValue::from_bits(get_field(worker, b"exitedAfterDisconnect").to_bits()).is_undefined() { set_field(worker, b"exitedAfterDisconnect", TAG_FALSE_F64); @@ -1400,7 +1459,10 @@ fn invoke_disconnect_callbacks_if_idle() { } } -extern "C" fn cluster_exit_emit_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn cluster_exit_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let worker = js_closure_get_capture_f64(closure, 0); let code = js_closure_get_capture_f64(closure, 1); let signal = js_closure_get_capture_f64(closure, 2); diff --git a/crates/perry-runtime/src/dgram.rs b/crates/perry-runtime/src/dgram.rs index 2e3d21f73d..9512701e6f 100644 --- a/crates/perry-runtime/src/dgram.rs +++ b/crates/perry-runtime/src/dgram.rs @@ -574,7 +574,10 @@ pub(crate) fn socket_async_ids(socket: f64) -> crate::async_hooks::AsyncResource } } -extern "C" fn dgram_async_dispose(closure: *const ClosureHeader) -> f64 { +extern "C" fn dgram_async_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { close_impl(this_value(closure), &[]); let promise = crate::promise::js_promise_resolved(undefined_value()); boxed_pointer(promise as *const u8) diff --git a/crates/perry-runtime/src/dgram/net.rs b/crates/perry-runtime/src/dgram/net.rs index fc313e7448..b335c9b44e 100644 --- a/crates/perry-runtime/src/dgram/net.rs +++ b/crates/perry-runtime/src/dgram/net.rs @@ -481,7 +481,10 @@ pub(crate) fn finish_send(socket: f64, args: &[f64], outcome: Result undefined_value() } -extern "C" fn deferred_send_callback(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn deferred_send_callback( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let callback = crate::closure::js_closure_get_capture_f64(closure, 0); let socket = crate::closure::js_closure_get_capture_f64(closure, 1); let error = crate::closure::js_closure_get_capture_f64(closure, 2); diff --git a/crates/perry-runtime/src/dgram/ops.rs b/crates/perry-runtime/src/dgram/ops.rs index 813b995683..76711a6fbf 100644 --- a/crates/perry-runtime/src/dgram/ops.rs +++ b/crates/perry-runtime/src/dgram/ops.rs @@ -9,7 +9,10 @@ use super::*; use std::net::Ipv4Addr; -extern "C" fn dgram_abort_close_task(closure: *const ClosureHeader) -> f64 { +extern "C" fn dgram_abort_close_task( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return undefined_value(); } @@ -26,7 +29,10 @@ fn schedule_abort_close(socket: f64) { crate::builtins::js_queue_microtask(task as i64); } -extern "C" fn dgram_abort_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn dgram_abort_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if !closure.is_null() { schedule_abort_close(js_closure_get_capture_f64(closure, 0)); } @@ -485,7 +491,11 @@ fn send_blocked(socket: f64, address: &str) -> bool { )) != 0 } -extern "C" fn lookup_callback(_closure: *const ClosureHeader, _rest: f64) -> f64 { +extern "C" fn lookup_callback( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { undefined_value() } diff --git a/crates/perry-runtime/src/dgram/thunks.rs b/crates/perry-runtime/src/dgram/thunks.rs index d03771fbe3..18ad9b9ce7 100644 --- a/crates/perry-runtime/src/dgram/thunks.rs +++ b/crates/perry-runtime/src/dgram/thunks.rs @@ -8,42 +8,75 @@ use super::*; use crate::closure::ClosureHeader; -pub(crate) extern "C" fn dgram_send_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_send_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { send_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_sendto_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_sendto_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { sendto_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_bind_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_bind_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { bind_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_close_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_close_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { close_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_address_thunk(closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_address_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { address_impl(this_value(closure)) } pub(crate) extern "C" fn dgram_remote_address_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _rest: f64, ) -> f64 { remote_address_impl(this_value(closure)) } -pub(crate) extern "C" fn dgram_connect_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_connect_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { connect_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_disconnect_thunk(closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_disconnect_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { disconnect_impl(this_value(closure)) } -pub(crate) extern "C" fn dgram_on_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_on_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let socket = this_value(closure); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); @@ -52,7 +85,11 @@ pub(crate) extern "C" fn dgram_on_thunk(closure: *const ClosureHeader, rest: f64 socket } -pub(crate) extern "C" fn dgram_once_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_once_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let socket = this_value(closure); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); @@ -63,6 +100,7 @@ pub(crate) extern "C" fn dgram_once_thunk(closure: *const ClosureHeader, rest: f pub(crate) extern "C" fn dgram_remove_listener_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let socket = this_value(closure); @@ -73,7 +111,11 @@ pub(crate) extern "C" fn dgram_remove_listener_thunk( socket } -pub(crate) extern "C" fn dgram_emit_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_emit_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let socket = this_value(closure); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); @@ -83,6 +125,7 @@ pub(crate) extern "C" fn dgram_emit_thunk(closure: *const ClosureHeader, rest: f pub(crate) extern "C" fn dgram_listener_count_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let args = collect_rest_args(rest); @@ -90,12 +133,17 @@ pub(crate) extern "C" fn dgram_listener_count_thunk( listener_snapshot(this_value(closure), event).len() as f64 } -pub(crate) extern "C" fn dgram_event_names_thunk(closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_event_names_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { event_names_impl(this_value(closure)) } pub(crate) extern "C" fn dgram_add_membership_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { membership_impl( @@ -107,6 +155,7 @@ pub(crate) extern "C" fn dgram_add_membership_thunk( pub(crate) extern "C" fn dgram_drop_membership_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { membership_impl( @@ -118,6 +167,7 @@ pub(crate) extern "C" fn dgram_drop_membership_thunk( pub(crate) extern "C" fn dgram_add_source_membership_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { source_membership_impl( @@ -129,6 +179,7 @@ pub(crate) extern "C" fn dgram_add_source_membership_thunk( pub(crate) extern "C" fn dgram_drop_source_membership_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { source_membership_impl( @@ -140,6 +191,7 @@ pub(crate) extern "C" fn dgram_drop_source_membership_thunk( pub(crate) extern "C" fn dgram_set_broadcast_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_broadcast_impl(this_value(closure), &collect_rest_args(rest)) @@ -147,6 +199,7 @@ pub(crate) extern "C" fn dgram_set_broadcast_thunk( pub(crate) extern "C" fn dgram_set_multicast_ttl_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_multicast_ttl_impl(this_value(closure), &collect_rest_args(rest)) @@ -154,6 +207,7 @@ pub(crate) extern "C" fn dgram_set_multicast_ttl_thunk( pub(crate) extern "C" fn dgram_set_multicast_loopback_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_multicast_loopback_impl(this_value(closure), &collect_rest_args(rest)) @@ -161,17 +215,23 @@ pub(crate) extern "C" fn dgram_set_multicast_loopback_thunk( pub(crate) extern "C" fn dgram_set_multicast_interface_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_multicast_interface_impl(this_value(closure), &collect_rest_args(rest)) } -pub(crate) extern "C" fn dgram_set_ttl_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_set_ttl_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { set_ttl_impl(this_value(closure), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_recv_buffer_size_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_buffer_size_impl( @@ -184,6 +244,7 @@ pub(crate) extern "C" fn dgram_set_recv_buffer_size_thunk( pub(crate) extern "C" fn dgram_set_send_buffer_size_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { set_buffer_size_impl( @@ -196,6 +257,7 @@ pub(crate) extern "C" fn dgram_set_send_buffer_size_thunk( pub(crate) extern "C" fn dgram_get_recv_buffer_size_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _rest: f64, ) -> f64 { get_buffer_size_impl( @@ -207,6 +269,7 @@ pub(crate) extern "C" fn dgram_get_recv_buffer_size_thunk( pub(crate) extern "C" fn dgram_get_send_buffer_size_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _rest: f64, ) -> f64 { get_buffer_size_impl( @@ -216,14 +279,26 @@ pub(crate) extern "C" fn dgram_get_send_buffer_size_thunk( ) } -pub(crate) extern "C" fn dgram_ref_thunk(closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_ref_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { ref_impl(this_value(closure), true) } -pub(crate) extern "C" fn dgram_unref_thunk(closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_unref_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { ref_impl(this_value(closure), false) } -pub(crate) extern "C" fn dgram_zero_thunk(_closure: *const ClosureHeader, _rest: f64) -> f64 { +pub(crate) extern "C" fn dgram_zero_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { 0.0 } diff --git a/crates/perry-runtime/src/disposable.rs b/crates/perry-runtime/src/disposable.rs index 268d79fe71..7c63eafec3 100644 --- a/crates/perry-runtime/src/disposable.rs +++ b/crates/perry-runtime/src/disposable.rs @@ -97,7 +97,10 @@ fn resolve_dispose_method(resource: f64, want_async: bool) -> f64 { // read `this` observe the resource. Used by `stack.use(resource)`. // --------------------------------------------------------------------------- -extern "C" fn bound_dispose_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn bound_dispose_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let method = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let resource = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); if !is_callable_value(method) { @@ -245,7 +248,10 @@ fn run_disposers(stack: *mut ObjectHeader) { // `adopt` closure: captures (value, onDispose) and calls onDispose(value). // --------------------------------------------------------------------------- -extern "C" fn adopt_disposer_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn adopt_disposer_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let value = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let on_dispose = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); let cb = JSValue::from_bits(on_dispose.to_bits()); diff --git a/crates/perry-runtime/src/dns.rs b/crates/perry-runtime/src/dns.rs index ea60a53806..3adebe5a5d 100644 --- a/crates/perry-runtime/src/dns.rs +++ b/crates/perry-runtime/src/dns.rs @@ -1206,15 +1206,23 @@ fn dns_promise_reverse(args: i64) -> f64 { } } -extern "C" fn dns_noop_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn dns_noop_thunk(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { undefined_value() } -extern "C" fn dns_noop2_thunk(_closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { +extern "C" fn dns_noop2_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + _b: f64, +) -> f64 { undefined_value() } -extern "C" fn dns_resolver_get_servers_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn dns_resolver_get_servers_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this_value = crate::object::js_implicit_this_get(); let Some(obj) = resolver_object_from_value(this_value) else { return empty_array_value(); @@ -1224,6 +1232,7 @@ extern "C" fn dns_resolver_get_servers_thunk(_closure: *const ClosureHeader) -> extern "C" fn dns_resolver_set_servers_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, servers_value: f64, ) -> f64 { let this_value = crate::object::js_implicit_this_get(); diff --git a/crates/perry-runtime/src/dyn_eval/interp.rs b/crates/perry-runtime/src/dyn_eval/interp.rs index 63fed8ff81..9b63f153a2 100644 --- a/crates/perry-runtime/src/dyn_eval/interp.rs +++ b/crates/perry-runtime/src/dyn_eval/interp.rs @@ -275,13 +275,18 @@ fn ensure_thunk_registered() { #[cfg(not(panic = "abort"))] extern "C-unwind" fn interp_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, raw_args: f64, ) -> f64 { interp_thunk_impl(closure, raw_args) } #[cfg(panic = "abort")] -extern "C" fn interp_thunk(closure: *const crate::closure::ClosureHeader, raw_args: f64) -> f64 { +extern "C" fn interp_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + raw_args: f64, +) -> f64 { interp_thunk_impl(closure, raw_args) } diff --git a/crates/perry-runtime/src/dyn_eval/tests.rs b/crates/perry-runtime/src/dyn_eval/tests.rs index 9f436ab22d..a702999818 100644 --- a/crates/perry-runtime/src/dyn_eval/tests.rs +++ b/crates/perry-runtime/src/dyn_eval/tests.rs @@ -444,7 +444,11 @@ fn generator_body_rejected_at_construction() { // ── host bridging: interpreted → host ────────────────────────────────────── -extern "C" fn host_double_thunk(_closure: *const crate::closure::ClosureHeader, v: f64) -> f64 { +extern "C" fn host_double_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + v: f64, +) -> f64 { num(as_num_raw(v) * 2.0) } diff --git a/crates/perry-runtime/src/error.rs b/crates/perry-runtime/src/error.rs index 85846243df..31dc5081bf 100644 --- a/crates/perry-runtime/src/error.rs +++ b/crates/perry-runtime/src/error.rs @@ -1174,22 +1174,40 @@ fn throw_capture_stack_trace_target_type_error() -> ! { // .getFileName()` throws `(string).getFileName is not a function` and modules // like `next/dist/compiled/send` (bundled depd) crash at eager init. -extern "C" fn callsite_undefined(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_undefined( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn callsite_null(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_null( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_NULL) } -extern "C" fn callsite_zero(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_zero( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 0.0 } -extern "C" fn callsite_false(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_false( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_FALSE) } -extern "C" fn callsite_true(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_true( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_TRUE) } -extern "C" fn callsite_to_string(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn callsite_to_string( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let s = js_string_from_bytes(b"".as_ptr(), 11); crate::value::js_nanbox_string(s as i64) } @@ -1297,7 +1315,10 @@ unsafe fn compute_stack_value(receiver: f64) -> f64 { /// Lazy `stack` accessor installed by `Error.captureStackTrace`. Fires on read /// with `this` bound to the target object (V8 semantics: `prepareStackTrace` is /// consulted at access time, not capture time). -extern "C" fn error_stack_lazy_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn error_stack_lazy_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let receiver = crate::object::js_implicit_this_get(); unsafe { compute_stack_value(receiver) } } diff --git a/crates/perry-runtime/src/error_subclass_stack.rs b/crates/perry-runtime/src/error_subclass_stack.rs index 2dbd4f96de..2e715cd942 100644 --- a/crates/perry-runtime/src/error_subclass_stack.rs +++ b/crates/perry-runtime/src/error_subclass_stack.rs @@ -77,7 +77,10 @@ unsafe fn error_subclass_stack_head(receiver: f64) -> String { /// reports `"E: m"`, and the assignment happens after `super()` returns. A /// user `Error.prepareStackTrace` still wins, same as `captureStackTrace`'s /// getter. -extern "C" fn error_subclass_stack_getter(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn error_subclass_stack_getter( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let receiver = crate::object::js_implicit_this_get(); unsafe { if let Some(prep) = error_prepare_stack_trace_override() { @@ -122,6 +125,7 @@ extern "C" fn error_subclass_stack_getter(closure: *const crate::closure::Closur /// enumerability, different reflection — a deliberate simplification.) extern "C" fn error_subclass_stack_setter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let receiver = crate::object::js_implicit_this_get(); diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index 99198bf612..00d559691c 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -167,7 +167,10 @@ fn event_bool_field(event: *mut ObjectHeader, name: &[u8]) -> bool { crate::value::js_is_truthy(value) != 0 } -extern "C" fn event_prevent_default_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn event_prevent_default_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this_value = crate::object::js_implicit_this_get(); let Some(event) = value_as_ptr::(this_value) else { return undefined_value(); @@ -178,7 +181,10 @@ extern "C" fn event_prevent_default_thunk(_closure: *const crate::closure::Closu undefined_value() } -extern "C" fn event_stop_propagation_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn event_stop_propagation_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this_value = crate::object::js_implicit_this_get(); if let Some(event) = value_as_ptr::(this_value) { set_event_field(event, b"_stopped", bool_value(true)); @@ -188,6 +194,7 @@ extern "C" fn event_stop_propagation_thunk(_closure: *const crate::closure::Clos extern "C" fn event_stop_immediate_propagation_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_value = crate::object::js_implicit_this_get(); if let Some(event) = value_as_ptr::(this_value) { @@ -808,6 +815,7 @@ unsafe fn remove_event_listener_with_capture( extern "C" fn event_target_abort_remove_listener( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let target = crate::closure::js_closure_get_capture_ptr(closure, 0) as *mut ObjectHeader; let event_name_ptr = @@ -1165,27 +1173,31 @@ fn event_proto_receiver() -> *mut ObjectHeader { extern "C" fn event_proto_prevent_default_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_prevent_default_thunk(closure) + event_prevent_default_thunk(closure, crate::closure::JsThis::current()) } extern "C" fn event_proto_stop_propagation_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_stop_propagation_thunk(closure) + event_stop_propagation_thunk(closure, crate::closure::JsThis::current()) } extern "C" fn event_proto_stop_immediate_propagation_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_stop_immediate_propagation_thunk(closure) + event_stop_immediate_propagation_thunk(closure, crate::closure::JsThis::current()) } extern "C" fn event_proto_init_event_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, bubbles: f64, cancelable: f64, @@ -1226,6 +1238,7 @@ extern "C" fn event_proto_init_event_thunk( extern "C" fn event_proto_composed_path_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let event = scope.root_raw_mut_ptr(event_proto_receiver()); @@ -1367,6 +1380,7 @@ unsafe fn bound_listener_call( extern "C" fn event_target_add_event_listener_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, options: f64, @@ -1376,6 +1390,7 @@ extern "C" fn event_target_add_event_listener_thunk( extern "C" fn event_target_remove_event_listener_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, options: f64, @@ -1385,6 +1400,7 @@ extern "C" fn event_target_remove_event_listener_thunk( extern "C" fn event_target_dispatch_event_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { unsafe { js_event_target_dispatch_event(bound_event_target(closure), event) } diff --git a/crates/perry-runtime/src/fs/deferred.rs b/crates/perry-runtime/src/fs/deferred.rs index 1b8197cb70..dbc8d5786e 100644 --- a/crates/perry-runtime/src/fs/deferred.rs +++ b/crates/perry-runtime/src/fs/deferred.rs @@ -264,7 +264,10 @@ fn callback_from_sink(sink: f64) -> *const ClosureHeader { /// The parked operation. Runs on a later event-loop turn; never on the turn /// that scheduled it, which is the whole point. -extern "C" fn perform_deferred_fs_op(closure: *const ClosureHeader) -> f64 { +extern "C" fn perform_deferred_fs_op( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let mode = js_closure_get_capture_f64(closure, CAP_MODE); // The operation allocates (error values, decoded strings), so the captured // JS values are read through handles rather than held raw across it — the 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 0eafaa264f..1412b1152c 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -923,7 +923,10 @@ fn start_promise_watcher(id: usize, state: &mut PromiseWatchState) { state.active = true; } -extern "C" fn watch_file_poll_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn watch_file_poll_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let delivery = WATCH_FILE_STATES.with(|states| { let mut states = states.borrow_mut(); @@ -946,13 +949,19 @@ extern "C" fn watch_file_poll_impl(closure: *const ClosureHeader) -> f64 { undefined_value() } -extern "C" fn fs_watcher_abort_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn fs_watcher_abort_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; close_fs_watcher(id); undefined_value() } -extern "C" fn promise_watcher_abort_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn promise_watcher_abort_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let signal = PROMISE_WATCHERS.with(|watchers| { watchers @@ -969,14 +978,20 @@ extern "C" fn promise_watcher_abort_impl(closure: *const ClosureHeader) -> f64 { undefined_value() } -extern "C" fn fs_watcher_close_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn fs_watcher_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let self_value = js_closure_get_capture_f64(closure, 1); close_fs_watcher(id); self_value } -extern "C" fn fs_watcher_ref_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn fs_watcher_ref_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let self_value = js_closure_get_capture_f64(closure, 1); FS_WATCHERS.with(|watchers| { @@ -987,7 +1002,10 @@ extern "C" fn fs_watcher_ref_impl(closure: *const ClosureHeader) -> f64 { self_value } -extern "C" fn fs_watcher_unref_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn fs_watcher_unref_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let self_value = js_closure_get_capture_f64(closure, 1); FS_WATCHERS.with(|watchers| { @@ -1000,6 +1018,7 @@ extern "C" fn fs_watcher_unref_impl(closure: *const ClosureHeader) -> f64 { extern "C" fn fs_watcher_on_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1017,6 +1036,7 @@ extern "C" fn fs_watcher_on_impl( extern "C" fn fs_watcher_once_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1034,6 +1054,7 @@ extern "C" fn fs_watcher_once_impl( extern "C" fn fs_watcher_off_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1049,7 +1070,10 @@ extern "C" fn fs_watcher_off_impl( self_value } -extern "C" fn stat_watcher_ref_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stat_watcher_ref_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let self_value = js_closure_get_capture_f64(closure, 1); WATCH_FILE_STATES.with(|states| { @@ -1060,7 +1084,10 @@ extern "C" fn stat_watcher_ref_impl(closure: *const ClosureHeader) -> f64 { self_value } -extern "C" fn stat_watcher_unref_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stat_watcher_unref_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let self_value = js_closure_get_capture_f64(closure, 1); WATCH_FILE_STATES.with(|states| { @@ -1073,6 +1100,7 @@ extern "C" fn stat_watcher_unref_impl(closure: *const ClosureHeader) -> f64 { extern "C" fn stat_watcher_on_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1090,6 +1118,7 @@ extern "C" fn stat_watcher_on_impl( extern "C" fn stat_watcher_once_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1107,6 +1136,7 @@ extern "C" fn stat_watcher_once_impl( extern "C" fn stat_watcher_off_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event_value: f64, listener: f64, ) -> f64 { @@ -1135,7 +1165,10 @@ enum GlobNextAction { Entry(FsGlobMatch, bool), } -extern "C" fn glob_iterator_next_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn glob_iterator_next_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let action = GLOB_ITERATORS.with(|iterators| { let mut iterators = iterators.borrow_mut(); @@ -1163,7 +1196,10 @@ extern "C" fn glob_iterator_next_impl(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn glob_iterator_return_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn glob_iterator_return_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; GLOB_ITERATORS.with(|iterators| { iterators.borrow_mut().remove(&id); @@ -1171,11 +1207,17 @@ extern "C" fn glob_iterator_return_impl(closure: *const ClosureHeader) -> f64 { resolved_iterator_promise(undefined_value(), true) } -extern "C" fn glob_iterator_self_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn glob_iterator_self_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_closure_get_capture_f64(closure, 1) } -extern "C" fn bun_glob_sync_iterator_next_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_glob_sync_iterator_next_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let action = GLOB_ITERATORS.with(|iterators| { let mut iterators = iterators.borrow_mut(); @@ -1205,7 +1247,10 @@ extern "C" fn bun_glob_sync_iterator_next_impl(closure: *const ClosureHeader) -> } } -extern "C" fn bun_glob_sync_iterator_return_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_glob_sync_iterator_return_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; GLOB_ITERATORS.with(|iterators| { iterators.borrow_mut().remove(&id); @@ -1213,11 +1258,17 @@ extern "C" fn bun_glob_sync_iterator_return_impl(closure: *const ClosureHeader) iterator_result(undefined_value(), true) } -extern "C" fn bun_glob_sync_iterator_self_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_glob_sync_iterator_self_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_closure_get_capture_f64(closure, 1) } -extern "C" fn promise_watcher_next_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn promise_watcher_next_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let action = PROMISE_WATCHERS.with(|watchers| { let mut watchers = watchers.borrow_mut(); @@ -1255,7 +1306,10 @@ extern "C" fn promise_watcher_next_impl(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn promise_watcher_return_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn promise_watcher_return_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let pending = close_promise_watcher_return(id); for promise in pending { @@ -1264,7 +1318,10 @@ extern "C" fn promise_watcher_return_impl(closure: *const ClosureHeader) -> f64 resolved_iterator_promise(undefined_value(), true) } -extern "C" fn promise_watcher_self_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn promise_watcher_self_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_closure_get_capture_f64(closure, 1) } diff --git a/crates/perry-runtime/src/fs/fd_ops.rs b/crates/perry-runtime/src/fs/fd_ops.rs index 3cd693f0ad..3b94055f31 100644 --- a/crates/perry-runtime/src/fs/fd_ops.rs +++ b/crates/perry-runtime/src/fs/fd_ops.rs @@ -916,11 +916,17 @@ pub(crate) fn make_dir_method(id: usize, func: *const u8) -> f64 { f64::from_bits(crate::value::JSValue::pointer(closure as *const u8).bits()) } -pub(crate) extern "C" fn dir_read_sync_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn dir_read_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { dir_read_next(dir_id_of(closure)) } -pub(crate) extern "C" fn dir_close_sync_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn dir_close_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = dir_id_of(closure); match dir_close_sync_result(id) { Ok(()) => f64::from_bits(crate::value::TAG_UNDEFINED), @@ -950,7 +956,10 @@ fn dir_clear_operation_pending(id: usize) { }); } -extern "C" fn dir_finish_read_promise_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_finish_read_promise_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = dir_id_of(closure); let promise = crate::closure::js_closure_get_capture_ptr(closure, 1) as *mut crate::promise::Promise; @@ -982,7 +991,10 @@ fn dir_pending_read_promise(id: usize, result: Result, f64>) -> f64 f64::from_bits(crate::value::JSValue::pointer(promise as *const u8).bits()) } -extern "C" fn dir_finish_read_callback_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_finish_read_callback_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { const TAG_NULL: u64 = 0x7FFC_0000_0000_0002; let id = dir_id_of(closure); let callback = crate::closure::js_closure_get_capture_ptr(closure, 1) as *const ClosureHeader; @@ -1026,7 +1038,11 @@ fn dir_schedule_read_callback( crate::builtins::js_queue_microtask(closure as i64); } -pub(crate) extern "C" fn dir_read_impl(closure: *const ClosureHeader, callback: f64) -> f64 { +pub(crate) extern "C" fn dir_read_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback: f64, +) -> f64 { const TAG_NULL: u64 = 0x7FFC_0000_0000_0002; let id = dir_id_of(closure); let begin = dir_begin_async_operation(id); @@ -1068,7 +1084,11 @@ pub(crate) extern "C" fn dir_read_impl(closure: *const ClosureHeader, callback: } } -pub(crate) extern "C" fn dir_close_impl(closure: *const ClosureHeader, callback: f64) -> f64 { +pub(crate) extern "C" fn dir_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback: f64, +) -> f64 { const TAG_NULL: u64 = 0x7FFC_0000_0000_0002; let closed = dir_close_result(dir_id_of(closure)); let cb = extract_closure_ptr(callback); @@ -1121,7 +1141,10 @@ fn dir_iterator_result(value: f64, done: bool) -> f64 { f64::from_bits(crate::value::JSValue::pointer(obj as *const u8).bits()) } -extern "C" fn dir_iterator_next_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_iterator_next_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match dir_read_next_result(dir_iterator_id_of(closure)) { Ok(Some(value)) => promise_value_fs(dir_iterator_result(value, false)), Ok(None) => { @@ -1135,7 +1158,10 @@ extern "C" fn dir_iterator_next_impl(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn dir_iterator_return_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_iterator_return_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { dir_mark_closed(dir_iterator_id_of(closure)); promise_value_fs(dir_iterator_result( f64::from_bits(crate::value::TAG_UNDEFINED), @@ -1143,25 +1169,40 @@ extern "C" fn dir_iterator_return_impl(closure: *const ClosureHeader) -> f64 { )) } -extern "C" fn dir_iterator_self_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_iterator_self_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { dir_iterator_self_value(closure) } -extern "C" fn dir_entries_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_entries_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { unsafe { build_dir_iterator_object(dir_id_of(closure)) } } -extern "C" fn dir_dispose_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_dispose_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { dir_mark_closed(dir_id_of(closure)); f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn dir_async_dispose_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_async_dispose_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { dir_mark_closed(dir_id_of(closure)); promise_undefined_fs() } -extern "C" fn dir_path_getter_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn dir_path_getter_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::closure::js_closure_get_capture_f64(closure, 0) } diff --git a/crates/perry-runtime/src/fs/filehandle.rs b/crates/perry-runtime/src/fs/filehandle.rs index 9e928540f8..639a1325d5 100644 --- a/crates/perry-runtime/src/fs/filehandle.rs +++ b/crates/perry-runtime/src/fs/filehandle.rs @@ -267,7 +267,11 @@ fn build_read_lines_step(value: f64, done: bool) -> f64 { f64::from_bits(crate::value::JSValue::pointer(obj as *const u8).bits()) } -extern "C" fn read_lines_next_impl(closure: *const ClosureHeader, _arg: f64) -> f64 { +extern "C" fn read_lines_next_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { let id = read_lines_id(closure); let next_line = READ_LINES_REGISTRY.with(|states| { let mut states = states.borrow_mut(); @@ -294,7 +298,11 @@ extern "C" fn read_lines_next_impl(closure: *const ClosureHeader, _arg: f64) -> promise_value_fs(build_read_lines_step(value, false)) } -extern "C" fn read_lines_return_impl(closure: *const ClosureHeader, _arg: f64) -> f64 { +extern "C" fn read_lines_return_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { READ_LINES_REGISTRY.with(|states| { states.borrow_mut().remove(&read_lines_id(closure)); }); @@ -304,14 +312,20 @@ extern "C" fn read_lines_return_impl(closure: *const ClosureHeader, _arg: f64) - )) } -extern "C" fn read_lines_close_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn read_lines_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { READ_LINES_REGISTRY.with(|states| { states.borrow_mut().remove(&read_lines_id(closure)); }); f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn read_lines_iterator_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn read_lines_iterator_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::closure::js_closure_get_capture_ptr(closure, 0) as u64) } @@ -396,7 +410,10 @@ fn read_filehandle_webstream_chunk(fd: i32) -> Option> { }) } -extern "C" fn filehandle_webstream_pull_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn filehandle_webstream_pull_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let handle = webstream_handle(closure); let fallback_fd = filehandle_fd(closure); let fd = filehandle_field_fd(handle).unwrap_or(fallback_fd); @@ -412,7 +429,11 @@ extern "C" fn filehandle_webstream_pull_impl(closure: *const ClosureHeader) -> f f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn filehandle_webstream_cancel_impl(closure: *const ClosureHeader, _reason: f64) -> f64 { +extern "C" fn filehandle_webstream_cancel_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _reason: f64, +) -> f64 { let handle = webstream_handle(closure); let fallback_fd = filehandle_fd(closure); let fd = filehandle_field_fd(handle).unwrap_or(fallback_fd); @@ -441,6 +462,7 @@ fn readable_webstream_auto_close(options: f64) -> bool { pub(crate) extern "C" fn filehandle_readable_web_stream_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let raw = READABLE_WEB_STREAM_FACTORY.load(Ordering::Acquire); @@ -702,7 +724,10 @@ fn read_stream_iter_action(id: usize) -> StreamIterAction { }) } -extern "C" fn stream_iter_next_async_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_iter_next_async_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match read_stream_iter_action(stream_iter_id(closure)) { StreamIterAction::Batch(bytes) => { promise_value_fs(build_read_lines_step(build_chunk_batch(&bytes), false)) @@ -715,7 +740,10 @@ extern "C" fn stream_iter_next_async_impl(closure: *const ClosureHeader) -> f64 } } -extern "C" fn stream_iter_return_async_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_iter_return_async_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let close = STREAM_ITER_REGISTRY.with(|states| { let mut states = states.borrow_mut(); let Some(mut state) = states.remove(&stream_iter_id(closure)) else { @@ -728,7 +756,10 @@ extern "C" fn stream_iter_return_async_impl(closure: *const ClosureHeader) -> f6 promise_value_fs(build_read_lines_step(undefined_value(), true)) } -extern "C" fn stream_iter_next_sync_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_iter_next_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match read_stream_iter_action(stream_iter_id(closure)) { StreamIterAction::Batch(bytes) => build_read_lines_step(build_chunk_batch(&bytes), false), StreamIterAction::Done(close) => { @@ -739,7 +770,10 @@ extern "C" fn stream_iter_next_sync_impl(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn stream_iter_return_sync_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_iter_return_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let close = STREAM_ITER_REGISTRY.with(|states| { let mut states = states.borrow_mut(); let Some(mut state) = states.remove(&stream_iter_id(closure)) else { @@ -752,7 +786,10 @@ extern "C" fn stream_iter_return_sync_impl(closure: *const ClosureHeader) -> f64 build_read_lines_step(undefined_value(), true) } -extern "C" fn stream_iter_self_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_iter_self_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { stream_iter_self(closure) } @@ -804,7 +841,11 @@ fn build_stream_iter_state( } } -pub(crate) extern "C" fn filehandle_pull_impl(closure: *const ClosureHeader, options: f64) -> f64 { +pub(crate) extern "C" fn filehandle_pull_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { let state = build_stream_iter_state(closure, options); let id = next_stream_iter_id(); STREAM_ITER_REGISTRY.with(|states| { @@ -815,6 +856,7 @@ pub(crate) extern "C" fn filehandle_pull_impl(closure: *const ClosureHeader, opt pub(crate) extern "C" fn filehandle_pull_sync_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let state = build_stream_iter_state(closure, options); @@ -918,60 +960,86 @@ fn writer_end_common(id: usize) -> Result { Ok(total) } -extern "C" fn writer_write_impl(closure: *const ClosureHeader, data: f64) -> f64 { +extern "C" fn writer_write_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, +) -> f64 { match writer_write_common(writer_id(closure), data, false, true) { Ok(_) => promise_undefined_fs(), Err(err) => promise_rejected_fs(err), } } -extern "C" fn writer_writev_impl(closure: *const ClosureHeader, buffers: f64) -> f64 { +extern "C" fn writer_writev_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + buffers: f64, +) -> f64 { match writer_write_common(writer_id(closure), buffers, true, true) { Ok(_) => promise_undefined_fs(), Err(err) => promise_rejected_fs(err), } } -extern "C" fn writer_write_sync_impl(closure: *const ClosureHeader, data: f64) -> f64 { +extern "C" fn writer_write_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, +) -> f64 { match writer_write_common(writer_id(closure), data, false, false) { Ok(wrote) => bool_value(wrote), Err(err) => crate::exception::js_throw(err), } } -extern "C" fn writer_writev_sync_impl(closure: *const ClosureHeader, buffers: f64) -> f64 { +extern "C" fn writer_writev_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + buffers: f64, +) -> f64 { match writer_write_common(writer_id(closure), buffers, true, false) { Ok(wrote) => bool_value(wrote), Err(err) => crate::exception::js_throw(err), } } -extern "C" fn writer_end_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn writer_end_impl(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { match writer_end_common(writer_id(closure)) { Ok(total) => promise_value_fs(total as f64), Err(err) => promise_rejected_fs(err), } } -extern "C" fn writer_end_sync_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn writer_end_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match writer_end_common(writer_id(closure)) { Ok(total) => total as f64, Err(err) => crate::exception::js_throw(err), } } -extern "C" fn writer_fail_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn writer_fail_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = writer_end_common(writer_id(closure)); undefined_value() } -extern "C" fn writer_async_dispose_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn writer_async_dispose_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = writer_end_common(writer_id(closure)); promise_undefined_fs() } pub(crate) extern "C" fn filehandle_writer_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let handle = filehandle_object(closure).unwrap_or(undefined_value()); @@ -1059,7 +1127,10 @@ fn filehandle_close_promise() -> f64 { }) } -pub(crate) extern "C" fn filehandle_close_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn filehandle_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let fd = filehandle_fd(closure); if let Some(handle) = filehandle_object(closure) { close_filehandle_fd(filehandle_field_fd(handle).unwrap_or(fd), handle); @@ -1069,7 +1140,10 @@ pub(crate) extern "C" fn filehandle_close_impl(closure: *const ClosureHeader) -> filehandle_close_promise() } -pub(crate) extern "C" fn filehandle_sync_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn filehandle_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { // Bypass `js_fs_fsync_sync`'s arg-validation: FileHandle may legitimately // hold a `-1` fd sentinel from a failed open, and Node's API surfaces that // earlier (at `open`), not here. @@ -1077,12 +1151,19 @@ pub(crate) extern "C" fn filehandle_sync_impl(closure: *const ClosureHeader) -> promise_undefined_fs() } -pub(crate) extern "C" fn filehandle_datasync_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn filehandle_datasync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = crate::fs::fdatasync_sync_inner(filehandle_fd(closure)); promise_undefined_fs() } -pub(crate) extern "C" fn filehandle_stat_impl(closure: *const ClosureHeader, options: f64) -> f64 { +pub(crate) extern "C" fn filehandle_stat_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { let fd = match live_filehandle_fd_or_ebadf(closure, "fstat") { Ok(fd) => fd, Err(rejection) => return rejection, @@ -1090,7 +1171,11 @@ pub(crate) extern "C" fn filehandle_stat_impl(closure: *const ClosureHeader, opt promise_value_fs(js_fs_fstat_sync_options(fd as f64, options)) } -pub(crate) extern "C" fn filehandle_truncate_impl(closure: *const ClosureHeader, len: f64) -> f64 { +pub(crate) extern "C" fn filehandle_truncate_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + len: f64, +) -> f64 { let fd = match live_filehandle_fd_or_ebadf(closure, "ftruncate") { Ok(fd) => fd, Err(rejection) => return rejection, @@ -1101,6 +1186,7 @@ pub(crate) extern "C" fn filehandle_truncate_impl(closure: *const ClosureHeader, pub(crate) extern "C" fn filehandle_utimes_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, atime: f64, mtime: f64, ) -> f64 { @@ -1112,7 +1198,11 @@ pub(crate) extern "C" fn filehandle_utimes_impl( promise_undefined_fs() } -pub(crate) extern "C" fn filehandle_chmod_impl(closure: *const ClosureHeader, mode: f64) -> f64 { +pub(crate) extern "C" fn filehandle_chmod_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + mode: f64, +) -> f64 { let fd = match live_filehandle_fd_or_ebadf(closure, "fchmod") { Ok(fd) => fd, Err(rejection) => return rejection, @@ -1123,6 +1213,7 @@ pub(crate) extern "C" fn filehandle_chmod_impl(closure: *const ClosureHeader, mo pub(crate) extern "C" fn filehandle_chown_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, uid: f64, gid: f64, ) -> f64 { @@ -1136,6 +1227,7 @@ pub(crate) extern "C" fn filehandle_chown_impl( pub(crate) extern "C" fn filehandle_read_file_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, encoding: f64, ) -> f64 { let fd = filehandle_fd(closure); @@ -1174,6 +1266,7 @@ pub(crate) extern "C" fn filehandle_read_file_impl( pub(crate) extern "C" fn filehandle_write_file_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, data: f64, options: f64, ) -> f64 { @@ -1191,6 +1284,7 @@ pub(crate) extern "C" fn filehandle_write_file_impl( pub(crate) extern "C" fn filehandle_append_file_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, data: f64, options: f64, ) -> f64 { @@ -1206,6 +1300,7 @@ pub(crate) extern "C" fn filehandle_append_file_impl( pub(crate) extern "C" fn filehandle_read_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, buffer: f64, offset: f64, length: f64, @@ -1258,6 +1353,7 @@ pub(crate) extern "C" fn filehandle_read_impl( pub(crate) extern "C" fn filehandle_write_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, data: f64, offset: f64, length: f64, @@ -1288,6 +1384,7 @@ pub(crate) extern "C" fn filehandle_write_impl( pub(crate) extern "C" fn filehandle_readv_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, buffers: f64, position: f64, ) -> f64 { @@ -1305,6 +1402,7 @@ pub(crate) extern "C" fn filehandle_readv_impl( pub(crate) extern "C" fn filehandle_writev_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, buffers: f64, position: f64, ) -> f64 { @@ -1326,6 +1424,7 @@ pub(crate) fn path_for_fd(fd: i32) -> Option { pub(crate) extern "C" fn filehandle_create_read_stream_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let fallback_fd = filehandle_fd(closure); @@ -1352,6 +1451,7 @@ pub(crate) extern "C" fn filehandle_create_read_stream_impl( pub(crate) extern "C" fn filehandle_create_write_stream_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let fallback_fd = filehandle_fd(closure); @@ -1378,6 +1478,7 @@ pub(crate) extern "C" fn filehandle_create_write_stream_impl( pub(crate) extern "C" fn filehandle_read_lines_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let fallback_fd = filehandle_fd(closure); diff --git a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs index b9730a46b3..3df41715eb 100644 --- a/crates/perry-runtime/src/fs/mkdtemp_disposable.rs +++ b/crates/perry-runtime/src/fs/mkdtemp_disposable.rs @@ -38,7 +38,10 @@ fn rejected_promise(reason: f64) -> f64 { f64::from_bits(crate::value::JSValue::pointer(promise as *const u8).bits()) } -extern "C" fn mkdtemp_disposable_remove_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn mkdtemp_disposable_remove_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let path_value = crate::closure::js_closure_get_capture_f64(closure, 0); match remove_temp_dir_result(path_value) { Ok(()) => f64::from_bits(crate::value::TAG_UNDEFINED), @@ -46,7 +49,10 @@ extern "C" fn mkdtemp_disposable_remove_impl(closure: *const ClosureHeader) -> f } } -extern "C" fn mkdtemp_disposable_async_remove_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn mkdtemp_disposable_async_remove_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let path_value = crate::closure::js_closure_get_capture_f64(closure, 0); match remove_temp_dir_result(path_value) { Ok(()) => resolved_promise(f64::from_bits(crate::value::TAG_UNDEFINED)), diff --git a/crates/perry-runtime/src/fs/stats.rs b/crates/perry-runtime/src/fs/stats.rs index c5cf61b816..edbba67238 100644 --- a/crates/perry-runtime/src/fs/stats.rs +++ b/crates/perry-runtime/src/fs/stats.rs @@ -16,6 +16,7 @@ use super::*; pub(crate) extern "C" fn stats_closure_return_captured( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { // Slot 0 holds the pre-computed NaN-boxed boolean. crate::closure::js_closure_get_capture_f64(closure, 0) diff --git a/crates/perry-runtime/src/fs/stream.rs b/crates/perry-runtime/src/fs/stream.rs index 28c6143479..056881fd1a 100644 --- a/crates/perry-runtime/src/fs/stream.rs +++ b/crates/perry-runtime/src/fs/stream.rs @@ -878,7 +878,10 @@ fn schedule_write_stream_turn(id: usize) { } } -extern "C" fn write_stream_turn_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn write_stream_turn_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&id) { @@ -1111,6 +1114,7 @@ fn write_stream_close_step(id: usize) { pub(crate) extern "C" fn write_stream_write_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, encoding: f64, cb: f64, @@ -1155,6 +1159,7 @@ pub(crate) extern "C" fn write_stream_write_impl( pub(crate) extern "C" fn write_stream_end_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, encoding: f64, cb: f64, @@ -1199,6 +1204,7 @@ pub(crate) extern "C" fn write_stream_end_impl( pub(crate) extern "C" fn write_stream_on_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1208,6 +1214,7 @@ pub(crate) extern "C" fn write_stream_on_impl( pub(crate) extern "C" fn write_stream_once_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1217,6 +1224,7 @@ pub(crate) extern "C" fn write_stream_once_impl( pub(crate) extern "C" fn stream_emit_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, arg: f64, ) -> f64 { @@ -1245,7 +1253,11 @@ use stream_errors::*; mod utf8_stream; pub(crate) use utf8_stream::*; -pub(crate) extern "C" fn write_stream_close_impl(closure: *const ClosureHeader, cb: f64) -> f64 { +pub(crate) extern "C" fn write_stream_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, +) -> f64 { let id = stream_id_of(closure); if is_callable_value(cb) { add_listener(id, "close", cb, true); @@ -1352,7 +1364,10 @@ fn install_pipe_drain_resume(source_id: usize, dest: f64) { let _ = call_js_method2(dest, b"once", string_value(b"drain"), listener); } -extern "C" fn read_stream_resume_from_drain_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn read_stream_resume_from_drain_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&id) { @@ -1474,6 +1489,7 @@ fn read_stream_pump(id: usize) { pub(crate) extern "C" fn read_stream_on_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1492,6 +1508,7 @@ pub(crate) extern "C" fn read_stream_on_impl( pub(crate) extern "C" fn read_stream_once_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1510,6 +1527,7 @@ pub(crate) extern "C" fn read_stream_once_impl( pub(crate) extern "C" fn read_stream_pipe_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, dest: f64, options: f64, ) -> f64 { @@ -1528,7 +1546,10 @@ pub(crate) extern "C" fn read_stream_pipe_impl( dest } -pub(crate) extern "C" fn read_stream_pause_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn read_stream_pause_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&stream_id_of(closure)) { state.paused = true; @@ -1537,7 +1558,10 @@ pub(crate) extern "C" fn read_stream_pause_impl(closure: *const ClosureHeader) - current_receiver_value() } -pub(crate) extern "C" fn read_stream_resume_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn read_stream_resume_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&id) { @@ -1548,7 +1572,10 @@ pub(crate) extern "C" fn read_stream_resume_impl(closure: *const ClosureHeader) current_receiver_value() } -pub(crate) extern "C" fn read_stream_is_paused_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn read_stream_is_paused_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let paused = STREAM_REGISTRY.with(|registry| { registry .borrow() @@ -1559,7 +1586,11 @@ pub(crate) extern "C" fn read_stream_is_paused_impl(closure: *const ClosureHeade bool_value(paused) } -pub(crate) extern "C" fn read_stream_close_impl(closure: *const ClosureHeader, cb: f64) -> f64 { +pub(crate) extern "C" fn read_stream_close_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, +) -> f64 { let id = stream_id_of(closure); if is_callable_value(cb) { add_listener(id, "close", cb, true); diff --git a/crates/perry-runtime/src/fs/stream/read_turn.rs b/crates/perry-runtime/src/fs/stream/read_turn.rs index d39e0332c1..09884d0bcc 100644 --- a/crates/perry-runtime/src/fs/stream/read_turn.rs +++ b/crates/perry-runtime/src/fs/stream/read_turn.rs @@ -26,7 +26,10 @@ pub(super) fn schedule_read_stream_turn(id: usize) { } } -pub(super) extern "C" fn read_stream_turn_impl(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn read_stream_turn_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); let should_close = STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&id) { diff --git a/crates/perry-runtime/src/fs/stream/utf8_stream.rs b/crates/perry-runtime/src/fs/stream/utf8_stream.rs index 0840ae4920..41e61ecac0 100644 --- a/crates/perry-runtime/src/fs/stream/utf8_stream.rs +++ b/crates/perry-runtime/src/fs/stream/utf8_stream.rs @@ -754,7 +754,11 @@ fn utf8_stream_write_by_id(id: usize, data: f64) -> f64 { bool_value(len_after < high_water_mark) } -pub(crate) extern "C" fn utf8_stream_write_impl(closure: *const ClosureHeader, data: f64) -> f64 { +pub(crate) extern "C" fn utf8_stream_write_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, +) -> f64 { utf8_stream_write_by_id(stream_id_of(closure), data) } @@ -839,6 +843,7 @@ fn utf8_stream_flush_by_id(id: usize, callback: f64) -> f64 { pub(crate) extern "C" fn utf8_stream_flush_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback: f64, ) -> f64 { utf8_stream_flush_by_id(stream_id_of(closure), callback) @@ -879,7 +884,10 @@ fn utf8_stream_flush_sync_by_id(id: usize) -> f64 { undefined_value() } -pub(crate) extern "C" fn utf8_stream_flush_sync_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_stream_flush_sync_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { utf8_stream_flush_sync_by_id(stream_id_of(closure)) } @@ -898,7 +906,10 @@ fn utf8_emit_close_events(id: usize, emit_finish: bool) { utf8_emit_event0(id, "close"); } -pub(crate) extern "C" fn utf8_close_events_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_close_events_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); let emit_finish = js_closure_get_capture_ptr(closure, 1) != 0; utf8_emit_close_events(id, emit_finish); @@ -970,7 +981,10 @@ fn utf8_stream_end_by_id(id: usize) -> f64 { undefined_value() } -pub(crate) extern "C" fn utf8_stream_end_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_stream_end_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { utf8_stream_end_by_id(stream_id_of(closure)) } @@ -992,7 +1006,10 @@ fn utf8_stream_destroy_by_id(id: usize) -> f64 { undefined_value() } -pub(crate) extern "C" fn utf8_stream_destroy_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_stream_destroy_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { utf8_stream_destroy_by_id(stream_id_of(closure)) } @@ -1085,6 +1102,7 @@ fn utf8_stream_reopen_by_id(id: usize, file_value: f64) -> f64 { pub(crate) extern "C" fn utf8_stream_reopen_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, file_value: f64, ) -> f64 { utf8_stream_reopen_by_id(stream_id_of(closure), file_value) @@ -1100,6 +1118,7 @@ pub extern "C" fn js_fs_utf8_stream_reopen(stream_value: f64, file_value: f64) - pub(crate) extern "C" fn utf8_stream_on_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1110,6 +1129,7 @@ pub(crate) extern "C" fn utf8_stream_on_impl( pub(crate) extern "C" fn utf8_stream_once_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1120,6 +1140,7 @@ pub(crate) extern "C" fn utf8_stream_once_impl( pub(crate) extern "C" fn utf8_stream_off_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1129,6 +1150,7 @@ pub(crate) extern "C" fn utf8_stream_off_impl( pub(crate) extern "C" fn utf8_stream_remove_all_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1146,6 +1168,7 @@ pub(crate) extern "C" fn utf8_stream_remove_all_impl( pub(crate) extern "C" fn utf8_stream_listener_count_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1162,6 +1185,7 @@ pub(crate) extern "C" fn utf8_stream_listener_count_impl( pub(crate) extern "C" fn utf8_stream_emit_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, arg: f64, ) -> f64 { @@ -1260,7 +1284,10 @@ pub extern "C" fn js_fs_utf8_stream_emit(stream_value: f64, event: f64, arg: f64 utf8_stream_emit_value(stream_value, event, arg).unwrap_or_else(|| bool_value(false)) } -pub(crate) extern "C" fn utf8_periodic_flush_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_periodic_flush_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); let destroyed = UTF8_STREAM_REGISTRY.with(|registry| { registry @@ -1369,6 +1396,7 @@ fn utf8_async_open_finish(id: usize, err: f64, fd_value: f64) -> f64 { pub(crate) extern "C" fn utf8_async_open_done_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, fd_value: f64, ) -> f64 { @@ -1406,6 +1434,7 @@ fn utf8_custom_open(id: usize) -> bool { pub(crate) extern "C" fn utf8_async_mkdir_done_impl( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1497,7 +1526,10 @@ pub(crate) fn utf8_start_async_open(id: usize) { utf8_schedule_native_open(id); } -extern "C" fn utf8_async_native_mkdir_then_custom_open_impl(closure: *const ClosureHeader) -> f64 { +extern "C" fn utf8_async_native_mkdir_then_custom_open_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); let Some(file) = UTF8_STREAM_REGISTRY.with(|registry| { registry @@ -1517,7 +1549,10 @@ extern "C" fn utf8_async_native_mkdir_then_custom_open_impl(closure: *const Clos undefined_value() } -pub(crate) extern "C" fn utf8_async_open_impl(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn utf8_async_open_impl( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = stream_id_of(closure); let Some(file) = UTF8_STREAM_REGISTRY.with(|registry| { registry 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 00976de64e..45a1017b10 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 @@ -119,12 +119,16 @@ fn array_slot_enumeration_walks_the_heap() { extern "C" fn species_destination( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _length: f64, ) -> f64 { crate::closure::js_closure_get_capture_f64(closure, 0) } -extern "C" fn interrupt_species_copy(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn interrupt_species_copy( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::exception::js_throw(9983.0) } 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 cd2b46e64e..1e4a623ccb 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 @@ -30,7 +30,10 @@ use super::*; -extern "C" fn test_bound_method_body(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn test_bound_method_body( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 0.0 } 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 1ea175c046..617a4b07b1 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 @@ -303,6 +303,7 @@ fn test_registry_tracks_holder_across_three_moving_minors() { extern "C" fn finreg_registry_test_callback( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _held: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) 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 21dc222b33..478303a479 100644 --- a/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs +++ b/crates/perry-runtime/src/gc/tests/copying/weak_semantics.rs @@ -116,6 +116,7 @@ fn test_copying_minor_weakmap_dead_key_entry_clears() { extern "C" fn finreg_test_callback( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _held: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) 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 bbfb2fd0d4..4627097e4c 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 @@ -125,7 +125,10 @@ fn test_typed_shape_descriptor_dynamic_pointer_mutation_falls_back_to_unknown_la clear_mark_seeds(); } -extern "C" fn layout_mask_test_closure(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn layout_mask_test_closure( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 0.0 } 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 a3d765182b..d08ebcf323 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_container_values.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_container_values.rs @@ -141,6 +141,7 @@ thread_local! { /// already-collected `(key, item)` pairs goes stale. extern "C" fn group_by_moving_callback( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _item: f64, index: f64, ) -> 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 7bdd253754..9f6105cabd 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_define_property.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_define_property.rs @@ -77,7 +77,10 @@ fn addr_of(value: f64) -> usize { /// receiver `js_object_define_property` is holding — and then allocates the /// payload string, so the retired from-space bytes are reused before the caller /// reads its locals again. -extern "C" fn moving_value_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn moving_value_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let trace = collect_minor_trace(GcTriggerKind::Direct); GETTER_COPIED_OBJECTS.with(|c| c.set(c.get() + trace.copying_nursery.copied_objects)); string_value("payload") @@ -252,7 +255,10 @@ fn desc_view_field_values_are_rooted() { } } -extern "C" fn moving_writable_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn moving_writable_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let trace = collect_minor_trace(GcTriggerKind::Direct); GETTER_COPIED_OBJECTS.with(|c| c.set(c.get() + trace.copying_nursery.copied_objects)); f64::from_bits(crate::value::TAG_TRUE) 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 206abc0be8..991780912f 100644 --- a/crates/perry-runtime/src/gc/tests/rooted_for_in.rs +++ b/crates/perry-runtime/src/gc/tests/rooted_for_in.rs @@ -8,7 +8,11 @@ thread_local! { static COPIED: std::cell::Cell = const { std::cell::Cell::new(0) }; } -extern "C" fn moving_own_keys(_closure: *const crate::closure::ClosureHeader, target: f64) -> f64 { +extern "C" fn moving_own_keys( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + target: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let target = scope.root_nanbox_f64(target); let trace = collect_minor_trace(GcTriggerKind::Direct); @@ -131,6 +135,7 @@ fn for_in_grown_result_and_receiver_survive_prototype_collection() { extern "C" fn moving_descriptor( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, ) -> f64 { @@ -150,7 +155,10 @@ fn descriptor_trap_collection_preserves_for_in_target_and_keys() { run(false, true); } -extern "C" fn moving_value(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn moving_value( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let trace = collect_minor_trace(GcTriggerKind::Direct); COPIED.with(|count| count.set(count.get() + trace.copying_nursery.copied_objects)); 23.0 @@ -158,6 +166,7 @@ extern "C" fn moving_value(_closure: *const crate::closure::ClosureHeader) -> f6 extern "C" fn descriptor_with_moving_field( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _target: f64, _key: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots.rs b/crates/perry-runtime/src/gc/tests/runtime_roots.rs index b78396beca..43073c6402 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots.rs @@ -454,6 +454,7 @@ fn test_prototype_resolution_stack_scanner_is_registered() { extern "C" fn test_reviver_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _key: f64, value: f64, ) -> f64 { @@ -467,6 +468,7 @@ thread_local! { extern "C" fn test_reviver_count_closure_leaf( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _key: f64, value: f64, ) -> f64 { @@ -486,6 +488,7 @@ extern "C" fn test_reviver_count_closure_leaf( extern "C" fn test_promise_identity_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { let _ = crate::gc::gc_collect_minor(); @@ -494,6 +497,7 @@ extern "C" fn test_promise_identity_force_minor_gc( extern "C" fn test_promise_finally_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { let _ = crate::gc::gc_collect_minor(); @@ -502,6 +506,7 @@ extern "C" fn test_promise_finally_force_minor_gc( extern "C" fn test_array_identity_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _index: f64, ) -> f64 { @@ -517,6 +522,7 @@ thread_local! { extern "C" fn test_foreach_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, key: f64, ) -> f64 { @@ -532,6 +538,7 @@ extern "C" fn test_foreach_force_minor_gc( extern "C" fn test_async_hook_init_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _async_id: f64, _type_name: f64, _trigger_async_id: f64, @@ -543,6 +550,7 @@ extern "C" fn test_async_hook_init_force_minor_gc( extern "C" fn test_async_hook_event_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _async_id: f64, ) -> f64 { let _ = crate::gc::gc_collect_minor(); @@ -557,6 +565,7 @@ thread_local! { extern "C" fn test_timer_capture_arg( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { TEST_TIMER_CALLBACK_PTR.with(|slot| slot.set(closure as usize)); @@ -565,13 +574,17 @@ extern "C" fn test_timer_capture_arg( f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn test_timer_force_minor_gc(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn test_timer_force_minor_gc( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = crate::gc::gc_collect_minor(); f64::from_bits(crate::value::TAG_UNDEFINED) } extern "C" fn test_rest_first_value( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let rest_ptr = (rest.to_bits() & POINTER_MASK) as *const crate::array::ArrayHeader; 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 c642cd13e8..0ecfb78796 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 @@ -13,6 +13,7 @@ use super::super::support::*; extern "C" fn collect_then_double( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _index: f64, _recv: f64, 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 13f79d52f9..7dc918457f 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 @@ -31,6 +31,7 @@ fn seen() -> Vec { extern "C" fn record_this_and_six_args( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a0: f64, a1: f64, _a2: f64, @@ -49,6 +50,7 @@ extern "C" fn record_this_and_six_args( extern "C" fn record_two_args( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a0: f64, a1: f64, ) -> f64 { @@ -59,6 +61,7 @@ extern "C" fn record_two_args( #[allow(clippy::too_many_arguments)] extern "C" fn record_rest_and_arguments_after_16( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a0: f64, _a1: f64, _a2: f64, 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 28c71731df..431ef6dbc1 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 @@ -1660,6 +1660,7 @@ thread_local! { extern "C" fn test_sort_comparator_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, ) -> f64 { @@ -1752,7 +1753,10 @@ fn test_array_sort_comparator_rooted_buffers_survive_copied_minor_gc() { } } -extern "C" fn test_tojson_force_minor_gc(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn test_tojson_force_minor_gc( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = crate::gc::gc_collect_minor(); test_string_value(b"tojson-out") } @@ -1869,6 +1873,7 @@ fn test_json_stringify_array_rederives_elements_after_tojson_minor_gc() { extern "C" fn test_replacer_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, matched: f64, _offset: f64, _whole: f64, @@ -1917,6 +1922,7 @@ fn test_string_replace_all_fn_rederives_subject_after_callback_minor_gc() { extern "C" fn test_bigint_comparator_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, ) -> f64 { 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 e08b2eda40..a27f628429 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 @@ -36,7 +36,11 @@ use super::*; /// A function pointer to register in the generator-function registry. Never /// called — only its address is used. -extern "C" fn fake_generator_body(_c: *const crate::closure::ClosureHeader, _a: f64) -> f64 { +extern "C" fn fake_generator_body( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } 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 d249f341eb..ded5d05b43 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 @@ -1,6 +1,9 @@ use super::*; -extern "C" fn test_current_async_id(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn test_current_async_id( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::async_hooks::execution_async_id_u64() as f64 } diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs index 1275d81e81..031f0287fb 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs @@ -14,13 +14,17 @@ use super::super::super::*; use super::super::support::*; -extern "C" fn collect_arity0(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collect_arity0( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::gc::gc_collect_minor(); f64::from_bits(crate::value::TAG_UNDEFINED) } extern "C" fn collect_arity3( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, _index: f64, _recv: f64, 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 42c6dd1245..3b577a35f7 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 @@ -72,7 +72,10 @@ fn json_nested_records_fallback_runs_getter_once_and_survives_actual_movement() } } -extern "C" fn array_getter_collects(_closure: *const crate::ClosureHeader) -> f64 { +extern "C" fn array_getter_collects( + _closure: *const crate::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { GETTER_CALLS.with(|c| c.set(c.get() + 1)); crate::gc::gc_collect_minor(); 17.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 fbf11f0a06..c86568e799 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 @@ -296,12 +296,18 @@ fn perex_recompile_publishes_after_success_before_throwing_lastindex_write() { ); } -extern "C" fn flags_collect_then_throw(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn flags_collect_then_throw( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(812.0) } -extern "C" fn flags_collect_then_return(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn flags_collect_then_return( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); js_nanbox_string(crate::string::js_string_from_bytes(b"g".as_ptr(), 1) as i64) } 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 d1988a7019..848beb3eb4 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 @@ -67,18 +67,34 @@ fn test(receiver: &RuntimeHandle<'_>, input: &RuntimeHandle<'_>) -> bool { input.with_const_ptr(|input| crate::regex::js_regexp_test(raw, input)) != 0 } -extern "C" fn return_this(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn return_this( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { crate::object::js_implicit_this_get() } -extern "C" fn return_null(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn return_null( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { f64::from_bits(TAG_NULL) } -extern "C" fn read_answer(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn read_answer( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); api::finish(dispatch::get(&receiver, b"answer")) } -extern "C" fn collect_and_echo(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn collect_and_echo( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let argument = scope.root_nanbox_f64(arg); @@ -86,18 +102,25 @@ extern "C" fn collect_and_echo(_: *const crate::closure::ClosureHeader, arg: f64 put(&receiver, b"seen", &argument); receiver.get_nanbox_f64() } -extern "C" fn collecting_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collecting_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); let fp = collect_and_echo as *const u8; crate::closure::js_register_closure_arity(fp, 1); js_nanbox_pointer(crate::closure::js_closure_alloc_singleton(fp) as i64) } -extern "C" fn collect_missing_apply(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collect_missing_apply( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); f64::from_bits(TAG_UNDEFINED) } extern "C" fn echo_apply( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _target: f64, receiver: f64, args: f64, @@ -115,23 +138,40 @@ extern "C" fn echo_apply( put(&receiver, b"seen", &value); receiver.get_nanbox_f64() } -extern "C" fn throw_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn throw_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(843.0) } -extern "C" fn throw_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throw_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(844.0) } -extern "C" fn ordered_string(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_string( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ORDER.with(|n| n.set(n.get() * 10 + 1)); js_nanbox_string(crate::string::js_string_from_bytes(b"x".as_ptr(), 1) as i64) } -extern "C" fn ordered_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn ordered_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { ORDER.with(|n| n.set(n.get() * 10 + 3)); js_nanbox_pointer(crate::array::js_array_alloc(0) as i64) } -extern "C" fn ordered_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_getter( + _: *const crate::closure::ClosureHeader, + _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); 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 f925f4d5ea..2bc3485c95 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 @@ -151,7 +151,11 @@ impl Drop for Directory { std::fs::remove_dir_all(&self.0).unwrap(); } } -extern "C" fn exclude(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn exclude( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let arg = scope.root_nanbox_f64(arg); gc_collect_minor(); @@ -164,7 +168,11 @@ extern "C" fn exclude(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 TAG_FALSE }) } -extern "C" fn throw_exclude(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn throw_exclude( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(991.0) } 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 3fb3eb69c2..0c8c467ff0 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 @@ -132,23 +132,47 @@ fn retained_input_population() -> usize { && bytes(js_nanbox_string(p as i64)) == b"a unique input retained only by the iterator" }) } -extern "C" fn identity(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn identity( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { arg } -extern "C" fn throw_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throw_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::exception::js_throw(941.0) } -extern "C" fn throw_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn throw_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(942.0) } -extern "C" fn no_match(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn no_match( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { f64::from_bits(TAG_NULL) } -extern "C" fn return_this(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn return_this( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { crate::object::js_implicit_this_get() } -extern "C" fn factory(_: *const crate::closure::ClosureHeader, receiver: f64, flags: f64) -> f64 { +extern "C" fn factory( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + receiver: f64, + flags: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(receiver); let flags = scope.root_nanbox_f64(flags); @@ -511,7 +535,10 @@ fn perex_match_all_proxy_species_and_dynamic_regexp_constructors_keep_original_s assert_eq!(get(&step, b"done").to_bits(), crate::value::TAG_TRUE); } -extern "C" fn collecting_missing_construct(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collecting_missing_construct( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); f64::from_bits(TAG_UNDEFINED) } @@ -527,32 +554,52 @@ fn ordered_field(event: u64, name: &[u8]) -> f64 { gc_collect_minor(); get(&receiver, name) } -extern "C" fn ordered_input(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_input( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ordered_field(1, b"text") } -extern "C" fn ordered_constructor(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_constructor( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ordered_field(2, b"holder") } -extern "C" fn ordered_species(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_species( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ordered_field(3, b"factory") } -extern "C" fn ordered_flags(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_flags( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ordered_field(4, b"flagText") } extern "C" fn ordered_factory( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, receiver: f64, flags: f64, ) -> f64 { ordered(5); - factory(closure, receiver, flags) + factory(closure, crate::closure::JsThis::current(), receiver, flags) } -extern "C" fn ordered_index(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ordered_index( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { ordered(6); gc_collect_minor(); 1.75 } -extern "C" fn ordered_set_index(_: *const crate::closure::ClosureHeader, index: f64) -> f64 { +extern "C" fn ordered_set_index( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + index: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); ordered(7); @@ -619,7 +666,11 @@ fn perex_match_all_species_and_lastindex_order_survives_collecting_callbacks() { assert_eq!(bytes(get(&matcher, b"seenFlags")), b"gu"); } -extern "C" fn arrow_species(_: *const crate::closure::ClosureHeader, value: f64) -> f64 { +extern "C" fn arrow_species( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { value } 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 d47029787f..94163f186e 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 @@ -69,26 +69,45 @@ fn bytes(value: f64) -> Vec { let (data, n) = crate::string::str_bytes_from_jsvalue(value, &mut short).unwrap(); unsafe { std::slice::from_raw_parts(data, n as usize).to_vec() } } -extern "C" fn identity(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn identity( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { arg } -extern "C" fn throw_string(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throw_string( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::exception::js_throw(901.0) } -extern "C" fn search_override(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn search_override( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); gc_collect_minor(); api::finish(dispatch::set_last_index(&receiver, 7.0)); receiver.get_nanbox_f64() } -extern "C" fn search_throw(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn search_throw( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); api::finish(dispatch::set_last_index(&receiver, 7.0)); crate::exception::js_throw(902.0) } -extern "C" fn global_override(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn global_override( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); gc_collect_minor(); @@ -106,7 +125,10 @@ extern "C" fn global_override(_: *const crate::closure::ClosureHeader, _: f64) - put(&receiver, b"0", &capture); receiver.get_nanbox_f64() } -extern "C" fn collecting_flags(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collecting_flags( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); js_nanbox_string(crate::string::js_string_from_bytes(b"gu".as_ptr(), 2) as i64) } 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 b368f6c518..011a558f56 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 @@ -314,7 +314,10 @@ fn perex_public_throwing_lastindex_write_releases_native_scratch_and_roots() { ); } -extern "C" fn throw_on_coercion(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throw_on_coercion( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(731.0) } 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 ae48894c50..644c3362a1 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 @@ -106,10 +106,18 @@ pub(super) fn captured<'s>( ); f } -extern "C" fn throw_text(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throw_text( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::exception::js_throw(971.0) } -extern "C" fn hook(_: *const crate::closure::ClosureHeader, input: f64, replacement: f64) -> f64 { +extern "C" fn hook( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + input: f64, + replacement: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let input = scope.root_nanbox_f64(input); let replacement = scope.root_nanbox_f64(replacement); @@ -119,6 +127,7 @@ extern "C" fn hook(_: *const crate::closure::ClosureHeader, input: f64, replacem } extern "C" fn builtin_callback( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, matched: f64, first: f64, absent: f64, @@ -163,7 +172,11 @@ extern "C" fn builtin_callback( ); matched.get_nanbox_f64() } -extern "C" fn alias_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn alias_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); gc_collect_minor(); @@ -180,6 +193,7 @@ extern "C" fn alias_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 } extern "C" fn alias_callback( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, matched: f64, position: f64, input: f64, @@ -205,7 +219,10 @@ extern "C" fn alias_callback( put(&state, b"replacements", get(&state, b"replacements") + 1.0); js_nanbox_string(crate::string::js_string_from_bytes(b"X".as_ptr(), 1) as i64) } -extern "C" fn named_getter(c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn named_getter( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); gc_collect_minor(); @@ -216,6 +233,7 @@ extern "C" fn named_getter(c: *const crate::closure::ClosureHeader) -> f64 { } extern "C" fn collecting_throw( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _: f64, _: f64, _: f64, @@ -447,7 +465,10 @@ fn perex_replace_collecting_throw_cleans_native_arguments_roots_and_this() { crate::object::js_implicit_this_set(displaced.get_nanbox_f64()); } -extern "C" fn primitive_hook_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn primitive_hook_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { assert_eq!(crate::object::js_implicit_this_get(), 23.0); gc_collect_minor(); crate::closure::js_register_closure_arity(hook as *const u8, 2); 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 a3839ddeaf..894dd8a0d6 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 @@ -114,6 +114,7 @@ fn direct_templates_match_the_ordinary_loop() { extern "C" fn describe( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, matched: f64, capture: f64, position: f64, @@ -197,6 +198,7 @@ fn direct_callbacks_receive_the_ordinary_arguments() { extern "C" fn meddle( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, matched: f64, _position: f64, _input: f64, @@ -212,7 +214,11 @@ extern "C" fn meddle( matched.get_nanbox_f64() } -extern "C" fn never_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn never_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { f64::from_bits(crate::value::TAG_NULL) } @@ -229,7 +235,11 @@ fn a_replacer_cannot_change_which_matches_are_replaced() { assert_eq!(last, 0.0); } -extern "C" fn counting_exec(c: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn counting_exec( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); put(&state, b"calls", get(&state, b"calls") + 1.0); 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 8c4c192ea1..81e0940173 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 @@ -134,7 +134,10 @@ fn getter(owner: &RuntimeHandle<'_>, name: &[u8], value: &RuntimeHandle<'_>) { f64::from_bits(TAG_UNDEFINED), ); } -extern "C" fn throwing(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn throwing( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::exception::js_throw(983.0) } @@ -239,7 +242,12 @@ fn perex_split_literal_half_pairs_nonoverlap_and_uint32_limits() { assert_eq!(bytes(item(&result, 4095)), b"\xed\xb8\x80"); } -extern "C" fn hook(_: *const crate::closure::ClosureHeader, input: f64, limit: f64) -> f64 { +extern "C" fn hook( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + input: f64, + limit: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let input = scope.root_nanbox_f64(input); let limit = scope.root_nanbox_f64(limit); @@ -251,7 +259,10 @@ extern "C" fn hook(_: *const crate::closure::ClosureHeader, input: f64, limit: f )); input.get_nanbox_f64() } -extern "C" fn primitive_hook(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn primitive_hook( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { assert_eq!(crate::object::js_implicit_this_get(), 23.0); gc_collect_minor(); crate::closure::js_register_closure_arity(hook as *const u8, 2); @@ -310,11 +321,19 @@ fn event(n: u64) { ORDER.with(|o| o.set(o.get() * 10 + n)); } -extern "C" fn matrix_third(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn matrix_third( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(2, b"value") } -extern "C" fn matrix_hook(_: *const crate::closure::ClosureHeader, input: f64, _: f64) -> f64 { +extern "C" fn matrix_hook( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + input: f64, + _: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let input = scope.root_nanbox_f64(input); let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); @@ -328,7 +347,10 @@ extern "C" fn matrix_hook(_: *const crate::closure::ClosureHeader, input: f64, _ input.get_nanbox_f64() } -extern "C" fn matrix_hook_getter(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn matrix_hook_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { assert!(crate::proxy::reflect_value_is_object( crate::object::js_implicit_this_get() )); @@ -559,22 +581,42 @@ fn field_event(n: u64, name: &[u8]) -> f64 { gc_collect_minor(); get(&receiver, name) } -extern "C" fn input_text(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn input_text( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(1, b"text") } -extern "C" fn constructor_get(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn constructor_get( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(2, b"holder") } -extern "C" fn species_get(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn species_get( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(3, b"factory") } -extern "C" fn flags_get(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn flags_get( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(4, b"flagText") } -extern "C" fn limit_get(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn limit_get( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(6, b"number") } -extern "C" fn factory(_: *const crate::closure::ClosureHeader, receiver: f64, flags: f64) -> f64 { +extern "C" fn factory( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + receiver: f64, + flags: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(receiver); let flags = scope.root_nanbox_f64(flags); @@ -584,7 +626,11 @@ extern "C" fn factory(_: *const crate::closure::ClosureHeader, receiver: f64, fl put(&matcher, b"seenFlags", flags.get_nanbox_f64()); matcher.get_nanbox_f64() } -extern "C" fn empty_exec(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { +extern "C" fn empty_exec( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, +) -> f64 { event(7); gc_collect_minor(); f64::from_bits(TAG_NULL) @@ -661,7 +707,11 @@ fn perex_split_species_order_zero_limit_and_empty_input() { ); } -extern "C" fn custom_exec(c: *const crate::closure::ClosureHeader, input: f64) -> f64 { +extern "C" fn custom_exec( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + input: f64, +) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); let input = scope.root_nanbox_f64(input); @@ -682,7 +732,10 @@ extern "C" fn custom_exec(c: *const crate::closure::ClosureHeader, input: f64) - check(&nested, &[Some(b"a"), Some(b"c")]); get(&state, b"result") } -extern "C" fn capture_get(c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn capture_get( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); gc_collect_minor(); @@ -749,7 +802,12 @@ fn perex_split_custom_exec_capture_values_reentrancy_and_limit_short_circuit() { ); } -extern "C" fn throwing_hook(_: *const crate::closure::ClosureHeader, _: f64, _: f64) -> f64 { +extern "C" fn throwing_hook( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, + _: f64, +) -> f64 { gc_collect_minor(); crate::exception::js_throw(984.0) } @@ -908,19 +966,30 @@ fn perex_split_raw_copy_reacquires_moved_storage_and_cleans_up_partial_output() assert_eq!(external_side_live_bytes(), live); } -extern "C" fn primitive_result(c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn primitive_result( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = RuntimeHandleScope::new(); let value = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); gc_collect_minor(); value.get_nanbox_f64() } -extern "C" fn primitive_number_hint(c: *const crate::closure::ClosureHeader, hint: f64) -> f64 { +extern "C" fn primitive_number_hint( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + hint: f64, +) -> f64 { assert_eq!(bytes(hint), b"number"); - primitive_result(c) + primitive_result(c, crate::closure::JsThis::current()) } -extern "C" fn primitive_string_hint(c: *const crate::closure::ClosureHeader, hint: f64) -> f64 { +extern "C" fn primitive_string_hint( + c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + hint: f64, +) -> f64 { assert_eq!(bytes(hint), b"string"); - primitive_result(c) + primitive_result(c, crate::closure::JsThis::current()) } #[test] @@ -1048,7 +1117,10 @@ fn perex_construction_uses_strict_string_conversion_for_pattern_and_flags() { } } } -extern "C" fn separator_text(_: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn separator_text( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_event(8, b"text") } @@ -1242,6 +1314,7 @@ fn perex_split_forward_search_matches_the_sticky_specification() { /// JavaScript can reach it afterwards, as any user factory could. extern "C" fn recording_regexp_species( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, receiver: f64, flags: f64, ) -> f64 { 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 6b1b323f33..db3c3eb259 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 @@ -27,7 +27,10 @@ use crate::regex::RegExpHeader; use crate::string::StringHeader; /// Stand-in for `{ valueOf() { …allocating user JS…; return 0 } }`. -extern "C" fn collect_then_zero(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn collect_then_zero( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::gc::gc_collect_minor(); // Recycle the just-retired from-space blocks back out as fresh strings, so // a stale borrow reads THESE bytes rather than the subject's old ones. 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 cce9203fd5..1c439da528 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 @@ -1,7 +1,12 @@ use super::*; use crate::closure::ClosureHeader; -extern "C" fn captured_direction(closure: *const ClosureHeader, a: f64, b: f64) -> f64 { +extern "C" fn captured_direction( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, +) -> f64 { // Model generated capture loads: no forwarding lookup can repair a stale // closure argument. The getter changes only the relocated capture. let direction = unsafe { @@ -10,7 +15,10 @@ extern "C" fn captured_direction(closure: *const ClosureHeader, a: f64, b: f64) direction * (a - b) } -extern "C" fn collecting_getter(closure: *const ClosureHeader) -> f64 { +extern "C" fn collecting_getter( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = RuntimeHandleScope::new(); let getter = scope.root_raw_const_ptr(closure); gc_collect_minor(); @@ -25,11 +33,19 @@ extern "C" fn collecting_getter(closure: *const ClosureHeader) -> f64 { 3.0 } -extern "C" fn accept_sorted_value(_closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn accept_sorted_value( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn collecting_index_setter(_closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn collecting_index_setter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, +) -> f64 { gc_collect_minor(); f64::from_bits(crate::value::TAG_UNDEFINED) } 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 3b471a048d..e6cfd19ac1 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 @@ -125,7 +125,10 @@ thread_local! { /// The form object's `toString`: forces a real copying minor — the moving /// collection a user `toString`'s loop back-edge polls would run — then /// returns the form name. -extern "C" fn normalize_form_force_minor_gc(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn normalize_form_force_minor_gc( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { NORMALIZE_FORM_COERCIONS.with(|c| c.set(c.get() + 1)); let _ = crate::gc::gc_collect_minor(); test_string_value(b"NFC") 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 4865bab032..c01012c977 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 @@ -21,6 +21,7 @@ use super::*; /// RELOCATED rather than freed. extern "C" fn test_thenable_then_force_minor_gc( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, resolve: f64, _reject: f64, ) -> f64 { 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 f3a95c3046..0ddf2081ad 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 @@ -590,7 +590,10 @@ fn test_transient_runtime_handle_object_overflow_set_gc() { #[test] fn test_transient_runtime_handle_closure_captures_gc() { let _legacy_pacing = crate::gc::policy::force_legacy_gc_pacing(); - extern "C" fn captured_func(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn captured_func( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 0.0 } diff --git a/crates/perry-runtime/src/gc/tests/step_bounds.rs b/crates/perry-runtime/src/gc/tests/step_bounds.rs index 11977024bf..e8faf54ed2 100644 --- a/crates/perry-runtime/src/gc/tests/step_bounds.rs +++ b/crates/perry-runtime/src/gc/tests/step_bounds.rs @@ -24,6 +24,7 @@ fn reset_old_reclaim_pressure() { extern "C" fn finreg_step_bounds_callback( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _held: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) diff --git a/crates/perry-runtime/src/gc/tests/support.rs b/crates/perry-runtime/src/gc/tests/support.rs index 8cfd348382..ff2e3cce5f 100644 --- a/crates/perry-runtime/src/gc/tests/support.rs +++ b/crates/perry-runtime/src/gc/tests/support.rs @@ -747,12 +747,14 @@ pub(super) fn old_page_dirty_for(page: usize) -> bool { pub(super) extern "C" fn test_no_capture_singleton_func( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { 0.0 } pub(super) extern "C" fn test_captured_singleton_func( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { 0.0 } diff --git a/crates/perry-runtime/src/intl.rs b/crates/perry-runtime/src/intl.rs index 7e195ee4ec..7ec7fa62e8 100644 --- a/crates/perry-runtime/src/intl.rs +++ b/crates/perry-runtime/src/intl.rs @@ -1642,6 +1642,7 @@ fn make_instance(closure: *const ClosureHeader, kind: &str, locales: f64, option pub(super) extern "C" fn number_format_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { make_instance(closure, KIND_NUMBER, rest_arg(rest, 0), rest_arg(rest, 1)) @@ -1649,6 +1650,7 @@ pub(super) extern "C" fn number_format_constructor_thunk( pub(super) extern "C" fn date_time_format_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { make_instance( @@ -1661,6 +1663,7 @@ pub(super) extern "C" fn date_time_format_constructor_thunk( pub(super) extern "C" fn collator_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { make_instance(closure, KIND_COLLATOR, rest_arg(rest, 0), rest_arg(rest, 1)) @@ -1668,6 +1671,7 @@ pub(super) extern "C" fn collator_constructor_thunk( pub(super) extern "C" fn segmenter_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { require_new_target("Segmenter"); @@ -1681,6 +1685,7 @@ pub(super) extern "C" fn segmenter_constructor_thunk( pub(super) extern "C" fn list_format_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { require_new_target("ListFormat"); @@ -1694,6 +1699,7 @@ pub(super) extern "C" fn list_format_constructor_thunk( pub(super) extern "C" fn relative_time_format_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { require_new_target("RelativeTimeFormat"); @@ -1707,6 +1713,7 @@ pub(super) extern "C" fn relative_time_format_constructor_thunk( pub(super) extern "C" fn plural_rules_constructor_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { require_new_target("PluralRules"); @@ -1751,7 +1758,11 @@ fn supported_locales_array(locales: f64, options: f64) -> f64 { js_nanbox_pointer(arr as i64) } -extern "C" fn supported_locales_of_thunk(_closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn supported_locales_of_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { supported_locales_array(rest_arg(rest, 0), rest_arg(rest, 1)) } diff --git a/crates/perry-runtime/src/intl/date_collator.rs b/crates/perry-runtime/src/intl/date_collator.rs index 396c1fc7ad..fe09d0b3d1 100644 --- a/crates/perry-runtime/src/intl/date_collator.rs +++ b/crates/perry-runtime/src/intl/date_collator.rs @@ -61,6 +61,7 @@ fn date_arg_to_clipped_ms(value: f64) -> f64 { pub(crate) extern "C" fn date_time_format_bound_format_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "format", KIND_DATE_TIME); @@ -78,6 +79,7 @@ pub(crate) extern "C" fn date_time_format_bound_format_thunk( /// construction with name `""` and length 1). pub(crate) extern "C" fn date_time_format_format_getter_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = this_intl_object("format", KIND_DATE_TIME); get_field(obj, KEY_DTF_BOUND_FORMAT) @@ -85,6 +87,7 @@ pub(crate) extern "C" fn date_time_format_format_getter_thunk( pub(crate) extern "C" fn date_time_format_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("formatToParts", KIND_DATE_TIME); @@ -96,6 +99,7 @@ pub(crate) extern "C" fn date_time_format_to_parts_thunk( pub(crate) extern "C" fn date_time_format_bound_to_parts_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "formatToParts", KIND_DATE_TIME); @@ -1381,6 +1385,7 @@ pub(crate) use range::*; pub(crate) extern "C" fn date_time_format_resolved_options_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_DATE_TIME); date_time_format_resolved_options_object(obj) @@ -1388,6 +1393,7 @@ pub(crate) extern "C" fn date_time_format_resolved_options_thunk( pub(crate) extern "C" fn date_time_format_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_DATE_TIME); date_time_format_resolved_options_object(obj) @@ -1687,6 +1693,7 @@ pub(super) fn validate_collator_options(options: f64) { pub(crate) extern "C" fn collator_bound_compare_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, left: f64, right: f64, ) -> f64 { @@ -1697,7 +1704,10 @@ pub(crate) extern "C" fn collator_bound_compare_thunk( /// `get Intl.Collator.prototype.compare` — validate the receiver and return its /// stable [[BoundCompare]] function. The constructor gives that function the /// anonymous built-in shape required by ECMA-402 (`name: ""`, `length: 2`). -pub(crate) extern "C" fn collator_compare_getter_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn collator_compare_getter_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("compare", KIND_COLLATOR); get_field(obj, KEY_COL_BOUND_COMPARE) } @@ -1765,13 +1775,17 @@ pub(crate) fn collator_compare_object(obj: *const ObjectHeader, left: f64, right } } -pub(crate) extern "C" fn collator_resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn collator_resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_COLLATOR); collator_resolved_options_object(obj) } pub(crate) extern "C" fn collator_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_COLLATOR); collator_resolved_options_object(obj) diff --git a/crates/perry-runtime/src/intl/date_collator/range.rs b/crates/perry-runtime/src/intl/date_collator/range.rs index 3f6b6f42a5..755aa50476 100644 --- a/crates/perry-runtime/src/intl/date_collator/range.rs +++ b/crates/perry-runtime/src/intl/date_collator/range.rs @@ -182,6 +182,7 @@ pub(crate) fn date_time_format_range_parts_value( pub(crate) extern "C" fn date_time_format_range_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -194,6 +195,7 @@ pub(crate) extern "C" fn date_time_format_range_thunk( pub(crate) extern "C" fn date_time_format_bound_range_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -206,6 +208,7 @@ pub(crate) extern "C" fn date_time_format_bound_range_thunk( pub(crate) extern "C" fn date_time_format_range_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -218,6 +221,7 @@ pub(crate) extern "C" fn date_time_format_range_to_parts_thunk( pub(crate) extern "C" fn date_time_format_bound_range_to_parts_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/intl/display_names.rs b/crates/perry-runtime/src/intl/display_names.rs index e3aa6f1b01..06175e0551 100644 --- a/crates/perry-runtime/src/intl/display_names.rs +++ b/crates/perry-runtime/src/intl/display_names.rs @@ -188,12 +188,18 @@ fn resolved_options_object(obj: *const ObjectHeader) -> f64 { js_nanbox_pointer(out as i64) } -extern "C" fn bound_resolved_options_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn bound_resolved_options_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", super::KIND_DISPLAY_NAMES); resolved_options_object(obj) } -pub(super) extern "C" fn resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", super::KIND_DISPLAY_NAMES); resolved_options_object(obj) } @@ -339,17 +345,29 @@ fn of_value(obj: *const ObjectHeader, code_value: f64) -> f64 { } } -extern "C" fn bound_of_thunk(closure: *const ClosureHeader, code: f64) -> f64 { +extern "C" fn bound_of_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + code: f64, +) -> f64 { let obj = captured_intl_object(closure, "of", super::KIND_DISPLAY_NAMES); of_value(obj, code) } -pub(super) extern "C" fn of_thunk(_closure: *const ClosureHeader, code: f64) -> f64 { +pub(super) extern "C" fn of_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + code: f64, +) -> f64 { let obj = this_intl_object("of", super::KIND_DISPLAY_NAMES); of_value(obj, code) } -pub(super) extern "C" fn constructor_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(super) extern "C" fn constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { super::make_instance( closure, super::KIND_DISPLAY_NAMES, diff --git a/crates/perry-runtime/src/intl/duration_format.rs b/crates/perry-runtime/src/intl/duration_format.rs index c55e3eefa8..ca12889569 100644 --- a/crates/perry-runtime/src/intl/duration_format.rs +++ b/crates/perry-runtime/src/intl/duration_format.rs @@ -367,7 +367,10 @@ fn resolved_options_object(obj: *const ObjectHeader) -> f64 { js_nanbox_pointer(out as i64) } -pub(super) extern "C" fn resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", super::KIND_DURATION_FORMAT); resolved_options_object(obj) } @@ -881,18 +884,30 @@ pub(super) fn format_temporal_duration(duration: f64, locales: f64, options: f64 format_value(obj, duration) } -pub(super) extern "C" fn format_thunk(_closure: *const ClosureHeader, duration: f64) -> f64 { +pub(super) extern "C" fn format_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + duration: f64, +) -> f64 { let obj = this_intl_object("format", super::KIND_DURATION_FORMAT); format_value(obj, duration) } -pub(super) extern "C" fn to_parts_thunk(_closure: *const ClosureHeader, duration: f64) -> f64 { +pub(super) extern "C" fn to_parts_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + duration: f64, +) -> f64 { let obj = this_intl_object("formatToParts", super::KIND_DURATION_FORMAT); let vals = to_duration_record(duration); df_parts_to_js_array(&partition(obj, &vals)) } -pub(super) extern "C" fn constructor_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(super) extern "C" fn constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { // `Intl.DurationFormat` is `[[Construct]]`-only: a bare call is a TypeError. if crate::object::js_new_target_get().to_bits() == crate::value::TAG_UNDEFINED { throw_type_error("Constructor Intl.DurationFormat requires 'new'"); diff --git a/crates/perry-runtime/src/intl/list_relative_plural.rs b/crates/perry-runtime/src/intl/list_relative_plural.rs index bb5dc6ed95..764f454c63 100644 --- a/crates/perry-runtime/src/intl/list_relative_plural.rs +++ b/crates/perry-runtime/src/intl/list_relative_plural.rs @@ -204,6 +204,7 @@ pub(crate) fn list_format_instance_parts( pub(crate) extern "C" fn list_format_format_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("format", KIND_LIST_FORMAT); @@ -217,6 +218,7 @@ pub(crate) extern "C" fn list_format_format_thunk( pub(crate) extern "C" fn list_format_bound_format_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "format", KIND_LIST_FORMAT); @@ -230,6 +232,7 @@ pub(crate) extern "C" fn list_format_bound_format_thunk( pub(crate) extern "C" fn list_format_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("formatToParts", KIND_LIST_FORMAT); @@ -238,6 +241,7 @@ pub(crate) extern "C" fn list_format_to_parts_thunk( pub(crate) extern "C" fn list_format_bound_to_parts_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "formatToParts", KIND_LIST_FORMAT); @@ -264,13 +268,17 @@ pub(crate) fn list_format_resolved_options_object(obj: *const ObjectHeader) -> f js_nanbox_pointer(out as i64) } -pub(crate) extern "C" fn list_format_resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn list_format_resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_LIST_FORMAT); list_format_resolved_options_object(obj) } pub(crate) extern "C" fn list_format_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_LIST_FORMAT); list_format_resolved_options_object(obj) @@ -585,6 +593,7 @@ fn rtf_parts_to_js_array(parts: &[(&'static str, String)], unit: &str) -> f64 { pub(crate) extern "C" fn rtf_format_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { @@ -599,6 +608,7 @@ pub(crate) extern "C" fn rtf_format_thunk( pub(crate) extern "C" fn rtf_bound_format_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { @@ -613,6 +623,7 @@ pub(crate) extern "C" fn rtf_bound_format_thunk( pub(crate) extern "C" fn rtf_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { @@ -623,6 +634,7 @@ pub(crate) extern "C" fn rtf_to_parts_thunk( pub(crate) extern "C" fn rtf_bound_to_parts_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { @@ -658,12 +670,18 @@ pub(crate) fn rtf_resolved_options_object(obj: *const ObjectHeader) -> f64 { js_nanbox_pointer(out as i64) } -pub(crate) extern "C" fn rtf_resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn rtf_resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_RELATIVE_TIME); rtf_resolved_options_object(obj) } -pub(crate) extern "C" fn rtf_bound_resolved_options_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn rtf_bound_resolved_options_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_RELATIVE_TIME); rtf_resolved_options_object(obj) } @@ -987,6 +1005,7 @@ mod plural_category_tests { pub(crate) extern "C" fn plural_rules_select_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("select", KIND_PLURAL_RULES); @@ -995,6 +1014,7 @@ pub(crate) extern "C" fn plural_rules_select_thunk( pub(crate) extern "C" fn plural_rules_bound_select_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "select", KIND_PLURAL_RULES); @@ -1003,6 +1023,7 @@ pub(crate) extern "C" fn plural_rules_bound_select_thunk( pub(crate) extern "C" fn plural_rules_select_range_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -1103,6 +1124,7 @@ pub(crate) fn plural_rules_resolved_options_object(obj: *const ObjectHeader) -> pub(crate) extern "C" fn plural_rules_resolved_options_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_PLURAL_RULES); plural_rules_resolved_options_object(obj) @@ -1110,6 +1132,7 @@ pub(crate) extern "C" fn plural_rules_resolved_options_thunk( pub(crate) extern "C" fn plural_rules_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_PLURAL_RULES); plural_rules_resolved_options_object(obj) diff --git a/crates/perry-runtime/src/intl/locale.rs b/crates/perry-runtime/src/intl/locale.rs index 837d898a7a..9e22b04931 100644 --- a/crates/perry-runtime/src/intl/locale.rs +++ b/crates/perry-runtime/src/intl/locale.rs @@ -606,44 +606,74 @@ fn make_locale_instance(proto_bits: u64, p: &ParsedLocale) -> f64 { js_nanbox_pointer(obj as i64) } -extern "C" fn locale_bound_to_string(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_to_string( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = captured_intl_object(closure, "toString", KIND_LOCALE); string_value(&get_string_field(obj, KEY_FULL).unwrap_or_default()) } -extern "C" fn locale_bound_maximize(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_maximize( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = captured_intl_object(closure, "maximize", KIND_LOCALE); transform_instance(obj, likely_subtags::maximize) } -extern "C" fn locale_bound_minimize(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_minimize( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = captured_intl_object(closure, "minimize", KIND_LOCALE); transform_instance(obj, likely_subtags::minimize) } -extern "C" fn locale_bound_get_calendars(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_calendars( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { calendars_of(captured_intl_object(closure, "getCalendars", KIND_LOCALE)) } -extern "C" fn locale_bound_get_collations(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_collations( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { collations_of(captured_intl_object(closure, "getCollations", KIND_LOCALE)) } -extern "C" fn locale_bound_get_hour_cycles(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_hour_cycles( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { hour_cycles_of(captured_intl_object(closure, "getHourCycles", KIND_LOCALE)) } -extern "C" fn locale_bound_get_numbering_systems(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_numbering_systems( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { numbering_systems_of(captured_intl_object( closure, "getNumberingSystems", KIND_LOCALE, )) } -extern "C" fn locale_bound_get_time_zones(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_time_zones( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { time_zones_of(captured_intl_object(closure, "getTimeZones", KIND_LOCALE)) } -extern "C" fn locale_bound_get_text_info(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_text_info( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { text_info_of(captured_intl_object(closure, "getTextInfo", KIND_LOCALE)) } -extern "C" fn locale_bound_get_week_info(closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_bound_get_week_info( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { week_info_of(captured_intl_object(closure, "getWeekInfo", KIND_LOCALE)) } @@ -656,7 +686,11 @@ fn transform_instance(obj: *const ObjectHeader, transform: fn(&mut ParsedLocale) make_locale_instance(proto, &p) } -pub(super) extern "C" fn locale_constructor_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +pub(super) extern "C" fn locale_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { super::require_new_target("Locale"); let tag_value = super::rest_arg(rest, 0); let options_value = super::rest_arg(rest, 1); @@ -736,38 +770,68 @@ fn field_or_undefined(obj: *const ObjectHeader, key: &str) -> f64 { } } -extern "C" fn locale_to_string_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_to_string_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = locale_this("toString"); string_value(&get_string_field(obj, KEY_FULL).unwrap_or_default()) } -extern "C" fn locale_maximize_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_maximize_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { transform_instance(locale_this("maximize"), likely_subtags::maximize) } -extern "C" fn locale_minimize_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_minimize_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { transform_instance(locale_this("minimize"), likely_subtags::minimize) } -extern "C" fn locale_get_calendars_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_calendars_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { calendars_of(locale_this("getCalendars")) } -extern "C" fn locale_get_collations_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_collations_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { collations_of(locale_this("getCollations")) } -extern "C" fn locale_get_hour_cycles_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_hour_cycles_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { hour_cycles_of(locale_this("getHourCycles")) } -extern "C" fn locale_get_numbering_systems_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_numbering_systems_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { numbering_systems_of(locale_this("getNumberingSystems")) } -extern "C" fn locale_get_time_zones_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_time_zones_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { time_zones_of(locale_this("getTimeZones")) } -extern "C" fn locale_get_text_info_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_text_info_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { text_info_of(locale_this("getTextInfo")) } -extern "C" fn locale_get_week_info_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn locale_get_week_info_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { week_info_of(locale_this("getWeekInfo")) } @@ -896,37 +960,43 @@ fn parsed_from_instance(obj: *const ObjectHeader) -> ParsedLocale { parse_language_tag(&full).unwrap_or_default() } -extern "C" fn getter_base_name(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_base_name(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("baseName"), KEY_BASENAME) } -extern "C" fn getter_language(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_language(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("language"), KEY_LANGUAGE) } -extern "C" fn getter_script(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_script(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("script"), KEY_SCRIPT) } -extern "C" fn getter_region(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_region(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("region"), KEY_REGION) } -extern "C" fn getter_calendar(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_calendar(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("calendar"), KEY_CALENDAR) } -extern "C" fn getter_case_first(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_case_first(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("caseFirst"), KEY_CASEFIRST) } -extern "C" fn getter_collation(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_collation(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("collation"), KEY_COLLATION) } -extern "C" fn getter_hour_cycle(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_hour_cycle(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { field_or_undefined(locale_this("hourCycle"), KEY_HOURCYCLE) } -extern "C" fn getter_numbering_system(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_numbering_system( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_or_undefined(locale_this("numberingSystem"), KEY_NUMBERINGSYSTEM) } -extern "C" fn getter_first_day_of_week(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_first_day_of_week( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { field_or_undefined(locale_this("firstDayOfWeek"), KEY_FIRSTDAYOFWEEK) } -extern "C" fn getter_numeric(_c: *const ClosureHeader) -> f64 { +extern "C" fn getter_numeric(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let obj = locale_this("numeric"); let value = get_field(obj, KEY_NUMERIC); if value.to_bits() == crate::value::TAG_TRUE { diff --git a/crates/perry-runtime/src/intl/locales.rs b/crates/perry-runtime/src/intl/locales.rs index 22dbe4cb58..7922bdea5e 100644 --- a/crates/perry-runtime/src/intl/locales.rs +++ b/crates/perry-runtime/src/intl/locales.rs @@ -27,6 +27,7 @@ pub(super) fn get_canonical_locales(locales: f64) -> f64 { pub(super) extern "C" fn get_canonical_locales_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, locales: f64, ) -> f64 { get_canonical_locales(locales) @@ -151,6 +152,7 @@ fn supported_values_list(key: &str) -> Option<&'static [&'static str]> { pub(super) extern "C" fn supported_values_of_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { // Coerce `key` to String first (the spec's GetOption-like step), then a diff --git a/crates/perry-runtime/src/intl/number_format.rs b/crates/perry-runtime/src/intl/number_format.rs index b20e3bfa5e..990c21dbae 100644 --- a/crates/perry-runtime/src/intl/number_format.rs +++ b/crates/perry-runtime/src/intl/number_format.rs @@ -1385,13 +1385,17 @@ fn unwrap_legacy_constructed(value: f64) -> Option { /// KEY_NF_BOUND_FORMAT slot (set at construction, name `""`, length 1) rather /// than the public own `format` property, so user mutation/deletion of that /// property can't change what the accessor returns. -pub(crate) extern "C" fn number_format_format_getter_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn number_format_format_getter_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("format", KIND_NUMBER); get_field(obj, KEY_NF_BOUND_FORMAT) } pub(crate) extern "C" fn number_format_bound_format_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "format", KIND_NUMBER); @@ -1416,6 +1420,7 @@ pub(crate) fn number_format_format_object(obj: *const ObjectHeader, value: f64) pub(crate) extern "C" fn number_format_resolved_options_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_NUMBER); number_format_resolved_options_object(obj) @@ -1423,6 +1428,7 @@ pub(crate) extern "C" fn number_format_resolved_options_thunk( pub(crate) extern "C" fn number_format_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_NUMBER); number_format_resolved_options_object(obj) @@ -1430,6 +1436,7 @@ pub(crate) extern "C" fn number_format_bound_resolved_options_thunk( pub(crate) extern "C" fn number_format_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("formatToParts", KIND_NUMBER); @@ -1439,6 +1446,7 @@ pub(crate) extern "C" fn number_format_to_parts_thunk( pub(crate) extern "C" fn number_format_bound_to_parts_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "formatToParts", KIND_NUMBER); @@ -1534,6 +1542,7 @@ pub(crate) fn number_format_range_parts_value( pub(crate) extern "C" fn number_format_range_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -1543,6 +1552,7 @@ pub(crate) extern "C" fn number_format_range_thunk( pub(crate) extern "C" fn number_format_range_to_parts_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/intl/segmenter.rs b/crates/perry-runtime/src/intl/segmenter.rs index 0986d6f0f1..d7ca05779d 100644 --- a/crates/perry-runtime/src/intl/segmenter.rs +++ b/crates/perry-runtime/src/intl/segmenter.rs @@ -420,7 +420,10 @@ fn install_segments_iterator(segments: &crate::gc::RuntimeHandle<'_>) { }); } -extern "C" fn segments_iterator_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn segments_iterator_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let segments = scope.root_raw_const_ptr(segments_from_this()); segments.with_const_ptr(|segments: *const crate::ArrayHeader| { @@ -442,6 +445,7 @@ fn segments_from_this() -> *const crate::ArrayHeader { pub(crate) extern "C" fn segmenter_containing_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, index: f64, ) -> f64 { let segments = segments_from_this(); @@ -492,6 +496,7 @@ pub(crate) extern "C" fn segmenter_containing_thunk( pub(crate) extern "C" fn segmenter_segment_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = this_intl_object("segment", KIND_SEGMENTER); @@ -500,6 +505,7 @@ pub(crate) extern "C" fn segmenter_segment_thunk( pub(crate) extern "C" fn segmenter_bound_segment_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let obj = captured_intl_object(closure, "segment", KIND_SEGMENTER); @@ -512,13 +518,17 @@ pub(crate) fn segmenter_segment_object(obj: *const ObjectHeader, value: f64) -> build_segments(&granularity, value) } -pub(crate) extern "C" fn segmenter_resolved_options_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn segmenter_resolved_options_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = this_intl_object("resolvedOptions", KIND_SEGMENTER); segmenter_resolved_options_object(obj) } pub(crate) extern "C" fn segmenter_bound_resolved_options_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let obj = captured_intl_object(closure, "resolvedOptions", KIND_SEGMENTER); segmenter_resolved_options_object(obj) diff --git a/crates/perry-runtime/src/intl/segments_view.rs b/crates/perry-runtime/src/intl/segments_view.rs index ab579eb174..34d89cfed6 100644 --- a/crates/perry-runtime/src/intl/segments_view.rs +++ b/crates/perry-runtime/src/intl/segments_view.rs @@ -956,7 +956,10 @@ mod view_mode_tests { ); } - extern "C" fn patched_test_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn patched_test_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(JSValue::bool(true).bits()) } diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index 5578a87d49..e998c0277e 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -107,11 +107,19 @@ unsafe fn helper_over(values: &[f64]) -> f64 { /// The closure the combinator tests map with. Perry closures are invoked with /// the argument in the first `f64` slot; `js_closure_alloc` + a registered /// arity is the runtime-side equivalent of a compiled arrow function. -extern "C" fn double_it(_c: *const crate::closure::ClosureHeader, x: f64) -> f64 { +extern "C" fn double_it( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + x: f64, +) -> f64 { f64::from_bits(JSValue::number(f64::from_bits(x.to_bits()) * 2.0).bits()) } -extern "C" fn is_even(_c: *const crate::closure::ClosureHeader, x: f64) -> f64 { +extern "C" fn is_even( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + x: f64, +) -> f64 { let v = f64::from_bits(x.to_bits()); f64::from_bits(if v as i64 % 2 == 0 { crate::value::TAG_TRUE @@ -121,13 +129,22 @@ extern "C" fn is_even(_c: *const crate::closure::ClosureHeader, x: f64) -> f64 { } /// `(x) => [x, x * 10]`, for `flatMap`. -extern "C" fn pair_with_ten_times(_c: *const crate::closure::ClosureHeader, x: f64) -> f64 { +extern "C" fn pair_with_ten_times( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + x: f64, +) -> f64 { let v = f64::from_bits(x.to_bits()); unsafe { number_array(&[v, v * 10.0]) } } /// `(acc, v) => acc + v`, for `reduce`. -extern "C" fn add(_c: *const crate::closure::ClosureHeader, a: f64, b: f64) -> f64 { +extern "C" fn add( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, +) -> f64 { let sum = f64::from_bits(a.to_bits()) + f64::from_bits(b.to_bits()); f64::from_bits(JSValue::number(sum).bits()) } @@ -499,7 +516,11 @@ fn an_own_next_method_is_called_with_the_iterator_as_this() { /// Bits of the `this` the last `next()` observed. static OBSERVED_THIS: AtomicU64 = AtomicU64::new(0); - extern "C" fn next_recording_this(_c: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { + extern "C" fn next_recording_this( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, + ) -> f64 { let this = crate::object::js_implicit_this_get(); OBSERVED_THIS.store(this.to_bits(), Ordering::SeqCst); unsafe { crate::iter_result::make_iter_result(JSValue::undefined(), true) } 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 af066bfadb..3df6808b00 100644 --- a/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs +++ b/crates/perry-runtime/src/json/replacer_hidden_fields_tests.rs @@ -1,6 +1,11 @@ use super::*; -extern "C" fn identity(_closure: *const crate::ClosureHeader, _key: f64, value: f64) -> f64 { +extern "C" fn identity( + _closure: *const crate::ClosureHeader, + _this: crate::closure::JsThis, + _key: f64, + value: f64, +) -> f64 { value } diff --git a/crates/perry-runtime/src/json_tape/cached_read.rs b/crates/perry-runtime/src/json_tape/cached_read.rs index 7ca2152bf5..6047154fe0 100644 --- a/crates/perry-runtime/src/json_tape/cached_read.rs +++ b/crates/perry-runtime/src/json_tape/cached_read.rs @@ -379,7 +379,10 @@ mod tests { } } - extern "C" fn descriptor_getter(_: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn descriptor_getter( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 61.0 } diff --git a/crates/perry-runtime/src/jsx.rs b/crates/perry-runtime/src/jsx.rs index 04ce561b0a..2493bf6f94 100644 --- a/crates/perry-runtime/src/jsx.rs +++ b/crates/perry-runtime/src/jsx.rs @@ -398,7 +398,11 @@ mod tests { } /// A function component: `(props) => {props.children}`. - extern "C" fn span_component(_closure: *const ClosureHeader, props: f64) -> f64 { + extern "C" fn span_component( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + props: f64, + ) -> f64 { js_jsx(str_val("span"), props) } diff --git a/crates/perry-runtime/src/lib.rs b/crates/perry-runtime/src/lib.rs index fadafdf6a5..3f433d3f78 100644 --- a/crates/perry-runtime/src/lib.rs +++ b/crates/perry-runtime/src/lib.rs @@ -359,7 +359,7 @@ pub mod win_console; pub use array::ArrayHeader; pub use bigint::BigIntHeader; pub use buffer::BufferHeader; -pub use closure::ClosureHeader; +pub use closure::{ClosureHeader, JsThis}; pub use map::MapHeader; pub use object::ObjectHeader; pub use object::{object_live_slot_count, perry_object_header_abi_revision}; diff --git a/crates/perry-runtime/src/map_tombstone_tests.rs b/crates/perry-runtime/src/map_tombstone_tests.rs index eb06abc483..028ca68add 100644 --- a/crates/perry-runtime/src/map_tombstone_tests.rs +++ b/crates/perry-runtime/src/map_tombstone_tests.rs @@ -17,6 +17,7 @@ crate::perry_thread_local! { extern "C" fn delete_current_map_entry( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, key: f64, collection: f64, @@ -29,6 +30,7 @@ extern "C" fn delete_current_map_entry( extern "C" fn delete_earlier_map_entry( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, key: f64, collection: f64, @@ -488,6 +490,7 @@ fn clear_truncates_the_squeeze_history() { extern "C" fn delete_earlier_then_live_read( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, key: f64, collection: f64, diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index d5c3122a51..e907af5b23 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -103,19 +103,28 @@ fn closure_value(func_ptr: *const u8, name: &str, arity: u32) -> f64 { crate::value::js_nanbox_pointer(closure as i64) } -extern "C" fn noop0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn noop0(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { js_undefined() } -extern "C" fn noop1(_closure: *const ClosureHeader, _arg0: f64) -> f64 { +extern "C" fn noop1( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg0: f64, +) -> f64 { js_undefined() } -extern "C" fn noop2(_closure: *const ClosureHeader, _arg0: f64, _arg1: f64) -> f64 { +extern "C" fn noop2( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg0: f64, + _arg1: f64, +) -> f64 { js_undefined() } -extern "C" fn has_ref(_closure: *const ClosureHeader) -> f64 { +extern "C" fn has_ref(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { js_bool(false) } @@ -327,7 +336,11 @@ fn invoke_message_handler(handler: f64, event: f64, port_box: f64) { /// Macrotask body: deliver exactly one queued message to `port_box`'s port. /// Scheduled via `setImmediate`, so the event loop pumps it; chains naturally /// because a handler that posts again schedules a fresh macrotask. -extern "C" fn deliver_one_message(_closure: *const ClosureHeader, port_box: f64) -> f64 { +extern "C" fn deliver_one_message( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + port_box: f64, +) -> f64 { let port_ptr = crate::value::js_nanbox_get_pointer(port_box) as usize; if port_ptr == 0 { return js_undefined(); @@ -378,7 +391,12 @@ fn schedule_delivery(port_ptr: usize) { } /// `port.postMessage(data[, transferList])`. -extern "C" fn port_post_message(_closure: *const ClosureHeader, data: f64, _transfer: f64) -> f64 { +extern "C" fn port_post_message( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, + _transfer: f64, +) -> f64 { let self_ptr = this_port_ptr(); if self_ptr == 0 { return js_undefined(); @@ -423,12 +441,12 @@ fn start_port(self_ptr: usize) { } } -extern "C" fn port_start(_closure: *const ClosureHeader) -> f64 { +extern "C" fn port_start(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { start_port(this_port_ptr()); js_undefined() } -extern "C" fn port_close(_closure: *const ClosureHeader) -> f64 { +extern "C" fn port_close(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let self_ptr = this_port_ptr(); if self_ptr != 0 { with_port_states(|map| { @@ -457,7 +475,10 @@ extern "C" fn port_close(_closure: *const ClosureHeader) -> f64 { } /// `port.onmessage` getter — return the stored handler (null if unset). -extern "C" fn port_onmessage_get(_closure: *const ClosureHeader) -> f64 { +extern "C" fn port_onmessage_get( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let self_ptr = this_port_ptr(); if self_ptr == 0 { return js_null(); @@ -467,7 +488,11 @@ extern "C" fn port_onmessage_get(_closure: *const ClosureHeader) -> f64 { /// `port.onmessage` setter — store the handler and (per HTML) implicitly /// start the port, flushing any queued messages. -extern "C" fn port_onmessage_set(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn port_onmessage_set( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { let self_ptr = this_port_ptr(); if self_ptr == 0 { return js_undefined(); @@ -481,6 +506,7 @@ extern "C" fn port_onmessage_set(_closure: *const ClosureHeader, value: f64) -> extern "C" fn port_add_event_listener( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, type_value: f64, listener: f64, ) -> f64 { @@ -512,6 +538,7 @@ extern "C" fn port_add_event_listener( extern "C" fn port_remove_event_listener( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, type_value: f64, listener: f64, ) -> f64 { @@ -644,6 +671,7 @@ fn same_thread_message_channel_new() -> f64 { pub(crate) extern "C" fn js_message_channel_constructor_call_error( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { throw_constructor_call_error() } @@ -682,6 +710,7 @@ pub extern "C" fn js_broadcast_channel_new(name: f64) -> f64 { pub(crate) extern "C" fn js_broadcast_channel_constructor_call_error( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { throw_constructor_call_error() @@ -689,6 +718,7 @@ pub(crate) extern "C" fn js_broadcast_channel_constructor_call_error( pub(crate) extern "C" fn js_message_port_constructor_call_error( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { throw_constructor_call_error() } diff --git a/crates/perry-runtime/src/module_require.rs b/crates/perry-runtime/src/module_require.rs index 10c918edf4..d38bce4f54 100644 --- a/crates/perry-runtime/src/module_require.rs +++ b/crates/perry-runtime/src/module_require.rs @@ -885,7 +885,11 @@ fn run_custom_extension(path: &std::path::Path, record: f64, filename: &str) { } } -extern "C" fn require_thunk(closure: *const ClosureHeader, id: f64) -> f64 { +extern "C" fn require_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + id: f64, +) -> f64 { let specifier = value_to_string(id, "id"); if specifier.is_empty() { let message = "The argument 'id' must be a non-empty string"; @@ -907,7 +911,12 @@ extern "C" fn require_thunk(closure: *const ClosureHeader, id: f64) -> f64 { } } -extern "C" fn resolve_thunk(closure: *const ClosureHeader, request: f64, _options: f64) -> f64 { +extern "C" fn resolve_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + request: f64, + _options: f64, +) -> f64 { let specifier = value_to_string(request, "request"); if let Some(resolved) = resolve_builtin(&specifier) { return string_value(resolved); @@ -920,7 +929,11 @@ extern "C" fn resolve_thunk(closure: *const ClosureHeader, request: f64, _option } } -extern "C" fn resolve_paths_thunk(closure: *const ClosureHeader, request: f64) -> f64 { +extern "C" fn resolve_paths_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + request: f64, +) -> f64 { let specifier = value_to_string(request, "request"); if supported_require_builtin(&specifier).is_some() { return null(); @@ -1512,7 +1525,7 @@ static KEEP_JS_MODULE_AMBIENT_REQUIRE: extern "C" fn() -> f64 = js_module_ambien /// Returns the required value directly (no Promise). #[no_mangle] pub extern "C" fn js_module_ambient_require_apply(spec: f64) -> f64 { - require_thunk(std::ptr::null(), spec) + require_thunk(std::ptr::null(), crate::closure::JsThis::current(), spec) } /// Keepalive anchor for the auto-optimize whole-program build (generated-code-only 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 1f93ba4e2e..7ac7562954 100644 --- a/crates/perry-runtime/src/module_require/import_meta_resolve.rs +++ b/crates/perry-runtime/src/module_require/import_meta_resolve.rs @@ -191,7 +191,12 @@ pub extern "C" fn js_import_meta_resolve(specifier: f64, parent: f64, fallback: )) } -extern "C" fn resolve_closure(closure: *mut ClosureHeader, specifier: f64, parent: f64) -> f64 { +extern "C" fn resolve_closure( + closure: *mut ClosureHeader, + _this: crate::closure::JsThis, + specifier: f64, + parent: f64, +) -> f64 { let fallback = js_closure_get_capture_f64(closure, 0); js_import_meta_resolve(specifier, parent, fallback) } diff --git a/crates/perry-runtime/src/node_api_host/functions.rs b/crates/perry-runtime/src/node_api_host/functions.rs index 9c4471529a..5f5805b5a1 100644 --- a/crates/perry-runtime/src/node_api_host/functions.rs +++ b/crates/perry-runtime/src/node_api_host/functions.rs @@ -37,6 +37,7 @@ fn current_callback_record(index: usize) -> Option { extern "C" fn napi_callback_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arguments: f64, ) -> f64 { let env = current_env(); 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 d870cd12f7..c1b2420efa 100644 --- a/crates/perry-runtime/src/node_api_host/v10_tests.rs +++ b/crates/perry-runtime/src/node_api_host/v10_tests.rs @@ -71,7 +71,10 @@ fn take_exception(env: NapiEnv) -> NapiValue { exception } -extern "C" fn plain_closure_body(_closure: *const ClosureHeader) -> f64 { +extern "C" fn plain_closure_body( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -557,7 +560,11 @@ thread_local! { static UNCAUGHT_CALLBACK_ERRORS: RefCell = const { RefCell::new(0) }; } -extern "C" fn count_uncaught_callback_error(_closure: *const ClosureHeader, _error: f64) -> f64 { +extern "C" fn count_uncaught_callback_error( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _error: f64, +) -> f64 { UNCAUGHT_CALLBACK_ERRORS.with(|count| *count.borrow_mut() += 1); f64::from_bits(crate::value::TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/node_inspector.rs b/crates/perry-runtime/src/node_inspector.rs index f7c6fb4ca8..a8836a38cf 100644 --- a/crates/perry-runtime/src/node_inspector.rs +++ b/crates/perry-runtime/src/node_inspector.rs @@ -1108,7 +1108,10 @@ fn promise_value(result: Result) -> f64 { boxed_pointer(promise as *const u8) } -extern "C" fn endpoint_dispose(_closure: *const ClosureHeader) -> f64 { +extern "C" fn endpoint_dispose( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_node_inspector_close() } @@ -1180,6 +1183,7 @@ fn inspector_console_emit(kind: &str, first: f64, second: f64, third: f64) -> f6 extern "C" fn inspector_console_log( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -1189,6 +1193,7 @@ extern "C" fn inspector_console_log( extern "C" fn inspector_console_info( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -1198,6 +1203,7 @@ extern "C" fn inspector_console_info( extern "C" fn inspector_console_debug( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -1207,6 +1213,7 @@ extern "C" fn inspector_console_debug( extern "C" fn inspector_console_warn( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -1216,6 +1223,7 @@ extern "C" fn inspector_console_warn( extern "C" fn inspector_console_error( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -1347,23 +1355,33 @@ pub extern "C" fn js_node_inspector_wait_for_debugger() -> f64 { undefined() } -extern "C" fn session_connect_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn session_connect_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_connect(raw_ptr_from_value(this) as i64) } -extern "C" fn session_connect_main_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn session_connect_main_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_connect_to_main_thread(raw_ptr_from_value(this) as i64) } -extern "C" fn session_disconnect_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn session_disconnect_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_disconnect(raw_ptr_from_value(this) as i64) } extern "C" fn session_post_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, method: f64, params: f64, callback: f64, @@ -1374,6 +1392,7 @@ extern "C" fn session_post_thunk( extern "C" fn promises_session_post_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, method: f64, params: f64, callback: f64, @@ -1387,28 +1406,48 @@ extern "C" fn promises_session_post_thunk( ) } -extern "C" fn session_on_thunk(_closure: *const ClosureHeader, event: f64, listener: f64) -> f64 { +extern "C" fn session_on_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + listener: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_on(raw_ptr_from_value(this) as i64, event, listener) } -extern "C" fn session_once_thunk(_closure: *const ClosureHeader, event: f64, listener: f64) -> f64 { +extern "C" fn session_once_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + listener: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_once(raw_ptr_from_value(this) as i64, event, listener) } -extern "C" fn session_off_thunk(_closure: *const ClosureHeader, event: f64, listener: f64) -> f64 { +extern "C" fn session_off_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + listener: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_off(raw_ptr_from_value(this) as i64, event, listener) } -extern "C" fn session_listener_count_thunk(_closure: *const ClosureHeader, event: f64) -> f64 { +extern "C" fn session_listener_count_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); js_node_inspector_session_listener_count(raw_ptr_from_value(this) as i64, event) } extern "C" fn session_remove_all_listeners_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); diff --git a/crates/perry-runtime/src/node_repl.rs b/crates/perry-runtime/src/node_repl.rs index 9b8656ae9f..d28e9f9785 100644 --- a/crates/perry-runtime/src/node_repl.rs +++ b/crates/perry-runtime/src/node_repl.rs @@ -366,13 +366,23 @@ fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { crate::value::js_nanbox_pointer(closure as i64) } -extern "C" fn repl_on_thunk(_closure: *const ClosureHeader, event: f64, listener: f64) -> f64 { +extern "C" fn repl_on_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + listener: f64, +) -> f64 { let server = crate::object::js_implicit_this_get(); add_listener(server, event, listener, false); server } -extern "C" fn repl_once_thunk(_closure: *const ClosureHeader, event: f64, listener: f64) -> f64 { +extern "C" fn repl_once_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + listener: f64, +) -> f64 { let server = crate::object::js_implicit_this_get(); add_listener(server, event, listener, true); server @@ -380,6 +390,7 @@ extern "C" fn repl_once_thunk(_closure: *const ClosureHeader, event: f64, listen extern "C" fn repl_emit_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, arg0: f64, arg1: f64, @@ -398,6 +409,7 @@ extern "C" fn repl_emit_thunk( extern "C" fn repl_display_prompt_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _preserve_cursor: f64, ) -> f64 { let server = crate::object::js_implicit_this_get(); @@ -405,12 +417,16 @@ extern "C" fn repl_display_prompt_thunk( undefined() } -extern "C" fn repl_clear_buffered_command_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn repl_clear_buffered_command_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { undefined() } extern "C" fn repl_setup_history_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _path: f64, callback: f64, ) -> f64 { @@ -425,6 +441,7 @@ extern "C" fn repl_setup_history_thunk( extern "C" fn repl_define_command_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, keyword: f64, command: f64, ) -> f64 { @@ -439,7 +456,11 @@ extern "C" fn repl_define_command_thunk( undefined() } -extern "C" fn repl_write_thunk(_closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn repl_write_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { let server = crate::object::js_implicit_this_get(); let input = string_to_rust(chunk).unwrap_or_default(); for line in input.split_inclusive('\n') { diff --git a/crates/perry-runtime/src/node_stream.rs b/crates/perry-runtime/src/node_stream.rs index 03baefbc16..8060d16ef3 100644 --- a/crates/perry-runtime/src/node_stream.rs +++ b/crates/perry-runtime/src/node_stream.rs @@ -183,16 +183,31 @@ fn this_value(closure: *const ClosureHeader) -> f64 { crate::object::js_implicit_this_get() } -extern "C" fn ns_chain0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_chain0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { this_value(closure) } -extern "C" fn ns_chain1(closure: *const ClosureHeader, _a: f64) -> f64 { +extern "C" fn ns_chain1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, +) -> f64 { this_value(closure) } -extern "C" fn ns_chain2(closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { +extern "C" fn ns_chain2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + _b: f64, +) -> f64 { this_value(closure) } -extern "C" fn ns_chain3(closure: *const ClosureHeader, _a: f64, _b: f64, _c: f64) -> f64 { +extern "C" fn ns_chain3( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + _b: f64, + _c: f64, +) -> f64 { this_value(closure) } @@ -230,7 +245,11 @@ fn call_old_stream_on(old_stream: f64, event: &[u8], listener: *const ClosureHea } } -extern "C" fn ns_wrap_data(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn ns_wrap_data( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -239,7 +258,7 @@ extern "C" fn ns_wrap_data(closure: *const ClosureHeader, chunk: f64) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_wrap_end(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_wrap_end(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -248,7 +267,11 @@ extern "C" fn ns_wrap_end(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_wrap_error(closure: *const ClosureHeader, err: f64) -> f64 { +extern "C" fn ns_wrap_error( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -257,7 +280,7 @@ extern "C" fn ns_wrap_error(closure: *const ClosureHeader, err: f64) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_wrap_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_wrap_close(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -266,7 +289,11 @@ extern "C" fn ns_wrap_close(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_wrap1(closure: *const ClosureHeader, old_stream: f64) -> f64 { +extern "C" fn ns_wrap1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + old_stream: f64, +) -> f64 { let stream = this_value(closure); 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); @@ -289,7 +316,10 @@ extern "C" fn ns_wrap1(closure: *const ClosureHeader, old_stream: f64) -> f64 { stream } -extern "C" fn ns_readable_from_drain(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_from_drain( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -303,7 +333,10 @@ extern "C" fn ns_readable_from_drain(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_readable_event_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_event_microtask( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -320,7 +353,10 @@ extern "C" fn ns_readable_event_microtask(closure: *const ClosureHeader) -> f64 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_readable_end_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_end_microtask( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -336,7 +372,10 @@ extern "C" fn ns_readable_end_microtask(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_readable_resume_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_resume_microtask( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -355,7 +394,10 @@ extern "C" fn ns_readable_resume_microtask(closure: *const ClosureHeader) -> f64 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_finished_error_false_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_finished_error_false_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -373,7 +415,10 @@ extern "C" fn ns_finished_error_false_close(closure: *const ClosureHeader) -> f6 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_finished_default_completion(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_finished_default_completion( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() || js_closure_get_capture_f64(closure, 2).to_bits() == TAG_TRUE { return f64::from_bits(TAG_UNDEFINED); } @@ -398,7 +443,10 @@ extern "C" fn ns_finished_default_completion(closure: *const ClosureHeader) -> f f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_finished_signal_abort(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_finished_signal_abort( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -420,7 +468,10 @@ extern "C" fn ns_finished_signal_abort(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_writable_finish_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_writable_finish_microtask( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -454,7 +505,11 @@ extern "C" fn ns_writable_finish_microtask(closure: *const ClosureHeader) -> f64 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_construct_callback_done(closure: *const ClosureHeader, err: f64) -> f64 { +extern "C" fn ns_construct_callback_done( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -465,7 +520,11 @@ extern "C" fn ns_construct_callback_done(closure: *const ClosureHeader, err: f64 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_writable_final_callback_done(closure: *const ClosureHeader, err: f64) -> f64 { +extern "C" fn ns_writable_final_callback_done( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -494,7 +553,12 @@ extern "C" fn ns_writable_final_callback_done(closure: *const ClosureHeader, err f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_emit2(closure: *const ClosureHeader, event: f64, arg: f64) -> f64 { +extern "C" fn ns_emit2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + arg: f64, +) -> f64 { let stream = this_value(closure); let mut args = crate::array::js_array_alloc(0); if arg.to_bits() != TAG_UNDEFINED { @@ -503,22 +567,27 @@ extern "C" fn ns_emit2(closure: *const ClosureHeader, event: f64, arg: f64) -> f emit_stream_event_from_array(stream, event, args) } -extern "C" fn ns_emit_rest(closure: *const ClosureHeader, event: f64, rest: f64) -> f64 { +extern "C" fn ns_emit_rest( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + rest: f64, +) -> f64 { emit_stream_event_from_array( this_value(closure), event, raw_ptr_from_value(rest) as *const _, ) } -extern "C" fn ns_resume0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_resume0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { resume_readable_stream(this_value(closure)) } -extern "C" fn ns_pause0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_pause0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { pause_readable_stream(this_value(closure)) } -extern "C" fn ns_is_paused0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_is_paused0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { f64::from_bits(if readable_is_paused(this_value(closure)) { TAG_TRUE } else { @@ -526,18 +595,29 @@ extern "C" fn ns_is_paused0(closure: *const ClosureHeader) -> f64 { }) } -extern "C" fn ns_async_dispose(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_async_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream = this_value(closure); destroy_stream(stream, abort_error()); resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn ns_read1(closure: *const ClosureHeader, n: f64) -> f64 { +extern "C" fn ns_read1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + n: f64, +) -> f64 { let stream = this_value(closure); read_stream_with_size_arg(stream, n) } -extern "C" fn ns_set_encoding1(closure: *const ClosureHeader, encoding: f64) -> f64 { +extern "C" fn ns_set_encoding1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + encoding: f64, +) -> f64 { let stream = this_value(closure); set_visible_readable_encoding(stream, normalize_readable_encoding(encoding)); stream @@ -768,7 +848,11 @@ fn push_chunk_backpressure_result(stream: f64, total: f64) -> f64 { } /// `readable.push(chunk)` for the untyped/`as any` object-method path. -extern "C" fn ns_push1(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn ns_push1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { let stream = readable_push_receiver(closure); if get_hidden_value(stream, hidden_readable_flag_key()).is_none() { throw_readable_push_invalid_receiver(); @@ -838,25 +922,42 @@ fn readable_unshift_after_end_error() -> f64 { crate::value::js_nanbox_pointer(err as i64) } -extern "C" fn ns_unshift1(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn ns_unshift1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { unshift_chunk(this_value(closure), chunk) } /// `readable.compose(stream)` (#1539): the instance-method form of /// `stream.compose`, routed through the shared compose builder so the same /// data-flow and error handling paths cover module and prototype calls. -extern "C" fn ns_compose1(closure: *const ClosureHeader, arg: f64) -> f64 { +extern "C" fn ns_compose1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { build_node_stream_compose(vec![this_value(closure), arg]) } -extern "C" fn ns_pipe2(closure: *const ClosureHeader, dest: f64, options: f64) -> f64 { +extern "C" fn ns_pipe2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + dest: f64, + options: f64, +) -> f64 { if pipe_destination_is_missing(dest) { throw_readable_pipe_missing_destination(); } let stream = this_value(closure); pipe_stream_to_destination(stream, dest, pipe_options_end(options)) } -extern "C" fn ns_writable_write_done(closure: *const ClosureHeader, err: f64) -> f64 { +extern "C" fn ns_writable_write_done( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -867,7 +968,11 @@ extern "C" fn ns_writable_write_done(closure: *const ClosureHeader, err: f64) -> f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_unpipe1(closure: *const ClosureHeader, dest: f64) -> f64 { +extern "C" fn ns_unpipe1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + dest: f64, +) -> f64 { let stream = this_value(closure); if dest.to_bits() == TAG_UNDEFINED { unpipe_all_destinations(stream); @@ -877,140 +982,12 @@ extern "C" fn ns_unpipe1(closure: *const ClosureHeader, dest: f64) -> f64 { stream } -fn pipe_listener_value(listener: *const ClosureHeader) -> f64 { - box_pointer(listener as *const u8) -} - -fn set_pipe_listener_captures( - listener: *mut ClosureHeader, - src: f64, - dest: f64, - unpipe: f64, - error: f64, - close: f64, - finish: f64, -) { - js_closure_set_capture_f64(listener, 0, src); - js_closure_set_capture_f64(listener, 1, dest); - js_closure_set_capture_f64(listener, 2, unpipe); - js_closure_set_capture_f64(listener, 3, error); - js_closure_set_capture_f64(listener, 4, close); - js_closure_set_capture_f64(listener, 5, finish); -} - -fn cleanup_pipe_listeners_from_closure(closure: *const ClosureHeader) { - if closure.is_null() { - return; - } - let dest = js_closure_get_capture_f64(closure, 1); - let unpipe = js_closure_get_capture_f64(closure, 2); - let error = js_closure_get_capture_f64(closure, 3); - let close = js_closure_get_capture_f64(closure, 4); - let finish = js_closure_get_capture_f64(closure, 5); - let _ = remove_stream_listener_for_event(dest, string_value(b"unpipe"), unpipe); - let _ = remove_stream_listener_for_event(dest, string_value(b"error"), error); - let _ = remove_stream_listener_for_event(dest, string_value(b"close"), close); - let _ = remove_stream_listener_for_event(dest, string_value(b"finish"), finish); -} - -extern "C" fn pipe_unpipe_callback(closure: *const ClosureHeader, src: f64) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - let expected_src = js_closure_get_capture_f64(closure, 0); - if src.to_bits() == expected_src.to_bits() { - cleanup_pipe_listeners_from_closure(closure); - } - f64::from_bits(TAG_UNDEFINED) -} - -extern "C" fn pipe_error_callback(closure: *const ClosureHeader, err: f64) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - let src = js_closure_get_capture_f64(closure, 0); - let dest = js_closure_get_capture_f64(closure, 1); - if !unpipe_destination(src, dest) { - cleanup_pipe_listeners_from_closure(closure); - } - if stream_listener_count_for_event(dest, string_value(b"error")) == 0 { - crate::exception::js_throw(err); - } - f64::from_bits(TAG_UNDEFINED) -} - -extern "C" fn pipe_close_callback(closure: *const ClosureHeader) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - let src = js_closure_get_capture_f64(closure, 0); - let dest = js_closure_get_capture_f64(closure, 1); - if !unpipe_destination(src, dest) { - cleanup_pipe_listeners_from_closure(closure); - } - f64::from_bits(TAG_UNDEFINED) -} - -extern "C" fn pipe_finish_callback(closure: *const ClosureHeader) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - let src = js_closure_get_capture_f64(closure, 0); - let dest = js_closure_get_capture_f64(closure, 1); - if !unpipe_destination(src, dest) { - cleanup_pipe_listeners_from_closure(closure); - } - f64::from_bits(TAG_UNDEFINED) -} - -extern "C" fn pipe_drain_callback(closure: *const ClosureHeader) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - let src = js_closure_get_capture_f64(closure, 0); - let dest = js_closure_get_capture_f64(closure, 1); - let listener = js_closure_get_capture_f64(closure, 2); - let _ = remove_stream_listener_for_event(dest, string_value(b"drain"), listener); - if pipe_destination_contains(src, dest) && !stream_destroyed(src) { - if stream_hidden_ended(src) && pending_readable_chunk_count(src) == 0 { - set_readable_flowing(src, f64::from_bits(TAG_TRUE)); - schedule_readable_end(src); - return f64::from_bits(TAG_UNDEFINED); - } - let _ = resume_readable_stream_from_pipe(src); - } - f64::from_bits(TAG_UNDEFINED) -} - -fn finish_pipe_destination(dest: f64) { - if stream_destroyed(dest) || has_truthy_hidden(dest, hidden_finish_emitted_key()) { - return; - } - if writable_length(dest) > 0.0 { - set_hidden_value( - dest, - hidden_stream_pipe_end_pending_key(), - f64::from_bits(TAG_TRUE), - ); - } else { - set_hidden_value( - dest, - hidden_stream_pipe_end_pending_key(), - f64::from_bits(TAG_FALSE), - ); - if !finish_transform_stream(dest, None) { - finish_stream(dest, None); - } - } -} - -extern "C" fn pipe_finish_destination_callback(closure: *const ClosureHeader) -> f64 { - if closure.is_null() { - return f64::from_bits(TAG_UNDEFINED); - } - finish_pipe_destination(js_closure_get_capture_f64(closure, 0)); - f64::from_bits(TAG_UNDEFINED) -} +mod pipe_listeners; +use pipe_listeners::{ + finish_pipe_destination, pipe_close_callback, pipe_drain_callback, pipe_error_callback, + pipe_finish_callback, pipe_finish_destination_callback, pipe_listener_value, + pipe_unpipe_callback, set_pipe_listener_captures, +}; fn install_pipe_destination_listeners(src: f64, dest: f64) { let unpipe = js_closure_alloc(pipe_unpipe_callback as *const u8, 6); @@ -1115,7 +1092,12 @@ fn pipe_destination_is_missing(dest: f64) -> bool { value.is_undefined() || value.is_null() } -extern "C" fn transform_write_callback(closure: *const ClosureHeader, err: f64, value: f64) -> f64 { +extern "C" fn transform_write_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, + value: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -1132,7 +1114,12 @@ extern "C" fn transform_write_callback(closure: *const ClosureHeader, err: f64, f64::from_bits(TAG_UNDEFINED) } -extern "C" fn transform_flush_callback(closure: *const ClosureHeader, err: f64, value: f64) -> f64 { +extern "C" fn transform_flush_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, + value: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -1162,26 +1149,41 @@ extern "C" fn transform_flush_callback(closure: *const ClosureHeader, err: f64, f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_write3(closure: *const ClosureHeader, chunk: f64, enc: f64, cb: f64) -> f64 { +extern "C" fn ns_write3( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, + enc: f64, + cb: f64, +) -> f64 { let stream = this_value(closure); write_writable_chunk(stream, chunk, enc, cb) } -extern "C" fn ns_end3(closure: *const ClosureHeader, chunk: f64, encoding: f64, cb: f64) -> f64 { +extern "C" fn ns_end3( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, + encoding: f64, + cb: f64, +) -> f64 { let stream = this_value(closure); finish_stream_with_args(stream, chunk, encoding, cb); stream } -extern "C" fn ns_cork0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_cork0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { cork_stream(this_value(closure)) } -extern "C" fn ns_uncork0(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_uncork0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { uncork_stream(this_value(closure)) } -extern "C" fn writable_write_callback_noop(_closure: *const ClosureHeader) -> f64 { +extern "C" fn writable_write_callback_noop( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/node_stream/async_iterator.rs b/crates/perry-runtime/src/node_stream/async_iterator.rs index d46ac575a1..6cfdc990df 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator.rs @@ -54,6 +54,7 @@ fn readable_iterator_done() -> f64 { extern "C" fn ns_readable_iterator_chunk_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let outer = js_closure_get_capture_ptr(closure, 0) as *mut crate::promise::Promise; @@ -65,6 +66,7 @@ extern "C" fn ns_readable_iterator_chunk_fulfilled( extern "C" fn ns_readable_iterator_chunk_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let outer = js_closure_get_capture_ptr(closure, 0) as *mut crate::promise::Promise; @@ -76,6 +78,7 @@ extern "C" fn ns_readable_iterator_chunk_rejected( extern "C" fn ns_readable_source_iterator_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, result: f64, ) -> f64 { let iterator = js_closure_get_capture_f64(closure, 0); @@ -99,6 +102,7 @@ extern "C" fn ns_readable_source_iterator_fulfilled( extern "C" fn ns_readable_source_iterator_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let iterator = js_closure_get_capture_f64(closure, 0); @@ -339,7 +343,11 @@ fn iterator_from_listener(closure: *const ClosureHeader) -> f64 { } /// `data` listener: resolve a waiting `next()` or buffer the chunk. -extern "C" fn ns_readable_iter_on_data(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn ns_readable_iter_on_data( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -377,7 +385,10 @@ extern "C" fn ns_readable_iter_on_data(closure: *const ClosureHeader, chunk: f64 /// `end` listener: a waiting `next()` resolves to `{done:true}`; otherwise the /// end is recorded so a later `next()` (after the queue drains) reports done. -extern "C" fn ns_readable_iter_on_end(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_iter_on_end( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -400,7 +411,11 @@ extern "C" fn ns_readable_iter_on_end(closure: *const ClosureHeader) -> f64 { /// `error` listener: reject a waiting `next()` or store the error for the next /// pull. -extern "C" fn ns_readable_iter_on_error(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn ns_readable_iter_on_error( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -572,7 +587,10 @@ pub(super) fn call_source_iterator_return(stream: f64) { settle_iterator_return_value(returned); } -extern "C" fn ns_readable_iterator_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_iterator_next( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let iterator = scope.root_nanbox_f64(this_value(closure)); if iterator_is_done(iterator.get_nanbox_f64()) { @@ -675,7 +693,10 @@ extern "C" fn ns_readable_iterator_next(closure: *const ClosureHeader) -> f64 { box_pointer(promise.get_raw_const_ptr()) } -extern "C" fn ns_readable_iterator_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_iterator_return( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let iterator = this_value(closure); let already_done = iterator_is_done(iterator); iterator_mark_done(iterator); @@ -692,15 +713,25 @@ extern "C" fn ns_readable_iterator_return(closure: *const ClosureHeader) -> f64 readable_iterator_done() } -extern "C" fn ns_readable_iterator_self(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_readable_iterator_self( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { this_value(closure) } -pub(super) extern "C" fn ns_async_iterator(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_async_iterator( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { build_readable_async_iterator(this_value(closure), true) } -pub(super) extern "C" fn ns_iterator1(closure: *const ClosureHeader, opts: f64) -> f64 { +pub(super) extern "C" fn ns_iterator1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + opts: f64, +) -> f64 { build_readable_async_iterator(this_value(closure), destroy_on_return_from_options(opts)) } @@ -808,7 +839,10 @@ pub extern "C" fn js_make_single_value_async_iterator(value: f64) -> f64 { iterator } -extern "C" fn single_value_iterator_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn single_value_iterator_next( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let iterator = this_value(closure); if iterator_is_done(iterator) { return readable_iterator_done(); @@ -819,7 +853,10 @@ extern "C" fn single_value_iterator_next(closure: *const ClosureHeader) -> f64 { resolved_promise(iterator_result(value, false)) } -extern "C" fn single_value_iterator_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn single_value_iterator_return( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { iterator_mark_done(this_value(closure)); readable_iterator_done() } @@ -932,9 +969,9 @@ mod fifo_pending_tests { js_closure_set_capture_f64(data_cb, 0, iterator); set_readable_flowing(stream, f64::from_bits(TAG_TRUE)); - ns_readable_iter_on_data(data_cb, 1.0); + ns_readable_iter_on_data(data_cb, crate::closure::JsThis::current(), 1.0); assert!(readable_is_flowing(stream)); - ns_readable_iter_on_data(data_cb, 2.0); + ns_readable_iter_on_data(data_cb, crate::closure::JsThis::current(), 2.0); assert!(readable_is_paused(stream)); assert_eq!( @@ -1006,7 +1043,8 @@ mod fifo_pending_tests { let rejected = js_closure_alloc(ns_readable_source_iterator_rejected as *const u8, 1); js_closure_set_capture_f64(rejected, 0, iterator); - let result = ns_readable_source_iterator_rejected(rejected, 7.0); + let result = + ns_readable_source_iterator_rejected(rejected, crate::closure::JsThis::current(), 7.0); let promise = crate::value::js_nanbox_get_pointer(result) as *mut crate::promise::Promise; assert!(iterator_is_done(iterator)); assert!(stream_destroyed(stream)); diff --git a/crates/perry-runtime/src/node_stream/pipe_listeners.rs b/crates/perry-runtime/src/node_stream/pipe_listeners.rs new file mode 100644 index 0000000000..f7b66dd0d8 --- /dev/null +++ b/crates/perry-runtime/src/node_stream/pipe_listeners.rs @@ -0,0 +1,158 @@ +//! The `pipe()` destination listeners: closures capturing (src, dest, and their siblings) that unpipe, forward errors and finish the destination. + +use super::*; + +pub(super) fn pipe_listener_value(listener: *const ClosureHeader) -> f64 { + box_pointer(listener as *const u8) +} + +pub(super) fn set_pipe_listener_captures( + listener: *mut ClosureHeader, + src: f64, + dest: f64, + unpipe: f64, + error: f64, + close: f64, + finish: f64, +) { + js_closure_set_capture_f64(listener, 0, src); + js_closure_set_capture_f64(listener, 1, dest); + js_closure_set_capture_f64(listener, 2, unpipe); + js_closure_set_capture_f64(listener, 3, error); + js_closure_set_capture_f64(listener, 4, close); + js_closure_set_capture_f64(listener, 5, finish); +} + +fn cleanup_pipe_listeners_from_closure(closure: *const ClosureHeader) { + if closure.is_null() { + return; + } + let dest = js_closure_get_capture_f64(closure, 1); + let unpipe = js_closure_get_capture_f64(closure, 2); + let error = js_closure_get_capture_f64(closure, 3); + let close = js_closure_get_capture_f64(closure, 4); + let finish = js_closure_get_capture_f64(closure, 5); + let _ = remove_stream_listener_for_event(dest, string_value(b"unpipe"), unpipe); + let _ = remove_stream_listener_for_event(dest, string_value(b"error"), error); + let _ = remove_stream_listener_for_event(dest, string_value(b"close"), close); + let _ = remove_stream_listener_for_event(dest, string_value(b"finish"), finish); +} + +pub(super) extern "C" fn pipe_unpipe_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + src: f64, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + let expected_src = js_closure_get_capture_f64(closure, 0); + if src.to_bits() == expected_src.to_bits() { + cleanup_pipe_listeners_from_closure(closure); + } + f64::from_bits(TAG_UNDEFINED) +} + +pub(super) extern "C" fn pipe_error_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + let src = js_closure_get_capture_f64(closure, 0); + let dest = js_closure_get_capture_f64(closure, 1); + if !unpipe_destination(src, dest) { + cleanup_pipe_listeners_from_closure(closure); + } + if stream_listener_count_for_event(dest, string_value(b"error")) == 0 { + crate::exception::js_throw(err); + } + f64::from_bits(TAG_UNDEFINED) +} + +pub(super) extern "C" fn pipe_close_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + let src = js_closure_get_capture_f64(closure, 0); + let dest = js_closure_get_capture_f64(closure, 1); + if !unpipe_destination(src, dest) { + cleanup_pipe_listeners_from_closure(closure); + } + f64::from_bits(TAG_UNDEFINED) +} + +pub(super) extern "C" fn pipe_finish_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + let src = js_closure_get_capture_f64(closure, 0); + let dest = js_closure_get_capture_f64(closure, 1); + if !unpipe_destination(src, dest) { + cleanup_pipe_listeners_from_closure(closure); + } + f64::from_bits(TAG_UNDEFINED) +} + +pub(super) extern "C" fn pipe_drain_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + let src = js_closure_get_capture_f64(closure, 0); + let dest = js_closure_get_capture_f64(closure, 1); + let listener = js_closure_get_capture_f64(closure, 2); + let _ = remove_stream_listener_for_event(dest, string_value(b"drain"), listener); + if pipe_destination_contains(src, dest) && !stream_destroyed(src) { + if stream_hidden_ended(src) && pending_readable_chunk_count(src) == 0 { + set_readable_flowing(src, f64::from_bits(TAG_TRUE)); + schedule_readable_end(src); + return f64::from_bits(TAG_UNDEFINED); + } + let _ = resume_readable_stream_from_pipe(src); + } + f64::from_bits(TAG_UNDEFINED) +} + +pub(super) fn finish_pipe_destination(dest: f64) { + if stream_destroyed(dest) || has_truthy_hidden(dest, hidden_finish_emitted_key()) { + return; + } + if writable_length(dest) > 0.0 { + set_hidden_value( + dest, + hidden_stream_pipe_end_pending_key(), + f64::from_bits(TAG_TRUE), + ); + } else { + set_hidden_value( + dest, + hidden_stream_pipe_end_pending_key(), + f64::from_bits(TAG_FALSE), + ); + if !finish_transform_stream(dest, None) { + finish_stream(dest, None); + } + } +} + +pub(super) extern "C" fn pipe_finish_destination_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + if closure.is_null() { + return f64::from_bits(TAG_UNDEFINED); + } + finish_pipe_destination(js_closure_get_capture_f64(closure, 0)); + f64::from_bits(TAG_UNDEFINED) +} 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 e2d1be22d7..5eba52f560 100644 --- a/crates/perry-runtime/src/node_stream/readable_from_promises.rs +++ b/crates/perry-runtime/src/node_stream/readable_from_promises.rs @@ -110,6 +110,7 @@ fn settle_readable_from_promise_rejected(stream: f64, chunk: f64, reason: f64) { pub(super) extern "C" fn ns_readable_from_promise_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { if closure.is_null() { @@ -123,6 +124,7 @@ pub(super) extern "C" fn ns_readable_from_promise_fulfilled( pub(super) extern "C" fn ns_readable_from_promise_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { if closure.is_null() { diff --git a/crates/perry-runtime/src/node_stream_constructors/builders.rs b/crates/perry-runtime/src/node_stream_constructors/builders.rs index cfbbac66e4..bea5497fca 100644 --- a/crates/perry-runtime/src/node_stream_constructors/builders.rs +++ b/crates/perry-runtime/src/node_stream_constructors/builders.rs @@ -286,6 +286,7 @@ pub unsafe extern "C" fn js_array_subclass_init_args( /// Legacy shape-carried compatibility closure for `Array.prototype.fill`. pub(super) extern "C" fn ns_array_fill( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, start: f64, end: f64, diff --git a/crates/perry-runtime/src/node_stream_constructors/pipeline.rs b/crates/perry-runtime/src/node_stream_constructors/pipeline.rs index c9a2f992e7..228aca61e8 100644 --- a/crates/perry-runtime/src/node_stream_constructors/pipeline.rs +++ b/crates/perry-runtime/src/node_stream_constructors/pipeline.rs @@ -45,6 +45,7 @@ fn node_stream_duplex_from_source_chunks(source: f64) -> f64 { pub(super) extern "C" fn duplex_from_writable_write_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, encoding: f64, cb: f64, @@ -58,6 +59,7 @@ pub(super) extern "C" fn duplex_from_writable_write_callback( pub(super) extern "C" fn duplex_from_writable_final_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, cb: f64, ) -> f64 { if closure.is_null() { @@ -292,6 +294,7 @@ pub extern "C" fn js_node_stream_pipeline(args: *const crate::array::ArrayHeader pub(crate) extern "C" fn duplex_pair_write_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _encoding: f64, cb: f64, @@ -311,7 +314,11 @@ pub(crate) extern "C" fn duplex_pair_write_callback( f64::from_bits(TAG_UNDEFINED) } -pub(crate) extern "C" fn duplex_pair_final_callback(closure: *const ClosureHeader, cb: f64) -> f64 { +pub(crate) extern "C" fn duplex_pair_final_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } 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 9f6c80aa46..dd04525405 100644 --- a/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs +++ b/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs @@ -214,7 +214,11 @@ fn call_stream_callback(callback: f64, err: f64) { } } -extern "C" fn node_to_web_readable_pull(closure: *const ClosureHeader, controller: f64) -> f64 { +extern "C" fn node_to_web_readable_pull( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + controller: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -262,14 +266,22 @@ fn settle_foreign_readable_pull(controller: f64, result: f64) { } } -extern "C" fn foreign_readable_pull_fulfilled(closure: *const ClosureHeader, result: f64) -> f64 { +extern "C" fn foreign_readable_pull_fulfilled( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + result: f64, +) -> f64 { if !closure.is_null() { settle_foreign_readable_pull(js_closure_get_capture_f64(closure, 0), result); } f64::from_bits(TAG_UNDEFINED) } -extern "C" fn foreign_readable_pull_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn foreign_readable_pull_rejected( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { if let Some(error) = web_readable_error() { let scope = crate::gc::RuntimeHandleScope::new(); @@ -288,7 +300,11 @@ extern "C" fn foreign_readable_pull_rejected(closure: *const ClosureHeader, reas /// other foreign Readables whose bytes live outside node:stream's hidden chunk /// array. Returning the chained promise keeps the Web-stream pull in flight /// until the foreign source yields, ends, or rejects (#9616). -extern "C" fn foreign_readable_to_web_pull(closure: *const ClosureHeader, controller: f64) -> f64 { +extern "C" fn foreign_readable_to_web_pull( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + controller: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -328,7 +344,11 @@ extern "C" fn foreign_readable_to_web_pull(closure: *const ClosureHeader, contro box_pointer(chained as *const u8) } -extern "C" fn foreign_readable_to_web_cancel(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn foreign_readable_to_web_cancel( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return resolved_promise(f64::from_bits(TAG_UNDEFINED)); } @@ -341,7 +361,11 @@ extern "C" fn foreign_readable_to_web_cancel(closure: *const ClosureHeader, reas returned.get_nanbox_f64() } -extern "C" fn node_to_web_readable_cancel(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn node_to_web_readable_cancel( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { destroy_stream(js_closure_get_capture_f64(closure, 0), reason); } @@ -408,7 +432,10 @@ fn node_readable_to_web(node_stream: f64) -> Option { })) } -extern "C" fn fallback_web_reader_read(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_web_reader_read( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return resolved_promise(build_web_read_result(f64::from_bits(TAG_UNDEFINED), true)); } @@ -424,14 +451,21 @@ extern "C" fn fallback_web_reader_read(closure: *const ClosureHeader) -> f64 { resolved_promise(result) } -extern "C" fn fallback_web_reader_cancel(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn fallback_web_reader_cancel( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { destroy_stream(js_closure_get_capture_f64(closure, 0), reason); } resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn fallback_web_readable_get_reader(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_web_readable_get_reader( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -450,7 +484,10 @@ extern "C" fn fallback_web_readable_get_reader(closure: *const ClosureHeader) -> ]) } -extern "C" fn fallback_foreign_reader_read(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_foreign_reader_read( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return resolved_promise(build_web_read_result(f64::from_bits(TAG_UNDEFINED), true)); } @@ -459,11 +496,18 @@ extern "C" fn fallback_foreign_reader_read(closure: *const ClosureHeader) -> f64 call_method_no_args(iterator.get_nanbox_f64(), b"next") } -extern "C" fn fallback_foreign_reader_cancel(closure: *const ClosureHeader, reason: f64) -> f64 { - foreign_readable_to_web_cancel(closure, reason) +extern "C" fn fallback_foreign_reader_cancel( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { + foreign_readable_to_web_cancel(closure, crate::closure::JsThis::current(), reason) } -extern "C" fn fallback_foreign_get_reader(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_foreign_get_reader( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -548,7 +592,11 @@ fn fallback_node_readable_to_web(node_stream: f64) -> f64 { ]) } -extern "C" fn node_to_web_writable_write(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn node_to_web_writable_write( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { if !closure.is_null() { let node_stream = js_closure_get_capture_f64(closure, 0); let _ = write_writable_chunk( @@ -561,7 +609,10 @@ extern "C" fn node_to_web_writable_write(closure: *const ClosureHeader, chunk: f f64::from_bits(TAG_UNDEFINED) } -extern "C" fn node_to_web_writable_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn node_to_web_writable_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if !closure.is_null() { let node_stream = js_closure_get_capture_f64(closure, 0); finish_stream_with_args( @@ -574,7 +625,11 @@ extern "C" fn node_to_web_writable_close(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn node_to_web_writable_abort(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn node_to_web_writable_abort( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { destroy_stream(js_closure_get_capture_f64(closure, 0), reason); } @@ -603,7 +658,11 @@ fn node_writable_to_web(node_stream: f64) -> Option { }) } -extern "C" fn fallback_web_writer_write(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn fallback_web_writer_write( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { if !closure.is_null() { let node_stream = js_closure_get_capture_f64(closure, 0); let _ = write_writable_chunk( @@ -616,7 +675,10 @@ extern "C" fn fallback_web_writer_write(closure: *const ClosureHeader, chunk: f6 resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn fallback_web_writer_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_web_writer_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if !closure.is_null() { finish_stream_with_args( js_closure_get_capture_f64(closure, 0), @@ -628,14 +690,21 @@ extern "C" fn fallback_web_writer_close(closure: *const ClosureHeader) -> f64 { resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn fallback_web_writer_abort(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn fallback_web_writer_abort( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { destroy_stream(js_closure_get_capture_f64(closure, 0), reason); } resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn fallback_web_writable_get_writer(closure: *const ClosureHeader) -> f64 { +extern "C" fn fallback_web_writable_get_writer( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -714,7 +783,11 @@ fn install_web_readable_adapter(node_stream: f64, web_stream: f64) -> bool { true } -extern "C" fn web_to_node_readable_read(closure: *const ClosureHeader, _size: f64) -> f64 { +extern "C" fn web_to_node_readable_read( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _size: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -755,6 +828,7 @@ fn pump_web_reader(node_stream: f64, reader: f64) { extern "C" fn web_to_node_readable_read_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, result: f64, ) -> f64 { if closure.is_null() { @@ -780,6 +854,7 @@ extern "C" fn web_to_node_readable_read_fulfilled( extern "C" fn web_to_node_readable_read_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { if !closure.is_null() { @@ -837,6 +912,7 @@ fn install_web_writable_adapter(node_stream: f64, web_stream: f64) -> bool { extern "C" fn web_to_node_writable_write( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _encoding: f64, callback: f64, @@ -855,7 +931,11 @@ extern "C" fn web_to_node_writable_write( f64::from_bits(TAG_UNDEFINED) } -extern "C" fn web_to_node_writable_final(closure: *const ClosureHeader, callback: f64) -> f64 { +extern "C" fn web_to_node_writable_final( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback: f64, +) -> f64 { if closure.is_null() { call_stream_callback(callback, f64::from_bits(TAG_UNDEFINED)); return f64::from_bits(TAG_UNDEFINED); @@ -872,6 +952,7 @@ extern "C" fn web_to_node_writable_final(closure: *const ClosureHeader, callback extern "C" fn web_to_node_writable_destroy( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, callback: f64, ) -> f64 { @@ -908,7 +989,11 @@ fn attach_web_writable_callback(promise: *mut crate::promise::Promise, callback: crate::promise::js_promise_attach_handlers(promise, fulfilled, rejected); } -extern "C" fn web_to_node_writable_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn web_to_node_writable_fulfilled( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, +) -> f64 { if !closure.is_null() { call_stream_callback( js_closure_get_capture_f64(closure, 0), @@ -918,7 +1003,11 @@ extern "C" fn web_to_node_writable_fulfilled(closure: *const ClosureHeader, _val f64::from_bits(TAG_UNDEFINED) } -extern "C" fn web_to_node_writable_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn web_to_node_writable_rejected( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if !closure.is_null() { call_stream_callback(js_closure_get_capture_f64(closure, 0), reason); } diff --git a/crates/perry-runtime/src/node_stream_destroy_state.rs b/crates/perry-runtime/src/node_stream_destroy_state.rs index 71a83596b9..69a862522d 100644 --- a/crates/perry-runtime/src/node_stream_destroy_state.rs +++ b/crates/perry-runtime/src/node_stream_destroy_state.rs @@ -9,7 +9,10 @@ use super::{ stream_value_from_handle, this_value, TAG_FALSE, TAG_NULL, TAG_TRUE, TAG_UNDEFINED, }; -pub(super) extern "C" fn ns_destroy_error_microtask(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_destroy_error_microtask( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -25,7 +28,11 @@ pub(super) extern "C" fn ns_destroy_error_microtask(closure: *const ClosureHeade f64::from_bits(TAG_UNDEFINED) } -extern "C" fn ns_destroy_option_done(closure: *const ClosureHeader, err: f64) -> f64 { +extern "C" fn ns_destroy_option_done( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -78,7 +85,11 @@ pub(super) fn destroy_stream(stream: f64, err: f64) { queue_destroy_events(stream, err); } -pub(super) extern "C" fn ns_destroy1(closure: *const ClosureHeader, err: f64) -> f64 { +pub(super) extern "C" fn ns_destroy1( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { let stream = this_value(closure); destroy_stream(stream, err); stream diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index 0bf15920e9..fabc8c94f7 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -320,6 +320,7 @@ fn require_event_emitter_async_resource_receiver( extern "C" fn ns_ee_async_resource_emit_rest( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, rest: f64, ) -> f64 { @@ -333,14 +334,17 @@ extern "C" fn ns_ee_async_resource_emit_rest( if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_enter(runtime_async_id); } - let result = ns_emit_rest(closure, event, rest); + let result = ns_emit_rest(closure, crate::closure::JsThis::current(), event, rest); if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_leave(runtime_async_id); } result } -extern "C" fn ns_ee_async_resource_destroy(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_ee_async_resource_destroy( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match require_event_emitter_async_resource_receiver(closure) { EventEmitterAsyncResourceBacking::ExternalEmitter(handle) => { crate::object::event_emitter_async_resource_dispatch() @@ -353,7 +357,10 @@ extern "C" fn ns_ee_async_resource_destroy(closure: *const ClosureHeader) -> f64 } } -extern "C" fn ns_ee_async_resource_getter(closure: *const ClosureHeader) -> f64 { +extern "C" fn ns_ee_async_resource_getter( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let operation = crate::closure::js_closure_get_capture_ptr(closure, 1) as u32; match require_event_emitter_async_resource_receiver(closure) { EventEmitterAsyncResourceBacking::ExternalEmitter(handle) => { diff --git a/crates/perry-runtime/src/node_stream_event_emitter.rs b/crates/perry-runtime/src/node_stream_event_emitter.rs index 4aababf4f1..231b8ee699 100644 --- a/crates/perry-runtime/src/node_stream_event_emitter.rs +++ b/crates/perry-runtime/src/node_stream_event_emitter.rs @@ -7,7 +7,11 @@ const STREAM_EVENT_NAMES_KEY: &[u8] = b"__perryStreamEventNames"; const STREAM_LISTENERS_PREFIX: &[u8] = b"__perryStreamListeners:"; const STREAM_ONCE_PREFIX: &[u8] = b"__perryStreamOnce:"; -pub(super) extern "C" fn ns_set_max_listeners(closure: *const ClosureHeader, value: f64) -> f64 { +pub(super) extern "C" fn ns_set_max_listeners( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { set_stream_max_listeners(super::this_value(closure), value) } @@ -73,7 +77,10 @@ pub(crate) fn validate_max_listeners(value: f64) -> f64 { n } -pub(super) extern "C" fn ns_get_max_listeners(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_get_max_listeners( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { stream_max_listeners(super::this_value(closure)) } @@ -86,13 +93,23 @@ fn stream_max_listeners(stream: f64) -> f64 { super::get_hidden_value(stream, super::hidden_max_listeners_key()).unwrap_or(10.0) } -pub(super) extern "C" fn ns_on2(closure: *const ClosureHeader, event: f64, cb: f64) -> f64 { +pub(super) extern "C" fn ns_on2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + cb: f64, +) -> f64 { let stream = super::this_value(closure); add_stream_listener_for_event(stream, event, cb); stream } -pub(super) extern "C" fn ns_once2(closure: *const ClosureHeader, event: f64, cb: f64) -> f64 { +pub(super) extern "C" fn ns_once2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + cb: f64, +) -> f64 { let stream = super::this_value(closure); add_stream_listener_for_event_with_options(stream, event, cb, true, false); stream @@ -100,6 +117,7 @@ pub(super) extern "C" fn ns_once2(closure: *const ClosureHeader, event: f64, cb: pub(super) extern "C" fn ns_prepend_listener2( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -110,6 +128,7 @@ pub(super) extern "C" fn ns_prepend_listener2( pub(super) extern "C" fn ns_prepend_once_listener2( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -120,6 +139,7 @@ pub(super) extern "C" fn ns_prepend_once_listener2( pub(super) extern "C" fn ns_remove_listener2( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -128,12 +148,18 @@ pub(super) extern "C" fn ns_remove_listener2( stream } -pub(super) extern "C" fn ns_off2(closure: *const ClosureHeader, event: f64, cb: f64) -> f64 { - ns_remove_listener2(closure, event, cb) +pub(super) extern "C" fn ns_off2( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, + cb: f64, +) -> f64 { + ns_remove_listener2(closure, crate::closure::JsThis::current(), event, cb) } pub(super) extern "C" fn ns_remove_all_listeners1( closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let stream = super::this_value(closure); @@ -215,7 +241,11 @@ pub extern "C" fn js_node_stream_method_remove_all_listeners( stream } -pub(super) extern "C" fn ns_listener_count(closure: *const ClosureHeader, event: f64) -> f64 { +pub(super) extern "C" fn ns_listener_count( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, +) -> f64 { stream_listener_count_for_event(super::this_value(closure), event) as f64 } @@ -228,7 +258,10 @@ pub extern "C" fn js_node_stream_method_listener_count(stream_handle: i64, event stream_listener_count_for_event(stream, event) as f64 } -pub(super) extern "C" fn ns_event_names(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_event_names( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream = super::this_value(closure); f64::from_bits(JSValue::pointer(stream_event_names_array(stream) as *const u8).bits()) } @@ -238,7 +271,11 @@ pub extern "C" fn js_node_stream_method_event_names(stream_handle: i64) -> i64 { stream_event_names_array(super::stream_value_from_handle(stream_handle)) as i64 } -pub(super) extern "C" fn ns_listeners(closure: *const ClosureHeader, event: f64) -> f64 { +pub(super) extern "C" fn ns_listeners( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, +) -> f64 { let stream = super::this_value(closure); f64::from_bits( JSValue::pointer(stream_listeners_array_for_event(stream, event, false) as *const u8) @@ -246,7 +283,11 @@ pub(super) extern "C" fn ns_listeners(closure: *const ClosureHeader, event: f64) ) } -pub(super) extern "C" fn ns_raw_listeners(closure: *const ClosureHeader, event: f64) -> f64 { +pub(super) extern "C" fn ns_raw_listeners( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, +) -> f64 { let stream = super::this_value(closure); f64::from_bits( JSValue::pointer(stream_listeners_array_for_event(stream, event, true) as *const u8).bits(), @@ -678,7 +719,11 @@ pub(super) fn call_listener_args(stream: f64, listener: f64, args: &[f64]) -> f6 result } -pub(super) extern "C" fn ns_capture_rejection(closure: *const ClosureHeader, reason: f64) -> f64 { +pub(super) extern "C" fn ns_capture_rejection( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(super::TAG_UNDEFINED); } diff --git a/crates/perry-runtime/src/node_stream_iter_helpers.rs b/crates/perry-runtime/src/node_stream_iter_helpers.rs index d6d81e3a92..2bded8877d 100644 --- a/crates/perry-runtime/src/node_stream_iter_helpers.rs +++ b/crates/perry-runtime/src/node_stream_iter_helpers.rs @@ -9,7 +9,10 @@ use crate::closure::{ use crate::object::{js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader}; use crate::value::JSValue; -pub(super) extern "C" fn ns_undefined0(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_undefined0( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } @@ -142,7 +145,10 @@ pub(super) fn promise_from_capture( crate::value::js_nanbox_get_pointer(f64::from_bits(bits)) as *mut crate::promise::Promise } -pub(super) extern "C" fn ns_stream_abort_listener(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_stream_abort_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -270,7 +276,11 @@ pub(super) fn extend_with_array( out } -pub(super) extern "C" fn ns_iter_to_array(closure: *const ClosureHeader, opts: f64) -> f64 { +pub(super) extern "C" fn ns_iter_to_array( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + opts: f64, +) -> f64 { let this = this_value(closure); // An already-errored stream rejects immediately (matches the error-first // check `settle_consuming` used). @@ -321,7 +331,10 @@ fn register_to_array_abort(stream: f64, opts: f64, result: f64) { /// Abort-listener body for `toArray`: cancel the live stream (terminating the /// internal `from_async` iterator that drives it) and reject the result with an /// AbortError. A no-op if the result already settled. -pub(super) extern "C" fn ns_to_array_abort(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_to_array_abort( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -334,7 +347,12 @@ pub(super) extern "C" fn ns_to_array_abort(closure: *const ClosureHeader) -> f64 f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn ns_iter_map(closure: *const ClosureHeader, mapper: f64, opts: f64) -> f64 { +pub(super) extern "C" fn ns_iter_map( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + mapper: f64, + opts: f64, +) -> f64 { let this = this_value(closure); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); @@ -364,6 +382,7 @@ pub(super) extern "C" fn ns_iter_map(closure: *const ClosureHeader, mapper: f64, pub(super) extern "C" fn ns_iter_filter( closure: *const ClosureHeader, + _this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { @@ -530,7 +549,11 @@ fn consume_finalize(state: *const ClosureHeader) { /// `.then` fulfilment for `iterator.next()`: read the iter-result, then /// either finalize (done) or invoke the user callback and await its result. -extern "C" fn consume_on_next(state: *const ClosureHeader, iter_result: f64) -> f64 { +extern "C" fn consume_on_next( + state: *const ClosureHeader, + _this: crate::closure::JsThis, + iter_result: f64, +) -> f64 { if state.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -584,7 +607,11 @@ extern "C" fn consume_on_next(state: *const ClosureHeader, iter_result: f64) -> /// `.then` fulfilment for the awaited callback result: update the /// accumulator / short-circuit, then pull the next element. -extern "C" fn consume_on_cb(closure: *const ClosureHeader, cb_result: f64) -> f64 { +extern "C" fn consume_on_cb( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb_result: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -618,7 +645,11 @@ extern "C" fn consume_on_cb(closure: *const ClosureHeader, cb_result: f64) -> f6 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn consume_reject(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn consume_reject( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -719,7 +750,10 @@ fn register_consume_abort(stream: f64, opts: f64, state: *const ClosureHeader) { /// Abort-listener body for a consuming helper (`forEach`/`reduce`/`find`/ /// `some`/`every`): close the iterator and reject the result with an /// AbortError. A no-op if the result already settled. -pub(super) extern "C" fn ns_consume_abort(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_consume_abort( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -733,6 +767,7 @@ pub(super) extern "C" fn ns_consume_abort(closure: *const ClosureHeader) -> f64 pub(super) extern "C" fn ns_iter_reduce( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reducer: f64, initial: f64, opts: f64, @@ -748,6 +783,7 @@ pub(super) extern "C" fn ns_iter_reduce( pub(super) extern "C" fn ns_iter_for_each( closure: *const ClosureHeader, + _this: crate::closure::JsThis, action: f64, opts: f64, ) -> f64 { @@ -762,6 +798,7 @@ pub(super) extern "C" fn ns_iter_for_each( pub(super) extern "C" fn ns_iter_find( closure: *const ClosureHeader, + _this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { @@ -776,6 +813,7 @@ pub(super) extern "C" fn ns_iter_find( pub(super) extern "C" fn ns_iter_some( closure: *const ClosureHeader, + _this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { @@ -790,6 +828,7 @@ pub(super) extern "C" fn ns_iter_some( pub(super) extern "C" fn ns_iter_every( closure: *const ClosureHeader, + _this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { @@ -804,6 +843,7 @@ pub(super) extern "C" fn ns_iter_every( pub(super) extern "C" fn ns_iter_flat_map( closure: *const ClosureHeader, + _this: crate::closure::JsThis, mapper: f64, opts: f64, ) -> f64 { @@ -981,6 +1021,7 @@ fn take_source_fulfilled(iterator: f64, result: f64) -> f64 { pub(super) extern "C" fn ns_take_source_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, result: f64, ) -> f64 { take_source_fulfilled(js_closure_get_capture_f64(closure, 0), result) @@ -988,6 +1029,7 @@ pub(super) extern "C" fn ns_take_source_fulfilled( pub(super) extern "C" fn ns_take_source_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -1002,6 +1044,7 @@ pub(super) extern "C" fn ns_take_source_rejected( pub(super) extern "C" fn ns_take_limit_fulfilled( closure: *const ClosureHeader, + _this: crate::closure::JsThis, result: f64, ) -> f64 { let iterator = js_closure_get_capture_f64(closure, 0); @@ -1012,13 +1055,17 @@ pub(super) extern "C" fn ns_take_limit_fulfilled( pub(super) extern "C" fn ns_take_limit_rejected( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _reason: f64, ) -> f64 { finish_take_source(js_closure_get_capture_f64(closure, 0), None, true); take_source_done_result() } -pub(super) extern "C" fn ns_take_source_next(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_take_source_next( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let iterator = scope.root_nanbox_f64(this_value(closure)); if has_truthy_hidden(iterator.get_nanbox_f64(), hidden_key(TAKE_SOURCE_DONE_KEY)) { @@ -1111,7 +1158,10 @@ pub(super) extern "C" fn ns_take_source_next(closure: *const ClosureHeader) -> f ) as *const u8) } -pub(super) extern "C" fn ns_take_source_return(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn ns_take_source_return( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { finish_take_source(this_value(closure), None, true); take_source_done_result() } @@ -1143,7 +1193,11 @@ fn take_source_iterator(source: f64, result: f64, count: u32) -> f64 { iterator } -pub(super) extern "C" fn ns_iter_take(closure: *const ClosureHeader, count: f64) -> f64 { +pub(super) extern "C" fn ns_iter_take( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + count: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let this = scope.root_nanbox_f64(this_value(closure)); if !readable_chunks_nonempty(this.get_nanbox_f64()) { @@ -1188,7 +1242,11 @@ pub(super) extern "C" fn ns_iter_take(closure: *const ClosureHeader, count: f64) result } -pub(super) extern "C" fn ns_iter_drop(closure: *const ClosureHeader, count: f64) -> f64 { +pub(super) extern "C" fn ns_iter_drop( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + count: f64, +) -> f64 { let this = this_value(closure); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); @@ -1208,7 +1266,7 @@ pub(super) extern "C" fn ns_iter_drop(closure: *const ClosureHeader, count: f64) mod take_tests { use super::*; - extern "C" fn source_next(closure: *const ClosureHeader) -> f64 { + extern "C" fn source_next(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let source = this_value(closure); let value = get_hidden_value(source, hidden_key(b"count")).unwrap_or(0.0) + 1.0; set_hidden_value(source, hidden_key(b"count"), value); @@ -1230,7 +1288,10 @@ mod take_tests { box_pointer(result as *const u8) } - extern "C" fn source_return(closure: *const ClosureHeader) -> f64 { + extern "C" fn source_return( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let source = this_value(closure); set_hidden_value(source, hidden_key(b"returned"), f64::from_bits(TAG_TRUE)); f64::from_bits(TAG_UNDEFINED) @@ -1262,7 +1323,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!has_truthy_hidden(source, hidden_key(b"returned"))); @@ -1290,7 +1351,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!stream_destroyed(stream)); @@ -1318,7 +1379,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); assert_eq!(readable_hidden_error(result), Some(7.0)); assert_eq!(get_hidden_value(result, hidden_signal_key()), Some(8.0)); diff --git a/crates/perry-runtime/src/node_stream_pipeline.rs b/crates/perry-runtime/src/node_stream_pipeline.rs index b028ceacb0..c214b00762 100644 --- a/crates/perry-runtime/src/node_stream_pipeline.rs +++ b/crates/perry-runtime/src/node_stream_pipeline.rs @@ -15,7 +15,10 @@ pub(super) struct PipelineOptions { pub(super) signal: Option, } -pub(super) extern "C" fn pipeline_success_callback(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn pipeline_success_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -30,7 +33,11 @@ pub(super) extern "C" fn pipeline_success_callback(closure: *const ClosureHeader f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn pipeline_error_callback(closure: *const ClosureHeader, err: f64) -> f64 { +pub(super) extern "C" fn pipeline_error_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -47,7 +54,10 @@ pub(super) extern "C" fn pipeline_error_callback(closure: *const ClosureHeader, f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn pipeline_close_callback(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn pipeline_close_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -527,7 +537,11 @@ pub(super) fn fail_collected_pipeline(stages: &[f64], callback: f64, err: f64) { } } -extern "C" fn collected_pipeline_error_noop(_closure: *const ClosureHeader, _err: f64) -> f64 { +extern "C" fn collected_pipeline_error_noop( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _err: f64, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } @@ -890,6 +904,7 @@ fn fail_composed_duplex(composite: f64, source: f64, stages: f64, err: f64) { pub(super) extern "C" fn compose_stage_error_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, ) -> f64 { if closure.is_null() { @@ -904,6 +919,7 @@ pub(super) extern "C" fn compose_stage_error_callback( pub(super) extern "C" fn compose_source_data_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, ) -> f64 { if closure.is_null() { @@ -922,7 +938,10 @@ pub(super) extern "C" fn compose_source_data_callback( f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn compose_source_end_callback(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn compose_source_end_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -940,6 +959,7 @@ pub(super) extern "C" fn compose_source_end_callback(closure: *const ClosureHead pub(super) extern "C" fn compose_source_error_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, ) -> f64 { if closure.is_null() { @@ -954,6 +974,7 @@ pub(super) extern "C" fn compose_source_error_callback( pub(super) extern "C" fn compose_duplex_write_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _encoding: f64, cb: f64, @@ -979,6 +1000,7 @@ pub(super) extern "C" fn compose_duplex_write_callback( pub(super) extern "C" fn compose_duplex_final_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, cb: f64, ) -> f64 { if closure.is_null() { diff --git a/crates/perry-runtime/src/node_stream_state_view.rs b/crates/perry-runtime/src/node_stream_state_view.rs index d3aeb2bc94..6b8d623231 100644 --- a/crates/perry-runtime/src/node_stream_state_view.rs +++ b/crates/perry-runtime/src/node_stream_state_view.rs @@ -232,7 +232,10 @@ fn captured_field(closure: *const ClosureHeader) -> Option<(usize, &'static str) field_name(id as usize) } -extern "C" fn stream_state_get(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_state_get( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let Some((kind, field)) = captured_field(closure) else { return f64::from_bits(TAG_UNDEFINED); }; @@ -248,7 +251,11 @@ extern "C" fn stream_state_get(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn stream_state_set(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn stream_state_set( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { let Some((_, field)) = captured_field(closure) else { return f64::from_bits(TAG_UNDEFINED); }; diff --git a/crates/perry-runtime/src/node_stream_tests.rs b/crates/perry-runtime/src/node_stream_tests.rs index 69b11548f4..b30606d49a 100644 --- a/crates/perry-runtime/src/node_stream_tests.rs +++ b/crates/perry-runtime/src/node_stream_tests.rs @@ -35,7 +35,10 @@ fn catches_runtime_throw(f: impl FnOnce()) -> bool { crate::exception::catch_js_throw(f).is_err() } -extern "C" fn capture_finished_callback(_closure: *const ClosureHeader) -> f64 { +extern "C" fn capture_finished_callback( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { FINISHED_CALLBACK_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } @@ -113,7 +116,11 @@ fn bare_writable_and_transform_reject_missing_methods() { })); } -extern "C" fn capture_uncaught_stream_error(_closure: *const ClosureHeader, _error: f64) -> f64 { +extern "C" fn capture_uncaught_stream_error( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _error: f64, +) -> f64 { UNCAUGHT_STREAM_ERROR_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } @@ -167,7 +174,11 @@ fn pipe_cleanup_does_not_swallow_destination_error() { crate::os::test_clear_process_event_listeners(); } -extern "C" fn return_pipeline_source(_closure: *const ClosureHeader, source: f64) -> f64 { +extern "C" fn return_pipeline_source( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { source } @@ -212,6 +223,7 @@ fn string_contents(value: f64) -> String { pub(super) extern "C" fn write_capture( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _enc: f64, cb: f64, @@ -227,6 +239,7 @@ pub(super) extern "C" fn write_capture( pub(super) extern "C" fn write_capture_pending( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _enc: f64, cb: f64, @@ -240,6 +253,7 @@ pub(super) extern "C" fn write_capture_pending( extern "C" fn write_capture_encoding( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, enc: f64, cb: f64, @@ -261,6 +275,7 @@ extern "C" fn write_capture_encoding( extern "C" fn write_callback_error( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _chunk: f64, _enc: f64, cb: f64, @@ -272,7 +287,12 @@ extern "C" fn write_callback_error( f64::from_bits(TAG_UNDEFINED) } -extern "C" fn writev_capture(_closure: *const ClosureHeader, chunks: f64, cb: f64) -> f64 { +extern "C" fn writev_capture( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunks: f64, + cb: f64, +) -> f64 { let chunks = raw_ptr_from_value(chunks) as *const crate::array::ArrayHeader; let len = crate::array::js_array_length(chunks); for i in 0..len { @@ -299,16 +319,26 @@ extern "C" fn writev_capture(_closure: *const ClosureHeader, chunks: f64, cb: f6 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn capture_write_callback(_closure: *const ClosureHeader) -> f64 { +extern "C" fn capture_write_callback( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { WRITE_CALLBACK_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn noop_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn noop_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_data_listener(closure: *const ClosureHeader, chunk: f64) -> f64 { +pub(super) extern "C" fn capture_data_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { let expected = crate::closure::js_closure_get_capture_f64(closure, 0); let actual = crate::object::js_implicit_this_get(); READABLE_THIS_MATCHES.with(|matches| { @@ -325,7 +355,11 @@ pub(super) extern "C" fn capture_data_listener(closure: *const ClosureHeader, ch f64::from_bits(TAG_UNDEFINED) } -extern "C" fn capture_data_text_listener(_closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn capture_data_text_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { READABLE_DATA_STRING_FLAGS.with(|flags| { flags .borrow_mut() @@ -335,7 +369,10 @@ extern "C" fn capture_data_text_listener(_closure: *const ClosureHeader, chunk: f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_readable_listener(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_readable_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream = crate::closure::js_closure_get_capture_f64(closure, 0); let got = js_node_stream_method_read(raw_ptr_from_value(stream) as i64, f64::NAN); READABLE_READ_CAPTURED.with(|captured| { @@ -349,7 +386,10 @@ pub(super) extern "C" fn capture_readable_listener(closure: *const ClosureHeader f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_end_listener(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_end_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let expected = crate::closure::js_closure_get_capture_f64(closure, 0); let actual = crate::object::js_implicit_this_get(); READABLE_THIS_MATCHES.with(|matches| { @@ -409,13 +449,18 @@ fn readable_set_encoding_read_returns_decoded_string() { assert_eq!(string_contents(got), "abcd"); } -extern "C" fn capture_error_listener(_closure: *const ClosureHeader, _err: f64) -> f64 { +extern "C" fn capture_error_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _err: f64, +) -> f64 { ERROR_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } pub(super) extern "C" fn capture_expected_arg_listener( closure: *const ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { let expected = crate::closure::js_closure_get_capture_f64(closure, 0); @@ -427,27 +472,42 @@ pub(super) extern "C" fn capture_expected_arg_listener( f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_pause_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_pause_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { STREAM_EVENT_ORDER.with(|events| events.borrow_mut().push(b'P')); f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_resume_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_resume_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { STREAM_EVENT_ORDER.with(|events| events.borrow_mut().push(b'R')); f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_finish_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_finish_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { WRITABLE_FINISH_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_close_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_close_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { WRITABLE_CLOSE_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_end_callback_state(closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_end_callback_state( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream = crate::closure::js_closure_get_capture_f64(closure, 0); let handle = raw_ptr_from_value(stream) as i64; let finish_count = WRITABLE_FINISH_COUNT.with(|count| *count.borrow()); @@ -459,23 +519,34 @@ pub(super) extern "C" fn capture_end_callback_state(closure: *const ClosureHeade f64::from_bits(TAG_UNDEFINED) } -pub(super) extern "C" fn capture_drain_listener(_closure: *const ClosureHeader) -> f64 { +pub(super) extern "C" fn capture_drain_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { WRITABLE_DRAIN_COUNT.with(|count| *count.borrow_mut() += 1); f64::from_bits(TAG_UNDEFINED) } -extern "C" fn read_records_this(closure: *const ClosureHeader) -> f64 { +extern "C" fn read_records_this( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream = crate::closure::js_closure_get_capture_f64(closure, 0); set_hidden_value(stream, hidden_error_key(), string_value("from-read")); f64::from_bits(TAG_UNDEFINED) } -extern "C" fn read_throws(closure: *const ClosureHeader, _size: f64) -> f64 { +extern "C" fn read_throws( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _size: f64, +) -> f64 { crate::exception::js_throw(crate::closure::js_closure_get_capture_f64(closure, 0)) } extern "C" fn transform_upper_callback( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _enc: f64, cb: f64, @@ -499,6 +570,7 @@ extern "C" fn transform_upper_callback( extern "C" fn transform_identity_callback( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _enc: f64, cb: f64, @@ -512,6 +584,7 @@ extern "C" fn transform_identity_callback( extern "C" fn transform_error_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, _chunk: f64, _enc: f64, cb: f64, @@ -525,6 +598,7 @@ extern "C" fn transform_error_callback( extern "C" fn transform_push_pair_callback( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _chunk: f64, _enc: f64, cb: f64, @@ -543,7 +617,11 @@ extern "C" fn transform_push_pair_callback( f64::from_bits(TAG_UNDEFINED) } -extern "C" fn transform_flush_tail_callback(_closure: *const ClosureHeader, cb: f64) -> f64 { +extern "C" fn transform_flush_tail_callback( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); TRANSFORM_THIS_HAS_STREAM_STATE.with(|matches| { matches.borrow_mut().push( @@ -1175,6 +1253,7 @@ fn stream_methods_use_implicit_this_without_closure_capture() { let prev_this = crate::object::js_implicit_this_set(stream); let _ = ns_end3( std::ptr::null(), + crate::closure::JsThis::current(), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), @@ -1243,7 +1322,13 @@ fn readable_pipe_stub_returns_destination_and_rejects_missing_destination() { let dest = js_node_stream_writable_new(f64::from_bits(TAG_UNDEFINED)); assert_eq!( - ns_pipe2(std::ptr::null(), dest, f64::from_bits(TAG_UNDEFINED)).to_bits(), + ns_pipe2( + std::ptr::null(), + crate::closure::JsThis::current(), + dest, + f64::from_bits(TAG_UNDEFINED) + ) + .to_bits(), dest.to_bits() ); assert!(pipe_destination_is_missing(f64::from_bits(TAG_UNDEFINED))); diff --git a/crates/perry-runtime/src/node_submodules/blob.rs b/crates/perry-runtime/src/node_submodules/blob.rs index 1558b90783..d9ba4b48cd 100644 --- a/crates/perry-runtime/src/node_submodules/blob.rs +++ b/crates/perry-runtime/src/node_submodules/blob.rs @@ -88,23 +88,33 @@ fn blob_reader_promise_rejected(reason: f64) -> f64 { }) } -extern "C" fn blob_text_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn blob_text_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let bytes = captured_blob_bytes(closure); blob_reader_promise_value(bytes_to_text_value(&bytes)) } -extern "C" fn blob_array_buffer_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn blob_array_buffer_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let bytes = captured_blob_bytes(closure); blob_reader_promise_value(bytes_to_array_buffer_value(&bytes)) } -extern "C" fn blob_bytes_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn blob_bytes_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let bytes = captured_blob_bytes(closure); blob_reader_promise_value(bytes_to_uint8_array_value(&bytes)) } extern "C" fn blob_slice_method( closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, content_type: f64, @@ -129,26 +139,38 @@ extern "C" fn blob_slice_method( blob_value_from_bytes_and_type(&bytes[lo as usize..hi as usize], &content_type) } -extern "C" fn blob_stream_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn blob_stream_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let bytes = captured_blob_bytes(closure); crate::node_stream::js_node_stream_readable_from(bytes_to_uint8_array_value(&bytes)) } -extern "C" fn file_blob_text_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_text_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match read_file_blob_bytes(captured_file_blob_id(closure)) { Ok(bytes) => blob_reader_promise_value(bytes_to_text_value(&bytes)), Err(reason) => blob_reader_promise_rejected(reason), } } -extern "C" fn file_blob_array_buffer_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_array_buffer_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match read_file_blob_bytes(captured_file_blob_id(closure)) { Ok(bytes) => blob_reader_promise_value(bytes_to_array_buffer_value(&bytes)), Err(reason) => blob_reader_promise_rejected(reason), } } -extern "C" fn file_blob_bytes_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_bytes_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match read_file_blob_bytes(captured_file_blob_id(closure)) { Ok(bytes) => blob_reader_promise_value(bytes_to_uint8_array_value(&bytes)), Err(reason) => blob_reader_promise_rejected(reason), @@ -157,6 +179,7 @@ extern "C" fn file_blob_bytes_method(closure: *const ClosureHeader) -> f64 { extern "C" fn file_blob_slice_method( closure: *const ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, content_type: f64, @@ -193,11 +216,17 @@ extern "C" fn file_blob_slice_method( blob_value_from_file_state(child) } -extern "C" fn file_blob_stream_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { file_blob_stream_value(captured_file_blob_id(closure)) } -extern "C" fn file_blob_stream_get_reader_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_get_reader_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream_id = captured_file_blob_stream_id(closure); let obj = js_object_alloc(0, 4); set_named_value( @@ -223,11 +252,17 @@ extern "C" fn file_blob_stream_get_reader_method(closure: *const ClosureHeader) object_value(obj) } -extern "C" fn file_blob_stream_next_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_next_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { file_blob_stream_next(captured_file_blob_stream_id(closure)) } -extern "C" fn file_blob_stream_return_method(closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_return_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stream_id = captured_file_blob_stream_id(closure); FILE_BLOB_STREAMS.with(|streams| { if let Some(state) = streams.borrow_mut().get_mut(&stream_id) { @@ -237,20 +272,34 @@ extern "C" fn file_blob_stream_return_method(closure: *const ClosureHeader) -> f resolved_iterator_promise(f64::from_bits(crate::value::TAG_UNDEFINED), true) } -extern "C" fn file_blob_stream_values_method(closure: *const ClosureHeader, _options: f64) -> f64 { +extern "C" fn file_blob_stream_values_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _options: f64, +) -> f64 { let _ = closure; crate::object::js_implicit_this_get() } -extern "C" fn file_blob_stream_async_iterator_method(_closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_async_iterator_method( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::object::js_implicit_this_get() } -extern "C" fn file_blob_stream_undefined_method(_closure: *const ClosureHeader) -> f64 { +extern "C" fn file_blob_stream_undefined_method( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn file_blob_stream_cancel_method(closure: *const ClosureHeader, _reason: f64) -> f64 { +extern "C" fn file_blob_stream_cancel_method( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _reason: f64, +) -> f64 { let stream_id = captured_file_blob_stream_id(closure); FILE_BLOB_STREAMS.with(|streams| { if let Some(state) = streams.borrow_mut().get_mut(&stream_id) { diff --git a/crates/perry-runtime/src/node_submodules/consumers.rs b/crates/perry-runtime/src/node_submodules/consumers.rs index ad44e03cd7..f00d46c5d0 100644 --- a/crates/perry-runtime/src/node_submodules/consumers.rs +++ b/crates/perry-runtime/src/node_submodules/consumers.rs @@ -478,7 +478,7 @@ fn call_collector_method( if let Some(promise) = promise_ptr_from_value(result) { crate::promise::js_promise_then(promise, step, reject); } else { - consumer_collect_step(step, result); + consumer_collect_step(step, crate::closure::JsThis::current(), result); } } @@ -583,13 +583,21 @@ pub(crate) fn consume_bytes(stream: f64) -> f64 { consume_stream(ConsumerKind::Bytes, stream) } -extern "C" fn consumer_collect_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn consumer_collect_rejected( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { let promise = js_closure_get_capture_ptr(closure, 0) as *mut crate::Promise; crate::promise::js_promise_reject(promise, reason); 0.0 } -extern "C" fn consumer_collect_step(closure: *const ClosureHeader, iter_result: f64) -> f64 { +extern "C" fn consumer_collect_step( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + iter_result: f64, +) -> f64 { let promise = js_closure_get_capture_ptr(closure, 0) as *mut crate::Promise; let mut result_arr = js_closure_get_capture_ptr(closure, 1) as *mut crate::array::ArrayHeader; let receiver = js_closure_get_capture_f64(closure, 2); @@ -620,16 +628,25 @@ extern "C" fn consumer_collect_step(closure: *const ClosureHeader, iter_result: 0.0 } -pub(crate) extern "C" fn thunk_consumers_text(_closure: *const ClosureHeader, stream: f64) -> f64 { +pub(crate) extern "C" fn thunk_consumers_text( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + stream: f64, +) -> f64 { consume_stream(ConsumerKind::Text, stream) } -pub(crate) extern "C" fn thunk_consumers_json(_closure: *const ClosureHeader, stream: f64) -> f64 { +pub(crate) extern "C" fn thunk_consumers_json( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + stream: f64, +) -> f64 { consume_stream(ConsumerKind::Json, stream) } pub(crate) extern "C" fn thunk_consumers_buffer( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, stream: f64, ) -> f64 { consume_stream(ConsumerKind::Buffer, stream) @@ -638,15 +655,24 @@ pub(crate) extern "C" fn thunk_consumers_buffer( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_consumers_arrayBuffer( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, stream: f64, ) -> f64 { consume_stream(ConsumerKind::ArrayBuffer, stream) } -pub(crate) extern "C" fn thunk_consumers_bytes(_closure: *const ClosureHeader, stream: f64) -> f64 { +pub(crate) extern "C" fn thunk_consumers_bytes( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + stream: f64, +) -> f64 { consume_stream(ConsumerKind::Bytes, stream) } -pub(crate) extern "C" fn thunk_consumers_blob(_closure: *const ClosureHeader, stream: f64) -> f64 { +pub(crate) extern "C" fn thunk_consumers_blob( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + stream: f64, +) -> f64 { consume_stream(ConsumerKind::Blob, stream) } diff --git a/crates/perry-runtime/src/node_submodules/diagnostics.rs b/crates/perry-runtime/src/node_submodules/diagnostics.rs index f293c558f9..8badd5e8f7 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics.rs @@ -47,7 +47,11 @@ use super::*; // surface as no-op thrower thunks the same way the other submodules do — // real-tracing semantics are a follow-up under #793. -pub(crate) extern "C" fn thunk_diag_noop(_closure: *const ClosureHeader, _arg: f64) -> f64 { +pub(crate) extern "C" fn thunk_diag_noop( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { f64::from_bits(crate::value::JSValue::undefined().bits()) } @@ -631,129 +635,10 @@ pub enum ErrUserProp { Bits(u64), } -unsafe fn error_user_prop_string(value: f64) -> String { - let ptr = crate::value::js_jsvalue_to_string(value); - if ptr.is_null() { - return String::new(); - } - let len = (*ptr).byte_len as usize; - let data = (ptr as *const u8).add(std::mem::size_of::()); - String::from_utf8_lossy(std::slice::from_raw_parts(data, len)).into_owned() -} - -/// Record a user-assigned own property on an `Error` object. `error_ptr` is -/// the `ErrorHeader` pointer (the NaN-box pointer payload). Called from the -/// `GC_TYPE_ERROR` branch of `js_object_set_field_by_name`. -pub fn set_error_user_prop(error_ptr: usize, key: &str, value: f64) { - if error_ptr == 0 { - return; - } - // #6759 phase 1: the property bag now hangs off the error's own metadata - // record instead of a table keyed by its address, so it moves with the - // error, dies with it, and cannot be inherited by a later tenant of a - // recycled address. Insertion order comes free from the bag object's - // `keys_array`. - unsafe { - let Some(bag) = crate::object::cell_expando_ensure(error_ptr) else { - return; - }; - let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); - crate::object::js_object_set_field_by_name(bag, key_ptr, value); - } -} - -/// Look up a user-assigned own property on an `Error` object, materialising it -/// back into a NaN-boxed `f64`. Returns `None` if no such property was set. -/// Called from the `GC_TYPE_ERROR` branch of `js_object_get_field_by_name`. -pub fn error_user_prop(error_ptr: usize, key: &str) -> Option { - if error_ptr == 0 { - return None; - } - unsafe { - let bag = crate::object::cell_expando_get(error_ptr)?; - let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); - // Distinguish "absent" from "present and undefined": a bare get would - // return `undefined` for both, and the caller uses `None` to mean the - // error has no such own property at all. - let key_boxed = f64::from_bits(crate::js_nanbox_string(key_ptr as i64).to_bits()); - if !crate::object::obj_value_has_own_key( - crate::value::js_nanbox_pointer(bag as i64), - key_boxed, - ) { - return None; - } - Some(f64::from_bits( - crate::object::js_object_get_field_by_name(bag, key_ptr).bits(), - )) - } -} - -/// Remove a user-assigned own property from an Error object. Returns true -/// when the property existed (used by `delete err.prop` and data↔accessor -/// descriptor conversions). -pub fn remove_error_user_prop(error_ptr: usize, key: &str) -> bool { - if error_ptr == 0 { - return false; - } - unsafe { - let Some(bag) = crate::object::cell_expando_get(error_ptr) else { - return false; - }; - let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); - let key_boxed = f64::from_bits(crate::js_nanbox_string(key_ptr as i64).to_bits()); - if !crate::object::obj_value_has_own_key( - crate::value::js_nanbox_pointer(bag as i64), - key_boxed, - ) { - return false; - } - crate::object::js_object_delete_field(bag, key_ptr); - true - } -} - -/// Return user-assigned own properties on an Error object as materialized JS -/// values so util.inspect/console formatting can show them. -pub fn error_user_props(error_ptr: usize) -> Vec<(String, f64)> { - if error_ptr == 0 { - return Vec::new(); - } - unsafe { - let Some(bag) = crate::object::cell_expando_get(error_ptr) else { - return Vec::new(); - }; - // The bag is an ordinary object, so its `keys_array` already holds the - // keys in ECMA-262 insertion order — no sort, and no ordering of our - // own to keep in step with node's. - let keys_view = crate::object::object_keys(bag); - let keys = keys_view.arr(); - if keys.is_null() { - return Vec::new(); - } - let len = keys_view.count() as usize; - let mut out = Vec::with_capacity(len); - for i in 0..len { - let key_val = crate::array::js_array_get_f64(keys, i as u32); - // A tombstoned key slot (#9029) reads back as undefined through - // the hole canonicalization (#323); stringifying it would mint a - // phantom "undefined" prop. Undefined is never a legal key. - if key_val.to_bits() == crate::value::TAG_UNDEFINED { - continue; - } - let name_ptr = crate::value::js_jsvalue_to_string(key_val); - if name_ptr.is_null() { - continue; - } - let name = error_user_prop_string(f64::from_bits( - crate::js_nanbox_string(name_ptr as i64).to_bits(), - )); - let value = - f64::from_bits(crate::object::js_object_get_field_by_name(bag, name_ptr).bits()); - out.push((name, value)); - } - out - } -} +mod user_props; +pub use user_props::{ + error_user_prop, error_user_props, remove_error_user_prop, set_error_user_prop, +}; pub(crate) fn throw_invalid_arg() -> ! { let msg = b"The argument is invalid"; @@ -849,7 +734,10 @@ pub(crate) fn catch_js f64>(f: F) -> Result { // #854: diagnostics_channel captured-error helper retained for the subsystem #[allow(dead_code)] -pub(crate) extern "C" fn throw_captured_error(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn throw_captured_error( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let err = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); crate::exception::js_throw(err) } @@ -1236,6 +1124,7 @@ pub(crate) fn run_store_wrapped( pub(crate) extern "C" fn diag_channel_subscribe( closure: *const ClosureHeader, + _this: crate::closure::JsThis, subscriber: f64, ) -> f64 { add_subscriber(method_id(closure), subscriber); @@ -1244,18 +1133,24 @@ pub(crate) extern "C" fn diag_channel_subscribe( pub(crate) extern "C" fn diag_channel_unsubscribe( closure: *const ClosureHeader, + _this: crate::closure::JsThis, subscriber: f64, ) -> f64 { bool_value(remove_subscriber(method_id(closure), subscriber)) } -pub(crate) extern "C" fn diag_channel_publish(closure: *const ClosureHeader, data: f64) -> f64 { +pub(crate) extern "C" fn diag_channel_publish( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, +) -> f64 { publish_channel(method_id(closure), data); undefined() } pub(crate) extern "C" fn diag_channel_bind_store( closure: *const ClosureHeader, + _this: crate::closure::JsThis, store: f64, transform: f64, ) -> f64 { @@ -1298,6 +1193,7 @@ pub(crate) extern "C" fn diag_channel_bind_store( pub(crate) extern "C" fn diag_channel_unbind_store( closure: *const ClosureHeader, + _this: crate::closure::JsThis, store: f64, ) -> f64 { let id = method_id(closure); @@ -1347,7 +1243,10 @@ pub(crate) fn call_store_run(store: f64, context: f64, next: f64) -> f64 { js_closure_call2(closure_ptr(run), context, next) } -pub(crate) extern "C" fn store_next_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn store_next_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_ptr(closure, 0); let data = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); let fn_value = f64::from_bits(js_closure_get_capture_ptr(closure, 2) as u64); @@ -1360,7 +1259,10 @@ pub(crate) extern "C" fn store_next_thunk(closure: *const ClosureHeader) -> f64 run_store_wrapped(id, data, fn_value, this_arg, &args) } -pub(crate) extern "C" fn store_chain_thunk(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn store_chain_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let store = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let context = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); let next = f64::from_bits(js_closure_get_capture_ptr(closure, 2) as u64); @@ -1425,6 +1327,7 @@ fn unbox_arg_array(arr_value: f64) -> Vec { pub(crate) extern "C" fn diag_channel_run_stores( closure: *const ClosureHeader, + _this: crate::closure::JsThis, all_args: f64, ) -> f64 { let id = method_id(closure); @@ -1493,13 +1396,18 @@ pub(crate) extern "C" fn diag_channel_run_stores( suppress_uncaught_drain(|| js_closure_call0(closure_ptr(next_value))) } -pub(crate) extern "C" fn thunk_diag_channel(closure: *const ClosureHeader, name: f64) -> f64 { +pub(crate) extern "C" fn thunk_diag_channel( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + name: f64, +) -> f64 { let _ = closure; channel_obj(ensure_channel(name)) } pub(crate) extern "C" fn thunk_diag_subscribe( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, subscriber: f64, ) -> f64 { @@ -1510,6 +1418,7 @@ pub(crate) extern "C" fn thunk_diag_subscribe( pub(crate) extern "C" fn thunk_diag_unsubscribe( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, subscriber: f64, ) -> f64 { @@ -1519,6 +1428,7 @@ pub(crate) extern "C" fn thunk_diag_unsubscribe( pub(crate) extern "C" fn thunk_diag_has_subscribers( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, ) -> f64 { let key = match channel_key(name) { @@ -1576,7 +1486,11 @@ pub(crate) fn channel_from_object_property(obj_value: f64, prop: &str) -> i64 { }) } -pub(crate) extern "C" fn diag_trace_subscribe(closure: *const ClosureHeader, handlers: f64) -> f64 { +pub(crate) extern "C" fn diag_trace_subscribe( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + handlers: f64, +) -> f64 { let id = method_id(closure); let events = DIAG_TRACES.with(|m| m.borrow().get(&id).map(|t| t.events).unwrap_or([0; 5])); for (idx, name) in ["start", "end", "asyncStart", "asyncEnd", "error"] @@ -1604,6 +1518,7 @@ pub(crate) extern "C" fn diag_trace_subscribe(closure: *const ClosureHeader, han pub(crate) extern "C" fn diag_trace_unsubscribe( closure: *const ClosureHeader, + _this: crate::closure::JsThis, handlers: f64, ) -> f64 { let id = method_id(closure); @@ -1637,6 +1552,7 @@ pub(crate) fn call_fn_value(fn_value: f64, this_arg: f64, args: &[f64]) -> f64 { pub(crate) extern "C" fn diag_trace_sync( closure: *const ClosureHeader, + _this: crate::closure::JsThis, fn_value: f64, context: f64, this_arg: f64, @@ -1683,6 +1599,7 @@ pub(crate) extern "C" fn diag_trace_sync( pub(crate) extern "C" fn diag_trace_promise( closure: *const ClosureHeader, + _this: crate::closure::JsThis, fn_value: f64, context: f64, this_arg: f64, @@ -1762,6 +1679,7 @@ pub(crate) extern "C" fn diag_trace_promise( /// (#3086). `arguments[0]` is `err`, `arguments[1]` is `res`. pub(crate) extern "C" fn diag_trace_wrapped_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, all_args: f64, ) -> f64 { let callback = js_closure_get_capture_f64(closure, 0); @@ -1804,7 +1722,11 @@ fn throw_trace_callback_not_function() -> ! { crate::exception::js_throw(boxed_ptr(err)) } -pub(crate) extern "C" fn diag_trace_callback(closure: *const ClosureHeader, all_args: f64) -> f64 { +pub(crate) extern "C" fn diag_trace_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + all_args: f64, +) -> f64 { // Synthetic-arguments rest array: [fn, position, context, thisArg, ...args] // (#3086). `position` defaults to -1 (the last trailing arg), the callback // lives at `args[position]`, and the wrapped callback is spliced in at that @@ -1902,6 +1824,7 @@ pub(crate) extern "C" fn diag_trace_callback(closure: *const ClosureHeader, all_ pub(crate) extern "C" fn thunk_diag_tracing_channel( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name_or_channels: f64, ) -> f64 { let id = next_diag_id(); diff --git a/crates/perry-runtime/src/node_submodules/diagnostics/user_props.rs b/crates/perry-runtime/src/node_submodules/diagnostics/user_props.rs new file mode 100644 index 0000000000..df69cfe00b --- /dev/null +++ b/crates/perry-runtime/src/node_submodules/diagnostics/user_props.rs @@ -0,0 +1,127 @@ +//! User-assigned own properties on `Error` objects (`err.code = ...`), kept beside the error's diagnostics. + +use super::*; + +unsafe fn error_user_prop_string(value: f64) -> String { + let ptr = crate::value::js_jsvalue_to_string(value); + if ptr.is_null() { + return String::new(); + } + let len = (*ptr).byte_len as usize; + let data = (ptr as *const u8).add(std::mem::size_of::()); + String::from_utf8_lossy(std::slice::from_raw_parts(data, len)).into_owned() +} + +/// Record a user-assigned own property on an `Error` object. `error_ptr` is +/// the `ErrorHeader` pointer (the NaN-box pointer payload). Called from the +/// `GC_TYPE_ERROR` branch of `js_object_set_field_by_name`. +pub fn set_error_user_prop(error_ptr: usize, key: &str, value: f64) { + if error_ptr == 0 { + return; + } + // #6759 phase 1: the property bag now hangs off the error's own metadata + // record instead of a table keyed by its address, so it moves with the + // error, dies with it, and cannot be inherited by a later tenant of a + // recycled address. Insertion order comes free from the bag object's + // `keys_array`. + unsafe { + let Some(bag) = crate::object::cell_expando_ensure(error_ptr) else { + return; + }; + let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); + crate::object::js_object_set_field_by_name(bag, key_ptr, value); + } +} + +/// Look up a user-assigned own property on an `Error` object, materialising it +/// back into a NaN-boxed `f64`. Returns `None` if no such property was set. +/// Called from the `GC_TYPE_ERROR` branch of `js_object_get_field_by_name`. +pub fn error_user_prop(error_ptr: usize, key: &str) -> Option { + if error_ptr == 0 { + return None; + } + unsafe { + let bag = crate::object::cell_expando_get(error_ptr)?; + let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); + // Distinguish "absent" from "present and undefined": a bare get would + // return `undefined` for both, and the caller uses `None` to mean the + // error has no such own property at all. + let key_boxed = f64::from_bits(crate::js_nanbox_string(key_ptr as i64).to_bits()); + if !crate::object::obj_value_has_own_key( + crate::value::js_nanbox_pointer(bag as i64), + key_boxed, + ) { + return None; + } + Some(f64::from_bits( + crate::object::js_object_get_field_by_name(bag, key_ptr).bits(), + )) + } +} + +/// Remove a user-assigned own property from an Error object. Returns true +/// when the property existed (used by `delete err.prop` and data↔accessor +/// descriptor conversions). +pub fn remove_error_user_prop(error_ptr: usize, key: &str) -> bool { + if error_ptr == 0 { + return false; + } + unsafe { + let Some(bag) = crate::object::cell_expando_get(error_ptr) else { + return false; + }; + let key_ptr = js_string_from_bytes(key.as_ptr(), key.len() as u32); + let key_boxed = f64::from_bits(crate::js_nanbox_string(key_ptr as i64).to_bits()); + if !crate::object::obj_value_has_own_key( + crate::value::js_nanbox_pointer(bag as i64), + key_boxed, + ) { + return false; + } + crate::object::js_object_delete_field(bag, key_ptr); + true + } +} + +/// Return user-assigned own properties on an Error object as materialized JS +/// values so util.inspect/console formatting can show them. +pub fn error_user_props(error_ptr: usize) -> Vec<(String, f64)> { + if error_ptr == 0 { + return Vec::new(); + } + unsafe { + let Some(bag) = crate::object::cell_expando_get(error_ptr) else { + return Vec::new(); + }; + // The bag is an ordinary object, so its `keys_array` already holds the + // keys in ECMA-262 insertion order — no sort, and no ordering of our + // own to keep in step with node's. + let keys_view = crate::object::object_keys(bag); + let keys = keys_view.arr(); + if keys.is_null() { + return Vec::new(); + } + let len = keys_view.count() as usize; + let mut out = Vec::with_capacity(len); + for i in 0..len { + let key_val = crate::array::js_array_get_f64(keys, i as u32); + // A tombstoned key slot (#9029) reads back as undefined through + // the hole canonicalization (#323); stringifying it would mint a + // phantom "undefined" prop. Undefined is never a legal key. + if key_val.to_bits() == crate::value::TAG_UNDEFINED { + continue; + } + let name_ptr = crate::value::js_jsvalue_to_string(key_val); + if name_ptr.is_null() { + continue; + } + let name = error_user_prop_string(f64::from_bits( + crate::js_nanbox_string(name_ptr as i64).to_bits(), + )); + let value = + f64::from_bits(crate::object::js_object_get_field_by_name(bag, name_ptr).bits()); + out.push((name, value)); + } + out + } +} diff --git a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs index 38fe92dc6c..9adb95c045 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs @@ -99,7 +99,10 @@ fn install_dispose(obj: *mut ObjectHeader, method: f64) { } } -pub(crate) extern "C" fn diag_store_scope_dispose(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn diag_store_scope_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope_id = js_closure_get_capture_ptr(closure, 0); let handles = DIAG_STORE_SCOPES.with(|m| { let mut m = m.borrow_mut(); @@ -120,6 +123,7 @@ pub(crate) extern "C" fn diag_store_scope_dispose(closure: *const ClosureHeader) pub(crate) extern "C" fn diag_channel_with_store_scope( closure: *const ClosureHeader, + _this: crate::closure::JsThis, data: f64, ) -> f64 { let id = method_id(closure); @@ -208,6 +212,7 @@ pub(crate) fn update_all_bounded_active() { pub(crate) extern "C" fn diag_bounded_subscribe( closure: *const ClosureHeader, + _this: crate::closure::JsThis, handlers: f64, ) -> f64 { let events = bounded_events(method_id(closure)); @@ -227,6 +232,7 @@ pub(crate) extern "C" fn diag_bounded_subscribe( pub(crate) extern "C" fn diag_bounded_unsubscribe( closure: *const ClosureHeader, + _this: crate::closure::JsThis, handlers: f64, ) -> f64 { let events = bounded_events(method_id(closure)); @@ -240,7 +246,11 @@ pub(crate) extern "C" fn diag_bounded_unsubscribe( bool_value(ok) } -pub(crate) extern "C" fn diag_bounded_run(closure: *const ClosureHeader, all_args: f64) -> f64 { +pub(crate) extern "C" fn diag_bounded_run( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + all_args: f64, +) -> f64 { let all = unbox_arg_array(all_args); let undef = undefined(); let context = all.first().copied().unwrap_or(undef); @@ -268,7 +278,10 @@ pub(crate) extern "C" fn diag_bounded_run(closure: *const ClosureHeader, all_arg } } -pub(crate) extern "C" fn diag_bounded_scope_dispose(closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn diag_bounded_scope_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if js_closure_get_capture_ptr(closure, 2) != 0 { return undefined(); } @@ -281,6 +294,7 @@ pub(crate) extern "C" fn diag_bounded_scope_dispose(closure: *const ClosureHeade pub(crate) extern "C" fn diag_bounded_with_scope( closure: *const ClosureHeader, + _this: crate::closure::JsThis, context: f64, ) -> f64 { let events = bounded_events(method_id(closure)); @@ -306,6 +320,7 @@ fn bounded_run_method_closure(id: i64) -> f64 { pub(crate) extern "C" fn thunk_diag_bounded_channel( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name_or_channels: f64, ) -> f64 { if is_symbol_value(name_or_channels) { diff --git a/crates/perry-runtime/src/node_submodules/fs_promises.rs b/crates/perry-runtime/src/node_submodules/fs_promises.rs index ccf79ce01f..755931a895 100644 --- a/crates/perry-runtime/src/node_submodules/fs_promises.rs +++ b/crates/perry-runtime/src/node_submodules/fs_promises.rs @@ -76,32 +76,60 @@ fn promise_from_result_value(call: impl FnOnce() -> Result) -> f64 { #[no_mangle] pub extern "C" fn js_fs_promises_read_file(path: f64, options: f64) -> f64 { - thunk_fs_promises_readFile(std::ptr::null(), path, options) + thunk_fs_promises_readFile( + std::ptr::null(), + crate::closure::JsThis::current(), + path, + options, + ) } #[no_mangle] pub extern "C" fn js_fs_promises_write_file(path: f64, data: f64, options: f64) -> f64 { - thunk_fs_promises_writeFile(std::ptr::null(), path, data, options) + thunk_fs_promises_writeFile( + std::ptr::null(), + crate::closure::JsThis::current(), + path, + data, + options, + ) } #[no_mangle] pub extern "C" fn js_fs_promises_append_file(path: f64, data: f64, options: f64) -> f64 { - thunk_fs_promises_appendFile(std::ptr::null(), path, data, options) + thunk_fs_promises_appendFile( + std::ptr::null(), + crate::closure::JsThis::current(), + path, + data, + options, + ) } #[no_mangle] pub extern "C" fn js_fs_promises_mkdir(path: f64, options: f64) -> f64 { - thunk_fs_promises_mkdir(std::ptr::null(), path, options) + thunk_fs_promises_mkdir( + std::ptr::null(), + crate::closure::JsThis::current(), + path, + options, + ) } #[no_mangle] pub extern "C" fn js_fs_promises_rmdir(path: f64, options: f64) -> f64 { - thunk_fs_promises_rmdir(std::ptr::null(), path, options) + thunk_fs_promises_rmdir( + std::ptr::null(), + crate::closure::JsThis::current(), + path, + options, + ) } #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_fs_promises_readFile( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, encoding: f64, ) -> f64 { @@ -114,6 +142,7 @@ pub(crate) extern "C" fn thunk_fs_promises_readFile( pub(crate) extern "C" fn thunk_fs_promises_open( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, flags: f64, _mode: f64, @@ -143,6 +172,7 @@ pub(crate) extern "C" fn thunk_fs_promises_open( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_fs_promises_writeFile( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, data: f64, options: f64, @@ -160,6 +190,7 @@ pub(crate) extern "C" fn thunk_fs_promises_writeFile( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_fs_promises_appendFile( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, data: f64, options: f64, @@ -175,6 +206,7 @@ pub(crate) extern "C" fn thunk_fs_promises_appendFile( pub(crate) extern "C" fn thunk_fs_promises_chmod( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, mode: f64, ) -> f64 { @@ -183,6 +215,7 @@ pub(crate) extern "C" fn thunk_fs_promises_chmod( pub(crate) extern "C" fn thunk_fs_promises_chown( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, uid: f64, gid: f64, @@ -192,6 +225,7 @@ pub(crate) extern "C" fn thunk_fs_promises_chown( pub(crate) extern "C" fn thunk_fs_promises_lchown( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, uid: f64, gid: f64, @@ -203,6 +237,7 @@ pub(crate) extern "C" fn thunk_fs_promises_lchown( pub(crate) extern "C" fn thunk_fs_promises_lchmod( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, mode: f64, ) -> f64 { @@ -219,6 +254,7 @@ pub(crate) extern "C" fn thunk_fs_promises_lchmod( pub(crate) extern "C" fn thunk_fs_promises_mkdir( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -229,6 +265,7 @@ pub(crate) extern "C" fn thunk_fs_promises_mkdir( pub(crate) extern "C" fn thunk_fs_promises_readdir( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -240,6 +277,7 @@ pub(crate) extern "C" fn thunk_fs_promises_readdir( pub(crate) extern "C" fn thunk_fs_promises_stat( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -248,6 +286,7 @@ pub(crate) extern "C" fn thunk_fs_promises_stat( pub(crate) extern "C" fn thunk_fs_promises_statfs( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -256,6 +295,7 @@ pub(crate) extern "C" fn thunk_fs_promises_statfs( pub(crate) extern "C" fn thunk_fs_promises_lstat( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -264,6 +304,7 @@ pub(crate) extern "C" fn thunk_fs_promises_lstat( pub(crate) extern "C" fn thunk_fs_promises_rm( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -272,6 +313,7 @@ pub(crate) extern "C" fn thunk_fs_promises_rm( pub(crate) extern "C" fn thunk_fs_promises_rmdir( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -280,6 +322,7 @@ pub(crate) extern "C" fn thunk_fs_promises_rmdir( pub(crate) extern "C" fn thunk_fs_promises_unlink( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, ) -> f64 { // #9574: writeFile/appendFile are parked on the timer queue, while most @@ -297,6 +340,7 @@ pub(crate) extern "C" fn thunk_fs_promises_unlink( pub(crate) extern "C" fn thunk_fs_promises_rename( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, from: f64, to: f64, ) -> f64 { @@ -306,6 +350,7 @@ pub(crate) extern "C" fn thunk_fs_promises_rename( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_fs_promises_copyFile( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, from: f64, to: f64, flags: f64, @@ -315,6 +360,7 @@ pub(crate) extern "C" fn thunk_fs_promises_copyFile( pub(crate) extern "C" fn thunk_fs_promises_cp( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, from: f64, to: f64, options: f64, @@ -324,6 +370,7 @@ pub(crate) extern "C" fn thunk_fs_promises_cp( pub(crate) extern "C" fn thunk_fs_promises_truncate( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, len: f64, ) -> f64 { @@ -332,6 +379,7 @@ pub(crate) extern "C" fn thunk_fs_promises_truncate( pub(crate) extern "C" fn thunk_fs_promises_utimes( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, atime: f64, mtime: f64, @@ -343,6 +391,7 @@ pub(crate) extern "C" fn thunk_fs_promises_utimes( pub(crate) extern "C" fn thunk_fs_promises_lutimes( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, atime: f64, mtime: f64, @@ -354,6 +403,7 @@ pub(crate) extern "C" fn thunk_fs_promises_lutimes( pub(crate) extern "C" fn thunk_fs_promises_link( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, from: f64, to: f64, ) -> f64 { @@ -362,6 +412,7 @@ pub(crate) extern "C" fn thunk_fs_promises_link( pub(crate) extern "C" fn thunk_fs_promises_symlink( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, path: f64, _type: f64, @@ -371,6 +422,7 @@ pub(crate) extern "C" fn thunk_fs_promises_symlink( pub(crate) extern "C" fn thunk_fs_promises_readlink( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -379,6 +431,7 @@ pub(crate) extern "C" fn thunk_fs_promises_readlink( pub(crate) extern "C" fn thunk_fs_promises_realpath( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -387,6 +440,7 @@ pub(crate) extern "C" fn thunk_fs_promises_realpath( pub(crate) extern "C" fn thunk_fs_promises_mkdtemp( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, prefix: f64, options: f64, ) -> f64 { @@ -396,6 +450,7 @@ pub(crate) extern "C" fn thunk_fs_promises_mkdtemp( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_fs_promises_mkdtempDisposable( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, prefix: f64, options: f64, ) -> f64 { @@ -404,6 +459,7 @@ pub(crate) extern "C" fn thunk_fs_promises_mkdtempDisposable( pub(crate) extern "C" fn thunk_fs_promises_opendir( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, ) -> f64 { match crate::fs::js_fs_opendir_value_with_path(path) { @@ -420,6 +476,7 @@ pub(crate) extern "C" fn thunk_fs_promises_opendir( pub(crate) extern "C" fn thunk_fs_promises_glob( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, pattern: f64, options: f64, ) -> f64 { @@ -428,6 +485,7 @@ pub(crate) extern "C" fn thunk_fs_promises_glob( pub(crate) extern "C" fn thunk_fs_promises_watch( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, options: f64, ) -> f64 { @@ -436,6 +494,7 @@ pub(crate) extern "C" fn thunk_fs_promises_watch( pub(crate) extern "C" fn thunk_fs_promises_access( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, path: f64, mode: f64, ) -> f64 { @@ -672,7 +731,10 @@ fn abort_pending_question(interface: f64) { crate::promise::js_promise_reject(promise, crate::url::js_abort_error_value()); } -extern "C" fn readline_promises_abort_question(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_promises_abort_question( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { abort_pending_question(this_value(closure)); undefined() } @@ -697,12 +759,19 @@ fn append_interface_input(interface: f64, chunk: f64) { set_value_field(interface, RL_IF_PENDING_TEXT, boxed_str(pending.as_bytes())); } -extern "C" fn readline_promises_input_data(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn readline_promises_input_data( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { append_interface_input(this_value(closure), chunk); undefined() } -extern "C" fn readline_promises_input_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_promises_input_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let interface = this_value(closure); cleanup_question_abort_listener(interface); set_value_field(interface, RL_IF_CLOSED, bool_value(true)); @@ -730,7 +799,10 @@ fn attach_interface_input(interface: f64, input: f64) { let _ = crate::node_stream::js_node_stream_method_on(raw, boxed_str(b"close"), close_value); } -extern "C" fn readline_promises_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_promises_close( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let interface = this_value(closure); cleanup_question_abort_listener(interface); set_value_field(interface, RL_IF_PENDING_PROMISE, undefined()); @@ -740,6 +812,7 @@ extern "C" fn readline_promises_close(closure: *const ClosureHeader) -> f64 { extern "C" fn readline_promises_question( closure: *const ClosureHeader, + _this: crate::closure::JsThis, query: f64, options: f64, ) -> f64 { @@ -814,6 +887,7 @@ fn readline_promises_create_interface(opts: f64) -> f64 { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_readline_createInterface( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, opts: f64, ) -> f64 { readline_promises_create_interface(opts) @@ -822,6 +896,7 @@ pub(crate) extern "C" fn thunk_readline_createInterface( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_readline_Interface( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, opts: f64, ) -> f64 { readline_promises_create_interface(opts) @@ -856,7 +931,10 @@ fn flush_readline_actions(value: f64) { } } -extern "C" fn readline_auto_commit_callback(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_auto_commit_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { flush_readline_actions(this_value(closure)); undefined() } @@ -886,7 +964,11 @@ fn append_readline_action(value: f64, sequence: String) { schedule_readline_auto_commit(value); } -extern "C" fn readline_action_clear_line(closure: *const ClosureHeader, dir: f64) -> f64 { +extern "C" fn readline_action_clear_line( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + dir: f64, +) -> f64 { let value = this_value(closure); let mode = match number_to_i32(dir) { 1 => 0, @@ -897,13 +979,21 @@ extern "C" fn readline_action_clear_line(closure: *const ClosureHeader, dir: f64 value } -extern "C" fn readline_action_clear_screen_down(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_action_clear_screen_down( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let value = this_value(closure); append_readline_action(value, "\x1b[0J".to_string()); value } -extern "C" fn readline_action_cursor_to(closure: *const ClosureHeader, x: f64, y: f64) -> f64 { +extern "C" fn readline_action_cursor_to( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + x: f64, + y: f64, +) -> f64 { let value = this_value(closure); let col = number_to_i32(x).saturating_add(1); let sequence = if is_null_or_undefined(y) { @@ -916,7 +1006,12 @@ extern "C" fn readline_action_cursor_to(closure: *const ClosureHeader, x: f64, y value } -extern "C" fn readline_action_move_cursor(closure: *const ClosureHeader, dx: f64, dy: f64) -> f64 { +extern "C" fn readline_action_move_cursor( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + dx: f64, + dy: f64, +) -> f64 { let value = this_value(closure); let dx = number_to_i32(dx); let dy = number_to_i32(dy); @@ -935,12 +1030,18 @@ extern "C" fn readline_action_move_cursor(closure: *const ClosureHeader, dx: f64 value } -extern "C" fn readline_action_commit(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_action_commit( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { flush_readline_actions(this_value(closure)); promise_value(null_value()) } -extern "C" fn readline_action_rollback(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_action_rollback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let value = this_value(closure); set_value_field(value, RL_ACTION_QUEUE, boxed_str(b"")); set_value_field(value, RL_ACTION_AUTO_PENDING, bool_value(false)); @@ -995,6 +1096,7 @@ pub extern "C" fn js_readline_promises_readline_new(output: f64, options: f64) - #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_readline_Readline( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, output: f64, options: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/node_submodules/hono_jsx.rs b/crates/perry-runtime/src/node_submodules/hono_jsx.rs index 20c52a96cd..e524aad3f2 100644 --- a/crates/perry-runtime/src/node_submodules/hono_jsx.rs +++ b/crates/perry-runtime/src/node_submodules/hono_jsx.rs @@ -30,6 +30,7 @@ fn fragment_marker_f64() -> f64 { /// `jsx(type, props)` — forward to the built-in renderer. pub(crate) extern "C" fn thunk_hono_jsx( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, type_arg: f64, props: f64, ) -> f64 { @@ -39,6 +40,7 @@ pub(crate) extern "C" fn thunk_hono_jsx( /// `jsxs(type, props)` — multi-child shape, same dispatch. pub(crate) extern "C" fn thunk_hono_jsxs( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, type_arg: f64, props: f64, ) -> f64 { @@ -48,14 +50,22 @@ pub(crate) extern "C" fn thunk_hono_jsxs( /// `Fragment` — when used as a component (`jsx(Fragment, { children })`) it /// renders its children with no wrapping element. Reuse `js_jsx`'s Fragment /// path by passing the `"__Fragment"` marker tag. -pub(crate) extern "C" fn thunk_hono_fragment(_closure: *const ClosureHeader, props: f64) -> f64 { +pub(crate) extern "C" fn thunk_hono_fragment( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + props: f64, +) -> f64 { crate::jsx::js_jsx(fragment_marker_f64(), props) } /// `Suspense` — Perry renders synchronously (server-side), so a Suspense /// boundary just renders its children (the fallback is never needed because /// there is no streaming-suspension point). Same shape as Fragment. -pub(crate) extern "C" fn thunk_hono_suspense(_closure: *const ClosureHeader, props: f64) -> f64 { +pub(crate) extern "C" fn thunk_hono_suspense( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + props: f64, +) -> f64 { crate::jsx::js_jsx(fragment_marker_f64(), props) } @@ -63,7 +73,11 @@ pub(crate) extern "C" fn thunk_hono_suspense(_closure: *const ClosureHeader, pro /// (`js_jsx` boxes nodes itself), so this is an exposed stub returning the /// props/value it was handed. Its sole purpose is to make the named export /// resolve with `typeof === "function"`. -pub(crate) extern "C" fn thunk_hono_jsxnode(_closure: *const ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn thunk_hono_jsxnode( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { arg } @@ -88,6 +102,7 @@ pub unsafe extern "C" fn js_register_jsx_render_stream(f: RenderStreamFn) { /// when no stream backend is linked. pub(crate) extern "C" fn thunk_hono_render_to_readable_stream( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, node: f64, _options: f64, ) -> f64 { @@ -112,6 +127,10 @@ pub(crate) extern "C" fn thunk_hono_render_to_readable_stream( /// `undefined` helper for any export we deliberately don't back. #[allow(dead_code)] -pub(crate) extern "C" fn thunk_hono_undefined(_closure: *const ClosureHeader, _arg: f64) -> f64 { +pub(crate) extern "C" fn thunk_hono_undefined( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 09abf6d87a..7e4e9bbfc5 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -96,6 +96,7 @@ macro_rules! thunk { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { let msg: &'static str = $msg; @@ -197,6 +198,7 @@ thunk!( extern "C" fn thunk_vm_create_context( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, sandbox: f64, options: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/node_submodules/stream_promises.rs b/crates/perry-runtime/src/node_submodules/stream_promises.rs index 1b6d5e1120..9e287140f9 100644 --- a/crates/perry-runtime/src/node_submodules/stream_promises.rs +++ b/crates/perry-runtime/src/node_submodules/stream_promises.rs @@ -176,7 +176,10 @@ pub(crate) fn signal_reason(_signal: f64) -> f64 { abort_error_value() } -extern "C" fn stream_promises_abort_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_promises_abort_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let promise_value = js_closure_get_capture_f64(closure, 0); let signal = js_closure_get_capture_f64(closure, 1); let promise = @@ -216,6 +219,7 @@ fn event_value(name: &[u8]) -> f64 { extern "C" fn stream_promises_finished_error_listener( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, ) -> f64 { let state = js_closure_get_capture_ptr(closure, 0) as *mut ObjectHeader; @@ -223,7 +227,10 @@ extern "C" fn stream_promises_finished_error_listener( undefined_value() } -extern "C" fn stream_promises_finished_side_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_promises_finished_side_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let state = js_closure_get_capture_ptr(closure, 0) as *mut ObjectHeader; let readable_side = js_closure_get_capture_f64(closure, 1).to_bits() == crate::value::TAG_TRUE; if readable_side { @@ -235,7 +242,10 @@ extern "C" fn stream_promises_finished_side_listener(closure: *const ClosureHead undefined_value() } -extern "C" fn stream_promises_finished_close_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_promises_finished_close_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let state = js_closure_get_capture_ptr(closure, 0) as *mut ObjectHeader; if finished_state_bool(state, b"settled") { return undefined_value(); @@ -537,6 +547,7 @@ fn validate_pipeline_promise_args(source: f64, destination: f64) -> Option extern "C" fn stream_promises_pipeline_callback( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, value: f64, ) -> f64 { @@ -562,6 +573,7 @@ fn catch_stream_promises_throw(call: impl FnOnce()) -> Result<(), f64> { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_streamP_pipeline( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, source: f64, destination: f64, options_or_rest: f64, @@ -607,6 +619,7 @@ pub(crate) extern "C" fn thunk_streamP_pipeline( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_streamP_finished( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, stream: f64, options: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index 15acb0c498..bd54f0903e 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -218,13 +218,21 @@ fn assert_mock_target_method(value: f64) { } } -extern "C" fn mock_timers_enable(_closure: *const ClosureHeader, options: f64) -> f64 { +extern "C" fn mock_timers_enable( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + options: f64, +) -> f64 { let (apis, now) = parse_mock_timer_options(options); crate::timer::js_mock_timers_enable(apis, now); undefined_value() } -extern "C" fn mock_timers_tick(_closure: *const ClosureHeader, ms: f64) -> f64 { +extern "C" fn mock_timers_tick( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ms: f64, +) -> f64 { let delay = if is_undefined_value(ms) { 1.0 } else { @@ -234,18 +242,28 @@ extern "C" fn mock_timers_tick(_closure: *const ClosureHeader, ms: f64) -> f64 { undefined_value() } -extern "C" fn mock_timers_run_all(_closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_timers_run_all( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::timer::js_mock_timers_run_all(); undefined_value() } -extern "C" fn mock_timers_set_time(_closure: *const ClosureHeader, ms: f64) -> f64 { +extern "C" fn mock_timers_set_time( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ms: f64, +) -> f64 { let time = validate_mock_timer_number("time", ms, false); crate::timer::js_mock_timers_set_time(time); undefined_value() } -extern "C" fn mock_timers_reset(_closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_timers_reset( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::timer::js_mock_timers_reset(); undefined_value() } @@ -775,7 +793,11 @@ fn record_mock_call(id: i64, args_value: f64, this_value: f64, result: f64, erro #[path = "test_metadata_unit_tests.rs"] mod metadata_tests; -extern "C" fn mock_function_invoke(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn mock_function_invoke( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let id = closure_id(closure); let args = array_values(rest).unwrap_or_default(); let (implementation, is_symbol_method) = MOCK_STATES.with(|states| { @@ -854,7 +876,10 @@ extern "C" fn mock_function_invoke(closure: *const ClosureHeader, rest: f64) -> } } -extern "C" fn mock_context_call_count(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_context_call_count( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); MOCK_STATES.with(|states| { states @@ -872,7 +897,10 @@ extern "C" fn mock_context_call_count(closure: *const ClosureHeader) -> f64 { }) } -extern "C" fn mock_context_reset_calls(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_context_reset_calls( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); MOCK_STATES.with(|states| { if let Some(state) = states.borrow_mut().iter_mut().find(|state| state.id == id) { @@ -884,6 +912,7 @@ extern "C" fn mock_context_reset_calls(closure: *const ClosureHeader) -> f64 { extern "C" fn mock_context_mock_implementation( closure: *const ClosureHeader, + _this: crate::closure::JsThis, implementation: f64, ) -> f64 { assert_callable_arg("implementation", implementation); @@ -898,6 +927,7 @@ extern "C" fn mock_context_mock_implementation( extern "C" fn mock_context_mock_implementation_once( closure: *const ClosureHeader, + _this: crate::closure::JsThis, implementation: f64, on_call: f64, ) -> f64 { @@ -929,13 +959,17 @@ extern "C" fn mock_context_mock_implementation_once( undefined_value() } -extern "C" fn mock_context_restore(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_context_restore( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { restore_mock_state(closure_id(closure)); undefined_value() } extern "C" fn mock_fn_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, original: f64, implementation_or_options: f64, options: f64, @@ -970,6 +1004,7 @@ extern "C" fn mock_fn_thunk( extern "C" fn mock_method_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, property: f64, implementation: f64, @@ -1100,6 +1135,7 @@ fn create_getter_mock(target: f64, property: f64, implementation: f64, times: Op extern "C" fn mock_getter_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, property: f64, implementation: f64, @@ -1167,6 +1203,7 @@ fn create_setter_mock(target: f64, property: f64, implementation: f64, times: Op extern "C" fn mock_setter_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, property: f64, implementation: f64, @@ -1190,7 +1227,10 @@ extern "C" fn mock_setter_thunk( ) } -extern "C" fn mock_reset_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_reset_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { restore_tracked_mocks(); MOCK_STATES.with(|states| { for state in states.borrow_mut().iter_mut() { @@ -1216,7 +1256,10 @@ fn restore_tracked_mocks() { property_mock::restore_all(); } -extern "C" fn mock_restore_all_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_restore_all_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { restore_tracked_mocks(); undefined_value() } @@ -1301,14 +1344,22 @@ fn mock_object_value() -> f64 { }) } -extern "C" fn test_context_diagnostic(_closure: *const ClosureHeader, message: f64) -> f64 { +extern "C" fn test_context_diagnostic( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + message: f64, +) -> f64 { let message = value_to_string(message).unwrap_or_else(|| crate::builtins::format_jsvalue(message, 0)); CURRENT_DIAGNOSTICS.with(|diagnostics| diagnostics.borrow_mut().push(message)); undefined_value() } -extern "C" fn test_context_plan(_closure: *const ClosureHeader, expected: f64) -> f64 { +extern "C" fn test_context_plan( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + expected: f64, +) -> f64 { let n = crate::builtins::js_number_coerce(expected); if !n.is_finite() || n < 0.0 { let message = format!( @@ -1321,7 +1372,11 @@ extern "C" fn test_context_plan(_closure: *const ClosureHeader, expected: f64) - undefined_value() } -extern "C" fn test_context_skip(_closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn test_context_skip( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { CURRENT_TEST_OVERRIDE.with(|slot| slot.set(TEST_OVERRIDE_SKIP)); if let Some(reason) = value_to_string(reason) { CURRENT_DIAGNOSTICS @@ -1330,7 +1385,11 @@ extern "C" fn test_context_skip(_closure: *const ClosureHeader, reason: f64) -> undefined_value() } -extern "C" fn test_context_todo(_closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn test_context_todo( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { CURRENT_TEST_OVERRIDE.with(|slot| slot.set(TEST_OVERRIDE_TODO)); if let Some(reason) = value_to_string(reason) { CURRENT_DIAGNOSTICS @@ -1347,6 +1406,7 @@ macro_rules! test_context_assertion3 { ($name:ident, $assertion:path) => { extern "C" fn $name( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, actual: f64, expected: f64, message: f64, @@ -1399,6 +1459,7 @@ test_context_assertion3!(test_context_assert_throws, crate::object::js_assert_th extern "C" fn test_context_assert_ok( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, message: f64, ) -> f64 { @@ -1406,12 +1467,20 @@ extern "C" fn test_context_assert_ok( crate::object::js_assert_ok(value, message) } -extern "C" fn test_context_assert_fail(_closure: *const ClosureHeader, message: f64) -> f64 { +extern "C" fn test_context_assert_fail( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + message: f64, +) -> f64 { record_test_context_assertion(); crate::object::js_assert_fail(message) } -extern "C" fn test_context_assert_if_error(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn test_context_assert_if_error( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { record_test_context_assertion(); crate::object::js_assert_if_error(value) } @@ -1530,6 +1599,7 @@ pub extern "C" fn js_node_test_mock_fn( ) -> f64 { mock_fn_thunk( std::ptr::null(), + crate::closure::JsThis::current(), original, implementation_or_options, options, @@ -1543,7 +1613,14 @@ pub extern "C" fn js_node_test_mock_method( implementation: f64, options: f64, ) -> f64 { - mock_method_thunk(std::ptr::null(), target, property, implementation, options) + mock_method_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + target, + property, + implementation, + options, + ) } #[no_mangle] @@ -1553,7 +1630,14 @@ pub extern "C" fn js_node_test_mock_getter( implementation: f64, options: f64, ) -> f64 { - mock_getter_thunk(std::ptr::null(), target, property, implementation, options) + mock_getter_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + target, + property, + implementation, + options, + ) } #[no_mangle] @@ -1563,7 +1647,14 @@ pub extern "C" fn js_node_test_mock_setter( implementation: f64, options: f64, ) -> f64 { - mock_setter_thunk(std::ptr::null(), target, property, implementation, options) + mock_setter_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + target, + property, + implementation, + options, + ) } #[no_mangle] @@ -1583,47 +1674,55 @@ pub extern "C" fn js_node_test_mock_property_with_presence( #[no_mangle] pub extern "C" fn js_node_test_mock_reset() -> f64 { - mock_reset_thunk(std::ptr::null()) + mock_reset_thunk(std::ptr::null(), crate::closure::JsThis::current()) } #[no_mangle] pub extern "C" fn js_node_test_mock_restore_all() -> f64 { - mock_restore_all_thunk(std::ptr::null()) + mock_restore_all_thunk(std::ptr::null(), crate::closure::JsThis::current()) } #[no_mangle] pub extern "C" fn js_node_test_snapshot_set_default_serializers(serializers: f64) -> f64 { - snapshot_set_default_serializers(std::ptr::null(), serializers) + snapshot_set_default_serializers( + std::ptr::null(), + crate::closure::JsThis::current(), + serializers, + ) } #[no_mangle] pub extern "C" fn js_node_test_snapshot_set_resolve_snapshot_path(resolver: f64) -> f64 { - snapshot_set_resolve_snapshot_path(std::ptr::null(), resolver) + snapshot_set_resolve_snapshot_path( + std::ptr::null(), + crate::closure::JsThis::current(), + resolver, + ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_enable(options: f64) -> f64 { - mock_timers_enable(std::ptr::null(), options) + mock_timers_enable(std::ptr::null(), crate::closure::JsThis::current(), options) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_tick(ms: f64) -> f64 { - mock_timers_tick(std::ptr::null(), ms) + mock_timers_tick(std::ptr::null(), crate::closure::JsThis::current(), ms) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_run_all() -> f64 { - mock_timers_run_all(std::ptr::null()) + mock_timers_run_all(std::ptr::null(), crate::closure::JsThis::current()) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_set_time(ms: f64) -> f64 { - mock_timers_set_time(std::ptr::null(), ms) + mock_timers_set_time(std::ptr::null(), crate::closure::JsThis::current(), ms) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_reset() -> f64 { - mock_timers_reset(std::ptr::null()) + mock_timers_reset(std::ptr::null(), crate::closure::JsThis::current()) } pub(crate) fn decorate_test_export( 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 72d3699d0b..b8ed9b1290 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 @@ -1,6 +1,11 @@ use super::*; -extern "C" fn original_two_args(_closure: *const ClosureHeader, _first: f64, _second: f64) -> f64 { +extern "C" fn original_two_args( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _first: f64, + _second: f64, +) -> f64 { undefined_value() } 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 b87c52cb40..19b27d08e4 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 @@ -5,7 +5,10 @@ thread_local! { static REENTRANT_ACTIVE: Cell = const { Cell::new(false) }; } -extern "C" fn reentrant_implementation(_closure: *const ClosureHeader) -> f64 { +extern "C" fn reentrant_implementation( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let recurse = REENTRANT_ACTIVE.with(|active| !active.replace(true)); if recurse { let mock = REENTRANT_MOCK.with(Cell::get); @@ -15,11 +18,11 @@ extern "C" fn reentrant_implementation(_closure: *const ClosureHeader) -> f64 { 10.0 } -extern "C" fn return_twenty(_closure: *const ClosureHeader) -> f64 { +extern "C" fn return_twenty(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 20.0 } -extern "C" fn return_thirty(_closure: *const ClosureHeader) -> f64 { +extern "C" fn return_thirty(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 30.0 } diff --git a/crates/perry-runtime/src/node_submodules/test_property.rs b/crates/perry-runtime/src/node_submodules/test_property.rs index 6100c0beb6..f1784d8e0a 100644 --- a/crates/perry-runtime/src/node_submodules/test_property.rs +++ b/crates/perry-runtime/src/node_submodules/test_property.rs @@ -286,7 +286,10 @@ fn record_property_access(id: i64, access_type: &str, fallback: f64) -> f64 { value.get_nanbox_f64() } -extern "C" fn mock_property_get(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_property_get( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); let value = PROPERTY_STATES.with(|states| { states @@ -299,7 +302,11 @@ extern "C" fn mock_property_get(closure: *const ClosureHeader) -> f64 { record_property_access(id, "get", value) } -extern "C" fn mock_property_set(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn mock_property_set( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { let id = closure_id(closure); let (writable, property) = PROPERTY_STATES.with(|states| { states @@ -322,7 +329,10 @@ extern "C" fn mock_property_set(closure: *const ClosureHeader, value: f64) -> f6 undefined_value() } -extern "C" fn mock_property_access_count(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_property_access_count( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); PROPERTY_STATES.with(|states| { states @@ -334,7 +344,10 @@ extern "C" fn mock_property_access_count(closure: *const ClosureHeader) -> f64 { }) } -extern "C" fn mock_property_accesses(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_property_accesses( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); let accesses = PROPERTY_STATES.with(|states| { states @@ -354,7 +367,10 @@ extern "C" fn mock_property_accesses(closure: *const ClosureHeader) -> f64 { boxed_ptr(crate::array::js_array_slice(ptr, 0, len as i32)) } -extern "C" fn mock_property_reset_accesses(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_property_reset_accesses( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = closure_id(closure); let accesses = boxed_ptr(crate::array::js_array_alloc(0)); PROPERTY_STATES.with(|states| { @@ -366,12 +382,17 @@ extern "C" fn mock_property_reset_accesses(closure: *const ClosureHeader) -> f64 undefined_value() } -extern "C" fn mock_property_implementation(closure: *const ClosureHeader, value: f64) -> f64 { - mock_property_set(closure, value) +extern "C" fn mock_property_implementation( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { + mock_property_set(closure, crate::closure::JsThis::current(), value) } extern "C" fn mock_property_implementation_once( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, rest: f64, ) -> f64 { @@ -407,13 +428,17 @@ extern "C" fn mock_property_implementation_once( undefined_value() } -extern "C" fn mock_property_restore(closure: *const ClosureHeader) -> f64 { +extern "C" fn mock_property_restore( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { restore_property_state(closure_id(closure)); undefined_value() } extern "C" fn mock_property_proxy_get( closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, receiver: f64, @@ -434,6 +459,7 @@ extern "C" fn mock_property_proxy_get( extern "C" fn tracker_property_thunk( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, target: f64, property: f64, rest: f64, diff --git a/crates/perry-runtime/src/node_submodules/test_reporters.rs b/crates/perry-runtime/src/node_submodules/test_reporters.rs index 1d647dbd73..0c44febc32 100644 --- a/crates/perry-runtime/src/node_submodules/test_reporters.rs +++ b/crates/perry-runtime/src/node_submodules/test_reporters.rs @@ -15,23 +15,43 @@ fn reporter_with_kind(kind: i32, source: f64) -> f64 { readable_from_text(output) } -pub(crate) extern "C" fn thunk_reporter_spec(_closure: *const ClosureHeader, source: f64) -> f64 { +pub(crate) extern "C" fn thunk_reporter_spec( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { reporter_with_kind(REPORTER_SPEC, source) } -pub(crate) extern "C" fn thunk_reporter_tap(_closure: *const ClosureHeader, source: f64) -> f64 { +pub(crate) extern "C" fn thunk_reporter_tap( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { reporter_with_kind(REPORTER_TAP, source) } -pub(crate) extern "C" fn thunk_reporter_dot(_closure: *const ClosureHeader, source: f64) -> f64 { +pub(crate) extern "C" fn thunk_reporter_dot( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { reporter_with_kind(REPORTER_DOT, source) } -pub(crate) extern "C" fn thunk_reporter_junit(_closure: *const ClosureHeader, source: f64) -> f64 { +pub(crate) extern "C" fn thunk_reporter_junit( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { reporter_with_kind(REPORTER_JUNIT, source) } -pub(crate) extern "C" fn thunk_reporter_lcov(_closure: *const ClosureHeader, source: f64) -> f64 { +pub(crate) extern "C" fn thunk_reporter_lcov( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + source: f64, +) -> f64 { reporter_with_kind(REPORTER_LCOV, source) } @@ -49,6 +69,7 @@ fn reporter_transform(kind: i32) -> f64 { extern "C" fn reporter_transform_chunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, _encoding: f64, callback: f64, diff --git a/crates/perry-runtime/src/node_submodules/test_runner.rs b/crates/perry-runtime/src/node_submodules/test_runner.rs index a1c4a17821..ffe84ce31d 100644 --- a/crates/perry-runtime/src/node_submodules/test_runner.rs +++ b/crates/perry-runtime/src/node_submodules/test_runner.rs @@ -361,7 +361,11 @@ fn take_directive_reason( } } -extern "C" fn test_done(_closure: *const ClosureHeader, error: f64) -> f64 { +extern "C" fn test_done( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + error: f64, +) -> f64 { if !is_undefined_value(error) && !JSValue::from_bits(error.to_bits()).is_null() { crate::exception::js_throw(error); } @@ -722,7 +726,10 @@ fn print_summary(stats: &Stats) { println!("ℹ duration_ms 0"); } -extern "C" fn test_runner_task(_closure: *const ClosureHeader) -> f64 { +extern "C" fn test_runner_task( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let root = TEST_RUNNER.with(|runner| { let mut runner = runner.borrow_mut(); runner.running = true; @@ -766,6 +773,7 @@ extern "C" fn test_runner_task(_closure: *const ClosureHeader) -> f64 { pub(crate) extern "C" fn thunk_test( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -775,6 +783,7 @@ pub(crate) extern "C" fn thunk_test( pub(crate) extern "C" fn thunk_test_skip( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -784,6 +793,7 @@ pub(crate) extern "C" fn thunk_test_skip( pub(crate) extern "C" fn thunk_test_todo( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -793,6 +803,7 @@ pub(crate) extern "C" fn thunk_test_todo( pub(crate) extern "C" fn thunk_test_only( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -802,6 +813,7 @@ pub(crate) extern "C" fn thunk_test_only( pub(crate) extern "C" fn thunk_test_suite( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -811,6 +823,7 @@ pub(crate) extern "C" fn thunk_test_suite( pub(crate) extern "C" fn thunk_test_suite_skip( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -820,6 +833,7 @@ pub(crate) extern "C" fn thunk_test_suite_skip( pub(crate) extern "C" fn thunk_test_suite_todo( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -829,6 +843,7 @@ pub(crate) extern "C" fn thunk_test_suite_todo( pub(crate) extern "C" fn thunk_test_suite_only( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, callback: f64, @@ -836,16 +851,25 @@ pub(crate) extern "C" fn thunk_test_suite_only( register_suite(TestMode::Only, name, options, callback) } -pub(crate) extern "C" fn thunk_test_before(_closure: *const ClosureHeader, callback: f64) -> f64 { +pub(crate) extern "C" fn thunk_test_before( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback: f64, +) -> f64 { register_hook(HookKind::Before, callback) } -pub(crate) extern "C" fn thunk_test_after(_closure: *const ClosureHeader, callback: f64) -> f64 { +pub(crate) extern "C" fn thunk_test_after( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback: f64, +) -> f64 { register_hook(HookKind::After, callback) } pub(crate) extern "C" fn thunk_test_before_each( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback: f64, ) -> f64 { register_hook(HookKind::BeforeEach, callback) @@ -853,12 +877,17 @@ pub(crate) extern "C" fn thunk_test_before_each( pub(crate) extern "C" fn thunk_test_after_each( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, callback: f64, ) -> f64 { register_hook(HookKind::AfterEach, callback) } -pub(crate) extern "C" fn thunk_test_run(_closure: *const ClosureHeader, _options: f64) -> f64 { +pub(crate) extern "C" fn thunk_test_run( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _options: f64, +) -> f64 { let mut array = crate::array::js_array_alloc(0); for _ in 0..10 { array = crate::array::js_array_push_f64(array, undefined_value()); @@ -868,52 +897,98 @@ pub(crate) extern "C" fn thunk_test_run(_closure: *const ClosureHeader, _options #[no_mangle] pub extern "C" fn js_node_test_register(name: f64, options: f64, callback: f64) -> f64 { - thunk_test(std::ptr::null(), name, options, callback) + thunk_test( + std::ptr::null(), + crate::closure::JsThis::current(), + name, + options, + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_skip(name: f64, options: f64, callback: f64) -> f64 { - thunk_test_skip(std::ptr::null(), name, options, callback) + thunk_test_skip( + std::ptr::null(), + crate::closure::JsThis::current(), + name, + options, + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_todo(name: f64, options: f64, callback: f64) -> f64 { - thunk_test_todo(std::ptr::null(), name, options, callback) + thunk_test_todo( + std::ptr::null(), + crate::closure::JsThis::current(), + name, + options, + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_only(name: f64, options: f64, callback: f64) -> f64 { - thunk_test_only(std::ptr::null(), name, options, callback) + thunk_test_only( + std::ptr::null(), + crate::closure::JsThis::current(), + name, + options, + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_suite(name: f64, options: f64, callback: f64) -> f64 { - thunk_test_suite(std::ptr::null(), name, options, callback) + thunk_test_suite( + std::ptr::null(), + crate::closure::JsThis::current(), + name, + options, + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_before(callback: f64) -> f64 { - thunk_test_before(std::ptr::null(), callback) + thunk_test_before( + std::ptr::null(), + crate::closure::JsThis::current(), + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_after(callback: f64) -> f64 { - thunk_test_after(std::ptr::null(), callback) + thunk_test_after( + std::ptr::null(), + crate::closure::JsThis::current(), + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_before_each(callback: f64) -> f64 { - thunk_test_before_each(std::ptr::null(), callback) + thunk_test_before_each( + std::ptr::null(), + crate::closure::JsThis::current(), + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_after_each(callback: f64) -> f64 { - thunk_test_after_each(std::ptr::null(), callback) + thunk_test_after_each( + std::ptr::null(), + crate::closure::JsThis::current(), + callback, + ) } #[no_mangle] pub extern "C" fn js_node_test_run(options: f64) -> f64 { - thunk_test_run(std::ptr::null(), options) + thunk_test_run(std::ptr::null(), crate::closure::JsThis::current(), options) } pub(crate) fn scan_node_test_runner_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { diff --git a/crates/perry-runtime/src/node_submodules/test_snapshot.rs b/crates/perry-runtime/src/node_submodules/test_snapshot.rs index 4f169e6818..a55e796ad8 100644 --- a/crates/perry-runtime/src/node_submodules/test_snapshot.rs +++ b/crates/perry-runtime/src/node_submodules/test_snapshot.rs @@ -29,6 +29,7 @@ fn snapshot_payload(value: f64) -> String { pub(super) extern "C" fn snapshot_set_default_serializers( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, serializers: f64, ) -> f64 { if !is_array_value(serializers) { @@ -44,6 +45,7 @@ pub(super) extern "C" fn snapshot_set_default_serializers( pub(super) extern "C" fn snapshot_set_resolve_snapshot_path( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, resolver: f64, ) -> f64 { if !is_callable_value(resolver) { @@ -61,6 +63,7 @@ fn validate_snapshot_assertion_options(options: f64) { pub(super) extern "C" fn assert_snapshot( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, options: f64, ) -> f64 { @@ -117,6 +120,7 @@ pub(super) extern "C" fn assert_snapshot( pub(super) extern "C" fn assert_file_snapshot( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, path_value: f64, options: f64, 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 c27a4bc471..8f4a4cfe9d 100644 --- a/crates/perry-runtime/src/node_submodules/test_unit_tests.rs +++ b/crates/perry-runtime/src/node_submodules/test_unit_tests.rs @@ -24,7 +24,10 @@ fn caught_error_code(f: impl FnOnce() -> f64) -> &'static str { .expect("error should have a registered Node code") } -extern "C" fn allocating_true_option(_closure: *const ClosureHeader) -> f64 { +extern "C" fn allocating_true_option( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = js_object_alloc(0, 0); f64::from_bits(crate::value::TAG_TRUE) } diff --git a/crates/perry-runtime/src/node_submodules/tests.rs b/crates/perry-runtime/src/node_submodules/tests.rs index a6abfdede0..5fd197bed5 100644 --- a/crates/perry-runtime/src/node_submodules/tests.rs +++ b/crates/perry-runtime/src/node_submodules/tests.rs @@ -614,8 +614,12 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { f64::from_bits(crate::value::TAG_FALSE), ); - let promise_value = - thunk_streamP_finished(std::ptr::null(), stream, boxed_ptr(opts as *const u8)); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + boxed_ptr(opts as *const u8), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 1); @@ -627,15 +631,24 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { #[test] fn stream_promises_finished_rejects_invalid_inputs() { - let number_promise = thunk_streamP_finished(std::ptr::null(), 123.0, undefined_value()); + let number_promise = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + 123.0, + undefined_value(), + ); assert_rejected_node_error( number_promise, "ERR_INVALID_ARG_TYPE", "The \"stream\" argument must be an instance of ReadableStream, WritableStream, or Stream. Received type number (123)", ); - let string_promise = - thunk_streamP_finished(std::ptr::null(), string_value("x"), undefined_value()); + let string_promise = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + string_value("x"), + undefined_value(), + ); assert_rejected_node_error( string_promise, "ERR_INVALID_ARG_TYPE", @@ -645,6 +658,7 @@ fn stream_promises_finished_rejects_invalid_inputs() { let object = js_object_alloc(0, 0); let object_promise = thunk_streamP_finished( std::ptr::null(), + crate::closure::JsThis::current(), boxed_ptr(object as *const u8), undefined_value(), ); @@ -661,7 +675,12 @@ fn stream_promises_finished_rejects_hidden_stream_error() { let err = abort_error_value(); crate::node_stream::test_set_hidden_error(stream, err); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + undefined_value(), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 2); @@ -675,7 +694,12 @@ fn stream_promises_finished_rejects_hidden_stream_error() { fn stream_promises_finished_rejects_later_destroy_error() { let stream = crate::node_stream::js_node_stream_readable_new(undefined_value()); crate::node_stream::test_install_manual_read(stream); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + undefined_value(), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -744,7 +768,12 @@ fn stream_promises_finished_resolves_for_resumed_readable_from() { let handle = object_ptr_from_value(stream).expect("stream object") as i64; let _ = crate::node_stream::js_node_stream_method_resume(handle); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + undefined_value(), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -756,7 +785,12 @@ fn stream_promises_finished_resolves_for_resumed_readable_from() { #[test] fn stream_promises_finished_duplex_default_waits_after_writable_finish_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + undefined_value(), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -775,7 +809,12 @@ fn stream_promises_finished_duplex_default_waits_after_writable_finish_only() { #[test] fn stream_promises_finished_duplex_default_waits_after_readable_end_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, undefined_value()); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + undefined_value(), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -795,7 +834,12 @@ fn stream_promises_finished_duplex_default_waits_after_readable_end_only() { fn stream_promises_finished_duplex_readable_false_resolves_after_writable_finish() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let options = finished_bool_option(b"readable", false); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, options); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + options, + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -810,7 +854,12 @@ fn stream_promises_finished_duplex_readable_false_resolves_after_writable_finish fn stream_promises_finished_duplex_writable_false_resolves_after_readable_end() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let options = finished_bool_option(b"writable", false); - let promise_value = thunk_streamP_finished(std::ptr::null(), stream, options); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + options, + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -825,7 +874,12 @@ thread_local! { static PIPELINE_CAPTURED: RefCell> = const { RefCell::new(Vec::new()) }; } -extern "C" fn pipeline_write_capture(_closure: *const ClosureHeader, chunk: f64, _enc: f64) -> f64 { +extern "C" fn pipeline_write_capture( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, + _enc: f64, +) -> f64 { PIPELINE_CAPTURED.with(|captured| { captured .borrow_mut() @@ -834,7 +888,11 @@ extern "C" fn pipeline_write_capture(_closure: *const ClosureHeader, chunk: f64, f64::from_bits(crate::value::TAG_TRUE) } -extern "C" fn pipeline_end_capture(_closure: *const ClosureHeader, _arg: f64) -> f64 { +extern "C" fn pipeline_end_capture( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { undefined_value() } @@ -865,6 +923,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { let promise_value = thunk_streamP_pipeline( std::ptr::null(), + crate::closure::JsThis::current(), source, boxed_ptr(sink as *const u8), undefined_value(), @@ -881,6 +940,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { fn stream_promises_pipeline_rejects_missing_streams() { let direct_promise = thunk_streamP_pipeline( std::ptr::null(), + crate::closure::JsThis::current(), undefined_value(), undefined_value(), undefined_value(), @@ -894,6 +954,7 @@ fn stream_promises_pipeline_rejects_missing_streams() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), + crate::closure::JsThis::current(), 123.0, undefined_value(), boxed_ptr(empty_rest as *const u8), @@ -907,7 +968,13 @@ fn stream_promises_pipeline_rejects_missing_streams() { #[test] fn stream_promises_pipeline_rejects_invalid_source_body() { - let direct_promise = thunk_streamP_pipeline(std::ptr::null(), 123.0, 456.0, undefined_value()); + let direct_promise = thunk_streamP_pipeline( + std::ptr::null(), + crate::closure::JsThis::current(), + 123.0, + 456.0, + undefined_value(), + ); assert_rejected_node_error( direct_promise, "ERR_INVALID_ARG_TYPE", @@ -917,6 +984,7 @@ fn stream_promises_pipeline_rejects_invalid_source_body() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), + crate::closure::JsThis::current(), 123.0, 456.0, boxed_ptr(empty_rest as *const u8), @@ -940,8 +1008,12 @@ fn stream_promises_finished_rejects_when_signal_aborts() { ); let stream = crate::node_stream::js_node_stream_passthrough_new(undefined_value()); - let promise_value = - thunk_streamP_finished(std::ptr::null(), stream, boxed_ptr(opts as *const u8)); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + boxed_ptr(opts as *const u8), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 0); @@ -974,8 +1046,12 @@ fn stream_promises_finished_with_signal_resolves_for_finished_writable_side_stub crate::object::js_implicit_this_set(prev_this); let _ = crate::promise::js_promise_run_microtasks(); - let promise_value = - thunk_streamP_finished(std::ptr::null(), stream, boxed_ptr(opts as *const u8)); + let promise_value = thunk_streamP_finished( + std::ptr::null(), + crate::closure::JsThis::current(), + stream, + boxed_ptr(opts as *const u8), + ); let promise = promise_ptr(promise_value); assert_eq!(crate::promise::js_promise_state(promise), 1); diff --git a/crates/perry-runtime/src/node_submodules/timers.rs b/crates/perry-runtime/src/node_submodules/timers.rs index c4df18185c..1e413cfcfc 100644 --- a/crates/perry-runtime/src/node_submodules/timers.rs +++ b/crates/perry-runtime/src/node_submodules/timers.rs @@ -92,6 +92,7 @@ fn promise_timer(delay_ms: f64, value: f64, has_ref: bool) -> *mut crate::promis /// Refs #1213. pub(crate) extern "C" fn timers_promises_set_timeout( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, delay_ms: f64, value: f64, options: f64, @@ -126,6 +127,7 @@ pub(crate) extern "C" fn timers_promises_set_timeout( /// Refs #1213. pub(crate) extern "C" fn timers_promises_set_immediate( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, options: f64, ) -> f64 { @@ -152,13 +154,23 @@ pub(crate) extern "C" fn timers_promises_set_immediate( pub(crate) extern "C" fn timers_promises_scheduler_wait( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, delay_ms: f64, options: f64, ) -> f64 { - timers_promises_set_timeout(_closure, delay_ms, f64::from_bits(TAG_UNDEFINED), options) + timers_promises_set_timeout( + _closure, + crate::closure::JsThis::current(), + delay_ms, + f64::from_bits(TAG_UNDEFINED), + options, + ) } -pub(crate) extern "C" fn timers_promises_scheduler_yield(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn timers_promises_scheduler_yield( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let promise = promise_timer(0.0, f64::from_bits(TAG_UNDEFINED), true); crate::value::js_nanbox_pointer(promise as i64) } @@ -191,7 +203,10 @@ fn iter_result(value: f64, done: bool) -> f64 { boxed_ptr(obj as *const u8) } -extern "C" fn timers_promises_interval_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn timers_promises_interval_next( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let value = js_closure_get_capture_f64(closure, 0); let signal = js_closure_get_capture_f64(closure, 1); let delay_ms = js_closure_get_capture_f64(closure, 2); @@ -228,11 +243,17 @@ extern "C" fn timers_promises_interval_next(closure: *const ClosureHeader) -> f6 boxed_ptr(promise as *const u8) } -extern "C" fn timers_promises_interval_self(closure: *const ClosureHeader) -> f64 { +extern "C" fn timers_promises_interval_self( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_closure_get_capture_f64(closure, 0) } -extern "C" fn timers_promises_interval_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn timers_promises_interval_return( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let next_value = js_closure_get_capture_f64(closure, 0); let next = boxed_value_to_ptr::(next_value); js_closure_set_capture_f64(next, 3, 1.0); @@ -247,6 +268,7 @@ extern "C" fn timers_promises_interval_return(closure: *const ClosureHeader) -> /// returned or the optional AbortSignal rejects the pending tick. pub(crate) extern "C" fn timers_promises_set_interval( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, delay_ms: f64, value: f64, options: f64, @@ -334,6 +356,7 @@ fn rest_array_values(rest: f64) -> Vec { pub(crate) extern "C" fn timers_ns_set_timeout( _c: *const ClosureHeader, + _this: crate::closure::JsThis, cb: f64, ms: f64, rest: f64, @@ -350,6 +373,7 @@ pub(crate) extern "C" fn timers_ns_set_timeout( } pub(crate) extern "C" fn timers_ns_set_interval( _c: *const ClosureHeader, + _this: crate::closure::JsThis, cb: f64, ms: f64, rest: f64, @@ -366,6 +390,7 @@ pub(crate) extern "C" fn timers_ns_set_interval( } pub(crate) extern "C" fn timers_ns_set_immediate( _c: *const ClosureHeader, + _this: crate::closure::JsThis, cb: f64, rest: f64, ) -> f64 { @@ -378,21 +403,34 @@ pub(crate) extern "C" fn timers_ns_set_immediate( ) }) } -pub(crate) extern "C" fn timers_ns_clear_timeout(_c: *const ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn timers_ns_clear_timeout( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { crate::timer::js_clear_timeout_value(arg); f64::from_bits(TAG_UNDEFINED) } -pub(crate) extern "C" fn timers_ns_clear_interval(_c: *const ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn timers_ns_clear_interval( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { crate::timer::js_clear_interval_value(arg); f64::from_bits(TAG_UNDEFINED) } -pub(crate) extern "C" fn timers_ns_clear_immediate(_c: *const ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn timers_ns_clear_immediate( + _c: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { crate::timer::js_clear_immediate_value(arg); f64::from_bits(TAG_UNDEFINED) } pub(crate) extern "C" fn timers_promises_scheduler( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { let msg = b"scheduler is not a function"; diff --git a/crates/perry-runtime/src/node_submodules/trace_events.rs b/crates/perry-runtime/src/node_submodules/trace_events.rs index 58542a2e4e..382d2434f6 100644 --- a/crates/perry-runtime/src/node_submodules/trace_events.rs +++ b/crates/perry-runtime/src/node_submodules/trace_events.rs @@ -303,7 +303,11 @@ fn set_trace_enabled(id: i64, enabled: bool) { } } -extern "C" fn trace_tracing_constructor(_closure: *const ClosureHeader, categories: f64) -> f64 { +extern "C" fn trace_tracing_constructor( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + categories: f64, +) -> f64 { if crate::object::js_new_target_get().to_bits() == crate::value::TAG_UNDEFINED { throw_type_error_no_code(b"Class constructor Tracing cannot be invoked without 'new'"); } @@ -313,17 +317,26 @@ extern "C" fn trace_tracing_constructor(_closure: *const ClosureHeader, categori create_trace(categories_handle.get_nanbox_f64(), category_names) } -extern "C" fn trace_tracing_enable(_closure: *const ClosureHeader) -> f64 { +extern "C" fn trace_tracing_enable( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { set_trace_enabled(this_trace_id(), true); undefined() } -extern "C" fn trace_tracing_disable(_closure: *const ClosureHeader) -> f64 { +extern "C" fn trace_tracing_disable( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { set_trace_enabled(this_trace_id(), false); undefined() } -extern "C" fn trace_categories_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn trace_categories_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = this_trace_id(); let categories = trace_state_value(id, |state| state.categories); let scope = crate::gc::RuntimeHandleScope::new(); @@ -331,7 +344,10 @@ extern "C" fn trace_categories_getter(_closure: *const ClosureHeader) -> f64 { string_value(&categories_from_array(categories.get_nanbox_f64()).join(",")) } -extern "C" fn trace_enabled_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn trace_enabled_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = this_trace_id(); trace_state_value(id, |state| bool_value(state.enabled)) } @@ -445,6 +461,7 @@ fn create_trace(categories: f64, source_categories: Vec) -> f64 { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_trace_events_createTracing( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { init_trace_events_runtime(); @@ -456,6 +473,7 @@ pub(crate) extern "C" fn thunk_trace_events_createTracing( #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn thunk_trace_events_getEnabledCategories( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { init_trace_events_runtime(); diff --git a/crates/perry-runtime/src/node_test.rs b/crates/perry-runtime/src/node_test.rs index b6d4c7e0da..8b550151ae 100644 --- a/crates/perry-runtime/src/node_test.rs +++ b/crates/perry-runtime/src/node_test.rs @@ -40,19 +40,29 @@ fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { boxed_pointer(closure as *const u8) } -extern "C" fn noop0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn noop0(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { undefined() } -extern "C" fn noop1(_closure: *const ClosureHeader, _arg0: f64) -> f64 { +extern "C" fn noop1( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg0: f64, +) -> f64 { undefined() } -extern "C" fn noop3(_closure: *const ClosureHeader, _arg0: f64, _arg1: f64, _arg2: f64) -> f64 { +extern "C" fn noop3( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _arg0: f64, + _arg1: f64, + _arg2: f64, +) -> f64 { undefined() } -extern "C" fn zero0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn zero0(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 0.0 } diff --git a/crates/perry-runtime/src/node_v8.rs b/crates/perry-runtime/src/node_v8.rs index 3c651066de..2414424848 100644 --- a/crates/perry-runtime/src/node_v8.rs +++ b/crates/perry-runtime/src/node_v8.rs @@ -614,6 +614,15 @@ pub extern "C" fn js_v8_noop_undefined() -> f64 { undefined() } +/// [`js_v8_noop_undefined`] as a JS body (the function object +/// `v8.promiseHooks.onInit(fn)` returns). +extern "C" fn v8_noop_undefined_body( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + undefined() +} + /// `v8.startupSnapshot.isBuildingSnapshot()` — Node returns the *number* `0` /// (never building a snapshot under Perry), NOT a boolean. #[no_mangle] @@ -645,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(js_v8_noop_undefined as *const u8); + let c = crate::closure::js_closure_alloc_singleton(v8_noop_undefined_body as *const u8); 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 edfeb0c332..a41eb5c4c0 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -1467,6 +1467,7 @@ fn make_script(code: String, options: f64) -> f64 { extern "C" fn vm_script_create_cached_data_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _unused1: f64, _unused2: f64, ) -> f64 { @@ -1482,6 +1483,7 @@ extern "C" fn vm_script_create_cached_data_method( extern "C" fn vm_script_run_in_this_context_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _options: f64, _unused: f64, ) -> f64 { @@ -1502,6 +1504,7 @@ extern "C" fn vm_script_run_in_this_context_method( extern "C" fn vm_script_run_in_context_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, contextified_object: f64, _options: f64, ) -> f64 { @@ -1521,6 +1524,7 @@ extern "C" fn vm_script_run_in_context_method( extern "C" fn vm_script_run_in_new_context_method( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, context_object: f64, options: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index 2edf414b53..6f1fc6543f 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -159,11 +159,17 @@ fn new_module_base(kind: &str, status: &str, identifier: String) -> *mut ObjectH module } -extern "C" fn module_namespace_getter(closure: *const ClosureHeader) -> f64 { +extern "C" fn module_namespace_getter( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_vm_module_namespace(crate::closure::js_closure_get_capture_f64(closure, 0)) } -extern "C" fn module_error_getter(closure: *const ClosureHeader) -> f64 { +extern "C" fn module_error_getter( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { js_vm_module_error(crate::closure::js_closure_get_capture_f64(closure, 0)) } diff --git a/crates/perry-runtime/src/object/accessor_pair_tests.rs b/crates/perry-runtime/src/object/accessor_pair_tests.rs index 87e84f1835..308a42f48e 100644 --- a/crates/perry-runtime/src/object/accessor_pair_tests.rs +++ b/crates/perry-runtime/src/object/accessor_pair_tests.rs @@ -7,7 +7,10 @@ fn key(name: &str) -> *mut crate::StringHeader { } unsafe fn closure_bits() -> u64 { - extern "C" fn noop(_c: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn noop( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } let c = crate::closure::js_closure_alloc(noop as *const u8, 0); diff --git a/crates/perry-runtime/src/object/arguments.rs b/crates/perry-runtime/src/object/arguments.rs index 38d0ba4d39..d76bb28f2f 100644 --- a/crates/perry-runtime/src/object/arguments.rs +++ b/crates/perry-runtime/src/object/arguments.rs @@ -202,7 +202,10 @@ fn bool_value(value: bool) -> f64 { }) } -extern "C" fn arguments_throw_type_error(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn arguments_throw_type_error( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { super::throw_object_type_error( b"'caller', 'callee', and 'arguments' properties may not be accessed", ); @@ -694,7 +697,7 @@ pub(crate) unsafe fn arguments_object_get_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null()); + arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::current()); } if let Some(box_ptr) = mapped_box { let value = crate::r#box::js_box_get(box_ptr); @@ -737,7 +740,7 @@ pub(crate) unsafe fn arguments_object_set_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null()); + arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::current()); } if !super::own_key_present(obj, key) { return false; diff --git a/crates/perry-runtime/src/object/assert.rs b/crates/perry-runtime/src/object/assert.rs index 65602cd86e..9df6becabd 100644 --- a/crates/perry-runtime/src/object/assert.rs +++ b/crates/perry-runtime/src/object/assert.rs @@ -508,6 +508,7 @@ fn promise_from_assert_async_input(input: f64) -> AssertAsyncInput { extern "C" fn assert_rejects_fulfilled( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { let result = @@ -527,6 +528,7 @@ extern "C" fn assert_rejects_fulfilled( extern "C" fn assert_rejects_rejected( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let result = @@ -550,6 +552,7 @@ extern "C" fn assert_rejects_rejected( extern "C" fn assert_does_not_reject_fulfilled( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { let result = @@ -560,6 +563,7 @@ extern "C" fn assert_does_not_reject_fulfilled( extern "C" fn assert_does_not_reject_rejected( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let result = diff --git a/crates/perry-runtime/src/object/async_generator_queue.rs b/crates/perry-runtime/src/object/async_generator_queue.rs index 982a941b83..1e3787e040 100644 --- a/crates/perry-runtime/src/object/async_generator_queue.rs +++ b/crates/perry-runtime/src/object/async_generator_queue.rs @@ -289,15 +289,27 @@ fn original_from_wrapper(closure: *const ClosureHeader) -> *const ClosureHeader js_closure_get_capture_ptr(closure, 1) as *const ClosureHeader } -extern "C" fn async_generator_next_wrapper(closure: *const ClosureHeader, arg: f64) -> f64 { +extern "C" fn async_generator_next_wrapper( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { async_generator_request(closure, arg, RequestKind::NextOrThrow) } -extern "C" fn async_generator_return_wrapper(closure: *const ClosureHeader, arg: f64) -> f64 { +extern "C" fn async_generator_return_wrapper( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { async_generator_request(closure, arg, RequestKind::Return) } -extern "C" fn async_generator_throw_wrapper(closure: *const ClosureHeader, arg: f64) -> f64 { +extern "C" fn async_generator_throw_wrapper( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { async_generator_request(closure, arg, RequestKind::NextOrThrow) } @@ -429,6 +441,7 @@ fn make_return_step_wrapper( extern "C" fn async_generator_return_step_fulfill( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { if let Some(state_id) = state_id_from_wrapper(closure) { @@ -445,6 +458,7 @@ extern "C" fn async_generator_return_step_fulfill( extern "C" fn async_generator_return_step_reject( closure: *const ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { if let Some(state_id) = state_id_from_wrapper(closure) { @@ -456,14 +470,21 @@ extern "C" fn async_generator_return_step_reject( reason } -extern "C" fn async_generator_drain_wrapper(closure: *const ClosureHeader) -> f64 { +extern "C" fn async_generator_drain_wrapper( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if let Some(state_id) = state_id_from_wrapper(closure) { process_one_queued_request(state_id); } f64::from_bits(TAG_UNDEFINED) } -extern "C" fn async_generator_settle_fulfill(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn async_generator_settle_fulfill( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { if let Some(state_id) = state_id_from_wrapper(closure) { let out = js_closure_get_capture_ptr(closure, 1) as *mut Promise; finish_after_pending_result(state_id, out, true, value); @@ -471,7 +492,11 @@ extern "C" fn async_generator_settle_fulfill(closure: *const ClosureHeader, valu value } -extern "C" fn async_generator_settle_reject(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn async_generator_settle_reject( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if let Some(state_id) = state_id_from_wrapper(closure) { let out = js_closure_get_capture_ptr(closure, 1) as *mut Promise; finish_after_pending_result(state_id, out, false, reason); diff --git a/crates/perry-runtime/src/object/class_registry/construct.rs b/crates/perry-runtime/src/object/class_registry/construct.rs index b56a78ed0f..01f76c129e 100644 --- a/crates/perry-runtime/src/object/class_registry/construct.rs +++ b/crates/perry-runtime/src/object/class_registry/construct.rs @@ -599,7 +599,12 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .get(1) .copied() .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); - return crate::object::global_this_blob_thunk(std::ptr::null(), parts, options); + return crate::object::global_this_blob_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + parts, + options, + ); } #[cfg(feature = "global-webfetch")] "File" => { @@ -617,6 +622,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_file_thunk( std::ptr::null(), + crate::closure::JsThis::current(), parts, name, options, @@ -646,7 +652,11 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .first() .copied() .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); - return crate::object::global_this_headers_thunk(std::ptr::null(), init); + return crate::object::global_this_headers_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + init, + ); } #[cfg(feature = "global-webfetch")] "Request" => { @@ -658,7 +668,12 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .get(1) .copied() .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); - return crate::object::global_this_request_thunk(std::ptr::null(), input, init); + return crate::object::global_this_request_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + input, + init, + ); } #[cfg(feature = "global-webfetch")] "Response" => { @@ -670,7 +685,12 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .get(1) .copied() .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); - return crate::object::global_this_response_thunk(std::ptr::null(), body, init); + return crate::object::global_this_response_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + body, + init, + ); } "Event" => { let event_type = args @@ -849,7 +869,10 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( return crate::messaging::js_message_port_constructor_error(); } "Storage" => { - return crate::web_storage::storage_constructor_illegal(std::ptr::null()); + return crate::web_storage::storage_constructor_illegal( + std::ptr::null(), + crate::closure::JsThis::current(), + ); } "BroadcastChannel" => { let name = args @@ -1897,9 +1920,8 @@ pub unsafe extern "C" fn js_new_function_construct_with_new_target( /// the `new (args)` widened path here in /// `js_new_function_construct` needs to gate the constructor dispatch /// on a real closure to avoid SIGSEGV'ing on non-callable callees -/// (`new someObject()`, `new someStringVar()`, etc.). Uses the -/// `_reserved` magic word `crate::closure::CLOSURE_MAGIC` that every -/// `js_closure_alloc*` site stamps on allocation. +/// (`new someObject()`, `new someStringVar()`, etc.). The kind is the GC +/// header's type byte (`closure_kind_probe`), not a payload magic word. pub(crate) fn is_callable_function_value(value: f64) -> bool { use crate::value::JSValue; let jv = JSValue::from_bits(value.to_bits()); @@ -1940,43 +1962,5 @@ pub(super) fn is_arrow_function_value(value: f64) -> bool { crate::closure::closure_is_arrow(ptr) } -/// Lookup helper: returns the registered prototype-method value for -/// `(class_id, name)`, or None if no assignment matched. Walks the -/// parent-class chain so methods registered on a base class are found -/// via subclass instances. -pub(crate) fn lookup_own_prototype_method(class_id: u32, name: &str) -> Option { - if class_is_key_deleted(class_id, name) { - return None; - } - CLASS_PROTOTYPE_METHODS.with(|table| { - let guard = table.read().ok()?; - let bits = guard.as_ref()?.get(&class_id)?.get(name)?; - Some(f64::from_bits(*bits)) - }) -} - -pub(crate) fn lookup_prototype_method(class_id: u32, name: &str) -> Option { - CLASS_PROTOTYPE_METHODS.with(|table| { - let guard = table.read().ok()?; - let map = guard.as_ref()?; - let mut cid = class_id; - let mut depth = 0usize; - while depth < 32 { - if !class_is_key_deleted(cid, name) { - if let Some(per_class) = map.get(&cid) { - if let Some(&bits) = per_class.get(name) { - return Some(f64::from_bits(bits)); - } - } - } - match crate::object::class_generic_origin(cid).or_else(|| get_parent_class_id(cid)) { - Some(p) if p != 0 && p != cid => { - cid = p; - depth += 1; - } - _ => break, - } - } - None - }) -} +mod prototype_methods; +pub(crate) use prototype_methods::{lookup_own_prototype_method, lookup_prototype_method}; diff --git a/crates/perry-runtime/src/object/class_registry/construct/prototype_methods.rs b/crates/perry-runtime/src/object/class_registry/construct/prototype_methods.rs new file mode 100644 index 0000000000..532d49a560 --- /dev/null +++ b/crates/perry-runtime/src/object/class_registry/construct/prototype_methods.rs @@ -0,0 +1,44 @@ +//! Prototype-method lookups by (class id, name), own and through the parent chain. + +use super::*; + +/// Lookup helper: returns the registered prototype-method value for +/// `(class_id, name)`, or None if no assignment matched. Walks the +/// parent-class chain so methods registered on a base class are found +/// via subclass instances. +pub(crate) fn lookup_own_prototype_method(class_id: u32, name: &str) -> Option { + if class_is_key_deleted(class_id, name) { + return None; + } + CLASS_PROTOTYPE_METHODS.with(|table| { + let guard = table.read().ok()?; + let bits = guard.as_ref()?.get(&class_id)?.get(name)?; + Some(f64::from_bits(*bits)) + }) +} + +pub(crate) fn lookup_prototype_method(class_id: u32, name: &str) -> Option { + CLASS_PROTOTYPE_METHODS.with(|table| { + let guard = table.read().ok()?; + let map = guard.as_ref()?; + let mut cid = class_id; + let mut depth = 0usize; + while depth < 32 { + if !class_is_key_deleted(cid, name) { + if let Some(per_class) = map.get(&cid) { + if let Some(&bits) = per_class.get(name) { + return Some(f64::from_bits(bits)); + } + } + } + match crate::object::class_generic_origin(cid).or_else(|| get_parent_class_id(cid)) { + Some(p) if p != 0 && p != cid => { + cid = p; + depth += 1; + } + _ => break, + } + } + None + }) +} diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index 6c6cd60d86..bdd74db2c4 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -206,7 +206,10 @@ pub(crate) fn class_own_static_accessor_ptrs(class_id: u32, name: &str) -> Optio /// Trampoline giving a raw vtable getter func_ptr (`fn(this) -> f64`) the /// closure calling convention. The receiver comes from `IMPLICIT_THIS`, set /// by the method-call dispatch the closure value travels through. -extern "C" fn class_accessor_getter_thunk(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn class_accessor_getter_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let raw = crate::closure::js_closure_get_capture_ptr(closure, 0) as usize; if raw == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -219,6 +222,7 @@ extern "C" fn class_accessor_getter_thunk(closure: *const crate::closure::Closur /// Trampoline for a raw vtable setter func_ptr (`fn(this, value) -> f64`). extern "C" fn class_accessor_setter_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let raw = crate::closure::js_closure_get_capture_ptr(closure, 0) as usize; diff --git a/crates/perry-runtime/src/object/collection_proto_thunks.rs b/crates/perry-runtime/src/object/collection_proto_thunks.rs index 277aabb80b..a38f2d13b0 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks.rs @@ -466,6 +466,7 @@ fn weak_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { pub(super) extern "C" fn set_proto_add_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let set = set_receiver_or_throw("add"); @@ -477,6 +478,7 @@ pub(super) extern "C" fn set_proto_add_thunk( pub(super) extern "C" fn set_proto_has_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let set = set_receiver_or_throw("has"); @@ -485,6 +487,7 @@ pub(super) extern "C" fn set_proto_has_thunk( pub(super) extern "C" fn set_proto_size_getter_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let set = set_receiver_or_throw("size"); crate::set::js_set_size(set) as f64 @@ -492,6 +495,7 @@ pub(super) extern "C" fn set_proto_size_getter_thunk( pub(super) extern "C" fn set_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let set = set_receiver_or_throw("delete"); @@ -500,6 +504,7 @@ pub(super) extern "C" fn set_proto_delete_thunk( pub(super) extern "C" fn set_proto_clear_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let set = set_receiver_or_throw("clear"); @@ -509,6 +514,7 @@ pub(super) extern "C" fn set_proto_clear_thunk( pub(super) extern "C" fn set_proto_foreach_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, cb: f64, this_arg: f64, ) -> f64 { @@ -519,6 +525,7 @@ pub(super) extern "C" fn set_proto_foreach_thunk( pub(super) extern "C" fn set_proto_values_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let set = set_receiver_or_throw("values"); @@ -532,6 +539,7 @@ pub(super) extern "C" fn set_proto_values_thunk( pub(super) extern "C" fn set_proto_keys_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let set = set_receiver_or_throw("keys"); @@ -545,6 +553,7 @@ pub(super) extern "C" fn set_proto_keys_thunk( pub(super) extern "C" fn set_proto_entries_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let set = set_receiver_or_throw("entries"); @@ -558,6 +567,7 @@ pub(super) extern "C" fn set_proto_entries_thunk( pub(super) extern "C" fn map_proto_get_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let map = map_receiver_or_throw("get"); @@ -566,6 +576,7 @@ pub(super) extern "C" fn map_proto_get_thunk( pub(super) extern "C" fn map_proto_set_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, v: f64, ) -> f64 { @@ -578,6 +589,7 @@ pub(super) extern "C" fn map_proto_set_thunk( pub(super) extern "C" fn map_proto_has_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let map = map_receiver_or_throw("has"); @@ -586,6 +598,7 @@ pub(super) extern "C" fn map_proto_has_thunk( pub(super) extern "C" fn map_proto_size_getter_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let map = map_receiver_or_throw("size"); crate::map::js_map_size(map) as f64 @@ -593,6 +606,7 @@ pub(super) extern "C" fn map_proto_size_getter_thunk( pub(super) extern "C" fn map_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let map = map_receiver_or_throw("delete"); @@ -601,6 +615,7 @@ pub(super) extern "C" fn map_proto_delete_thunk( pub(super) extern "C" fn map_proto_clear_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let map = map_receiver_or_throw("clear"); @@ -610,6 +625,7 @@ pub(super) extern "C" fn map_proto_clear_thunk( pub(super) extern "C" fn map_proto_foreach_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, cb: f64, this_arg: f64, ) -> f64 { @@ -620,6 +636,7 @@ pub(super) extern "C" fn map_proto_foreach_thunk( pub(super) extern "C" fn map_proto_keys_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let map = map_receiver_or_throw("keys"); @@ -633,6 +650,7 @@ pub(super) extern "C" fn map_proto_keys_thunk( pub(super) extern "C" fn map_proto_values_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let map = map_receiver_or_throw("values"); @@ -646,6 +664,7 @@ pub(super) extern "C" fn map_proto_values_thunk( pub(super) extern "C" fn map_proto_entries_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _v: f64, ) -> f64 { let map = map_receiver_or_throw("entries"); @@ -659,6 +678,7 @@ pub(super) extern "C" fn map_proto_entries_thunk( pub(super) extern "C" fn weakset_proto_add_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKSET, "WeakSet.prototype", "add"); @@ -667,6 +687,7 @@ pub(super) extern "C" fn weakset_proto_add_thunk( pub(super) extern "C" fn weakset_proto_has_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKSET, "WeakSet.prototype", "has"); @@ -675,6 +696,7 @@ pub(super) extern "C" fn weakset_proto_has_thunk( pub(super) extern "C" fn weakset_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, v: f64, ) -> f64 { let r = weak_receiver_or_throw( @@ -687,6 +709,7 @@ pub(super) extern "C" fn weakset_proto_delete_thunk( pub(super) extern "C" fn weakmap_proto_get_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "get"); @@ -695,6 +718,7 @@ pub(super) extern "C" fn weakmap_proto_get_thunk( pub(super) extern "C" fn weakmap_proto_set_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, v: f64, ) -> f64 { @@ -704,6 +728,7 @@ pub(super) extern "C" fn weakmap_proto_set_thunk( pub(super) extern "C" fn weakmap_proto_has_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "has"); @@ -712,6 +737,7 @@ pub(super) extern "C" fn weakmap_proto_has_thunk( pub(super) extern "C" fn weakmap_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, k: f64, ) -> f64 { let r = weak_receiver_or_throw( diff --git a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs index 37b803dab5..518c269fca 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs @@ -20,22 +20,42 @@ fn reflective_map_mutators_keep_subclass_identity() { receiver.get_nanbox_f64(), )); assert_eq!( - map_proto_set_thunk(std::ptr::null(), 1.0, 10.0).to_bits(), + map_proto_set_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + 1.0, + 10.0 + ) + .to_bits(), receiver.get_nanbox_f64().to_bits() ); - assert_eq!(map_proto_get_thunk(std::ptr::null(), 1.0), 10.0); - assert_eq!(map_proto_size_getter_thunk(std::ptr::null()), 1.0); assert_eq!( - map_proto_delete_thunk(std::ptr::null(), 1.0).to_bits(), + map_proto_get_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0), + 10.0 + ); + assert_eq!( + map_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + 1.0 + ); + assert_eq!( + map_proto_delete_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), crate::value::TAG_TRUE ); assert_eq!( - map_proto_has_thunk(std::ptr::null(), 1.0).to_bits(), + map_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), crate::value::TAG_FALSE ); - map_proto_set_thunk(std::ptr::null(), 2.0, 20.0); - map_proto_clear_thunk(std::ptr::null(), 0.0); - assert_eq!(map_proto_size_getter_thunk(std::ptr::null()), 0.0); + map_proto_set_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + 2.0, + 20.0, + ); + map_proto_clear_thunk(std::ptr::null(), crate::closure::JsThis::current(), 0.0); + assert_eq!( + map_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + 0.0 + ); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } @@ -48,25 +68,31 @@ fn reflective_set_mutators_keep_subclass_identity() { receiver.get_nanbox_f64(), )); assert_eq!( - set_proto_add_thunk(std::ptr::null(), 1.0).to_bits(), + set_proto_add_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), receiver.get_nanbox_f64().to_bits() ); assert_eq!( - set_proto_has_thunk(std::ptr::null(), 1.0).to_bits(), + set_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), crate::value::TAG_TRUE ); - assert_eq!(set_proto_size_getter_thunk(std::ptr::null()), 1.0); assert_eq!( - set_proto_delete_thunk(std::ptr::null(), 1.0).to_bits(), + set_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + 1.0 + ); + assert_eq!( + set_proto_delete_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), crate::value::TAG_TRUE ); assert_eq!( - set_proto_has_thunk(std::ptr::null(), 1.0).to_bits(), + set_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), crate::value::TAG_FALSE ); - set_proto_add_thunk(std::ptr::null(), 2.0); - set_proto_clear_thunk(std::ptr::null(), 0.0); - assert_eq!(set_proto_size_getter_thunk(std::ptr::null()), 0.0); + set_proto_add_thunk(std::ptr::null(), crate::closure::JsThis::current(), 2.0); + set_proto_clear_thunk(std::ptr::null(), crate::closure::JsThis::current(), 0.0); + assert_eq!( + set_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + 0.0 + ); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } diff --git a/crates/perry-runtime/src/object/dataview_proto_thunks.rs b/crates/perry-runtime/src/object/dataview_proto_thunks.rs index 6b01c74c3a..8ce6bbaf0b 100644 --- a/crates/perry-runtime/src/object/dataview_proto_thunks.rs +++ b/crates/perry-runtime/src/object/dataview_proto_thunks.rs @@ -67,6 +67,7 @@ fn undef() -> f64 { extern "C" fn dataview_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let addr = require_dataview_receiver(); let buf = addr as *const crate::buffer::BufferHeader; @@ -77,13 +78,17 @@ extern "C" fn dataview_byte_length_getter_thunk( extern "C" fn dataview_byte_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let addr = require_dataview_receiver(); let offset = crate::buffer::buffer_byte_offset(addr); f64::from_bits(crate::value::JSValue::number(offset as f64).bits()) } -extern "C" fn dataview_buffer_getter_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn dataview_buffer_getter_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let addr = require_dataview_receiver(); let backing = crate::buffer::buffer_backing_array_buffer(addr); f64::from_bits(crate::value::js_nanbox_pointer(backing as i64).to_bits()) @@ -133,6 +138,7 @@ macro_rules! dataview_get_thunk { ($name:ident, $suffix:literal) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, offset: f64, rest: f64, ) -> f64 { @@ -145,6 +151,7 @@ macro_rules! dataview_set_thunk { ($name:ident, $suffix:literal) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, offset: f64, value: f64, rest: f64, diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index 267e1c987d..f2eeccd5a2 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -35,7 +35,10 @@ fn require_date_timestamp() -> f64 { macro_rules! date_getter_thunk { ($name:ident, $rt:path) => { - extern "C" fn $name(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn $name( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { $rt(require_date_timestamp()) } }; @@ -75,7 +78,10 @@ date_getter_thunk!( /// (`Date.prototype.toISOString.call(x)`) reaches here: brand-check `this` /// (TypeError on a non-Date receiver), then format-or-throw (RangeError when /// the time value is `NaN`). -extern "C" fn date_to_iso_string(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_to_iso_string( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); if !crate::date::is_date_value(this) { super::object_ops::throw_object_type_error(b"this is not a Date object."); @@ -87,7 +93,10 @@ extern "C" fn date_to_iso_string(_closure: *const crate::closure::ClosureHeader) /// `Date.prototype.toUTCString` (and its legacy `toGMTString` alias) /// reflective thunk: brand-check `this` (TypeError on a non-Date receiver), /// then format as "Sun, 23 Mar 2014 00:00:00 GMT" (or "Invalid Date"). -extern "C" fn date_to_utc_string(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_to_utc_string( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); if !crate::date::is_date_value(this) { super::object_ops::throw_object_type_error(b"this is not a Date object."); @@ -97,13 +106,19 @@ extern "C" fn date_to_utc_string(_closure: *const crate::closure::ClosureHeader) } #[cfg(feature = "temporal")] -extern "C" fn date_to_temporal_instant(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_to_temporal_instant( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let timestamp = require_date_timestamp(); crate::temporal::instant::from_epoch_milliseconds_static(&[timestamp]) } #[cfg(not(feature = "temporal"))] -extern "C" fn date_to_temporal_instant(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_to_temporal_instant( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -114,7 +129,10 @@ extern "C" fn date_to_temporal_instant(_closure: *const crate::closure::ClosureH /// otherwise return `Invoke(this, "toISOString")`. So it works for a real Date /// (Invalid → `null`), a plain object carrying its own `toISOString`, and a /// `Number(-Infinity)` wrapper (→ `null`). -extern "C" fn date_to_json(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_to_json( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); date_to_json_value(this) } @@ -214,7 +232,7 @@ pub(crate) fn date_to_json_value(this: f64) -> f64 { #[cfg(test)] pub(crate) fn test_date_to_json_current_this() -> f64 { - date_to_json(std::ptr::null()) + date_to_json(std::ptr::null(), crate::closure::JsThis::current()) } /// True iff `value` is an ECMAScript Object (`Type(O) is Object`). Objects are @@ -245,7 +263,11 @@ fn value_is_object(value: f64) -> bool { /// The `hint` comparison is against the primitive String value only: a /// `new String("number")` wrapper or any non-string is an invalid hint /// (TypeError), matching test262 `hint-invalid.js`. -extern "C" fn date_to_primitive(_closure: *const crate::closure::ClosureHeader, hint: f64) -> f64 { +extern "C" fn date_to_primitive( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + hint: f64, +) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); // Step 1: brand-check `Type(O) is Object` (throws for undefined/null/86/''/true). if !value_is_object(this) { @@ -375,18 +397,29 @@ pub(crate) fn install_date_proto_to_primitive(proto_obj: *mut ObjectHeader) { // `getOwnPropertyDescriptor(Date, "UTC")`). /// `Date.now()` — current time in milliseconds. -extern "C" fn date_now_static(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_now_static( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::date::js_date_now() } /// `Date.parse(string)` — ToString the argument, then parse to a ms timestamp. -extern "C" fn date_parse_static(_closure: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn date_parse_static( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { let s = crate::value::js_jsvalue_to_string(arg); crate::date::js_date_parse(s as *const crate::string::StringHeader) } /// `Date.UTC(year, month?, day?, …)` — variadic; forwards to `js_date_utc`. -extern "C" fn date_utc_static(_closure: *const crate::closure::ClosureHeader, rest: f64) -> f64 { +extern "C" fn date_utc_static( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let vals = super::global_this::global_this_rest_array_values(rest); crate::date::js_date_utc(vals.as_ptr(), vals.len() as i32) } @@ -428,7 +461,10 @@ pub(crate) fn install_date_constructor_statics(ctor: *mut crate::closure::Closur } /// Legacy `Date.prototype.getYear` — `getFullYear() - 1900` (NaN-preserving). -extern "C" fn date_get_year(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn date_get_year( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let fy = crate::date::js_date_get_full_year(require_date_timestamp()); if fy.is_nan() { fy @@ -533,7 +569,11 @@ fn require_date_this() -> f64 { /// rebuild. macro_rules! date_setter_thunk { ($name:ident, $is_utc:expr, $field:expr) => { - extern "C" fn $name(_closure: *const crate::closure::ClosureHeader, rest: f64) -> f64 { + extern "C" fn $name( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, + ) -> f64 { let this = require_date_this(); let args = super::global_this::global_this_rest_array_values(rest); crate::date::js_date_apply_setter( @@ -613,6 +653,7 @@ fn date_to_locale_opts_impl(rest: f64, ctx: crate::intl::TemporalLocaleCtx) -> f extern "C" fn date_to_locale_string_opts( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainDateTime) @@ -620,6 +661,7 @@ extern "C" fn date_to_locale_string_opts( extern "C" fn date_to_locale_date_string_opts( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainDate) @@ -627,6 +669,7 @@ extern "C" fn date_to_locale_date_string_opts( extern "C" fn date_to_locale_time_string_opts( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainTime) diff --git a/crates/perry-runtime/src/object/disposable_proto_thunks.rs b/crates/perry-runtime/src/object/disposable_proto_thunks.rs index 14b6869976..c29c7d4391 100644 --- a/crates/perry-runtime/src/object/disposable_proto_thunks.rs +++ b/crates/perry-runtime/src/object/disposable_proto_thunks.rs @@ -55,6 +55,7 @@ fn stack_receiver_or_throw(want_async: bool, method: &str) -> *mut ObjectHeader pub(super) extern "C" fn ds_proto_use_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, resource: f64, ) -> f64 { let stack = stack_receiver_or_throw(false, "use"); @@ -63,6 +64,7 @@ pub(super) extern "C" fn ds_proto_use_thunk( pub(super) extern "C" fn ds_proto_adopt_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, on_dispose: f64, ) -> f64 { @@ -72,24 +74,32 @@ pub(super) extern "C" fn ds_proto_adopt_thunk( pub(super) extern "C" fn ds_proto_defer_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_dispose: f64, ) -> f64 { let stack = stack_receiver_or_throw(false, "defer"); js_disposable_stack_defer(stack, on_dispose) } -pub(super) extern "C" fn ds_proto_dispose_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn ds_proto_dispose_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stack = stack_receiver_or_throw(false, "dispose"); js_disposable_stack_dispose(stack) } -pub(super) extern "C" fn ds_proto_move_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn ds_proto_move_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stack = stack_receiver_or_throw(false, "move"); js_disposable_stack_move(stack) } pub(super) extern "C" fn ds_proto_disposed_getter_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let stack = stack_receiver_or_throw(false, "disposed"); js_disposable_stack_disposed(stack) @@ -99,6 +109,7 @@ pub(super) extern "C" fn ds_proto_disposed_getter_thunk( pub(super) extern "C" fn ads_proto_use_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, resource: f64, ) -> f64 { let stack = stack_receiver_or_throw(true, "use"); @@ -107,6 +118,7 @@ pub(super) extern "C" fn ads_proto_use_thunk( pub(super) extern "C" fn ads_proto_adopt_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, on_dispose: f64, ) -> f64 { @@ -116,6 +128,7 @@ pub(super) extern "C" fn ads_proto_adopt_thunk( pub(super) extern "C" fn ads_proto_defer_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_dispose: f64, ) -> f64 { let stack = stack_receiver_or_throw(true, "defer"); @@ -124,18 +137,23 @@ pub(super) extern "C" fn ads_proto_defer_thunk( pub(super) extern "C" fn ads_proto_dispose_async_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let stack = stack_receiver_or_throw(true, "disposeAsync"); js_async_disposable_stack_dispose_async(stack) } -pub(super) extern "C" fn ads_proto_move_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn ads_proto_move_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let stack = stack_receiver_or_throw(true, "move"); js_disposable_stack_move(stack) } pub(super) extern "C" fn ads_proto_disposed_getter_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let stack = stack_receiver_or_throw(true, "disposed"); js_disposable_stack_disposed(stack) 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 a8fed503e9..3e7d47f542 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 @@ -1,6 +1,9 @@ use super::*; -extern "C" fn noop(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn noop( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -47,7 +50,10 @@ fn function_presence_follows_prototypes_without_reading_values() { #[test] fn generator_presence_initializes_function_parents() { let _lock = crate::gc::global_side_table_test_lock(); - extern "C" fn generator_body(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn generator_body( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } crate::closure::js_register_closure_generator_function(generator_body as *const u8); diff --git a/crates/perry-runtime/src/object/global_fetch.rs b/crates/perry-runtime/src/object/global_fetch.rs index 84f95a2c3d..3748116a23 100644 --- a/crates/perry-runtime/src/object/global_fetch.rs +++ b/crates/perry-runtime/src/object/global_fetch.rs @@ -673,6 +673,7 @@ pub(super) fn call_global_response_static_error() -> f64 { pub(super) extern "C" fn global_this_fetch_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, rest: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/object/global_this/array_error.rs b/crates/perry-runtime/src/object/global_this/array_error.rs index 23b2afa573..4ccb0e0d40 100644 --- a/crates/perry-runtime/src/object/global_this/array_error.rs +++ b/crates/perry-runtime/src/object/global_this/array_error.rs @@ -17,6 +17,7 @@ pub(crate) fn global_this_rest_array_values(rest: f64) -> Vec { pub(crate) extern "C" fn function_prototype_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, this_arg: f64, rest: f64, ) -> f64 { @@ -63,6 +64,7 @@ pub(crate) extern "C" fn function_prototype_call_thunk( /// hit exactly this: `Reflect.apply(bind, call, [fn])` yielded `undefined`. pub(crate) extern "C" fn function_prototype_bind_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, this_arg: f64, rest: f64, ) -> f64 { @@ -75,6 +77,7 @@ pub(crate) extern "C" fn function_prototype_bind_thunk( pub(crate) extern "C" fn global_this_set_timeout_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, delay: f64, rest: f64, @@ -97,6 +100,7 @@ pub(crate) extern "C" fn global_this_set_timeout_thunk( pub(crate) extern "C" fn global_this_clear_timeout_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { crate::timer::js_clear_timeout_value(arg); @@ -105,6 +109,7 @@ pub(crate) extern "C" fn global_this_clear_timeout_thunk( pub(crate) extern "C" fn global_this_set_interval_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, delay: f64, rest: f64, @@ -127,6 +132,7 @@ pub(crate) extern "C" fn global_this_set_interval_thunk( pub(crate) extern "C" fn global_this_clear_interval_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { crate::timer::js_clear_interval_value(arg); @@ -135,6 +141,7 @@ pub(crate) extern "C" fn global_this_clear_interval_thunk( pub(crate) extern "C" fn global_this_set_immediate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { @@ -151,6 +158,7 @@ pub(crate) extern "C" fn global_this_set_immediate_thunk( pub(crate) extern "C" fn global_this_clear_immediate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { crate::timer::js_clear_immediate_value(arg); @@ -159,6 +167,7 @@ pub(crate) extern "C" fn global_this_clear_immediate_thunk( pub(crate) extern "C" fn global_this_queue_microtask_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, ) -> f64 { let callback = unsafe { crate::timer::js_timer_validate_callback(callback, 3) }; @@ -182,6 +191,7 @@ pub(crate) extern "C" fn global_this_queue_microtask_thunk( /// `undefined`, so the `.call` access threw before the IIFE body ran. pub(crate) extern "C" fn object_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { // Delegate to the canonical `js_object_to_string` so this callable form // (`const f = Object.prototype.toString; f.call(x)`) shares the full brand @@ -195,6 +205,7 @@ pub(crate) extern "C" fn object_prototype_to_string_thunk( pub(crate) extern "C" fn object_prototype_is_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -248,6 +259,7 @@ pub(crate) fn is_native_error_subclass_constructor(name: &str) -> bool { pub(crate) extern "C" fn date_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); // ECMA-262 21.4.4.41 Date.prototype.toString does `thisTimeValue(this)`, @@ -263,6 +275,7 @@ pub(crate) extern "C" fn date_prototype_to_string_thunk( pub(crate) extern "C" fn object_prototype_has_own_property_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -271,6 +284,7 @@ pub(crate) extern "C" fn object_prototype_has_own_property_thunk( pub(crate) extern "C" fn object_prototype_property_is_enumerable_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -282,6 +296,7 @@ pub(crate) extern "C" fn object_prototype_property_is_enumerable_thunk( // not just the direct `o.__defineGetter__(...)` native-dispatch path. pub(crate) extern "C" fn object_prototype_define_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, getter: f64, ) -> f64 { @@ -291,6 +306,7 @@ pub(crate) extern "C" fn object_prototype_define_getter_thunk( pub(crate) extern "C" fn object_prototype_define_setter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, setter: f64, ) -> f64 { @@ -300,6 +316,7 @@ pub(crate) extern "C" fn object_prototype_define_setter_thunk( pub(crate) extern "C" fn object_prototype_lookup_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -308,6 +325,7 @@ pub(crate) extern "C" fn object_prototype_lookup_getter_thunk( pub(crate) extern "C" fn object_prototype_lookup_setter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -316,6 +334,7 @@ pub(crate) extern "C" fn object_prototype_lookup_setter_thunk( pub(crate) extern "C" fn error_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); let this_jsv = crate::value::JSValue::from_bits(this_value.to_bits()); @@ -381,6 +400,7 @@ unsafe fn string_header_to_owned(ptr: *const crate::StringHeader) -> String { pub(crate) extern "C" fn object_prototype_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); unsafe { super::super::js_object_default_value_of(this_value) } @@ -388,6 +408,7 @@ pub(crate) extern "C" fn object_prototype_value_of_thunk( pub(crate) extern "C" fn object_prototype_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); unsafe { super::super::js_object_prototype_to_locale_string(this_value) } @@ -570,6 +591,7 @@ pub(crate) unsafe fn function_apply_proxy(target: f64, receiver: f64, args_array pub(crate) extern "C" fn function_prototype_apply_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, this_arg: f64, args_array: f64, ) -> f64 { @@ -605,6 +627,7 @@ pub(crate) extern "C" fn function_prototype_apply_thunk( /// `Object.prototype.toString` (which keeps its own real thunk). pub(crate) extern "C" fn function_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this_bits = IMPLICIT_THIS.with(|c| c.get()); let this_jsv = JSValue::from_bits(this_bits); @@ -662,6 +685,7 @@ pub(crate) extern "C" fn function_prototype_to_string_thunk( /// `Cannot read properties of undefined (reading 'call')` at module init. pub(crate) extern "C" fn array_prototype_slice_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, start_val: f64, end_val: f64, ) -> f64 { @@ -738,6 +762,7 @@ pub(crate) extern "C" fn array_prototype_slice_thunk( /// iterate the receiver they are given. pub(crate) extern "C" fn array_prototype_values_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -746,6 +771,7 @@ pub(crate) extern "C" fn array_prototype_values_thunk( pub(crate) extern "C" fn array_prototype_flat_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, depth: f64, ) -> f64 { crate::array::js_arraylike_flat(crate::object::js_implicit_this_get(), depth) @@ -753,6 +779,7 @@ pub(crate) extern "C" fn array_prototype_flat_thunk( pub(crate) extern "C" fn array_prototype_pop_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -760,6 +787,7 @@ pub(crate) extern "C" fn array_prototype_pop_thunk( } pub(crate) extern "C" fn array_prototype_shift_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -767,6 +795,7 @@ pub(crate) extern "C" fn array_prototype_shift_thunk( } pub(crate) extern "C" fn array_prototype_reverse_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -774,6 +803,7 @@ pub(crate) extern "C" fn array_prototype_reverse_thunk( } pub(crate) extern "C" fn array_prototype_push_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -782,6 +812,7 @@ pub(crate) extern "C" fn array_prototype_push_thunk( } pub(crate) extern "C" fn array_prototype_unshift_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -790,6 +821,7 @@ pub(crate) extern "C" fn array_prototype_unshift_thunk( } pub(crate) extern "C" fn array_prototype_splice_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -798,6 +830,7 @@ pub(crate) extern "C" fn array_prototype_splice_thunk( } pub(crate) extern "C" fn array_prototype_sort_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, comparator: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -814,6 +847,7 @@ pub(crate) extern "C" fn array_prototype_sort_thunk( /// the `end` params the spec resolves `undefined` to `len`). pub(crate) extern "C" fn array_prototype_fill_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -832,6 +866,7 @@ pub(crate) extern "C" fn array_prototype_fill_thunk( } pub(crate) extern "C" fn array_prototype_copy_within_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -858,7 +893,11 @@ pub(crate) extern "C" fn array_prototype_copy_within_thunk( macro_rules! array_proto_arraylike_cb_thunk { ($name:ident, $engine:path) => { #[allow(non_snake_case)] // thunk name mirrors JS API surface - pub(crate) extern "C" fn $name(_c: *const crate::closure::ClosureHeader, rest: f64) -> f64 { + pub(crate) extern "C" fn $name( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, + ) -> f64 { let this = crate::object::js_implicit_this_get(); let args = global_this_rest_array_values(rest); let a = |i: usize| { @@ -895,7 +934,11 @@ array_proto_arraylike_cb_thunk!( macro_rules! array_proto_arraylike_optarg_thunk { ($name:ident, $engine:path) => { #[allow(non_snake_case)] // thunk name mirrors JS API surface - pub(crate) extern "C" fn $name(_c: *const crate::closure::ClosureHeader, rest: f64) -> f64 { + pub(crate) extern "C" fn $name( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, + ) -> f64 { let this = crate::object::js_implicit_this_get(); let args = global_this_rest_array_values(rest); let a = |i: usize| { @@ -921,7 +964,11 @@ fn reduce_right_engine(recv: f64, cb: f64, has_init: i32, init: f64) -> f64 { macro_rules! array_proto_arraylike_search_thunk { ($name:ident, $engine:path) => { #[allow(non_snake_case)] // thunk name mirrors JS API surface - pub(crate) extern "C" fn $name(_c: *const crate::closure::ClosureHeader, rest: f64) -> f64 { + pub(crate) extern "C" fn $name( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, + ) -> f64 { let this = crate::object::js_implicit_this_get(); let args = global_this_rest_array_values(rest); let a = |i: usize| { @@ -948,6 +995,7 @@ array_proto_arraylike_search_thunk!( pub(crate) extern "C" fn array_proto_at_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, idx: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -955,6 +1003,7 @@ pub(crate) extern "C" fn array_proto_at_thunk( } pub(crate) extern "C" fn array_proto_join_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, sep: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -968,6 +1017,7 @@ pub(crate) extern "C" fn array_proto_join_thunk( /// receiver semantics; rewriting it to `x.toString()` loses the Array method. pub(crate) extern "C" fn array_prototype_to_string_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); crate::value::array_prototype_to_string(this) @@ -975,6 +1025,7 @@ pub(crate) extern "C" fn array_prototype_to_string_thunk( pub(crate) extern "C" fn array_prototype_concat_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); 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 aabf57db76..50cc7e2211 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 @@ -1,4 +1,9 @@ -extern "C" fn add(_closure: *const crate::closure::ClosureHeader, a: f64, b: f64) -> f64 { +extern "C" fn add( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + a: f64, + b: f64, +) -> f64 { a + b } 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 d03cb60106..2821f672d9 100644 --- a/crates/perry-runtime/src/object/global_this/bigint_promise.rs +++ b/crates/perry-runtime/src/object/global_this/bigint_promise.rs @@ -10,6 +10,7 @@ fn nanbox_array_or_undef(arr: *mut crate::array::ArrayHeader) -> f64 { pub(crate) extern "C" fn object_keys_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { nanbox_array_or_undef(super::super::js_object_keys_value(value)) @@ -17,6 +18,7 @@ pub(crate) extern "C" fn object_keys_thunk( pub(crate) extern "C" fn object_values_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { nanbox_array_or_undef(super::super::js_object_values_value(value)) @@ -24,6 +26,7 @@ pub(crate) extern "C" fn object_values_thunk( pub(crate) extern "C" fn object_entries_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { nanbox_array_or_undef(super::super::js_object_entries_value(value)) @@ -31,6 +34,7 @@ pub(crate) extern "C" fn object_entries_thunk( pub(crate) extern "C" fn object_freeze_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_freeze(value) @@ -38,6 +42,7 @@ pub(crate) extern "C" fn object_freeze_thunk( pub(crate) extern "C" fn object_create_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, props: f64, ) -> f64 { @@ -50,6 +55,7 @@ pub(crate) extern "C" fn object_create_thunk( pub(crate) extern "C" fn object_seal_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_seal(value) @@ -57,6 +63,7 @@ pub(crate) extern "C" fn object_seal_thunk( pub(crate) extern "C" fn object_is_sealed_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_is_sealed(value) @@ -64,6 +71,7 @@ pub(crate) extern "C" fn object_is_sealed_thunk( pub(crate) extern "C" fn object_is_frozen_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_is_frozen(value) @@ -71,6 +79,7 @@ pub(crate) extern "C" fn object_is_frozen_thunk( pub(crate) extern "C" fn object_is_extensible_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_is_extensible(value) @@ -78,6 +87,7 @@ pub(crate) extern "C" fn object_is_extensible_thunk( pub(crate) extern "C" fn object_prevent_extensions_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_prevent_extensions(value) @@ -85,6 +95,7 @@ pub(crate) extern "C" fn object_prevent_extensions_thunk( pub(crate) extern "C" fn object_is_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, ) -> f64 { @@ -93,6 +104,7 @@ pub(crate) extern "C" fn object_is_thunk( pub(crate) extern "C" fn object_set_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, proto: f64, ) -> f64 { @@ -101,6 +113,7 @@ pub(crate) extern "C" fn object_set_prototype_of_thunk( pub(crate) extern "C" fn object_get_own_property_symbols_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let arr = unsafe { crate::symbol::js_object_get_own_property_symbols(value) }; @@ -109,6 +122,7 @@ pub(crate) extern "C" fn object_get_own_property_symbols_thunk( pub(crate) extern "C" fn object_get_own_property_descriptors_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_get_own_property_descriptors(value) @@ -116,6 +130,7 @@ pub(crate) extern "C" fn object_get_own_property_descriptors_thunk( pub(crate) extern "C" fn object_define_properties_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, descriptors: f64, ) -> f64 { @@ -124,6 +139,7 @@ pub(crate) extern "C" fn object_define_properties_thunk( pub(crate) extern "C" fn object_group_by_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, items: f64, callback: f64, ) -> f64 { @@ -132,6 +148,7 @@ pub(crate) extern "C" fn object_group_by_thunk( pub(crate) extern "C" fn object_get_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_get_prototype_of(value) @@ -139,6 +156,7 @@ pub(crate) extern "C" fn object_get_prototype_of_thunk( pub(crate) extern "C" fn object_get_own_property_names_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_get_own_property_names(value) @@ -146,6 +164,7 @@ pub(crate) extern "C" fn object_get_own_property_names_thunk( pub(crate) extern "C" fn object_get_own_property_descriptor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, key: f64, ) -> f64 { @@ -154,6 +173,7 @@ pub(crate) extern "C" fn object_get_own_property_descriptor_thunk( pub(crate) extern "C" fn object_define_property_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, key: f64, descriptor: f64, @@ -163,6 +183,7 @@ pub(crate) extern "C" fn object_define_property_thunk( pub(crate) extern "C" fn object_from_entries_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { super::super::js_object_from_entries(value) @@ -170,6 +191,7 @@ pub(crate) extern "C" fn object_from_entries_thunk( pub(crate) extern "C" fn object_assign_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, rest: f64, ) -> f64 { @@ -187,6 +209,7 @@ pub(crate) extern "C" fn object_assign_thunk( /// `Object.prototype.hasOwnProperty.call(obj, key)`. pub(crate) extern "C" fn object_hasown_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, key: f64, ) -> f64 { @@ -195,6 +218,7 @@ pub(crate) extern "C" fn object_hasown_thunk( pub(crate) extern "C" fn array_is_array_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::array::js_array_is_array(value) @@ -202,6 +226,7 @@ pub(crate) extern "C" fn array_is_array_thunk( pub(crate) extern "C" fn array_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { // Reflective `Array.from.call(C, items)` / `Array.from.apply(C, [items])` @@ -217,6 +242,7 @@ pub(crate) extern "C" fn array_from_thunk( pub(crate) extern "C" fn array_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { // Reflective `Array.of.call(C, ...items)` binds `C` as the implicit `this`. @@ -230,6 +256,7 @@ pub(crate) extern "C" fn array_of_thunk( pub(crate) extern "C" fn number_is_nan_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_number_is_nan(value) @@ -237,6 +264,7 @@ pub(crate) extern "C" fn number_is_nan_thunk( pub(crate) extern "C" fn number_is_finite_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_number_is_finite(value) @@ -244,6 +272,7 @@ pub(crate) extern "C" fn number_is_finite_thunk( pub(crate) extern "C" fn number_is_integer_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_number_is_integer(value) @@ -340,6 +369,7 @@ pub extern "C" fn js_bigint_as_uint_n_call(bits: f64, value: f64) -> f64 { pub(crate) extern "C" fn bigint_as_int_n_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bits: f64, value: f64, ) -> f64 { @@ -348,6 +378,7 @@ pub(crate) extern "C" fn bigint_as_int_n_thunk( pub(crate) extern "C" fn bigint_as_uint_n_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bits: f64, value: f64, ) -> f64 { @@ -356,6 +387,7 @@ pub(crate) extern "C" fn bigint_as_uint_n_thunk( pub(crate) extern "C" fn json_parse_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, text: f64, reviver: f64, ) -> f64 { @@ -378,6 +410,7 @@ pub(crate) extern "C" fn json_parse_thunk( pub(crate) extern "C" fn json_stringify_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, replacer: f64, space: f64, @@ -387,6 +420,7 @@ pub(crate) extern "C" fn json_stringify_thunk( pub(crate) extern "C" fn json_raw_json_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, text: f64, ) -> f64 { unsafe { crate::json::js_json_raw_json(text) } @@ -394,6 +428,7 @@ pub(crate) extern "C" fn json_raw_json_thunk( pub(crate) extern "C" fn json_is_raw_json_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { unsafe { crate::json::js_json_is_raw_json(value) } @@ -401,6 +436,7 @@ pub(crate) extern "C" fn json_is_raw_json_thunk( pub(crate) extern "C" fn reflect_apply_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, this_arg: f64, args: f64, @@ -419,6 +455,7 @@ pub(crate) extern "C" fn reflect_apply_thunk( /// `undefined`, which it already resolves to `target` per spec. pub(crate) extern "C" fn reflect_construct_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, args_like: f64, new_target: f64, @@ -434,6 +471,7 @@ pub(crate) extern "C" fn reflect_construct_thunk( /// class-static-init time and threw on destructuring the `undefined`). pub(crate) extern "C" fn reflect_own_keys_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, ) -> f64 { crate::proxy::js_reflect_own_keys(target) @@ -441,6 +479,7 @@ pub(crate) extern "C" fn reflect_own_keys_thunk( pub(crate) extern "C" fn reflect_get_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, receiver: f64, @@ -450,6 +489,7 @@ pub(crate) extern "C" fn reflect_get_thunk( pub(crate) extern "C" fn reflect_set_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, value: f64, @@ -460,6 +500,7 @@ pub(crate) extern "C" fn reflect_set_thunk( pub(crate) extern "C" fn reflect_has_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, ) -> f64 { @@ -468,6 +509,7 @@ pub(crate) extern "C" fn reflect_has_thunk( pub(crate) extern "C" fn reflect_delete_property_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, ) -> f64 { @@ -476,6 +518,7 @@ pub(crate) extern "C" fn reflect_delete_property_thunk( pub(crate) extern "C" fn reflect_define_property_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, descriptor: f64, @@ -485,6 +528,7 @@ pub(crate) extern "C" fn reflect_define_property_thunk( pub(crate) extern "C" fn reflect_get_own_property_descriptor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, key: f64, ) -> f64 { @@ -493,6 +537,7 @@ pub(crate) extern "C" fn reflect_get_own_property_descriptor_thunk( pub(crate) extern "C" fn reflect_get_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, ) -> f64 { crate::proxy::js_reflect_get_prototype_of(target) @@ -500,6 +545,7 @@ pub(crate) extern "C" fn reflect_get_prototype_of_thunk( pub(crate) extern "C" fn reflect_set_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, proto: f64, ) -> f64 { @@ -508,6 +554,7 @@ pub(crate) extern "C" fn reflect_set_prototype_of_thunk( pub(crate) extern "C" fn reflect_is_extensible_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, ) -> f64 { crate::proxy::js_reflect_is_extensible(target) @@ -515,6 +562,7 @@ pub(crate) extern "C" fn reflect_is_extensible_thunk( pub(crate) extern "C" fn reflect_prevent_extensions_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, ) -> f64 { crate::proxy::js_reflect_prevent_extensions(target) @@ -522,6 +570,7 @@ pub(crate) extern "C" fn reflect_prevent_extensions_thunk( pub(crate) extern "C" fn symbol_for_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, key: f64, ) -> f64 { unsafe { crate::symbol::js_symbol_for(key) } @@ -529,6 +578,7 @@ pub(crate) extern "C" fn symbol_for_thunk( pub(crate) extern "C" fn symbol_key_for_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, symbol: f64, ) -> f64 { unsafe { crate::symbol::js_symbol_key_for(symbol) } @@ -536,6 +586,7 @@ pub(crate) extern "C" fn symbol_key_for_thunk( pub(crate) extern "C" fn number_is_safe_integer_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_number_is_safe_integer(value) @@ -546,6 +597,7 @@ pub(crate) extern "C" fn number_is_safe_integer_thunk( // the array-like the array-form runtime helpers expect. pub(crate) extern "C" fn string_from_char_code_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let s = crate::string::js_string_from_char_code_array(rest); @@ -554,6 +606,7 @@ pub(crate) extern "C" fn string_from_char_code_static( pub(crate) extern "C" fn string_from_code_point_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let s = crate::string::js_string_from_code_point_array(rest); @@ -571,6 +624,7 @@ pub(crate) extern "C" fn string_from_code_point_static( // thunks intentionally use the native Promise machinery regardless of `this`. extern "C" fn promise_resolve_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); @@ -579,6 +633,7 @@ extern "C" fn promise_resolve_static( extern "C" fn promise_reject_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); @@ -587,6 +642,7 @@ extern "C" fn promise_reject_static( extern "C" fn promise_all_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, iterable: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); @@ -595,6 +651,7 @@ extern "C" fn promise_all_static( extern "C" fn promise_race_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, iterable: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); @@ -603,6 +660,7 @@ extern "C" fn promise_race_static( extern "C" fn promise_all_settled_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, iterable: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); @@ -611,13 +669,17 @@ extern "C" fn promise_all_settled_static( extern "C" fn promise_any_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, iterable: f64, ) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); crate::promise::js_promise_any_spec(this_ctor, iterable) } -extern "C" fn promise_with_resolvers_static(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn promise_with_resolvers_static( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this_ctor = crate::object::js_implicit_this_get(); crate::promise::js_promise_with_resolvers_spec(this_ctor) } @@ -625,6 +687,7 @@ extern "C" fn promise_with_resolvers_static(_closure: *const crate::closure::Clo // `Promise.try(fn, ...args)`: call-arity 1 (callback) + rest (forwarded args). extern "C" fn promise_try_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { @@ -638,6 +701,7 @@ extern "C" fn promise_try_static( // passed straight through as the substitutions array-like. pub(crate) extern "C" fn string_raw_static( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, call_site: f64, rest: f64, ) -> f64 { @@ -647,21 +711,24 @@ pub(crate) extern "C" fn string_raw_static( pub(crate) extern "C" fn number_parse_float_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { - global_this_parse_float_thunk(closure, value) + global_this_parse_float_thunk(closure, crate::closure::JsThis::current(), value) } pub(crate) extern "C" fn number_parse_int_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, radix: f64, ) -> f64 { - global_this_parse_int_thunk(closure, value, radix) + global_this_parse_int_thunk(closure, crate::closure::JsThis::current(), value, radix) } pub(crate) extern "C" fn typed_array_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, source: f64, map_fn: f64, this_arg: f64, @@ -824,6 +891,7 @@ fn typed_array_create_from_values( pub(crate) extern "C" fn typed_array_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let kind_opt = typed_array_constructor_this_kind(); 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 0f5cfc3eac..a7a33b5359 100644 --- a/crates/perry-runtime/src/object/global_this/builtin_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/builtin_thunks.rs @@ -2,6 +2,7 @@ use super::*; pub(crate) extern "C" fn global_this_array_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let rest_value = crate::value::JSValue::from_bits(rest.to_bits()); @@ -29,6 +30,7 @@ pub(crate) extern "C" fn global_this_array_thunk( pub(crate) extern "C" fn global_this_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let string_ptr = crate::builtins::js_string_coerce(value); @@ -37,6 +39,7 @@ pub(crate) extern "C" fn global_this_string_thunk( pub(crate) extern "C" fn global_this_object_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::object::js_object_coerce(value) @@ -44,6 +47,7 @@ pub(crate) extern "C" fn global_this_object_thunk( pub(crate) extern "C" fn global_this_structured_clone_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _options: f64, ) -> f64 { @@ -52,6 +56,7 @@ pub(crate) extern "C" fn global_this_structured_clone_thunk( pub(crate) extern "C" fn global_this_atob_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let decoded = crate::string::js_atob(value); @@ -60,6 +65,7 @@ pub(crate) extern "C" fn global_this_atob_thunk( pub(crate) extern "C" fn global_this_btoa_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let encoded = crate::string::js_btoa(value); @@ -68,12 +74,16 @@ pub(crate) extern "C" fn global_this_btoa_thunk( pub(crate) extern "C" fn math_f16round_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::math::js_math_f16round(value) } -pub(crate) extern "C" fn math_random_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +pub(crate) extern "C" fn math_random_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::math::js_math_random() } @@ -98,6 +108,7 @@ macro_rules! math_unary_thunk { ($name:ident, $body:expr) => { pub(crate) extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let x = math_number_arg(value); @@ -134,6 +145,7 @@ math_unary_thunk!(math_trunc_thunk, |x: f64| x.trunc()); pub(crate) extern "C" fn math_round_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let x = math_number_arg(value); @@ -164,6 +176,7 @@ pub(crate) fn js_math_round_value(x: f64) -> f64 { pub(crate) extern "C" fn math_sign_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::math::js_math_sign(value) @@ -171,6 +184,7 @@ pub(crate) extern "C" fn math_sign_thunk( pub(crate) extern "C" fn math_clz32_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { math_to_uint32(value).leading_zeros() as f64 @@ -178,6 +192,7 @@ pub(crate) extern "C" fn math_clz32_thunk( pub(crate) extern "C" fn math_atan2_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, y: f64, x: f64, ) -> f64 { @@ -186,6 +201,7 @@ pub(crate) extern "C" fn math_atan2_thunk( pub(crate) extern "C" fn math_imul_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, ) -> f64 { @@ -194,6 +210,7 @@ pub(crate) extern "C" fn math_imul_thunk( pub(crate) extern "C" fn math_pow_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, base: f64, exp: f64, ) -> f64 { @@ -202,6 +219,7 @@ pub(crate) extern "C" fn math_pow_thunk( pub(crate) extern "C" fn math_min_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let values = global_this_rest_array_values(rest); @@ -227,6 +245,7 @@ pub(crate) extern "C" fn math_min_thunk( pub(crate) extern "C" fn math_max_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let values = global_this_rest_array_values(rest); @@ -252,6 +271,7 @@ pub(crate) extern "C" fn math_max_thunk( pub(crate) extern "C" fn math_hypot_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let mut result = 0.0; @@ -267,6 +287,7 @@ pub(crate) extern "C" fn math_hypot_thunk( pub(crate) extern "C" fn global_this_parse_int_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, radix: f64, ) -> f64 { @@ -276,6 +297,7 @@ pub(crate) extern "C" fn global_this_parse_int_thunk( pub(crate) extern "C" fn global_this_parse_float_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let s = crate::builtins::js_string_coerce(value); @@ -284,6 +306,7 @@ pub(crate) extern "C" fn global_this_parse_float_thunk( pub(crate) extern "C" fn global_this_is_nan_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_is_nan(value) @@ -297,6 +320,7 @@ pub(crate) extern "C" fn global_this_is_nan_thunk( /// but ignored. Returns `undefined`. pub(crate) extern "C" fn global_this_gc_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _force: f64, ) -> f64 { crate::gc::js_gc_collect(); @@ -305,6 +329,7 @@ pub(crate) extern "C" fn global_this_gc_thunk( pub(crate) extern "C" fn global_this_is_finite_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::builtins::js_is_finite(value) @@ -312,6 +337,7 @@ pub(crate) extern "C" fn global_this_is_finite_thunk( pub(crate) extern "C" fn global_this_encode_uri_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_encode_uri(value)) @@ -319,6 +345,7 @@ pub(crate) extern "C" fn global_this_encode_uri_thunk( pub(crate) extern "C" fn global_this_decode_uri_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_decode_uri(value)) @@ -326,6 +353,7 @@ pub(crate) extern "C" fn global_this_decode_uri_thunk( pub(crate) extern "C" fn global_this_encode_uri_component_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_encode_uri_component(value)) @@ -333,6 +361,7 @@ pub(crate) extern "C" fn global_this_encode_uri_component_thunk( pub(crate) extern "C" fn global_this_decode_uri_component_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_decode_uri_component(value)) @@ -343,6 +372,7 @@ pub(crate) extern "C" fn global_this_decode_uri_component_thunk( // needs them as real callable globalThis function values. pub(crate) extern "C" fn global_this_escape_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_escape(value)) @@ -350,6 +380,7 @@ pub(crate) extern "C" fn global_this_escape_thunk( pub(crate) extern "C" fn global_this_unescape_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::value::js_nanbox_string(crate::builtins::js_unescape(value)) @@ -361,6 +392,7 @@ pub(crate) extern "C" fn global_this_unescape_thunk( // `const N = Number; N("42")` and `const B = Boolean; B(0)` match Node. pub(crate) extern "C" fn global_this_number_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let jsv = crate::value::JSValue::from_bits(value.to_bits()); @@ -375,6 +407,7 @@ pub(crate) extern "C" fn global_this_number_thunk( pub(crate) extern "C" fn global_this_boolean_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let b = crate::value::js_is_truthy(value) != 0; @@ -383,6 +416,7 @@ pub(crate) extern "C" fn global_this_boolean_thunk( pub(crate) extern "C" fn global_this_error_capture_stack_trace_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, constructor_opt: f64, ) -> f64 { @@ -392,6 +426,7 @@ pub(crate) extern "C" fn global_this_error_capture_stack_trace_thunk( /// #2904: `Error.isError(value)` thunk — delegates to the runtime duck-check. pub(crate) extern "C" fn global_this_error_is_error_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { crate::error::js_error_is_error(value) @@ -546,6 +581,7 @@ fn js_function_ctor_from_strings_impl(args_ptr: *const f64, args_len: usize) -> #[cfg(not(panic = "abort"))] pub(crate) extern "C-unwind" fn global_this_function_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { function_call_thunk_impl(rest) @@ -554,6 +590,7 @@ pub(crate) extern "C-unwind" fn global_this_function_call_thunk( #[cfg(panic = "abort")] pub(crate) extern "C" fn global_this_function_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { function_call_thunk_impl(rest) @@ -585,6 +622,7 @@ fn refuse_dynamic_function(args_len: usize, body: &str) -> ! { /// real one with identical `this`/arguments. extern "C" fn depd_wrapfunction_outer_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, fn_v: f64, _log: f64, _deprecate: f64, @@ -621,6 +659,7 @@ const _: extern "C" fn(*const f64, usize) -> f64 = js_function_ctor_from_strings /// callers that invoke it get a usable string rather than a crash. pub(crate) extern "C" fn global_this_error_prepare_stack_trace_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, error: f64, _structured_stack: f64, ) -> f64 { @@ -642,6 +681,7 @@ pub(crate) extern "C" fn global_this_error_prepare_stack_trace_thunk( /// existing `js_proxy_revocable` implementation in `crate::proxy`. pub(crate) extern "C" fn proxy_revocable_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, handler: f64, ) -> f64 { @@ -712,7 +752,11 @@ mod tests { array_value(&[string_value("a"), string_value("b")]), string_value("return a + b"), ]); - let f = global_this_function_call_thunk(std::ptr::null(), rest); + let f = global_this_function_call_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + rest, + ); let args = [ f64::from_bits(crate::value::JSValue::number(2.0).bits()), f64::from_bits(crate::value::JSValue::number(3.0).bits()), 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 5fae385daf..82c120e9f2 100644 --- a/crates/perry-runtime/src/object/global_this/ctor_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/ctor_thunks.rs @@ -19,6 +19,7 @@ pub(crate) fn normalize_eval_this_body(body: &str) -> Option { pub(crate) extern "C" fn typed_array_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor %TypedArray% requires 'new'") @@ -26,6 +27,7 @@ pub(crate) extern "C" fn typed_array_constructor_call_thunk( pub(crate) extern "C" fn construct_only_builtin_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor requires 'new'") } @@ -44,6 +46,7 @@ pub(crate) extern "C" fn construct_only_builtin_call_thunk( #[cfg(feature = "regex-engine")] pub(crate) extern "C" fn regexp_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, pattern: f64, flags: f64, ) -> f64 { @@ -55,6 +58,7 @@ pub(crate) extern "C" fn regexp_constructor_call_thunk( #[cfg(not(feature = "regex-engine"))] pub(crate) extern "C" fn regexp_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _pattern: f64, _flags: f64, ) -> f64 { @@ -68,6 +72,7 @@ pub(crate) extern "C" fn regexp_constructor_call_thunk( // construct-expression path and is unaffected.) pub(crate) extern "C" fn map_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor Map requires 'new'") @@ -75,6 +80,7 @@ pub(crate) extern "C" fn map_constructor_call_thunk( pub(crate) extern "C" fn set_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor Set requires 'new'") @@ -82,6 +88,7 @@ pub(crate) extern "C" fn set_constructor_call_thunk( pub(crate) extern "C" fn weak_map_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor WeakMap requires 'new'") @@ -89,6 +96,7 @@ pub(crate) extern "C" fn weak_map_constructor_call_thunk( pub(crate) extern "C" fn weak_set_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor WeakSet requires 'new'") @@ -96,6 +104,7 @@ pub(crate) extern "C" fn weak_set_constructor_call_thunk( pub(crate) extern "C" fn weak_ref_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor WeakRef requires 'new'") @@ -103,6 +112,7 @@ pub(crate) extern "C" fn weak_ref_constructor_call_thunk( pub(crate) extern "C" fn promise_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::super::object_ops::throw_object_type_error(b"Constructor Promise requires 'new'") @@ -110,6 +120,7 @@ pub(crate) extern "C" fn promise_constructor_call_thunk( pub(crate) extern "C" fn global_this_url_pattern_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, base: f64, ) -> f64 { @@ -123,6 +134,7 @@ fn error_constructor_call(kind: u32, message: f64) -> f64 { pub(crate) extern "C" fn error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_ERROR, message) @@ -130,6 +142,7 @@ pub(crate) extern "C" fn error_constructor_call_thunk( pub(crate) extern "C" fn type_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_TYPE_ERROR, message) @@ -137,6 +150,7 @@ pub(crate) extern "C" fn type_error_constructor_call_thunk( pub(crate) extern "C" fn range_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_RANGE_ERROR, message) @@ -144,6 +158,7 @@ pub(crate) extern "C" fn range_error_constructor_call_thunk( pub(crate) extern "C" fn reference_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_REFERENCE_ERROR, message) @@ -151,6 +166,7 @@ pub(crate) extern "C" fn reference_error_constructor_call_thunk( pub(crate) extern "C" fn syntax_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_SYNTAX_ERROR, message) @@ -158,6 +174,7 @@ pub(crate) extern "C" fn syntax_error_constructor_call_thunk( pub(crate) extern "C" fn eval_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_EVAL_ERROR, message) @@ -165,6 +182,7 @@ pub(crate) extern "C" fn eval_error_constructor_call_thunk( pub(crate) extern "C" fn uri_error_constructor_call_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { error_constructor_call(crate::error::ERROR_KIND_URI_ERROR, message) @@ -278,6 +296,7 @@ pub(crate) fn is_array_prototype_method_value(value: f64, method: &str) -> bool pub(crate) extern "C" fn webcrypto_illegal_constructor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { crate::fs::validate::throw_type_error_with_code( "Illegal constructor", @@ -295,6 +314,7 @@ pub extern "C" fn js_webcrypto_illegal_constructor() -> f64 { pub(crate) extern "C" fn global_this_crypto_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { super::super::native_module::webcrypto_namespace() } @@ -320,6 +340,7 @@ fn require_webcrypto_this() -> f64 { pub(crate) extern "C" fn webcrypto_get_random_values_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, array: f64, ) -> f64 { let this_value = require_webcrypto_this(); @@ -340,6 +361,7 @@ pub(crate) extern "C" fn webcrypto_get_random_values_thunk( /// name through the whole native-call tower on every UUID. pub(crate) extern "C" fn webcrypto_random_uuid_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { require_webcrypto_this(); let ptr = crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); @@ -359,6 +381,7 @@ pub(crate) extern "C" fn webcrypto_random_uuid_thunk( pub(crate) extern "C" fn webcrypto_subtle_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { require_webcrypto_this(); super::super::native_module::subtle_crypto_namespace() @@ -394,24 +417,28 @@ fn cryptokey_property_getter(key: &[u8]) -> f64 { pub(crate) extern "C" fn cryptokey_algorithm_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { cryptokey_property_getter(b"algorithm") } pub(crate) extern "C" fn cryptokey_extractable_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { cryptokey_property_getter(b"extractable") } pub(crate) extern "C" fn cryptokey_type_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { cryptokey_property_getter(b"type") } pub(crate) extern "C" fn cryptokey_usages_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { cryptokey_property_getter(b"usages") } 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 488b43b269..df35ec13b7 100644 --- a/crates/perry-runtime/src/object/global_this/fetch_globals.rs +++ b/crates/perry-runtime/src/object/global_this/fetch_globals.rs @@ -193,6 +193,7 @@ pub unsafe extern "C" fn js_global_or_console_property_by_name( #[inline(never)] pub(crate) extern "C" fn global_this_builtin_noop_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { std::hint::black_box(global_this_builtin_noop_thunk as *const u8); @@ -201,6 +202,7 @@ pub(crate) extern "C" fn global_this_builtin_noop_thunk( pub(crate) extern "C" fn global_this_date_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { let string = crate::date::js_date_to_string(crate::date::js_date_new()); @@ -268,6 +270,7 @@ fn global_this_init_headers_handle(init: f64) -> f64 { pub(crate) extern "C" fn global_this_blob_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, parts: f64, options: f64, ) -> f64 { @@ -277,6 +280,7 @@ pub(crate) extern "C" fn global_this_blob_thunk( pub(crate) extern "C" fn global_this_file_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, parts: f64, name: f64, options: f64, @@ -293,6 +297,7 @@ pub(crate) extern "C" fn global_this_file_thunk( pub(crate) extern "C" fn global_this_headers_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, init: f64, ) -> f64 { let headers = super::super::global_fetch::call_global_headers_new(); @@ -307,6 +312,7 @@ pub(crate) extern "C" fn global_this_headers_thunk( pub(crate) extern "C" fn global_this_response_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, body: f64, init: f64, ) -> f64 { @@ -341,6 +347,7 @@ pub(crate) extern "C" fn global_this_response_thunk( pub(crate) extern "C" fn global_this_request_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, init: f64, ) -> f64 { @@ -464,9 +471,19 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( undef }; let handle = if kind == 1 { - global_this_request_thunk(std::ptr::null(), arg0, arg1) + global_this_request_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + arg0, + arg1, + ) } else { - global_this_response_thunk(std::ptr::null(), arg0, arg1) + global_this_response_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + arg0, + arg1, + ) }; let this_box = crate::value::js_nanbox_pointer(inst as i64); attach_fetch_handle_to_this(this_box, handle); @@ -478,7 +495,12 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( /// `undefined` (the super-call value). #[no_mangle] pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) -> f64 { - let handle = global_this_request_thunk(std::ptr::null(), input, init); + let handle = global_this_request_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + input, + init, + ); unsafe { attach_fetch_handle_to_this(this_box, handle) }; f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -487,7 +509,12 @@ pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) /// `js_request_subclass_init` for the Response handle. #[no_mangle] pub extern "C" fn js_response_subclass_init(this_box: f64, body: f64, init: f64) -> f64 { - let handle = global_this_response_thunk(std::ptr::null(), body, init); + let handle = global_this_response_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + body, + init, + ); unsafe { attach_fetch_handle_to_this(this_box, handle) }; f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -1002,9 +1029,19 @@ pub unsafe extern "C" fn js_fetch_or_value_super( undef }; let handle = if kind == Some("Request") { - global_this_request_thunk(std::ptr::null(), arg0, arg1) + global_this_request_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + arg0, + arg1, + ) } else { - global_this_response_thunk(std::ptr::null(), arg0, arg1) + global_this_response_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + arg0, + arg1, + ) }; attach_fetch_handle_to_this(this_box, handle); undef @@ -1135,12 +1172,14 @@ static KEEP_JS_FETCH_OR_VALUE_SUPER: unsafe extern "C" fn(f64, f64, *const f64, pub(crate) extern "C" fn global_this_response_error_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { super::super::global_fetch::call_global_response_static_error() } pub(crate) extern "C" fn global_this_response_json_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, init: f64, ) -> f64 { @@ -1162,6 +1201,7 @@ pub(crate) extern "C" fn global_this_response_json_thunk( pub(crate) extern "C" fn global_this_response_redirect_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, url: f64, status: f64, ) -> f64 { @@ -1176,6 +1216,7 @@ pub(crate) extern "C" fn global_this_response_redirect_thunk( pub(crate) extern "C" fn global_this_eval_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, source: f64, ) -> f64 { // PerformEval step: "If Type(x) is not String, return x." A non-string @@ -1227,7 +1268,10 @@ pub(crate) extern "C" fn global_this_eval_thunk( mod dynamic_super_new_target_tests { use super::*; - extern "C" fn read_new_target(_closure: *const crate::ClosureHeader) -> f64 { + extern "C" fn read_new_target( + _closure: *const crate::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { crate::object::js_new_target_get() } diff --git a/crates/perry-runtime/src/object/global_this/generator.rs b/crates/perry-runtime/src/object/global_this/generator.rs index d5134d42aa..21c6236d98 100644 --- a/crates/perry-runtime/src/object/global_this/generator.rs +++ b/crates/perry-runtime/src/object/global_this/generator.rs @@ -397,36 +397,42 @@ fn generator_proto_method(method: &[u8], arg: f64, is_async: bool) -> f64 { extern "C" fn generator_proto_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"next", arg, false) } extern "C" fn generator_proto_return_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"return", arg, false) } extern "C" fn generator_proto_throw_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"throw", arg, false) } extern "C" fn async_generator_proto_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"next", arg, true) } extern "C" fn async_generator_proto_return_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"return", arg, true) } extern "C" fn async_generator_proto_throw_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { generator_proto_method(b"throw", arg, true) @@ -438,6 +444,7 @@ extern "C" fn async_generator_proto_throw_thunk( /// iterator and either throws or silently produces nothing. extern "C" fn async_generator_proto_async_iterator_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { crate::object::js_implicit_this_get() @@ -448,6 +455,7 @@ extern "C" fn async_generator_proto_async_iterator_thunk( /// note in `build_generator_tower` for why the sync prototype now carries this. extern "C" fn generator_proto_iterator_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { crate::object::js_implicit_this_get() 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 87fc702462..b5255d3f9c 100644 --- a/crates/perry-runtime/src/object/global_this/install_static.rs +++ b/crates/perry-runtime/src/object/global_this/install_static.rs @@ -51,6 +51,7 @@ pub extern "C" fn js_promise_static_function_value(name_ptr: *const u8, name_len extern "C" fn url_can_parse_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, base: f64, ) -> f64 { @@ -66,6 +67,7 @@ extern "C" fn url_can_parse_thunk( extern "C" fn url_parse_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, base: f64, ) -> f64 { @@ -91,6 +93,7 @@ extern "C" fn url_parse_thunk( // compile-time `Uint8Array.fromBase64(str)` call path produces. extern "C" fn uint8array_from_base64_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, opts: f64, ) -> f64 { @@ -100,6 +103,7 @@ extern "C" fn uint8array_from_base64_thunk( extern "C" fn uint8array_from_hex_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, ) -> f64 { let buf = crate::buffer::js_u8_from_hex(input.to_bits() as i64); @@ -108,6 +112,7 @@ extern "C" fn uint8array_from_hex_thunk( extern "C" fn subtle_crypto_supports_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let args = global_this_rest_array_values(rest); @@ -186,6 +191,7 @@ fn subtle_crypto_dispatch_rest(method_name: &str, rest: f64) -> f64 { pub(crate) extern "C" fn subtle_crypto_encapsulate_bits_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { subtle_crypto_dispatch_rest("encapsulateBits", rest) @@ -193,6 +199,7 @@ pub(crate) extern "C" fn subtle_crypto_encapsulate_bits_thunk( pub(crate) extern "C" fn subtle_crypto_decapsulate_bits_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { subtle_crypto_dispatch_rest("decapsulateBits", rest) @@ -200,6 +207,7 @@ pub(crate) extern "C" fn subtle_crypto_decapsulate_bits_thunk( pub(crate) extern "C" fn subtle_crypto_encapsulate_key_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { subtle_crypto_dispatch_rest("encapsulateKey", rest) @@ -207,6 +215,7 @@ pub(crate) extern "C" fn subtle_crypto_encapsulate_key_thunk( pub(crate) extern "C" fn subtle_crypto_decapsulate_key_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { subtle_crypto_dispatch_rest("decapsulateKey", rest) @@ -986,6 +995,7 @@ pub(crate) fn install_noop_proto_methods(proto_obj: *mut ObjectHeader, methods: pub(crate) extern "C" fn url_pattern_test_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, rest: f64, ) -> f64 { @@ -997,6 +1007,7 @@ pub(crate) extern "C" fn url_pattern_test_thunk( pub(crate) extern "C" fn url_pattern_exec_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, rest: f64, ) -> f64 { @@ -1024,6 +1035,7 @@ fn rest_first_arg(rest: f64) -> f64 { /// 27.2.4.8, 22.2.5.2, 23.2.2.4). pub(crate) extern "C" fn builtin_species_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { f64::from_bits(IMPLICIT_THIS.with(|c| c.get())) } 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 1ade09495d..39fdf6b921 100644 --- a/crates/perry-runtime/src/object/global_this/math_temporal.rs +++ b/crates/perry-runtime/src/object/global_this/math_temporal.rs @@ -86,6 +86,7 @@ pub(crate) fn install_math_namespace(ns_obj: *mut ObjectHeader) { #[cfg(feature = "temporal")] extern "C" fn temporal_duration_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::duration::construct(&global_this_rest_array_values(rest)) @@ -94,6 +95,7 @@ extern "C" fn temporal_duration_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_duration_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::duration::from_static(&global_this_rest_array_values(rest)) @@ -102,6 +104,7 @@ extern "C" fn temporal_duration_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_duration_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::duration::compare_static(&global_this_rest_array_values(rest)) @@ -110,6 +113,7 @@ extern "C" fn temporal_duration_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_instant_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::instant::construct(&global_this_rest_array_values(rest)) @@ -118,6 +122,7 @@ extern "C" fn temporal_instant_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_instant_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::instant::from_static(&global_this_rest_array_values(rest)) @@ -126,6 +131,7 @@ extern "C" fn temporal_instant_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_instant_from_epoch_ms_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::instant::from_epoch_milliseconds_static(&global_this_rest_array_values(rest)) @@ -134,6 +140,7 @@ extern "C" fn temporal_instant_from_epoch_ms_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_instant_from_epoch_ns_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::instant::from_epoch_nanoseconds_static(&global_this_rest_array_values(rest)) @@ -142,6 +149,7 @@ extern "C" fn temporal_instant_from_epoch_ns_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_instant_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::instant::compare_static(&global_this_rest_array_values(rest)) @@ -150,6 +158,7 @@ extern "C" fn temporal_instant_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date::construct(&global_this_rest_array_values(rest)) @@ -158,6 +167,7 @@ extern "C" fn temporal_plain_date_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date::from_static(&global_this_rest_array_values(rest)) @@ -166,6 +176,7 @@ extern "C" fn temporal_plain_date_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date::compare_static(&global_this_rest_array_values(rest)) @@ -174,6 +185,7 @@ extern "C" fn temporal_plain_date_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_time_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_time::construct(&global_this_rest_array_values(rest)) @@ -182,6 +194,7 @@ extern "C" fn temporal_plain_time_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_time_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_time::from_static(&global_this_rest_array_values(rest)) @@ -190,6 +203,7 @@ extern "C" fn temporal_plain_time_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_time_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_time::compare_static(&global_this_rest_array_values(rest)) @@ -198,6 +212,7 @@ extern "C" fn temporal_plain_time_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_time_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date_time::construct(&global_this_rest_array_values(rest)) @@ -206,6 +221,7 @@ extern "C" fn temporal_plain_date_time_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_time_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date_time::from_static(&global_this_rest_array_values(rest)) @@ -214,6 +230,7 @@ extern "C" fn temporal_plain_date_time_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_date_time_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_date_time::compare_static(&global_this_rest_array_values(rest)) @@ -222,6 +239,7 @@ extern "C" fn temporal_plain_date_time_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_year_month_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_year_month::construct(&global_this_rest_array_values(rest)) @@ -230,6 +248,7 @@ extern "C" fn temporal_plain_year_month_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_year_month_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_year_month::from_static(&global_this_rest_array_values(rest)) @@ -238,6 +257,7 @@ extern "C" fn temporal_plain_year_month_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_year_month_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_year_month::compare_static(&global_this_rest_array_values(rest)) @@ -246,6 +266,7 @@ extern "C" fn temporal_plain_year_month_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_month_day_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_month_day::construct(&global_this_rest_array_values(rest)) @@ -254,6 +275,7 @@ extern "C" fn temporal_plain_month_day_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_plain_month_day_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::plain_month_day::from_static(&global_this_rest_array_values(rest)) @@ -262,6 +284,7 @@ extern "C" fn temporal_plain_month_day_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_zoned_date_time_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::zoned_date_time::construct(&global_this_rest_array_values(rest)) @@ -270,6 +293,7 @@ extern "C" fn temporal_zoned_date_time_ctor_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_zoned_date_time_from_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::zoned_date_time::from_static(&global_this_rest_array_values(rest)) @@ -278,6 +302,7 @@ extern "C" fn temporal_zoned_date_time_from_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_zoned_date_time_compare_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::zoned_date_time::compare_static(&global_this_rest_array_values(rest)) @@ -288,6 +313,7 @@ extern "C" fn temporal_zoned_date_time_compare_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_instant_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::instant(&global_this_rest_array_values(rest)) @@ -296,6 +322,7 @@ extern "C" fn temporal_now_instant_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_timezone_id_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::time_zone_id(&global_this_rest_array_values(rest)) @@ -304,6 +331,7 @@ extern "C" fn temporal_now_timezone_id_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_plain_date_time_iso_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::plain_date_time_iso(&global_this_rest_array_values(rest)) @@ -312,6 +340,7 @@ extern "C" fn temporal_now_plain_date_time_iso_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_plain_date_iso_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::plain_date_iso(&global_this_rest_array_values(rest)) @@ -320,6 +349,7 @@ extern "C" fn temporal_now_plain_date_iso_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_plain_time_iso_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::plain_time_iso(&global_this_rest_array_values(rest)) @@ -328,6 +358,7 @@ extern "C" fn temporal_now_plain_time_iso_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_now_zoned_date_time_iso_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { crate::temporal::now::zoned_date_time_iso(&global_this_rest_array_values(rest)) @@ -382,7 +413,10 @@ fn build_temporal_now_namespace() -> f64 { /// Throws `TypeError` on a non-Temporal or wrong-brand receiver (the getter /// `branding.js` tests: `blank.call(undefined)`, `years.call({})`, …). #[cfg(feature = "temporal")] -extern "C" fn temporal_proto_getter_thunk(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn temporal_proto_getter_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let recv = super::super::js_implicit_this_get(); let cl = closure as usize; let kind = crate::closure::closure_get_dynamic_prop(cl, "__tkind"); @@ -408,6 +442,7 @@ extern "C" fn temporal_proto_getter_thunk(closure: *const crate::closure::Closur #[cfg(feature = "temporal")] extern "C" fn temporal_proto_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let recv = super::super::js_implicit_this_get(); @@ -606,6 +641,7 @@ fn temporal_brand_type_error(type_name: &str, member: &str) -> ! { #[cfg(feature = "temporal")] extern "C" fn temporal_zdt_proto_getter_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); // The accessor's name is `"get "`; recover the bare property. @@ -624,6 +660,7 @@ extern "C" fn temporal_zdt_proto_getter_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_zdt_proto_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); 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 40bade6aac..c387e709f6 100644 --- a/crates/perry-runtime/src/object/global_this/proto_methods.rs +++ b/crates/perry-runtime/src/object/global_this/proto_methods.rs @@ -15,12 +15,16 @@ fn web_method_receiver(name: &str) -> *mut ObjectHeader { crate::exception::js_throw(crate::value::js_nanbox_pointer(error as i64)) } -extern "C" fn url_prototype_href_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn url_prototype_href_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::url::js_url_get_href(web_method_receiver("URL")) } extern "C" fn abort_controller_prototype_abort_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { crate::url::js_abort_controller_abort_reason(web_method_receiver("AbortController"), reason); @@ -29,6 +33,7 @@ extern "C" fn abort_controller_prototype_abort_thunk( extern "C" fn abort_signal_prototype_throw_if_aborted_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { crate::url::js_abort_signal_throw_if_aborted(web_method_receiver("AbortSignal")) } @@ -227,6 +232,7 @@ fn install_object_prototype_dunder_proto(proto_obj: *mut ObjectHeader) { extern "C" fn object_prototype_dunder_proto_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { // Spec (Annex B §B.3.1 `get __proto__`): `ToObject(this).[[GetPrototypeOf]]()`. // `js_object_get_prototype_of` already implements exactly this shape — @@ -239,6 +245,7 @@ extern "C" fn object_prototype_dunder_proto_getter_thunk( extern "C" fn object_prototype_dunder_proto_setter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let receiver = crate::object::js_implicit_this_get(); 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 44b7d88a61..e275311e47 100644 --- a/crates/perry-runtime/src/object/global_this/typed_array.rs +++ b/crates/perry-runtime/src/object/global_this/typed_array.rs @@ -25,6 +25,7 @@ fn array_buffer_brand_error() -> ! { pub(crate) extern "C" fn array_buffer_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match array_buffer_receiver_addr() { Some(addr) => { @@ -60,6 +61,7 @@ fn shared_array_buffer_receiver_addr() -> Option { pub(crate) extern "C" fn shared_array_buffer_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match shared_array_buffer_receiver_addr() { Some(addr) => { @@ -81,6 +83,7 @@ pub(crate) extern "C" fn shared_array_buffer_byte_length_getter_thunk( /// is shared with the instance dispatch in `buffer_dispatch`. pub(crate) extern "C" fn shared_array_buffer_slice_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -97,6 +100,7 @@ pub(crate) extern "C" fn shared_array_buffer_slice_thunk( pub(crate) extern "C" fn array_buffer_slice_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { @@ -130,6 +134,7 @@ fn array_buffer_method_via_dispatch(method: &'static str, arg: f64, brand_error: pub(crate) extern "C" fn array_buffer_resize_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( @@ -141,6 +146,7 @@ pub(crate) extern "C" fn array_buffer_resize_thunk( pub(crate) extern "C" fn array_buffer_transfer_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( @@ -152,6 +158,7 @@ pub(crate) extern "C" fn array_buffer_transfer_thunk( pub(crate) extern "C" fn array_buffer_transfer_to_fixed_length_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( @@ -191,18 +198,21 @@ fn array_buffer_flag_getter(key: &[u8]) -> f64 { pub(crate) extern "C" fn array_buffer_resizable_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { array_buffer_flag_getter(b"resizable") } pub(crate) extern "C" fn array_buffer_max_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { array_buffer_flag_getter(b"maxByteLength") } pub(crate) extern "C" fn array_buffer_detached_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { array_buffer_flag_getter(b"detached") } @@ -292,6 +302,7 @@ pub(crate) unsafe fn validate_array_buffer_species_constructor(addr: usize) { pub(crate) extern "C" fn array_buffer_is_view_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { // #11239: the same classifier `util.types.isArrayBufferView` and the @@ -416,6 +427,7 @@ fn typed_array_buffer_value(receiver: TypedArrayAccessorReceiver) -> f64 { /// `%TypedArray%.prototype.length` getter — element count of the receiver. extern "C" fn typed_array_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match typed_array_receiver() { Some(receiver) => { @@ -428,6 +440,7 @@ extern "C" fn typed_array_length_getter_thunk( /// `%TypedArray%.prototype.byteLength` getter — `length * BYTES_PER_ELEMENT`. extern "C" fn typed_array_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match typed_array_receiver() { Some(receiver) => { @@ -440,6 +453,7 @@ extern "C" fn typed_array_byte_length_getter_thunk( /// `%TypedArray%.prototype.byteOffset` getter. extern "C" fn typed_array_byte_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match typed_array_receiver() { Some(receiver) => { @@ -453,6 +467,7 @@ extern "C" fn typed_array_byte_offset_getter_thunk( /// identity as the direct property path. extern "C" fn typed_array_buffer_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match typed_array_receiver() { Some(receiver) => typed_array_buffer_value(receiver), @@ -470,6 +485,7 @@ extern "C" fn typed_array_buffer_getter_thunk( /// `Cannot read properties of undefined (reading 'get')`. extern "C" fn typed_array_to_string_tag_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); match crate::object::typed_array_to_string_tag_name(this) { diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index e618f2cfa5..e3bfb2f84b 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -492,6 +492,7 @@ fn throw_requires_new(what: &str) -> ! { #[cfg(feature = "wasm-host")] extern "C" fn webassembly_compile_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bytes: f64, ) -> f64 { crate::webassembly::js_webassembly_compile(bytes) @@ -500,6 +501,7 @@ extern "C" fn webassembly_compile_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_compile_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _bytes: f64, ) -> f64 { wasm_unsupported_rejection("WebAssembly.compile") @@ -508,6 +510,7 @@ extern "C" fn webassembly_compile_thunk( #[cfg(feature = "wasm-host")] extern "C" fn webassembly_instantiate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bytes: f64, imports: f64, ) -> f64 { @@ -517,6 +520,7 @@ extern "C" fn webassembly_instantiate_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_instantiate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _bytes: f64, _imports: f64, ) -> f64 { @@ -526,6 +530,7 @@ extern "C" fn webassembly_instantiate_thunk( #[cfg(feature = "wasm-host")] extern "C" fn webassembly_validate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bytes: f64, ) -> f64 { crate::webassembly::js_webassembly_validate(bytes) @@ -534,6 +539,7 @@ extern "C" fn webassembly_validate_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_validate_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _bytes: f64, ) -> f64 { f64::from_bits(crate::value::JSValue::bool(false).bits()) @@ -543,6 +549,7 @@ extern "C" fn webassembly_validate_thunk( /// has no streaming path either, so these reject under both cfgs. extern "C" fn webassembly_compile_streaming_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _source: f64, ) -> f64 { wasm_unsupported_rejection("WebAssembly.compileStreaming") @@ -550,6 +557,7 @@ extern "C" fn webassembly_compile_streaming_thunk( extern "C" fn webassembly_instantiate_streaming_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _source: f64, ) -> f64 { wasm_unsupported_rejection("WebAssembly.instantiateStreaming") @@ -558,6 +566,7 @@ extern "C" fn webassembly_instantiate_streaming_thunk( /// `WebAssembly.promising` (JSPI) — kept as an existing-but-inert function. extern "C" fn webassembly_unsupported_static_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { undefined() @@ -569,6 +578,7 @@ extern "C" fn webassembly_unsupported_static_thunk( extern "C" fn webassembly_module_ctor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, bytes: f64, ) -> f64 { if !invoked_as_constructor(closure) { @@ -591,6 +601,7 @@ extern "C" fn webassembly_module_ctor_thunk( extern "C" fn webassembly_instance_ctor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, module: f64, imports: f64, ) -> f64 { @@ -614,6 +625,7 @@ extern "C" fn webassembly_instance_ctor_thunk( extern "C" fn webassembly_table_ctor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, descriptor: f64, ) -> f64 { if !invoked_as_constructor(closure) { @@ -635,6 +647,7 @@ extern "C" fn webassembly_table_ctor_thunk( extern "C" fn webassembly_global_ctor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, descriptor: f64, initial: f64, ) -> f64 { @@ -732,6 +745,7 @@ fn wasm_memory_new_buffer(pages: u32) -> f64 { extern "C" fn webassembly_memory_ctor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, descriptor: f64, ) -> f64 { if !invoked_as_constructor(closure) { @@ -859,6 +873,7 @@ fn wasm_memory_grow_on(this: f64, delta: f64) -> Result { extern "C" fn webassembly_memory_grow_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, delta: f64, ) -> f64 { let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); @@ -879,6 +894,7 @@ extern "C" fn webassembly_memory_grow_thunk( extern "C" fn webassembly_compile_error_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { wasm_error_from_message_value(b"CompileError", message) @@ -886,6 +902,7 @@ extern "C" fn webassembly_compile_error_ctor_thunk( extern "C" fn webassembly_link_error_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { wasm_error_from_message_value(b"LinkError", message) @@ -893,6 +910,7 @@ extern "C" fn webassembly_link_error_ctor_thunk( extern "C" fn webassembly_runtime_error_ctor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, message: f64, ) -> f64 { wasm_error_from_message_value(b"RuntimeError", message) @@ -903,6 +921,7 @@ extern "C" fn webassembly_runtime_error_ctor_thunk( #[cfg(feature = "wasm-host")] extern "C" fn webassembly_module_exports_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, module: f64, ) -> f64 { crate::webassembly::js_webassembly_module_exports(module) @@ -911,6 +930,7 @@ extern "C" fn webassembly_module_exports_thunk( #[cfg(feature = "wasm-host")] extern "C" fn webassembly_module_imports_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, module: f64, ) -> f64 { crate::webassembly::js_webassembly_module_imports(module) @@ -919,6 +939,7 @@ extern "C" fn webassembly_module_imports_thunk( #[cfg(feature = "wasm-host")] extern "C" fn webassembly_module_custom_sections_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, module: f64, name: f64, ) -> f64 { @@ -930,6 +951,7 @@ extern "C" fn webassembly_module_custom_sections_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_module_exports_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _module: f64, ) -> f64 { super::super::object_ops::throw_object_type_error( @@ -940,6 +962,7 @@ extern "C" fn webassembly_module_exports_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_module_imports_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _module: f64, ) -> f64 { super::super::object_ops::throw_object_type_error( @@ -950,6 +973,7 @@ extern "C" fn webassembly_module_imports_thunk( #[cfg(not(feature = "wasm-host"))] extern "C" fn webassembly_module_custom_sections_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _module: f64, _name: f64, ) -> f64 { @@ -1475,19 +1499,32 @@ mod tests { fn async_members_reject_with_compile_error() { let closure = std::ptr::null(); assert_rejected_with_compile_error( - webassembly_compile_thunk(closure, undefined()), + webassembly_compile_thunk(closure, crate::closure::JsThis::current(), undefined()), "WebAssembly.compile", ); assert_rejected_with_compile_error( - webassembly_instantiate_thunk(closure, undefined(), undefined()), + webassembly_instantiate_thunk( + closure, + crate::closure::JsThis::current(), + undefined(), + undefined(), + ), "WebAssembly.instantiate", ); assert_rejected_with_compile_error( - webassembly_compile_streaming_thunk(closure, undefined()), + webassembly_compile_streaming_thunk( + closure, + crate::closure::JsThis::current(), + undefined(), + ), "WebAssembly.compileStreaming", ); assert_rejected_with_compile_error( - webassembly_instantiate_streaming_thunk(closure, undefined()), + webassembly_instantiate_streaming_thunk( + closure, + crate::closure::JsThis::current(), + undefined(), + ), "WebAssembly.instantiateStreaming", ); } @@ -1495,7 +1532,11 @@ mod tests { #[cfg(not(feature = "wasm-host"))] #[test] fn validate_reports_false() { - let result = webassembly_validate_thunk(std::ptr::null(), undefined()); + let result = webassembly_validate_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + undefined(), + ); assert_eq!( result.to_bits(), crate::value::JSValue::bool(false).bits(), @@ -1506,17 +1547,29 @@ mod tests { #[test] fn error_constructors_build_branded_error_objects() { let message = string_value("boom"); - let compile_err = webassembly_compile_error_ctor_thunk(std::ptr::null(), message); + let compile_err = webassembly_compile_error_ctor_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + message, + ); assert_eq!(error_name_bytes(compile_err), b"CompileError".to_vec()); assert_eq!(error_message_string(compile_err), "boom"); assert_eq!(value_gc_type(compile_err), Some(crate::gc::GC_TYPE_ERROR)); // Message coercion mirrors `new Error(v)`. - let numbered = webassembly_link_error_ctor_thunk(std::ptr::null(), 42.0); + let numbered = webassembly_link_error_ctor_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + 42.0, + ); assert_eq!(error_name_bytes(numbered), b"LinkError".to_vec()); assert_eq!(error_message_string(numbered), "42"); - let bare = webassembly_runtime_error_ctor_thunk(std::ptr::null(), undefined()); + let bare = webassembly_runtime_error_ctor_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + undefined(), + ); assert_eq!(error_name_bytes(bare), b"RuntimeError".to_vec()); assert_eq!(error_message_string(bare), ""); } @@ -1528,8 +1581,16 @@ mod tests { let link_ctor = ns_field(ns, b"LinkError"); let runtime_ctor = ns_field(ns, b"RuntimeError"); - let compile_err = webassembly_compile_error_ctor_thunk(std::ptr::null(), string_value("x")); - let link_err = webassembly_link_error_ctor_thunk(std::ptr::null(), string_value("x")); + let compile_err = webassembly_compile_error_ctor_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + string_value("x"), + ); + let link_err = webassembly_link_error_ctor_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + string_value("x"), + ); assert_eq!( webassembly_error_ctor_instanceof(compile_err, compile_ctor), 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 9d8d3ecbde..18dec3192f 100644 --- a/crates/perry-runtime/src/object/inherited_read_cache_tests.rs +++ b/crates/perry-runtime/src/object/inherited_read_cache_tests.rs @@ -803,7 +803,10 @@ fn a_null_prototype_receiver_never_primes() { } } -extern "C" fn forty_two_getter(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn forty_two_getter( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 42.0 } diff --git a/crates/perry-runtime/src/object/instanceof.rs b/crates/perry-runtime/src/object/instanceof.rs index 7b8c44d60a..8b8bd5cb57 100644 --- a/crates/perry-runtime/src/object/instanceof.rs +++ b/crates/perry-runtime/src/object/instanceof.rs @@ -462,6 +462,7 @@ fn throw_invalid_instanceof_rhs(type_ref: f64) -> ! { /// through `IMPLICIT_THIS` set by the `.call`/member dispatch. pub(crate) extern "C" fn function_prototype_has_instance_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let constructor = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index 5b59c9b17c..f2d85a9062 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -232,24 +232,35 @@ fn brand_type_error(method: &str) -> f64 { extern "C" fn array_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { dispatch_on_implicit_this("next") } } -extern "C" fn map_iterator_next_thunk(_c: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { +extern "C" fn map_iterator_next_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { unsafe { dispatch_on_implicit_this("next") } } -extern "C" fn set_iterator_next_thunk(_c: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { +extern "C" fn set_iterator_next_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { unsafe { dispatch_on_implicit_this("next") } } extern "C" fn string_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { dispatch_on_implicit_this("next") } } extern "C" fn regexp_string_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { @@ -268,6 +279,7 @@ extern "C" fn regexp_string_iterator_next_thunk( /// a saved/bound canonical method must not re-enter a later `next` override. extern "C" fn iterator_helper_next_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { @@ -284,6 +296,7 @@ extern "C" fn iterator_helper_next_thunk( /// `%IteratorPrototype%[Symbol.iterator]()` returns `this` (the iterator). extern "C" fn iterator_proto_symbol_iterator_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { super::js_implicit_this_get() @@ -682,11 +695,17 @@ mod override_probe_allocation_tests { const PATCHED_SENTINEL: f64 = 4242.0; - extern "C" fn patched_next_thunk(_closure: *const ClosureHeader) -> f64 { + extern "C" fn patched_next_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { PATCHED_SENTINEL } - extern "C" fn accessor_getter_thunk(_closure: *const ClosureHeader) -> f64 { + extern "C" fn accessor_getter_thunk( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/object/literal_constructor.rs b/crates/perry-runtime/src/object/literal_constructor.rs index 81a9fd10ad..3af5cffeb5 100644 --- a/crates/perry-runtime/src/object/literal_constructor.rs +++ b/crates/perry-runtime/src/object/literal_constructor.rs @@ -43,7 +43,11 @@ pub extern "C" fn js_literal_shape_initialize( mod tests { use super::*; - extern "C" fn collecting_setter(_closure: *const crate::ClosureHeader, _value: f64) -> f64 { + extern "C" fn collecting_setter( + _closure: *const crate::ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, + ) -> f64 { crate::gc::gc_collect_minor(); f64::from_bits(crate::value::TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index d761a5ed66..1d033c5f60 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -358,8 +358,16 @@ pub(crate) use field_get_set::{ private_member_access_hints_restore, private_member_access_hints_savepoint, scan_private_lexical_brand_roots_mut, }; +#[cfg(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" +))] +pub(crate) use this_binding::implicit_this_bits; #[cfg(test)] pub(crate) use this_binding::js_derived_super_scope_push; +#[cfg(test)] +pub(crate) use this_binding::this_witness_counts; pub(crate) use this_binding::SuperNewTargetScope; pub(crate) use this_binding::{ derived_super_binding_stack_restore, derived_super_binding_stack_savepoint, @@ -372,7 +380,7 @@ pub(crate) use this_binding::{ pub use this_binding::{ js_implicit_this_get, js_implicit_this_get_sloppy, js_implicit_this_set, js_new_target_get, js_new_target_set, js_static_this_arm_classref, js_static_this_arm_value, - js_static_this_resolve, ImplicitThisScope, + js_static_this_resolve, js_this_param_witness, ImplicitThisScope, }; pub use to_string_tag::js_object_to_string; pub(crate) use to_string_tag::typed_array_to_string_tag_name; 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 c499c69119..03f6b74b42 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 @@ -1,11 +1,19 @@ //! #10045: own callable properties must beat Function.prototype fast paths. use crate::{closure, gc::RuntimeHandleScope, value}; -extern "C" fn original(_closure: *const closure::ClosureHeader, _arg: f64) -> f64 { +extern "C" fn original( + _closure: *const closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { -1.0 } -extern "C" fn own_method(_closure: *const closure::ClosureHeader, _arg: f64) -> f64 { +extern "C" fn own_method( + _closure: *const closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { crate::object::js_implicit_this_get() } 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 76eb6d9251..e499930d35 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 @@ -121,7 +121,10 @@ fn builtin_callee_gets_the_primitive_receiver_and_a_user_callee_the_wrapper() { "a built-in callee must receive the primitive, not a ToObject wrapper" ); - extern "C" fn sloppy_user_body(_c: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn sloppy_user_body( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 0.0 } let user = crate::closure::js_closure_alloc(sloppy_user_body as *const u8, 0); 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 e827e90e19..b18754450b 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 @@ -103,6 +103,7 @@ fn pad_start_coerces_target_and_defaults_the_fill() { extern "C" fn undef_replacer( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _matched: f64, _offset: f64, _whole: f64, 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 6be6c0851f..82bcc6ccd4 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 @@ -152,7 +152,7 @@ mod tests { use super::*; use std::sync::atomic::Ordering; - extern "C" fn target_body(_c: *const ClosureHeader) -> f64 { + extern "C" fn target_body(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { 42.0 } diff --git a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs index c5b257c303..e19cf2b2c0 100644 --- a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs @@ -163,8 +163,10 @@ fn object_prototype_builtin_invokes_to_string_without_self_redispatch() { // thunk again and overflowed the native stack before reaching `toString`. let receiver = crate::object::builtin_prototype_value("Object"); let previous = crate::object::js_implicit_this_set(receiver); - let result = - crate::object::global_this::object_prototype_to_locale_string_thunk(std::ptr::null()); + let result = crate::object::global_this::object_prototype_to_locale_string_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + ); crate::object::js_implicit_this_set(previous); let ptr = crate::value::js_get_string_pointer_unified(result) as *const crate::StringHeader; diff --git a/crates/perry-runtime/src/object/native_get/tests.rs b/crates/perry-runtime/src/object/native_get/tests.rs index 141c68210d..7a041ac2eb 100644 --- a/crates/perry-runtime/src/object/native_get/tests.rs +++ b/crates/perry-runtime/src/object/native_get/tests.rs @@ -95,7 +95,10 @@ fn prototype_data_mutation_shadow_delete_and_freeze_match_forced_slow() { static GETTER_CALLS: AtomicUsize = AtomicUsize::new(0); -extern "C" fn getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { GETTER_CALLS.fetch_add(1, Ordering::Relaxed); 47.0 } 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 171cd7f209..7742f85996 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 @@ -28,6 +28,7 @@ const ASYNC_RESOURCE_METHODS: &[(&str, u32)] = &[ /// `run`, `exit`, and `runInAsyncScope`. extern "C" fn async_hooks_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { unsafe { 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 51075e1182..757b000d49 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports.rs @@ -220,6 +220,7 @@ fn async_hooks_static_method_value( extern "C" fn fs_namespace_descriptor_getter_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { unsafe { let property_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *const u8; @@ -230,6 +231,7 @@ extern "C" fn fs_namespace_descriptor_getter_thunk( extern "C" fn fs_namespace_descriptor_setter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) @@ -306,12 +308,14 @@ pub(crate) fn is_cluster_emitter_method(prop: &str) -> bool { extern "C" fn sqlite_statement_sync_constructor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { crate::fs::validate::throw_error_with_code("Illegal constructor", "ERR_ILLEGAL_CONSTRUCTOR") } extern "C" fn sqlite_session_constructor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { crate::fs::validate::throw_error_with_code("Illegal constructor", "ERR_ILLEGAL_CONSTRUCTOR") } @@ -371,6 +375,7 @@ fn native_callable_export_display_name<'a>(module: &str, prop: &'a str) -> &'a s extern "C" fn buffer_constructor_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, encoding_or_offset: f64, length: f64, @@ -416,7 +421,10 @@ extern "C" fn buffer_constructor_thunk( crate::value::js_nanbox_pointer(buf as i64) } -extern "C" fn buffer_prototype_method_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn buffer_prototype_method_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -447,6 +455,7 @@ fn buffer_prototype_this_addr() -> Option { /// Node's `Buffer.prototype`. extern "C" fn buffer_prototype_parent_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match buffer_prototype_this_addr() { Some(addr) => { @@ -460,6 +469,7 @@ extern "C" fn buffer_prototype_parent_getter_thunk( /// `.byteOffset`, still a real own accessor on Node's `Buffer.prototype`. extern "C" fn buffer_prototype_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match buffer_prototype_this_addr() { Some(addr) => crate::buffer::buffer_byte_offset(addr) as f64, @@ -757,6 +767,7 @@ fn attach_assert_prototype(constructor_value: f64) { extern "C" fn sqlite_database_sync_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg0: f64, arg1: f64, arg2: f64, @@ -1104,6 +1115,7 @@ fn attach_crypto_key_object_shape(closure_addr: usize, constructor_value: f64) { extern "C" fn x509_issuer_certificate_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } @@ -1342,6 +1354,7 @@ pub(crate) fn scan_tls_derived_prototype_roots_mut( extern "C" fn tls_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { unsafe { 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 026efdf05b..9097cd3050 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 @@ -29,6 +29,7 @@ use super::*; /// memory-sharing `ArrayBuffer` view. extern "C" fn fast_buffer_constructor_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, byte_offset: f64, length: f64, @@ -43,12 +44,21 @@ extern "C" fn fast_buffer_constructor_thunk( let buf = crate::buffer::js_buffer_alloc(size, 0); return crate::value::js_nanbox_pointer(buf as i64); } - super::buffer_constructor_thunk(closure, value, byte_offset, length) + super::buffer_constructor_thunk( + closure, + crate::closure::JsThis::current(), + value, + byte_offset, + length, + ) } /// `get [Symbol.species]` on `Buffer`: answers the `FastBuffer` held in the /// getter's own capture slot 0. -extern "C" fn buffer_species_getter_thunk(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn buffer_species_getter_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::closure::js_closure_get_capture_f64(closure, 0) } 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 b1ab0c2b47..9308d85403 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 @@ -3,6 +3,7 @@ use std::cell::Cell; extern "C" fn module_cjs_extension_noop_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _module: f64, _filename: f64, ) -> f64 { @@ -187,6 +188,7 @@ pub(crate) fn module_constants_value() -> f64 { extern "C" fn module_wrap_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, source: f64, ) -> f64 { let value = JSValue::from_bits(source.to_bits()); @@ -243,6 +245,7 @@ fn module_wrapper_value() -> f64 { extern "C" fn module_prototype_method_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, _b: f64, _c: f64, @@ -252,6 +255,7 @@ extern "C" fn module_prototype_method_thunk( extern "C" fn module_prototype_load_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, filename: f64, _b: f64, _c: f64, @@ -261,6 +265,7 @@ extern "C" fn module_prototype_load_thunk( extern "C" fn module_prototype_require_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, specifier: f64, _b: f64, _c: f64, @@ -291,6 +296,7 @@ fn module_prototype_method(name: &str, length: u32) -> f64 { extern "C" fn module_prototype_constructor_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { // Resolve through the canonical callable cache at access time. Capturing // the constructor while its own attach was still in progress preserved a @@ -301,18 +307,23 @@ extern "C" fn module_prototype_constructor_getter( bound_native_callable_export_value("module", "Module") } -extern "C" fn module_prototype_false_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn module_prototype_false_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { native_bool_value(false) } extern "C" fn module_prototype_parent_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) } extern "C" fn module_prototype_parent_setter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) 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 2ea2051b3e..2cb5648801 100644 --- a/crates/perry-runtime/src/object/native_module/namespace_builders.rs +++ b/crates/perry-runtime/src/object/native_module/namespace_builders.rs @@ -327,6 +327,7 @@ unsafe fn global_agent_build_name(options: f64, is_https: bool) -> String { extern "C" fn global_agent_get_name_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, ) -> f64 { @@ -339,6 +340,7 @@ extern "C" fn global_agent_get_name_thunk( extern "C" fn global_agent_keep_socket_alive_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _socket: f64, ) -> f64 { f64::from_bits(JSValue::bool(true).bits()) @@ -346,6 +348,7 @@ extern "C" fn global_agent_keep_socket_alive_thunk( extern "C" fn global_agent_reuse_socket_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _socket: f64, _request: f64, ) -> f64 { @@ -354,6 +357,7 @@ extern "C" fn global_agent_reuse_socket_thunk( extern "C" fn global_agent_listener_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, @@ -395,12 +399,16 @@ extern "C" fn global_agent_listener_thunk( } } -extern "C" fn global_agent_destroy_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn global_agent_destroy_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { f64::from_bits(JSValue::undefined().bits()) } extern "C" fn global_agent_add_request_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, first: f64, second: f64, third: f64, 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 3a99e677db..1dd64d8b60 100644 --- a/crates/perry-runtime/src/object/native_module/web_locks.rs +++ b/crates/perry-runtime/src/object/native_module/web_locks.rs @@ -542,6 +542,7 @@ fn web_locks_release(id: u64) { extern "C" fn worker_threads_locks_release_fulfill( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let id = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; @@ -554,6 +555,7 @@ extern "C" fn worker_threads_locks_release_fulfill( extern "C" fn worker_threads_locks_release_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let id = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; @@ -566,6 +568,7 @@ extern "C" fn worker_threads_locks_release_reject( extern "C" fn worker_threads_locks_request( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name_value: f64, options_or_callback: f64, maybe_callback: f64, @@ -657,18 +660,22 @@ pub extern "C" fn js_worker_threads_locks_request( ) -> f64 { worker_threads_locks_request( std::ptr::null(), + crate::closure::JsThis::current(), name_value, options_or_callback, maybe_callback, ) } -extern "C" fn worker_threads_locks_query(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn worker_threads_locks_query( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let snapshot = web_locks_query_snapshot(); web_locks_object_value(crate::promise::js_promise_resolved(snapshot)) } #[no_mangle] pub extern "C" fn js_worker_threads_locks_query() -> f64 { - worker_threads_locks_query(std::ptr::null()) + worker_threads_locks_query(std::ptr::null(), crate::closure::JsThis::current()) } diff --git a/crates/perry-runtime/src/object/native_module_stream.rs b/crates/perry-runtime/src/object/native_module_stream.rs index fe5b2dfbed..2be44fa0b7 100644 --- a/crates/perry-runtime/src/object/native_module_stream.rs +++ b/crates/perry-runtime/src/object/native_module_stream.rs @@ -215,6 +215,7 @@ pub(crate) fn is_stream_event_emitter_prototype_value(value: f64) -> bool { extern "C" fn stream_static_method_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg0: f64, arg1: f64, ) -> f64 { 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 ef6915bbd8..25eb7e0fb9 100644 --- a/crates/perry-runtime/src/object/own_override_push_tests.rs +++ b/crates/perry-runtime/src/object/own_override_push_tests.rs @@ -28,7 +28,11 @@ unsafe fn push_or_own(arr: *mut ArrayHeader, value: f64) -> (u64, u32) { (bits, own) } -extern "C" fn own_push_fixture(_closure: *const crate::closure::ClosureHeader, x: f64) -> f64 { +extern "C" fn own_push_fixture( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + x: f64, +) -> f64 { x * 10.0 } diff --git a/crates/perry-runtime/src/object/primitive_proto_thunks.rs b/crates/perry-runtime/src/object/primitive_proto_thunks.rs index b7ffdddcc0..04c2174657 100644 --- a/crates/perry-runtime/src/object/primitive_proto_thunks.rs +++ b/crates/perry-runtime/src/object/primitive_proto_thunks.rs @@ -295,12 +295,14 @@ fn string_value(ptr: *mut crate::string::StringHeader) -> f64 { pub(super) extern "C" fn number_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { number_receiver_or_throw("valueOf") } pub(super) extern "C" fn number_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, radix: f64, ) -> f64 { string_value(crate::value::js_jsvalue_to_string_radix( @@ -311,6 +313,7 @@ pub(super) extern "C" fn number_proto_to_string_thunk( pub(super) extern "C" fn number_proto_to_fixed_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, decimals: f64, ) -> f64 { string_value(crate::string::js_number_to_fixed( @@ -321,6 +324,7 @@ pub(super) extern "C" fn number_proto_to_fixed_thunk( pub(super) extern "C" fn number_proto_to_precision_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, precision: f64, ) -> f64 { string_value(crate::string::js_number_to_precision( @@ -331,6 +335,7 @@ pub(super) extern "C" fn number_proto_to_precision_thunk( pub(super) extern "C" fn number_proto_to_exponential_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, decimals: f64, ) -> f64 { string_value(crate::string::js_number_to_exponential( @@ -357,6 +362,7 @@ pub(super) extern "C" fn number_proto_to_exponential_thunk( /// (Intl builds a fresh instance per call; there is no formatter cache). pub(super) extern "C" fn number_proto_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let n = number_receiver_or_throw("toLocaleString"); @@ -379,12 +385,14 @@ pub(super) extern "C" fn number_proto_to_locale_string_thunk( pub(super) extern "C" fn boolean_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { boolean_receiver_or_throw("valueOf") } pub(super) extern "C" fn boolean_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let value = boolean_receiver_or_throw("toString"); let bytes = if crate::value::JSValue::from_bits(value.to_bits()).as_bool() { @@ -398,12 +406,14 @@ pub(super) extern "C" fn boolean_proto_to_string_thunk( pub(super) extern "C" fn symbol_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { symbol_receiver_or_throw("valueOf") } pub(super) extern "C" fn symbol_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let symbol = symbol_receiver_or_throw("toString"); let s = @@ -413,12 +423,14 @@ pub(super) extern "C" fn symbol_proto_to_string_thunk( pub(super) extern "C" fn bigint_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { bigint_receiver_or_throw("valueOf") } pub(super) extern "C" fn bigint_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, radix: f64, ) -> f64 { string_value(crate::value::js_jsvalue_to_string_radix( @@ -429,6 +441,7 @@ pub(super) extern "C" fn bigint_proto_to_string_thunk( pub(super) extern "C" fn bigint_proto_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let value = bigint_receiver_or_throw("toLocaleString"); @@ -464,6 +477,7 @@ pub(super) extern "C" fn bigint_proto_to_locale_string_thunk( /// `TypeError` for any other receiver. pub(super) extern "C" fn string_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { string_receiver_or_throw("toString") } @@ -472,6 +486,7 @@ pub(super) extern "C" fn string_proto_to_string_thunk( /// `toString()`: returns the [[StringData]] of the receiver. pub(super) extern "C" fn string_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { string_receiver_or_throw("valueOf") } diff --git a/crates/perry-runtime/src/object/property_key.rs b/crates/perry-runtime/src/object/property_key.rs index 1f96d52d19..05ca3527aa 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -561,7 +561,10 @@ pub unsafe extern "C" fn js_object_super_call( mod property_key_tests { use super::*; - extern "C" fn accessor_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn accessor_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { 123.0 } diff --git a/crates/perry-runtime/src/object/regex_proto_thunks.rs b/crates/perry-runtime/src/object/regex_proto_thunks.rs index be0ccac2c5..03755946ac 100644 --- a/crates/perry-runtime/src/object/regex_proto_thunks.rs +++ b/crates/perry-runtime/src/object/regex_proto_thunks.rs @@ -89,47 +89,56 @@ fn flag_getter(getter: &str, flag: char) -> f64 { pub(super) extern "C" fn regex_proto_global_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("global", 'g') } pub(super) extern "C" fn regex_proto_ignore_case_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("ignoreCase", 'i') } pub(super) extern "C" fn regex_proto_multiline_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("multiline", 'm') } pub(super) extern "C" fn regex_proto_dot_all_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("dotAll", 's') } pub(super) extern "C" fn regex_proto_sticky_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("sticky", 'y') } pub(super) extern "C" fn regex_proto_unicode_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("unicode", 'u') } pub(super) extern "C" fn regex_proto_unicode_sets_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("unicodeSets", 'v') } pub(super) extern "C" fn regex_proto_has_indices_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { flag_getter("hasIndices", 'd') } pub(super) extern "C" fn regex_proto_source_getter( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { match regex_receiver_or_throw("source") { RegexReceiver::Regex(re) => { @@ -147,7 +156,10 @@ pub(super) extern "C" fn regex_proto_source_getter( /// `get RegExp.prototype.flags` — spec 22.2.6.4. Reads each flag property off /// the (generic) receiver via `Get` + `ToBoolean` and assembles in canonical /// order `d g i m s u v y`. Throws `TypeError` only if `this` is not an Object. -pub(super) extern "C" fn regex_proto_flags_getter(_c: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn regex_proto_flags_getter( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { #[cfg(feature = "regex-engine")] { let value = crate::regex::perex_api::finish(crate::regex::perex_match_search::flags( @@ -230,6 +242,7 @@ fn install_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_exec_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { let re = regex_instance_or_throw("exec"); @@ -263,6 +276,7 @@ pub(crate) fn is_builtin_regexp_exec(value: f64) -> bool { #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_test_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { let matched = crate::regex::perex_api::finish(crate::regex::perex_dispatch::test_value( @@ -282,6 +296,7 @@ pub(super) extern "C" fn regex_proto_test_thunk( #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_compile_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, pattern: f64, flags: f64, ) -> f64 { @@ -295,6 +310,7 @@ pub(super) extern "C" fn regex_proto_compile_thunk( /// `RegExp.prototype.toString.call({ source: "x", flags: "g" })` works. pub(super) extern "C" fn regex_proto_to_string_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let receiver = crate::value::JSValue::from_bits(IMPLICIT_THIS.with(|c| c.get())); if !receiver.is_pointer() diff --git a/crates/perry-runtime/src/object/reserved_floor.rs b/crates/perry-runtime/src/object/reserved_floor.rs index e0c95a12db..6bbaeb7939 100644 --- a/crates/perry-runtime/src/object/reserved_floor.rs +++ b/crates/perry-runtime/src/object/reserved_floor.rs @@ -238,7 +238,11 @@ mod tests { /// path `for…of`'s fused `js_for_of_next` takes). #[test] fn own_next_shadows_the_builtin_advance() { - extern "C" fn patched_next(_c: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { + extern "C" fn patched_next( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, + ) -> f64 { unsafe { crate::iter_result::make_iter_result(JSValue::number(777.0), false) } } unsafe { 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 01814225bd..625efd6c1d 100644 --- a/crates/perry-runtime/src/object/restricted_function_store_tests.rs +++ b/crates/perry-runtime/src/object/restricted_function_store_tests.rs @@ -2,18 +2,21 @@ extern "C" fn non_strict_restricted_store_test_body( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { 9525.0 } extern "C" fn strict_restricted_store_test_body( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { 9526.0 } extern "C" fn method_restricted_store_test_body( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { 9527.0 } diff --git a/crates/perry-runtime/src/object/string_proto_thunks.rs b/crates/perry-runtime/src/object/string_proto_thunks.rs index 593d2d12e7..b00de6314a 100644 --- a/crates/perry-runtime/src/object/string_proto_thunks.rs +++ b/crates/perry-runtime/src/object/string_proto_thunks.rs @@ -163,6 +163,7 @@ fn install_generic_string_proto_methods(proto_obj: *mut ObjectHeader) { /// own method name off the closure. Args arrive as a rest array. pub(super) extern "C" fn string_proto_generic_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { // Copy the method name to an owned String — `string_this_or_throw` may run @@ -270,6 +271,7 @@ fn string_this_or_throw(method: &str) -> *mut crate::string::StringHeader { pub(super) extern "C" fn string_proto_char_at_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, pos: f64, ) -> f64 { let s = string_this_or_throw("charAt"); @@ -280,6 +282,7 @@ pub(super) extern "C" fn string_proto_char_at_thunk( pub(super) extern "C" fn string_proto_char_code_at_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, pos: f64, ) -> f64 { let s = string_this_or_throw("charCodeAt"); @@ -289,6 +292,7 @@ pub(super) extern "C" fn string_proto_char_code_at_thunk( pub(super) extern "C" fn string_proto_code_point_at_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, pos: f64, ) -> f64 { let s = string_this_or_throw("codePointAt"); @@ -298,6 +302,7 @@ pub(super) extern "C" fn string_proto_code_point_at_thunk( pub(super) extern "C" fn string_proto_at_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, index: f64, ) -> f64 { let s = string_this_or_throw("at"); @@ -307,23 +312,33 @@ pub(super) extern "C" fn string_proto_at_thunk( pub(super) extern "C" fn string_proto_symbol_iterator_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let s = string_this_or_throw("[Symbol.iterator]"); crate::string::string_values_iter(s) } #[cfg(feature = "regex-engine")] -extern "C" fn string_proto_match_thunk(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn string_proto_match_thunk( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { crate::regex::js_string_match_js(crate::object::js_implicit_this_get(), arg) } #[cfg(feature = "regex-engine")] -extern "C" fn string_proto_search_thunk(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn string_proto_search_thunk( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { crate::regex::js_string_search_js(crate::object::js_implicit_this_get(), arg) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_match_all_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, arg: f64, ) -> f64 { crate::regex::js_string_match_all_js(crate::object::js_implicit_this_get(), arg) @@ -332,6 +347,7 @@ extern "C" fn string_proto_match_all_thunk( #[cfg(feature = "regex-engine")] extern "C" fn string_proto_replace_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, search: f64, replacement: f64, ) -> f64 { @@ -340,6 +356,7 @@ extern "C" fn string_proto_replace_thunk( #[cfg(feature = "regex-engine")] extern "C" fn string_proto_replace_all_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, search: f64, replacement: f64, ) -> f64 { @@ -353,6 +370,7 @@ extern "C" fn string_proto_replace_all_thunk( #[cfg(feature = "regex-engine")] extern "C" fn string_proto_split_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, separator: f64, limit: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/object/temporal_proto.rs b/crates/perry-runtime/src/object/temporal_proto.rs index 9a6acecbea..805a065c3f 100644 --- a/crates/perry-runtime/src/object/temporal_proto.rs +++ b/crates/perry-runtime/src/object/temporal_proto.rs @@ -48,6 +48,7 @@ fn throw_brand(member: &str) -> ! { /// `"hour"`), and routes to the brand router; a non-Temporal `this` throws. pub(super) extern "C" fn temporal_proto_getter_thunk( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let recv = f64::from_bits(IMPLICIT_THIS.with(|x| x.get())); let full = closure_name(c); @@ -63,6 +64,7 @@ pub(super) extern "C" fn temporal_proto_getter_thunk( /// receiver, and routes to the brand router with the rest-array args. pub(super) extern "C" fn temporal_proto_method_thunk( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { let recv = f64::from_bits(IMPLICIT_THIS.with(|x| x.get())); diff --git a/crates/perry-runtime/src/object/tests.rs b/crates/perry-runtime/src/object/tests.rs index 77b9a3688a..92acc8a542 100644 --- a/crates/perry-runtime/src/object/tests.rs +++ b/crates/perry-runtime/src/object/tests.rs @@ -95,6 +95,7 @@ unsafe fn installed_builtin_method(ctor_name: &str, method_name: &str) -> f64 { extern "C" fn symbol_to_primitive_nan( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, hint: f64, ) -> f64 { let hint_value = JSValue::from_bits(hint.to_bits()); @@ -102,12 +103,16 @@ extern "C" fn symbol_to_primitive_nan( f64::NAN } -extern "C" fn value_of_finite(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn value_of_finite( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 1.0 } extern "C" fn symbol_to_primitive_this_object( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, hint: f64, ) -> f64 { let hint_value = JSValue::from_bits(hint.to_bits()); @@ -115,7 +120,10 @@ extern "C" fn symbol_to_primitive_this_object( crate::object::js_implicit_this_get() } -extern "C" fn to_iso_string_sentinel(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn to_iso_string_sentinel( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let string = crate::string::js_string_from_bytes(b"iso".as_ptr(), 3); crate::value::js_nanbox_string(string as i64) } @@ -411,7 +419,10 @@ fn closure_name_can_be_redefined_with_define_property() { } } -extern "C" fn closure_accessor_getter(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn closure_accessor_getter( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { 4.0 } diff --git a/crates/perry-runtime/src/object/this_binding.rs b/crates/perry-runtime/src/object/this_binding.rs index f996dd19f4..738ec47437 100644 --- a/crates/perry-runtime/src/object/this_binding.rs +++ b/crates/perry-runtime/src/object/this_binding.rs @@ -8,9 +8,10 @@ use std::cell::{Cell, RefCell}; // Implicit `this` for closure-typed class fields invoked method-style. // // Issue #519: when `obj.fn(args)` calls a closure stored as a class field, -// the field-scan dispatch in `js_native_call_method` can't bind `this` -// through the closure ABI (closures take `(closure_ptr, arg0, …)` — no -// `this` slot). Hono's RegExpRouter does this with `match = match` (the +// the field-scan dispatch in `js_native_call_method` could not bind `this` +// through the closure ABI, which then had no `this` slot (it has one since +// this-as-a-parameter stage 1, `perry_abi::JS_BODY_THIS_PARAM`, but bodies +// still read this cell). Hono's RegExpRouter does this with `match = match` (the // imported function from matcher.js), and the function body's // `this.buildAllMatchers()` reads `this = 0` and TypeErrors out. // @@ -172,6 +173,89 @@ pub extern "C" fn js_static_this_resolve(default_this: f64) -> f64 { }) } +/// The implicit-`this` cell's raw bits: the receiver a JS body called right +/// now would read. `closure::JsThis::current` is the body-call funnel's +/// spelling of it (through `agent_ptrs` everywhere but Apple aarch64, where +/// this `HotTls` read is the fast path). +#[cfg(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" +))] +#[inline(always)] +pub(crate) fn implicit_this_bits() -> u64 { + implicit_this_cell().get() +} + +static THIS_WITNESS_CHECKS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +static THIS_WITNESS_MISMATCHES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +static THIS_WITNESS_REPORT: std::sync::Once = std::sync::Once::new(); + +/// How many mismatches [`js_this_param_witness`] names individually before it +/// only counts. +const THIS_WITNESS_NAMED: u64 = 64; + +/// The this-as-a-parameter stage-1 witness: the prologue of every compiled +/// body that reads the implicit-`this` cell calls this with its `this` +/// PARAMETER (`perry_abi::JS_BODY_THIS_PARAM`) and its own name, and it +/// compares the two. Stage 1 requires that every caller passes exactly the +/// receiver the cell holds, so a mismatch names a caller that does not. +/// +/// Emitted ONLY by a build compiled with `PERRY_THIS_WITNESS=1` — a +/// COMPILE-time knob, so a product build contains no call and pays nothing. +/// Reported once, on stderr, at exit, as one `PERRY_THIS_WITNESS checks=N +/// mismatches=M` line; the first mismatches are also named as they happen. +/// A leaf: it never allocates on the JS heap and never collects. +#[no_mangle] +pub extern "C" fn js_this_param_witness(param_bits: u64, site: *const u8, site_len: u64) { + use std::sync::atomic::Ordering; + THIS_WITNESS_REPORT.call_once(|| unsafe { + libc::atexit(this_witness_report); + }); + THIS_WITNESS_CHECKS.fetch_add(1, Ordering::Relaxed); + let cell_bits = implicit_this_cell().get(); + if cell_bits == param_bits { + return; + } + let n = THIS_WITNESS_MISMATCHES.fetch_add(1, Ordering::Relaxed); + if n < THIS_WITNESS_NAMED { + let name = if site.is_null() { + "?".into() + } else { + String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(site, site_len as usize) }) + }; + eprintln!( + "PERRY_THIS_WITNESS mismatch in {name}: this param={param_bits:#018x} cell={cell_bits:#018x}" + ); + } +} + +/// `(checks, mismatches)` so far (tests). +#[cfg(test)] +pub(crate) fn this_witness_counts() -> (u64, u64) { + use std::sync::atomic::Ordering; + ( + THIS_WITNESS_CHECKS.load(Ordering::Relaxed), + THIS_WITNESS_MISMATCHES.load(Ordering::Relaxed), + ) +} + +extern "C" fn this_witness_report() { + use std::sync::atomic::Ordering; + eprintln!( + "PERRY_THIS_WITNESS checks={} mismatches={}", + THIS_WITNESS_CHECKS.load(Ordering::Relaxed), + THIS_WITNESS_MISMATCHES.load(Ordering::Relaxed) + ); +} + +/// Keepalive anchor — `js_this_param_witness` is called only from generated +/// code of a witness build, so the auto-optimize whole-program build would +/// otherwise dead-strip it. +#[cfg(feature = "keepalive-anchors")] +#[used(compiler)] +static KEEP_JS_THIS_PARAM_WITNESS: extern "C" fn(u64, *const u8, u64) = js_this_param_witness; + /// Read the current implicit `this` (issue #519). #[no_mangle] pub extern "C" fn js_implicit_this_get() -> f64 { 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 7484816602..fddc88000c 100644 --- a/crates/perry-runtime/src/object/typed_array_proto_thunks.rs +++ b/crates/perry-runtime/src/object/typed_array_proto_thunks.rs @@ -1012,6 +1012,7 @@ macro_rules! ta_thunk { ($name:ident, $method:literal, $argc:literal) => { pub(super) extern "C" fn $name( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, d: f64, @@ -1052,6 +1053,7 @@ ta_thunk!(ta_with_thunk, "with", 2); pub(super) extern "C" fn ta_last_index_of_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, search_element: f64, rest: f64, ) -> f64 { @@ -1063,6 +1065,7 @@ pub(super) extern "C" fn ta_last_index_of_thunk( pub(super) extern "C" fn ta_reduce_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { @@ -1074,6 +1077,7 @@ pub(super) extern "C" fn ta_reduce_thunk( pub(super) extern "C" fn ta_reduce_right_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { @@ -1089,6 +1093,7 @@ pub(super) extern "C" fn ta_reduce_right_thunk( /// but keeps the install path total. pub(super) extern "C" fn ta_generic_thunk( c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a: f64, b: f64, d: f64, @@ -1148,6 +1153,7 @@ unsafe fn uint8_receiver_or_throw(method: &str) -> usize { pub(super) extern "C" fn u8_to_base64_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, opts: f64, _b: f64, _d: f64, @@ -1161,6 +1167,7 @@ pub(super) extern "C" fn u8_to_base64_thunk( pub(super) extern "C" fn u8_to_hex_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _a: f64, _b: f64, _d: f64, @@ -1173,6 +1180,7 @@ pub(super) extern "C" fn u8_to_hex_thunk( pub(super) extern "C" fn u8_set_from_base64_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, str_arg: f64, opts: f64, _d: f64, @@ -1186,6 +1194,7 @@ pub(super) extern "C" fn u8_set_from_base64_thunk( pub(super) extern "C" fn u8_set_from_hex_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, str_arg: f64, _b: f64, _d: f64, diff --git a/crates/perry-runtime/src/object/util_types.rs b/crates/perry-runtime/src/object/util_types.rs index 0674bff749..16ce3f781c 100644 --- a/crates/perry-runtime/src/object/util_types.rs +++ b/crates/perry-runtime/src/object/util_types.rs @@ -433,8 +433,12 @@ mod buffer_view_tests { expected_view ); assert_eq!( - super::super::global_this::array_buffer_is_view_thunk(std::ptr::null(), value) - .to_bits(), + super::super::global_this::array_buffer_is_view_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + value + ) + .to_bits(), expected_view, ); assert_eq!( diff --git a/crates/perry-runtime/src/object/view_brand_tests.rs b/crates/perry-runtime/src/object/view_brand_tests.rs index 8e738fc933..60db49c473 100644 --- a/crates/perry-runtime/src/object/view_brand_tests.rs +++ b/crates/perry-runtime/src/object/view_brand_tests.rs @@ -32,7 +32,11 @@ fn row(value: f64) -> Row { use crate::object::js_instanceof; Row { is_view_direct: is_true(crate::object::js_util_types_is_array_buffer_view(value)), - is_view_value: is_true(array_buffer_is_view_thunk(std::ptr::null(), value)), + is_view_value: is_true(array_buffer_is_view_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + value, + )), typed: is_true(crate::object::js_util_types_is_typed_array(value)), uint8: is_true(crate::object::js_util_types_is_uint8_array(value)), int8: is_true(crate::object::js_util_types_is_int8_array(value)), diff --git a/crates/perry-runtime/src/object/weakref_proto_thunks.rs b/crates/perry-runtime/src/object/weakref_proto_thunks.rs index 05629d1b73..ee535c98d8 100644 --- a/crates/perry-runtime/src/object/weakref_proto_thunks.rs +++ b/crates/perry-runtime/src/object/weakref_proto_thunks.rs @@ -256,6 +256,7 @@ fn wrapper_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { pub(super) extern "C" fn weakref_proto_deref_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let r = wrapper_receiver_or_throw(CLASS_ID_WEAKREF, "WeakRef.prototype", "deref"); js_weakref_deref(r) @@ -263,6 +264,7 @@ pub(super) extern "C" fn weakref_proto_deref_thunk( pub(super) extern "C" fn finreg_proto_register_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, target: f64, held: f64, token: f64, @@ -277,6 +279,7 @@ pub(super) extern "C" fn finreg_proto_register_thunk( pub(super) extern "C" fn finreg_proto_unregister_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, token: f64, ) -> f64 { let r = wrapper_receiver_or_throw( @@ -366,11 +369,18 @@ mod tests { /// answer, so the assertions below cannot pass by accident. const FOREIGN_SENTINEL: i32 = 7947; - extern "C" fn foreign_method_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn foreign_method_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(JSValue::int32(FOREIGN_SENTINEL).bits()) } - extern "C" fn foreign_method_thunk_1(_c: *const crate::closure::ClosureHeader, _a: f64) -> f64 { + extern "C" fn foreign_method_thunk_1( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, + ) -> f64 { f64::from_bits(JSValue::int32(FOREIGN_SENTINEL).bits()) } diff --git a/crates/perry-runtime/src/os/os_process_emitter.rs b/crates/perry-runtime/src/os/os_process_emitter.rs index 7d91e5a191..5f3985e734 100644 --- a/crates/perry-runtime/src/os/os_process_emitter.rs +++ b/crates/perry-runtime/src/os/os_process_emitter.rs @@ -148,6 +148,7 @@ fn listener_lookup_ptr(listener_bits: i64) -> *const crate::closure::ClosureHead extern "C" fn process_once_raw_wrapper( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest_args: f64, ) -> f64 { if closure.is_null() { diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index fdf6dece4f..b67afa7478 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -50,6 +50,7 @@ fn callable_closure_ptr(value: f64) -> usize { /// fires the optional completion callback (see [`schedule_write_callback`]). extern "C" fn process_stdout_write_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, arg2: f64, arg3: f64, @@ -62,6 +63,7 @@ extern "C" fn process_stdout_write_stub( /// `write` impl for process.stderr. Same as stdout, targeting fd 2. extern "C" fn process_stderr_write_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, chunk: f64, arg2: f64, arg3: f64, @@ -76,6 +78,7 @@ extern "C" fn process_stderr_write_stub( /// optional completion callback so an awaited `stdin.write(x, cb)` resolves. extern "C" fn process_stdin_write_noop_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _chunk: f64, arg2: f64, arg3: f64, @@ -86,6 +89,7 @@ extern "C" fn process_stdin_write_noop_stub( extern "C" fn process_stream_emit_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { f64::from_bits(crate::value::TAG_TRUE) @@ -93,6 +97,7 @@ extern "C" fn process_stream_emit_stub( extern "C" fn process_stream_on_once_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { f64::from_bits(crate::value::TAG_UNDEFINED) @@ -105,6 +110,7 @@ extern "C" fn process_stream_on_once_stub( /// Node's `this`-returning contract. Encoding-aware reads remain future work. extern "C" fn process_stream_set_encoding_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { crate::object::js_implicit_this_get() @@ -155,6 +161,7 @@ fn stdin_reader_should_stop() -> bool { /// hold on the event loop and stops the reader. No-op return (`undefined`). extern "C" fn process_stdin_detach_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_DETACHED.store(true, std::sync::atomic::Ordering::Release); @@ -171,6 +178,7 @@ extern "C" fn process_stdin_detach_stub( /// delivery (#9676). Node's contract: an unref'd stdin still emits `'data'`. extern "C" fn process_stdin_unref_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_UNREFED.store(true, std::sync::atomic::Ordering::Release); @@ -185,6 +193,7 @@ extern "C" fn process_stdin_unref_stub( /// stream. `resume()` remains the one call that restarts a stopped reader. extern "C" fn process_stdin_ref_stub( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_UNREFED.store(false, std::sync::atomic::Ordering::Release); @@ -686,6 +695,7 @@ pub fn stdin_chunk_jsvalue(chunk: &[u8]) -> f64 { /// the reader can decide. extern "C" fn process_stdin_set_encoding( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, encoding: f64, ) -> f64 { let name = stdin_event_name(encoding).unwrap_or_default(); @@ -839,6 +849,7 @@ pub fn enable_process_stdin_keypress_events(callback: i64) { /// `process.stdin.addListener(event, cb)` / `.on(...)` reached as an object method. extern "C" fn process_stdin_add_listener( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -850,12 +861,13 @@ extern "C" fn process_stdin_add_listener( } return stdin_this_value(); } - process_stdin_on(closure, event, callback) + process_stdin_on(closure, crate::closure::JsThis::current(), event, callback) } /// `process.stdin.once(event, cb)` reached as an object method. extern "C" fn process_stdin_add_listener_once( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -867,12 +879,13 @@ extern "C" fn process_stdin_add_listener_once( } return stdin_this_value(); } - process_stdin_once(closure, event, callback) + process_stdin_once(closure, crate::closure::JsThis::current(), event, callback) } /// `process.stdin.removeListener(event, cb)` / `.off(...)`. extern "C" fn process_stdin_remove_listener( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -938,6 +951,7 @@ fn clear_stdin_listener_list(list: &std::sync::Mutex>) { /// call that Node treats as authoritative. extern "C" fn process_stdin_remove_all_listeners( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let name = stdin_event_name(event); @@ -999,6 +1013,7 @@ extern "C" fn process_stdin_remove_all_listeners( /// as a real array (empty when there are none), like Node's EventEmitter. extern "C" fn process_stdin_listeners( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let name = stdin_event_name(event).unwrap_or_default(); @@ -1193,6 +1208,7 @@ fn register_generic_stdin_keypress_listener(event: f64, callback: f64, once: boo /// listener and starts the reader. Returns `this` so callers can chain. extern "C" fn process_stdin_on( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1232,6 +1248,7 @@ extern "C" fn process_stdin_on( /// `process.stdin.once(event, cb)` — fires the listener exactly once. extern "C" fn process_stdin_once( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1269,7 +1286,11 @@ extern "C" fn process_stdin_once( /// `process.stdin.read([size])` — returns buffered input as a string (stdin is /// `setEncoding("utf8")` in practice) or `null` when nothing is buffered, per /// Node's `Readable.read()` contract. -extern "C" fn process_stdin_read(_closure: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { +extern "C" fn process_stdin_read( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, +) -> f64 { let bytes = match STDIN_BUFFER.lock() { Ok(mut b) => std::mem::take(&mut *b), Err(_) => return f64::from_bits(crate::value::TAG_NULL), @@ -1316,6 +1337,7 @@ pub fn reset_process_stdin_liveness_for_tests() { /// paused stdin can resume. extern "C" fn process_stdin_resume( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_DETACHED.store(false, std::sync::atomic::Ordering::Release); @@ -1378,86 +1400,8 @@ mod empty_checkpoint_tests { } #[cfg(test)] -mod reader_lifecycle_tests { - use super::{reader_slot_claim_start, reader_slot_claim_stop}; - use std::sync::atomic::{AtomicBool, Ordering}; - - /// #10895: replays the interleaving that stranded fd 0 without a reader — - /// the reader decides to stop, and `resume()` asks for a restart BEFORE - /// the dying reader has finished leaving. The stop decision must already - /// have released the reader slot, or the restart's claim fails and nobody - /// ever reads stdin again. - /// - /// Runs on local flags: no fd-0 reader is spawned and no process-global - /// stdin state is touched, so it cannot disturb the liveness tests. - #[test] - fn a_restart_requested_while_the_reader_is_leaving_is_not_lost() { - // A reader is running and `pause()` has latched the detach. - let started = AtomicBool::new(true); - let detached = AtomicBool::new(true); - assert!( - reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire)), - "a detached reader must decide to stop" - ); - // `resume()`: clear the latch, then ask for a reader. The old reader - // has not run another instruction since its stop decision. - detached.store(false, Ordering::Release); - assert!( - reader_slot_claim_start(&started), - "restart lost: the stopping reader still held the reader slot" - ); - // The respawned reader owns the slot; a second request is a no-op. - assert!(!reader_slot_claim_start(&started)); - } - - /// The other order: `resume()` clears the latch before the reader looks. - /// The reader keeps running and no second reader may be started on fd 0. - #[test] - fn a_resume_that_beats_the_stop_check_keeps_the_one_reader() { - let started = AtomicBool::new(true); - let detached = AtomicBool::new(true); - detached.store(false, Ordering::Release); - assert!(!reader_slot_claim_start(&started)); - assert!(!reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire))); - assert!(started.load(Ordering::Acquire)); - } - - /// Hammer the handshake from two threads: a "reader" that stops whenever - /// it sees the latch and a "main" that pauses/resumes. After every - /// resume the slot must be owned — by the surviving reader or by the - /// restart — never stranded. - #[test] - fn pause_resume_storm_never_strands_the_slot() { - use std::sync::Arc; - let started = Arc::new(AtomicBool::new(true)); - let detached = Arc::new(AtomicBool::new(false)); - let done = Arc::new(AtomicBool::new(false)); - let reader = { - let (started, detached, done) = (started.clone(), detached.clone(), done.clone()); - std::thread::spawn(move || { - while !done.load(Ordering::Acquire) { - // A live reader polls the latch between reads; one that - // stopped waits to be "respawned" by main's claim. - if started.load(Ordering::Acquire) { - reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire)); - } - std::hint::spin_loop(); - } - }) - }; - for _ in 0..200_000 { - detached.store(true, Ordering::Release); // pause() - detached.store(false, Ordering::Release); // resume(): clear … - reader_slot_claim_start(&started); // … then ensure a reader - assert!( - started.load(Ordering::Acquire), - "resume() returned with no reader owning fd 0" - ); - } - done.store(true, Ordering::Release); - reader.join().unwrap(); - } -} +#[path = "os_process_streams/reader_lifecycle_tests.rs"] +mod reader_lifecycle_tests; fn pump_stdin_data_chunks() { let has_bytes = STDIN_BUFFER.lock().map(|b| !b.is_empty()).unwrap_or(false); diff --git a/crates/perry-runtime/src/os_process_streams/reader_lifecycle_tests.rs b/crates/perry-runtime/src/os_process_streams/reader_lifecycle_tests.rs new file mode 100644 index 0000000000..af8faae47f --- /dev/null +++ b/crates/perry-runtime/src/os_process_streams/reader_lifecycle_tests.rs @@ -0,0 +1,78 @@ +use super::{reader_slot_claim_start, reader_slot_claim_stop}; +use std::sync::atomic::{AtomicBool, Ordering}; + +/// #10895: replays the interleaving that stranded fd 0 without a reader — +/// the reader decides to stop, and `resume()` asks for a restart BEFORE +/// the dying reader has finished leaving. The stop decision must already +/// have released the reader slot, or the restart's claim fails and nobody +/// ever reads stdin again. +/// +/// Runs on local flags: no fd-0 reader is spawned and no process-global +/// stdin state is touched, so it cannot disturb the liveness tests. +#[test] +fn a_restart_requested_while_the_reader_is_leaving_is_not_lost() { + // A reader is running and `pause()` has latched the detach. + let started = AtomicBool::new(true); + let detached = AtomicBool::new(true); + assert!( + reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire)), + "a detached reader must decide to stop" + ); + // `resume()`: clear the latch, then ask for a reader. The old reader + // has not run another instruction since its stop decision. + detached.store(false, Ordering::Release); + assert!( + reader_slot_claim_start(&started), + "restart lost: the stopping reader still held the reader slot" + ); + // The respawned reader owns the slot; a second request is a no-op. + assert!(!reader_slot_claim_start(&started)); +} + +/// The other order: `resume()` clears the latch before the reader looks. +/// The reader keeps running and no second reader may be started on fd 0. +#[test] +fn a_resume_that_beats_the_stop_check_keeps_the_one_reader() { + let started = AtomicBool::new(true); + let detached = AtomicBool::new(true); + detached.store(false, Ordering::Release); + assert!(!reader_slot_claim_start(&started)); + assert!(!reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire))); + assert!(started.load(Ordering::Acquire)); +} + +/// Hammer the handshake from two threads: a "reader" that stops whenever +/// it sees the latch and a "main" that pauses/resumes. After every +/// resume the slot must be owned — by the surviving reader or by the +/// restart — never stranded. +#[test] +fn pause_resume_storm_never_strands_the_slot() { + use std::sync::Arc; + let started = Arc::new(AtomicBool::new(true)); + let detached = Arc::new(AtomicBool::new(false)); + let done = Arc::new(AtomicBool::new(false)); + let reader = { + let (started, detached, done) = (started.clone(), detached.clone(), done.clone()); + std::thread::spawn(move || { + while !done.load(Ordering::Acquire) { + // A live reader polls the latch between reads; one that + // stopped waits to be "respawned" by main's claim. + if started.load(Ordering::Acquire) { + reader_slot_claim_stop(&started, || detached.load(Ordering::Acquire)); + } + std::hint::spin_loop(); + } + }) + }; + for _ in 0..200_000 { + detached.store(true, Ordering::Release); // pause() + detached.store(false, Ordering::Release); // resume(): clear … + reader_slot_claim_start(&started); // … then ensure a reader + assert!( + started.load(Ordering::Acquire), + "resume() returned with no reader owning fd 0" + ); + } + done.store(true, Ordering::Release); + reader.join().unwrap(); +} diff --git a/crates/perry-runtime/src/perf_hooks.rs b/crates/perry-runtime/src/perf_hooks.rs index 3ec25f3069..d5eb74eaad 100644 --- a/crates/perry-runtime/src/perf_hooks.rs +++ b/crates/perry-runtime/src/perf_hooks.rs @@ -1690,6 +1690,7 @@ fn schedule_flush() { #[no_mangle] pub extern "C" fn js_perf_observer_flush_all( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { FLUSH_SCHEDULED.with(|f| f.set(false)); let work: Vec<(u64, u64, Vec)> = OBSERVERS.with(|o| { diff --git a/crates/perry-runtime/src/perf_hooks/prototypes.rs b/crates/perry-runtime/src/perf_hooks/prototypes.rs index 72889e853d..28d926c217 100644 --- a/crates/perry-runtime/src/perf_hooks/prototypes.rs +++ b/crates/perry-runtime/src/perf_hooks/prototypes.rs @@ -28,7 +28,10 @@ fn invalid_perf_receiver(class_name: &str) -> ! { ) } -extern "C" fn perf_entry_field_getter_thunk(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn perf_entry_field_getter_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { unsafe { let this = crate::object::js_implicit_this_get(); let Some(obj) = as_object_ptr(this) else { @@ -52,7 +55,10 @@ extern "C" fn perf_entry_field_getter_thunk(closure: *const crate::closure::Clos } } -extern "C" fn perf_entry_to_json_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn perf_entry_to_json_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { unsafe { let this = crate::object::js_implicit_this_get(); let Some(obj) = as_object_ptr(this) else { @@ -65,12 +71,16 @@ extern "C" fn perf_entry_to_json_thunk(_closure: *const crate::closure::ClosureH } } -extern "C" fn perf_time_origin_getter_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn perf_time_origin_getter_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { time_origin_ms() } extern "C" fn perf_supported_entry_types_getter_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { perf_supported_entry_types_value() } @@ -83,6 +93,7 @@ pub(crate) fn perf_supported_entry_types_value() -> f64 { extern "C" fn perf_observer_observe_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, options: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -94,6 +105,7 @@ extern "C" fn perf_observer_observe_thunk( extern "C" fn perf_observer_disconnect_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); if !is_perf_observer_value(this) { @@ -104,6 +116,7 @@ extern "C" fn perf_observer_disconnect_thunk( extern "C" fn perf_observer_take_records_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); if !is_perf_observer_value(this) { @@ -112,7 +125,10 @@ extern "C" fn perf_observer_take_records_thunk( js_perf_observer_take_records(this) } -extern "C" fn perf_list_get_entries_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn perf_list_get_entries_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let this = crate::object::js_implicit_this_get(); if !is_perf_observer_list_value(this) { invalid_perf_receiver("PerformanceObserverEntryList"); @@ -122,6 +138,7 @@ extern "C" fn perf_list_get_entries_thunk(_closure: *const crate::closure::Closu extern "C" fn perf_list_get_by_type_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, entry_type: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -133,6 +150,7 @@ extern "C" fn perf_list_get_by_type_thunk( extern "C" fn perf_list_get_by_name_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); diff --git a/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs b/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs index a1d6ad18a9..ffc9bbcb70 100644 --- a/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs +++ b/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs @@ -10,6 +10,7 @@ fn require_performance_receiver() { pub(super) extern "C" fn clear_marks( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, ) -> f64 { require_performance_receiver(); @@ -18,6 +19,7 @@ pub(super) extern "C" fn clear_marks( pub(super) extern "C" fn clear_measures( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, ) -> f64 { require_performance_receiver(); @@ -26,18 +28,23 @@ pub(super) extern "C" fn clear_measures( pub(super) extern "C" fn clear_resource_timings( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { require_performance_receiver(); js_perf_clear_resource_timings() } -pub(super) extern "C" fn get_entries(_closure: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn get_entries( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { require_performance_receiver(); js_perf_get_entries() } pub(super) extern "C" fn get_entries_by_name( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, entry_type: f64, ) -> f64 { @@ -47,6 +54,7 @@ pub(super) extern "C" fn get_entries_by_name( pub(super) extern "C" fn get_entries_by_type( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, entry_type: f64, ) -> f64 { require_performance_receiver(); @@ -55,6 +63,7 @@ pub(super) extern "C" fn get_entries_by_type( pub(super) extern "C" fn mark( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, options: f64, ) -> f64 { @@ -64,6 +73,7 @@ pub(super) extern "C" fn mark( pub(super) extern "C" fn measure( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, start_or_options: f64, end: f64, @@ -72,26 +82,34 @@ pub(super) extern "C" fn measure( js_perf_measure(name, start_or_options, end) } -pub(super) extern "C" fn now(_closure: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn now( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { require_performance_receiver(); crate::date::js_performance_now() } pub(super) extern "C" fn set_resource_timing_buffer_size( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, size: f64, ) -> f64 { require_performance_receiver(); js_perf_set_resource_timing_buffer_size(size) } -pub(super) extern "C" fn to_json(_closure: *const crate::closure::ClosureHeader) -> f64 { +pub(super) extern "C" fn to_json( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { require_performance_receiver(); js_perf_to_json() } pub(super) extern "C" fn event_loop_utilization( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, utilization1: f64, utilization2: f64, ) -> f64 { @@ -101,6 +119,7 @@ pub(super) extern "C" fn event_loop_utilization( pub(super) extern "C" fn mark_resource_timing( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, timing_info: f64, requested_url: f64, initiator_type: f64, @@ -125,6 +144,7 @@ pub(super) extern "C" fn mark_resource_timing( pub(super) extern "C" fn timerify( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, function: f64, options: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/perf_hooks/timerify.rs b/crates/perry-runtime/src/perf_hooks/timerify.rs index 1f442dc465..361aedca30 100644 --- a/crates/perry-runtime/src/perf_hooks/timerify.rs +++ b/crates/perry-runtime/src/perf_hooks/timerify.rs @@ -48,6 +48,7 @@ unsafe fn finish_timerify_entry(name_value: f64, start_time: f64, histogram: f64 extern "C" fn perf_timerify_settle( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, outcome: f64, ) -> f64 { unsafe { @@ -64,6 +65,7 @@ extern "C" fn perf_timerify_settle( extern "C" fn perf_timerify_wrapper( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, rest: f64, ) -> f64 { unsafe { diff --git a/crates/perry-runtime/src/process/env_misc.rs b/crates/perry-runtime/src/process/env_misc.rs index cfba2ee481..88a93b3e7b 100644 --- a/crates/perry-runtime/src/process/env_misc.rs +++ b/crates/perry-runtime/src/process/env_misc.rs @@ -648,7 +648,10 @@ fn set_error_string_prop(error: *mut crate::error::ErrorHeader, name: &str, valu static WARNED_PROCESS_WARNING_TRACE_HINT: AtomicBool = AtomicBool::new(false); -extern "C" fn process_warning_callback(closure: *const ClosureHeader) -> f64 { +extern "C" fn process_warning_callback( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { use std::io::Write; if closure.is_null() { diff --git a/crates/perry-runtime/src/process/finalization.rs b/crates/perry-runtime/src/process/finalization.rs index 08c8dc93d7..8601555658 100644 --- a/crates/perry-runtime/src/process/finalization.rs +++ b/crates/perry-runtime/src/process/finalization.rs @@ -6,6 +6,7 @@ use super::*; extern "C" fn process_finalization_before_exit_listener( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _code: f64, ) -> f64 { js_process_run_finalization_before_exit(); @@ -191,6 +192,7 @@ pub fn scan_process_finalization_roots_mut(visitor: &mut crate::gc::RuntimeRootV extern "C" fn process_finalization_register_function( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, callback: f64, ) -> f64 { @@ -199,6 +201,7 @@ extern "C" fn process_finalization_register_function( extern "C" fn process_finalization_register_before_exit_function( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, callback: f64, ) -> f64 { @@ -207,6 +210,7 @@ extern "C" fn process_finalization_register_before_exit_function( extern "C" fn process_finalization_unregister_function( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, obj: f64, ) -> f64 { process_finalization_unregister(obj) diff --git a/crates/perry-runtime/src/process/ipc.rs b/crates/perry-runtime/src/process/ipc.rs index 4d0d8a661e..696d4dbe95 100644 --- a/crates/perry-runtime/src/process/ipc.rs +++ b/crates/perry-runtime/src/process/ipc.rs @@ -390,7 +390,10 @@ fn process_channel_value() -> f64 { object_value(obj) } -extern "C" fn process_channel_ref_fn(_closure: *const ClosureHeader) -> f64 { +extern "C" fn process_channel_ref_fn( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { process_ipc_ensure_initialized(); let mut state = ipc_lock(); if state.available && state.connected { @@ -399,7 +402,10 @@ extern "C" fn process_channel_ref_fn(_closure: *const ClosureHeader) -> f64 { undefined_value() } -extern "C" fn process_channel_unref_fn(_closure: *const ClosureHeader) -> f64 { +extern "C" fn process_channel_unref_fn( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { process_ipc_ensure_initialized(); let mut state = ipc_lock(); if state.available && state.connected { @@ -408,12 +414,16 @@ extern "C" fn process_channel_unref_fn(_closure: *const ClosureHeader) -> f64 { undefined_value() } -extern "C" fn process_ipc_disconnect_fn(_closure: *const ClosureHeader) -> f64 { +extern "C" fn process_ipc_disconnect_fn( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { process_ipc_disconnect_call() } extern "C" fn process_ipc_send_fn( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, message: f64, a2: f64, a3: f64, @@ -562,7 +572,10 @@ fn defer_send_callback(cb: f64, ok: bool) { crate::timer::js_set_immediate_callback(deferred as i64); } -extern "C" fn process_ipc_send_callback_thunk(closure: *const ClosureHeader) -> f64 { +extern "C" fn process_ipc_send_callback_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let cb = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); if crate::fs::extract_closure_ptr(cb).is_null() { return undefined_value(); diff --git a/crates/perry-runtime/src/process/node_module.rs b/crates/perry-runtime/src/process/node_module.rs index 6a87ae504a..9e7326ed9e 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -57,6 +57,7 @@ pub extern "C" fn js_module_constants() -> f64 { extern "C" fn module_require_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, specifier: f64, ) -> f64 { js_module_instance_require(specifier) @@ -872,7 +873,10 @@ fn module_hook_member(value: f64, name: &str) -> f64 { crate::fs::validate::throw_type_error_with_code(&message, "ERR_INVALID_ARG_TYPE"); } -extern "C" fn module_hooks_deregister(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn module_hooks_deregister( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as u64; // 2026-07-09 GC audit wave 2: deregister used to only flip `active`, // leaving the resolve/load closures strongly rooted by @@ -962,6 +966,7 @@ pub extern "C" fn js_module_register_hooks(hooks: f64) -> f64 { extern "C" fn module_loader_next_resolve( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, specifier: f64, _context: f64, ) -> f64 { @@ -973,6 +978,7 @@ extern "C" fn module_loader_next_resolve( extern "C" fn module_loader_next_load( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _url: f64, _context: f64, ) -> f64 { @@ -1030,6 +1036,7 @@ fn module_loader_result_url(result: f64, fallback: f64) -> f64 { extern "C" fn module_loader_hook_chain( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, context: f64, ) -> f64 { 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 1c15add93b..5ba92d6592 100644 --- a/crates/perry-runtime/src/process/node_module/source_map.rs +++ b/crates/perry-runtime/src/process/node_module/source_map.rs @@ -97,7 +97,10 @@ fn source_map_method(name: &str, thunk: SourceMapThunk) -> f64 { crate::value::js_nanbox_pointer(closure as i64) } -extern "C" fn source_map_payload_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn source_map_payload_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = source_map_receiver(); let scope = crate::gc::RuntimeHandleScope::new(); let payload = scope.root_nanbox_f64(f64::from_bits( @@ -106,7 +109,10 @@ extern "C" fn source_map_payload_getter(_closure: *const ClosureHeader) -> f64 { crate::builtins::js_structured_clone(payload.get_nanbox_f64()) } -extern "C" fn source_map_line_lengths_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn source_map_line_lengths_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let obj = source_map_receiver(); let scope = crate::gc::RuntimeHandleScope::new(); let value = scope.root_nanbox_f64(f64::from_bits( @@ -599,7 +605,11 @@ fn source_map_origin_object( /// decoded entry whose generated position is `<=` the query, shaped like /// Node's `{ generatedLine, generatedColumn, originalSource, originalLine, /// originalColumn, name? }`. Returns `{}` when no entry precedes the query. -extern "C" fn source_map_find_entry_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn source_map_find_entry_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let _ = closure; let receiver = source_map_receiver(); let scope = crate::gc::RuntimeHandleScope::new(); @@ -647,7 +657,11 @@ extern "C" fn source_map_find_entry_thunk(closure: *const ClosureHeader, rest: f /// not a finite number) and tags on the `name`/`fileName` of the entry at that /// generated position. The lone special case is a numeric `(0, 0)` query, for /// which Node returns an empty object. -extern "C" fn source_map_find_origin_thunk(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn source_map_find_origin_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { let _ = closure; let receiver = source_map_receiver(); let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/process/permission.rs b/crates/perry-runtime/src/process/permission.rs index e8b2fb5eb6..4ec02294d2 100644 --- a/crates/perry-runtime/src/process/permission.rs +++ b/crates/perry-runtime/src/process/permission.rs @@ -146,6 +146,7 @@ fn throw_permission_arg_type(name: &str, value: f64) -> ! { extern "C" fn process_permission_has_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, scope_value: f64, reference_value: f64, ) -> f64 { @@ -168,6 +169,7 @@ extern "C" fn process_permission_has_thunk( extern "C" fn process_permission_drop_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, scope_value: f64, reference_value: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/process/report.rs b/crates/perry-runtime/src/process/report.rs index e0e995b59c..1c5421c675 100644 --- a/crates/perry-runtime/src/process/report.rs +++ b/crates/perry-runtime/src/process/report.rs @@ -34,6 +34,7 @@ pub(crate) fn process_features_value() -> f64 { extern "C" fn process_report_function_get_report( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, err: f64, ) -> f64 { validate_report_error_arg(err); @@ -42,6 +43,7 @@ extern "C" fn process_report_function_get_report( extern "C" fn process_report_function_write_report( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, file: f64, err: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/promise/assimilate.rs b/crates/perry-runtime/src/promise/assimilate.rs index 7f28ad80a9..b916d6a430 100644 --- a/crates/perry-runtime/src/promise/assimilate.rs +++ b/crates/perry-runtime/src/promise/assimilate.rs @@ -251,7 +251,10 @@ pub(super) fn enqueue_native_adoption_job(outer: *mut Promise, inner: *mut Promi /// a synchronous copy, matching V8's reaction-job. A still-pending inner /// falls back to the existing chain wiring: its settlement path already /// delivers through the microtask runner. -extern "C" fn native_promise_adoption_job(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn native_promise_adoption_job( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { use crate::closure::js_closure_get_capture_ptr; let outer = js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -317,6 +320,7 @@ fn thenable_job_take_guard(guard_arr: *mut crate::array::ArrayHeader) -> bool { pub(super) extern "C" fn thenable_job_resolve_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -331,6 +335,7 @@ pub(super) extern "C" fn thenable_job_resolve_fn( pub(super) extern "C" fn thenable_job_reject_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -343,7 +348,10 @@ pub(super) extern "C" fn thenable_job_reject_fn( 0.0 } -extern "C" fn promise_resolve_thenable_job(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn promise_resolve_thenable_job( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { use crate::array::{js_array_alloc, js_array_set_f64}; use crate::closure::{ js_closure_alloc, js_closure_get_capture_f64, js_closure_get_capture_ptr, diff --git a/crates/perry-runtime/src/promise/async_step.rs b/crates/perry-runtime/src/promise/async_step.rs index a709b4592e..a4a86f497d 100644 --- a/crates/perry-runtime/src/promise/async_step.rs +++ b/crates/perry-runtime/src/promise/async_step.rs @@ -1026,6 +1026,7 @@ fn build_async_step_thunks( // Capture layout: [step_closure_ptr] extern "C" fn async_step_fulfill_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let step = crate::closure::js_closure_get_capture_ptr(closure, 0) @@ -1148,6 +1149,7 @@ fn forward_swallowed_rejection(result: f64, trap_next: *mut Promise) { extern "C" fn async_step_reject_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let step = crate::closure::js_closure_get_capture_ptr(closure, 0) @@ -1372,11 +1374,16 @@ fn array_from_async_call_next( } // Synchronous iterator path: invoke the handler directly with the // result so the iteration loop continues without going through .then. - array_from_async_step(chain_closure as *const _, next_result); + array_from_async_step( + chain_closure as *const _, + crate::closure::JsThis::current(), + next_result, + ); } extern "C" fn array_from_async_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let result_promise = crate::closure::js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -1392,6 +1399,7 @@ extern "C" fn array_from_async_reject( /// another `.next()` call. extern "C" fn array_from_async_step( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, iter_result: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1480,6 +1488,7 @@ fn array_from_async_await_value(closure: *const crate::closure::ClosureHeader, v extern "C" fn array_from_async_value_step( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let state = crate::closure::js_closure_get_capture_ptr(closure, 0) @@ -1515,6 +1524,7 @@ fn array_from_async_map_or_push(closure: *const crate::closure::ClosureHeader, v extern "C" fn array_from_async_mapped_step( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let state = crate::closure::js_closure_get_capture_ptr(closure, 0) @@ -1603,6 +1613,7 @@ mod tests { extern "C" fn relocating_step( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _value: f64, _is_error: f64, ) -> f64 { @@ -1702,7 +1713,7 @@ mod tests { ]) }); - async_step_fulfill_thunk(thunk, 41.0); + async_step_fulfill_thunk(thunk, crate::closure::JsThis::current(), 41.0); assert_eq!( (*captured_to).state, diff --git a/crates/perry-runtime/src/promise/checked_dispatch.rs b/crates/perry-runtime/src/promise/checked_dispatch.rs index c8bcb5f9e1..54ac22139c 100644 --- a/crates/perry-runtime/src/promise/checked_dispatch.rs +++ b/crates/perry-runtime/src/promise/checked_dispatch.rs @@ -65,7 +65,12 @@ pub extern "C" fn js_promise_then_checked( // backing cell and reads `this.constructor` for species chaining. let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_then_thunk(std::ptr::null(), on_fulfilled, on_rejected); + let result = promise_prototype_then_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + on_fulfilled, + on_rejected, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -86,7 +91,11 @@ pub extern "C" fn js_promise_catch_checked(promise_val: f64, on_rejected: f64) - { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_catch_thunk(std::ptr::null(), on_rejected); + let result = promise_prototype_catch_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + on_rejected, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -103,7 +112,11 @@ pub extern "C" fn js_promise_finally_checked(promise_val: f64, on_finally: f64) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_finally_thunk(std::ptr::null(), on_finally); + let result = promise_prototype_finally_thunk( + std::ptr::null(), + crate::closure::JsThis::current(), + on_finally, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index f12c6e8189..0a16aa087c 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -859,6 +859,7 @@ pub(super) fn make_resolving_functions( /// rather than fulfilling with the thenable as a plain value. pub(super) extern "C" fn promise_resolve_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -896,6 +897,7 @@ pub(super) extern "C" fn promise_resolve_fn( /// Called when user calls reject(reason) inside the executor. pub(super) extern "C" fn promise_reject_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1079,6 +1081,7 @@ fn attach_promise_all_after_prior_reaction(promise: *mut Promise, state: Promise extern "C" fn promise_all_ordered_fulfill_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_closure_get_capture_ptr}; @@ -1096,6 +1099,7 @@ extern "C" fn promise_all_ordered_fulfill_handler( extern "C" fn promise_all_ordered_reject_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1195,6 +1199,7 @@ pub extern "C" fn js_promise_race(promises_arr: *const crate::array::ArrayHeader /// Handler for Promise.race fulfill — resolves the race promise with the first value extern "C" fn promise_race_resolve_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1212,6 +1217,7 @@ extern "C" fn promise_race_resolve_handler( /// Handler for Promise.race reject — rejects the race promise with the first reason extern "C" fn promise_race_reject_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1537,6 +1543,7 @@ pub extern "C" fn js_promise_all_settled( extern "C" fn promise_all_settled_fulfill_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::array::{js_array_get_f64, js_array_set_f64, ArrayHeader}; @@ -1565,6 +1572,7 @@ extern "C" fn promise_all_settled_fulfill_handler( extern "C" fn promise_all_settled_reject_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::array::{js_array_get_f64, js_array_set_f64, ArrayHeader}; @@ -1671,6 +1679,7 @@ pub extern "C" fn js_promise_any(promises_arr: *const crate::array::ArrayHeader) extern "C" fn promise_any_fulfill_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::array::{js_array_get_f64, js_array_set_f64, ArrayHeader}; @@ -1694,6 +1703,7 @@ extern "C" fn promise_any_fulfill_handler( extern "C" fn promise_any_reject_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::array::{js_array_get_f64, js_array_set_f64, ArrayHeader}; @@ -1798,6 +1808,7 @@ mod tests { extern "C" fn test_thenable_resolve_twice( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_fulfilled: f64, _on_rejected: f64, ) -> f64 { @@ -1812,6 +1823,7 @@ mod tests { extern "C" fn test_thenable_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _on_fulfilled: f64, on_rejected: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/promise/microtasks.rs b/crates/perry-runtime/src/promise/microtasks.rs index 8e2bb62df0..f460c190b8 100644 --- a/crates/perry-runtime/src/promise/microtasks.rs +++ b/crates/perry-runtime/src/promise/microtasks.rs @@ -1189,7 +1189,13 @@ fn call_async_step_direct( // the stored function pointer instead. unsafe { let func_ptr = (*step_closure).func_ptr; - crate::closure::body_call::js_body_call!(func_ptr, step_closure, value, is_error_bits) + crate::closure::body_call::js_body_call!( + func_ptr, + step_closure, + crate::closure::JsThis::current(), + value, + is_error_bits + ) } } diff --git a/crates/perry-runtime/src/promise/microtasks/empty.rs b/crates/perry-runtime/src/promise/microtasks/empty.rs index ab19a55f4e..0072a167a9 100644 --- a/crates/perry-runtime/src/promise/microtasks/empty.rs +++ b/crates/perry-runtime/src/promise/microtasks/empty.rs @@ -79,7 +79,10 @@ mod tests { fn queued_microtask_runs_and_restores_quiescence() { isolated(|| { static CALLED: AtomicU64 = AtomicU64::new(0); - extern "C" fn callback(_: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn callback( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } @@ -181,7 +184,11 @@ mod tests { fn buffered_stdin_is_delivered_without_any_timer() { isolated(|| { static CALLED: AtomicU64 = AtomicU64::new(0); - extern "C" fn callback(_: *const crate::closure::ClosureHeader, _: f64) -> f64 { + extern "C" fn callback( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _: f64, + ) -> f64 { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } @@ -237,7 +244,10 @@ mod tests { fn exit_promise_checkpoint_leaves_ticks_forbidden() { isolated(|| { static CALLED: AtomicU64 = AtomicU64::new(0); - extern "C" fn callback(_: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn callback( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { CALLED.fetch_add(1, Ordering::Relaxed); 0.0 } diff --git a/crates/perry-runtime/src/promise/rejection_loop_tests.rs b/crates/perry-runtime/src/promise/rejection_loop_tests.rs index 5bccbf643a..b3401d7ddc 100644 --- a/crates/perry-runtime/src/promise/rejection_loop_tests.rs +++ b/crates/perry-runtime/src/promise/rejection_loop_tests.rs @@ -3,7 +3,12 @@ use super::*; // Models `for (...) { try { await Promise.reject(remaining) } catch {} }`. // Every resumption advances the loop even though no fulfilled await occurs. -extern "C" fn catching_step(step: ClosurePtr, remaining: f64, is_error: f64) -> f64 { +extern "C" fn catching_step( + step: ClosurePtr, + _this: crate::closure::JsThis, + remaining: f64, + is_error: f64, +) -> f64 { if is_error.to_bits() != crate::value::TAG_TRUE { return crate::value::js_nanbox_pointer(js_async_step_done(-1.0, step) as i64); } 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 9e509a9983..537b58fbbf 100644 --- a/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs +++ b/crates/perry-runtime/src/promise/resolving_function_kind_tests.rs @@ -64,7 +64,11 @@ fn resolving_functions_answer_reflection_from_their_kind() { } } -extern "C" fn unrelated_native_body(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn unrelated_native_body( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { value } diff --git a/crates/perry-runtime/src/promise/scanners.rs b/crates/perry-runtime/src/promise/scanners.rs index 71f4417f8c..ea74f91fe7 100644 --- a/crates/perry-runtime/src/promise/scanners.rs +++ b/crates/perry-runtime/src/promise/scanners.rs @@ -1056,6 +1056,7 @@ pub(crate) unsafe fn test_store_with_resolvers_result_fields( extern "C" fn with_resolvers_resolve_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let promise_box = crate::closure::js_closure_get_capture_f64(closure, 0); @@ -1066,6 +1067,7 @@ extern "C" fn with_resolvers_resolve_handler( extern "C" fn with_resolvers_reject_handler( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let promise_box = crate::closure::js_closure_get_capture_f64(closure, 0); diff --git a/crates/perry-runtime/src/promise/spec_combinators.rs b/crates/perry-runtime/src/promise/spec_combinators.rs index 21d99fe3e7..7dcb248480 100644 --- a/crates/perry-runtime/src/promise/spec_combinators.rs +++ b/crates/perry-runtime/src/promise/spec_combinators.rs @@ -167,6 +167,7 @@ fn is_callable(value: f64) -> bool { /// TypeError if either slot is already set (executor called twice). extern "C" fn capability_executor_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, resolve: f64, reject: f64, ) -> f64 { @@ -392,6 +393,7 @@ fn dec_remaining(state: *mut crate::array::ArrayHeader) -> bool { /// Promise.all Resolve Element Function. extern "C" fn all_resolve_element_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let guard = js_closure_get_capture_ptr(closure, 0) as *mut crate::array::ArrayHeader; @@ -414,6 +416,7 @@ extern "C" fn all_resolve_element_fn( /// Promise.allSettled Resolve Element Function → `{status:"fulfilled", value}`. extern "C" fn settled_fulfill_element_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { settled_element(closure, value, true) @@ -422,6 +425,7 @@ extern "C" fn settled_fulfill_element_fn( /// Promise.allSettled Reject Element Function → `{status:"rejected", reason}`. extern "C" fn settled_reject_element_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { settled_element(closure, reason, false) @@ -471,6 +475,7 @@ fn settled_element( /// AggregateError once all reject. extern "C" fn any_reject_element_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let guard = js_closure_get_capture_ptr(closure, 0) as *mut crate::array::ArrayHeader; @@ -1331,6 +1336,7 @@ mod fast_arm_tests { extern "C" fn own_then_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_fulfilled: f64, _on_rejected: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/promise/then.rs b/crates/perry-runtime/src/promise/then.rs index c1a550b085..2a7f15cf05 100644 --- a/crates/perry-runtime/src/promise/then.rs +++ b/crates/perry-runtime/src/promise/then.rs @@ -436,6 +436,7 @@ pub extern "C" fn js_promise_resolve_with_promise(outer: *mut Promise, inner: *m /// Internal callback for forwarding resolve from inner to outer promise extern "C" fn promise_forward_resolve( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let outer_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -446,6 +447,7 @@ extern "C" fn promise_forward_resolve( /// Internal callback for forwarding reject from inner to outer promise extern "C" fn promise_forward_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let outer_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -1204,6 +1206,7 @@ fn promise_species_constructor(receiver: f64) -> f64 { // Captures: [on_fulfilled_f64, cap_resolve_f64, cap_reject_f64] extern "C" fn then_cap_fulfill_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_native_call_value}; @@ -1238,6 +1241,7 @@ extern "C" fn then_cap_fulfill_fn( // Captures: [on_rejected_f64, cap_resolve_f64, cap_reject_f64] extern "C" fn then_cap_reject_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_native_call_value}; @@ -1335,6 +1339,7 @@ fn perform_promise_then_with_cap( // Captures [value_f64]: ignores its argument and returns the captured value. extern "C" fn spec_value_return_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { crate::closure::js_closure_get_capture_f64(closure, 0) @@ -1343,6 +1348,7 @@ extern "C" fn spec_value_return_fn( // Captures [reason_f64]: ignores its argument and throws the captured reason. extern "C" fn spec_reason_thrower_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { let reason = crate::closure::js_closure_get_capture_f64(closure, 0); @@ -1352,6 +1358,7 @@ extern "C" fn spec_reason_thrower_fn( // thenFinally: captures [C_f64, on_finally_f64], called with fulfilled value. extern "C" fn spec_then_finally_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { use crate::closure::{ @@ -1388,6 +1395,7 @@ extern "C" fn spec_then_finally_fn( // catchFinally: captures [C_f64, on_finally_f64], called with rejection reason. extern "C" fn spec_catch_finally_fn( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { use crate::closure::{ @@ -1445,6 +1453,7 @@ fn ensure_spec_finally_arities_registered() { pub(crate) extern "C" fn promise_prototype_then_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_fulfilled: f64, on_rejected: f64, ) -> f64 { @@ -1501,6 +1510,7 @@ pub(crate) extern "C" fn promise_prototype_then_thunk( pub(crate) extern "C" fn promise_prototype_catch_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_rejected: f64, ) -> f64 { let receiver = crate::object::js_implicit_this_get(); @@ -1514,6 +1524,7 @@ pub(crate) extern "C" fn promise_prototype_catch_thunk( pub(crate) extern "C" fn promise_prototype_finally_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_finally: f64, ) -> f64 { use crate::closure::{js_closure_alloc, js_closure_set_capture_f64}; @@ -1590,6 +1601,7 @@ pub unsafe fn js_promise_bound_method(_promise: *mut Promise, property: &str) -> /// Called with the upstream fulfilled `value`. extern "C" fn finally_fulfill_wrapper( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { finally_wrapper_common(closure, value, true) @@ -1600,6 +1612,7 @@ extern "C" fn finally_fulfill_wrapper( /// Called with the upstream rejection `reason`. extern "C" fn finally_reject_wrapper( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { finally_wrapper_common(closure, reason, false) @@ -1742,6 +1755,7 @@ fn finally_settle_next_with_extra_hop(next: *mut Promise, orig: f64, is_fulfille /// Captures [next_promise_ptr (i64), orig (f64), is_fulfilled (TAG_TRUE/FALSE)]. extern "C" fn finally_cleanup_fulfill( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _cleanup_value: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_closure_get_capture_ptr}; @@ -1762,6 +1776,7 @@ extern "C" fn finally_cleanup_fulfill( /// the upstream outcome). Captures [next_promise_ptr (i64)]. extern "C" fn finally_cleanup_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, cleanup_reason: f64, ) -> f64 { use crate::closure::js_closure_get_capture_ptr; @@ -1776,6 +1791,7 @@ extern "C" fn finally_cleanup_reject( /// Captures [next_promise_ptr (i64), value (f64)]. extern "C" fn finally_passthrough_fulfill( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_closure_get_capture_ptr}; @@ -1818,6 +1834,7 @@ extern "C" fn finally_passthrough_fulfill( /// Captures [next_promise_ptr (i64), reason (f64)]. extern "C" fn finally_passthrough_reject( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _: f64, ) -> f64 { use crate::closure::{js_closure_get_capture_f64, js_closure_get_capture_ptr}; diff --git a/crates/perry-runtime/src/proxy.rs b/crates/perry-runtime/src/proxy.rs index c933074964..ea2fda15aa 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -2918,7 +2918,10 @@ mod tests { /// including through a nested proxy whose inner proxy gets revoked. #[test] fn revoked_proxy_keeps_creation_callability() { - extern "C" fn dummy_fn(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn dummy_fn( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { f64::from_bits(TAG_UNDEFINED) } let f = crate::closure::js_closure_alloc(dummy_fn as *const u8, 0); diff --git a/crates/perry-runtime/src/proxy/reflect_misc.rs b/crates/perry-runtime/src/proxy/reflect_misc.rs index 2ae7545d1f..ebc1a867ea 100644 --- a/crates/perry-runtime/src/proxy/reflect_misc.rs +++ b/crates/perry-runtime/src/proxy/reflect_misc.rs @@ -366,6 +366,7 @@ pub extern "C" fn js_reflect_prevent_extensions(target: f64) -> f64 { /// already-revoked proxy is a no-op (Node's `revoke()` is idempotent). (#2846) pub(super) extern "C" fn proxy_revoke_trampoline( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let proxy = crate::closure::js_closure_get_capture_f64(closure, 0); js_proxy_revoke(proxy); @@ -378,6 +379,7 @@ mod tests { extern "C" fn weighted_six( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, a0: f64, a1: f64, a2: f64, diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index fe5fcd1ae6..5d73a37808 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -139,7 +139,10 @@ mod platform_impl { val } - extern "C" fn pty_disposable_dispose(closure: *const ClosureHeader) -> f64 { + extern "C" fn pty_disposable_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let this = cp_this(closure); let target = cp_get_field(this, b"__ptyTarget"); let cb = cp_get_field(this, b"__ptyCb"); @@ -165,19 +168,31 @@ mod platform_impl { // ----- IPty method bodies (slot 0 of each closure = the IPty object) ---- - extern "C" fn pty_method_on_data(closure: *const ClosureHeader, cb: f64) -> f64 { + extern "C" fn pty_method_on_data( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, + ) -> f64 { let this = cp_this(closure); pty_register(this, "data", cb); pty_make_disposable(this, "data", cb) } - extern "C" fn pty_method_on_exit(closure: *const ClosureHeader, cb: f64) -> f64 { + extern "C" fn pty_method_on_exit( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cb: f64, + ) -> f64 { let this = cp_this(closure); pty_register(this, "exit", cb); pty_make_disposable(this, "exit", cb) } - extern "C" fn pty_method_write(closure: *const ClosureHeader, data: f64) -> f64 { + extern "C" fn pty_method_write( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + data: f64, + ) -> f64 { let this = cp_this(closure); if let Some(handle) = pty_handle_of(this) { let bytes = cp_value_to_bytes(data); @@ -188,7 +203,12 @@ mod platform_impl { cp_undefined() } - extern "C" fn pty_method_resize(closure: *const ClosureHeader, cols: f64, rows: f64) -> f64 { + extern "C" fn pty_method_resize( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + cols: f64, + rows: f64, + ) -> f64 { let this = cp_this(closure); let cols_i = pty_arg_i32(cols); let rows_i = pty_arg_i32(rows); @@ -208,7 +228,11 @@ mod platform_impl { cp_undefined() } - extern "C" fn pty_method_kill(closure: *const ClosureHeader, signal: f64) -> f64 { + extern "C" fn pty_method_kill( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + signal: f64, + ) -> f64 { let this = cp_this(closure); if let Some(handle) = pty_handle_of(this) { reactor::pty_live_kill(handle, pty_parse_kill_signal(signal)); @@ -216,14 +240,20 @@ mod platform_impl { cp_undefined() } - extern "C" fn pty_method_pause(closure: *const ClosureHeader) -> f64 { + extern "C" fn pty_method_pause( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { if let Some(handle) = pty_handle_of(cp_this(closure)) { reactor::pty_live_set_paused(handle, true); } cp_undefined() } - extern "C" fn pty_method_resume(closure: *const ClosureHeader) -> f64 { + extern "C" fn pty_method_resume( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { if let Some(handle) = pty_handle_of(cp_this(closure)) { reactor::pty_live_set_paused(handle, false); } @@ -233,9 +263,12 @@ mod platform_impl { /// opencode's `Pty` wrapper calls `dispose()`; node-pty's UnixTerminal /// exposes the equivalent `destroy()`. Both are "hang up the terminal": /// kill with the default SIGHUP. - extern "C" fn pty_method_dispose(closure: *const ClosureHeader) -> f64 { - pty_method_resume(closure); - pty_method_kill(closure, cp_undefined()) + extern "C" fn pty_method_dispose( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { + pty_method_resume(closure, crate::closure::JsThis::current()); + pty_method_kill(closure, crate::closure::JsThis::current(), cp_undefined()) } /// `kill([signal])` — node-pty defaults to `SIGHUP` (a hangup is how a @@ -430,14 +463,22 @@ mod platform_impl { /// runner can't interleave their pumps. static TEST_LOCK: Mutex<()> = Mutex::new(()); - extern "C" fn test_data_listener(_closure: *const ClosureHeader, chunk: f64) -> f64 { + extern "C" fn test_data_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, + ) -> f64 { if let Some(s) = cp_value_to_string(chunk) { DATA_SINK.lock().unwrap().push_str(&s); } cp_undefined() } - extern "C" fn test_exit_listener(_closure: *const ClosureHeader, payload: f64) -> f64 { + extern "C" fn test_exit_listener( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + payload: f64, + ) -> f64 { let code = cp_get_field(payload, b"exitCode"); let signal = cp_get_field(payload, b"signal"); *EXIT_SINK.lock().unwrap() = Some((code, signal)); diff --git a/crates/perry-runtime/src/pty/tests.rs b/crates/perry-runtime/src/pty/tests.rs index 6fc2129be6..56950891db 100644 --- a/crates/perry-runtime/src/pty/tests.rs +++ b/crates/perry-runtime/src/pty/tests.rs @@ -13,17 +13,29 @@ thread_local! { static REMOVED_CALLS: Cell = const { Cell::new(0) }; } -extern "C" fn removed(_closure: *const ClosureHeader, _chunk: f64) -> f64 { +extern "C" fn removed( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _chunk: f64, +) -> f64 { REMOVED_CALLS.with(|n| n.set(n.get() + 1)); cp_undefined() } -extern "C" fn data(_closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn data( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { OUTPUT.with(|o| o.borrow_mut().push_str(&cp_value_to_string(chunk).unwrap())); cp_undefined() } -extern "C" fn exited(_closure: *const ClosureHeader, event: f64) -> f64 { +extern "C" fn exited( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + event: f64, +) -> f64 { EXITS.with(|e| e.borrow_mut().push(cp_get_field(event, b"exitCode"))); cp_undefined() } diff --git a/crates/perry-runtime/src/readline_helpers.rs b/crates/perry-runtime/src/readline_helpers.rs index e7c5e8a6ae..88d3dd5ca0 100644 --- a/crates/perry-runtime/src/readline_helpers.rs +++ b/crates/perry-runtime/src/readline_helpers.rs @@ -246,7 +246,11 @@ pub fn split_keypress_chunks(bytes: &[u8]) -> Vec> { chunks } -extern "C" fn emit_keypress_data(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn emit_keypress_data( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + chunk: f64, +) -> f64 { let stream_scope = crate::gc::RuntimeHandleScope::new(); let stream = stream_scope.root_nanbox_f64(js_closure_get_capture_f64(closure, 0)); let chunk = stream_scope.root_nanbox_f64(chunk); diff --git a/crates/perry-runtime/src/regex/match_all.rs b/crates/perry-runtime/src/regex/match_all.rs index ff57fa7980..4ad2297fd3 100644 --- a/crates/perry-runtime/src/regex/match_all.rs +++ b/crates/perry-runtime/src/regex/match_all.rs @@ -141,6 +141,7 @@ pub(crate) fn regexp(receiver: f64, argument: f64) -> Result { pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, argument: f64, ) -> f64 { api::finish(regexp(crate::object::js_implicit_this_get(), argument)) diff --git a/crates/perry-runtime/src/regex/perex_match_search.rs b/crates/perry-runtime/src/regex/perex_match_search.rs index f6dc3cebad..5c0ee32b7b 100644 --- a/crates/perry-runtime/src/regex/perex_match_search.rs +++ b/crates/perry-runtime/src/regex/perex_match_search.rs @@ -354,14 +354,22 @@ pub(crate) fn string( dispatch::call_one(&method, &re, &argument) } -pub(crate) extern "C" fn match_thunk(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn match_thunk( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { api::finish(regexp( Operation::Match, crate::object::js_implicit_this_get(), arg, )) } -pub(crate) extern "C" fn search_thunk(_: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +pub(crate) extern "C" fn search_thunk( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { api::finish(regexp( Operation::Search, crate::object::js_implicit_this_get(), diff --git a/crates/perry-runtime/src/regex/perex_replace.rs b/crates/perry-runtime/src/regex/perex_replace.rs index 2d7f66ebcc..bc21b07d78 100644 --- a/crates/perry-runtime/src/regex/perex_replace.rs +++ b/crates/perry-runtime/src/regex/perex_replace.rs @@ -328,6 +328,7 @@ pub(crate) fn string( pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, replacement: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/regex/perex_split.rs b/crates/perry-runtime/src/regex/perex_split.rs index 42d43d04c9..1cb37aa06b 100644 --- a/crates/perry-runtime/src/regex/perex_split.rs +++ b/crates/perry-runtime/src/regex/perex_split.rs @@ -608,6 +608,7 @@ fn string_via_engine(receiver: f64, separator: f64, limit_value: f64) -> Result< pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, limit: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/regex/site_test.rs b/crates/perry-runtime/src/regex/site_test.rs index af6e75aea8..c1e709385f 100644 --- a/crates/perry-runtime/src/regex/site_test.rs +++ b/crates/perry-runtime/src/regex/site_test.rs @@ -641,7 +641,10 @@ mod tests { ); } - extern "C" fn exact_factory(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn exact_factory( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let re = js_regexp_new_factory_site( string("x"), string("g"), @@ -651,7 +654,10 @@ mod tests { crate::value::js_nanbox_pointer(re as i64) } - extern "C" fn replacement_factory(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn replacement_factory( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let object = crate::object::js_object_alloc(0, 0); crate::object::js_object_set_field_by_name( object, @@ -662,11 +668,14 @@ mod tests { } #[inline(never)] - extern "C" fn nested_factory_wrapper(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn nested_factory_wrapper( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { // Keep this observably distinct from `exact_factory` under release // function merging while modeling a non-literal wrapper with effects. NESTED_WRAPPER_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - exact_factory(std::ptr::null()) + exact_factory(std::ptr::null(), crate::closure::JsThis::current()) } fn closure(function: *const u8) -> f64 { @@ -802,7 +811,11 @@ mod tests { assert_eq!(TEST_DECLINED_CALLEE.with(std::cell::Cell::get), 1); } - extern "C" fn patched_test(_closure: *const crate::closure::ClosureHeader, _arg: f64) -> f64 { + extern "C" fn patched_test( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _arg: f64, + ) -> f64 { f64::from_bits(crate::value::TAG_TRUE) } diff --git a/crates/perry-runtime/src/set_tombstone_tests.rs b/crates/perry-runtime/src/set_tombstone_tests.rs index 7f3adfbcd4..c75dfa17cc 100644 --- a/crates/perry-runtime/src/set_tombstone_tests.rs +++ b/crates/perry-runtime/src/set_tombstone_tests.rs @@ -9,6 +9,7 @@ crate::perry_thread_local! { extern "C" fn delete_current_set_value( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _value_again: f64, collection: f64, @@ -21,6 +22,7 @@ extern "C" fn delete_current_set_value( extern "C" fn delete_earlier_set_value( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _value_again: f64, collection: f64, @@ -397,6 +399,7 @@ fn clear_truncates_the_squeeze_history() { extern "C" fn delete_earlier_then_live_read( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, _value_again: f64, collection: f64, diff --git a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs index 94b504233f..768e3e9398 100644 --- a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs +++ b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs @@ -10,12 +10,16 @@ thread_local! { } /// Getter body: answers the `this` it was invoked with. -extern "C" fn this_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn this_getter(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { crate::object::js_implicit_this_get() } /// Setter body: records `(this, value)`. -extern "C" fn recording_setter(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn recording_setter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { let this = crate::object::js_implicit_this_get(); SETTER_CALL.with(|c| c.set(Some((this.to_bits(), value.to_bits())))); f64::from_bits(TAG_UNDEFINED) diff --git a/crates/perry-runtime/src/text.rs b/crates/perry-runtime/src/text.rs index d6ff922eaf..227d271af6 100644 --- a/crates/perry-runtime/src/text.rs +++ b/crates/perry-runtime/src/text.rs @@ -804,6 +804,7 @@ fn require_text_brand(value: f64, class_id: u32, message: &[u8]) { #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_decode_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -819,6 +820,7 @@ pub(crate) extern "C" fn text_decoder_decode_thunk( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_encoding_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); require_text_brand( @@ -833,6 +835,7 @@ pub(crate) extern "C" fn text_decoder_encoding_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_fatal_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); require_text_brand( @@ -846,6 +849,7 @@ pub(crate) extern "C" fn text_decoder_fatal_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_ignore_bom_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); require_text_brand( @@ -859,6 +863,7 @@ pub(crate) extern "C" fn text_decoder_ignore_bom_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encoding_getter( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let this = crate::object::js_implicit_this_get(); require_text_brand( @@ -873,6 +878,7 @@ pub(crate) extern "C" fn text_encoder_encoding_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encode_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, input: f64, ) -> f64 { let this = crate::object::js_implicit_this_get(); @@ -887,6 +893,7 @@ pub(crate) extern "C" fn text_encoder_encode_thunk( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encode_into_thunk( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, source: f64, dest: f64, ) -> f64 { diff --git a/crates/perry-runtime/src/thread.rs b/crates/perry-runtime/src/thread.rs index 490e0da36a..a84ede5248 100644 --- a/crates/perry-runtime/src/thread.rs +++ b/crates/perry-runtime/src/thread.rs @@ -1210,7 +1210,7 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn(local_closure, arg); + let result = call_fn(local_closure, crate::closure::JsThis::current(), arg); results.push(serialize_nanbox_for_thread(result.to_bits())); } @@ -1295,7 +1295,7 @@ unsafe fn single_thread_map( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, arg); + let result = call_fn(closure, crate::closure::JsThis::current(), arg); let result_arr = result_handle.get_raw_mut_ptr::(); // GC_STORE_AUDIT(BARRIERED): single-thread map result slot uses the shared array slot-store helper. store_thread_array_slot(result_arr, i, result.to_bits()); @@ -1463,7 +1463,7 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn(local_closure, arg); + let result = call_fn(local_closure, crate::closure::JsThis::current(), arg); let keep = is_truthy_bits(result.to_bits()); if keep { kept.push(serialize_nanbox_for_thread(arg.to_bits())); @@ -1543,7 +1543,7 @@ unsafe fn single_thread_filter( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, arg); + let result = call_fn(closure, crate::closure::JsThis::current(), arg); let keep = is_truthy_bits(result.to_bits()); if keep { let result_arr = result_handle.get_raw_mut_ptr::(); @@ -1684,7 +1684,7 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { unsafe { crate::closure::body_call::js_body_fn!(func_usize as *const u8;) }; let local_closure = closure_handle.get_raw_mut_ptr::() as *const ClosureHeader; - call_fn(local_closure) + call_fn(local_closure, crate::closure::JsThis::current()) })); match call_result { diff --git a/crates/perry-runtime/src/timer/handle_object.rs b/crates/perry-runtime/src/timer/handle_object.rs index 41ffb79740..d5cf6936b6 100644 --- a/crates/perry-runtime/src/timer/handle_object.rs +++ b/crates/perry-runtime/src/timer/handle_object.rs @@ -148,7 +148,10 @@ fn timer_receiver() -> (f64, Option) { (this, id) } -extern "C" fn timer_proto_ref_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_ref_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (this, id) = timer_receiver(); if let Some(id) = id { js_timer_ref(id); @@ -156,7 +159,10 @@ extern "C" fn timer_proto_ref_thunk(_c: *const crate::closure::ClosureHeader) -> this } -extern "C" fn timer_proto_unref_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_unref_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (this, id) = timer_receiver(); if let Some(id) = id { js_timer_unref(id); @@ -164,7 +170,10 @@ extern "C" fn timer_proto_unref_thunk(_c: *const crate::closure::ClosureHeader) this } -extern "C" fn timer_proto_has_ref_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_has_ref_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (_, id) = timer_receiver(); match id { Some(id) if js_timer_has_ref(id) != 0 => { @@ -176,7 +185,10 @@ extern "C" fn timer_proto_has_ref_thunk(_c: *const crate::closure::ClosureHeader } } -extern "C" fn timer_proto_refresh_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_refresh_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (this, id) = timer_receiver(); if let Some(id) = id { js_timer_refresh(id); @@ -293,7 +305,10 @@ fn clear_every_kind(id: i64) { clearImmediate(id); } -extern "C" fn timer_proto_close_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_close_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (this, id) = timer_receiver(); if let Some(id) = id { clear_every_kind(id); @@ -302,7 +317,10 @@ extern "C" fn timer_proto_close_thunk(_c: *const crate::closure::ClosureHeader) } /// `t[Symbol.dispose]()` — `using t = setTimeout(...)` clears the timer (#1213). -extern "C" fn timer_proto_dispose_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn timer_proto_dispose_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let (_, id) = timer_receiver(); if let Some(id) = id { clear_every_kind(id); @@ -315,6 +333,7 @@ extern "C" fn timer_proto_dispose_thunk(_c: *const crate::closure::ClosureHeader /// conversion, so `+setImmediate(...)` must stay `NaN` (#10542). extern "C" fn timer_proto_to_primitive_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _hint: f64, ) -> f64 { let (_, id) = timer_receiver(); @@ -328,7 +347,11 @@ extern "C" fn timer_proto_to_primitive_thunk( /// is not usable directly. This stands in for it so `t.constructor.name` is /// answered by an ordinary prototype property instead of the fabricated /// `{ name }` object the handle path had to synthesize per read. -extern "C" fn timer_ctor_thunk(_c: *const crate::closure::ClosureHeader, _a: f64) -> f64 { +extern "C" fn timer_ctor_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _a: f64, +) -> f64 { throw_timer_type_error(b"Timeout is not a constructor") } diff --git a/crates/perry-runtime/src/timer/tests_inline.rs b/crates/perry-runtime/src/timer/tests_inline.rs index 1c2326d553..e57852874d 100644 --- a/crates/perry-runtime/src/timer/tests_inline.rs +++ b/crates/perry-runtime/src/timer/tests_inline.rs @@ -145,7 +145,10 @@ mod mock_dispatch_own_pin_tests { /// the shared registry when the churn starts, so it is the very first one /// evicted once the churn passes the cap — and this callback observes its /// own eviction while it is still running. - extern "C" fn churn_then_check_self(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn churn_then_check_self( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let id = SELF_ID.load(Ordering::SeqCst); for _ in 0..(ref_states::TIMER_REF_STATES_CAP + 2_000) { // #340/#341: the producer returns the handle object; `clearTimeout` diff --git a/crates/perry-runtime/src/tty.rs b/crates/perry-runtime/src/tty.rs index be057a9968..01d96dfe8b 100644 --- a/crates/perry-runtime/src/tty.rs +++ b/crates/perry-runtime/src/tty.rs @@ -620,6 +620,7 @@ fn validate_color_count(count: f64) -> f64 { #[no_mangle] pub extern "C" fn js_tty_write_stream_get_color_depth( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, env: f64, ) -> f64 { let jsval = JSValue::from_bits(env.to_bits()); @@ -635,6 +636,7 @@ pub extern "C" fn js_tty_write_stream_get_color_depth( #[no_mangle] pub extern "C" fn js_tty_write_stream_has_colors( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, count: f64, env: f64, ) -> f64 { @@ -908,6 +910,7 @@ pub(crate) fn is_tty_stream_instance(value: f64, class_name: &str) -> bool { #[no_mangle] pub extern "C" fn js_tty_read_stream_set_raw_mode( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, mode: f64, ) -> f64 { let enabled = crate::value::js_is_truthy(mode) != 0; @@ -969,6 +972,7 @@ fn write_control_sequence(sequence: String) -> f64 { #[no_mangle] pub extern "C" fn js_tty_write_stream_cursor_to( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, x: f64, y: f64, _callback: f64, @@ -987,6 +991,7 @@ pub extern "C" fn js_tty_write_stream_cursor_to( #[no_mangle] pub extern "C" fn js_tty_write_stream_move_cursor( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, dx: f64, dy: f64, _callback: f64, @@ -1010,6 +1015,7 @@ pub extern "C" fn js_tty_write_stream_move_cursor( #[no_mangle] pub extern "C" fn js_tty_write_stream_clear_line( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, dir: f64, _callback: f64, ) -> f64 { @@ -1024,6 +1030,7 @@ pub extern "C" fn js_tty_write_stream_clear_line( #[no_mangle] pub extern "C" fn js_tty_write_stream_clear_screen_down( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, _callback: f64, ) -> f64 { write_control_sequence("\x1b[0J".to_string()) @@ -1046,7 +1053,10 @@ pub(crate) fn tty_write_stream_dimension(property: &str) -> Option { } #[no_mangle] -pub extern "C" fn js_tty_write_stream_get_window_size(_closure: *const ClosureHeader) -> f64 { +pub extern "C" fn js_tty_write_stream_get_window_size( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let fd = current_fd(1); let arr = crate::array::js_array_alloc(2); let (cols, rows) = winsize_value(fd).unwrap_or((0, 0)); @@ -1066,7 +1076,10 @@ pub extern "C" fn js_tty_write_stream_get_window_size(_closure: *const ClosureHe } #[no_mangle] -pub extern "C" fn js_tty_write_stream_refresh_size(_closure: *const ClosureHeader) -> f64 { +pub extern "C" fn js_tty_write_stream_refresh_size( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let _ = winsize_value(current_fd(1)); TAG_UNDEFINED_F64 } @@ -1083,6 +1096,7 @@ fn register_resize_callback(callback: i64) { #[no_mangle] pub extern "C" fn js_tty_write_stream_on( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1098,6 +1112,7 @@ pub extern "C" fn js_tty_write_stream_on( #[no_mangle] pub extern "C" fn js_tty_write_stream_remove_listener( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, _callback: f64, ) -> f64 { @@ -1110,6 +1125,7 @@ pub extern "C" fn js_tty_write_stream_remove_listener( #[no_mangle] pub extern "C" fn js_tty_write_stream_remove_all_listeners( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, event: f64, ) -> f64 { let event_js = JSValue::from_bits(event.to_bits()); diff --git a/crates/perry-runtime/src/tui/handle_object.rs b/crates/perry-runtime/src/tui/handle_object.rs index 4855f0ab49..55b7074039 100644 --- a/crates/perry-runtime/src/tui/handle_object.rs +++ b/crates/perry-runtime/src/tui/handle_object.rs @@ -237,42 +237,62 @@ fn receiver_id(kind: TuiKind) -> Option { const UNDEFINED: u64 = crate::value::TAG_UNDEFINED; -extern "C" fn state_proto_get_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn state_proto_get_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match receiver_id(TuiKind::State) { Some(id) => super::state::state_get_by_id(id), None => f64::from_bits(UNDEFINED), } } -extern "C" fn state_proto_set_thunk(_c: *const crate::closure::ClosureHeader, value: f64) -> f64 { +extern "C" fn state_proto_set_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { if let Some(id) = receiver_id(TuiKind::State) { super::state::state_set_by_id(id, value); } f64::from_bits(UNDEFINED) } -extern "C" fn ref_proto_get_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn ref_proto_get_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match receiver_id(TuiKind::RefBox) { Some(id) => super::hooks::ref_get_by_id(id), None => f64::from_bits(UNDEFINED), } } -extern "C" fn ref_proto_set_thunk(_c: *const crate::closure::ClosureHeader, value: f64) -> f64 { +extern "C" fn ref_proto_set_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { if let Some(id) = receiver_id(TuiKind::RefBox) { super::hooks::ref_set_by_id(id, value); } f64::from_bits(UNDEFINED) } -extern "C" fn app_proto_exit_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn app_proto_exit_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if receiver_id(TuiKind::App).is_some() { super::input::EXIT_FLAG.store(true, Ordering::Release); } f64::from_bits(UNDEFINED) } -extern "C" fn app_proto_wait_until_exit_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn app_proto_wait_until_exit_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if receiver_id(TuiKind::App).is_some() { super::hooks::wait_until_exit_blocking(); } @@ -281,6 +301,7 @@ extern "C" fn app_proto_wait_until_exit_thunk(_c: *const crate::closure::Closure extern "C" fn stdout_proto_write_thunk( _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { if receiver_id(TuiKind::Stdout).is_some() { @@ -290,35 +311,51 @@ extern "C" fn stdout_proto_write_thunk( f64::from_bits(UNDEFINED) } -extern "C" fn stdout_proto_columns_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn stdout_proto_columns_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match receiver_id(TuiKind::Stdout) { Some(_) => super::hooks::js_perry_tui_stdout_columns(0), None => f64::from_bits(UNDEFINED), } } -extern "C" fn stdout_proto_rows_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn stdout_proto_rows_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { match receiver_id(TuiKind::Stdout) { Some(_) => super::hooks::js_perry_tui_stdout_rows(0), None => f64::from_bits(UNDEFINED), } } -extern "C" fn focus_proto_next_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn focus_proto_next_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if receiver_id(TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus_next(); } f64::from_bits(UNDEFINED) } -extern "C" fn focus_proto_previous_thunk(_c: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn focus_proto_previous_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if receiver_id(TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus_previous(); } f64::from_bits(UNDEFINED) } -extern "C" fn focus_proto_focus_thunk(_c: *const crate::closure::ClosureHeader, id: f64) -> f64 { +extern "C" fn focus_proto_focus_thunk( + _c: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + id: f64, +) -> f64 { if receiver_id(TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus(id); } diff --git a/crates/perry-runtime/src/tui/hooks.rs b/crates/perry-runtime/src/tui/hooks.rs index 72599de0a4..b2625ec3de 100644 --- a/crates/perry-runtime/src/tui/hooks.rs +++ b/crates/perry-runtime/src/tui/hooks.rs @@ -390,6 +390,7 @@ pub extern "C" fn js_perry_tui_use_state_set(slot_idx: f64, value: f64) -> f64 { #[no_mangle] pub extern "C" fn perry_tui_state_setter_trampoline( closure: *const ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { if closure.is_null() { diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index 1d8e6d93c3..1e1daa95b3 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -23,7 +23,11 @@ extern "C" fn test_direct_method(_this: f64, value: f64) -> f64 { value } -extern "C" fn test_direct_closure(_closure: *const crate::closure::ClosureHeader, arg: f64) -> f64 { +extern "C" fn test_direct_closure( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + arg: f64, +) -> f64 { TEST_DIRECT_CLOSURE_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); arg } diff --git a/crates/perry-runtime/src/url/abort.rs b/crates/perry-runtime/src/url/abort.rs index e08bebf187..8500413d1d 100644 --- a/crates/perry-runtime/src/url/abort.rs +++ b/crates/perry-runtime/src/url/abort.rs @@ -68,6 +68,7 @@ fn alloc_abort_signal() -> *mut ObjectHeader { extern "C" fn abort_signal_add_event_listener_method( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, ) -> f64 { @@ -78,6 +79,7 @@ extern "C" fn abort_signal_add_event_listener_method( extern "C" fn abort_signal_remove_event_listener_method( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, ) -> f64 { @@ -100,6 +102,7 @@ fn abort_signal_listener_method_value(signal: *mut ObjectHeader, add: bool) -> f extern "C" fn abort_controller_abort_method( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, reason: f64, ) -> f64 { let controller_bits = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; @@ -273,7 +276,10 @@ fn notify_fetch_abort(signal_ptr: i64) { } } -extern "C" fn abort_error_constructor_thunk(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn abort_error_constructor_thunk( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { crate::error::js_throw_illegal_constructor_type_error() } @@ -420,6 +426,7 @@ pub extern "C" fn js_abort_signal_remove_listener( /// "abort" once, so Node's `{ once: true }` is behaviorally implied. extern "C" fn abort_signal_add_event_listener_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, _options: f64, @@ -434,6 +441,7 @@ extern "C" fn abort_signal_add_event_listener_thunk( /// Bound-method thunk: `signal.removeEventListener(type, listener[, options])`. extern "C" fn abort_signal_remove_event_listener_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, event_type: f64, listener: f64, _options: f64, @@ -448,6 +456,7 @@ extern "C" fn abort_signal_remove_event_listener_thunk( /// Bound-method thunk: `signal.throwIfAborted()`. extern "C" fn abort_signal_throw_if_aborted_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, ) -> f64 { let signal_bits = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; let signal = @@ -545,7 +554,10 @@ fn timeout_dom_exception_value() -> f64 { /// signal with a `TimeoutError` and firing its `abort` listeners (which is how /// a pending `fetch` bound by the signal learns to reject — see /// `js_fetch_with_options`). -extern "C" fn abort_signal_timeout_fire(closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn abort_signal_timeout_fire( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let signal_bits = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; let signal = crate::value::js_nanbox_get_pointer(f64::from_bits(signal_bits)) as *mut ObjectHeader; @@ -622,6 +634,7 @@ pub extern "C" fn js_abort_signal_throw_if_aborted(signal: *mut ObjectHeader) -> extern "C" fn abort_any_propagate_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, _arg: f64, ) -> f64 { // capture 0 = combined signal pointer (NaN-boxed), capture 1 = source signal. diff --git a/crates/perry-runtime/src/url/prototype.rs b/crates/perry-runtime/src/url/prototype.rs index 6ee8b26701..fdaf2a435f 100644 --- a/crates/perry-runtime/src/url/prototype.rs +++ b/crates/perry-runtime/src/url/prototype.rs @@ -24,7 +24,10 @@ fn require_url_receiver(name: &str) -> *mut ObjectHeader { macro_rules! url_getter { ($fn_name:ident, $name:literal, $slot:expr) => { - extern "C" fn $fn_name(_closure: *const crate::closure::ClosureHeader) -> f64 { + extern "C" fn $fn_name( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + ) -> f64 { let obj = require_url_receiver($name); crate::object::js_object_get_field_f64(obj, $slot) } @@ -33,7 +36,11 @@ macro_rules! url_getter { macro_rules! url_setter { ($fn_name:ident, $name:literal, $setter:path) => { - extern "C" fn $fn_name(_closure: *const crate::closure::ClosureHeader, value: f64) -> f64 { + extern "C" fn $fn_name( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + value: f64, + ) -> f64 { let obj = require_url_receiver($name); $setter(obj, value); f64::from_bits(crate::value::TAG_UNDEFINED) diff --git a/crates/perry-runtime/src/url/search_params.rs b/crates/perry-runtime/src/url/search_params.rs index 12fb116992..909be365e9 100644 --- a/crates/perry-runtime/src/url/search_params.rs +++ b/crates/perry-runtime/src/url/search_params.rs @@ -1038,6 +1038,7 @@ fn usp_thunk_receiver(closure: *const crate::closure::ClosureHeader) -> *mut Obj extern "C" fn usp_append_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, value: f64, ) -> f64 { @@ -1047,6 +1048,7 @@ extern "C" fn usp_append_thunk( extern "C" fn usp_set_thunk( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, name: f64, value: f64, ) -> f64 { @@ -1054,7 +1056,11 @@ extern "C" fn usp_set_thunk( f64::from_bits(crate::value::TAG_UNDEFINED) } -extern "C" fn usp_get_thunk(closure: *const crate::closure::ClosureHeader, name: f64) -> f64 { +extern "C" fn usp_get_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + name: f64, +) -> f64 { // js_url_search_params_get returns a raw `*mut StringHeader` for the // static-lowering path; rebuild the boxed value here instead. let wanted = coerce_search_param_arg(name); @@ -1065,7 +1071,11 @@ extern "C" fn usp_get_thunk(closure: *const crate::closure::ClosureHeader, name: } } -extern "C" fn usp_has_thunk(closure: *const crate::closure::ClosureHeader, name: f64) -> f64 { +extern "C" fn usp_has_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + name: f64, +) -> f64 { if js_url_search_params_has(usp_thunk_receiver(closure), name) != 0.0 { f64::from_bits(crate::value::TAG_TRUE) } else { @@ -1073,7 +1083,11 @@ extern "C" fn usp_has_thunk(closure: *const crate::closure::ClosureHeader, name: } } -extern "C" fn usp_delete_thunk(closure: *const crate::closure::ClosureHeader, name: f64) -> f64 { +extern "C" fn usp_delete_thunk( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + name: f64, +) -> f64 { js_url_search_params_delete(usp_thunk_receiver(closure), name); 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 7dd1d558d6..e24a8aa4d6 100644 --- a/crates/perry-runtime/src/util_abort.rs +++ b/crates/perry-runtime/src/util_abort.rs @@ -77,7 +77,10 @@ fn promise_from_capture(closure: *const ClosureHeader, index: u32) -> *mut crate js_nanbox_get_pointer(f64::from_bits(bits)) as *mut crate::promise::Promise } -extern "C" fn aborted_resolve_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn aborted_resolve_listener( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let promise = promise_from_capture(closure, 0); let signal = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); if !promise.is_null() { diff --git a/crates/perry-runtime/src/util_debuglog.rs b/crates/perry-runtime/src/util_debuglog.rs index e47cc83093..f3ad5151a7 100644 --- a/crates/perry-runtime/src/util_debuglog.rs +++ b/crates/perry-runtime/src/util_debuglog.rs @@ -194,7 +194,11 @@ fn write_stderr_line(line: &str) { eprint!("{line}"); } -extern "C" fn logger_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 { +extern "C" fn logger_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest_value: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/util_promisify.rs b/crates/perry-runtime/src/util_promisify.rs index 37e80998d9..31aa4112b3 100644 --- a/crates/perry-runtime/src/util_promisify.rs +++ b/crates/perry-runtime/src/util_promisify.rs @@ -319,7 +319,11 @@ pub extern "C" fn js_util_callbackify(fn_value: f64) -> f64 { /// Receives the rest array of forwarded args (NaN-boxed pointer to an /// `ArrayHeader`). Builds an args list `[…rest, inner_cb]`, runs the /// original under a setjmp trap so a sync `throw` rejects the promise. -extern "C" fn outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 { +extern "C" fn outer_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest_value: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fn_value = if closure.is_null() { @@ -426,7 +430,12 @@ extern "C" fn outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 /// /// Standard Node convention: a null/undefined `err` means the call /// succeeded with `value`. Anything else is a rejection. -extern "C" fn inner_callback_thunk(closure: *const ClosureHeader, err: f64, value: f64) -> f64 { +extern "C" fn inner_callback_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + err: f64, + value: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -446,7 +455,11 @@ extern "C" fn inner_callback_thunk(closure: *const ClosureHeader, err: f64, valu /// [`outer_thunk`] but installs a 3-arg inner callback so the resolved /// value is Node's `{ publicKey, privateKey }` object (via /// `customPromisifyArgs`) rather than just the public key. -extern "C" fn gkp_outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 { +extern "C" fn gkp_outer_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest_value: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fn_value = if closure.is_null() { @@ -548,6 +561,7 @@ extern "C" fn gkp_outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> /// `customPromisifyArgs`); otherwise rejects with `err`. extern "C" fn gkp_inner_callback_thunk( closure: *const ClosureHeader, + _this: crate::closure::JsThis, err: f64, public_key: f64, private_key: f64, @@ -602,7 +616,11 @@ extern "C" fn gkp_inner_callback_thunk( /// `util.deprecate()` wrapper body. Receives all user arguments bundled in /// `rest_value`, emits one warning per wrapper, then forwards the call. -extern "C" fn deprecate_outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 { +extern "C" fn deprecate_outer_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest_value: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -654,7 +672,11 @@ extern "C" fn deprecate_outer_thunk(closure: *const ClosureHeader, rest_value: f /// /// The last incoming argument is the Node-style callback. Every preceding /// argument is forwarded to the original promise-returning function. -extern "C" fn callbackify_outer_thunk(closure: *const ClosureHeader, rest_value: f64) -> f64 { +extern "C" fn callbackify_outer_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest_value: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let fn_value = if closure.is_null() { @@ -841,7 +863,11 @@ fn throw_callbackify_not_thenable(value: f64) -> ! { throw_plain_type_error("The \"original\" function did not return a Promise"); } -extern "C" fn callbackify_fulfilled_thunk(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn callbackify_fulfilled_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + value: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } @@ -853,7 +879,11 @@ extern "C" fn callbackify_fulfilled_thunk(closure: *const ClosureHeader, value: TAG_UNDEFINED_F64 } -extern "C" fn callbackify_rejected_thunk(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn callbackify_rejected_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/v8.rs b/crates/perry-runtime/src/v8.rs index 72a002f66a..a34a9c34c5 100644 --- a/crates/perry-runtime/src/v8.rs +++ b/crates/perry-runtime/src/v8.rs @@ -168,7 +168,10 @@ fn register_stop_trampoline_once() { }); } -extern "C" fn promise_hook_stop_trampoline(closure: *const ClosureHeader) -> f64 { +extern "C" fn promise_hook_stop_trampoline( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { if closure.is_null() { return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/wasi.rs b/crates/perry-runtime/src/wasi.rs index d73d85fef5..b9b5767579 100644 --- a/crates/perry-runtime/src/wasi.rs +++ b/crates/perry-runtime/src/wasi.rs @@ -740,7 +740,10 @@ pub(crate) unsafe fn js_wasi_init_subclass(this_box: f64, options: f64) { } #[no_mangle] -pub extern "C" fn js_wasi_get_import_object(_closure: *const ClosureHeader) -> f64 { +pub extern "C" fn js_wasi_get_import_object( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); if heap_object_ptr(this.get_nanbox_f64()).is_none() || !is_wasi_instance(this.get_nanbox_f64()) @@ -777,7 +780,11 @@ pub extern "C" fn js_wasi_get_import_object(_closure: *const ClosureHeader) -> f } #[no_mangle] -pub extern "C" fn js_wasi_start(_closure: *const ClosureHeader, instance: f64) -> f64 { +pub extern "C" fn js_wasi_start( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + instance: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_nanbox_f64(instance); let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw()); @@ -817,7 +824,11 @@ pub extern "C" fn js_wasi_start(_closure: *const ClosureHeader, instance: f64) - } #[no_mangle] -pub extern "C" fn js_wasi_initialize(_closure: *const ClosureHeader, instance: f64) -> f64 { +pub extern "C" fn js_wasi_initialize( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + instance: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_nanbox_f64(instance); let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw()); @@ -852,6 +863,7 @@ pub extern "C" fn js_wasi_initialize(_closure: *const ClosureHeader, instance: f #[no_mangle] pub extern "C" fn js_wasi_finalize_bindings( _closure: *const ClosureHeader, + _this: crate::closure::JsThis, instance: f64, rest: f64, ) -> f64 { @@ -1140,6 +1152,7 @@ fn import_function_name(closure: *const ClosureHeader) -> &'static str { #[no_mangle] pub extern "C" fn js_wasi_import_stub( closure: *const ClosureHeader, + _this: crate::closure::JsThis, arg0: f64, arg1: f64, arg2: f64, diff --git a/crates/perry-runtime/src/weakref/test_support.rs b/crates/perry-runtime/src/weakref/test_support.rs index b61e18a886..a4f0022044 100644 --- a/crates/perry-runtime/src/weakref/test_support.rs +++ b/crates/perry-runtime/src/weakref/test_support.rs @@ -82,7 +82,11 @@ fn empty_checkpoint_delivers_recorded_finalization_job() { use super::*; use std::sync::atomic::{AtomicU64, Ordering}; static DELIVERED: AtomicU64 = AtomicU64::new(0); - extern "C" fn cleanup(_: *const crate::closure::ClosureHeader, held: f64) -> f64 { + extern "C" fn cleanup( + _: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + held: f64, + ) -> f64 { DELIVERED.store(held.to_bits(), Ordering::Relaxed); 0.0 } diff --git a/crates/perry-runtime/src/web_storage.rs b/crates/perry-runtime/src/web_storage.rs index cd8bd861b9..4828dd70f6 100644 --- a/crates/perry-runtime/src/web_storage.rs +++ b/crates/perry-runtime/src/web_storage.rs @@ -26,58 +26,91 @@ thread_local! { static SESSION_STORE: RefCell> = const { RefCell::new(BTreeMap::new()) }; } -pub extern "C" fn storage_constructor_illegal(_closure: *const ClosureHeader) -> f64 { +pub extern "C" fn storage_constructor_illegal( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { throw_error("Illegal constructor") } -extern "C" fn storage_global_setter(_closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn storage_global_setter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _value: f64, +) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn storage_local_global_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn storage_local_global_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { storage_global_value(StorageKind::Local) } -extern "C" fn storage_session_global_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn storage_session_global_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { storage_global_value(StorageKind::Session) } -extern "C" fn storage_length_getter(_closure: *const ClosureHeader) -> f64 { +extern "C" fn storage_length_getter( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; with_store(kind, |store| store.len() as f64) } -extern "C" fn storage_get_item(_closure: *const ClosureHeader, key: f64) -> f64 { +extern "C" fn storage_get_item( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + key: f64, +) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; storage_get_item_impl(kind, key) } -extern "C" fn storage_set_item(_closure: *const ClosureHeader, key: f64, value: f64) -> f64 { +extern "C" fn storage_set_item( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + key: f64, + value: f64, +) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; storage_set_item_impl(kind, key, value) } -extern "C" fn storage_remove_item(_closure: *const ClosureHeader, key: f64) -> f64 { +extern "C" fn storage_remove_item( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + key: f64, +) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; storage_remove_item_impl(kind, key) } -extern "C" fn storage_key(_closure: *const ClosureHeader, index: f64) -> f64 { +extern "C" fn storage_key( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + index: f64, +) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; storage_key_impl(kind, index) } -extern "C" fn storage_clear(_closure: *const ClosureHeader) -> f64 { +extern "C" fn storage_clear(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { let Some(kind) = receiver_kind() else { return throw_type_error("Illegal invocation"); }; diff --git a/crates/perry-runtime/src/webassembly.rs b/crates/perry-runtime/src/webassembly.rs index 44a4a8dbbd..63452a83dd 100644 --- a/crates/perry-runtime/src/webassembly.rs +++ b/crates/perry-runtime/src/webassembly.rs @@ -1088,7 +1088,10 @@ fn global_handle_from_receiver() -> Option<*mut c_void> { .map(|handle| handle as *mut c_void) } -extern "C" fn js_wasm_global_get(_closure: *const crate::closure::ClosureHeader) -> f64 { +extern "C" fn js_wasm_global_get( + _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { let Some(handle) = global_handle_from_receiver() else { return nanbox_undefined(); }; @@ -1102,6 +1105,7 @@ extern "C" fn js_wasm_global_get(_closure: *const crate::closure::ClosureHeader) extern "C" fn js_wasm_global_set( _closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, value: f64, ) -> f64 { let Some(handle) = global_handle_from_receiver() else { @@ -1567,7 +1571,7 @@ pub extern "C" fn js_webassembly_instantiate(bytes_jsval: f64, imports_jsval: f6 /// `WebAssembly.callExport(handle, name, ...args)` — invoke an exported /// function by name with numeric arguments. Currently supports up to 4 -/// numeric args, mirroring the closure-call ABI in `closure.rs`. All +/// numeric args, mirroring the `js_closure_call{N}` entries. All /// arguments and the return value are passed as f64; the runtime infers /// the wasm signature from the export type and widens/narrows as needed. /// diff --git a/crates/perry-runtime/src/webassembly_calls.rs b/crates/perry-runtime/src/webassembly_calls.rs index 07d1a5e376..c18fc70e89 100644 --- a/crates/perry-runtime/src/webassembly_calls.rs +++ b/crates/perry-runtime/src/webassembly_calls.rs @@ -4,6 +4,7 @@ use super::*; extern "C" fn js_wasm_instance_result_then( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, on_fulfilled: f64, _on_rejected: f64, ) -> f64 { @@ -45,6 +46,7 @@ macro_rules! wasm_export_call_shim { ($name:ident $(, $arg:ident)*) => { pub(super) extern "C" fn $name( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, $($arg: f64),* ) -> f64 { call_captured_wasm_export(closure, &[$($arg),*]) diff --git a/crates/perry-runtime/src/webassembly_table.rs b/crates/perry-runtime/src/webassembly_table.rs index 4cbfab0f6a..f7157f5adc 100644 --- a/crates/perry-runtime/src/webassembly_table.rs +++ b/crates/perry-runtime/src/webassembly_table.rs @@ -57,7 +57,11 @@ pub(super) fn wasm_function_external(value: f64) -> *mut c_void { crate::closure::js_closure_get_capture_f64(closure, 6) as usize as *mut c_void } -extern "C" fn js_wasm_table_get(closure: *const crate::closure::ClosureHeader, index: f64) -> f64 { +extern "C" fn js_wasm_table_get( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + index: f64, +) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let (external, inst, name, table) = table_method_context(&scope, closure); let values = scope.root_nanbox_f64(table_values(table.get_nanbox_f64())); @@ -120,6 +124,7 @@ extern "C" fn js_wasm_table_get(closure: *const crate::closure::ClosureHeader, i extern "C" fn js_wasm_table_set( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, index: f64, value: f64, ) -> f64 { @@ -163,6 +168,7 @@ extern "C" fn js_wasm_table_set( extern "C" fn js_wasm_table_grow( closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, delta: f64, value: f64, ) -> f64 { diff --git a/crates/perry-stdlib/src/bun_sql.rs b/crates/perry-stdlib/src/bun_sql.rs index 58ff4c0c56..44469ddc6e 100644 --- a/crates/perry-stdlib/src/bun_sql.rs +++ b/crates/perry-stdlib/src/bun_sql.rs @@ -122,6 +122,7 @@ unsafe fn execute_sqlite_query(db_handle: Handle, strings_value: f64, params_val extern "C" fn bun_sql_query_tag( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, strings_value: f64, params_value: f64, ) -> f64 { @@ -146,7 +147,11 @@ unsafe fn finish_transaction(db_handle: Handle, nested: bool, success: bool) { }); } -extern "C" fn bun_sql_transaction_fulfilled(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn bun_sql_transaction_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, +) -> f64 { unsafe { let handle = captured_handle(closure); let nested = js_closure_get_capture_f64(closure, 1) != 0.0; @@ -155,7 +160,11 @@ extern "C" fn bun_sql_transaction_fulfilled(closure: *const ClosureHeader, value } } -extern "C" fn bun_sql_transaction_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn bun_sql_transaction_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { unsafe { let scope = RuntimeHandleScope::new(); let reason = scope.root_nanbox_f64(reason); @@ -171,7 +180,11 @@ extern "C" fn bun_sql_transaction_rejected(closure: *const ClosureHeader, reason } } -extern "C" fn bun_sql_begin(closure: *const ClosureHeader, callback_value: f64) -> f64 { +extern "C" fn bun_sql_begin( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + callback_value: f64, +) -> f64 { unsafe { if closure_ptr_from_value(callback_value).is_none() { let error = js_call_catching(|| throw_plain_type("SQL.begin expects a callback")) @@ -259,11 +272,18 @@ extern "C" fn bun_sql_begin(closure: *const ClosureHeader, callback_value: f64) } } -extern "C" fn bun_sql_reserve(closure: *const ClosureHeader, _options: f64) -> f64 { +extern "C" fn bun_sql_reserve( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _options: f64, +) -> f64 { unsafe { resolved_promise(captured_client(closure)) } } -extern "C" fn bun_sql_release(_closure: *const ClosureHeader) -> f64 { +extern "C" fn bun_sql_release( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { undefined_f64() } @@ -279,7 +299,11 @@ unsafe fn call_onclose(callback_value: f64, client: f64, error: f64) -> Result<( .map(|_| ()) } -extern "C" fn bun_sql_close(closure: *const ClosureHeader, _options: f64) -> f64 { +extern "C" fn bun_sql_close( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _options: f64, +) -> f64 { unsafe { let scope = RuntimeHandleScope::new(); let db_handle = captured_handle(closure); diff --git a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs index 94d7681106..3dc3e1f62f 100644 --- a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs +++ b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs @@ -48,6 +48,7 @@ fn async_local_storage_method_name_static(property: &str) -> Option<&'static [u8 extern "C" fn async_local_storage_unbound_method_thunk( closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, rest: f64, ) -> f64 { unsafe { diff --git a/crates/perry-stdlib/src/crypto/random.rs b/crates/perry-stdlib/src/crypto/random.rs index 4dfbc60836..66c200cc71 100644 --- a/crates/perry-stdlib/src/crypto/random.rs +++ b/crates/perry-stdlib/src/crypto/random.rs @@ -964,6 +964,7 @@ mod tests { extern "C" fn record_cb_thunk( _closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, err: f64, value: f64, ) -> f64 { diff --git a/crates/perry-stdlib/src/domain.rs b/crates/perry-stdlib/src/domain.rs index ca20eb4764..6b51485d89 100644 --- a/crates/perry-stdlib/src/domain.rs +++ b/crates/perry-stdlib/src/domain.rs @@ -459,7 +459,11 @@ pub extern "C" fn js_domain_exit(handle: Handle) -> f64 { undefined() } -extern "C" fn domain_bound_wrapper(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn domain_bound_wrapper( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + rest: f64, +) -> f64 { unsafe { let handle = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as Handle; let callback = perry_runtime::closure::js_closure_get_capture_f64(closure, 1); @@ -469,7 +473,11 @@ extern "C" fn domain_bound_wrapper(closure: *const ClosureHeader, rest: f64) -> } } -extern "C" fn domain_intercept_wrapper(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn domain_intercept_wrapper( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + rest: f64, +) -> f64 { unsafe { let handle = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as Handle; let callback = perry_runtime::closure::js_closure_get_capture_f64(closure, 1); diff --git a/crates/perry-stdlib/src/events.rs b/crates/perry-stdlib/src/events.rs index 770e0a5f1f..24cf4a5069 100644 --- a/crates/perry-stdlib/src/events.rs +++ b/crates/perry-stdlib/src/events.rs @@ -691,7 +691,11 @@ unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i6 wrapper as i64 } -extern "C" fn event_emitter_once_wrapper(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn event_emitter_once_wrapper( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + rest: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; unsafe { @@ -1051,7 +1055,11 @@ unsafe fn call_emitter_listener( const TAG_UNDEFINED_F64_BITS: u64 = 0x7FFC_0000_0000_0001; -extern "C" fn events_capture_rejection_handler(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn events_capture_rejection_handler( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let handle = js_closure_get_capture_ptr(closure, 0) as Handle; diff --git a/crates/perry-stdlib/src/events/events_on.rs b/crates/perry-stdlib/src/events/events_on.rs index a595265bfd..d842790751 100644 --- a/crates/perry-stdlib/src/events/events_on.rs +++ b/crates/perry-stdlib/src/events/events_on.rs @@ -83,7 +83,11 @@ 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. -extern "C" fn events_on_queue_listener(closure: *const ClosureHeader, arg0: f64) -> f64 { +extern "C" fn events_on_queue_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + arg0: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; @@ -115,7 +119,10 @@ extern "C" fn events_on_queue_listener(closure: *const ClosureHeader, arg0: f64) /// `next()` — drain a buffered event, reject on abort, finish when done, or /// return a pending Promise the listener will resolve on the next event. -extern "C" fn events_on_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_on_next( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; @@ -151,7 +158,10 @@ extern "C" fn events_on_next(closure: *const ClosureHeader) -> f64 { /// `return()` — end iteration: mark done, detach the listener, settle any /// blocked `next()` with `{ done: true }`. -extern "C" fn events_on_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_on_return( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let state = js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; @@ -191,13 +201,19 @@ extern "C" fn events_on_return(closure: *const ClosureHeader) -> f64 { events_resolved_iter_promise(f64::from_bits(TAG_UNDEFINED_F64_BITS), true) } -extern "C" fn events_on_aiter_self(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_on_aiter_self( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { perry_runtime::closure::js_closure_get_capture_f64(closure, 0) } /// `queue[Symbol.asyncIterator]()` — build a fresh `{ next, return }` iterator /// object bound to the shared state. -extern "C" fn events_on_async_iterator(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_on_async_iterator( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::{js_closure_alloc, js_closure_set_capture_ptr}; let state = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as *mut ArrayHeader; @@ -250,7 +266,10 @@ unsafe fn install_events_on_async_iterator(queue: *mut ArrayHeader, state: *mut ); } -extern "C" fn events_on_abort_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_on_abort_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let handle = js_closure_get_capture_ptr(closure, 0) as Handle; diff --git a/crates/perry-stdlib/src/events/module_helpers.rs b/crates/perry-stdlib/src/events/module_helpers.rs index d6fde1d471..1888a17f11 100644 --- a/crates/perry-stdlib/src/events/module_helpers.rs +++ b/crates/perry-stdlib/src/events/module_helpers.rs @@ -33,7 +33,10 @@ pub(super) unsafe fn call_net_socket_method(handle: i64, name: &str, args: &[f64 ) } -extern "C" fn events_abort_listener_dispose(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_abort_listener_dispose( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let signal_ptr = js_closure_get_capture_ptr(closure, 0); diff --git a/crates/perry-stdlib/src/events/once_helpers.rs b/crates/perry-stdlib/src/events/once_helpers.rs index 7866b722fa..7bccf9dd6b 100644 --- a/crates/perry-stdlib/src/events/once_helpers.rs +++ b/crates/perry-stdlib/src/events/once_helpers.rs @@ -38,7 +38,10 @@ unsafe fn remove_stream_or_socket_once_listener( } } -extern "C" fn events_once_abort_listener(closure: *const ClosureHeader) -> f64 { +extern "C" fn events_once_abort_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let handle = js_closure_get_capture_ptr(closure, 0) as Handle; @@ -58,7 +61,11 @@ extern "C" fn events_once_abort_listener(closure: *const ClosureHeader) -> f64 { undefined_value() } -extern "C" fn events_once_stream_resolve_listener(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn events_once_stream_resolve_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + rest: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let promise = js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -77,7 +84,11 @@ extern "C" fn events_once_stream_resolve_listener(closure: *const ClosureHeader, undefined_value() } -extern "C" fn events_once_stream_reject_listener(closure: *const ClosureHeader, rest: f64) -> f64 { +extern "C" fn events_once_stream_reject_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + rest: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let promise = js_closure_get_capture_ptr(closure, 0) as *mut Promise; @@ -115,7 +126,11 @@ fn first_rest_arg_or_undefined(rest: f64) -> f64 { } } -extern "C" fn events_once_event_target_listener(closure: *const ClosureHeader, arg0: f64) -> f64 { +extern "C" fn events_once_event_target_listener( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + arg0: f64, +) -> f64 { use perry_runtime::closure::js_closure_get_capture_ptr; let promise = js_closure_get_capture_ptr(closure, 0) as *mut Promise; diff --git a/crates/perry-stdlib/src/readline/mod.rs b/crates/perry-stdlib/src/readline/mod.rs index 0456134295..c1dc7bb8fc 100644 --- a/crates/perry-stdlib/src/readline/mod.rs +++ b/crates/perry-stdlib/src/readline/mod.rs @@ -1033,7 +1033,10 @@ enum NextAction { Pending, } -extern "C" fn readline_aiter_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_aiter_next( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let handle = js_closure_get_capture_f64(closure, 0) as i64; // Decide the outcome without holding the interface borrow across the Promise // allocation below (GC must be free to scan READLINE_INTERFACES). @@ -1060,7 +1063,10 @@ extern "C" fn readline_aiter_next(closure: *const ClosureHeader) -> f64 { } } -extern "C" fn readline_aiter_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_aiter_return( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let handle = js_closure_get_capture_f64(closure, 0) as i64; let pending = with_interface_mut(handle, |state| { state.ended = true; @@ -1076,7 +1082,10 @@ extern "C" fn readline_aiter_return(closure: *const ClosureHeader) -> f64 { resolved_iter_promise(undefined(), true) } -extern "C" fn readline_aiter_self(closure: *const ClosureHeader) -> f64 { +extern "C" fn readline_aiter_self( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { js_closure_get_capture_f64(closure, 0) } @@ -1145,13 +1154,20 @@ pub extern "C" fn js_readline_iterator(handle: i64) -> i64 { obj_handle.get_raw_mut_ptr::() as i64 } -extern "C" fn custom_input_data(closure: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn custom_input_data( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + chunk: f64, +) -> f64 { let handle = js_closure_get_capture_f64(closure, 0) as i64; append_custom_input(handle, chunk); undefined() } -extern "C" fn custom_input_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn custom_input_close( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let handle = js_closure_get_capture_f64(closure, 0) as i64; close_custom_interface(handle); undefined() @@ -1315,153 +1331,12 @@ fn ensure_reader_started() { // --------------------------------------------------------------------------- #[cfg(unix)] -mod termios_impl { - use std::sync::Mutex; - - /// Saved cooked-mode termios so we can restore on disable. Lazy-init - /// on the first enable call; survives toggle cycles. - static SAVED: Mutex> = Mutex::new(None); - - /// Enable raw mode on fd 0 (stdin). Returns true on success. - pub fn enable() -> bool { - unsafe { - let mut current: libc::termios = std::mem::zeroed(); - if libc::tcgetattr(0, &mut current) != 0 { - return false; - } - // Save the original on first enable so disable can restore. - { - let mut saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); - if saved.is_none() { - *saved = Some(current); - } - } - let mut raw = current; - // cfmakeraw equivalent (Node's setRawMode does roughly this). - raw.c_iflag &= !(libc::IGNBRK - | libc::BRKINT - | libc::PARMRK - | libc::ISTRIP - | libc::INLCR - | libc::IGNCR - | libc::ICRNL - | libc::IXON); - raw.c_oflag &= !libc::OPOST; - raw.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN); - raw.c_cflag &= !(libc::CSIZE | libc::PARENB); - raw.c_cflag |= libc::CS8; - raw.c_cc[libc::VMIN] = 1; - raw.c_cc[libc::VTIME] = 0; - libc::tcsetattr(0, libc::TCSANOW, &raw) == 0 - } - } - - /// Disable raw mode (restore the saved cooked-mode termios). - pub fn disable() -> bool { - unsafe { - let saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); - if let Some(t) = saved.as_ref() { - libc::tcsetattr(0, libc::TCSANOW, t) == 0 - } else { - // Never enabled — nothing to restore. - true - } - } - } -} +#[path = "termios_unix.rs"] +mod termios_impl; #[cfg(all(windows, not(unix)))] -mod termios_impl { - use std::sync::Mutex; - use windows_sys::Win32::System::Console::{ - GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, - ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING, - STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, - }; - - /// Saved console modes for the input + output handles. Set on first - /// `enable()`; restored by `disable()`. Two-tuple so we can leave - /// the output handle's mode untouched if we couldn't read it (e.g. - /// stdout redirected to a file — `GetConsoleMode` fails on - /// non-console handles). - static SAVED: Mutex)>> = Mutex::new(None); - - /// Flip stdin into byte-mode + virtual-terminal-input mode (so - /// arrow keys arrive as ANSI `\x1b[A..D` matching the Unix path's - /// parser) and stdout into virtual-terminal-processing mode (so the - /// renderer's CSI escapes actually move the cursor instead of - /// printing literally). Saves the original modes on first call so - /// `disable()` restores cleanly. (#406.) - pub fn enable() -> bool { - unsafe { - // windows-sys 0.61 (#720) made HANDLE a `*mut c_void` (was `isize` - // in 0.52). Use `.is_null()` + `INVALID_HANDLE_VALUE` constant - // instead of raw integer comparison. (#406 fix updated for - // windows-sys 0.61.) - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; - let h_in = GetStdHandle(STD_INPUT_HANDLE); - if h_in.is_null() || h_in == INVALID_HANDLE_VALUE { - return false; - } - let mut current_in: u32 = 0; - if GetConsoleMode(h_in, &mut current_in) == 0 { - return false; - } - let h_out = GetStdHandle(STD_OUTPUT_HANDLE); - let current_out = if !h_out.is_null() && h_out != INVALID_HANDLE_VALUE { - let mut m: u32 = 0; - if GetConsoleMode(h_out, &mut m) != 0 { - Some(m) - } else { - None - } - } else { - None - }; - - { - let mut saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); - if saved.is_none() { - *saved = Some((current_in, current_out)); - } - } - - let raw_in = (current_in - & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT)) - | ENABLE_VIRTUAL_TERMINAL_INPUT; - if SetConsoleMode(h_in, raw_in) == 0 { - return false; - } - if let Some(out_mode) = current_out { - let raw_out = out_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; - let _ = SetConsoleMode(h_out, raw_out); - } - true - } - } - - pub fn disable() -> bool { - unsafe { - let saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); - if let Some((in_mode, out_mode)) = saved.as_ref() { - use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; - let h_in = GetStdHandle(STD_INPUT_HANDLE); - if !h_in.is_null() && h_in != INVALID_HANDLE_VALUE { - let _ = SetConsoleMode(h_in, *in_mode); - } - if let Some(m) = out_mode { - let h_out = GetStdHandle(STD_OUTPUT_HANDLE); - if !h_out.is_null() && h_out != INVALID_HANDLE_VALUE { - let _ = SetConsoleMode(h_out, *m); - } - } - true - } else { - true - } - } - } -} +#[path = "termios_windows.rs"] +mod termios_impl; #[cfg(not(any(unix, windows)))] mod termios_impl { diff --git a/crates/perry-stdlib/src/readline/termios_unix.rs b/crates/perry-stdlib/src/readline/termios_unix.rs new file mode 100644 index 0000000000..3b24b566c9 --- /dev/null +++ b/crates/perry-stdlib/src/readline/termios_unix.rs @@ -0,0 +1,52 @@ +use std::sync::Mutex; + +/// Saved cooked-mode termios so we can restore on disable. Lazy-init +/// on the first enable call; survives toggle cycles. +static SAVED: Mutex> = Mutex::new(None); + +/// Enable raw mode on fd 0 (stdin). Returns true on success. +pub fn enable() -> bool { + unsafe { + let mut current: libc::termios = std::mem::zeroed(); + if libc::tcgetattr(0, &mut current) != 0 { + return false; + } + // Save the original on first enable so disable can restore. + { + let mut saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); + if saved.is_none() { + *saved = Some(current); + } + } + let mut raw = current; + // cfmakeraw equivalent (Node's setRawMode does roughly this). + raw.c_iflag &= !(libc::IGNBRK + | libc::BRKINT + | libc::PARMRK + | libc::ISTRIP + | libc::INLCR + | libc::IGNCR + | libc::ICRNL + | libc::IXON); + raw.c_oflag &= !libc::OPOST; + raw.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN); + raw.c_cflag &= !(libc::CSIZE | libc::PARENB); + raw.c_cflag |= libc::CS8; + raw.c_cc[libc::VMIN] = 1; + raw.c_cc[libc::VTIME] = 0; + libc::tcsetattr(0, libc::TCSANOW, &raw) == 0 + } +} + +/// Disable raw mode (restore the saved cooked-mode termios). +pub fn disable() -> bool { + unsafe { + let saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); + if let Some(t) = saved.as_ref() { + libc::tcsetattr(0, libc::TCSANOW, t) == 0 + } else { + // Never enabled — nothing to restore. + true + } + } +} diff --git a/crates/perry-stdlib/src/readline/termios_windows.rs b/crates/perry-stdlib/src/readline/termios_windows.rs new file mode 100644 index 0000000000..2867cb49aa --- /dev/null +++ b/crates/perry-stdlib/src/readline/termios_windows.rs @@ -0,0 +1,89 @@ +use std::sync::Mutex; +use windows_sys::Win32::System::Console::{ + GetConsoleMode, GetStdHandle, SetConsoleMode, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, + ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING, + STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, +}; + +/// Saved console modes for the input + output handles. Set on first +/// `enable()`; restored by `disable()`. Two-tuple so we can leave +/// the output handle's mode untouched if we couldn't read it (e.g. +/// stdout redirected to a file — `GetConsoleMode` fails on +/// non-console handles). +static SAVED: Mutex)>> = Mutex::new(None); + +/// Flip stdin into byte-mode + virtual-terminal-input mode (so +/// arrow keys arrive as ANSI `\x1b[A..D` matching the Unix path's +/// parser) and stdout into virtual-terminal-processing mode (so the +/// renderer's CSI escapes actually move the cursor instead of +/// printing literally). Saves the original modes on first call so +/// `disable()` restores cleanly. (#406.) +pub fn enable() -> bool { + unsafe { + // windows-sys 0.61 (#720) made HANDLE a `*mut c_void` (was `isize` + // in 0.52). Use `.is_null()` + `INVALID_HANDLE_VALUE` constant + // instead of raw integer comparison. (#406 fix updated for + // windows-sys 0.61.) + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + let h_in = GetStdHandle(STD_INPUT_HANDLE); + if h_in.is_null() || h_in == INVALID_HANDLE_VALUE { + return false; + } + let mut current_in: u32 = 0; + if GetConsoleMode(h_in, &mut current_in) == 0 { + return false; + } + let h_out = GetStdHandle(STD_OUTPUT_HANDLE); + let current_out = if !h_out.is_null() && h_out != INVALID_HANDLE_VALUE { + let mut m: u32 = 0; + if GetConsoleMode(h_out, &mut m) != 0 { + Some(m) + } else { + None + } + } else { + None + }; + + { + let mut saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); + if saved.is_none() { + *saved = Some((current_in, current_out)); + } + } + + let raw_in = (current_in + & !(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT)) + | ENABLE_VIRTUAL_TERMINAL_INPUT; + if SetConsoleMode(h_in, raw_in) == 0 { + return false; + } + if let Some(out_mode) = current_out { + let raw_out = out_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; + let _ = SetConsoleMode(h_out, raw_out); + } + true + } +} + +pub fn disable() -> bool { + unsafe { + let saved = SAVED.lock().unwrap_or_else(|p| p.into_inner()); + if let Some((in_mode, out_mode)) = saved.as_ref() { + use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE; + let h_in = GetStdHandle(STD_INPUT_HANDLE); + if !h_in.is_null() && h_in != INVALID_HANDLE_VALUE { + let _ = SetConsoleMode(h_in, *in_mode); + } + if let Some(m) = out_mode { + let h_out = GetStdHandle(STD_OUTPUT_HANDLE); + if !h_out.is_null() && h_out != INVALID_HANDLE_VALUE { + let _ = SetConsoleMode(h_out, *m); + } + } + true + } else { + true + } + } +} diff --git a/crates/perry-stdlib/src/readline/test_support.rs b/crates/perry-stdlib/src/readline/test_support.rs index 5d6a6e16dd..52a80e0b46 100644 --- a/crates/perry-stdlib/src/readline/test_support.rs +++ b/crates/perry-stdlib/src/readline/test_support.rs @@ -15,7 +15,11 @@ pub(super) fn test_inject_chunk(chunk: &[u8]) { PENDING_DATA.lock().unwrap().push(chunk.to_vec()); } -extern "C" fn count_data_callback(_closure: *const ClosureHeader, _chunk: f64) -> f64 { +extern "C" fn count_data_callback( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _chunk: f64, +) -> f64 { DATA_COUNT.with(|count| *count.borrow_mut() += 1); undefined() } @@ -24,7 +28,10 @@ pub(super) fn data_counter_callback() -> i64 { js_closure_alloc(count_data_callback as *const u8, 0) as i64 } -extern "C" fn count_readable_callback(_closure: *const ClosureHeader) -> f64 { +extern "C" fn count_readable_callback( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { DATA_COUNT.with(|count| *count.borrow_mut() += 1); undefined() } @@ -35,6 +42,7 @@ pub(super) fn readable_counter_callback() -> i64 { extern "C" fn record_keypress_callback( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, _seq: f64, key_obj: f64, ) -> f64 { diff --git a/crates/perry-stdlib/src/runtime_thread_exit_tests.rs b/crates/perry-stdlib/src/runtime_thread_exit_tests.rs index 285fb1c514..ebc6b38ff3 100644 --- a/crates/perry-stdlib/src/runtime_thread_exit_tests.rs +++ b/crates/perry-stdlib/src/runtime_thread_exit_tests.rs @@ -11,7 +11,10 @@ //! Only reachable with perry-runtime built as an ordinary dependency: its own //! unit tests keep freed blocks mapped and make these tables per-thread. -extern "C" fn probe_thunk(_closure: *const perry_runtime::ClosureHeader) -> f64 { +extern "C" fn probe_thunk( + _closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { 0.0 } 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 88ce3e9541..e851e51cbf 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 @@ -7,11 +7,20 @@ use perry_runtime::gc::RuntimeHandleScope; use perry_runtime::{ClosureHeader, JSValue, ObjectHeader}; -extern "C" fn thunk1(_closure: *const ClosureHeader, _a: f64) -> f64 { +extern "C" fn thunk1( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _a: f64, +) -> f64 { f64::from_bits(JSValue::undefined().bits()) } -extern "C" fn thunk2(_closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { +extern "C" fn thunk2( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _a: f64, + _b: f64, +) -> f64 { f64::from_bits(JSValue::undefined().bits()) } 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 ad035a99c8..91b6eab6df 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 @@ -7,7 +7,10 @@ use perry_runtime::ClosureHeader; -extern "C" fn probe_thunk(_closure: *const ClosureHeader) -> f64 { +extern "C" fn probe_thunk( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { 0.0 } 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 7de8477db6..2128666c87 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 @@ -6,7 +6,10 @@ //! join is not vacuous), and checks the exiting thread's `Arena::drop` //! released it. -extern "C" fn probe_thunk(_closure: *const perry_runtime::ClosureHeader) -> f64 { +extern "C" fn probe_thunk( + _closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { 0.0 } 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 6f17e5aa49..4833831c39 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 @@ -28,7 +28,10 @@ extern "C" { fn js_u8_buffer_read_f64(target: *const u8, index: i32) -> f64; } -extern "C" fn probe_thunk(_closure: *const perry_runtime::ClosureHeader) -> f64 { +extern "C" fn probe_thunk( + _closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { 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 6099eb6e4a..92f6084ca5 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 @@ -13,11 +13,16 @@ use perry_runtime::closure::js_closure_alloc; use perry_runtime::gc::RuntimeHandleScope; use perry_runtime::{ArrayHeader, ClosureHeader, JSValue}; -extern "C" fn thunk0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn thunk0(_closure: *const ClosureHeader, _this: perry_runtime::closure::JsThis) -> f64 { 0.0 } -extern "C" fn thunk2(_closure: *const ClosureHeader, _a: f64, _b: f64) -> f64 { +extern "C" fn thunk2( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _a: f64, + _b: f64, +) -> f64 { 0.0 } diff --git a/crates/perry-stdlib/src/sqlite/better.rs b/crates/perry-stdlib/src/sqlite/better.rs index 6680e3a711..863478d437 100644 --- a/crates/perry-stdlib/src/sqlite/better.rs +++ b/crates/perry-stdlib/src/sqlite/better.rs @@ -413,6 +413,7 @@ pub unsafe extern "C" fn js_sqlite_pragma( /// Captures: [0] = db_handle (as f64), [1] = original closure ptr (as i64) pub(crate) unsafe extern "C" fn sqlite_tx_wrapper( wrapper_closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, arg0: f64, ) -> f64 { use perry_runtime::closure::{ diff --git a/crates/perry-stdlib/src/sqlite/bun.rs b/crates/perry-stdlib/src/sqlite/bun.rs index e2f2dea61c..05357935ac 100644 --- a/crates/perry-stdlib/src/sqlite/bun.rs +++ b/crates/perry-stdlib/src/sqlite/bun.rs @@ -209,6 +209,7 @@ pub unsafe extern "C" fn js_bun_sqlite_statement_finalize(stmt_handle: Handle) { unsafe extern "C" fn bun_sqlite_transaction_wrapper( wrapper: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, rest_value: f64, ) -> f64 { let db_handle = js_closure_get_capture_f64(wrapper, 0) as Handle; diff --git a/crates/perry-stdlib/src/sqlite/dispatch.rs b/crates/perry-stdlib/src/sqlite/dispatch.rs index 4235c182d8..8ecc085b05 100644 --- a/crates/perry-stdlib/src/sqlite/dispatch.rs +++ b/crates/perry-stdlib/src/sqlite/dispatch.rs @@ -200,7 +200,10 @@ pub unsafe fn dispatch_node_sqlite_database_property( } } -pub(crate) extern "C" fn sql_tag_store_constructor_thunk(_closure: *const ClosureHeader) -> f64 { +pub(crate) extern "C" fn sql_tag_store_constructor_thunk( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { throw_illegal_constructor() } diff --git a/crates/perry-stdlib/src/streams.rs b/crates/perry-stdlib/src/streams.rs index 9be233d55b..cbd65dbb7c 100644 --- a/crates/perry-stdlib/src/streams.rs +++ b/crates/perry-stdlib/src/streams.rs @@ -1086,7 +1086,10 @@ unsafe fn invoke_start(stream_id: usize) { } } -extern "C" fn readable_pull_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_pull_microtask( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let stream_bits = js_closure_get_capture_ptr(closure, 0) as u64; let stream_id = f64::from_bits(stream_bits) as usize; @@ -1161,7 +1164,11 @@ fn readable_pull_settled_closure(func: *const u8, stream_id: usize) -> *mut Clos closure } -extern "C" fn readable_pull_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn readable_pull_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _value: f64, +) -> f64 { unsafe { let stream_id = js_closure_get_capture_ptr(closure, 0) as usize; if let Some(s) = READABLE_STREAMS.lock().unwrap().get_mut(&stream_id) { @@ -1172,7 +1179,11 @@ extern "C" fn readable_pull_fulfilled(closure: *const ClosureHeader, _value: f64 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn readable_pull_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn readable_pull_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { unsafe { let stream_id = js_closure_get_capture_ptr(closure, 0) as usize; let should_error = { @@ -2166,7 +2177,11 @@ pub unsafe extern "C" fn js_readable_stream_controller_desired_size(stream_handl // ReadableStreamDefaultReader FFI // ───────────────────────────────────────────────────────────────────── -extern "C" fn readable_from_chunk_fulfilled(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn readable_from_chunk_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -2178,7 +2193,11 @@ extern "C" fn readable_from_chunk_fulfilled(closure: *const ClosureHeader, value f64::from_bits(TAG_UNDEFINED) } -extern "C" fn readable_from_chunk_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn readable_from_chunk_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -2337,7 +2356,7 @@ fn resolved_done_promise() -> f64 { } fn closure_capture_value( - func: extern "C" fn(*const ClosureHeader) -> f64, + func: extern "C" fn(*const ClosureHeader, perry_runtime::closure::JsThis) -> f64, value: f64, ) -> *mut ClosureHeader { let fn_ptr = func as *const u8; @@ -2355,7 +2374,10 @@ fn closure_capture_value_get(closure: *const ClosureHeader) -> f64 { f64::from_bits(bits) } -extern "C" fn readable_stream_iterator_next(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_stream_iterator_next( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let reader = closure_capture_value_get(closure); if reader.to_bits() == TAG_UNDEFINED { return resolved_done_promise(); @@ -2363,7 +2385,10 @@ extern "C" fn readable_stream_iterator_next(closure: *const ClosureHeader) -> f6 unsafe { box_promise(js_reader_read(reader)) } } -extern "C" fn readable_stream_iterator_return(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_stream_iterator_return( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let reader = closure_capture_value_get(closure); if reader.to_bits() != TAG_UNDEFINED { unsafe { @@ -2373,7 +2398,10 @@ extern "C" fn readable_stream_iterator_return(closure: *const ClosureHeader) -> resolved_done_promise() } -extern "C" fn readable_stream_iterator_self(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_stream_iterator_self( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { closure_capture_value_get(closure) } diff --git a/crates/perry-stdlib/src/streams/byob.rs b/crates/perry-stdlib/src/streams/byob.rs index 14bc0713e8..b5209da248 100644 --- a/crates/perry-stdlib/src/streams/byob.rs +++ b/crates/perry-stdlib/src/streams/byob.rs @@ -432,7 +432,11 @@ pub unsafe extern "C" fn js_readable_stream_controller_byob_request(stream_handl f64::from_bits(JSValue::object_ptr(obj as *mut u8).bits()) } -extern "C" fn byob_request_respond(closure: *const ClosureHeader, bytes_written: f64) -> f64 { +extern "C" fn byob_request_respond( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + bytes_written: f64, +) -> f64 { unsafe { let stream_id = perry_runtime::closure::js_closure_get_capture_f64(closure, 0) as usize; let n = JSValue::from_bits(bytes_written.to_bits()).to_number(); @@ -503,7 +507,11 @@ extern "C" fn byob_request_respond(closure: *const ClosureHeader, bytes_written: f64::from_bits(TAG_UNDEFINED) } -extern "C" fn byob_request_respond_with_new_view(closure: *const ClosureHeader, view: f64) -> f64 { +extern "C" fn byob_request_respond_with_new_view( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + view: f64, +) -> f64 { unsafe { let stream_id = perry_runtime::closure::js_closure_get_capture_f64(closure, 0) as usize; let view_bits = view.to_bits(); diff --git a/crates/perry-stdlib/src/streams/pipe.rs b/crates/perry-stdlib/src/streams/pipe.rs index a9deef471e..2cf8312da5 100644 --- a/crates/perry-stdlib/src/streams/pipe.rs +++ b/crates/perry-stdlib/src/streams/pipe.rs @@ -138,7 +138,10 @@ fn pipe_state_from_capture(closure: *const ClosureHeader) -> PipeState { } } -extern "C" fn readable_stream_pipe_to_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_stream_pipe_to_microtask( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let r_id = capture_f64(closure, 0) as usize; let w_id = capture_f64(closure, 1) as usize; @@ -150,6 +153,7 @@ extern "C" fn readable_stream_pipe_to_microtask(closure: *const ClosureHeader) - extern "C" fn readable_stream_pipe_to_read_fulfilled( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, result: f64, ) -> f64 { unsafe { @@ -171,6 +175,7 @@ extern "C" fn readable_stream_pipe_to_read_fulfilled( extern "C" fn readable_stream_pipe_to_write_fulfilled( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, _value: f64, ) -> f64 { unsafe { @@ -187,6 +192,7 @@ extern "C" fn readable_stream_pipe_to_write_fulfilled( extern "C" fn readable_stream_pipe_to_close_fulfilled( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, _value: f64, ) -> f64 { unsafe { @@ -206,6 +212,7 @@ extern "C" fn readable_stream_pipe_to_close_fulfilled( extern "C" fn readable_stream_pipe_to_read_rejected( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, reason: f64, ) -> f64 { unsafe { @@ -228,6 +235,7 @@ extern "C" fn readable_stream_pipe_to_read_rejected( extern "C" fn readable_stream_pipe_to_write_rejected( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, reason: f64, ) -> f64 { unsafe { @@ -248,7 +256,10 @@ extern "C" fn readable_stream_pipe_to_write_rejected( f64::from_bits(TAG_UNDEFINED) } -extern "C" fn readable_stream_pipe_to_aborted(closure: *const ClosureHeader) -> f64 { +extern "C" fn readable_stream_pipe_to_aborted( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let promise = promise_from_capture(closure, 2); if perry_runtime::promise::js_promise_state(promise) != 0 { @@ -427,6 +438,7 @@ unsafe fn cancel_source_and_reject( extern "C" fn readable_stream_pipe_to_shutdown_fulfilled( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, _value: f64, ) -> f64 { unsafe { @@ -447,6 +459,7 @@ extern "C" fn readable_stream_pipe_to_shutdown_fulfilled( extern "C" fn readable_stream_pipe_to_shutdown_rejected( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, reason: f64, ) -> f64 { unsafe { @@ -869,7 +882,7 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( state.abort_listener, ); if perry_runtime::url::js_abort_signal_is_aborted(signal_ptr) != 0 { - readable_stream_pipe_to_aborted(listener); + readable_stream_pipe_to_aborted(listener, perry_runtime::closure::JsThis::current()); return promise; } } @@ -934,7 +947,11 @@ mod tests { use super::*; use crate::streams::{alloc_readable, alloc_writable}; - extern "C" fn pending_abort_action(closure: *const ClosureHeader, _reason: f64) -> f64 { + extern "C" fn pending_abort_action( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _reason: f64, + ) -> f64 { let promise = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as *mut Promise; box_promise(promise) diff --git a/crates/perry-stdlib/src/streams/strategy.rs b/crates/perry-stdlib/src/streams/strategy.rs index 3ad9b92e21..b8284fe6ba 100644 --- a/crates/perry-stdlib/src/streams/strategy.rs +++ b/crates/perry-stdlib/src/streams/strategy.rs @@ -15,12 +15,20 @@ use super::*; // node_submodules. /// `CountQueuingStrategy.prototype.size` — every chunk counts as 1. -extern "C" fn count_queuing_strategy_size(_c: *const ClosureHeader, _chunk: f64) -> f64 { +extern "C" fn count_queuing_strategy_size( + _c: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _chunk: f64, +) -> f64 { 1.0 } /// `ByteLengthQueuingStrategy.prototype.size` — `chunk.byteLength`. -extern "C" fn byte_length_queuing_strategy_size(_c: *const ClosureHeader, chunk: f64) -> f64 { +extern "C" fn byte_length_queuing_strategy_size( + _c: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + chunk: f64, +) -> f64 { // Mirror Node's `return chunk.byteLength`: the generic property getter // resolves `.byteLength` for both registered buffers/typed arrays and // plain `{ byteLength }` objects. @@ -31,7 +39,7 @@ extern "C" fn byte_length_queuing_strategy_size(_c: *const ClosureHeader, chunk: /// 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, f64) -> f64, + size_fn: extern "C" fn(*const ClosureHeader, perry_runtime::closure::JsThis, f64) -> f64, ) -> f64 { let obj = js_object_alloc(0, 2); let keys = js_array_alloc(2); diff --git a/crates/perry-stdlib/src/streams/tee.rs b/crates/perry-stdlib/src/streams/tee.rs index bbf1f7a034..a9adad834b 100644 --- a/crates/perry-stdlib/src/streams/tee.rs +++ b/crates/perry-stdlib/src/streams/tee.rs @@ -324,7 +324,10 @@ pub(super) unsafe fn tee_schedule_pull_demand(source: usize) { /// later. `TEE_PULLING` stays held across the hop (single-threaded microtask /// dispatch — the remove+insert below has no interleaving window), so /// coalescing against enqueue/close reroutes keeps working. -extern "C" fn tee_demand_hop(closure: *const ClosureHeader) -> f64 { +extern "C" fn tee_demand_hop( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let source = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; TEE_PULLING.lock().unwrap().remove(&source); @@ -335,7 +338,10 @@ extern "C" fn tee_demand_hop(closure: *const ClosureHeader) -> f64 { /// The extra tick a byte-stream tee's CHAINED pull pays before the next /// cycle (see the chain decision in `tee_pull_microtask`). -extern "C" fn tee_byte_chain_hop(closure: *const ClosureHeader) -> f64 { +extern "C" fn tee_byte_chain_hop( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let source = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; tee_schedule_pull(source); @@ -343,7 +349,10 @@ extern "C" fn tee_byte_chain_hop(closure: *const ClosureHeader) -> f64 { f64::from_bits(0x7FFC_0000_0000_0001) // TAG_UNDEFINED } -extern "C" fn tee_close_tick(closure: *const ClosureHeader) -> f64 { +extern "C" fn tee_close_tick( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let source = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; tee_close_branches(source); @@ -351,7 +360,10 @@ extern "C" fn tee_close_tick(closure: *const ClosureHeader) -> f64 { f64::from_bits(0x7FFC_0000_0000_0001) // TAG_UNDEFINED } -extern "C" fn tee_pull_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn tee_pull_microtask( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let undef = f64::from_bits(0x7FFC_0000_0000_0001); // TAG_UNDEFINED unsafe { let source = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; diff --git a/crates/perry-stdlib/src/streams/transform.rs b/crates/perry-stdlib/src/streams/transform.rs index 5f82b200d9..17f7770112 100644 --- a/crates/perry-stdlib/src/streams/transform.rs +++ b/crates/perry-stdlib/src/streams/transform.rs @@ -281,7 +281,10 @@ pub(super) unsafe fn transform_write(writable_id: usize, chunk: f64) -> *mut Pro /// before the transformer runs (sink write algorithm → PerformTransform); /// teepipe2.js shows the transform output landing one tick later than a /// single-job deferral produces. Re-queue once, then run the transform. -extern "C" fn transform_write_job(closure: *const ClosureHeader) -> f64 { +extern "C" fn transform_write_job( + closure: *const ClosureHeader, + _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); @@ -298,7 +301,10 @@ extern "C" fn transform_write_job(closure: *const ClosureHeader) -> f64 { f64::from_bits(TAG_UNDEFINED) } -extern "C" fn transform_write_job2(closure: *const ClosureHeader) -> f64 { +extern "C" fn transform_write_job2( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let transform_cb = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0); let readable_id = perry_runtime::closure::js_closure_get_capture_ptr(closure, 1) as usize; @@ -523,7 +529,11 @@ unsafe fn transform_write_job_done(writable_id: usize) { /// The async transformer's returned promise fulfilled — settle the write with /// the same backpressure gate as the synchronous path. -extern "C" fn transform_write_settle_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn transform_write_settle_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _value: f64, +) -> f64 { unsafe { let readable_id = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; let promise = @@ -538,7 +548,11 @@ extern "C" fn transform_write_settle_fulfilled(closure: *const ClosureHeader, _v /// The async transformer's returned promise rejected — error the readable side /// and reject the write (spec: transformResultPromise rejection errors the /// TransformStream). -extern "C" fn transform_write_settle_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn transform_write_settle_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { unsafe { let readable_id = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as usize; let promise = @@ -657,6 +671,7 @@ mod backpressure_tests { extern "C" fn capture_transform( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, _chunk: f64, _controller: f64, ) -> f64 { @@ -931,7 +946,11 @@ unsafe fn error_transform_close( js_promise_reject(close_promise, f64::from_bits(reason_bits)); } -extern "C" fn transform_flush_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn transform_flush_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _value: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } @@ -945,7 +964,11 @@ extern "C" fn transform_flush_fulfilled(closure: *const ClosureHeader, _value: f f64::from_bits(TAG_UNDEFINED) } -extern "C" fn transform_flush_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn transform_flush_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } diff --git a/crates/perry-stdlib/src/streams/writable.rs b/crates/perry-stdlib/src/streams/writable.rs index 3d564e438f..a1f3f48ce7 100644 --- a/crates/perry-stdlib/src/streams/writable.rs +++ b/crates/perry-stdlib/src/streams/writable.rs @@ -273,7 +273,10 @@ fn writable_capture_promise(closure: *const ClosureHeader, idx: u32) -> *mut Pro perry_runtime::closure::js_closure_get_capture_ptr(closure, idx) as *mut Promise } -extern "C" fn writable_write_start_microtask(closure: *const ClosureHeader) -> f64 { +extern "C" fn writable_write_start_microtask( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { unsafe { let stream_id = writable_capture_usize(closure, 0); let writer_id = writable_capture_usize(closure, 1); @@ -291,7 +294,11 @@ extern "C" fn writable_write_start_microtask(closure: *const ClosureHeader) -> f f64::from_bits(TAG_UNDEFINED) } -extern "C" fn writable_write_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn writable_write_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _value: f64, +) -> f64 { unsafe { let stream_id = writable_capture_usize(closure, 0); let writer_id = writable_capture_usize(closure, 1); @@ -301,7 +308,11 @@ extern "C" fn writable_write_fulfilled(closure: *const ClosureHeader, _value: f6 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn writable_write_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn writable_write_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { unsafe { let stream_id = writable_capture_usize(closure, 0); let write_promise = writable_capture_promise(closure, 2); @@ -360,7 +371,11 @@ unsafe fn try_call_writable_close(cb: i64) -> Result { .map_err(f64::to_bits) } -extern "C" fn writable_close_fulfilled(closure: *const ClosureHeader, _value: f64) -> f64 { +extern "C" fn writable_close_fulfilled( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _value: f64, +) -> f64 { unsafe { let stream_id = writable_capture_usize(closure, 0); let close_promise = writable_capture_promise(closure, 1); @@ -369,7 +384,11 @@ extern "C" fn writable_close_fulfilled(closure: *const ClosureHeader, _value: f6 f64::from_bits(TAG_UNDEFINED) } -extern "C" fn writable_close_rejected(closure: *const ClosureHeader, reason: f64) -> f64 { +extern "C" fn writable_close_rejected( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + reason: f64, +) -> f64 { unsafe { let stream_id = writable_capture_usize(closure, 0); let close_promise = writable_capture_promise(closure, 1); diff --git a/crates/perry-stdlib/src/string_decoder.rs b/crates/perry-stdlib/src/string_decoder.rs index c747b98c21..1dc5345b64 100644 --- a/crates/perry-stdlib/src/string_decoder.rs +++ b/crates/perry-stdlib/src/string_decoder.rs @@ -213,6 +213,7 @@ unsafe fn this_string_decoder_handle() -> i64 { extern "C" fn string_decoder_proto_write( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, buf: f64, ) -> f64 { unsafe { dispatch_string_decoder(this_string_decoder_handle(), "write", &[buf]) } @@ -220,6 +221,7 @@ extern "C" fn string_decoder_proto_write( extern "C" fn string_decoder_proto_end( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, buf: f64, ) -> f64 { unsafe { dispatch_string_decoder(this_string_decoder_handle(), "end", &[buf]) } @@ -227,6 +229,7 @@ extern "C" fn string_decoder_proto_end( extern "C" fn string_decoder_proto_text( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, buf: f64, _offset: f64, ) -> f64 { @@ -235,18 +238,21 @@ extern "C" fn string_decoder_proto_text( extern "C" fn string_decoder_last_char_getter( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, ) -> f64 { unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastChar") } } extern "C" fn string_decoder_last_need_getter( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, ) -> f64 { unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastNeed") } } extern "C" fn string_decoder_last_total_getter( _closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, ) -> f64 { unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastTotal") } } diff --git a/crates/perry-stdlib/src/tls.rs b/crates/perry-stdlib/src/tls.rs index d0627dd4ce..4bcaad7591 100644 --- a/crates/perry-stdlib/src/tls.rs +++ b/crates/perry-stdlib/src/tls.rs @@ -1165,7 +1165,12 @@ unsafe fn failed_server_socket(server_handle: i64, servername: Option) - socket_id } -extern "C" fn tls_sni_completion(closure: *const ClosureHeader, error: f64, context: f64) -> f64 { +extern "C" fn tls_sni_completion( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + error: f64, + context: f64, +) -> f64 { unsafe { let server_handle = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as i64; let hostname = value_to_string(perry_runtime::closure::js_closure_get_capture_f64( diff --git a/crates/perry-stdlib/src/webcrypto/digest.rs b/crates/perry-stdlib/src/webcrypto/digest.rs index d464638195..2674a9cf50 100644 --- a/crates/perry-stdlib/src/webcrypto/digest.rs +++ b/crates/perry-stdlib/src/webcrypto/digest.rs @@ -8,7 +8,10 @@ use super::*; /// continue an event-loop iteration early: Auth.js hashes its CSRF token with /// `subtle.digest`, so a Next.js Server Component's `await auth()` completed /// ahead of Node and reordered the React Flight (RSC) rows of the response. -extern "C" fn webcrypto_digest_settle(closure: *const perry_runtime::ClosureHeader) -> f64 { +extern "C" fn webcrypto_digest_settle( + closure: *const perry_runtime::ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let promise_bits = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as u64; let value_bits = perry_runtime::closure::js_closure_get_capture_ptr(closure, 1) as u64; // Slot 2 holds the remaining macrotask hops (raw i64). Node's threadpool diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index e3d56bca3e..bd02f6de90 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -641,7 +641,10 @@ fn listener_once(options: f64) -> bool { perry_runtime::value::js_is_truthy(get_object_field(options, "once")) != 0 } -extern "C" fn worker_threads_noop0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_threads_noop0( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { js_undefined() } @@ -754,7 +757,10 @@ fn queue_worker_threads_microtask() { perry_runtime::event_pump::js_notify_main_thread(); } -extern "C" fn worker_threads_channels_microtask(_closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_threads_channels_microtask( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { js_worker_threads_channels_process_pending(); js_undefined() } @@ -1023,11 +1029,21 @@ fn push_parent_event(parent: AgentId, event: WorkerEvent) { perry_runtime::event_pump::js_notify_main_thread(); } -extern "C" fn worker_on(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn worker_on( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { worker_add_listener(captured_worker_id(closure), event, callback, false, false) } -extern "C" fn worker_once(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn worker_once( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { worker_add_listener(captured_worker_id(closure), event, callback, true, false) } @@ -1036,6 +1052,7 @@ extern "C" fn worker_once(closure: *const ClosureHeader, event: f64, callback: f /// `MessageEvent` (with `.data`) for "message" events. extern "C" fn worker_add_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1045,10 +1062,16 @@ extern "C" fn worker_add_event_listener( /// `worker.removeEventListener(type, listener)`. extern "C" fn worker_remove_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { - worker_off(closure, event, callback) + worker_off( + closure, + perry_runtime::closure::JsThis::current(), + event, + callback, + ) } fn worker_add_listener( @@ -1077,7 +1100,12 @@ fn worker_add_listener( js_undefined() } -extern "C" fn worker_off(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn worker_off( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { let worker_id = captured_worker_id(closure); let Some(event) = event_name(event) else { return js_undefined(); @@ -1092,7 +1120,11 @@ extern "C" fn worker_off(closure: *const ClosureHeader, event: f64, callback: f6 js_undefined() } -extern "C" fn worker_post_message(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn worker_post_message( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, +) -> f64 { let worker_id = captured_worker_id(closure); let message = unsafe { serialize_nanbox_for_thread(value.to_bits()) }; let sender = WORKERS @@ -1106,11 +1138,17 @@ extern "C" fn worker_post_message(closure: *const ClosureHeader, value: f64) -> js_undefined() } -extern "C" fn worker_terminate(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_terminate( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_terminate_by_id(captured_worker_id(closure)) } -extern "C" fn worker_reload(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_reload( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_reload_by_id(captured_worker_id(closure)) } @@ -1127,7 +1165,10 @@ fn worker_reload_by_id(worker_id: u64) -> f64 { js_undefined() } -extern "C" fn worker_ref(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_ref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_ref_by_id(captured_worker_id(closure)) } @@ -1139,7 +1180,10 @@ fn worker_ref_by_id(worker_id: u64) -> f64 { js_undefined() } -extern "C" fn worker_unref(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_unref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_unref_by_id(captured_worker_id(closure)) } @@ -1151,7 +1195,10 @@ fn worker_unref_by_id(worker_id: u64) -> f64 { js_undefined() } -extern "C" fn worker_get_heap_statistics(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_get_heap_statistics( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_get_heap_statistics_by_id(captured_worker_id(closure)) } @@ -1159,7 +1206,11 @@ fn worker_get_heap_statistics_by_id(_worker_id: u64) -> f64 { resolved_promise_value(unsafe { js_v8_get_heap_statistics() }) } -extern "C" fn worker_cpu_usage(closure: *const ClosureHeader, prior: f64) -> f64 { +extern "C" fn worker_cpu_usage( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + prior: f64, +) -> f64 { worker_cpu_usage_by_id(captured_worker_id(closure), prior) } @@ -1167,7 +1218,11 @@ fn worker_cpu_usage_by_id(_worker_id: u64, prior: f64) -> f64 { resolved_promise_value(unsafe { js_process_cpu_usage(prior) }) } -extern "C" fn worker_get_heap_snapshot(closure: *const ClosureHeader, options: f64) -> f64 { +extern "C" fn worker_get_heap_snapshot( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + options: f64, +) -> f64 { worker_get_heap_snapshot_by_id(captured_worker_id(closure), options) } @@ -1175,7 +1230,10 @@ fn worker_get_heap_snapshot_by_id(_worker_id: u64, _options: f64) -> f64 { resolved_promise_value(worker_readable_stream_object()) } -extern "C" fn worker_start_cpu_profile(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_start_cpu_profile( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_start_cpu_profile_by_id(captured_worker_id(closure)) } @@ -1183,7 +1241,10 @@ fn worker_start_cpu_profile_by_id(_worker_id: u64) -> f64 { resolved_promise_value(worker_profile_handle(0)) } -extern "C" fn worker_start_heap_profile(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_start_heap_profile( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { worker_start_heap_profile_by_id(captured_worker_id(closure)) } diff --git a/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs b/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs index a6cd495871..d42b588904 100644 --- a/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs +++ b/crates/perry-stdlib/src/worker_threads/broadcast_channel.rs @@ -8,7 +8,11 @@ use super::*; -extern "C" fn broadcast_post_message(closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn broadcast_post_message( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, +) -> f64 { let channel_id = port_id_from_closure(closure); let channel_name = BROADCAST_CHANNELS.with(|channels| { channels @@ -34,7 +38,10 @@ extern "C" fn broadcast_post_message(closure: *const ClosureHeader, value: f64) js_undefined() } -extern "C" fn broadcast_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn broadcast_close( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let channel_id = port_id_from_closure(closure); BROADCAST_CHANNELS.with(|channels| { if let Some(state) = channels.borrow_mut().get_mut(&channel_id) { @@ -46,7 +53,10 @@ extern "C" fn broadcast_close(closure: *const ClosureHeader) -> f64 { js_undefined() } -extern "C" fn broadcast_ref_or_unref(closure: *const ClosureHeader) -> f64 { +extern "C" fn broadcast_ref_or_unref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let channel_id = port_id_from_closure(closure); BROADCAST_CHANNELS.with(|channels| match channels.borrow().get(&channel_id) { Some(state) => f64::from_bits(state.object_bits), @@ -56,6 +66,7 @@ extern "C" fn broadcast_ref_or_unref(closure: *const ClosureHeader) -> f64 { extern "C" fn broadcast_add_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, options: f64, @@ -86,6 +97,7 @@ extern "C" fn broadcast_add_event_listener( extern "C" fn broadcast_remove_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { diff --git a/crates/perry-stdlib/src/worker_threads/message_port.rs b/crates/perry-stdlib/src/worker_threads/message_port.rs index d0332b0b83..ea3afd3a40 100644 --- a/crates/perry-stdlib/src/worker_threads/message_port.rs +++ b/crates/perry-stdlib/src/worker_threads/message_port.rs @@ -97,7 +97,12 @@ pub(super) fn message_port_object(port_id: u64) -> *mut perry_runtime::object::O } /// port.postMessage(value) — deliver to the peer port's inbox (#3157). -extern "C" fn port_post_message(closure: *const ClosureHeader, value: f64, _transfer: f64) -> f64 { +extern "C" fn port_post_message( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, + _transfer: f64, +) -> f64 { let port_id = port_id_from_closure(closure); if port_id == PARENT_PORT_HANDLE as u64 && CURRENT_WORKER_ID.with(|id| id.get()) != 0 { return js_worker_threads_post_message(value); @@ -127,12 +132,22 @@ extern "C" fn port_post_message(closure: *const ClosureHeader, value: f64, _tran } /// port.on(event, callback) / addListener (#3157). -extern "C" fn port_on(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn port_on( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { port_add_node_listener(closure, event, callback, false) } /// port.once(event, callback) (#6763). -extern "C" fn port_once(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn port_once( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { port_add_node_listener(closure, event, callback, true) } @@ -199,7 +214,12 @@ fn port_add_node_listener( } /// port.off(event) / removeListener (#3157). -extern "C" fn port_off(closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn port_off( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { let port_id = port_id_from_closure(closure); let event_name = string_value_to_string(event).unwrap_or_default(); if port_id == PARENT_PORT_HANDLE as u64 && CURRENT_WORKER_ID.with(|id| id.get()) != 0 { @@ -237,7 +257,11 @@ extern "C" fn port_off(closure: *const ClosureHeader, event: f64, callback: f64) js_undefined() } -extern "C" fn port_listener_count(closure: *const ClosureHeader, event: f64) -> f64 { +extern "C" fn port_listener_count( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, +) -> f64 { let port_id = port_id_from_closure(closure); let event_name = string_value_to_string(event).unwrap_or_default(); MESSAGE_PORTS.with(|ports| { @@ -264,6 +288,7 @@ extern "C" fn port_listener_count(closure: *const ClosureHeader, event: f64) -> /// port.addEventListener(event, callback) (#3598). extern "C" fn port_add_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, options: f64, @@ -318,6 +343,7 @@ extern "C" fn port_add_event_listener( /// port.removeEventListener(event, callback) (#3598). extern "C" fn port_remove_event_listener( closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -351,7 +377,10 @@ extern "C" fn port_remove_event_listener( } /// port.start() — enable delivery of queued messages to the listener (#3157). -extern "C" fn port_start(closure: *const ClosureHeader) -> f64 { +extern "C" fn port_start( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let port_id = port_id_from_closure(closure); MESSAGE_PORTS.with(|ports| { if let Some(state) = ports.borrow_mut().get_mut(&port_id) { @@ -361,7 +390,10 @@ extern "C" fn port_start(closure: *const ClosureHeader) -> f64 { js_undefined() } -extern "C" fn port_ref(closure: *const ClosureHeader) -> f64 { +extern "C" fn port_ref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let port_id = port_id_from_closure(closure); let has_handler = MESSAGE_PORTS.with(|ports| { ports @@ -380,7 +412,10 @@ extern "C" fn port_ref(closure: *const ClosureHeader) -> f64 { js_undefined() } -extern "C" fn port_unref(closure: *const ClosureHeader) -> f64 { +extern "C" fn port_unref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let port_id = port_id_from_closure(closure); let has_handler = MESSAGE_PORTS.with(|ports| { ports @@ -399,7 +434,10 @@ extern "C" fn port_unref(closure: *const ClosureHeader) -> f64 { js_undefined() } -extern "C" fn port_has_ref(closure: *const ClosureHeader) -> f64 { +extern "C" fn port_has_ref( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let port_id = port_id_from_closure(closure); let has_handler = MESSAGE_PORTS.with(|ports| { let ports = ports.borrow(); @@ -422,7 +460,10 @@ extern "C" fn port_has_ref(closure: *const ClosureHeader) -> f64 { } /// port.close() — mark closed and queue `close` events on both ends (#3157). -extern "C" fn port_close(closure: *const ClosureHeader) -> f64 { +extern "C" fn port_close( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let port_id = port_id_from_closure(closure); let peer_id = MESSAGE_PORTS.with(|ports| ports.borrow().get(&port_id).map(|state| state.peer)); MESSAGE_PORTS.with(|ports| { diff --git a/crates/perry-stdlib/src/worker_threads/parent_port.rs b/crates/perry-stdlib/src/worker_threads/parent_port.rs index e557031530..b9b98c4dba 100644 --- a/crates/perry-stdlib/src/worker_threads/parent_port.rs +++ b/crates/perry-stdlib/src/worker_threads/parent_port.rs @@ -63,6 +63,7 @@ pub(super) fn worker_parent_port_object() -> *mut perry_runtime::object::ObjectH extern "C" fn worker_parent_port_add_event_listener( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -72,6 +73,7 @@ extern "C" fn worker_parent_port_add_event_listener( extern "C" fn worker_parent_port_remove_event_listener( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -79,12 +81,17 @@ extern "C" fn worker_parent_port_remove_event_listener( js_worker_threads_parent_port_event_remove(event.to_bits() as i64, callback_ptr) } -extern "C" fn worker_parent_port_post_message(_closure: *const ClosureHeader, value: f64) -> f64 { +extern "C" fn worker_parent_port_post_message( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + value: f64, +) -> f64 { js_worker_threads_post_message(value) } extern "C" fn worker_parent_port_on( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -94,6 +101,7 @@ extern "C" fn worker_parent_port_on( extern "C" fn worker_parent_port_off( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, _callback: f64, ) -> f64 { diff --git a/crates/perry-stdlib/src/worker_threads/worker_surface.rs b/crates/perry-stdlib/src/worker_threads/worker_surface.rs index e22abbde5b..9261fa1a33 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_surface.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_surface.rs @@ -284,6 +284,7 @@ pub(super) fn web_worker_global_handler(name: &str) -> Option { extern "C" fn web_worker_post_message( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, value: f64, _transfer: f64, ) -> f64 { @@ -292,6 +293,7 @@ extern "C" fn web_worker_post_message( extern "C" fn web_worker_add_event_listener( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, _options: f64, @@ -302,6 +304,7 @@ extern "C" fn web_worker_add_event_listener( extern "C" fn web_worker_remove_event_listener( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -309,7 +312,10 @@ extern "C" fn web_worker_remove_event_listener( js_worker_threads_parent_port_event_remove(event.to_bits() as i64, callback_ptr) } -extern "C" fn web_worker_close(_closure: *const ClosureHeader) -> f64 { +extern "C" fn web_worker_close( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { CURRENT_WORKER_CLOSE_REQUESTED.with(|closed| closed.set(true)); js_undefined() } @@ -444,35 +450,70 @@ fn stream_remove_listener(event: f64, callback: f64) -> f64 { this } -extern "C" fn stream_on(_closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn stream_on( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { stream_register(event, callback) } -extern "C" fn stream_emit(_closure: *const ClosureHeader, event: f64, arg: f64) -> f64 { +extern "C" fn stream_emit( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + arg: f64, +) -> f64 { stream_emit_event(event, arg) } -extern "C" fn stream_off(_closure: *const ClosureHeader, event: f64, callback: f64) -> f64 { +extern "C" fn stream_off( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { stream_remove_listener(event, callback) } -extern "C" fn stream_this0(_closure: *const ClosureHeader) -> f64 { +extern "C" fn stream_this0( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { stream_this() } -extern "C" fn stream_this1(_closure: *const ClosureHeader, _arg: f64) -> f64 { +extern "C" fn stream_this1( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _arg: f64, +) -> f64 { stream_this() } -extern "C" fn stream_write(_closure: *const ClosureHeader, _chunk: f64, _encoding: f64) -> f64 { +extern "C" fn stream_write( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _chunk: f64, + _encoding: f64, +) -> f64 { js_bool(true) } -extern "C" fn stream_read(_closure: *const ClosureHeader, _size: f64) -> f64 { +extern "C" fn stream_read( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + _size: f64, +) -> f64 { js_null() } -extern "C" fn stream_pipe(_closure: *const ClosureHeader, dest: f64) -> f64 { +extern "C" fn stream_pipe( + _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, + dest: f64, +) -> f64 { dest } @@ -532,6 +573,7 @@ fn worker_writable_stream_object() -> f64 { extern "C" fn worker_event_loop_utilization( _closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, util1: f64, util2: f64, ) -> f64 { @@ -542,7 +584,10 @@ fn worker_profile_result(kind: &str) -> f64 { string_value(&format!("{{\"perryWorkerProfile\":\"{kind}\"}}")) } -extern "C" fn worker_profile_stop(closure: *const ClosureHeader) -> f64 { +extern "C" fn worker_profile_stop( + closure: *const ClosureHeader, + _this: perry_runtime::closure::JsThis, +) -> f64 { let kind_bits = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0) as u64; let kind = if kind_bits == 1 { "heap" } else { "cpu" }; resolved_promise_value(worker_profile_result(kind)) diff --git a/crates/perry-ui-ios/src/notifications.rs b/crates/perry-ui-ios/src/notifications.rs index bf62f8e169..a318c3cd2b 100644 --- a/crates/perry-ui-ios/src/notifications.rs +++ b/crates/perry-ui-ios/src/notifications.rs @@ -263,6 +263,7 @@ pub fn on_background_receive(callback: f64) { #[no_mangle] unsafe extern "C" fn perry_ios_notification_completion_trampoline( closure: *const perry_runtime::closure::ClosureHeader, + _this: perry_runtime::closure::JsThis, _arg: f64, ) -> f64 { let handle = perry_runtime::closure::js_closure_get_capture_ptr(closure, 0); diff --git a/crates/perry/src/commands/compile/object_cache.rs b/crates/perry/src/commands/compile/object_cache.rs index b515b1a32c..88a6988dbb 100644 --- a/crates/perry/src/commands/compile/object_cache.rs +++ b/crates/perry/src/commands/compile/object_cache.rs @@ -1194,6 +1194,12 @@ fn compute_object_cache_key_with_env( "env_recv_route_count", env_var("PERRY_RECV_ROUTE_COUNT").as_deref().unwrap_or(""), ); + // This-as-a-parameter stage 1: a `=1` build emits a witness call at the + // entry of every body that reads the implicit-`this` cell. + h.field( + "env_this_witness", + env_var("PERRY_THIS_WITNESS").as_deref().unwrap_or(""), + ); h.field( "env_full_outline_ic", env_var("PERRY_FULL_OUTLINE_IC").as_deref().unwrap_or(""), diff --git a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs index 79c534307e..57c09e2259 100644 --- a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs +++ b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs @@ -762,6 +762,7 @@ fn key_changes_with_codegen_env_vars() { "PERRY_TYPED_FEEDBACK", "PERRY_TYPED_FEEDBACK_TRACE", "PERRY_CONCAT_SITE_CACHE", + "PERRY_THIS_WITNESS", "PERRY_FULL_OUTLINE_IC", "PERRY_FULL_OUTLINE_IC_MIN_FUNCS", "PERRY_OUTLINE_METHOD_DISPATCH", diff --git a/crates/perry/tests/fixtures/this_param_routes.ts b/crates/perry/tests/fixtures/this_param_routes.ts new file mode 100644 index 0000000000..eff74d26ec --- /dev/null +++ b/crates/perry/tests/fixtures/this_param_routes.ts @@ -0,0 +1,72 @@ +// Every route by which a JS body that reads `this` from the implicit-`this` +// cell is entered. Stage 1 of this-as-a-parameter: each caller must hand the +// body, as its `this` PARAMETER, exactly the receiver the cell holds. +const N = process.argv.length > 99 ? 1 : 300; +const out: string[] = []; +let s = 0; + +function P(this: any, k: number) { this.k = k; } +(P as any).prototype.m = function (x: number) { return this.k + x; }; +Object.defineProperty((P as any).prototype, "kk", { + get: function (this: any) { return this.k * 10; }, + configurable: true, +}); +const p1: any = new (P as any)(1); +const p2: any = new (P as any)(2); + +const fexpr = function (this: any, x: number) { + return (this && typeof this.k === "number" ? this.k : -1000) + x; +}; +const owner: any = { k: 7 }; +owner.f = function (this: any, x: number) { return this.k * x; }; + +function topLevel(this: any, x: number) { + return (this && typeof this.k === "number" ? this.k : -1000) + x; +} +owner.t = topLevel; + +class C { + k = 5; + m(x: number) { return this.k + x; } +} +class D extends C { + g(x: number) { const f = super.m; return f.call(this, x); } +} +const d = new D(); + +const bound = fexpr.bind(owner); +const sorter: any = { + k: 3, + run(this: any) { + const a = [3, 1, 2]; + a.sort(function (x: number, y: number) { return x - y; }); + return a.join("") + this.k; + }, +}; + +for (let i = 0; i < N; i++) { + const o = i & 1 ? p1 : p2; + s += o.m(i); // ES5 prototype method, method site + s += o.kk; // prototype getter + s += owner.f(i); // own function-valued property + s += owner.t(i); // top-level function as a method + s += fexpr(i); // receiverless call of a function expression + s += fexpr.call(owner, i); // call + s += fexpr.apply(p1, [i]); // apply + s += bound(i); // bound function + s += d.g(i); // super method value (class method wrapper) + [1, 2].forEach(function (this: any, v: number) { s += this.k * v; }, p2); // callback + thisArg + s += [4].map(function (this: any, v: number) { return this.k + v; }, owner)[0]; + const q: any = new (fexpr as any)(1); // `new F()` + s += q instanceof (fexpr as any) ? 1 : 0; + if (i % 97 === 0) out.push(String(s)); +} +out.push(sorter.run()); +console.log(s, out.join(",")); + +// Value wrappers of every function kind (installed as function objects). +export function exportedValue(this: any) { return typeof this; } +async function asyncValue(this: any) { return 1; } +function* genValue(this: any) { yield 1; } +const values: any[] = [exportedValue, asyncValue, genValue, topLevel]; +console.log(values.map((f) => typeof f).join(",")); diff --git a/crates/perry/tests/this_param_abi.rs b/crates/perry/tests/this_param_abi.rs new file mode 100644 index 0000000000..5666f951cd --- /dev/null +++ b/crates/perry/tests/this_param_abi.rs @@ -0,0 +1,206 @@ +//! This-as-a-parameter, stage 1: every JS body — compiled closure, value +//! wrapper, native builtin — is `double body(i64 callee, i64 this, double +//! a0, ...)` (`perry_abi::JS_BODY_*`), and every caller hands the body, as its +//! `this` parameter, exactly the receiver the implicit-`this` cell holds for +//! the call. Bodies still read the cell, so stage 1 changes no behavior; what +//! it must not do is let the parameter and the cell disagree. +//! +//! A `PERRY_THIS_WITNESS=1` build makes the prologue of every compiled body +//! that reads the cell compare the two (`js_this_param_witness`), and reports +//! `PERRY_THIS_WITNESS checks=N mismatches=M` at exit. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn perry_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_perry")) +} + +const ROUTES: &str = include_str!("fixtures/this_param_routes.ts"); +/// node v26.5.1's output for `fixtures/this_param_routes.ts`. +const ROUTES_NODE: &str = + "346350 -933,-25431,81808,320762,1233\nfunction,function,function,function"; + +struct Run { + stdout: String, + stderr: String, + ll_dir: tempfile::TempDir, + _dir: tempfile::TempDir, +} + +/// Compile `source` with the witness on and the constructed IR saved, run it. +fn compile_and_run_witnessed(source: &str) -> Run { + let dir = tempfile::tempdir().expect("tempdir"); + let ll_dir = tempfile::tempdir().expect("ll tempdir"); + let entry = dir.path().join("main.ts"); + let output = dir.path().join("main_bin"); + std::fs::write(&entry, source).expect("write entry"); + let compile = Command::new(perry_bin()) + .current_dir(dir.path()) + .arg("compile") + .arg(&entry) + .arg("-o") + .arg(&output) + .env("PERRY_NO_CACHE", "1") + .env("PERRY_THIS_WITNESS", "1") + .env("PERRY_SAVE_LL", ll_dir.path()) + .output() + .expect("run perry compile"); + assert!( + compile.status.success(), + "perry compile failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&compile.stdout), + String::from_utf8_lossy(&compile.stderr) + ); + let run = Command::new(&output) + .current_dir(dir.path()) + .output() + .expect("run compiled binary"); + let stderr = String::from_utf8_lossy(&run.stderr).into_owned(); + assert!( + run.status.success(), + "binary failed ({:?})\nstderr:\n{stderr}", + run.status + ); + Run { + stdout: String::from_utf8_lossy(&run.stdout).trim().to_owned(), + stderr, + ll_dir, + _dir: dir, + } +} + +/// `(checks, mismatches)` from the witness's exit line. +fn witness_counts(stderr: &str) -> (u64, u64) { + let line = stderr + .lines() + .find(|l| l.starts_with("PERRY_THIS_WITNESS checks=")) + .unwrap_or_else(|| panic!("no witness report on stderr:\n{stderr}")); + let field = |name: &str| -> u64 { + line.split_whitespace() + .find_map(|w| w.strip_prefix(name)?.strip_prefix('=')?.parse().ok()) + .unwrap_or_else(|| panic!("no {name} in {line:?}")) + }; + (field("checks"), field("mismatches")) +} + +/// Sabotage: the method-site hit (or any caller) passes `undefined` — or the +/// runtime funnel passes anything but the cell's receiver — as `this` -> the +/// witness names the body and `mismatches` is non-zero. Sabotage: the witness +/// build emits no check -> `checks` is 0 and the lower bound fails. +#[test] +fn every_call_route_passes_the_cell_receiver_as_the_this_parameter() { + let run = compile_and_run_witnessed(ROUTES); + assert_eq!(run.stdout, ROUTES_NODE, "stderr:\n{}", run.stderr); + let (checks, mismatches) = witness_counts(&run.stderr); + // 300 iterations of ~13 routes into cell-reading bodies: a witness that + // did not run cannot pass for one that found nothing. + assert!( + checks >= 3000, + "the witness checked only {checks} body entries\nstderr:\n{}", + run.stderr + ); + assert_eq!( + mismatches, 0, + "a caller passed a receiver other than the cell's\nstderr:\n{}", + run.stderr + ); +} + +/// Every symbol the module hands to a closure allocator or registers as a +/// closure body is DEFINED with the JS body ABI. Sabotage: a wrapper family +/// (e.g. `__perry_wrap_`) defined without `%js_this` -> named here. +#[test] +fn every_installed_body_is_defined_with_the_js_body_abi() { + let run = compile_and_run_witnessed(ROUTES); + assert_eq!(run.stdout, ROUTES_NODE, "stderr:\n{}", run.stderr); + let ir = read_ir(run.ll_dir.path()); + let installed = installed_bodies(&ir); + assert!( + installed.len() >= 10, + "found only {} installed bodies in the IR: {installed:?}", + installed.len() + ); + let mut checked = 0; + let mut wrong = Vec::new(); + for name in &installed { + let Some(params) = defined_params(&ir, name) else { + continue; // defined in another module or the runtime + }; + checked += 1; + if !params.starts_with("i64 %this_closure, i64 %js_this") { + wrong.push(format!("@{name}({params})")); + } + } + assert!( + checked >= 10, + "only {checked} installed bodies are defined here" + ); + assert!( + wrong.is_empty(), + "bodies installed as function objects without the JS body ABI:\n{}", + wrong.join("\n") + ); + // Every wrapper family this fixture exercises is among them. + for family in ["@perry_closure_", "@__perry_wrap_"] { + assert!( + installed + .iter() + .any(|n| format!("@{n}").starts_with(family)), + "no {family}* body installed: {installed:?}" + ); + } +} + +fn read_ir(dir: &Path) -> String { + let mut ir = String::new(); + for entry in std::fs::read_dir(dir).expect("ll dir") { + let path = entry.expect("ll entry").path(); + if path.extension().is_some_and(|e| e == "ll") { + ir.push_str(&std::fs::read_to_string(&path).expect("read ll")); + ir.push('\n'); + } + } + assert!(!ir.is_empty(), "PERRY_SAVE_LL wrote no IR"); + ir +} + +/// Symbols passed as `ptr @X` to `js_closure_alloc*` / `js_register_closure_*`. +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); + } + } + } + } + out +} + +/// The parameter list of `define ... @name(...)`, if defined in `ir`. +fn defined_params(ir: &str, name: &str) -> Option { + let quoted = format!("@\"{name}\"("); + let plain = format!("@{name}("); + ir.lines() + .filter(|l| l.starts_with("define ")) + .find_map(|l| { + let at = l + .find(&plain) + .map(|i| i + plain.len()) + .or_else(|| l.find("ed).map(|i| i + quoted.len()))?; + let rest = &l[at..]; + Some(rest[..rest.find(')')?].to_string()) + }) +} diff --git a/scripts/check_js_body_call_funnel.py b/scripts/check_js_body_call_funnel.py index 57a700d4b0..4626a3588d 100755 --- a/scripts/check_js_body_call_funnel.py +++ b/scripts/check_js_body_call_funnel.py @@ -14,6 +14,20 @@ A transmute FROM a body type to an erased pointer (installing a native body) is not a call and is not this gate's business. + +The receiver is a parameter of every JS body (`perry_abi::JS_BODY_THIS_PARAM`, +this-as-a-parameter stage 1), and a native body is installed through an +erased `*const u8`, so nothing the compiler checks connects a native's +signature to the ABI it is called with. Two definition rules close that: + +* every `extern "C" fn` DEFINITION shaped like a JS body — its first + parameter a closure header, every other parameter `f64`, returning `f64` — + 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_*`, perry-ffi's `alloc_closure` / + `register_closure_arity`) declares the receiver. """ from __future__ import annotations @@ -93,6 +107,104 @@ def transmute_targets(text: str): NOT_A_BODY = "NOT-A-JS-BODY:" +# Rust definitions (never a pointer type: those have no name). +FN_HEAD_RE = re.compile(r'extern\s+"C(?:-unwind)?"\s+fn\s+(\$?\w+)\s*(?:<[^>]*>)?\s*\(') +RET_F64_BODY_RE = re.compile(r"\s*->\s*f64\s*(?:where[^{;]*)?\{") + + +def fn_defs(text: str): + """Yield (name, params_text, start) for every `extern "C" fn` DEFINITION + returning `f64`; the parameter list may nest parentheses (a macro's + `$($a: f64),*`).""" + for m in FN_HEAD_RE.finditer(text): + depth, i = 1, m.end() + while i < len(text) and depth: + depth += {"(": 1, ")": -1}.get(text[i], 0) + i += 1 + if depth == 0 and RET_F64_BODY_RE.match(text, i): + yield m.group(1), text[m.end() : i - 1], m.start() + + +CLOSURE_PARAM_RE = re.compile( + r"^\*(?:const|mut)\s+(?:[\w:]*::)?(?:ClosureHeader|RawClosureHeader)$|^ClosurePtr$" +) +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", + re.S, +) + + +def call_entries(root: Path) -> set[str]: + """`perry_abi::JS_CALL_ENTRIES`: runtime entry points that take a closure + as an ordinary argument (and read the receiver from the cell).""" + text = (root / "crates/perry-abi/src/lib.rs").read_text(encoding="utf-8") + m = re.search(r"pub const JS_CALL_ENTRIES:[^=]*=\s*\[(.*?)\];", text, re.S) + if not m: + raise SystemExit("check_js_body_call_funnel: perry_abi::JS_CALL_ENTRIES not found") + return set(re.findall(r'"(\w+)"', m.group(1))) + + +def param_types(params: str) -> list[str]: + """Types of a comma-separated parameter list (macro repetitions such as + `$($a: f64),*` count as `f64`).""" + params = re.sub(r"\$\(|\)\s*,?\s*[*+]", ",", params) + out = [] + for p in (x.strip() for x in params.split(",")): + if not p: + continue + out.append(p.split(":", 1)[-1].strip() if ":" in p else p) + return out + + +def definition_violations(root: Path): + entries = call_entries(root) + bases = list(RUNTIME_ROOTS) + sorted( + p.relative_to(root).as_posix() for p in root.glob("crates/perry-ext-*/src") + ) + ["crates/perry-ffi/src"] + defs: dict[str, list[tuple[str, int, list[str]]]] = {} + texts = [] + out = [] + for base in bases: + if not (root / base).exists(): + continue + for path in sorted((root / base).rglob("*.rs")): + rel = path.relative_to(root).as_posix() + text = path.read_text(encoding="utf-8") + lines = text.split("\n") + texts.append((rel, text)) + for name, params, start in fn_defs(text): + types = param_types(params) + line = text.count("\n", 0, start) + 1 + defs.setdefault(name, []).append((rel, line, types)) + if not types or not CLOSURE_PARAM_RE.match(types[0]): + continue + if len(types) > 1 and JS_THIS_RE.match(types[1]): + continue + if not all(t == "f64" for t in types[1:]): + continue + if name in entries or exempt(lines, line): + continue + out.append( + f"{rel}:{line}: JS body `{name}` does not declare the receiver " + "(`_this: JsThis` after the closure; perry_abi::JS_BODY_THIS_PARAM)" + ) + 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_*)" + ) + return out + def exempt(lines, line: int) -> bool: """A native (non-JS) callback with a body-shaped type — a Rust helper @@ -157,18 +269,43 @@ def self_test() -> int: "rt_funnel_ok": (RUNTIME_FUNNEL, 'fn f(p: *const u8) { let g: extern "C" fn(f64) -> f64 = unsafe { std::mem::transmute(p) }; }', False), "cg_indirect": ("crates/perry-codegen/src/lower_call/x.rs", "fn f() { blk.call_indirect(DOUBLE, &p, &a); }", True), + "def_no_this": ("crates/perry-runtime/src/x.rs", + 'extern "C" fn body(_c: *const ClosureHeader, a: f64) -> f64 { a }', True), + "def_with_this": ("crates/perry-runtime/src/x.rs", + 'extern "C" fn body(_c: *const ClosureHeader, _this: crate::closure::JsThis, a: f64) -> f64 { a }', False), + "def_macro_no_this": ("crates/perry-runtime/src/x.rs", + 'macro_rules! m { ($n:ident) => { extern "C" fn $n(_c: *const ClosureHeader, $($a: f64),*) -> f64 { 0.0 } }; }', True), + "def_entry_ok": ("crates/perry-runtime/src/x.rs", + 'pub extern "C" fn js_closure_call2(c: *const ClosureHeader, a: f64, b: f64) -> f64 { a }', False), + "def_exempt_ok": ("crates/perry-runtime/src/x.rs", + '// NOT-A-JS-BODY: a helper taking a closure argument.\nextern "C" fn helper(c: *const ClosureHeader, a: f64) -> f64 { a }', False), + "def_macro_rep_no_this": ("crates/perry-runtime/src/x.rs", + 'macro_rules! m { ($n:ident) => { extern "C" fn $n(c: *const ClosureHeader $(, $a: f64)*) -> f64 { 0.0 } }; }', True), + "def_macro_rep_ok": ("crates/perry-runtime/src/x.rs", + '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), "cg_funnel_ok": ("crates/perry-codegen/src/expr/body_call.rs", "fn f() { blk.call_indirect(DOUBLE, &p, &a); }", False), } failed = 0 for name, (rel, text, expect_red) in cases.items(): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) - for d in (*RUNTIME_ROOTS, CODEGEN_ROOT): + for d in (*RUNTIME_ROOTS, CODEGEN_ROOT, "crates/perry-abi/src"): (root / d).mkdir(parents=True, exist_ok=True) + (root / "crates/perry-abi/src/lib.rs").write_text( + 'pub const JS_CALL_ENTRIES: [&str; 1] = ["js_closure_call2"];', encoding="utf-8" + ) p = root / rel p.parent.mkdir(parents=True, exist_ok=True) p.write_text(text, encoding="utf-8") - red = bool(runtime_violations(root) or codegen_violations(root)) + red = bool( + runtime_violations(root) or codegen_violations(root) or definition_violations(root) + ) if red != expect_red: print(f"self-test {name}: expected {'red' if expect_red else 'green'}, got {'red' if red else 'green'}") failed += 1 @@ -184,13 +321,17 @@ def main() -> int: args = ap.parse_args() if args.self_test: return self_test() - violations = runtime_violations(REPO) + codegen_violations(REPO) + violations = runtime_violations(REPO) + codegen_violations(REPO) + definition_violations(REPO) if violations: print("\n".join(violations)) - print(f"\n{len(violations)} JS body call(s) outside the funnels " - "(runtime: closure/body_call.rs macros; codegen: expr::body_call::emit_js_body_call)") + print(f"\n{len(violations)} violation(s): JS body calls outside the funnels " + "(runtime: closure/body_call.rs macros; codegen: expr::body_call::emit_js_body_call) " + "or JS body definitions without the receiver") return 1 - print("check_js_body_call_funnel: every JS body call goes through its funnel") + print( + "check_js_body_call_funnel: every JS body call goes through its funnel " + "and every JS body declares the receiver" + ) return 0 diff --git a/scripts/gc_root_dominance_check.py b/scripts/gc_root_dominance_check.py index 123305dda7..c8dbd1b43f 100755 --- a/scripts/gc_root_dominance_check.py +++ b/scripts/gc_root_dominance_check.py @@ -569,6 +569,9 @@ def build_cfg(f): "js_closure_unbox_callee_checked", # object/this_binding.rs:160 -- a thread-local cell swap "js_implicit_this_set", "js_implicit_this_get", + # object/this_binding.rs -- the stage-1 this-parameter witness (witness + # builds only): compares its argument with that same cell, two atomics. + "js_this_param_witness", # `js_gc_note_slot_layout` (gc/layout.rs:814) and its `_aware` sibling # (:833). `_aware` is the same body behind an early return taken when # neither the new nor the old bits are pointer-bearing, so it does strictly From d443a47a2594df4d86fb096ba7d41b8810315c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 08:07:43 +0000 Subject: [PATCH 03/17] perry-ffi 0.6: one typed JS body ABI, receiver-taking call entries (this-as-a-parameter stage 1b) The JS body type (`JsThis`, `js_body_fn_ty!`, `JsBody0`..`JsBody16`) is defined once in perry-abi and shared by perry-runtime and perry-ffi 0.6.0. perry-ffi's `alloc_closure` / `register_closure_arity` / new `register_closure_rest` take a typed body instead of `*const u8`, so a body with the wrong signature does not compile (compile_fail doctests). Every call entry takes the receiver after the function: js_closure_call{0..16}(closure, this, ..), js_native_call_value(func, this, args, len), js_closure_call_array(closure, this, args, len) and js_closure_call_apply_with_spread(box, this, ..). js_closure_call1_receiverless is gone; V8's receiverless trampoline gets its own adapter, js_closure_v8_callback(env, args, len). `DirectCallN::call` takes the receiver. The ext/UI/audio crates pass receivers instead of saving and restoring the implicit-`this` cell themselves. Stage-1 semantics are unchanged: bodies still read the implicit-`this` cell. An entry handed a receiver other than the cell's value binds the cell to it for the call (an ImplicitThisScope, which roots the displaced value), except for an arrow callee; a receiver equal to the cell binds nothing. Runtime and stdlib plain calls pass `plain_call_receiver()`, which in stage 1 is the cell's current value, and codegen's plain closure calls pass the cell bits, so plain calls keep running with whatever the cell holds. check_js_body_call_funnel.py now also refuses an entry declaration without the receiver, a closure registrar declared outside runtime/stdlib/perry-ffi, and a perry-ffi registration function taking `*const u8`. --- Cargo.lock | 3 +- Cargo.toml | 4 +- changelog.d/perry-ffi-0.6-typed-js-bodies.md | 33 ++ crates/perry-abi/src/lib.rs | 143 +++++++- crates/perry-audio-miniaudio/src/lib.rs | 6 +- crates/perry-codegen/src/expr/call_spread.rs | 3 + crates/perry-codegen/src/expr/js_runtime.rs | 6 +- .../src/lower_call/console_promise.rs | 19 +- .../src/lower_call/method_override.rs | 3 + crates/perry-codegen/src/lower_call/mod.rs | 46 ++- .../src/lower_call/pipeline_call_tests.rs | 9 +- .../property_get/dynamic_dispatch.rs | 6 + .../property_get/static_dispatch.rs | 3 + .../runtime_decls/stdlib_ffi/language_core.rs | 8 +- .../src/runtime_decls/strings.rs | 7 +- crates/perry-ext-events/src/dispatch_ext.rs | 2 +- crates/perry-ext-events/src/lib.rs | 84 +++-- crates/perry-ext-events/src/max_listeners.rs | 10 +- .../perry-ext-events/src/module_iterators.rs | 16 +- crates/perry-ext-events/src/module_on.rs | 11 +- crates/perry-ext-events/src/tests.rs | 2 +- crates/perry-ext-http/src/agent.rs | 5 +- crates/perry-ext-http/src/client_abort.rs | 2 +- crates/perry-ext-http/src/client_events.rs | 41 ++- .../src/client_request_surface.rs | 22 +- crates/perry-ext-http/src/lib.rs | 11 +- .../perry-ext-http/src/server/bun_server.rs | 8 +- .../perry-ext-http/src/server/http2_server.rs | 8 +- .../src/server/http2_server/pump.rs | 13 +- .../perry-ext-http/src/server/https_server.rs | 15 +- crates/perry-ext-http/src/server/request.rs | 36 +- crates/perry-ext-http/src/server/response.rs | 4 +- .../perry-ext-http/src/server/response_end.rs | 2 +- crates/perry-ext-http/src/server/server.rs | 19 +- .../src/server/server/deferred_events.rs | 17 +- .../src/server/server/upgrade_tests.rs | 4 +- crates/perry-ext-http/src/server/upgrade.rs | 7 +- crates/perry-ext-http/src/tls_client.rs | 2 +- crates/perry-ext-net/src/bun_tcp.rs | 25 +- crates/perry-ext-net/src/dispatch.rs | 6 +- crates/perry-ext-net/src/lifecycle.rs | 7 +- crates/perry-ext-net/src/pipe.rs | 54 ++- crates/perry-ext-net/src/socket_emit.rs | 23 +- crates/perry-ext-net/src/socket_events.rs | 196 +++++------ crates/perry-ext-parcel-watcher/src/lib.rs | 8 +- crates/perry-ext-streams/src/lib.rs | 20 +- crates/perry-ext-typescript/src/bun.rs | 18 +- crates/perry-ext-ws/src/lib.rs | 47 ++- crates/perry-ext-ws/src/server.rs | 6 +- crates/perry-ext-zlib/src/lib.rs | 8 +- crates/perry-ext-zlib/src/stream.rs | 30 +- crates/perry-ffi/Cargo.toml | 7 +- crates/perry-ffi/src/closure.rs | 222 +++++++++---- crates/perry-ffi/src/lib.rs | 6 +- crates/perry-runtime/src/arkts_callbacks.rs | 4 +- crates/perry-runtime/src/array/flat_clone.rs | 3 +- crates/perry-runtime/src/array/generic.rs | 11 + .../perry-runtime/src/array/iter_methods.rs | 107 +++++- crates/perry-runtime/src/array/iter_object.rs | 5 +- crates/perry-runtime/src/array/iterator.rs | 67 +++- .../perry-runtime/src/array/reduce_right.rs | 1 + crates/perry-runtime/src/array/sort.rs | 4 +- crates/perry-runtime/src/array/tests.rs | 1 + crates/perry-runtime/src/async_hooks.rs | 167 +++------- .../src/async_hooks/context_snapshots.rs | 129 ++++++++ crates/perry-runtime/src/buffer/mod.rs | 9 +- crates/perry-runtime/src/builtins/console.rs | 9 +- .../perry-runtime/src/builtins/formatting.rs | 4 +- crates/perry-runtime/src/builtins/globals.rs | 76 ++++- crates/perry-runtime/src/bun_compat/plugin.rs | 25 +- .../src/bun_compat/self_global.rs | 6 +- crates/perry-runtime/src/bun_compat/spawn.rs | 20 +- crates/perry-runtime/src/bun_ffi/callback.rs | 1 + .../src/child_process/emitter.rs | 39 ++- .../src/child_process/failed_spawn.rs | 2 +- .../src/child_process/reactor.rs | 16 +- .../src/child_process/reactor/stdin_drain.rs | 8 +- crates/perry-runtime/src/closure/body_call.rs | 88 ++--- crates/perry-runtime/src/closure/dispatch.rs | 6 +- .../src/closure/dispatch/bound.rs | 7 +- .../src/closure/dispatch/calln.rs | 307 +++++++++--------- .../src/closure/dispatch/direct.rs | 39 ++- .../src/closure/dispatch/value_call.rs | 201 +++++++++--- .../src/closure/dynamic_props.rs | 12 +- crates/perry-runtime/src/closure/mod.rs | 12 +- .../src/closure/receiver_param_tests.rs | 44 ++- crates/perry-runtime/src/closure/registry.rs | 9 +- crates/perry-runtime/src/closure/shape.rs | 7 +- crates/perry-runtime/src/closure/tests.rs | 2 +- crates/perry-runtime/src/closure/wide_call.rs | 2 +- crates/perry-runtime/src/cluster.rs | 47 ++- crates/perry-runtime/src/collection_iter.rs | 7 +- crates/perry-runtime/src/dgram.rs | 10 +- crates/perry-runtime/src/disposable.rs | 13 +- crates/perry-runtime/src/dyn_eval/bench.rs | 9 +- crates/perry-runtime/src/dyn_eval/bridge.rs | 9 +- .../src/dyn_eval/registry_lifetime_tests.rs | 9 +- crates/perry-runtime/src/dyn_eval/tests.rs | 9 +- crates/perry-runtime/src/error.rs | 7 +- .../perry-runtime/src/error_subclass_stack.rs | 7 +- crates/perry-runtime/src/event_target.rs | 13 +- crates/perry-runtime/src/frame.rs | 7 +- crates/perry-runtime/src/fs/cp.rs | 7 +- .../src/fs/dir_glob_watch/glob.rs | 1 + .../src/fs/dir_glob_watch/watch.rs | 22 +- crates/perry-runtime/src/fs/fd_ops.rs | 32 +- crates/perry-runtime/src/fs/stream.rs | 16 +- .../src/fs/stream/utf8_stream.rs | 14 +- .../src/fs/stream/write_file_input.rs | 9 +- .../src/gc/tests/runtime_roots.rs | 5 +- .../runtime_roots/call_argument_lists.rs | 7 +- .../tests/runtime_roots/callback_scanners.rs | 8 +- .../runtime_roots/hook_dispatch_handles.rs | 10 +- .../gc/tests/runtime_roots/perex_dispatch.rs | 7 +- .../gc/tests/runtime_roots/perex_match_all.rs | 7 +- .../src/gc/tests/runtime_roots/perex_split.rs | 4 +- .../runtime_roots/thenable_assimilation.rs | 1 + .../perry-runtime/src/geisterhand_registry.rs | 10 +- crates/perry-runtime/src/intl/subclass.rs | 7 +- crates/perry-runtime/src/iterator_helpers.rs | 50 ++- .../src/iterator_helpers/tests.rs | 7 +- crates/perry-runtime/src/json/replacer.rs | 7 +- crates/perry-runtime/src/json/reviver.rs | 9 +- crates/perry-runtime/src/json/stringify.rs | 14 +- .../src/json/stringify_scalars.rs | 7 +- crates/perry-runtime/src/jsx.rs | 11 +- crates/perry-runtime/src/map.rs | 7 +- crates/perry-runtime/src/media_playback.rs | 15 +- crates/perry-runtime/src/messaging.rs | 1 + crates/perry-runtime/src/module_require.rs | 8 +- .../src/node_api_host/functions.rs | 1 + crates/perry-runtime/src/node_inspector.rs | 10 +- crates/perry-runtime/src/node_repl.rs | 10 +- crates/perry-runtime/src/node_stream.rs | 28 +- .../src/node_stream/async_iterator.rs | 11 +- .../async_iterator/foreign_eof_tests.rs | 5 +- .../node_stream_constructors/web_adapter.rs | 9 +- .../src/node_stream_destroy_state.rs | 7 +- .../perry-runtime/src/node_stream_dispatch.rs | 7 +- .../src/node_stream_event_emitter.rs | 17 +- .../src/node_stream_iter_helpers.rs | 20 +- .../perry-runtime/src/node_stream_pipeline.rs | 7 +- .../src/node_stream_readwrite.rs | 29 +- crates/perry-runtime/src/node_stream_tests.rs | 177 ++++++++-- .../src/node_stream_tests_extra.rs | 296 +++++++++++++++-- .../src/node_submodules/consumers.rs | 11 +- .../src/node_submodules/diagnostics.rs | 38 ++- .../src/node_submodules/diagnostics_tail.rs | 4 +- .../src/node_submodules/fs_promises.rs | 17 +- .../src/node_submodules/stream_promises.rs | 9 +- .../perry-runtime/src/node_submodules/test.rs | 39 ++- .../node_submodules/test_once_unit_tests.rs | 15 +- .../src/node_submodules/test_property.rs | 14 +- .../src/node_submodules/test_reporters.rs | 5 +- .../src/node_submodules/test_runner.rs | 39 ++- .../src/node_submodules/test_snapshot.rs | 6 +- .../src/node_submodules/tests.rs | 50 +-- .../src/node_submodules/timers.rs | 2 +- .../src/node_submodules/trace_events.rs | 1 + crates/perry-runtime/src/node_vm/modules.rs | 14 +- crates/perry-runtime/src/object/arguments.rs | 15 +- crates/perry-runtime/src/object/assert.rs | 16 +- .../src/object/async_generator_queue.rs | 2 +- .../src/object/bound_method_receiver_tests.rs | 7 +- .../src/object/class_constructors.rs | 14 +- .../src/object/class_registry/construct.rs | 26 +- .../object/class_registry/parent_static.rs | 35 +- .../parent_static/static_accessor_call.rs | 2 +- .../collection_proto_thunks/subclass_tests.rs | 78 ++++- .../src/object/date_proto_thunks.rs | 4 +- .../src/object/descriptor_state.rs | 2 +- .../src/object/field_get_set/accessors.rs | 8 +- .../src/object/field_get_set/field_ops.rs | 6 +- .../src/object/field_set_by_name/tail.rs | 12 +- .../src/object/global_this/array_error.rs | 16 +- .../src/object/global_this/bigint_promise.rs | 16 +- .../src/object/global_this/builtin_thunks.rs | 11 +- .../src/object/global_this/fetch_globals.rs | 26 +- .../src/object/global_this/generator.rs | 6 +- .../src/object/global_this_webassembly.rs | 24 +- crates/perry-runtime/src/object/groupby.rs | 7 +- .../src/object/handle_expando.rs | 5 +- crates/perry-runtime/src/object/instanceof.rs | 9 +- .../src/object/instanceof/proxy_rhs.rs | 7 +- .../src/object/iterator_prototypes.rs | 14 +- .../src/object/native_call_method.rs | 71 +++- .../native_call_method/collection_methods.rs | 1 + .../native_call_method/common_methods.rs | 8 +- .../src/object/native_call_method/disposal.rs | 7 +- .../native_call_method/function_shape.rs | 12 +- .../native_call_method/handle_methods.rs | 26 +- .../native_call_method/namespace_override.rs | 1 + .../object/native_call_method/object_proto.rs | 7 +- .../native_call_method/patched_proto.rs | 7 +- .../native_call_method/primitive_methods.rs | 40 ++- .../to_locale_string_tests.rs | 2 +- .../callable_exports/buffer_species.rs | 2 +- .../native_module/namespace_builders.rs | 8 +- .../src/object/native_module/web_locks.rs | 4 +- .../src/object/native_module_stream.rs | 21 +- .../perry-runtime/src/object/own_override.rs | 1 + .../perry-runtime/src/object/property_key.rs | 9 +- crates/perry-runtime/src/object/tests.rs | 26 +- .../src/object/typed_array_proto_thunks.rs | 2 +- crates/perry-runtime/src/object/util_types.rs | 2 +- .../src/object/view_brand_tests.rs | 2 +- .../src/os/os_process_emitter.rs | 22 +- .../perry-runtime/src/os_process_streams.rs | 30 +- .../perry-runtime/src/perf_hooks/timerify.rs | 7 +- crates/perry-runtime/src/plugin.rs | 4 +- .../perry-runtime/src/process/finalization.rs | 7 +- crates/perry-runtime/src/process/ipc.rs | 9 +- .../perry-runtime/src/process/node_module.rs | 12 +- .../perry-runtime/src/promise/assimilate.rs | 8 +- .../perry-runtime/src/promise/async_step.rs | 11 +- .../src/promise/checked_dispatch.rs | 16 +- .../perry-runtime/src/promise/combinators.rs | 266 +-------------- .../src/promise/combinators_tests.rs | 265 +++++++++++++++ .../perry-runtime/src/promise/microtasks.rs | 15 +- .../src/promise/spec_combinators.rs | 12 +- crates/perry-runtime/src/promise/subclass.rs | 9 +- crates/perry-runtime/src/promise/then.rs | 65 +++- crates/perry-runtime/src/proxy.rs | 55 +++- .../src/proxy/apply_construct.rs | 9 +- crates/perry-runtime/src/proxy/json.rs | 6 +- crates/perry-runtime/src/proxy/reflect.rs | 3 +- crates/perry-runtime/src/pty/mod.rs | 9 +- crates/perry-runtime/src/pty/tests.rs | 14 +- .../perry-runtime/src/regex/perex_dispatch.rs | 7 +- .../src/regex/perex_replace_storage.rs | 2 + crates/perry-runtime/src/regex/replace_fn.rs | 7 +- crates/perry-runtime/src/regex/site_test.rs | 18 +- crates/perry-runtime/src/set.rs | 7 +- crates/perry-runtime/src/symbol/accessors.rs | 8 +- crates/perry-runtime/src/symbol/iterator.rs | 14 +- crates/perry-runtime/src/thread.rs | 18 +- crates/perry-runtime/src/timer.rs | 14 +- crates/perry-runtime/src/tls.rs | 7 +- crates/perry-runtime/src/tty.rs | 2 +- crates/perry-runtime/src/tui/hooks.rs | 10 +- crates/perry-runtime/src/tui/input.rs | 2 +- crates/perry-runtime/src/tui/run.rs | 2 +- .../perry-runtime/src/typed_feedback/tests.rs | 9 +- .../perry-runtime/src/typedarray/construct.rs | 8 +- .../perry-runtime/src/typedarray/iterate.rs | 74 ++++- .../perry-runtime/src/typedarray/species.rs | 1 + .../perry-runtime/src/typedarray/transform.rs | 25 +- crates/perry-runtime/src/typedarray_props.rs | 11 +- crates/perry-runtime/src/url/abort.rs | 2 +- crates/perry-runtime/src/url/search_params.rs | 7 +- crates/perry-runtime/src/util_debuglog.rs | 12 +- crates/perry-runtime/src/util_promisify.rs | 45 ++- crates/perry-runtime/src/v8.rs | 10 +- .../perry-runtime/src/value/dynamic_array.rs | 12 +- .../src/value/to_string_array.rs | 16 +- .../src/value/to_string_class_ref.rs | 7 +- .../src/value/to_string_primitive.rs | 22 +- crates/perry-runtime/src/wasi.rs | 16 +- crates/perry-runtime/src/webassembly.rs | 8 +- crates/perry-runtime/src/webassembly_calls.rs | 1 + crates/perry-runtime/src/yoga.rs | 9 +- .../perry-stdlib/src/async_local_storage.rs | 14 +- crates/perry-stdlib/src/bun_sql.rs | 46 ++- .../src/common/net_method_values.rs | 1 + .../perry-stdlib/src/crypto/hash_handles.rs | 11 +- crates/perry-stdlib/src/crypto/keys.rs | 1 + crates/perry-stdlib/src/domain.rs | 8 +- crates/perry-stdlib/src/events.rs | 17 +- crates/perry-stdlib/src/events/warnings.rs | 1 + .../perry-stdlib/src/fetch/body_metadata.rs | 1 + crates/perry-stdlib/src/fetch/headers.rs | 7 +- crates/perry-stdlib/src/querystring.rs | 2 +- crates/perry-stdlib/src/readline/mod.rs | 39 ++- crates/perry-stdlib/src/readline/pump.rs | 32 +- crates/perry-stdlib/src/sqlite/backup.rs | 1 + crates/perry-stdlib/src/sqlite/better.rs | 6 +- crates/perry-stdlib/src/sqlite/bind.rs | 1 + crates/perry-stdlib/src/sqlite/bun.rs | 1 + .../src/sqlite/node_stmt_session.rs | 7 +- crates/perry-stdlib/src/streams.rs | 40 ++- crates/perry-stdlib/src/streams/pipe.rs | 2 +- crates/perry-stdlib/src/streams/tee.rs | 9 +- crates/perry-stdlib/src/tls.rs | 2 + crates/perry-stdlib/src/tls/event_pump.rs | 39 ++- crates/perry-stdlib/src/worker_threads.rs | 8 +- .../src/worker_threads/worker_pump.rs | 34 +- .../src/worker_threads/worker_surface.rs | 8 +- crates/perry-stdlib/src/zlib.rs | 32 +- crates/perry-ui-android/src/app.rs | 6 +- crates/perry-ui-android/src/callback.rs | 32 +- crates/perry-ui-android/src/dialog.rs | 4 +- crates/perry-ui-android/src/drag_drop.rs | 8 +- crates/perry-ui-android/src/media_playback.rs | 17 +- crates/perry-ui-android/src/pointer.rs | 4 +- crates/perry-ui-android/src/state.rs | 7 +- .../src/widgets/lazyvstack.rs | 8 +- crates/perry-ui-android/src/widgets/table.rs | 3 +- .../perry-ui-android/src/widgets/webview.rs | 19 +- crates/perry-ui-gtk4/src/app.rs | 10 +- crates/perry-ui-gtk4/src/audio.rs | 9 +- crates/perry-ui-gtk4/src/camera.rs | 9 +- crates/perry-ui-gtk4/src/dialog.rs | 12 +- crates/perry-ui-gtk4/src/drag_drop.rs | 8 +- crates/perry-ui-gtk4/src/file_dialog.rs | 10 +- crates/perry-ui-gtk4/src/issue_552_stub.rs | 8 +- crates/perry-ui-gtk4/src/location.rs | 6 +- crates/perry-ui-gtk4/src/media_playback.rs | 17 +- crates/perry-ui-gtk4/src/menu.rs | 9 +- crates/perry-ui-gtk4/src/network_stub.rs | 9 +- crates/perry-ui-gtk4/src/state.rs | 7 +- crates/perry-ui-gtk4/src/toolbar.rs | 4 +- crates/perry-ui-gtk4/src/tray.rs | 4 +- .../perry-ui-gtk4/src/widgets/bottom_nav.rs | 4 +- crates/perry-ui-gtk4/src/widgets/button.rs | 4 +- crates/perry-ui-gtk4/src/widgets/calendar.rs | 4 +- crates/perry-ui-gtk4/src/widgets/combobox.rs | 4 +- .../src/widgets/command_palette.rs | 4 +- .../perry-ui-gtk4/src/widgets/date_picker.rs | 4 +- .../src/widgets/image_gallery.rs | 4 +- .../perry-ui-gtk4/src/widgets/lazyvstack.rs | 9 +- crates/perry-ui-gtk4/src/widgets/mod.rs | 26 +- crates/perry-ui-gtk4/src/widgets/picker.rs | 8 +- crates/perry-ui-gtk4/src/widgets/rich_text.rs | 4 +- .../perry-ui-gtk4/src/widgets/scrollview.rs | 4 +- .../perry-ui-gtk4/src/widgets/securefield.rs | 8 +- crates/perry-ui-gtk4/src/widgets/slider.rs | 8 +- crates/perry-ui-gtk4/src/widgets/textarea.rs | 8 +- crates/perry-ui-gtk4/src/widgets/textfield.rs | 18 +- crates/perry-ui-gtk4/src/widgets/toggle.rs | 8 +- crates/perry-ui-gtk4/src/widgets/tree_view.rs | 4 +- crates/perry-ui-gtk4/src/widgets/webview.rs | 13 +- crates/perry-ui-gtk4/src/window.rs | 4 +- crates/perry-ui-ios/src/adaptive_layout.rs | 8 +- crates/perry-ui-ios/src/app.rs | 4 +- crates/perry-ui-ios/src/audio_playback.rs | 8 +- crates/perry-ui-ios/src/background.rs | 4 +- crates/perry-ui-ios/src/camera.rs | 11 +- crates/perry-ui-ios/src/deeplinks.rs | 4 +- crates/perry-ui-ios/src/drag_drop.rs | 8 +- crates/perry-ui-ios/src/file_dialog.rs | 8 +- crates/perry-ui-ios/src/geolocation.rs | 24 +- crates/perry-ui-ios/src/image_picker.rs | 4 +- crates/perry-ui-ios/src/location.rs | 9 +- crates/perry-ui-ios/src/media_playback.rs | 17 +- crates/perry-ui-ios/src/menu.rs | 4 +- crates/perry-ui-ios/src/network.rs | 4 +- crates/perry-ui-ios/src/notifications.rs | 12 +- crates/perry-ui-ios/src/pointer.rs | 4 +- crates/perry-ui-ios/src/state.rs | 7 +- crates/perry-ui-ios/src/widgets/alert.rs | 4 +- crates/perry-ui-ios/src/widgets/bottom_nav.rs | 4 +- crates/perry-ui-ios/src/widgets/button.rs | 4 +- crates/perry-ui-ios/src/widgets/calendar.rs | 4 +- crates/perry-ui-ios/src/widgets/combobox.rs | 4 +- .../perry-ui-ios/src/widgets/date_picker.rs | 4 +- .../perry-ui-ios/src/widgets/image_gallery.rs | 4 +- crates/perry-ui-ios/src/widgets/mod.rs | 2 +- crates/perry-ui-ios/src/widgets/picker.rs | 8 +- crates/perry-ui-ios/src/widgets/rich_text.rs | 4 +- crates/perry-ui-ios/src/widgets/scrollview.rs | 4 +- .../perry-ui-ios/src/widgets/securefield.rs | 4 +- crates/perry-ui-ios/src/widgets/slider.rs | 4 +- crates/perry-ui-ios/src/widgets/tabbar.rs | 8 +- crates/perry-ui-ios/src/widgets/textarea.rs | 4 +- crates/perry-ui-ios/src/widgets/textfield.rs | 6 +- crates/perry-ui-ios/src/widgets/toggle.rs | 4 +- crates/perry-ui-ios/src/widgets/tree_view.rs | 4 +- crates/perry-ui-ios/src/widgets/webview.rs | 17 +- .../perry-ui-ios/src/widgets/wheel_picker.rs | 4 +- crates/perry-ui-macos/src/app.rs | 28 +- crates/perry-ui-macos/src/audio_playback.rs | 8 +- crates/perry-ui-macos/src/background.rs | 4 +- crates/perry-ui-macos/src/deeplinks.rs | 4 +- crates/perry-ui-macos/src/drag_drop.rs | 8 +- crates/perry-ui-macos/src/file_dialog.rs | 44 ++- crates/perry-ui-macos/src/geolocation.rs | 24 +- crates/perry-ui-macos/src/image_picker.rs | 4 +- crates/perry-ui-macos/src/location.rs | 9 +- crates/perry-ui-macos/src/media_playback.rs | 17 +- crates/perry-ui-macos/src/menu.rs | 4 +- crates/perry-ui-macos/src/network.rs | 4 +- crates/perry-ui-macos/src/notifications.rs | 10 +- crates/perry-ui-macos/src/pointer.rs | 10 +- crates/perry-ui-macos/src/state.rs | 7 +- crates/perry-ui-macos/src/tray.rs | 4 +- crates/perry-ui-macos/src/widgets/alert.rs | 4 +- .../perry-ui-macos/src/widgets/bottom_nav.rs | 4 +- crates/perry-ui-macos/src/widgets/button.rs | 4 +- crates/perry-ui-macos/src/widgets/calendar.rs | 4 +- crates/perry-ui-macos/src/widgets/combobox.rs | 4 +- .../src/widgets/command_palette.rs | 4 +- .../perry-ui-macos/src/widgets/date_picker.rs | 4 +- .../src/widgets/foreach_registry.rs | 5 +- .../src/widgets/image_gallery.rs | 4 +- .../perry-ui-macos/src/widgets/lazyvstack.rs | 8 +- crates/perry-ui-macos/src/widgets/mod.rs | 4 +- crates/perry-ui-macos/src/widgets/picker.rs | 4 +- .../perry-ui-macos/src/widgets/rich_text.rs | 4 +- .../perry-ui-macos/src/widgets/scrollview.rs | 4 +- .../perry-ui-macos/src/widgets/securefield.rs | 4 +- crates/perry-ui-macos/src/widgets/slider.rs | 4 +- crates/perry-ui-macos/src/widgets/table.rs | 10 +- crates/perry-ui-macos/src/widgets/textarea.rs | 4 +- .../perry-ui-macos/src/widgets/textfield.rs | 8 +- crates/perry-ui-macos/src/widgets/toggle.rs | 4 +- crates/perry-ui-macos/src/widgets/toolbar.rs | 4 +- .../perry-ui-macos/src/widgets/tree_view.rs | 4 +- crates/perry-ui-macos/src/widgets/webview.rs | 21 +- crates/perry-ui-tvos/src/app.rs | 4 +- crates/perry-ui-tvos/src/audio_playback.rs | 8 +- crates/perry-ui-tvos/src/background.rs | 4 +- crates/perry-ui-tvos/src/file_dialog.rs | 8 +- crates/perry-ui-tvos/src/issue_552_stub.rs | 8 +- crates/perry-ui-tvos/src/location.rs | 9 +- crates/perry-ui-tvos/src/media_playback.rs | 17 +- crates/perry-ui-tvos/src/menu.rs | 4 +- crates/perry-ui-tvos/src/network_stub.rs | 9 +- crates/perry-ui-tvos/src/pointer.rs | 4 +- crates/perry-ui-tvos/src/state.rs | 7 +- crates/perry-ui-tvos/src/widgets/alert.rs | 4 +- .../perry-ui-tvos/src/widgets/bottom_nav.rs | 4 +- crates/perry-ui-tvos/src/widgets/button.rs | 4 +- crates/perry-ui-tvos/src/widgets/mod.rs | 2 +- .../perry-ui-tvos/src/widgets/scrollview.rs | 4 +- .../perry-ui-tvos/src/widgets/securefield.rs | 4 +- crates/perry-ui-tvos/src/widgets/slider.rs | 4 +- crates/perry-ui-tvos/src/widgets/tabbar.rs | 8 +- crates/perry-ui-tvos/src/widgets/textarea.rs | 4 +- crates/perry-ui-tvos/src/widgets/textfield.rs | 6 +- crates/perry-ui-tvos/src/widgets/toggle.rs | 4 +- crates/perry-ui-visionos/src/app.rs | 4 +- .../perry-ui-visionos/src/audio_playback.rs | 8 +- crates/perry-ui-visionos/src/background.rs | 4 +- crates/perry-ui-visionos/src/camera.rs | 11 +- crates/perry-ui-visionos/src/drag_drop.rs | 8 +- crates/perry-ui-visionos/src/file_dialog.rs | 8 +- .../perry-ui-visionos/src/issue_552_stub.rs | 8 +- crates/perry-ui-visionos/src/location.rs | 9 +- .../perry-ui-visionos/src/media_playback.rs | 17 +- crates/perry-ui-visionos/src/menu.rs | 4 +- crates/perry-ui-visionos/src/network_stub.rs | 9 +- crates/perry-ui-visionos/src/pointer.rs | 4 +- crates/perry-ui-visionos/src/state.rs | 7 +- crates/perry-ui-visionos/src/widgets/alert.rs | 4 +- .../src/widgets/bottom_nav.rs | 4 +- .../perry-ui-visionos/src/widgets/button.rs | 4 +- .../perry-ui-visionos/src/widgets/calendar.rs | 4 +- .../perry-ui-visionos/src/widgets/combobox.rs | 4 +- .../src/widgets/date_picker.rs | 4 +- crates/perry-ui-visionos/src/widgets/mod.rs | 2 +- .../src/widgets/rich_text.rs | 4 +- .../src/widgets/scrollview.rs | 4 +- .../src/widgets/securefield.rs | 4 +- .../perry-ui-visionos/src/widgets/slider.rs | 4 +- .../perry-ui-visionos/src/widgets/tabbar.rs | 8 +- .../perry-ui-visionos/src/widgets/textarea.rs | 4 +- .../src/widgets/textfield.rs | 6 +- .../src/widgets/tree_view.rs | 4 +- .../perry-ui-visionos/src/widgets/webview.rs | 17 +- .../src/widgets/wheel_picker.rs | 4 +- crates/perry-ui-watchos/src/audio_playback.rs | 8 +- crates/perry-ui-watchos/src/background.rs | 4 +- crates/perry-ui-watchos/src/issue_552_stub.rs | 8 +- crates/perry-ui-watchos/src/media_playback.rs | 17 +- crates/perry-ui-watchos/src/network_stub.rs | 9 +- crates/perry-ui-watchos/src/notifications.rs | 4 +- crates/perry-ui-watchos/src/state.rs | 7 +- crates/perry-ui-watchos/src/tree.rs | 24 +- crates/perry-ui-windows-winui/src/app.rs | 6 +- crates/perry-ui-windows-winui/src/widgets.rs | 10 +- crates/perry-ui-windows/src/app.rs | 12 +- crates/perry-ui-windows/src/dialog.rs | 20 +- crates/perry-ui-windows/src/drag_drop.rs | 8 +- crates/perry-ui-windows/src/file_dialog.rs | 8 +- crates/perry-ui-windows/src/folder_dialog.rs | 8 +- crates/perry-ui-windows/src/issue_552_stub.rs | 8 +- crates/perry-ui-windows/src/media_playback.rs | 17 +- crates/perry-ui-windows/src/menu.rs | 4 +- crates/perry-ui-windows/src/network_stub.rs | 9 +- crates/perry-ui-windows/src/pointer.rs | 14 +- crates/perry-ui-windows/src/state.rs | 7 +- crates/perry-ui-windows/src/toolbar.rs | 2 +- crates/perry-ui-windows/src/tray.rs | 4 +- .../src/widgets/bottom_nav.rs | 4 +- crates/perry-ui-windows/src/widgets/button.rs | 4 +- .../perry-ui-windows/src/widgets/calendar.rs | 4 +- .../perry-ui-windows/src/widgets/combobox.rs | 4 +- .../src/widgets/command_palette.rs | 4 +- .../src/widgets/date_picker.rs | 4 +- .../src/widgets/foreach_registry.rs | 5 +- .../src/widgets/image_gallery.rs | 4 +- .../src/widgets/lazyvstack.rs | 9 +- crates/perry-ui-windows/src/widgets/mod.rs | 2 +- crates/perry-ui-windows/src/widgets/picker.rs | 4 +- .../perry-ui-windows/src/widgets/rich_text.rs | 4 +- .../src/widgets/scrollview.rs | 4 +- .../src/widgets/securefield.rs | 4 +- crates/perry-ui-windows/src/widgets/slider.rs | 6 +- crates/perry-ui-windows/src/widgets/table.rs | 26 +- .../perry-ui-windows/src/widgets/textarea.rs | 4 +- .../perry-ui-windows/src/widgets/textfield.rs | 4 +- crates/perry-ui-windows/src/widgets/toggle.rs | 4 +- .../perry-ui-windows/src/widgets/tree_view.rs | 4 +- .../perry-ui-windows/src/widgets/webview.rs | 19 +- crates/perry-ui/src/key_dispatch.rs | 15 +- scripts/check_js_body_call_funnel.py | 118 ++++++- scripts/publish_perry_ffi.sh | 21 +- scripts/raw_handle_debt_files.txt | 3 +- scripts/unrooted_local_shape_baseline.json | 7 +- 509 files changed, 5723 insertions(+), 2421 deletions(-) create mode 100644 changelog.d/perry-ffi-0.6-typed-js-bodies.md create mode 100644 crates/perry-runtime/src/async_hooks/context_snapshots.rs create mode 100644 crates/perry-runtime/src/promise/combinators_tests.rs diff --git a/Cargo.lock b/Cargo.lock index a748b33e8a..34e8af7bba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5226,9 +5226,10 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1654" +version = "0.6.0" dependencies = [ "dashmap 6.2.1", + "perry-abi", "perry-native-registration", "perry-runtime", ] diff --git a/Cargo.toml b/Cargo.toml index e0e0fafe70..2bc256b457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -516,7 +516,7 @@ perry-hir = { path = "crates/perry-hir" } perry-transform = { path = "crates/perry-transform" } perry-codegen = { path = "crates/perry-codegen" } perry-dispatch = { path = "crates/perry-dispatch" } -perry-abi = { path = "crates/perry-abi" } +perry-abi = { path = "crates/perry-abi", version = "0.5.1654" } # #1112: `perry-ffi` is published for external native wrappers. Its optional # `runtime-link` edge supplies runtime symbols to in-tree adapter tests. # The dependency-free registration core sits below both crates so future @@ -532,7 +532,7 @@ perry-abi = { path = "crates/perry-abi" } # applies — tests and the shipped prebuilt keep every engine. This mirrors why # `wasm-host` must stay out of `default`, generalized to all heavy features. perry-runtime = { path = "crates/perry-runtime", version = "0.5.1011", default-features = false } -perry-ffi = { path = "crates/perry-ffi", version = "0.5.1011" } +perry-ffi = { path = "crates/perry-ffi", version = "0.6.0" } # turnloop P7 (docs/turnloop/p7-report.md): the loop-driven transport the four # database bindings share. rlib only — it is linked *into* each binding's # staticlib, never alongside it, so it exports no symbol of its own. diff --git a/changelog.d/perry-ffi-0.6-typed-js-bodies.md b/changelog.d/perry-ffi-0.6-typed-js-bodies.md new file mode 100644 index 0000000000..f8d45b6e0c --- /dev/null +++ b/changelog.d/perry-ffi-0.6-typed-js-bodies.md @@ -0,0 +1,33 @@ +**perry-ffi 0.6 (breaking): one JS body ABI, typed.** Every native body a +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`. +- 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, + this, &args)`. The runtime's entries match: `js_closure_call{N}(closure, + this, ...)`, `js_native_call_value(func, this, args, len)`, + `js_closure_call_array(closure, this, args, len)`. Addon code no longer + reads or writes the runtime's implicit-`this` cell (the ext crates' own + `js_implicit_this_set` save/restore pairs are gone). While bodies still + read that cell (this-as-a-parameter stage 1), an entry handed a receiver + other than the cell's binds the cell to it for the call; the runtime's own + plain calls pass the cell's value and bind nothing. +- perry-ffi now carries its own version (0.6.0) and depends on perry-abi, + 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 + `*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, + perry-audio-miniaudio) is updated. diff --git a/crates/perry-abi/src/lib.rs b/crates/perry-abi/src/lib.rs index b8bd1fbd33..1200931a79 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -39,24 +39,27 @@ pub const GC_HEADER_SIZE: usize = 8; /// The JS BODY calling convention. Every native body a function object runs — /// a compiled closure body, a value wrapper, a native builtin installed as a -/// function object — is +/// function object, a body an addon registers through perry-ffi — is /// /// ```text /// double body(i64 callee, i64 this, double a0, double a1, ...) /// ``` /// /// where `callee` is the function object (its captures follow the header) and -/// `this` is the NaN-boxed receiver bits, passed in an INTEGER register so -/// every floating-point argument register stays free for JS arguments (SysV -/// x86-64 / AAPCS64; Win64 assigns positionally, which is equally correct). -/// Passing more JS arguments than a body declares is safe (the caller owns -/// the stack argument area); fewer is padded with `undefined` by the caller. -/// The runtime calls bodies only through `closure/body_call.rs`; emitted code -/// only through `expr::body_call::emit_js_body_call`. +/// `this` is the NaN-boxed receiver bits ([`JsThis`]), passed in an INTEGER +/// register so every floating-point argument register stays free for JS +/// arguments (SysV x86-64 / AAPCS64; Win64 assigns positionally, which is +/// equally correct). Passing more JS arguments than a body declares is safe +/// (the caller owns the stack argument area); fewer is padded with +/// `undefined` by the caller. Its Rust type is [`js_body_fn_ty!`], defined +/// here and nowhere else. The runtime calls bodies only through +/// `closure/body_call.rs`; emitted code only through +/// `expr::body_call::emit_js_body_call`. /// /// While the implicit-`this` cell still exists (this-as-a-parameter stage 1), -/// a caller passes exactly the receiver the cell holds for the call, so the -/// parameter and the cell never disagree; bodies still read the cell. +/// bodies read the cell: every call entry passes its `this` argument as the +/// body's receiver parameter AND binds the cell to it for the call when the +/// cell holds anything else, so the parameter and the cell never disagree. pub const JS_BODY_CALLEE_PARAM: usize = 0; /// Native parameter index of the receiver (`this`) bits. pub const JS_BODY_THIS_PARAM: usize = 1; @@ -64,8 +67,111 @@ pub const JS_BODY_THIS_PARAM: usize = 1; pub const JS_BODY_FIRST_ARG_PARAM: usize = 2; /// Native parameters every JS body declares before its JS arguments. pub const JS_BODY_FIXED_PARAMS: usize = 2; -/// `js_closure_call{N}(callee, a0..aN-1)` exists for `N <= JS_CLOSURE_CALL_MAX_ARGS`; -/// wider calls use `js_closure_call_array`. + +/// NaN-boxed `undefined` (`value::TAG_UNDEFINED` in the runtime, which +/// asserts it equals this). +pub const TAG_UNDEFINED: u64 = 0x7FFC_0000_0000_0001; + +/// The receiver a JS body takes as its second native parameter +/// ([`JS_BODY_THIS_PARAM`]): the NaN-boxed `this` bits, in an integer +/// register (`repr(transparent)` over `u64`, so its ABI is exactly a `u64`'s). +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct JsThis(pub u64); + +impl JsThis { + /// `undefined`: the receiver of a plain (non-method) call. + pub const UNDEFINED: JsThis = JsThis(TAG_UNDEFINED); + + /// The receiver bits. + #[inline(always)] + pub const fn bits(self) -> u64 { + self.0 + } + + /// The receiver as a NaN-boxed value. + #[inline(always)] + pub fn as_f64(self) -> f64 { + f64::from_bits(self.0) + } + + /// A NaN-boxed value as a receiver. + #[inline(always)] + pub fn from_f64(value: f64) -> Self { + JsThis(value.to_bits()) + } +} + +/// THE Rust type of a JS body: `js_body_fn_ty!(Callee; a, b)` is +/// `unsafe extern "C" fn(*const Callee, JsThis, f64, f64) -> f64` — the +/// callee header type, then one `f64` per token. A safe `extern "C" fn` body +/// coerces to it. +#[macro_export] +macro_rules! js_body_fn_ty { + (@f64 $x:tt) => { f64 }; + ($callee:ty; $($x:tt),* $(,)?) => { + unsafe extern "C" fn( + *const $callee, + $crate::JsThis + $(, $crate::js_body_fn_ty!(@f64 $x))* + ) -> f64 + }; +} + +/// A JS body with a statically known JS arity: implemented for exactly the +/// [`js_body_fn_ty!`] pointer types (`JsBody0` .. `JsBody16`), 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. +/// +/// # Safety +/// Implemented only here, for the body pointer types; `code` is the body's +/// entry address. +pub unsafe trait JsBody: Copy { + /// The JS parameters the body declares. + const ARITY: u32; + /// The body's code address, for the runtime's registries. + fn code(self) -> *const u8; +} + +macro_rules! js_body_types { + ($($alias:ident = $n:literal [$($x:tt),*];)*) => {$( + #[doc = concat!("A JS body declaring ", stringify!($n), " JS parameters.")] + pub type $alias = js_body_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_types! { + JsBody0 = 0 []; + JsBody1 = 1 [a]; + JsBody2 = 2 [a, a]; + JsBody3 = 3 [a, a, a]; + JsBody4 = 4 [a, a, a, a]; + JsBody5 = 5 [a, a, a, a, a]; + JsBody6 = 6 [a, a, a, a, a, a]; + JsBody7 = 7 [a, a, a, a, a, a, a]; + JsBody8 = 8 [a, a, a, a, a, a, a, a]; + JsBody9 = 9 [a, a, a, a, a, a, a, a, a]; + JsBody10 = 10 [a, a, a, a, a, a, a, a, a, a]; + JsBody11 = 11 [a, a, a, a, a, a, a, a, a, a, a]; + JsBody12 = 12 [a, a, a, a, a, a, a, a, a, a, a, a]; + JsBody13 = 13 [a, a, a, a, a, a, a, a, a, a, a, a, a]; + 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]; +} + +/// `js_closure_call{N}(callee, this, a0..aN-1)` calls a function object with +/// receiver `this` ([`JsThis::UNDEFINED`] for a plain call); it exists for +/// `N <= JS_CLOSURE_CALL_MAX_ARGS`, and wider calls use +/// `js_closure_call_array(callee, this, args, len)`. pub const JS_CLOSURE_CALL_MAX_ARGS: usize = 16; /// The fixed-arity entries, indexed by JS argument count. pub const JS_CLOSURE_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 1] = [ @@ -87,10 +193,11 @@ pub const JS_CLOSURE_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 1] = [ "js_closure_call15", "js_closure_call16", ]; -/// Every runtime entry point emitted code calls to run a JS function. Each can -/// run arbitrary JS and therefore collect: `scripts/gc_root_dominance_check.py` -/// reads its poll-capable set from THIS list. -pub const JS_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 5] = [ +/// Every runtime entry point native code (emitted or Rust) calls to run a JS +/// function. Each takes the receiver after the function, can run arbitrary JS +/// and therefore collect: `scripts/gc_root_dominance_check.py` reads its +/// poll-capable set from THIS list. +pub const JS_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 1 + 4] = [ "js_closure_call0", "js_closure_call1", "js_closure_call2", @@ -108,10 +215,12 @@ pub const JS_CALL_ENTRIES: [&str; JS_CLOSURE_CALL_MAX_ARGS + 5] = [ "js_closure_call14", "js_closure_call15", "js_closure_call16", - "js_closure_call1_receiverless", "js_closure_call_array", "js_closure_call_apply_with_spread", "js_native_call_value", + // V8's callback trampoline contract (`func(env, args, len)`, no + // receiver): a plain call. + "js_closure_v8_callback", ]; /// `object::method_site::MethodEntry` — the words the emitted method-call site /// reads (`perry-codegen/src/expr/method_site.rs`). diff --git a/crates/perry-audio-miniaudio/src/lib.rs b/crates/perry-audio-miniaudio/src/lib.rs index 711c1dd834..ca30798ad5 100644 --- a/crates/perry-audio-miniaudio/src/lib.rs +++ b/crates/perry-audio-miniaudio/src/lib.rs @@ -107,7 +107,7 @@ extern "C" { // ============================================================================= extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_promise_run_microtasks() -> i32; fn js_run_stdlib_pump(); @@ -367,7 +367,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -396,7 +396,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-codegen/src/expr/call_spread.rs b/crates/perry-codegen/src/expr/call_spread.rs index ac9c85fac1..0550edde59 100644 --- a/crates/perry-codegen/src/expr/call_spread.rs +++ b/crates/perry-codegen/src/expr/call_spread.rs @@ -590,11 +590,14 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // Re-read below every allocation above: the slot is a mutable root // an evacuating cycle rewrites in place. let cb_box = callee_group.reread(ctx, cb_root)?; + // Stage 1: `f(...xs)` runs with the implicit-`this` cell's value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); let result = ctx.block().call( DOUBLE, "js_closure_call_apply_with_spread", &[ (DOUBLE, &cb_box), + (I64, &this_bits), (crate::types::PTR, ®s_ptr), (I64, ®s_len), (I64, &spread_handle), diff --git a/crates/perry-codegen/src/expr/js_runtime.rs b/crates/perry-codegen/src/expr/js_runtime.rs index b252a59b7c..4e2282e11f 100644 --- a/crates/perry-codegen/src/expr/js_runtime.rs +++ b/crates/perry-codegen/src/expr/js_runtime.rs @@ -253,7 +253,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // (`crates/perry-runtime/src/closure.rs`) which takes the i64 // closure pointer and dispatches to the right `js_closure_callN` // based on `args_len`. Codegen passes: - // func_ptr = ptrtoint @js_closure_call_array to i64 + // func_ptr = ptrtoint @js_closure_v8_callback to i64 // closure_env = unbox(closure) — raw *ClosureHeader as i64 // param_count = static usize from HIR // Result is a NaN-boxed JS handle (V8-handle tag 0x7FFB) that JS @@ -268,11 +268,11 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let closure_i64 = unbox_to_i64(blk, &closure_dbl); // ptrtoint of a function symbol: assigns a fresh SSA register // and emits the conversion. The resulting i64 is the address - // of `js_closure_call_array`, which we hand to js_create_callback + // of `js_closure_v8_callback`, which we hand to js_create_callback // as its trampoline target. let func_addr = blk.next_reg(); blk.emit_raw(format!( - "{} = ptrtoint ptr @js_closure_call_array to i64", + "{} = ptrtoint ptr @js_closure_v8_callback to i64", func_addr )); let pcount = (*param_count as i64).to_string(); diff --git a/crates/perry-codegen/src/lower_call/console_promise.rs b/crates/perry-codegen/src/lower_call/console_promise.rs index deab6ac180..ffc6e68d16 100644 --- a/crates/perry-codegen/src/lower_call/console_promise.rs +++ b/crates/perry-codegen/src/lower_call/console_promise.rs @@ -1578,10 +1578,10 @@ fn lower_closure_call_rooted<'a>( None => None, }; let recv_box = group.reread(ctx, callee_slot)?; - // One-argument receiverless calls have a runtime arrow-aware dispatcher. - // It folds arrow-ness into the existing closure-strategy cache and only - // performs OrdinaryCallBindThis for non-arrows. Other arities retain the - // generated save/restore path below. + // One-argument receiverless calls pass `undefined` to the call entry, + // which binds the implicit-`this` cell to it only for a non-arrow callee + // (arrow-ness comes from the existing closure-strategy cache). Other + // arities retain the generated save/restore path below. let receiverless_one_arg = method_recv.is_none() && !matches!(callee, Expr::PropertyGet { .. }) && args.len() == 1; @@ -1690,10 +1690,13 @@ fn lower_closure_call_rooted<'a>( } let result = if receiverless_one_arg { - ctx.block().call( - DOUBLE, - "js_closure_call1_receiverless", - &[(I64, &closure_handle), (DOUBLE, &lowered_args[0])], + // A plain call binds `undefined` (OrdinaryCallBindThis); the entry + // binds the implicit-`this` cell to it unless the callee is an arrow. + super::emit_closure_handle_call_this( + ctx, + &closure_handle, + crate::expr::body_call::JS_THIS_UNDEFINED, + &lowered_args, ) } else { // #3527: > 16 args marshal into an entry-block `[N x double]` buffer and diff --git a/crates/perry-codegen/src/lower_call/method_override.rs b/crates/perry-codegen/src/lower_call/method_override.rs index a605cfa9ac..f926a8ceb0 100644 --- a/crates/perry-codegen/src/lower_call/method_override.rs +++ b/crates/perry-codegen/src/lower_call/method_override.rs @@ -812,11 +812,14 @@ pub(super) fn emit_own_method_override_check( // #7211: rooted save/restore — the displaced implicit `this` is live // across `js_native_call_value`, which runs arbitrary user code. let prev_this = crate::rooting::implicit_this_save(ctx, &recv_for_this); + // Stage 1: the call runs with the implicit-`this` cell's value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); let v_override = ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &own_method), + (I64, &this_bits), (crate::types::PTR, &args_ptr), (I64, &args_len), ], diff --git a/crates/perry-codegen/src/lower_call/mod.rs b/crates/perry-codegen/src/lower_call/mod.rs index d3e3513408..01c294a368 100644 --- a/crates/perry-codegen/src/lower_call/mod.rs +++ b/crates/perry-codegen/src/lower_call/mod.rs @@ -276,31 +276,48 @@ pub(crate) fn emit_rooted_call( pub(crate) const MAX_FIXED_CLOSURE_CALL_ARGS: usize = 16; /// Dispatch an unboxed closure handle over already-lowered arguments through -/// the closure-call ABI. +/// the closure-call ABI as a PLAIN call. Stage 1 of this-as-a-parameter: a +/// plain call keeps the implicit-`this` cell's current value, so the +/// receiver passed is the cell's ([`crate::expr::body_call::current_this_bits`]) +/// and the entry binds nothing. +pub(crate) fn emit_closure_handle_call( + ctx: &mut FnCtx<'_>, + closure_handle: &str, + args: &[String], +) -> String { + let this_bits = crate::expr::body_call::current_this_bits(ctx); + emit_closure_handle_call_this(ctx, closure_handle, &this_bits, args) +} + +/// Dispatch an unboxed closure handle with receiver `this_bits` (the +/// receiver's NaN-boxed bits, `i64`, valid here — nothing between here and +/// the dispatch collects). /// /// Up to [`MAX_FIXED_CLOSURE_CALL_ARGS`] arguments use the per-arity -/// `js_closure_call{N}` register entry points — the fast path, unchanged. -/// Wider calls marshal the arguments into an entry-block `[N x double]` buffer -/// and dispatch through the variadic `js_closure_call_array(closure, args_ptr, -/// argc)`, which owns arbitrary-arity dispatch including rest bundling (#3527). -/// #10420: every closure-value call site used to either reject a 17th argument -/// at compile time or truncate the list to 16; they all route here now. +/// `js_closure_call{N}(closure, this, ...)` register entry points. Wider +/// calls marshal the arguments into an entry-block `[N x double]` buffer and +/// dispatch through `js_closure_call_array(closure, this, args_ptr, argc)`, +/// which owns arbitrary-arity dispatch including rest bundling (#3527). +/// #10420: every closure-value call site used to either reject a 17th +/// argument at compile time or truncate the list to 16; they all route here. /// /// The buffer is NOT a GC root, so callers pass values that are already valid /// below their last collection point; nothing emitted between the stores and /// the call can collect. -pub(crate) fn emit_closure_handle_call( +pub(crate) fn emit_closure_handle_call_this( ctx: &mut FnCtx<'_>, closure_handle: &str, + this_bits: &str, args: &[String], ) -> String { use crate::types::{DOUBLE, I64, PTR}; if args.len() <= MAX_FIXED_CLOSURE_CALL_ARGS { - let runtime_fn = format!("js_closure_call{}", args.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = Vec::with_capacity(args.len() + 1); + let runtime_fn = crate::runtime_abi::JS_CLOSURE_CALL_ENTRIES[args.len()]; + let mut call_args: Vec<(crate::types::LlvmType, &str)> = Vec::with_capacity(args.len() + 2); call_args.push((I64, closure_handle)); + call_args.push((I64, this_bits)); call_args.extend(args.iter().map(|value| (DOUBLE, value.as_str()))); - return ctx.block().call(DOUBLE, &runtime_fn, &call_args); + return ctx.block().call(DOUBLE, runtime_fn, &call_args); } let n = args.len(); let buf = ctx.func.alloca_entry_array(DOUBLE, n); @@ -313,7 +330,12 @@ pub(crate) fn emit_closure_handle_call( blk.call( DOUBLE, "js_closure_call_array", - &[(I64, closure_handle), (PTR, &buf), (I64, &argc)], + &[ + (I64, closure_handle), + (I64, this_bits), + (PTR, &buf), + (I64, &argc), + ], ) } diff --git a/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs b/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs index c93237181a..d1f74b291c 100644 --- a/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs +++ b/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs @@ -80,7 +80,7 @@ fn immutable_function_alias_calls_the_known_symbol_directly() { "an immutable FuncRef alias must use direct function lowering:\n{ir}" ); assert!( - !ir.contains("call double @js_closure_call1_receiverless"), + !ir.contains("call double @js_closure_call1("), "the immutable alias must not retain dynamic closure dispatch:\n{ir}" ); } @@ -88,12 +88,15 @@ fn immutable_function_alias_calls_the_known_symbol_directly() { #[test] fn mutable_function_alias_keeps_guarded_receiverless_dispatch() { let ir = alias_call_ir(true); + // A plain call passes `undefined`; the entry owns binding it. assert!( - ir.contains("call double @js_closure_call1_receiverless"), + ir.lines() + .any(|l| l.contains("call double @js_closure_call1(") + && l.contains(&format!("i64 {}, double", crate::nanbox::TAG_UNDEFINED_I64))), "a mutable alias has no stable target and must retain dynamic dispatch:\n{ir}" ); assert!( !ir.contains("call double @js_implicit_this_set"), - "the arrow-aware runtime dispatcher owns receiverless this binding:\n{ir}" + "the arrow-aware call entry owns receiverless this binding:\n{ir}" ); } diff --git a/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs b/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs index 7474208ebf..4e96c0cff6 100644 --- a/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs @@ -517,11 +517,14 @@ pub(crate) fn try_lower_instance_method_call( let recv_for_this_probe = recv_box.clone(); // #7211: rooted save/restore across the user-code dispatch. let prev_this_probe = crate::rooting::implicit_this_save(ctx, &recv_for_this_probe); + // Stage 1: the call runs with the implicit-`this` cell's value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); let v_override_probe = ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &own_method_probe), + (I64, &this_bits), (crate::types::PTR, &probe_args_ptr), (I64, &probe_args_len_str), ], @@ -1929,11 +1932,14 @@ fn emit_collapsed_instance_dispatch( ctx.current_block = override_idx; // #7211: rooted save/restore across the user-code dispatch. let prev_this = crate::rooting::implicit_this_save(ctx, recv_box); + // Stage 1: the call runs with the implicit-`this` cell's value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); let v_override = ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &own_method), + (I64, &this_bits), (crate::types::PTR, &args_ptr), (I64, &args_len), ], diff --git a/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs b/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs index d49de12ce3..ba478952f9 100644 --- a/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs @@ -338,11 +338,14 @@ pub(crate) fn try_lower_static_dispatch( let ptr_i64 = ctx.block().ptrtoint(&ptr_reg, I64); (ptr_i64, n.to_string()) }; + // Stage 1: the call runs with the implicit-`this` cell's value. + let this_bits = crate::expr::body_call::current_this_bits(ctx); return Ok(Some(ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &callee_val), + (I64, &this_bits), (I64, &args_ptr_i64), (I64, &args_len), ], 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 f6bab8ca1f..720c0f2967 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 @@ -133,11 +133,13 @@ pub(crate) fn declare_core(module: &mut LlModule) { // pointer to a `[N x double]` stack buffer; declare it PTR (ABI-identical // to I64 in the integer register class) so call sites can pass an alloca // directly. See `try_lower_closure_call_fallthrough` (#3527). - module.declare_function("js_closure_call_array", DOUBLE, &[I64, PTR, I64]); + module.declare_function("js_closure_call_array", DOUBLE, &[I64, I64, PTR, I64]); + // V8's callback trampoline (`func(env, args, len)`): its address only. + module.declare_function("js_closure_v8_callback", DOUBLE, &[I64, PTR, I64]); module.declare_function( "js_closure_call_apply_with_spread", DOUBLE, - &[DOUBLE, PTR, I64, I64], + &[DOUBLE, I64, PTR, I64, I64], ); module.declare_function("js_create_callback", DOUBLE, &[I64, I64, I64]); @@ -447,7 +449,7 @@ pub(crate) fn declare_core(module: &mut LlModule) { DOUBLE, &[DOUBLE, I64, I64, I64, I64], ); - module.declare_function("js_native_call_value", DOUBLE, &[DOUBLE, I64, I64]); + module.declare_function("js_native_call_value", DOUBLE, &[DOUBLE, I64, I64, I64]); module.declare_function("js_new_from_handle", DOUBLE, &[DOUBLE, I64, I64]); module.declare_function("js_new_instance", DOUBLE, &[I64, I64, I64, I64, I64]); module.declare_function("js_runtime_init", VOID, &[]); diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index b4344c54a9..87551cf469 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -276,17 +276,16 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { I64, &[DOUBLE, DOUBLE], ); - // `js_closure_call{N}(callee, a0..aN-1)`, generated from the ABI table so - // the declarations cannot drift from `perry_abi::JS_BODY_*`. + // `js_closure_call{N}(callee, this, a0..aN-1)`, generated from the ABI + // table so the declarations cannot drift from `perry_abi`. for (argc, name) in crate::runtime_abi::JS_CLOSURE_CALL_ENTRIES .iter() .enumerate() { - let mut params = vec![I64]; + let mut params = vec![I64, I64]; params.extend(std::iter::repeat_n(DOUBLE, argc)); module.declare_function(name, DOUBLE, ¶ms); } - module.declare_function("js_closure_call1_receiverless", DOUBLE, &[I64, DOUBLE]); module.declare_function("js_closure_resolve_arrow_direct_call", PTR, &[I64, I32]); // Phase B.16 / D follow-ups: more runtime functions discovered diff --git a/crates/perry-ext-events/src/dispatch_ext.rs b/crates/perry-ext-events/src/dispatch_ext.rs index 9aa471a6ed..267839b2dc 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 = unsafe { js_closure_alloc(noop_listener as *const u8, 0) }; + let closure = perry_ffi::alloc_closure(noop_listener as perry_ffi::JsBody0, 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 ad03ac4319..bb2898355a 100644 --- a/crates/perry-ext-events/src/lib.rs +++ b/crates/perry-ext-events/src/lib.rs @@ -117,17 +117,16 @@ extern "C" { on_fulfilled: *mut RawClosureHeader, on_rejected: *mut RawClosureHeader, ) -> *mut Promise; - // #1557: closure allocation hooks needed by events.on's queue-listener. + // #1557: closure capture hooks needed by events.on's queue-listener. // Mirrors perry-runtime::closure exports; declared here because perry-ffi - // doesn't yet expose them and perry-ext-events deliberately avoids a - // direct perry-runtime dep. - fn js_closure_alloc(fn_ptr: *const u8, capture_count: u32) -> *mut RawClosureHeader; + // doesn't expose them and perry-ext-events deliberately avoids a direct + // perry-runtime dep. Bodies are allocated through perry-ffi's typed + // `alloc_closure`. fn js_closure_set_capture_ptr(closure: *mut RawClosureHeader, slot: u32, ptr: i64); fn js_closure_get_capture_ptr(closure: *const RawClosureHeader, slot: u32) -> i64; fn js_array_push_f64(arr: *mut ArrayHeader, value: f64) -> *mut ArrayHeader; fn js_array_shift_f64(arr: *mut ArrayHeader) -> f64; fn js_promise_new() -> *mut Promise; - fn js_register_closure_arity(func_ptr: *const u8, arity: u32); fn js_closure_get_capture_f64(closure: *const RawClosureHeader, slot: u32) -> f64; fn js_closure_set_capture_f64(closure: *mut RawClosureHeader, slot: u32, value: f64); // #1557: AbortSignal listener attachment for events.addAbortListener. @@ -178,9 +177,13 @@ extern "C" { fn js_abort_error_value() -> f64; fn js_throw(value: f64) -> !; fn js_domain_emit_error(handle: Handle, error: f64, emitter: f64, domain_thrown: bool) -> bool; - fn js_implicit_this_set(value: f64) -> f64; fn js_jsvalue_to_string(value: f64) -> *mut StringHeader; - fn js_native_call_value(func_value: f64, args_ptr: *const f64, args_len: usize) -> f64; + fn js_native_call_value( + func_value: f64, + this: perry_ffi::JsThis, + args_ptr: *const f64, + args_len: usize, + ) -> f64; fn js_value_is_promise(value: f64) -> i32; fn js_register_event_emitter_handle_probe(f: unsafe extern "C" fn(i64) -> bool); fn js_register_event_emitter_async_resource_handle_probe(f: unsafe extern "C" fn(i64) -> bool); @@ -204,7 +207,6 @@ extern "C" { // TypeError [ERR_INVALID_ARG_TYPE]. Centralized in perry-runtime so the // stdlib and ext-events EventEmitter implementations stay byte-identical. fn js_validate_event_listener(listener_bits: i64, name_ptr: *const u8, name_len: u32) -> i64; - fn js_register_closure_rest(fn_ptr: *const u8, fixed_arity: u32); fn js_async_resource_new(type_value: f64, options: f64) -> i64; fn js_async_resource_async_id(handle: i64) -> f64; fn js_async_resource_trigger_async_id(handle: i64) -> f64; @@ -857,8 +859,8 @@ 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(|| unsafe { - js_register_closure_rest(event_emitter_once_wrapper as *const u8, 0); + RAW_ONCE_WRAPPER_REST_REGISTERED.call_once(|| { + perry_ffi::register_closure_rest(event_emitter_once_wrapper as perry_ffi::JsBody1, 0); }); } @@ -873,7 +875,7 @@ unsafe fn create_once_raw_wrapper(handle: Handle, event_name: &str, callback: i6 } ensure_raw_once_wrapper_rest_registered(); - let wrapper = js_closure_alloc(event_emitter_once_wrapper as *const u8, 4); + let wrapper = perry_ffi::alloc_closure(event_emitter_once_wrapper as perry_ffi::JsBody1, 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); @@ -1150,14 +1152,13 @@ unsafe fn collect_emit_args(args_ptr: *const ArrayHeader) -> Vec { unsafe fn call_emitter_listener(handle: Handle, callback: i64, args: &[f64]) -> f64 { let receiver = nanbox_pointer_bits(handle); let callback_value = nanbox_pointer_bits(callback); - let previous_this = js_implicit_this_set(receiver); - let result = if args.is_empty() { - js_native_call_value(callback_value, std::ptr::null(), 0) + // A listener runs with the emitter as `this`. + let this = perry_ffi::JsThis::from_f64(receiver); + if args.is_empty() { + js_native_call_value(callback_value, this, std::ptr::null(), 0) } else { - js_native_call_value(callback_value, args.as_ptr(), args.len()) - }; - js_implicit_this_set(previous_this); - result + js_native_call_value(callback_value, this, args.as_ptr(), args.len()) + } } extern "C" fn events_capture_rejection_handler( @@ -1186,7 +1187,8 @@ 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 = + perry_ffi::alloc_closure(events_capture_rejection_handler as perry_ffi::JsBody1, 1); js_closure_set_capture_ptr(on_rejected, 0, handle); js_promise_then(promise, std::ptr::null_mut(), on_rejected); } @@ -1811,7 +1813,8 @@ 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 = js_closure_alloc(events_once_abort_listener as *const u8, 2); + let abort_listener = + perry_ffi::alloc_closure(events_once_abort_listener as perry_ffi::JsBody0, 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( @@ -1835,7 +1838,8 @@ pub unsafe extern "C" fn js_events_once( if event_name_ptr.is_null() { return raw; } - let listener = js_closure_alloc(events_once_event_target_listener as *const u8, 3); + let listener = + perry_ffi::alloc_closure(events_once_event_target_listener as perry_ffi::JsBody1, 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); @@ -1851,21 +1855,30 @@ 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, ))); - js_register_closure_rest(events_once_stream_resolve_listener as *const u8, 0); - let listener = js_closure_alloc(events_once_stream_resolve_listener as *const u8, 4); + 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); 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" { - js_register_closure_rest(events_once_stream_reject_listener as *const u8, 0); + 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 = - js_closure_alloc(events_once_stream_reject_listener as *const u8, 4); + let reject_listener = perry_ffi::alloc_closure( + events_once_stream_reject_listener as perry_ffi::JsBody1, + 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( @@ -1913,9 +1926,16 @@ 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, ))); - js_register_closure_rest(events_once_stream_resolve_listener as *const u8, 0); - 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); + 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); 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); @@ -1926,8 +1946,10 @@ 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 = - js_closure_alloc(events_once_stream_reject_listener as *const u8, 4); + let reject_listener = perry_ffi::alloc_closure( + events_once_stream_reject_listener as perry_ffi::JsBody1, + 4, + ); let reject_listener = scope.root_addr(reject_listener as i64); js_closure_set_capture_ptr( reject_listener.get() as *mut RawClosureHeader, diff --git a/crates/perry-ext-events/src/max_listeners.rs b/crates/perry-ext-events/src/max_listeners.rs index 08ef6f75be..3509507021 100644 --- a/crates/perry-ext-events/src/max_listeners.rs +++ b/crates/perry-ext-events/src/max_listeners.rs @@ -114,10 +114,14 @@ unsafe fn call_process_emit_warning(warning: f64) { let process_value = JsValue::from_bits(process.to_bits()); let process_obj = process_value.as_pointer::(); let emit_warning = get_object_field_by_str(process_obj, "emitWarning"); - let previous_this = js_implicit_this_set(process); + // `process.emitWarning(warning)`: a method call of `process`. let args = [warning]; - js_native_call_value(emit_warning, args.as_ptr(), args.len()); - js_implicit_this_set(previous_this); + js_native_call_value( + emit_warning, + perry_ffi::JsThis::from_f64(process), + args.as_ptr(), + args.len(), + ); } pub(super) unsafe fn emit_max_listeners_warning( diff --git a/crates/perry-ext-events/src/module_iterators.rs b/crates/perry-ext-events/src/module_iterators.rs index ced03a4fde..2f38ea6b51 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 = js_closure_alloc(events_on_next as *const u8, 1); + let next = perry_ffi::alloc_closure(events_on_next as perry_ffi::JsBody0, 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 = js_closure_alloc(events_on_return as *const u8, 1); + let return_fn = perry_ffi::alloc_closure(events_on_return as perry_ffi::JsBody0, 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 = js_closure_alloc(events_on_iterator_self as *const u8, 1); + let self_fn = perry_ffi::alloc_closure(events_on_iterator_self as perry_ffi::JsBody0, 1); js_closure_set_capture_f64(self_fn, 0, iterator_root.get()); js_object_set_symbol_property( iterator_root.get(), @@ -438,11 +438,11 @@ 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)); - js_register_closure_arity(events_on_next as *const u8, 0); - js_register_closure_arity(events_on_return as *const u8, 0); - js_register_closure_arity(events_on_iterator_self as *const u8, 0); - js_register_closure_arity(events_on_async_iterator as *const u8, 0); - let closure = js_closure_alloc(events_on_async_iterator as *const u8, 1); + 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); js_closure_set_capture_ptr( closure, 0, diff --git a/crates/perry-ext-events/src/module_on.rs b/crates/perry-ext-events/src/module_on.rs index 5f58d453f3..4140a6998a 100644 --- a/crates/perry-ext-events/src/module_on.rs +++ b/crates/perry-ext-events/src/module_on.rs @@ -30,7 +30,7 @@ pub unsafe extern "C" fn js_events_on( if signal.is_some_and(signal_is_aborted) { js_throw(js_abort_error_value()); } - let listener = js_closure_alloc(events_on_queue_listener as *const u8, 1); + let listener = perry_ffi::alloc_closure(events_on_queue_listener as perry_ffi::JsBody1, 1); js_closure_set_capture_ptr( listener, 0, @@ -92,7 +92,8 @@ 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 = js_closure_alloc(events_on_close_listener as *const u8, 1); + let close_listener = + perry_ffi::alloc_closure(events_on_close_listener as perry_ffi::JsBody0, 1); js_closure_set_capture_ptr( close_listener, 0, @@ -136,7 +137,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 = + perry_ffi::alloc_closure(events_on_abort_listener as perry_ffi::JsBody0, 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); @@ -193,7 +195,8 @@ 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 = js_closure_alloc(events_abort_listener_dispose as *const u8, 2); + let dispose_closure = + perry_ffi::alloc_closure(events_abort_listener_dispose as perry_ffi::JsBody0, 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 53975ccf39..db53a00c2d 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 = unsafe { js_closure_alloc(noop_listener as *const u8, 0) }; + let closure = perry_ffi::alloc_closure(noop_listener as perry_ffi::JsBody0, 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 02b9c588c6..680a2adbe6 100644 --- a/crates/perry-ext-http/src/agent.rs +++ b/crates/perry-ext-http/src/agent.rs @@ -372,7 +372,8 @@ 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 *const u8, 2); + let listener = + perry_ffi::alloc_closure(agent_connection_abort_listener as perry_ffi::JsBody0, 2); if listener.is_null() { return; } @@ -1655,7 +1656,7 @@ unsafe fn invoke_create_connection_closure( let cc = scope.root_addr(closure_ptr); let options = scope.root_nanbox(build_connect_options(agent_handle, host, port, path)); let closure = JsClosure::from_raw(cc.get() as *const RawClosureHeader); - let ret = closure.call1(options.get()); + let ret = closure.call1(perry_ffi::JsThis::UNDEFINED, options.get()); // `net.connect` / `net.createConnection` return the socket id NaN-boxed // with POINTER_TAG; some codegen paths hand back a bare raw pointer. diff --git a/crates/perry-ext-http/src/client_abort.rs b/crates/perry-ext-http/src/client_abort.rs index 95b6c1caee..a67a5a16b7 100644 --- a/crates/perry-ext-http/src/client_abort.rs +++ b/crates/perry-ext-http/src/client_abort.rs @@ -40,7 +40,7 @@ pub(crate) unsafe fn attach_request_signal(request_handle: Handle, options: f64) return; } - let listener = perry_ffi::alloc_closure(request_signal_listener as *const u8, 1); + let listener = perry_ffi::alloc_closure(request_signal_listener as perry_ffi::JsBody0, 1); if listener.is_null() { return; } diff --git a/crates/perry-ext-http/src/client_events.rs b/crates/perry-ext-http/src/client_events.rs index 6d5ba6270c..2c4bd57426 100644 --- a/crates/perry-ext-http/src/client_events.rs +++ b/crates/perry-ext-http/src/client_events.rs @@ -113,7 +113,7 @@ pub(crate) unsafe fn fire_request_event_listeners(request_handle: Handle, event: for cb in listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } } @@ -136,7 +136,7 @@ pub(crate) unsafe fn fire_request_socket_event(request_handle: Handle) { for callback in listeners { if callback.get() != 0 { let closure = JsClosure::from_raw(callback.get() as *const RawClosureHeader); - let _ = closure.call1(value); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, value); } } } @@ -151,9 +151,9 @@ unsafe fn fire_incoming_event(incoming: Handle, event: &str, arg: Option) { } let closure = JsClosure::from_raw(callback as *const RawClosureHeader); if let Some(value) = arg { - let _ = closure.call1(value); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, value); } else { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } } @@ -189,7 +189,7 @@ pub(crate) unsafe fn fire_request_error_listeners(request_handle: Handle, arg: f for cb in listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg.get()); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg.get()); } } } @@ -314,14 +314,14 @@ pub(crate) unsafe fn handle_response_event( let response_listeners = scope.root_addrs(&response_listeners); if response_callback.get() != 0 { let closure = JsClosure::from_raw(response_callback.get() as *const RawClosureHeader); - let _ = closure.call1(arg); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg); } // #4909 — `.on('response', cb)` listeners fire too (the factory // callback is just Node's pre-registered once-listener). for cb in response_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg); } } @@ -354,7 +354,7 @@ pub(crate) unsafe fn handle_response_event( for cb in &data_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg.get()); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg.get()); } } for dest in &pipes { @@ -372,7 +372,7 @@ pub(crate) unsafe fn handle_response_event( for cb in end_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } @@ -478,7 +478,12 @@ pub(crate) unsafe fn handle_upgrade_event( for cb in listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call3(res_arg.get(), socket_arg.get(), head_arg.get()); + let _ = closure.call3( + perry_ffi::JsThis::UNDEFINED, + res_arg.get(), + socket_arg.get(), + head_arg.get(), + ); } } @@ -544,12 +549,12 @@ pub(crate) unsafe fn handle_response_head_event( let response_listeners = scope.root_addrs(&response_listeners); if response_callback.get() != 0 { let closure = JsClosure::from_raw(response_callback.get() as *const RawClosureHeader); - let _ = closure.call1(arg); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg); } for cb in response_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg); } } } @@ -608,7 +613,7 @@ pub(crate) unsafe fn handle_response_chunk_event(request_handle: Handle, chunk: for cb in data_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg.get()); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg.get()); } } for dest in pipes { @@ -674,7 +679,7 @@ pub(crate) unsafe fn handle_response_end_event(request_handle: Handle) { for cb in data_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call1(arg.get()); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg.get()); } } for dest in pipes { @@ -697,7 +702,7 @@ pub(crate) unsafe fn handle_response_end_event(request_handle: Handle) { for cb in end_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } @@ -863,7 +868,7 @@ pub(crate) unsafe fn handle_timeout_event(request_handle: Handle) { for cb in timeout_listeners { if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } // The transport deadline killed an in-flight exchange; if the handler @@ -894,12 +899,12 @@ pub(crate) unsafe fn handle_flushed_event(request_handle: Handle) { for cb in write_cbs { if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } fire_request_event_listeners(request_handle, "finish"); if end_cb != 0 { let closure = JsClosure::from_raw(end_cb as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } diff --git a/crates/perry-ext-http/src/client_request_surface.rs b/crates/perry-ext-http/src/client_request_surface.rs index 1415e32166..9f4e56e4be 100644 --- a/crates/perry-ext-http/src/client_request_surface.rs +++ b/crates/perry-ext-http/src/client_request_surface.rs @@ -17,8 +17,12 @@ extern "C" { method_name_ptr: *const u8, method_name_len: usize, ) -> f64; - fn js_register_closure_rest(func_ptr: *const u8, fixed_arity: u32); - fn js_closure_call_array(closure: i64, args: *const f64, args_len: i64) -> f64; + fn js_closure_call_array( + closure: i64, + this: perry_ffi::JsThis, + args: *const f64, + args_len: i64, + ) -> f64; fn js_object_set_field_by_name(object: *mut ObjectHeader, key: *const StringHeader, value: f64); } @@ -33,15 +37,15 @@ pub(crate) fn create_client_once_wrapper( if callback == 0 { return 0; } - CLIENT_ONCE_WRAPPER_REGISTERED.call_once(|| unsafe { - js_register_closure_rest(client_once_wrapper as *const u8, 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 *const u8, 5); + let wrapper = perry_ffi::alloc_closure(client_once_wrapper as perry_ffi::JsBody1, 5); let wrapper = scope.root_addr(wrapper as i64); let wrapper_ptr = wrapper.get() as *mut RawClosureHeader; unsafe { @@ -74,7 +78,7 @@ pub(crate) fn create_client_once_wrapper( extern "C" fn client_once_wrapper( closure: *const RawClosureHeader, - _this: perry_ffi::JsThis, + this: perry_ffi::JsThis, rest: f64, ) -> f64 { unsafe { @@ -141,16 +145,16 @@ extern "C" fn client_once_wrapper( } let value = JsValue::from_bits(rest.to_bits()); if !value.is_pointer() { - return js_closure_call_array(callback, std::ptr::null(), 0); + return js_closure_call_array(callback, this, std::ptr::null(), 0); } let array = value.as_pointer::(); if array.is_null() { - return js_closure_call_array(callback, std::ptr::null(), 0); + return js_closure_call_array(callback, this, std::ptr::null(), 0); } // The rest ABI creates this fresh, unshifted argument array for this // invocation; no JS callback has run since it was packed. let args = (array as *const u8).add(8) as *const f64; - js_closure_call_array(callback, args, (*array).length as i64) + js_closure_call_array(callback, this, args, (*array).length as i64) } } diff --git a/crates/perry-ext-http/src/lib.rs b/crates/perry-ext-http/src/lib.rs index e0a9f74caf..e663b51e45 100644 --- a/crates/perry-ext-http/src/lib.rs +++ b/crates/perry-ext-http/src/lib.rs @@ -920,10 +920,10 @@ unsafe fn invoke_create_socket( // 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 *const u8, 2); + 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 *const u8, 1); + let cb = perry_ffi::alloc_closure(http_create_socket_cb as perry_ffi::JsBody2, 1); if cb.is_null() { return; } @@ -945,7 +945,12 @@ unsafe fn invoke_create_socket( )); let closure = JsClosure::from_raw(cs.get() as *const RawClosureHeader); - closure.call3(req_val, options.get(), cb_val.get()); + closure.call3( + perry_ffi::JsThis::UNDEFINED, + req_val, + options.get(), + cb_val.get(), + ); } /// Continuation for a `createSocket` override's `cb(err, socket)` callback. diff --git a/crates/perry-ext-http/src/server/bun_server.rs b/crates/perry-ext-http/src/server/bun_server.rs index a9f964e264..c58456649e 100644 --- a/crates/perry-ext-http/src/server/bun_server.rs +++ b/crates/perry-ext-http/src/server/bun_server.rs @@ -9,7 +9,7 @@ use perry_ffi::{ GcRootVisitor, JsClosure, JsValue, RawClosureHeader, StringHeader, TransientRootScope, }; -use crate::server::request::{handle_to_pointer_f64, with_implicit_this, IncomingMessage}; +use crate::server::request::{handle_to_pointer_f64, IncomingMessage}; use crate::server::response::ServerResponse; use crate::server::server::{ finalize_or_park_request, synthesize_default_response_if_needed, HttpPendingRequest, HttpServer, @@ -382,7 +382,7 @@ fn invoke_fetch(server_handle: i64, request: f64) -> ClosureCallResult { } let closure = unsafe { JsClosure::from_raw(handler.get() as *const RawClosureHeader) }; let server = handle_to_pointer_f64(server_handle); - unsafe { call_catching(|| with_implicit_this(server, || closure.call2(request, server))) } + unsafe { call_catching(|| closure.call2(perry_ffi::JsThis::from_f64(server), request, server)) } } fn invoke_error(server_handle: i64, reason: f64) -> Option { @@ -394,7 +394,9 @@ fn invoke_error(server_handle: i64, reason: f64) -> Option { } let closure = unsafe { JsClosure::from_raw(handler.get() as *const RawClosureHeader) }; let server = handle_to_pointer_f64(server_handle); - Some(unsafe { call_catching(|| with_implicit_this(server, || closure.call1(reason.get()))) }) + Some(unsafe { + call_catching(|| closure.call1(perry_ffi::JsThis::from_f64(server), reason.get())) + }) } fn apply_response(response_handle: i64, value: f64) -> bool { diff --git a/crates/perry-ext-http/src/server/http2_server.rs b/crates/perry-ext-http/src/server/http2_server.rs index 5deff28fc4..1daaf62ace 100644 --- a/crates/perry-ext-http/src/server/http2_server.rs +++ b/crates/perry-ext-http/src/server/http2_server.rs @@ -394,7 +394,7 @@ pub(crate) fn call0(callback: i64) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } } @@ -407,7 +407,7 @@ pub(crate) fn call1(callback: i64, arg: f64) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call1(arg); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, arg); } } } @@ -420,7 +420,7 @@ pub(crate) fn call2(callback: i64, arg0: f64, arg1: f64) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call2(arg0, arg1); + let _ = closure.call2(perry_ffi::JsThis::UNDEFINED, arg0, arg1); } } } @@ -433,7 +433,7 @@ pub(crate) fn call3(callback: i64, arg0: f64, arg1: f64, arg2: f64) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call3(arg0, arg1, arg2); + let _ = closure.call3(perry_ffi::JsThis::UNDEFINED, arg0, arg1, arg2); } } } diff --git a/crates/perry-ext-http/src/server/http2_server/pump.rs b/crates/perry-ext-http/src/server/http2_server/pump.rs index 09ce2fc367..b1fa9d8472 100644 --- a/crates/perry-ext-http/src/server/http2_server/pump.rs +++ b/crates/perry-ext-http/src/server/http2_server/pump.rs @@ -7,7 +7,7 @@ use perry_ffi::{ alloc_buffer, get_handle, get_handle_mut, iter_handles_of, JsClosure, RawClosureHeader, }; -use crate::server::request::{handle_to_pointer_f64, with_implicit_this}; +use crate::server::request::handle_to_pointer_f64; use crate::server::response::ResponseShape; use crate::server::server::{finalize_or_park_request, HttpPendingRequest}; use crate::server::types::{js_promise_run_microtasks, POINTER_TAG, PTR_MASK, TAG_UNDEFINED}; @@ -64,9 +64,7 @@ pub(crate) fn process_pending_h2(pending: HttpPendingRequest) { let raw = handler.get() as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -80,9 +78,7 @@ pub(crate) fn process_pending_h2(pending: HttpPendingRequest) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -106,7 +102,8 @@ pub(crate) fn process_pending_h2(pending: HttpPendingRequest) { let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call2(stream_f64, headers_f64.get()); + let _ = + closure.call2(perry_ffi::JsThis::UNDEFINED, stream_f64, headers_f64.get()); } js_promise_run_microtasks(); } diff --git a/crates/perry-ext-http/src/server/https_server.rs b/crates/perry-ext-http/src/server/https_server.rs index 13142d461c..195b3d7e75 100644 --- a/crates/perry-ext-http/src/server/https_server.rs +++ b/crates/perry-ext-http/src/server/https_server.rs @@ -12,7 +12,7 @@ use perry_ffi::{ }; use crate::server::ensure_gc_scanner_registered; -use crate::server::request::{handle_to_pointer_f64, with_implicit_this}; +use crate::server::request::handle_to_pointer_f64; use crate::server::server::{ sanitize_request_timeout, signal_connections_close, HttpPendingRequest, HttpServer, }; @@ -470,9 +470,8 @@ pub(crate) fn process_pending_https(pending: HttpPendingRequest) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = + closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -485,9 +484,7 @@ pub(crate) fn process_pending_https(pending: HttpPendingRequest) { let raw = handler_rooted.get() as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -501,9 +498,7 @@ pub(crate) fn process_pending_https(pending: HttpPendingRequest) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } diff --git a/crates/perry-ext-http/src/server/request.rs b/crates/perry-ext-http/src/server/request.rs index 5e03f142b5..d4f3789d7f 100644 --- a/crates/perry-ext-http/src/server/request.rs +++ b/crates/perry-ext-http/src/server/request.rs @@ -828,7 +828,7 @@ pub(crate) fn emit_data_to_listeners(listeners: &[i64], body: &[u8], encoding: O let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call1(chunk.get()); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, chunk.get()); } } } @@ -852,17 +852,23 @@ pub(crate) fn emit_no_arg_to_listeners(listeners: &[i64]) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } } } /// Fire a one-arg event (`server.on('connection', socket)`). -pub(crate) fn emit_one_arg_to_listeners(listeners: &[i64], arg: f64) { - // #8082: the snapshot AND the arg cross every callback — root both. +/// Call every listener with `this` bound to `this_val` (the emitting object: +/// Node invokes server, socket and request callbacks that way, so +/// `server.listen(0, function() { this.address().port })` resolves `this` to +/// the server, #2132) and one argument. +pub(crate) fn emit_one_arg_to_listeners(listeners: &[i64], this_val: f64, arg: f64) { + // #8082: the snapshot, the receiver AND the arg cross every callback — + // root all of them. let scope = perry_ffi::TransientRootScope::enter(); let rooted = scope.root_addrs(listeners); + let this_val = scope.root_nanbox(this_val); let arg = scope.root_nanbox(arg); for cb in &rooted { let addr = cb.get(); @@ -873,7 +879,7 @@ pub(crate) fn emit_one_arg_to_listeners(listeners: &[i64], arg: f64) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call1(arg.get()); + let _ = closure.call1(perry_ffi::JsThis::from_f64(this_val.get()), arg.get()); } } } @@ -957,26 +963,6 @@ pub(crate) fn handle_to_pointer_f64(handle: i64) -> f64 { f64::from_bits(POINTER_TAG | (handle as u64 & PTR_MASK)) } -extern "C" { - /// `perry-runtime`'s implicit-`this` cell setter (resolved at final - /// link; declared here because `perry-runtime` is only a - /// dev-dependency of this crate). Returns the previous value. - fn js_implicit_this_set(value: f64) -> f64; -} - -/// Run `f` with the runtime's implicit-`this` cell bound to `this_val`, -/// restoring the previous binding afterward. Node invokes server, -/// socket, and request callbacks with `this` bound to the emitting -/// object, so the canonical -/// `server.listen(0, function() { this.address().port })` idiom -/// resolves `this` to the server (#2132). -pub(crate) fn with_implicit_this(this_val: f64, f: impl FnOnce() -> R) -> R { - let prev = unsafe { js_implicit_this_set(this_val) }; - let r = f(); - unsafe { js_implicit_this_set(prev) }; - r -} - // ============================================================================ // #4904: standalone `new http.IncomingMessage(socket)` support // ============================================================================ diff --git a/crates/perry-ext-http/src/server/response.rs b/crates/perry-ext-http/src/server/response.rs index 79b22c833a..95c86f06e7 100644 --- a/crates/perry-ext-http/src/server/response.rs +++ b/crates/perry-ext-http/src/server/response.rs @@ -1335,7 +1335,7 @@ pub unsafe extern "C" fn js_node_http_res_on( let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } handle_to_pointer_f64(handle) @@ -1377,7 +1377,7 @@ pub unsafe extern "C" fn js_node_http_res_once( let raw = callback as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } handle_to_pointer_f64(handle) diff --git a/crates/perry-ext-http/src/server/response_end.rs b/crates/perry-ext-http/src/server/response_end.rs index 5332c5c85c..ccc7bf3804 100644 --- a/crates/perry-ext-http/src/server/response_end.rs +++ b/crates/perry-ext-http/src/server/response_end.rs @@ -301,7 +301,7 @@ pub(crate) fn call_closure0(callback: i64) { unsafe { let closure = JsClosure::from_raw(callback as *const RawClosureHeader); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ext-http/src/server/server.rs b/crates/perry-ext-http/src/server/server.rs index 11880b1c21..7d52552384 100644 --- a/crates/perry-ext-http/src/server/server.rs +++ b/crates/perry-ext-http/src/server/server.rs @@ -14,7 +14,7 @@ use perry_ffi::{ }; use crate::server::ensure_gc_scanner_registered; -use crate::server::request::{handle_to_pointer_f64, with_implicit_this}; +use crate::server::request::handle_to_pointer_f64; use crate::server::response::{ResponseShape, ServerResponse}; use crate::server::types::{ extract_host, extract_port, js_handle_clear_side_tables, js_promise_run_microtasks, @@ -1156,9 +1156,7 @@ pub extern "C" fn js_node_http_server_process_pending() -> i32 { } let this_val = handle_to_pointer_f64(server_handle); let socket_val = handle_to_pointer_f64(socket_handle); - with_implicit_this(this_val, || { - crate::server::request::emit_one_arg_to_listeners(&listeners, socket_val) - }); + crate::server::request::emit_one_arg_to_listeners(&listeners, this_val, socket_val); count += 1; } @@ -1428,9 +1426,8 @@ fn process_pending(pending: HttpPendingRequest) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = + closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -1462,9 +1459,7 @@ fn process_pending(pending: HttpPendingRequest) { if !closure.is_null() { // `createServer(handler)` registers `handler` as a // `'request'` listener — same `this` = server binding. - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } @@ -1478,9 +1473,7 @@ fn process_pending(pending: HttpPendingRequest) { let raw = addr as *const RawClosureHeader; let closure = JsClosure::from_raw(raw); if !closure.is_null() { - with_implicit_this(server_this, || { - let _ = closure.call2(req_f64, res_f64); - }); + let _ = closure.call2(perry_ffi::JsThis::from_f64(server_this), req_f64, res_f64); } js_promise_run_microtasks(); } diff --git a/crates/perry-ext-http/src/server/server/deferred_events.rs b/crates/perry-ext-http/src/server/server/deferred_events.rs index e639d40231..2214f6cce4 100644 --- a/crates/perry-ext-http/src/server/server/deferred_events.rs +++ b/crates/perry-ext-http/src/server/server/deferred_events.rs @@ -2,9 +2,13 @@ use super::*; +/// The callbacks run with `this` bound to the emitting server (Node's +/// `EventEmitter` contract); `this_value` is the rooted receiver, re-read for +/// every callback. struct DeferredCallbacksCall { callbacks: *const perry_ffi::TransientRootedAddr, len: usize, + this_value: *const perry_ffi::TransientRootedNanbox, } unsafe extern "C" fn call_deferred_callbacks(data: *mut std::ffi::c_void) -> f64 { @@ -18,7 +22,7 @@ unsafe extern "C" fn call_deferred_callbacks(data: *mut std::ffi::c_void) -> f64 } let closure = JsClosure::from_raw(callback as *const RawClosureHeader); if !closure.is_null() { - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::from_f64((*call.this_value).get())); fired += 1; } } @@ -134,10 +138,12 @@ where // #8082: the drained snapshot crosses each callback — root it. let scope = perry_ffi::TransientRootScope::enter(); let rooted = scope.root_addrs(&cbs); + let this_rooted = scope.root_nanbox(this_val); perry_ext_ws::attached_server_listening(server_handle); let mut call = DeferredCallbacksCall { callbacks: rooted.as_ptr(), len: rooted.len(), + this_value: &this_rooted, }; unsafe { crate::js_async_hooks_provider_run_catching_with_this( @@ -178,9 +184,11 @@ where let this_value = handle_to_pointer_f64(server_handle); let scope = perry_ffi::TransientRootScope::enter(); let callbacks = scope.root_addrs(&callbacks); + let this_rooted = scope.root_nanbox(this_value); let mut call = DeferredCallbacksCall { callbacks: callbacks.as_ptr(), len: callbacks.len(), + this_value: &this_rooted, }; unsafe { crate::js_async_hooks_provider_run_catching_with_this( @@ -351,6 +359,8 @@ struct DeferredErrorCall { callbacks: *const perry_ffi::TransientRootedAddr, len: usize, error: *const ListenError, + /// The rooted receiver: the emitting server (see `DeferredCallbacksCall`). + this_value: *const perry_ffi::TransientRootedNanbox, } unsafe extern "C" fn call_deferred_error_callbacks(data: *mut std::ffi::c_void) -> f64 { @@ -369,7 +379,8 @@ unsafe extern "C" fn call_deferred_error_callbacks(data: *mut std::ffi::c_void) } let closure = JsClosure::from_raw(callback as *const RawClosureHeader); if !closure.is_null() { - let _ = closure.call1(error.get()); + let this = perry_ffi::JsThis::from_f64((*call.this_value).get()); + let _ = closure.call1(this, error.get()); fired += 1; } } @@ -451,10 +462,12 @@ where let this_val = handle_to_pointer_f64(server_handle); let scope = perry_ffi::TransientRootScope::enter(); let rooted = scope.root_addrs(&cbs); + let this_rooted = scope.root_nanbox(this_val); let mut call = DeferredErrorCall { callbacks: rooted.as_ptr(), len: rooted.len(), error: &error as *const ListenError, + this_value: &this_rooted, }; unsafe { crate::js_async_hooks_provider_run_catching_with_this( 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 e6a2e08252..5275beeefd 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,8 @@ 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 *const u8, 3); - let callback = scope.root_addr(alloc_closure(observe as *const u8, 3) as i64); + 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); unsafe { set_closure_capture_f64(callback.get() as *mut _, 0, 0.0); } diff --git a/crates/perry-ext-http/src/server/upgrade.rs b/crates/perry-ext-http/src/server/upgrade.rs index 1ac813ceb6..00729546c2 100644 --- a/crates/perry-ext-http/src/server/upgrade.rs +++ b/crates/perry-ext-http/src/server/upgrade.rs @@ -65,7 +65,12 @@ pub(crate) fn fire_upgrade_listeners( let closure = JsClosure::from_raw(raw); if !closure.is_null() { delivered = true; - let _ = closure.call3(req_f64, ws_id_f64, head_arg.get()); + let _ = closure.call3( + perry_ffi::JsThis::UNDEFINED, + req_f64, + ws_id_f64, + head_arg.get(), + ); } js_promise_run_microtasks(); } diff --git a/crates/perry-ext-http/src/tls_client.rs b/crates/perry-ext-http/src/tls_client.rs index e286dd836e..329928cb4c 100644 --- a/crates/perry-ext-http/src/tls_client.rs +++ b/crates/perry-ext-http/src/tls_client.rs @@ -930,7 +930,7 @@ pub(crate) unsafe fn check_server_identity_error( let cert = scope.root_nanbox(f64::from_bits(perry_ffi::alloc_object().bits())); let closure = perry_ffi::JsClosure::from_raw(callback.get() as *const perry_ffi::RawClosureHeader); - let result = closure.call2(host.get(), cert.get()); + let result = closure.call2(perry_ffi::JsThis::UNDEFINED, host.get(), cert.get()); let is_error = perry_ffi::JsValue::from_bits(js_error_is_error(result).to_bits()); if !is_error.is_bool() || !is_error.to_bool() { return None; diff --git a/crates/perry-ext-net/src/bun_tcp.rs b/crates/perry-ext-net/src/bun_tcp.rs index 20ad454900..3b6cbc8b85 100644 --- a/crates/perry-ext-net/src/bun_tcp.rs +++ b/crates/perry-ext-net/src/bun_tcp.rs @@ -205,8 +205,8 @@ fn dispatch_one(callback: i64, socket: i64) { } let frame = crate::dispatch_custody::DispatchFrame::park(vec![callback]); unsafe { - let _ = - JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader).call1(handle_value(socket)); + let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader) + .call1(perry_ffi::JsThis::UNDEFINED, handle_value(socket)); } } @@ -217,8 +217,11 @@ fn dispatch_two(callback: i64, socket: i64, payload: f64) { let mut frame = crate::dispatch_custody::DispatchFrame::park(vec![callback]); frame.set_payload(payload.to_bits()); unsafe { - let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader) - .call2(handle_value(socket), f64::from_bits(frame.payload_bits())); + let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader).call2( + perry_ffi::JsThis::UNDEFINED, + handle_value(socket), + f64::from_bits(frame.payload_bits()), + ); } } @@ -229,8 +232,11 @@ fn dispatch_error(callback: i64, socket: i64, message: &str) { let mut frame = crate::dispatch_custody::DispatchFrame::park(vec![callback]); frame.set_payload(unsafe { build_error_object(message) }.to_bits()); unsafe { - let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader) - .call2(handle_value(socket), f64::from_bits(frame.payload_bits())); + let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader).call2( + perry_ffi::JsThis::UNDEFINED, + handle_value(socket), + f64::from_bits(frame.payload_bits()), + ); } } @@ -483,8 +489,11 @@ fn dispatch_data(handle: i64, bytes: &Bytes) { let mut frame = frame; frame.set_payload(POINTER_TAG | (buffer as u64 & POINTER_MASK)); unsafe { - let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader) - .call2(handle_value(handle), f64::from_bits(frame.payload_bits())); + let _ = JsClosure::from_raw(frame.cb(0) as *const RawClosureHeader).call2( + perry_ffi::JsThis::UNDEFINED, + handle_value(handle), + f64::from_bits(frame.payload_bits()), + ); } } diff --git a/crates/perry-ext-net/src/dispatch.rs b/crates/perry-ext-net/src/dispatch.rs index eb849954d9..1a9fcf82c6 100644 --- a/crates/perry-ext-net/src/dispatch.rs +++ b/crates/perry-ext-net/src/dispatch.rs @@ -500,7 +500,11 @@ unsafe fn server_method(handle: i64, method: &str, args: &[f64]) -> Option if let Some(callback) = args.first().copied().map(unbox_to_i64) { if callback >= 0x1000 { let cb = JsClosure::from_raw(callback as *const RawClosureHeader); - let _ = cb.call2(null(), crate::js_net_server_get_connections(handle)); + let _ = cb.call2( + perry_ffi::JsThis::UNDEFINED, + null(), + crate::js_net_server_get_connections(handle), + ); } } undefined() diff --git a/crates/perry-ext-net/src/lifecycle.rs b/crates/perry-ext-net/src/lifecycle.rs index 482d4e5565..82b2c05d7e 100644 --- a/crates/perry-ext-net/src/lifecycle.rs +++ b/crates/perry-ext-net/src/lifecycle.rs @@ -95,10 +95,13 @@ pub(crate) unsafe fn dispatch_socket_completion(completion: u64, error: Option f64; - fn js_implicit_this_set(value: f64) -> f64; - fn js_native_call_value(func_value: f64, args_ptr: *const f64, args_len: usize) -> f64; + fn js_native_call_value( + func_value: f64, + this: perry_ffi::JsThis, + args_ptr: *const f64, + args_len: usize, + ) -> f64; } fn is_nullish(v: f64) -> bool { @@ -80,28 +84,42 @@ fn is_callable(v: f64) -> bool { /// `Get(dest, "write")(chunk)` with `this` bound to `dest`. fn generic_write(dest: f64, chunk: f64) { + // The `Get` can run a getter (user code): root the receiver and the + // argument across it and re-read both. + let scope = perry_ffi::TransientRootScope::enter(); + let dest = scope.root_nanbox(dest); + let chunk = scope.root_nanbox(chunk); unsafe { - let write_fn = js_dynamic_object_get_property(dest, c"write".as_ptr(), 5); + let write_fn = js_dynamic_object_get_property(dest.get(), c"write".as_ptr(), 5); if !is_callable(write_fn) { return; } - let prev = js_implicit_this_set(dest); - let args = [chunk]; - let _ = js_native_call_value(write_fn, args.as_ptr(), args.len()); - js_implicit_this_set(prev); + let args = [chunk.get()]; + let _ = js_native_call_value( + write_fn, + perry_ffi::JsThis::from_f64(dest.get()), + args.as_ptr(), + args.len(), + ); } } /// `Get(dest, "end")()` with `this` bound to `dest`. fn generic_end(dest: f64) { + // The `Get` can run a getter (user code): root the receiver across it. + let scope = perry_ffi::TransientRootScope::enter(); + let dest = scope.root_nanbox(dest); unsafe { - let end_fn = js_dynamic_object_get_property(dest, c"end".as_ptr(), 3); + let end_fn = js_dynamic_object_get_property(dest.get(), c"end".as_ptr(), 3); if !is_callable(end_fn) { return; } - let prev = js_implicit_this_set(dest); - let _ = js_native_call_value(end_fn, std::ptr::null(), 0); - js_implicit_this_set(prev); + let _ = js_native_call_value( + end_fn, + perry_ffi::JsThis::from_f64(dest.get()), + std::ptr::null(), + 0, + ); } } @@ -132,8 +150,8 @@ 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 *const u8, 1); - register_closure_arity(pipe_end_forward as *const u8, 0); + register_closure_arity(pipe_data_forward as perry_ffi::JsBody1, 1); + register_closure_arity(pipe_end_forward as perry_ffi::JsBody0, 0); }); } @@ -217,8 +235,8 @@ pub(crate) fn socket_pipe(handle: i64, dest: f64, options: f64) -> f64 { } }; - let data_closure = alloc_closure(pipe_data_forward as *const u8, 1); - let end_closure = alloc_closure(pipe_end_forward as *const u8, 2); + 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); if data_closure.is_null() || end_closure.is_null() { return f64::from_bits(TAG_UNDEFINED_BITS); } diff --git a/crates/perry-ext-net/src/socket_emit.rs b/crates/perry-ext-net/src/socket_emit.rs index e5353e68dc..09a3a5ef8b 100644 --- a/crates/perry-ext-net/src/socket_emit.rs +++ b/crates/perry-ext-net/src/socket_emit.rs @@ -3,7 +3,12 @@ use super::*; extern "C" { - fn js_closure_call_array(closure: i64, args: *const f64, args_len: i64) -> f64; + fn js_closure_call_array( + closure: i64, + this: perry_ffi::JsThis, + args: *const f64, + args_len: i64, + ) -> f64; } type HttpAgentSocketEventHook = extern "C" fn(i64, *const u8, usize); @@ -56,23 +61,23 @@ pub unsafe extern "C" fn js_ext_net_socket_emit( let mut frame = dispatch_custody::DispatchFrame::park(listeners_for(handle, &event)); frame.set_payloads(&arg_bits); // #11227: `emit()` binds `this` to the socket, like every Node emitter. - let this = crate::socket_events::ListenerThis::bind(handle); + let this = crate::socket_events::ListenerThis::of(handle); for index in 0..frame.len() { let callback = frame.cb(index); if callback == 0 { continue; } - this.rebind(); let closure = JsClosure::from_raw(callback as *const RawClosureHeader); match arg_bits.len() { 0 => { - let _ = closure.call0(); + let _ = closure.call0(this.receiver()); } 1 => { - let _ = closure.call1(f64::from_bits(frame.payload_bits())); + let _ = closure.call1(this.receiver(), f64::from_bits(frame.payload_bits())); } 2 => { let _ = closure.call2( + this.receiver(), f64::from_bits(frame.payload_bits_at(0)), f64::from_bits(frame.payload_bits_at(1)), ); @@ -81,12 +86,16 @@ pub unsafe extern "C" fn js_ext_net_socket_emit( let args = (0..arg_bits.len()) .map(|index| f64::from_bits(frame.payload_bits_at(index))) .collect::>(); - let _ = js_closure_call_array(callback, args.as_ptr(), args.len() as i64); + let _ = js_closure_call_array( + callback, + this.receiver(), + args.as_ptr(), + args.len() as i64, + ); } } } let emitted = frame.len() != 0; - drop(this); drop(frame); lifecycle::drain_once_listeners(handle, &event); if statics::http_agent_phases() diff --git a/crates/perry-ext-net/src/socket_events.rs b/crates/perry-ext-net/src/socket_events.rs index cca9333670..aea69f21f5 100644 --- a/crates/perry-ext-net/src/socket_events.rs +++ b/crates/perry-ext-net/src/socket_events.rs @@ -7,67 +7,40 @@ fn socket_receiver(handle: i64) -> f64 { f64::from_bits(0x7FFD_0000_0000_0000 | (handle as u64 & 0x0000_FFFF_FFFF_FFFF)) } -extern "C" { - fn js_implicit_this_set(value: f64) -> f64; -} - /// Node calls every EventEmitter listener with `this` bound to the emitter /// (#11227). Only `'connect'`/`'drain'`/`'readable'` did; every other socket /// and server event ran its `function` listeners with `this === undefined`, /// so undici's `onHttpSocketClose` (`this[kParser]`) threw on `close()`. /// -/// Binds the socket/server handle as the implicit receiver for the life of -/// the guard and restores the caller's on drop — including when a listener -/// unwinds. The displaced receiver can be a heap object that a collection -/// inside the listeners moves, so it is held in a transient root and re-read -/// at the restore (the #9445 shape `js_implicit_this_set`'s docs describe). +/// The receiver every listener of one dispatch is called with: the +/// socket/server handle. A handle id is not a heap address, so it needs no +/// root across the listeners. +#[derive(Clone, Copy)] pub(crate) struct ListenerThis { - receiver: f64, - previous: perry_ffi::TransientRootedNanbox, - _scope: perry_ffi::TransientRootScope, + receiver: perry_ffi::JsThis, } impl ListenerThis { - pub(crate) fn bind(handle: i64) -> Self { - let scope = perry_ffi::TransientRootScope::enter(); - let receiver = socket_receiver(handle); - let previous = scope.root_nanbox(unsafe { js_implicit_this_set(receiver) }); + pub(crate) fn of(handle: i64) -> Self { Self { - receiver, - previous, - _scope: scope, + receiver: perry_ffi::JsThis::from_f64(socket_receiver(handle)), } } - /// Re-install before each listener: the previous one may have left a - /// different binding behind (a handle id is not a heap address, so the - /// receiver itself never moves). - pub(crate) fn rebind(&self) { - unsafe { - js_implicit_this_set(self.receiver); - } - } -} - -impl Drop for ListenerThis { - fn drop(&mut self) { - unsafe { - js_implicit_this_set(self.previous.get()); - } + pub(crate) fn receiver(self) -> perry_ffi::JsThis { + self.receiver } } unsafe fn emit_socket_no_arg(handle: i64, event: &str) { let frame = dispatch_custody::DispatchFrame::park(listeners_for(handle, event)); - let this = ListenerThis::bind(handle); + let this = ListenerThis::of(handle); for index in 0..frame.len() { let callback = frame.cb(index); if callback != 0 { - this.rebind(); - let _ = JsClosure::from_raw(callback as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(callback as *const RawClosureHeader).call0(this.receiver()); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(handle, event); } @@ -80,16 +53,14 @@ unsafe fn emit_tls_secure_connect(handle: i64) { if !JsValue::from_bits(identity_error.to_bits()).is_undefined() { let mut frame = dispatch_custody::DispatchFrame::park(listeners_for(handle, "error")); frame.set_payload(identity_error.to_bits()); - let this = ListenerThis::bind(handle); + let this = ListenerThis::of(handle); for index in 0..frame.len() { let callback = frame.cb(index); if callback != 0 { - this.rebind(); let _ = JsClosure::from_raw(callback as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(handle, "error"); if let Some(socket) = statics::sockets().lock().unwrap().get_mut(&handle) { @@ -222,16 +193,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { f64::from_bits(0x7FFD_0000_0000_0000 | (buf as u64 & 0x0000_FFFF_FFFF_FFFF)) }; frame.set_payload(payload_f64.to_bits()); - let this = ListenerThis::bind(id); + let this = ListenerThis::of(id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); let _ = JsClosure::from_raw(cb as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(id, "data"); } @@ -256,16 +225,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { // raw NaN-boxed string and `err.message` was `undefined`. let err_f64 = build_error_object(&msg); frame.set_payload(err_f64.to_bits()); - let this = ListenerThis::bind(id); + let this = ListenerThis::of(id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); let _ = JsClosure::from_raw(cb as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(id, "error"); } @@ -279,16 +246,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { fn js_abort_error_value() -> f64; } frame.set_payload(js_abort_error_value().to_bits()); - let this = ListenerThis::bind(id); + let this = ListenerThis::of(id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); let _ = JsClosure::from_raw(cb as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(id, "error"); } @@ -304,15 +269,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { // listener-map / socket-map teardown, so don't remove // anything here. let frame = dispatch_custody::DispatchFrame::park(listeners_for(id, "end")); - let this = ListenerThis::bind(id); + let this = ListenerThis::of(id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); - let _ = JsClosure::from_raw(cb as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(cb as *const RawClosureHeader) + .call0(this.receiver()); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(id, "end"); // P1: a turnloop socket has no task to run the post-EOF @@ -350,15 +314,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { } let had_error = f64::from_bits(JsValue::from_bool(false).bits()); let frame = dispatch_custody::DispatchFrame::park(listeners_for(id, "close")); - let this = ListenerThis::bind(id); + let this = ListenerThis::of(id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); - let _ = JsClosure::from_raw(cb as *const RawClosureHeader).call1(had_error); + let _ = JsClosure::from_raw(cb as *const RawClosureHeader) + .call1(this.receiver(), had_error); } } - drop(this); drop(frame); statics::listeners().lock().unwrap().remove(&id); statics::sockets().lock().unwrap().remove(&id); @@ -409,15 +372,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { // #8259: sock_f64 is a handle id (not a heap address), so // only the callbacks need custody. let frame = dispatch_custody::DispatchFrame::park(cbs); - let this = ListenerThis::bind(server_id); + let this = ListenerThis::of(server_id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); - let _ = JsClosure::from_raw(cb as *const RawClosureHeader).call1(sock_f64); + let _ = JsClosure::from_raw(cb as *const RawClosureHeader) + .call1(this.receiver(), sock_f64); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(server_id, "connection"); server_state::release_pending_server_data(socket_id); @@ -443,15 +405,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { // so this frame is their ONLY root during dispatch — without // it a collection here can free, not just move, them. let frame = dispatch_custody::DispatchFrame::park(cbs); - let this = ListenerThis::bind(server_id); + let this = ListenerThis::of(server_id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); - let _ = JsClosure::from_raw(cb as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(cb as *const RawClosureHeader) + .call0(this.receiver()); } } - drop(this); drop(frame); } PendingNetEvent::ServerClose(server_id) => { @@ -467,15 +428,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { // #8259: removed from the table above — custody is the only // root; see the ServerListening arm. let frame = dispatch_custody::DispatchFrame::park(cbs); - let this = ListenerThis::bind(server_id); + let this = ListenerThis::of(server_id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); - let _ = JsClosure::from_raw(cb as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(cb as *const RawClosureHeader) + .call0(this.receiver()); } } - drop(this); drop(frame); // Tear down the server entry so the keepalive gate // (`js_ext_net_has_active_handles`) lets the runtime @@ -499,16 +459,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { let mut frame = dispatch_custody::DispatchFrame::park(cbs); let err_f64 = build_error_object(&msg); frame.set_payload(err_f64.to_bits()); - let this = ListenerThis::bind(server_id); + let this = ListenerThis::of(server_id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); let _ = JsClosure::from_raw(cb as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(server_id, "error"); } @@ -522,16 +480,14 @@ pub unsafe extern "C" fn js_ext_net_drain_pending() -> i32 { let mut frame = dispatch_custody::DispatchFrame::park(cbs); let info = server_state::build_drop_object(&info); frame.set_payload(info.to_bits()); - let this = ListenerThis::bind(server_id); + let this = ListenerThis::of(server_id); for i in 0..frame.len() { let cb = frame.cb(i); if cb != 0 { - this.rebind(); let _ = JsClosure::from_raw(cb as *const RawClosureHeader) - .call1(f64::from_bits(frame.payload_bits())); + .call1(this.receiver(), f64::from_bits(frame.payload_bits())); } } - drop(this); drop(frame); lifecycle::drain_once_listeners(server_id, "drop"); } @@ -562,37 +518,59 @@ mod listener_this_tests { use super::*; const UNDEFINED: u64 = 0x7FFC_0000_0000_0001; - const NULL: u64 = 0x7FFC_0000_0000_0002; + /// Not a NaN-boxed value any call passes: "the listener never ran". + const NOT_CALLED: f64 = 0.5; + + /// Records the receiver it was called with in its own capture slot 0 (a + /// slot the collector sees), so no static holds a heap value. + extern "C" fn record_this(closure: *const RawClosureHeader, this: perry_ffi::JsThis) -> f64 { + unsafe { + perry_ffi::set_closure_capture_f64(closure as *mut RawClosureHeader, 0, this.as_f64()) + }; + f64::from_bits(UNDEFINED) + } + + 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); + unsafe { perry_ffi::set_closure_capture_f64(closure, 0, NOT_CALLED) }; + closure as i64 + } + + fn seen_this(listener: i64) -> u64 { + unsafe { perry_ffi::closure_capture_f64(listener as *const RawClosureHeader, 0) }.to_bits() + } - /// #11227: every listener dispatch binds the emitter as `this`, rebinding - /// clobbers a leftover receiver, and the caller's receiver comes back. + /// #11227: every listener dispatch passes the emitter as `this`. The + /// listener reads `this` only from its receiver parameter, so this fails + /// if the dispatch makes a plain call (the listener would see + /// `undefined`). #[test] - fn binds_the_handle_and_restores_the_callers_receiver() { - let caller = f64::from_bits(UNDEFINED); - unsafe { js_implicit_this_set(caller) }; - { - let this = ListenerThis::bind(42); - let seen = unsafe { js_implicit_this_set(f64::from_bits(NULL)) }; - assert_eq!(seen.to_bits(), socket_receiver(42).to_bits()); - this.rebind(); - let seen = unsafe { js_implicit_this_set(socket_receiver(42)) }; - assert_eq!(seen.to_bits(), socket_receiver(42).to_bits()); - } - let restored = unsafe { js_implicit_this_set(caller) }; - assert_eq!(restored.to_bits(), UNDEFINED); + fn listeners_receive_the_emitting_handle_as_this() { + const HANDLE: i64 = 0x7A11_0042; + let listener = recording_listener(); + statics::listeners() + .lock() + .unwrap() + .entry(HANDLE) + .or_default() + .insert("probe".to_string(), vec![listener]); + unsafe { emit_socket_no_arg(HANDLE, "probe") }; + statics::listeners().lock().unwrap().remove(&HANDLE); + assert_eq!(seen_this(listener), socket_receiver(HANDLE).to_bits()); } + /// The negative control: the same listener called plainly sees + /// `undefined`, so the test above observes the receiver the dispatch + /// passed and nothing left over from an earlier call. #[test] - fn nested_dispatch_restores_the_outer_emitter() { - unsafe { js_implicit_this_set(f64::from_bits(UNDEFINED)) }; - let outer = ListenerThis::bind(7); - { - let _inner = ListenerThis::bind(9); - } - let seen = unsafe { js_implicit_this_set(socket_receiver(7)) }; - assert_eq!(seen.to_bits(), socket_receiver(7).to_bits()); - drop(outer); - let restored = unsafe { js_implicit_this_set(f64::from_bits(UNDEFINED)) }; - assert_eq!(restored.to_bits(), UNDEFINED); + fn a_plain_call_of_the_same_listener_sees_undefined() { + let listener = recording_listener(); + let _ = unsafe { + JsClosure::from_raw(listener as *const RawClosureHeader) + .call0(perry_ffi::JsThis::UNDEFINED) + }; + assert_eq!(seen_this(listener), UNDEFINED); } } diff --git a/crates/perry-ext-parcel-watcher/src/lib.rs b/crates/perry-ext-parcel-watcher/src/lib.rs index 181369b9fa..c4232fdaa5 100644 --- a/crates/perry-ext-parcel-watcher/src/lib.rs +++ b/crates/perry-ext-parcel-watcher/src/lib.rs @@ -553,7 +553,11 @@ fn fire_events(id: u64, events: Vec) -> bool { let callback = scope.root_addr(callback); let closure = unsafe { JsClosure::from_raw(callback.get() as *const RawClosureHeader) }; unsafe { - closure.call2(f64::from_bits(JsValue::NULL.bits()), events_value.get()); + closure.call2( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(JsValue::NULL.bits()), + events_value.get(), + ); } true } @@ -575,7 +579,7 @@ fn fire_error(id: u64, message: &str) -> bool { let callback = scope.root_addr(callback); let closure = unsafe { JsClosure::from_raw(callback.get() as *const RawClosureHeader) }; unsafe { - closure.call2(error.get(), events.get()); + closure.call2(perry_ffi::JsThis::UNDEFINED, error.get(), events.get()); } true } diff --git a/crates/perry-ext-streams/src/lib.rs b/crates/perry-ext-streams/src/lib.rs index 3a4b95d143..2b55bd3abc 100644 --- a/crates/perry-ext-streams/src/lib.rs +++ b/crates/perry-ext-streams/src/lib.rs @@ -428,7 +428,7 @@ unsafe fn invoke_start(stream_id: usize) { }; if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call1(controller); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, controller); } } @@ -452,7 +452,7 @@ unsafe fn maybe_pull(stream_id: usize) { return; } let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call1(controller); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, controller); if let Some(s) = READABLE_STREAMS.lock().unwrap().get_mut(&stream_id) { s.pulling = false; } @@ -597,7 +597,7 @@ pub unsafe extern "C" fn js_readable_stream_cancel( }; if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call1(reason); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, reason); } close_pending(id); promise_resolve_bits(promise, TAG_UNDEFINED); @@ -949,7 +949,7 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( .unwrap_or(0); if write_cb != 0 { let closure = JsClosure::from_raw(write_cb as *const RawClosureHeader); - let _ = closure.call1(f64::from_bits(chunk)); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, f64::from_bits(chunk)); } } } @@ -970,7 +970,7 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( .unwrap_or(0); if close_cb != 0 { let closure = JsClosure::from_raw(close_cb as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } if let Some(w) = WRITABLE_STREAMS.lock().unwrap().get_mut(&w_id) { w.state = WritableState::Closed; @@ -1073,7 +1073,7 @@ pub unsafe extern "C" fn js_writable_stream_close(stream_handle: f64) -> *mut Pr }; if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call0(); + let _ = closure.call0(perry_ffi::JsThis::UNDEFINED); } if !closed_p.is_null() { promise_resolve_bits(closed_p, TAG_UNDEFINED); @@ -1100,7 +1100,7 @@ pub unsafe extern "C" fn js_writable_stream_abort(stream_handle: f64, reason: f6 }; if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call1(reason); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, reason); } if !closed_p.is_null() { promise_reject_bits(closed_p, reason_bits); @@ -1145,7 +1145,7 @@ pub unsafe extern "C" fn js_writer_write(writer_handle: f64, chunk: f64) -> *mut }; if cb != 0 { let closure = JsClosure::from_raw(cb as *const RawClosureHeader); - let _ = closure.call1(chunk); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, chunk); } promise_resolve_bits(promise, TAG_UNDEFINED); promise @@ -1349,7 +1349,7 @@ unsafe fn transform_write(writable_id: usize, chunk: f64) -> *mut Promise { }; if transform_cb != 0 && readable_id != 0 { let closure = JsClosure::from_raw(transform_cb as *const RawClosureHeader); - let _ = closure.call2(chunk, readable_id as f64); + let _ = closure.call2(perry_ffi::JsThis::UNDEFINED, chunk, readable_id as f64); } else { // Identity transform — pass-through. js_readable_stream_controller_enqueue(readable_id as f64, chunk); @@ -1375,7 +1375,7 @@ unsafe fn transform_close(writable_id: usize) -> *mut Promise { }; if flush_cb != 0 && readable_id != 0 { let closure = JsClosure::from_raw(flush_cb as *const RawClosureHeader); - let _ = closure.call1(readable_id as f64); + let _ = closure.call1(perry_ffi::JsThis::UNDEFINED, readable_id as f64); } if readable_id != 0 { js_readable_stream_controller_close(readable_id as f64); diff --git a/crates/perry-ext-typescript/src/bun.rs b/crates/perry-ext-typescript/src/bun.rs index c3bd20acf3..6017de4459 100644 --- a/crates/perry-ext-typescript/src/bun.rs +++ b/crates/perry-ext-typescript/src/bun.rs @@ -694,9 +694,9 @@ fn ensure_build_runtime_registered() { ); }); NATIVE_CLOSURE_ARITIES_REGISTERED.call_once(|| { - register_closure_arity(bun_on_resolve as *const u8, 2); - register_closure_arity(bun_on_load as *const u8, 2); - register_closure_arity(bun_build_output_text as *const u8, 0); + 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); }); } @@ -797,7 +797,7 @@ extern "C" fn bun_on_load( register_plugin_hook(closure, options, callback, false) } -fn native_closure_value(function: *const u8, session: Handle) -> JsValue { +fn native_closure_value(function: perry_ffi::JsBody2, session: Handle) -> JsValue { let closure = alloc_closure(function, 1); if closure.is_null() { return JsValue::UNDEFINED; @@ -832,10 +832,10 @@ fn configure_plugins(options: f64) -> Handle { return session; } let on_resolve = scope.root_nanbox(f64::from_bits( - native_closure_value(bun_on_resolve as *const u8, session).bits(), + native_closure_value(bun_on_resolve as perry_ffi::JsBody2, session).bits(), )); let on_load = scope.root_nanbox(f64::from_bits( - native_closure_value(bun_on_load as *const u8, session).bits(), + native_closure_value(bun_on_load as perry_ffi::JsBody2, session).bits(), )); let builder = scope.root_nanbox(f64::from_bits( alloc_null_proto_object(&[ @@ -846,7 +846,7 @@ fn configure_plugins(options: f64) -> Handle { )); for callback in callbacks { let closure = unsafe { JsClosure::from_raw(callback.get() as *const RawClosureHeader) }; - unsafe { closure.call1(builder.get()) }; + unsafe { closure.call1(perry_ffi::JsThis::UNDEFINED, builder.get()) }; } session } @@ -894,7 +894,7 @@ fn invoke_hook(hook: &PluginHook, args: f64) -> JsValue { let callback = scope.root_addr(hook.callback); let args = scope.root_nanbox(args); let closure = unsafe { JsClosure::from_raw(callback.get() as *const RawClosureHeader) }; - JsValue::from_bits(unsafe { closure.call1(args.get()) }.to_bits()) + JsValue::from_bits(unsafe { closure.call1(perry_ffi::JsThis::UNDEFINED, args.get()) }.to_bits()) } fn file_parts(file: &FileName) -> (String, String) { @@ -1312,7 +1312,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 = alloc_closure(bun_build_output_text as *const u8, 1); + let text = perry_ffi::alloc_closure(bun_build_output_text as perry_ffi::JsBody0, 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()); diff --git a/crates/perry-ext-ws/src/lib.rs b/crates/perry-ext-ws/src/lib.rs index 55ee0ece1b..6a913116bf 100644 --- a/crates/perry-ext-ws/src/lib.rs +++ b/crates/perry-ext-ws/src/lib.rs @@ -1325,7 +1325,11 @@ pub unsafe extern "C" fn js_ws_handle_upgrade( // duplicates the usual `wss.emit("connection", ws, req)` idiom. if cb.get() != 0 { let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); - let _ = closure.call2(f64::from_bits(client_js_value(ws_id).bits()), req.get()); + let _ = closure.call2( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(client_js_value(ws_id).bits()), + req.get(), + ); } } @@ -1378,7 +1382,10 @@ pub extern "C" fn js_ws_process_pending() -> i32 { // Use the same handle value as the clients Set and // manual-upgrade callback, including dynamic dispatch. let _ = unsafe { - closure.call1(f64::from_bits(client_js_value(client_id).bits())) + closure.call1( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(client_js_value(client_id).bits()), + ) }; fired += 1; } @@ -1400,7 +1407,9 @@ pub extern "C" fn js_ws_process_pending() -> i32 { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call2(msg_f64, binary_f64) }; + let _ = unsafe { + closure.call2(perry_ffi::JsThis::UNDEFINED, msg_f64, binary_f64) + }; fired += 1; } } @@ -1415,6 +1424,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; let _ = unsafe { closure.call2( + perry_ffi::JsThis::UNDEFINED, f64::from_bits(client_js_value(ws_id).bits()), msg_f64, ) @@ -1433,7 +1443,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call1(value) }; + let _ = unsafe { closure.call1(perry_ffi::JsThis::UNDEFINED, value) }; fired += 1; } } @@ -1447,7 +1457,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call1(value) }; + let _ = unsafe { closure.call1(perry_ffi::JsThis::UNDEFINED, value) }; fired += 1; } } @@ -1472,7 +1482,9 @@ pub extern "C" fn js_ws_process_pending() -> i32 { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call2(code_f64, reason_f64) }; + let _ = unsafe { + closure.call2(perry_ffi::JsThis::UNDEFINED, code_f64, reason_f64) + }; fired += 1; } } @@ -1485,7 +1497,10 @@ pub extern "C" fn js_ws_process_pending() -> i32 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; let _ = unsafe { - closure.call1(f64::from_bits(client_js_value(ws_id).bits())) + closure.call1( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(client_js_value(ws_id).bits()), + ) }; fired += 1; } @@ -1502,8 +1517,10 @@ pub extern "C" fn js_ws_process_pending() -> i32 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; let s = alloc_string(&err); let _ = unsafe { - closure - .call1(f64::from_bits(JsValue::from_string_ptr(s.as_raw()).bits())) + closure.call1( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(JsValue::from_string_ptr(s.as_raw()).bits()), + ) }; fired += 1; } @@ -1516,8 +1533,10 @@ pub extern "C" fn js_ws_process_pending() -> i32 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; let s = alloc_string(&err); let _ = unsafe { - closure - .call1(f64::from_bits(JsValue::from_string_ptr(s.as_raw()).bits())) + closure.call1( + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(JsValue::from_string_ptr(s.as_raw()).bits()), + ) }; fired += 1; } @@ -1528,7 +1547,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { for cb in listeners { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call0() }; + let _ = unsafe { closure.call0(perry_ffi::JsThis::UNDEFINED) }; fired += 1; } } @@ -1537,7 +1556,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { for cb in listeners_on_server(server_handle, "close") { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call0() }; + let _ = unsafe { closure.call0(perry_ffi::JsThis::UNDEFINED) }; fired += 1; } } @@ -1549,7 +1568,7 @@ pub extern "C" fn js_ws_process_pending() -> i32 { for cb in listeners { if cb != 0 { let closure = unsafe { JsClosure::from_raw(cb as *const RawClosureHeader) }; - let _ = unsafe { closure.call0() }; + let _ = unsafe { closure.call0(perry_ffi::JsThis::UNDEFINED) }; fired += 1; } } diff --git a/crates/perry-ext-ws/src/server.rs b/crates/perry-ext-ws/src/server.rs index 98cc1a5841..1e2a54674c 100644 --- a/crates/perry-ext-ws/src/server.rs +++ b/crates/perry-ext-ws/src/server.rs @@ -335,13 +335,13 @@ fn emit_server_event(handle: Handle, event: &str, first: f64, second: f64, argc: let closure = JsClosure::from_raw(cb.get() as *const RawClosureHeader); match argc { 0 => { - closure.call0(); + closure.call0(perry_ffi::JsThis::UNDEFINED); } 1 => { - closure.call1(first.get()); + closure.call1(perry_ffi::JsThis::UNDEFINED, first.get()); } _ => { - closure.call2(first.get(), second.get()); + closure.call2(perry_ffi::JsThis::UNDEFINED, first.get(), second.get()); } } } diff --git a/crates/perry-ext-zlib/src/lib.rs b/crates/perry-ext-zlib/src/lib.rs index 1ce6214788..dc3a901b37 100644 --- a/crates/perry-ext-zlib/src/lib.rs +++ b/crates/perry-ext-zlib/src/lib.rs @@ -570,8 +570,8 @@ 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 *const u8, 2); - let callback = alloc_closure(record_dispatch_callback as *const u8, 0); + register_closure_arity(record_dispatch_callback as perry_ffi::JsBody2, 2); + let callback = alloc_closure(record_dispatch_callback as perry_ffi::JsBody2, 0); assert!(!callback.is_null()); let input = alloc_buffer(b"captured zlib export"); @@ -614,9 +614,9 @@ mod tests { } DISPATCH_CALLBACK_FIRED.with(|fired| fired.set(false)); let scope = perry_ffi::TransientRootScope::enter(); - register_closure_arity(callback as *const u8, 2); + 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 *const u8, 0)).bits(), + JsValue::from_object_ptr(alloc_closure(callback as perry_ffi::JsBody2, 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-ext-zlib/src/stream.rs b/crates/perry-ext-zlib/src/stream.rs index 91f7236bc3..21e14d8fb3 100644 --- a/crates/perry-ext-zlib/src/stream.rs +++ b/crates/perry-ext-zlib/src/stream.rs @@ -733,13 +733,19 @@ unsafe fn call_one_shot_callback(callback: i64, result: Result, String>) make_buffer_f64(&bytes) .unwrap_or_else(|| f64::from_bits(JsValue::UNDEFINED.bits())), ); - let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) - .call2(err, out.get()); + let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader).call2( + perry_ffi::JsThis::UNDEFINED, + err, + out.get(), + ); } Err(msg) => { let err = roots.root_nanbox(build_error_object(&msg)); - let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) - .call2(err.get(), f64::from_bits(JsValue::UNDEFINED.bits())); + let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader).call2( + perry_ffi::JsThis::UNDEFINED, + err.get(), + f64::from_bits(JsValue::UNDEFINED.bits()), + ); } } } @@ -1357,7 +1363,7 @@ unsafe extern "C" fn zlib_event_dispatch_thunk(data: *mut c_void) -> f64 { if callback.get() != 0 { let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) - .call1(buffer.get()); + .call1(perry_ffi::JsThis::UNDEFINED, buffer.get()); } } } @@ -1371,7 +1377,8 @@ unsafe extern "C" fn zlib_event_dispatch_thunk(data: *mut c_void) -> f64 { let roots = TransientRootScope::enter(); for callback in roots.root_addrs(&listeners_for(id, "finish")) { if callback.get() != 0 { - let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) + .call0(perry_ffi::JsThis::UNDEFINED); } } } @@ -1387,7 +1394,8 @@ unsafe extern "C" fn zlib_event_dispatch_thunk(data: *mut c_void) -> f64 { drop_buffered_stream(&mut statics().lock().unwrap(), id); for callback in end_callbacks { if callback.get() != 0 { - let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) + .call0(perry_ffi::JsThis::UNDEFINED); } } for destination in destinations { @@ -1395,7 +1403,8 @@ unsafe extern "C" fn zlib_event_dispatch_thunk(data: *mut c_void) -> f64 { } for callback in close_callbacks { if callback.get() != 0 { - let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) + .call0(perry_ffi::JsThis::UNDEFINED); } } } @@ -1407,13 +1416,14 @@ unsafe extern "C" fn zlib_event_dispatch_thunk(data: *mut c_void) -> f64 { for callback in callbacks { if callback.get() != 0 { let _ = JsClosure::from_raw(callback.get() as *const RawClosureHeader) - .call1(error.get()); + .call1(perry_ffi::JsThis::UNDEFINED, error.get()); } } } ZlibEvent::Callback(callback) => { if callback != 0 { - let _ = JsClosure::from_raw(callback as *const RawClosureHeader).call0(); + let _ = JsClosure::from_raw(callback as *const RawClosureHeader) + .call0(perry_ffi::JsThis::UNDEFINED); } } ZlibEvent::OneShotCallback(_, _, _) => { diff --git a/crates/perry-ffi/Cargo.toml b/crates/perry-ffi/Cargo.toml index 5092fd3d7b..30c8ad65e3 100644 --- a/crates/perry-ffi/Cargo.toml +++ b/crates/perry-ffi/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "perry-ffi" -version.workspace = true +# Its own version, not the workspace train: 0.6 is the JS body ABI with the +# receiver as a parameter (typed `JsBody` registration, receiver-taking calls), +# incompatible with every 0.5 addon. +version = "0.6.0" edition.workspace = true license.workspace = true description = "Stable FFI surface for native bindings packages that wrap Rust crates for Perry. Wrapper authors depend on this; perry-runtime internals stay free to evolve." @@ -17,6 +20,8 @@ workspace = true [dependencies] perry-native-registration.workspace = true +# The JS body ABI (`JsThis`, `js_body_fn_ty!`, `JsBody*`), shared with the runtime. +perry-abi.workspace = true # #1112: `perry-runtime` is restored as an OPTIONAL regular dep (not # a dev-dep) so the `runtime-link` feature actually propagates the diff --git a/crates/perry-ffi/src/closure.rs b/crates/perry-ffi/src/closure.rs index 004e32aa2a..dd5a6ac40c 100644 --- a/crates/perry-ffi/src/closure.rs +++ b/crates/perry-ffi/src/closure.rs @@ -1,6 +1,4 @@ -//! JavaScript closure invocation across the FFI boundary (added -//! in v0.5.x of the perry-ffi v0.5 surface — non-breaking; pure -//! additions). +//! JavaScript closure invocation across the FFI boundary. //! //! Many wrappers need to call back into TypeScript-side //! functions: @@ -18,9 +16,9 @@ //! //! All of these consume a `*const ClosureHeader` (the runtime's //! closure layout) and call it via `js_closure_call0` / -//! `js_closure_call1` / etc. — perry-runtime exports those as -//! `extern "C"` already, so perry-ffi just declares them and -//! exposes a typed [`JsClosure`] wrapper. +//! `js_closure_call1` / etc., passing the receiver (`this`) after the +//! closure — perry-runtime exports those as `extern "C"`, so perry-ffi +//! declares them and exposes a typed [`JsClosure`] wrapper. //! //! # Argument / return ABI //! @@ -41,55 +39,145 @@ use crate::ClosureHeader; pub use crate::ClosureHeader as RawClosureHeader; +/// The receiver every native closure body takes as its SECOND parameter, +/// and every call into a JS function passes: the NaN-boxed `this` bits, in +/// an integer register ([`JsThis::UNDEFINED`] for a plain call). Defined +/// once in perry-abi and shared with the runtime. +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: +/// +/// ```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); +/// ``` +/// +/// A JS body declaring 0 JS parameters. +pub type JsBody0 = perry_abi::JsBody0; +/// A JS body declaring 1 JS parameters. +pub type JsBody1 = perry_abi::JsBody1; +/// A JS body declaring 2 JS parameters. +pub type JsBody2 = perry_abi::JsBody2; +/// A JS body declaring 3 JS parameters. +pub type JsBody3 = perry_abi::JsBody3; +/// A JS body declaring 4 JS parameters. +pub type JsBody4 = perry_abi::JsBody4; +/// A JS body declaring 5 JS parameters. +pub type JsBody5 = perry_abi::JsBody5; +/// A JS body declaring 6 JS parameters. +pub type JsBody6 = perry_abi::JsBody6; +/// A JS body declaring 7 JS parameters. +pub type JsBody7 = perry_abi::JsBody7; +/// A JS body declaring 8 JS parameters. +pub type JsBody8 = perry_abi::JsBody8; +/// A JS body declaring 9 JS parameters. +pub type JsBody9 = perry_abi::JsBody9; +/// A JS body declaring 10 JS parameters. +pub type JsBody10 = perry_abi::JsBody10; +/// A JS body declaring 11 JS parameters. +pub type JsBody11 = perry_abi::JsBody11; +/// A JS body declaring 12 JS parameters. +pub type JsBody12 = perry_abi::JsBody12; +/// A JS body declaring 13 JS parameters. +pub type JsBody13 = perry_abi::JsBody13; +/// A JS body declaring 14 JS parameters. +pub type JsBody14 = perry_abi::JsBody14; +/// A JS body declaring 15 JS parameters. +pub type JsBody15 = perry_abi::JsBody15; +/// A JS body declaring 16 JS parameters. +pub type JsBody16 = perry_abi::JsBody16; +/// A JS body of any arity over perry-ffi's closure header. +pub use perry_abi::JsBody; + extern "C" { - fn js_closure_call0(closure: *const ClosureHeader) -> f64; - fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> f64; - fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64; - fn js_closure_call3(closure: *const ClosureHeader, arg0: f64, arg1: f64, arg2: f64) -> f64; + fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64; + fn js_closure_call1(closure: *const ClosureHeader, this: JsThis, arg0: f64) -> f64; + fn js_closure_call2(closure: *const ClosureHeader, this: JsThis, arg0: f64, arg1: f64) -> f64; + fn js_closure_call3( + closure: *const ClosureHeader, + this: JsThis, + arg0: f64, + arg1: f64, + arg2: f64, + ) -> f64; fn js_closure_call4( closure: *const ClosureHeader, + this: JsThis, arg0: f64, arg1: f64, arg2: f64, arg3: f64, ) -> f64; + fn js_closure_call_array( + closure: *const ClosureHeader, + this: JsThis, + args: *const f64, + args_len: i64, + ) -> f64; + fn js_native_call_value( + func_value: f64, + this: JsThis, + 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_get_capture_f64(closure: *const ClosureHeader, index: u32) -> f64; fn js_closure_set_capture_f64(closure: *mut ClosureHeader, index: u32, value: f64); } -/// The receiver every native closure body takes as its SECOND parameter: -/// the NaN-boxed `this` bits, passed in an integer register. A body a -/// wrapper hands to [`alloc_closure`] must be declared -/// -/// ```ignore -/// extern "C" fn body(closure: *const RawClosureHeader, this: JsThis, a0: f64, ...) -> f64 -/// ``` -/// -/// (perry's JS body ABI, `perry_abi::JS_BODY_*`). `repr(transparent)` over -/// `u64`, so it is ABI-identical to perry-runtime's `closure::JsThis`. -#[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct JsThis(pub u64); - -impl JsThis { - /// The receiver bits. - pub const fn bits(self) -> u64 { - self.0 - } +/// 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) } } -/// Register the arity the runtime uses when dispatching a native closure. -pub fn register_closure_arity(func: *const u8, arity: u32) { - unsafe { js_register_closure_arity(func, arity) } +/// 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) } } -/// Allocate a native closure with `capture_count` f64 capture slots. `func` -/// is a body declared with the receiver as its second parameter (see -/// [`JsThis`]). -pub fn alloc_closure(func: *const u8, capture_count: u32) -> *mut ClosureHeader { - unsafe { js_closure_alloc(func, capture_count) } +/// Allocate a native closure running `func` with `capture_count` f64 +/// capture slots. +/// +/// `func` must be a JS body pointer type; anything else 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); +/// ``` +/// +/// 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); +/// ``` +/// +/// 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); +/// ``` +pub fn alloc_closure>(func: F, capture_count: u32) -> *mut ClosureHeader { + unsafe { js_closure_alloc(func.code(), capture_count) } } /// Read an f64 capture slot from a native closure. @@ -108,14 +196,22 @@ pub unsafe fn set_closure_capture_f64(closure: *mut ClosureHeader, index: u32, v js_closure_set_capture_f64(closure, index, value) } +/// Call any JS value as a function with receiver `this` +/// ([`JsThis::UNDEFINED`] for a plain call): what `func.call(this, ...args)` +/// does. A value that is not callable throws a JS `TypeError`. +/// +/// # Safety +/// As [`JsClosure::call0`]; `args` must stay valid for the call. +pub unsafe fn call_value(func: f64, this: JsThis, args: &[f64]) -> f64 { + js_native_call_value(func, this, args.as_ptr(), args.len()) +} + /// Opaque handle to a JS closure (a `*const ClosureHeader`). /// /// Wrapper authors receive a `*const ClosureHeader` from their /// FFI parameter list, convert it via [`JsClosure::from_raw`], -/// then call zero through four args via the `call_*` methods. -/// Beyond 4 args, drop down to the raw extern fns — -/// perry-runtime exports `js_closure_call5..8` for completeness; -/// they're not yet wrapped here because no wrapper needs them. +/// then call it through the `call*` methods, each taking the receiver +/// first ([`JsThis::UNDEFINED`] for a plain call). #[repr(transparent)] #[derive(Copy, Clone)] pub struct JsClosure(*const ClosureHeader); @@ -149,9 +245,10 @@ impl JsClosure { self.0 } - /// Invoke the closure with no arguments. Returns the result - /// as a NaN-boxed f64 (the runtime's standard return ABI for - /// dynamic JS calls). + /// Invoke the closure with no arguments and receiver `this` + /// ([`JsThis::UNDEFINED`] for a plain call; a sloppy body sees + /// globalThis). Returns the result as a NaN-boxed f64 (the runtime's + /// standard return ABI for dynamic JS calls). /// /// # Safety /// @@ -160,28 +257,33 @@ impl JsClosure { /// the runtime / arena, so callers must not hold any /// references that would alias with allocations the closure /// may make. - pub unsafe fn call0(self) -> f64 { - js_closure_call0(self.0) + pub unsafe fn call0(self, this: JsThis) -> f64 { + js_closure_call0(self.0, this) + } + + /// Invoke with one argument. See [`Self::call0`]. + pub unsafe fn call1(self, this: JsThis, arg0: f64) -> f64 { + js_closure_call1(self.0, this, arg0) } - /// Invoke with one argument. See [`Self::call0`] safety. - pub unsafe fn call1(self, arg0: f64) -> f64 { - js_closure_call1(self.0, arg0) + /// Invoke with two arguments. See [`Self::call0`]. + pub unsafe fn call2(self, this: JsThis, arg0: f64, arg1: f64) -> f64 { + js_closure_call2(self.0, this, arg0, arg1) } - /// Invoke with two arguments. See [`Self::call0`] safety. - pub unsafe fn call2(self, arg0: f64, arg1: f64) -> f64 { - js_closure_call2(self.0, arg0, arg1) + /// Invoke with three arguments. See [`Self::call0`]. + pub unsafe fn call3(self, this: JsThis, arg0: f64, arg1: f64, arg2: f64) -> f64 { + js_closure_call3(self.0, this, arg0, arg1, arg2) } - /// Invoke with three arguments. See [`Self::call0`] safety. - pub unsafe fn call3(self, arg0: f64, arg1: f64, arg2: f64) -> f64 { - js_closure_call3(self.0, arg0, arg1, arg2) + /// Invoke with four arguments. See [`Self::call0`]. + pub unsafe fn call4(self, this: JsThis, arg0: f64, arg1: f64, arg2: f64, arg3: f64) -> f64 { + js_closure_call4(self.0, this, arg0, arg1, arg2, arg3) } - /// Invoke with four arguments. See [`Self::call0`] safety. - pub unsafe fn call4(self, arg0: f64, arg1: f64, arg2: f64, arg3: f64) -> f64 { - js_closure_call4(self.0, arg0, arg1, arg2, arg3) + /// Invoke with any number of arguments. See [`Self::call0`]. + pub unsafe fn call_slice(self, this: JsThis, args: &[f64]) -> f64 { + js_closure_call_array(self.0, this, args.as_ptr(), args.len() as i64) } } @@ -202,8 +304,8 @@ mod tests { unsafe extern "C" fn callback(_: *const ClosureHeader, _this: crate::JsThis) -> f64 { 0.0 } - register_closure_arity(callback as *const u8, 0); - let closure = alloc_closure(callback as *const u8, 1); + register_closure_arity(callback as JsBody0, 0); + let closure = alloc_closure(callback as JsBody0, 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 e5e462c094..41b9344636 100644 --- a/crates/perry-ffi/src/lib.rs +++ b/crates/perry-ffi/src/lib.rs @@ -106,8 +106,10 @@ pub use jsvalue::{ mod closure; mod transient_roots; pub use closure::{ - alloc_closure, closure_capture_f64, register_closure_arity, set_closure_capture_f64, JsClosure, - JsThis, RawClosureHeader, + 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, }; pub use transient_roots::{TransientRootScope, TransientRootedAddr, TransientRootedNanbox}; diff --git a/crates/perry-runtime/src/arkts_callbacks.rs b/crates/perry-runtime/src/arkts_callbacks.rs index ca7d524b57..bf292ded0a 100644 --- a/crates/perry-runtime/src/arkts_callbacks.rs +++ b/crates/perry-runtime/src/arkts_callbacks.rs @@ -172,7 +172,7 @@ pub extern "C" fn perry_arkts_invoke_callback(idx: i64) -> f64 { return f64::from_bits(TAG_UNDEFINED); } arkts_log(&format!("invoke calling closure idx={}", idx)); - let result = js_closure_call0(raw); + let result = js_closure_call0(raw, crate::closure::plain_call_receiver()); arkts_log(&format!("invoke RETURN idx={}", idx)); result } @@ -211,7 +211,7 @@ pub extern "C" fn perry_arkts_invoke_callback1(idx: i64, arg_d: f64) -> f64 { return f64::from_bits(TAG_UNDEFINED); } arkts_log(&format!("invoke1 calling closure idx={}", idx)); - let result = js_closure_call1(raw, arg_d); + let result = js_closure_call1(raw, crate::closure::plain_call_receiver(), arg_d); arkts_log(&format!("invoke1 RETURN idx={}", idx)); result } diff --git a/crates/perry-runtime/src/array/flat_clone.rs b/crates/perry-runtime/src/array/flat_clone.rs index f012a970e0..b1cd2c1c56 100644 --- a/crates/perry-runtime/src/array/flat_clone.rs +++ b/crates/perry-runtime/src/array/flat_clone.rs @@ -526,7 +526,8 @@ pub extern "C" fn js_array_clone(src: *const ArrayHeader) -> *mut ArrayHeader { && crate::closure::is_closure_ptr(fn_raw) { let fn_ptr = fn_raw as *const crate::closure::ClosureHeader; - let iter = crate::closure::js_closure_call0(fn_ptr); + let iter = + crate::closure::js_closure_call0(fn_ptr, crate::closure::plain_call_receiver()); if js_array_is_array(iter).to_bits() == crate::value::TAG_TRUE { let ptr = crate::value::js_nanbox_get_pointer(iter) as *mut ArrayHeader; if !ptr.is_null() { diff --git a/crates/perry-runtime/src/array/generic.rs b/crates/perry-runtime/src/array/generic.rs index 372a6d0088..e9244c1528 100644 --- a/crates/perry-runtime/src/array/generic.rs +++ b/crates/perry-runtime/src/array/generic.rs @@ -595,6 +595,7 @@ pub extern "C" fn js_arraylike_forEach(recv: f64, cb: f64, this_arg: f64) -> f64 let v = al_get(recv_h.get_nanbox_f64(), k); js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -633,6 +634,7 @@ pub extern "C" fn js_arraylike_map(recv: f64, cb: f64, this_arg: f64) -> f64 { let (mapped, result) = result_h.across_mut::(|| { js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -674,6 +676,7 @@ pub extern "C" fn js_arraylike_filter(recv: f64, cb: f64, this_arg: f64) -> f64 v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); let keep = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -711,6 +714,7 @@ pub extern "C" fn js_arraylike_some(recv: f64, cb: f64, this_arg: f64) -> f64 { let v = al_get(recv_h.get_nanbox_f64(), k); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -740,6 +744,7 @@ pub extern "C" fn js_arraylike_every(recv: f64, cb: f64, this_arg: f64) -> f64 { let v = al_get(recv_h.get_nanbox_f64(), k); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -771,6 +776,7 @@ pub extern "C" fn js_arraylike_find(recv: f64, cb: f64, this_arg: f64) -> f64 { v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -797,6 +803,7 @@ pub extern "C" fn js_arraylike_findIndex(recv: f64, cb: f64, this_arg: f64) -> f let v = al_get(recv_h.get_nanbox_f64(), k); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -826,6 +833,7 @@ pub extern "C" fn js_arraylike_findLast(recv: f64, cb: f64, this_arg: f64) -> f6 v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -854,6 +862,7 @@ pub extern "C" fn js_arraylike_findLastIndex(recv: f64, cb: f64, this_arg: f64) let v = al_get(recv_h.get_nanbox_f64(), k); let hit = js_closure_call3( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), v, k as f64, recv_h.get_nanbox_f64(), @@ -904,6 +913,7 @@ pub extern "C" fn js_arraylike_reduce(recv: f64, cb: f64, has_init: i32, init: f let v = al_get(recv_h.get_nanbox_f64(), k); acc_h.set_nanbox_f64(crate::closure::js_closure_call4( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), acc_h.get_nanbox_f64(), v, k as f64, @@ -948,6 +958,7 @@ pub extern "C" fn js_arraylike_reduceRight(recv: f64, cb: f64, has_init: i32, in let v = al_get(recv_h.get_nanbox_f64(), k); acc_h.set_nanbox_f64(crate::closure::js_closure_call4( callable(cb_h.get_nanbox_f64()), + crate::closure::plain_call_receiver(), acc_h.get_nanbox_f64(), v, k as f64, diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index 1884349430..4a1e90d990 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -290,7 +290,13 @@ pub extern "C" fn js_array_forEach(arr: *const ArrayHeader, callback: *const Clo continue; } let element = crate::array::array_spec_get(arr, i as u32); - cb_site.call(current_callback(), element, i as f64, self_value(&rooted)); + cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + self_value(&rooted), + ); } return; } @@ -302,7 +308,13 @@ pub extern "C" fn js_array_forEach(arr: *const ArrayHeader, callback: *const Clo // dispatch path supports call3 safely, so bound native // methods like `array.forEach(console.log)` can observe the // source array just like Node. - cb_site.call(current_callback(), element, i as f64, self_value(&rooted)); + cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + self_value(&rooted), + ); } } } @@ -393,7 +405,13 @@ pub extern "C" fn js_array_map( }; // JS .map() callback receives (element, index, array). let callback = cb_handle.get_raw_const_ptr::(); - let mapped = cb_site.call(callback, element, i as f64, rooted.receiver()); + let mapped = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); if is_plain { let result = result_arr(&result_rooted); // The head was just re-derived from `result_rooted` (a GC @@ -485,7 +503,13 @@ pub extern "C" fn js_array_map_discard(arr: *const ArrayHeader, callback: *const continue; } let element = crate::array::array_spec_get(arr, i as u32); - let _ = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let _ = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); } return; } @@ -493,7 +517,13 @@ pub extern "C" fn js_array_map_discard(arr: *const ArrayHeader, callback: *const let Some(element) = rooted.present(i) else { continue; }; - let _ = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let _ = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); } } } @@ -568,7 +598,13 @@ pub extern "C" fn js_array_filter( } }; let callback = cb_handle.get_raw_const_ptr::(); - let keep = cb_site.call(callback, element, i as f64, rooted.receiver()); + let keep = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); // Proper truthy check: handles NaN-boxed booleans (TAG_FALSE != 0.0 but is falsy) if crate::value::js_is_truthy(keep) != 0 { if is_plain { @@ -645,7 +681,13 @@ pub extern "C" fn js_array_find(arr: *const ArrayHeader, callback: *const Closur } else { rooted.get_or_undefined(i) }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); // Proper truthy check: handles NaN-boxed booleans if crate::value::js_is_truthy(result) != 0 { return element; @@ -715,7 +757,13 @@ pub extern "C" fn js_array_findIndex( } else { rooted.get_or_undefined(i) }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); // Proper truthy check: handles NaN-boxed booleans if crate::value::js_is_truthy(result) != 0 { return i as i32; @@ -769,7 +817,13 @@ pub extern "C" fn js_array_find_last( } else { rooted.get_or_undefined(i) }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); if crate::value::js_is_truthy(result) != 0 { return element; } @@ -821,7 +875,13 @@ pub extern "C" fn js_array_find_last_index( } else { rooted.get_or_undefined(i) }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); if crate::value::js_is_truthy(result) != 0 { return i as i32; } @@ -938,7 +998,13 @@ pub extern "C" fn js_array_some(arr: *const ArrayHeader, callback: *const Closur None => continue, } }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); if crate::value::js_is_truthy(result) != 0 { return f64::from_bits(TAG_TRUE); } @@ -1014,7 +1080,7 @@ pub extern "C" fn js_array_some_captureless( }; let result = callback( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), element, i as f64, array_receiver_value(arr), @@ -1101,7 +1167,13 @@ pub extern "C" fn js_array_every(arr: *const ArrayHeader, callback: *const Closu None => continue, } }; - let result = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let result = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); if crate::value::js_is_truthy(result) == 0 { return f64::from_bits(TAG_FALSE); } @@ -1160,7 +1232,13 @@ pub extern "C" fn js_array_flatMap( let Some(element) = rooted.present(i) else { continue; }; - let mapped = cb_site.call(current_callback(), element, i as f64, rooted.receiver()); + let mapped = cb_site.call( + current_callback(), + crate::closure::plain_call_receiver(), + element, + i as f64, + rooted.receiver(), + ); // Root first: detecting a lazy array may materialize it, and a // push in the inner loop can move the callback result's target. sub_rooted.set_nanbox_f64(mapped); @@ -1306,6 +1384,7 @@ pub extern "C" fn js_array_reduce( // Spec callback is `(accumulator, currentValue, currentIndex, array)`. let next = cb_site.call( current_callback(), + crate::closure::plain_call_receiver(), acc_rooted.get_nanbox_f64(), element, i as f64, diff --git a/crates/perry-runtime/src/array/iter_object.rs b/crates/perry-runtime/src/array/iter_object.rs index d5c08b37e1..12637d5e52 100644 --- a/crates/perry-runtime/src/array/iter_object.rs +++ b/crates/perry-runtime/src/array/iter_object.rs @@ -222,7 +222,10 @@ unsafe fn fs_dir_entries_iter_obj(arr: *const ArrayHeader, kind: i32) -> Option< if method_ptr == 0 || !crate::closure::is_closure_ptr(method_ptr as usize) { return None; } - let result = crate::closure::js_closure_call0(method_ptr as *const _); + let result = crate::closure::js_closure_call0( + method_ptr as *const _, + crate::closure::plain_call_receiver(), + ); let result_ptr = crate::value::js_nanbox_get_pointer(result); (result_ptr != 0).then_some(result_ptr) } diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index ccf6e7872f..46354b050f 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -508,6 +508,7 @@ fn async_from_sync_call_raw(iter: f64, method: &[u8], args: &[f64]) -> Result Ok(Some(value)), @@ -866,8 +874,14 @@ pub extern "C" fn js_get_async_iterator(value: f64) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); - let iterator = - unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let iterator = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // GetIterator step 5: the result must be an Object. if !is_async_iterator_object(iterator) { @@ -1203,8 +1217,11 @@ pub(crate) fn array_from_spread_value(value: f64) -> *mut ArrayHeader { // (#9305); the rethrow below runs after `js_try_end` pops this // trap, so it targets the enclosing handler. let outcome = crate::exception::arm_trap_and_run(trap_buf, || { - crate::closure::js_closure_call0(js_nanbox_get_pointer(rebound_h.get_nanbox_f64()) - as *const crate::closure::ClosureHeader) + crate::closure::js_closure_call0( + js_nanbox_get_pointer(rebound_h.get_nanbox_f64()) + as *const crate::closure::ClosureHeader, + crate::closure::plain_call_receiver(), + ) }); let iter = match outcome { Some(iter) => iter, @@ -1369,7 +1386,14 @@ pub extern "C" fn js_iterator_next_result(iter_f64: f64) -> f64 { let result = if is_callable_value(next) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let result = unsafe { crate::closure::js_native_call_value(next, std::ptr::null(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + next, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } else if next.to_bits() == crate::value::TAG_UNDEFINED @@ -1432,7 +1456,14 @@ pub extern "C" fn js_iterator_close_if_not_done(iter_f64: f64, done_f64: f64) -> let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let result = unsafe { crate::closure::js_native_call_value(ret, std::ptr::null(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + ret, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if !is_object_like_value(result) { throw_iterator_result_not_object(); @@ -1510,7 +1541,11 @@ pub(crate) fn sync_iterator_to_array_if_not_async(iter_f64: f64) -> Option<*mut let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let r = closure::js_closure_call1(next_ptr, f64::from_bits(TAG_UNDEFINED)); + let r = closure::js_closure_call1( + next_ptr, + crate::closure::plain_call_receiver(), + f64::from_bits(TAG_UNDEFINED), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); r }; @@ -1549,7 +1584,14 @@ pub(crate) fn call_symbol_async_iterator(value: f64) -> Option { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); - let iterator = unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let iterator = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None @@ -1722,6 +1764,7 @@ fn js_iterator_to_array_impl(iter_f64: f64) -> *mut ArrayHeader { scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_h.get_nanbox_f64())); let r = closure::js_closure_call1( js_nanbox_get_pointer(next_h.get_nanbox_f64()) as *const closure::ClosureHeader, + crate::closure::plain_call_receiver(), f64::from_bits(TAG_UNDEFINED), ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); @@ -1844,7 +1887,11 @@ fn js_async_iterator_to_array(iter_f64: f64) -> *mut ArrayHeader { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let r = closure::js_closure_call1(next_ptr, f64::from_bits(TAG_UNDEFINED)); + let r = closure::js_closure_call1( + next_ptr, + crate::closure::plain_call_receiver(), + f64::from_bits(TAG_UNDEFINED), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); r }; diff --git a/crates/perry-runtime/src/array/reduce_right.rs b/crates/perry-runtime/src/array/reduce_right.rs index a2653d4d5c..8ef1389e22 100644 --- a/crates/perry-runtime/src/array/reduce_right.rs +++ b/crates/perry-runtime/src/array/reduce_right.rs @@ -118,6 +118,7 @@ pub extern "C" fn js_array_reduce_right( // Spec callback `(accumulator, currentValue, currentIndex, array)`. let next = cb_site.call( callback, + crate::closure::plain_call_receiver(), acc_rooted.get_nanbox_f64(), element, i as f64, diff --git a/crates/perry-runtime/src/array/sort.rs b/crates/perry-runtime/src/array/sort.rs index afefd91184..bfbf9493aa 100644 --- a/crates/perry-runtime/src/array/sort.rs +++ b/crates/perry-runtime/src/array/sort.rs @@ -42,7 +42,9 @@ impl ComparatorCall { /// `direct` is a static code address and remains valid across relocation. #[inline(always)] pub(crate) fn less_equal_at(&self, comparator: *const ClosureHeader, a: f64, b: f64) -> bool { - let r = self.direct.call(comparator, a, b); + let r = self + .direct + .call(comparator, crate::closure::plain_call_receiver(), a, b); if r <= 0.0 { return true; } diff --git a/crates/perry-runtime/src/array/tests.rs b/crates/perry-runtime/src/array/tests.rs index 49edb111b2..82258508bc 100644 --- a/crates/perry-runtime/src/array/tests.rs +++ b/crates/perry-runtime/src/array/tests.rs @@ -392,6 +392,7 @@ fn test_array_exotic_descriptors_and_global_prototype_identity() { let args = [0.0, 1.0, 0.0, 1.0]; crate::closure::js_native_call_value( f64::from_bits(array_ctor.bits()), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ) diff --git a/crates/perry-runtime/src/async_hooks.rs b/crates/perry-runtime/src/async_hooks.rs index 0e64e3a434..fd185a537a 100644 --- a/crates/perry-runtime/src/async_hooks.rs +++ b/crates/perry-runtime/src/async_hooks.rs @@ -993,6 +993,7 @@ fn emit_init(async_id: u64, type_name: &str, trigger_async_id: u64, resource: f6 with_hook_callbacks(HookPhase::Init, type_name == "PROMISE", |callback| { js_closure_call4( callback, + crate::closure::plain_call_receiver(), async_id as f64, type_value_handle.get_nanbox_f64(), async_id_to_js_number(trigger_async_id), @@ -1013,7 +1014,11 @@ fn before_with_kind(async_id: u64, trigger_async_id: u64, is_promise: bool) { CURRENT_EXECUTION_ID.with(|c| c.set(async_id)); CURRENT_TRIGGER_ID.with(|c| c.set(trigger_async_id)); with_hook_callbacks(HookPhase::Before, is_promise, |callback| { - js_closure_call1(callback, async_id as f64); + js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + async_id as f64, + ); }); } @@ -1030,7 +1035,11 @@ fn after_with_kind(async_id: u64, is_promise: bool) { return; } with_hook_callbacks(HookPhase::After, is_promise, |callback| { - js_closure_call1(callback, async_id as f64); + js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + async_id as f64, + ); }); let prev = EXECUTION_STACK .with(|stack| stack.borrow_mut().pop()) @@ -1064,7 +1073,11 @@ pub fn promise_resolve(async_id: u64) { return; } with_hook_callbacks(HookPhase::PromiseResolve, true, |callback| { - js_closure_call1(callback, async_id as f64); + js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + async_id as f64, + ); }); } @@ -1086,7 +1099,11 @@ fn destroy_with_kind(async_id: u64, is_promise: bool) { return; } with_hook_callbacks(HookPhase::Destroy, is_promise, |callback| { - js_closure_call1(callback, async_id as f64); + js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + async_id as f64, + ); }); RESOURCES.lock().unwrap().remove(&async_id); } @@ -1104,7 +1121,11 @@ fn emit_explicit_destroy(async_id: u64) { return; } with_hook_callbacks(HookPhase::Destroy, false, |callback| { - js_closure_call1(callback, async_id as f64); + js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + async_id as f64, + ); }); RESOURCES.lock().unwrap().remove(&async_id); } @@ -1601,13 +1622,27 @@ pub extern "C" fn js_async_resource_run_in_async_scope( let callback_outcome = crate::exception::js_call_catching(|| { args_array_handle.with_const_ptr::(|arr| { if arr.is_null() { - unsafe { js_closure_call_array(callback as i64, ptr::null(), 0) } + unsafe { + js_closure_call_array( + callback as i64, + crate::closure::plain_call_receiver(), + ptr::null(), + 0, + ) + } } else { let len = js_array_length(arr) as i64; let data = unsafe { crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 }; - unsafe { js_closure_call_array(callback as i64, data, len) } + unsafe { + js_closure_call_array( + callback as i64, + crate::closure::plain_call_receiver(), + data, + len, + ) + } } }) }); @@ -1813,126 +1848,28 @@ pub extern "C" fn js_async_local_storage_static_bind_direct( js_async_resource_static_bind_value(callback_value, TAG_UNDEFINED_F64, TAG_UNDEFINED_F64) } -fn register_context_snapshot(snapshot: crate::async_context::AsyncContextSnapshot) -> usize { - let id = NEXT_CONTEXT_SNAPSHOT_ID.fetch_add(1, Ordering::Relaxed); - CONTEXT_SNAPSHOTS.lock().unwrap().insert(id, snapshot); - id -} - -fn run_with_context_snapshot(snapshot_id: usize, f: impl FnOnce() -> f64) -> f64 { - let snapshot = CONTEXT_SNAPSHOTS - .lock() - .unwrap() - .get(&snapshot_id) - .cloned() - .unwrap_or_default(); - let scope = crate::gc::RuntimeHandleScope::new(); - let mut snapshot = snapshot; - let snapshot_roots = crate::async_context::root_snapshot(&scope, &snapshot); - let previous = crate::async_context::enter_context(&snapshot); - // Guard-held (GC-scanned, throw-safe) — see runInAsyncScope (#788). - crate::async_context::push_context_guard( - crate::async_context::ContextGuardAction::RestoreSnapshot(previous), - ); - let result = f(); - let result_handle = scope.root_nanbox_f64(result); - crate::async_context::refresh_snapshot_from_roots(&mut snapshot, &snapshot_roots); - if let Some(action) = crate::async_context::pop_context_guard() { - crate::async_context::apply_context_guard(action); - } - result_handle.get_nanbox_f64() -} - -fn call_callback_with_rest(callback_value: f64, this_arg: f64, rest: f64) -> f64 { - if !is_callable_value(callback_value) { - throw_apply_not_function(callback_value); - } - let scope = crate::gc::RuntimeHandleScope::new(); - let callback_handle = scope.root_nanbox_f64(callback_value); - let this_arg_handle = scope.root_nanbox_f64(this_arg); - let rebound_bits = crate::closure::clone_closure_rebind_this( - callback_handle.get_nanbox_f64().to_bits(), - this_arg_handle.get_nanbox_f64(), - ); - let rebound_handle = scope.root_nanbox_f64(f64::from_bits(rebound_bits)); - let callback = crate::fs::extract_closure_ptr(rebound_handle.get_nanbox_f64()); - if callback.is_null() { - throw_apply_not_function(callback_handle.get_nanbox_f64()); - } - let args_array = ptr_from_nanboxed(rest) as *const ArrayHeader; - let args_array_handle = scope.root_raw_const_ptr(args_array); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_arg_handle.get_nanbox_f64(), - )); - let result = if args_array.is_null() { - unsafe { js_closure_call_array(callback as i64, ptr::null(), 0) } - } else { - let arr = args_array_handle.get_raw_const_ptr::(); - let len = js_array_length(arr) as i64; - let data = if arr.is_null() || len == 0 { - ptr::null() - } else { - unsafe { crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 } - }; - unsafe { js_closure_call_array(callback as i64, data, len) } - }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result -} - -extern "C" fn async_local_storage_snapshot_trampoline( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - callback_value: f64, - rest: f64, -) -> f64 { - let snapshot_id = js_closure_get_capture_ptr(closure, 0) as usize; - run_with_context_snapshot(snapshot_id, || { - // AsyncLocalStorage.snapshot() intentionally invokes the supplied - // callback as a plain function. The receiver used to call the snapshot - // wrapper itself is not forwarded. - call_callback_with_rest(callback_value, TAG_UNDEFINED_F64, rest) - }) -} +mod context_snapshots; +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(async_local_storage_snapshot_trampoline as *const u8, 1); + js_register_closure_rest( + context_snapshots::async_local_storage_snapshot_trampoline as *const u8, + 1, + ); flag.set(true); } }); } -fn async_local_storage_static_snapshot_value() -> f64 { - 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); - if closure.is_null() { - return TAG_UNDEFINED_F64; - } - js_closure_set_capture_ptr(closure, 0, snapshot_id as i64); - crate::object::set_builtin_closure_length(closure as usize, 1); - crate::object::set_bound_native_closure_name(closure, "bound"); - crate::value::js_nanbox_pointer(closure as i64) -} - -pub extern "C" fn js_async_local_storage_static_snapshot_method( - _closure: *const ClosureHeader, - _this: crate::closure::JsThis, - _rest: f64, -) -> f64 { - async_local_storage_static_snapshot_value() -} - -#[no_mangle] -pub extern "C" fn js_async_local_storage_static_snapshot_direct(_rest: i64) -> f64 { - async_local_storage_static_snapshot_value() -} - 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 new file mode 100644 index 0000000000..7c6a1400a3 --- /dev/null +++ b/crates/perry-runtime/src/async_hooks/context_snapshots.rs @@ -0,0 +1,129 @@ +//! `AsyncLocalStorage.snapshot()` and the context snapshots it and the +//! static bind helpers run callbacks under. Split out of `async_hooks.rs` +//! (file-size gate); items move unchanged. + +use super::*; + +pub(super) fn register_context_snapshot( + snapshot: crate::async_context::AsyncContextSnapshot, +) -> usize { + let id = NEXT_CONTEXT_SNAPSHOT_ID.fetch_add(1, Ordering::Relaxed); + CONTEXT_SNAPSHOTS.lock().unwrap().insert(id, snapshot); + id +} + +pub(super) fn run_with_context_snapshot(snapshot_id: usize, f: impl FnOnce() -> f64) -> f64 { + let snapshot = CONTEXT_SNAPSHOTS + .lock() + .unwrap() + .get(&snapshot_id) + .cloned() + .unwrap_or_default(); + let scope = crate::gc::RuntimeHandleScope::new(); + let mut snapshot = snapshot; + let snapshot_roots = crate::async_context::root_snapshot(&scope, &snapshot); + let previous = crate::async_context::enter_context(&snapshot); + // Guard-held (GC-scanned, throw-safe) — see runInAsyncScope (#788). + crate::async_context::push_context_guard( + crate::async_context::ContextGuardAction::RestoreSnapshot(previous), + ); + let result = f(); + let result_handle = scope.root_nanbox_f64(result); + crate::async_context::refresh_snapshot_from_roots(&mut snapshot, &snapshot_roots); + if let Some(action) = crate::async_context::pop_context_guard() { + crate::async_context::apply_context_guard(action); + } + result_handle.get_nanbox_f64() +} + +pub(super) fn call_callback_with_rest(callback_value: f64, this_arg: f64, rest: f64) -> f64 { + if !is_callable_value(callback_value) { + throw_apply_not_function(callback_value); + } + let scope = crate::gc::RuntimeHandleScope::new(); + let callback_handle = scope.root_nanbox_f64(callback_value); + let this_arg_handle = scope.root_nanbox_f64(this_arg); + let rebound_bits = crate::closure::clone_closure_rebind_this( + callback_handle.get_nanbox_f64().to_bits(), + this_arg_handle.get_nanbox_f64(), + ); + let rebound_handle = scope.root_nanbox_f64(f64::from_bits(rebound_bits)); + let callback = crate::fs::extract_closure_ptr(rebound_handle.get_nanbox_f64()); + if callback.is_null() { + throw_apply_not_function(callback_handle.get_nanbox_f64()); + } + let args_array = ptr_from_nanboxed(rest) as *const ArrayHeader; + let args_array_handle = scope.root_raw_const_ptr(args_array); + let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( + this_arg_handle.get_nanbox_f64(), + )); + let result = if args_array.is_null() { + unsafe { + js_closure_call_array( + callback as i64, + crate::closure::plain_call_receiver(), + ptr::null(), + 0, + ) + } + } else { + let arr = args_array_handle.get_raw_const_ptr::(); + let len = js_array_length(arr) as i64; + let data = if arr.is_null() || len == 0 { + ptr::null() + } else { + unsafe { crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 } + }; + unsafe { + js_closure_call_array( + callback as i64, + crate::closure::plain_call_receiver(), + data, + len, + ) + } + }; + crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); + result +} + +pub(super) extern "C" fn async_local_storage_snapshot_trampoline( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + callback_value: f64, + rest: f64, +) -> f64 { + let snapshot_id = js_closure_get_capture_ptr(closure, 0) as usize; + run_with_context_snapshot(snapshot_id, || { + // AsyncLocalStorage.snapshot() intentionally invokes the supplied + // callback as a plain function. The receiver used to call the snapshot + // wrapper itself is not forwarded. + call_callback_with_rest(callback_value, TAG_UNDEFINED_F64, rest) + }) +} + +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); + if closure.is_null() { + return TAG_UNDEFINED_F64; + } + js_closure_set_capture_ptr(closure, 0, snapshot_id as i64); + crate::object::set_builtin_closure_length(closure as usize, 1); + crate::object::set_bound_native_closure_name(closure, "bound"); + crate::value::js_nanbox_pointer(closure as i64) +} + +pub extern "C" fn js_async_local_storage_static_snapshot_method( + _closure: *const ClosureHeader, + _this: crate::closure::JsThis, + _rest: f64, +) -> f64 { + async_local_storage_static_snapshot_value() +} + +#[no_mangle] +pub extern "C" fn js_async_local_storage_static_snapshot_direct(_rest: i64) -> f64 { + async_local_storage_static_snapshot_value() +} diff --git a/crates/perry-runtime/src/buffer/mod.rs b/crates/perry-runtime/src/buffer/mod.rs index 184172401e..b896430e12 100644 --- a/crates/perry-runtime/src/buffer/mod.rs +++ b/crates/perry-runtime/src/buffer/mod.rs @@ -345,7 +345,14 @@ mod tests { unsafe { crate::symbol::js_object_get_symbol_property(buf_value, iter_sym_value) }; assert_ne!(method.to_bits(), crate::value::TAG_UNDEFINED); - let iterator = unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let iterator = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; let result = unsafe { crate::object::js_native_call_method( iterator, diff --git a/crates/perry-runtime/src/builtins/console.rs b/crates/perry-runtime/src/builtins/console.rs index 00874feed2..4267a81d69 100644 --- a/crates/perry-runtime/src/builtins/console.rs +++ b/crates/perry-runtime/src/builtins/console.rs @@ -567,7 +567,14 @@ extern "C" fn console_task_run( if !console_is_callable(callback) { throw_plain_console_error("First argument must be a function."); } - unsafe { crate::closure::js_native_call_value(callback, std::ptr::null(), 0) } + unsafe { + crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } } /// `console.context([name])` returns an inspector-scoped console object in diff --git a/crates/perry-runtime/src/builtins/formatting.rs b/crates/perry-runtime/src/builtins/formatting.rs index 61dd2d08d6..9b5600f39f 100644 --- a/crates/perry-runtime/src/builtins/formatting.rs +++ b/crates/perry-runtime/src/builtins/formatting.rs @@ -994,6 +994,7 @@ unsafe fn format_object_as_json( let undef_arg = f64::from_bits(crate::value::TAG_UNDEFINED); let ret = crate::closure::js_closure_call3( closure_ptr, + crate::closure::plain_call_receiver(), remaining, options_arg, undef_arg, @@ -1315,7 +1316,8 @@ fn format_accessor_property(acc: crate::object::AccessorDescriptor, depth: usize let closure = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - let value = crate::closure::js_closure_call0(closure); + let value = + crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); return format!("[{}: {}]", label, format_jsvalue_for_json(value, depth + 1)); } } diff --git a/crates/perry-runtime/src/builtins/globals.rs b/crates/perry-runtime/src/builtins/globals.rs index 4e42606df3..ea779f5455 100644 --- a/crates/perry-runtime/src/builtins/globals.rs +++ b/crates/perry-runtime/src/builtins/globals.rs @@ -1205,35 +1205,91 @@ pub(crate) fn drain_queued_microtasks_count() -> i32 { let cb_ptr = callback_handle.get_raw_const_ptr::(); match a.len() { 0 => { - js_closure_call0(cb_ptr); + js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } 1 => { - js_closure_call1(cb_ptr, a[0]); + js_closure_call1(cb_ptr, crate::closure::plain_call_receiver(), a[0]); } 2 => { - js_closure_call2(cb_ptr, a[0], a[1]); + js_closure_call2(cb_ptr, crate::closure::plain_call_receiver(), a[0], a[1]); } 3 => { - js_closure_call3(cb_ptr, a[0], a[1], a[2]); + js_closure_call3( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + ); } 4 => { - js_closure_call4(cb_ptr, a[0], a[1], a[2], a[3]); + js_closure_call4( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + a[3], + ); } 5 => { - js_closure_call5(cb_ptr, a[0], a[1], a[2], a[3], a[4]); + js_closure_call5( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + a[3], + a[4], + ); } 6 => { - js_closure_call6(cb_ptr, a[0], a[1], a[2], a[3], a[4], a[5]); + js_closure_call6( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + ); } 7 => { - js_closure_call7(cb_ptr, a[0], a[1], a[2], a[3], a[4], a[5], a[6]); + js_closure_call7( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + a[6], + ); } 8 => { - js_closure_call8(cb_ptr, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); + js_closure_call8( + cb_ptr, + crate::closure::plain_call_receiver(), + a[0], + a[1], + a[2], + a[3], + a[4], + a[5], + a[6], + a[7], + ); } // #10420: more than 8 trailing args used to clamp to 9. n => unsafe { - crate::closure::js_closure_call_array(cb_ptr as i64, a.as_ptr(), n as i64); + crate::closure::js_closure_call_array( + cb_ptr as i64, + crate::closure::plain_call_receiver(), + a.as_ptr(), + n as i64, + ); }, } crate::async_hooks::after(async_id); diff --git a/crates/perry-runtime/src/bun_compat/plugin.rs b/crates/perry-runtime/src/bun_compat/plugin.rs index d591aa594c..80e43b405b 100644 --- a/crates/perry-runtime/src/bun_compat/plugin.rs +++ b/crates/perry-runtime/src/bun_compat/plugin.rs @@ -107,7 +107,12 @@ fn register(plugin: f64) -> f64 { })); let result = crate::exception::catch_js_throw(|| unsafe { let args = [build.get_nanbox_f64()]; - crate::closure::js_native_call_value(setup.get_nanbox_f64(), args.as_ptr(), 1) + crate::closure::js_native_call_value( + setup.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) }); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); let result = match result { @@ -165,7 +170,14 @@ mod tests { ] { let hook = object_field(build.get_nanbox_f64(), name).expect("builder hook"); let args = [undefined(), callback.get_nanbox_f64()]; - let result = unsafe { crate::closure::js_native_call_value(hook, args.as_ptr(), 2) }; + let result = unsafe { + crate::closure::js_native_call_value( + hook, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 2, + ) + }; assert_eq!(result.to_bits(), undefined().to_bits()); } assert!(object_field(build.get_nanbox_f64(), b"config").is_some()); @@ -235,7 +247,14 @@ mod tests { 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()); - let result = unsafe { crate::closure::js_native_call_value(clear, std::ptr::null(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + clear, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(result.to_bits(), undefined().to_bits()); } } diff --git a/crates/perry-runtime/src/bun_compat/self_global.rs b/crates/perry-runtime/src/bun_compat/self_global.rs index 317b63b60c..2110e1cd96 100644 --- a/crates/perry-runtime/src/bun_compat/self_global.rs +++ b/crates/perry-runtime/src/bun_compat/self_global.rs @@ -100,7 +100,11 @@ mod tests { global_value.to_bits() ); - set_self(std::ptr::null(), crate::closure::JsThis::current(), 42.0); + set_self( + std::ptr::null(), + crate::closure::body_call::current_this(), + 42.0, + ); assert!(crate::object::get_accessor_descriptor(global as usize, "self").is_none()); super::super::js_set_bun_platform(); assert_eq!( diff --git a/crates/perry-runtime/src/bun_compat/spawn.rs b/crates/perry-runtime/src/bun_compat/spawn.rs index 0d28a26c04..a5d075ac14 100644 --- a/crates/perry-runtime/src/bun_compat/spawn.rs +++ b/crates/perry-runtime/src/bun_compat/spawn.rs @@ -72,7 +72,12 @@ fn call_value(callback: f64, args: &[f64]) -> f64 { let args = scope.root_nanbox_f64_slice(args); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); unsafe { - crate::closure::js_native_call_value(callback.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) } } @@ -90,7 +95,12 @@ fn call_method(receiver: f64, name: &[u8], args: &[f64]) -> f64 { )); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); let result = unsafe { - crate::closure::js_native_call_value(method.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }; crate::object::js_implicit_this_set(previous.get_nanbox_f64()); result @@ -925,7 +935,11 @@ extern "C" fn bun_pty_subprocess_dispose( closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let _ = bun_pty_subprocess_kill(closure, crate::closure::JsThis::current(), cp_undefined()); + let _ = bun_pty_subprocess_kill( + closure, + crate::closure::body_call::current_this(), + cp_undefined(), + ); cp_undefined() } diff --git a/crates/perry-runtime/src/bun_ffi/callback.rs b/crates/perry-runtime/src/bun_ffi/callback.rs index 076084642b..8f372d37e9 100644 --- a/crates/perry-runtime/src/bun_ffi/callback.rs +++ b/crates/perry-runtime/src/bun_ffi/callback.rs @@ -572,6 +572,7 @@ unsafe fn invoke_record( match crate::exception::js_call_catching(|| { crate::closure::js_native_call_value( callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), arguments.as_ptr(), arguments.len(), ) diff --git a/crates/perry-runtime/src/child_process/emitter.rs b/crates/perry-runtime/src/child_process/emitter.rs index 55efd49a7d..2e9c48441c 100644 --- a/crates/perry-runtime/src/child_process/emitter.rs +++ b/crates/perry-runtime/src/child_process/emitter.rs @@ -69,7 +69,12 @@ pub(crate) fn cp_emit(target: f64, event: &str, args: &[f64]) -> bool { js_implicit_this_set(target.get_nanbox_f64()); let current_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); unsafe { - let _ = js_native_call_value(cb, current_args.as_ptr(), current_args.len()); + let _ = js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + current_args.as_ptr(), + current_args.len(), + ); } js_implicit_this_set(prev.get_nanbox_f64()); fired = true; @@ -226,7 +231,11 @@ pub(crate) extern "C" fn cp_method_dispose( closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let _ = cp_method_kill(closure, crate::closure::JsThis::current(), cp_undefined()); + let _ = cp_method_kill( + closure, + crate::closure::body_call::current_this(), + cp_undefined(), + ); cp_undefined() } pub(crate) fn js_fork_child(args_len: usize) -> f64 { @@ -350,7 +359,12 @@ pub(crate) extern "C" fn cp_pipe_data_thunk( let prev = this_scope.root_nanbox_f64(js_implicit_this_set(dest)); let args = [chunk]; unsafe { - let _ = js_native_call_value(write, args.as_ptr(), args.len()); + let _ = js_native_call_value( + write, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } js_implicit_this_set(prev.get_nanbox_f64()); } @@ -371,7 +385,8 @@ pub(crate) extern "C" fn cp_pipe_end_thunk( let prev = this_scope.root_nanbox_f64(js_implicit_this_set(dest)); let args = [cp_undefined()]; unsafe { - let _ = js_native_call_value(end, args.as_ptr(), 0); + let _ = + js_native_call_value(end, crate::closure::plain_call_receiver(), args.as_ptr(), 0); } js_implicit_this_set(prev.get_nanbox_f64()); } @@ -410,7 +425,12 @@ pub(crate) extern "C" fn cp_stream_callback_thunk( let callback = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let args: [f64; 0] = []; unsafe { - let _ = js_native_call_value(callback, args.as_ptr(), 0); + let _ = js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 0, + ); } cp_undefined() } @@ -571,7 +591,14 @@ pub(crate) extern "C" fn cp_send_callback_thunk( cp_channel_closed_error() }; let args = [arg]; - unsafe { js_native_call_value(cb, args.as_ptr(), args.len()) }; + unsafe { + js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; cp_undefined() } diff --git a/crates/perry-runtime/src/child_process/failed_spawn.rs b/crates/perry-runtime/src/child_process/failed_spawn.rs index 27b12e1b0b..45c8946e95 100644 --- a/crates/perry-runtime/src/child_process/failed_spawn.rs +++ b/crates/perry-runtime/src/child_process/failed_spawn.rs @@ -11,7 +11,7 @@ pub(super) extern "C" fn emit_error_then_close( ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let cp = scope.root_nanbox_f64(cp_this(closure)); - reactor::cp_emit_spawn_error(closure, crate::closure::JsThis::current()); + reactor::cp_emit_spawn_error(closure, crate::closure::body_call::current_this()); let close = crate::closure::js_closure_alloc(reactor::cp_emit_spawn_close as *const u8, 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); diff --git a/crates/perry-runtime/src/child_process/reactor.rs b/crates/perry-runtime/src/child_process/reactor.rs index d6760692ce..1eed1dc748 100644 --- a/crates/perry-runtime/src/child_process/reactor.rs +++ b/crates/perry-runtime/src/child_process/reactor.rs @@ -1321,7 +1321,13 @@ fn cp_exec_fire_close(exec: Box, code: Option, signal: Optio super::cp_exec_callback_args(&run, &exec.run_options, &exec.cmd, &exec.file, &exec.mode); let cb = crate::fs::extract_closure_ptr(f64::from_bits(exec.cb_bits)); if !cb.is_null() { - crate::closure::js_closure_call3(cb, err, out, errout); + crate::closure::js_closure_call3( + cb, + crate::closure::plain_call_receiver(), + err, + out, + errout, + ); } } @@ -1351,7 +1357,13 @@ extern "C" fn cp_exec_cb_thunk( let errout = f64::from_bits(js_closure_get_capture_ptr(closure, 3) as u64); let cbptr = crate::fs::extract_closure_ptr(cb); if !cbptr.is_null() { - crate::closure::js_closure_call3(cbptr, err, out, errout); + crate::closure::js_closure_call3( + cbptr, + crate::closure::plain_call_receiver(), + err, + out, + errout, + ); } cp_undefined() } diff --git a/crates/perry-runtime/src/child_process/reactor/stdin_drain.rs b/crates/perry-runtime/src/child_process/reactor/stdin_drain.rs index 2a6314485e..5593523db2 100644 --- a/crates/perry-runtime/src/child_process/reactor/stdin_drain.rs +++ b/crates/perry-runtime/src/child_process/reactor/stdin_drain.rs @@ -65,8 +65,12 @@ pub(super) fn cp_stdin_written(handle: u64, len: usize, broken: bool) { for callback in &handles { let args: [f64; 0] = []; unsafe { - let _ = - crate::closure::js_native_call_value(callback.get_nanbox_f64(), args.as_ptr(), 0); + let _ = crate::closure::js_native_call_value( + callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 0, + ); } } } diff --git a/crates/perry-runtime/src/closure/body_call.rs b/crates/perry-runtime/src/closure/body_call.rs index 16848bb974..aebe395591 100644 --- a/crates/perry-runtime/src/closure/body_call.rs +++ b/crates/perry-runtime/src/closure/body_call.rs @@ -33,71 +33,51 @@ /// The receiver a JS body takes as its second native parameter /// (`perry_abi::JS_BODY_THIS_PARAM`): the NaN-boxed `this` bits, passed in an -/// integer register (`repr(transparent)` over `u64`, so the ABI is exactly -/// that of a `u64`). +/// integer register. Defined once in perry-abi and shared with perry-ffi. /// /// Stage 1 of this-as-a-parameter: every caller passes the receiver the /// implicit-`this` cell holds for the call (a runtime caller reads it with -/// [`JsThis::current`]), and bodies still read the cell. A native body -/// declares the parameter and must not assume anything about it yet. -#[repr(transparent)] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct JsThis(pub u64); +/// [`current_this`]), and bodies still read the cell. A native body declares +/// the parameter and must not assume anything about it yet. +pub use crate::codegen_abi::JsThis; -impl JsThis { - /// `undefined` as a receiver. - pub const UNDEFINED: JsThis = JsThis(crate::value::TAG_UNDEFINED); +const _: () = assert!(crate::codegen_abi::TAG_UNDEFINED == crate::value::TAG_UNDEFINED); - /// The receiver bits. - #[inline(always)] - pub const fn bits(self) -> u64 { - self.0 - } - - /// The receiver as a NaN-boxed `f64`. - #[inline(always)] - pub fn as_f64(self) -> f64 { - f64::from_bits(self.0) - } - - /// A NaN-boxed `f64` receiver. - #[inline(always)] - pub fn from_f64(value: f64) -> Self { - JsThis(value.to_bits()) - } +/// The receiver the implicit-`this` cell holds right now: what a body called +/// at this point would read from the cell. Read through the per-agent pointer +/// block (`agent_ptrs`), the cheapest path to the cell on every target but +/// Apple aarch64, where `HotTls` is the fast path. +#[inline(always)] +pub fn current_this() -> JsThis { + #[cfg(not(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" + )))] + return JsThis(crate::agent_ptrs::implicit_this_bits()); + #[cfg(all( + target_vendor = "apple", + target_arch = "aarch64", + target_pointer_width = "64" + ))] + return JsThis(crate::object::implicit_this_bits()); +} - /// The receiver the implicit-`this` cell holds right now: what a body - /// called at this point would read from the cell. Read through the - /// per-agent pointer block (`agent_ptrs`), the cheapest path to the cell - /// on every target but Apple aarch64, where `HotTls` is the fast path. - #[inline(always)] - pub fn current() -> Self { - #[cfg(not(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" - )))] - return JsThis(crate::agent_ptrs::implicit_this_bits()); - #[cfg(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" - ))] - return JsThis(crate::object::implicit_this_bits()); - } +/// The receiver a runtime caller passes for a PLAIN call (a call with no +/// receiver of its own). Stage 1 keeps the plain-call behavior of the cell: +/// a plain call runs with whatever the cell holds, so this is +/// [`current_this`], and the entry it is passed to binds nothing. +#[inline(always)] +pub fn plain_call_receiver() -> JsThis { + current_this() } /// The native type of a JS body taking the receiver and one `f64` per -/// token: `js_body_fn_ty!(a, b)` is -/// `extern "C" fn(*const ClosureHeader, JsThis, f64, f64) -> f64`. +/// token: `js_body_fn_ty!(a, b)` is perry-abi's +/// `unsafe extern "C" fn(*const ClosureHeader, JsThis, f64, f64) -> f64`. macro_rules! js_body_fn_ty { - (@f64 $x:tt) => { f64 }; ($($x:tt),* $(,)?) => { - extern "C" fn( - *const $crate::closure::ClosureHeader, - $crate::closure::body_call::JsThis - $(, $crate::closure::body_call::js_body_fn_ty!(@f64 $x))* - ) -> f64 + ::perry_abi::js_body_fn_ty!($crate::closure::ClosureHeader; $($x),*) }; } diff --git a/crates/perry-runtime/src/closure/dispatch.rs b/crates/perry-runtime/src/closure/dispatch.rs index 77caee6977..a4cb31bdb9 100644 --- a/crates/perry-runtime/src/closure/dispatch.rs +++ b/crates/perry-runtime/src/closure/dispatch.rs @@ -34,9 +34,9 @@ pub use validate::{clean_closure_ptr, dispatch_proxy_callee_or_throw, get_valid_ pub use calln::{ js_closure_call0, js_closure_call1, js_closure_call10, js_closure_call11, js_closure_call12, - js_closure_call13, js_closure_call14, js_closure_call15, js_closure_call16, - js_closure_call1_receiverless, js_closure_call2, js_closure_call3, js_closure_call4, - js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, js_closure_call9, + js_closure_call13, js_closure_call14, js_closure_call15, js_closure_call16, js_closure_call2, + js_closure_call3, js_closure_call4, js_closure_call5, js_closure_call6, js_closure_call7, + js_closure_call8, js_closure_call9, }; pub use direct::{DirectCall1, DirectCall2, DirectCall3, DirectCall4}; diff --git a/crates/perry-runtime/src/closure/dispatch/bound.rs b/crates/perry-runtime/src/closure/dispatch/bound.rs index f306a8355f..75439b60ed 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound.rs @@ -333,7 +333,12 @@ pub unsafe fn dispatch_bound_function(closure: *const ClosureHeader, args: &[f64 let target = rebind_explicit_this(target, bound_this); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(bound_this)); - let result = js_native_call_value(target, call_ptr, call_len); + let result = js_native_call_value( + target, + crate::closure::plain_call_receiver(), + call_ptr, + call_len, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index ad3171206b..039ff3d526 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -1,6 +1,13 @@ -//! Per-arity `js_closure_callN` FFI entry points (0..=16) and the shared -//! `dispatch_registered_call` / `dispatch_rest_or_declared_arity` routing -//! helpers. +//! Per-arity `js_closure_callN(closure, this, a0..)` FFI entry points +//! (0..=16) and the shared `dispatch_registered_call` / +//! `dispatch_rest_or_declared_arity` routing helpers. +//! +//! Stage 1 of this-as-a-parameter: bodies still read the implicit-`this` +//! cell, and the routing below passes the cell's value as every body's +//! receiver parameter. An entry therefore makes the cell hold the `this` it +//! is handed for the duration of the call ([`with_receiver`]); a caller that +//! passes the cell's current value (every runtime plain call, +//! `plain_call_receiver()`) binds nothing. //! //! The hot-loop counterpart -- resolve a closure ONCE and call it directly for //! the rest of the loop -- lives in the sibling `direct` module (#8180). It @@ -8,12 +15,34 @@ //! exactly one consumer. use super::*; +use crate::closure::JsThis; + +/// Run `call` with the implicit-`this` cell holding `this`. When the cell +/// already holds it — the common case — nothing is bound. Otherwise the cell +/// is bound for the call and the displaced value restored after it, rooted +/// across the call (an `ImplicitThisScope`), except for an arrow callee, whose +/// `this` is lexical and reads neither the cell nor the parameter. +#[inline(always)] +pub(crate) fn with_receiver( + closure: *const ClosureHeader, + this: JsThis, + call: impl FnOnce() -> f64, +) -> f64 { + if this == crate::closure::body_call::current_this() + || crate::closure::closure_is_arrow(closure) + { + return call(); + } + let scope = crate::gc::RuntimeHandleScope::new(); + let _bound = crate::object::ImplicitThisScope::bind(&scope, this.as_f64()); + call() +} -/// Call a closure with 0 arguments, returning f64 +/// Call a closure with receiver `this` and no arguments. #[cfg(panic = "abort")] #[no_mangle] -pub extern "C" fn js_closure_call0(closure: *const ClosureHeader) -> f64 { - js_closure_call0_impl(closure) +pub extern "C" fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { + with_receiver(closure, this, || js_closure_call0_impl(closure)) } /// Test/debug builds use Rust unwinding for JS exceptions. Keep this entry @@ -23,10 +52,40 @@ pub extern "C" fn js_closure_call0(closure: *const ClosureHeader) -> f64 { /// abort-on-unwind guard (#8479). #[cfg(not(panic = "abort"))] #[no_mangle] -pub extern "C-unwind" fn js_closure_call0(closure: *const ClosureHeader) -> f64 { - js_closure_call0_impl(closure) +pub extern "C-unwind" fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { + with_receiver(closure, this, || js_closure_call0_impl(closure)) } +// #8479: NOT `C-unwind` (below). The runtime is built `panic=abort` and JS +// throws travel as a raw Itanium `_Unwind_Exception` that must step THROUGH +// these frames untouched (see `crate::eh`). +macro_rules! closure_call_entry { + ($entry:ident, $cell:ident, $n:literal; $($a:ident),+) => { + #[doc = concat!("Call a closure with receiver `this` and ", stringify!($n), " argument(s).")] + #[no_mangle] + pub extern "C" fn $entry(closure: *const ClosureHeader, this: JsThis, $($a: f64),+) -> f64 { + with_receiver(closure, this, || $cell(closure, $($a),+)) + } + }; +} + +closure_call_entry!(js_closure_call1, closure_call1_cell, 1; a0); +closure_call_entry!(js_closure_call2, closure_call2_cell, 2; a0, a1); +closure_call_entry!(js_closure_call3, closure_call3_cell, 3; a0, a1, a2); +closure_call_entry!(js_closure_call4, closure_call4_cell, 4; a0, a1, a2, a3); +closure_call_entry!(js_closure_call5, closure_call5_cell, 5; a0, a1, a2, a3, a4); +closure_call_entry!(js_closure_call6, closure_call6_cell, 6; a0, a1, a2, a3, a4, a5); +closure_call_entry!(js_closure_call7, closure_call7_cell, 7; a0, a1, a2, a3, a4, a5, a6); +closure_call_entry!(js_closure_call8, closure_call8_cell, 8; a0, a1, a2, a3, a4, a5, a6, a7); +closure_call_entry!(js_closure_call9, closure_call9_cell, 9; a0, a1, a2, a3, a4, a5, a6, a7, a8); +closure_call_entry!(js_closure_call10, closure_call10_cell, 10; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); +closure_call_entry!(js_closure_call11, closure_call11_cell, 11; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +closure_call_entry!(js_closure_call12, closure_call12_cell, 12; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +closure_call_entry!(js_closure_call13, closure_call13_cell, 13; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +closure_call_entry!(js_closure_call14, closure_call14_cell, 14; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +closure_call_entry!(js_closure_call15, closure_call15_cell, 15; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); +closure_call_entry!(js_closure_call16, closure_call16_cell, 16; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + #[inline(always)] fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { let func_ptr = get_valid_func_ptr(closure); @@ -46,14 +105,13 @@ fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current() + crate::closure::body_call::current_this() ) }, } } /// Call a closure with 1 argument, returning f64 -#[no_mangle] // The one-argument value-call path can run arbitrary generated code and must // let a JS exception unwind to the generated caller's catch landing pad. // #8479: NOT `C-unwind`. The runtime is built `panic=abort` and JS throws @@ -65,7 +123,8 @@ fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { // throw trips ("panic in a function that cannot unwind"). #8416 introduced // the first two such guards here; #8464 added ~40 more and measurably // regressed main (+20 gap crashes, gc-stress) before being reverted. -pub extern "C" fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> f64 { +#[inline(always)] +fn closure_call1_cell(closure: *const ClosureHeader, arg0: f64) -> f64 { let func_ptr = get_valid_func_ptr(closure); if func_ptr.is_null() { return dispatch_proxy_callee_or_throw(closure, &[arg0]); @@ -75,7 +134,7 @@ pub extern "C" fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> func_ptr, arg0, resolve_strategy(func_ptr), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), ) } @@ -102,68 +161,11 @@ fn dispatch_call1_resolved( } } -/// Receiverless one-argument call. Arrow functions have lexical `this`, so -/// resetting the dynamic `IMPLICIT_THIS` cell around them is unobservable and -/// needlessly expensive in callback pipelines. Arrow-ness is already folded -/// into the unified dispatch cache; non-arrows retain OrdinaryCallBindThis and -/// root the displaced receiver across arbitrary generated code. -#[no_mangle] -// Same unwind contract as `js_closure_call1` above: keep `extern "C"`, not -// `extern "C-unwind"`, so raw JS exceptions can traverse this bridge. -#[cfg(panic = "abort")] -pub extern "C" fn js_closure_call1_receiverless(closure: *const ClosureHeader, arg0: f64) -> f64 { - js_closure_call1_receiverless_impl(closure, arg0) -} - -#[no_mangle] -#[cfg(not(panic = "abort"))] -pub extern "C-unwind" fn js_closure_call1_receiverless( - closure: *const ClosureHeader, - arg0: f64, -) -> f64 { - js_closure_call1_receiverless_impl(closure, arg0) -} - -#[inline(always)] -fn js_closure_call1_receiverless_impl(closure: *const ClosureHeader, arg0: f64) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - let strategy = (!func_ptr.is_null()).then(|| resolve_strategy(func_ptr)); - if let Some(arrow_strategy) = strategy.filter(|strategy| strategy.is_arrow()) { - // An arrow's `this` is lexical: it reads neither the cell nor the - // parameter, so the receiver passed is unobservable. - return dispatch_call1_resolved( - closure, - func_ptr, - arg0, - arrow_strategy, - crate::closure::JsThis::UNDEFINED, - ); - } - - let scope = crate::gc::RuntimeHandleScope::new(); - let undefined = f64::from_bits(crate::value::TAG_UNDEFINED); - let previous_this = crate::object::js_implicit_this_set(undefined); - let previous_this_handle = scope.root_nanbox_f64(previous_this); - let result = match strategy { - // The cell was bound to `undefined` just above. - Some(strategy) => dispatch_call1_resolved( - closure, - func_ptr, - arg0, - strategy, - crate::closure::JsThis::UNDEFINED, - ), - None => dispatch_proxy_callee_or_throw(closure, &[arg0]), - }; - crate::object::js_implicit_this_set(previous_this_handle.get_nanbox_f64()); - result -} - /// Call a closure with 2 arguments, returning f64 -#[no_mangle] // A dynamically-dispatched closure can throw into a generated caller's catch // landing pad; this bridge is on Next's loadManifest/readFileSync path. -pub extern "C" fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64 { +#[inline(always)] +fn closure_call2_cell(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64 { let func_ptr = get_valid_func_ptr(closure); if func_ptr.is_null() { return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1]); @@ -181,7 +183,7 @@ pub extern "C" fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1 ) @@ -190,13 +192,8 @@ pub extern "C" fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg } /// Call a closure with 3 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call3( - closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, -) -> f64 { +#[inline(always)] +fn closure_call3_cell(closure: *const ClosureHeader, arg0: f64, arg1: f64, arg2: f64) -> f64 { let func_ptr = get_valid_func_ptr(closure); if func_ptr.is_null() { return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1, arg2]); @@ -216,7 +213,7 @@ pub extern "C" fn js_closure_call3( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2 @@ -226,8 +223,8 @@ pub extern "C" fn js_closure_call3( } /// Call a closure with 4 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call4( +#[inline(always)] +fn closure_call4_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -261,7 +258,7 @@ pub extern "C" fn js_closure_call4( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -272,8 +269,8 @@ pub extern "C" fn js_closure_call4( } /// Call a closure with 5 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call5( +#[inline(always)] +fn closure_call5_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -313,7 +310,7 @@ pub extern "C" fn js_closure_call5( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -324,8 +321,8 @@ pub extern "C" fn js_closure_call5( } /// Call a closure with 6 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call6( +#[inline(always)] +fn closure_call6_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -371,7 +368,7 @@ pub extern "C" fn js_closure_call6( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -416,8 +413,8 @@ pub(crate) fn dispatch_rest_or_declared_arity( } /// Call a closure with 7 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call7( +#[inline(always)] +fn closure_call7_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -446,7 +443,7 @@ pub extern "C" fn js_closure_call7( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -459,8 +456,8 @@ pub extern "C" fn js_closure_call7( } /// Call a closure with 8 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call8( +#[inline(always)] +fn closure_call8_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -490,7 +487,7 @@ pub extern "C" fn js_closure_call8( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -504,8 +501,8 @@ pub extern "C" fn js_closure_call8( } /// Call a closure with 9 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call9( +#[inline(always)] +fn closure_call9_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -536,7 +533,7 @@ pub extern "C" fn js_closure_call9( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -551,8 +548,8 @@ pub extern "C" fn js_closure_call9( } /// Call a closure with 10 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call10( +#[inline(always)] +fn closure_call10_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -584,7 +581,7 @@ pub extern "C" fn js_closure_call10( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -600,8 +597,8 @@ pub extern "C" fn js_closure_call10( } /// Call a closure with 11 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call11( +#[inline(always)] +fn closure_call11_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -640,7 +637,7 @@ pub extern "C" fn js_closure_call11( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -657,8 +654,8 @@ pub extern "C" fn js_closure_call11( } /// Call a closure with 12 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call12( +#[inline(always)] +fn closure_call12_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -698,7 +695,7 @@ pub extern "C" fn js_closure_call12( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -716,8 +713,8 @@ pub extern "C" fn js_closure_call12( } /// Call a closure with 13 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call13( +#[inline(always)] +fn closure_call13_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -758,7 +755,7 @@ pub extern "C" fn js_closure_call13( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -777,8 +774,8 @@ pub extern "C" fn js_closure_call13( } /// Call a closure with 14 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call14( +#[inline(always)] +fn closure_call14_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -821,7 +818,7 @@ pub extern "C" fn js_closure_call14( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -841,8 +838,8 @@ pub extern "C" fn js_closure_call14( } /// Call a closure with 15 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call15( +#[inline(always)] +fn closure_call15_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -888,7 +885,7 @@ pub extern "C" fn js_closure_call15( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -909,8 +906,8 @@ pub extern "C" fn js_closure_call15( } /// Call a closure with 16 arguments, returning f64 -#[no_mangle] -pub extern "C" fn js_closure_call16( +#[inline(always)] +fn closure_call16_cell( closure: *const ClosureHeader, arg0: f64, arg1: f64, @@ -957,7 +954,7 @@ pub extern "C" fn js_closure_call16( crate::closure::body_call::js_body_call!( func_ptr, closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, arg2, @@ -978,38 +975,6 @@ pub extern "C" fn js_closure_call16( } } -#[cfg(test)] -mod receiverless_tests { - use super::*; - - extern "C" fn observe_dynamic_this( - _: *const ClosureHeader, - _this: crate::closure::JsThis, - _: f64, - ) -> f64 { - crate::object::js_implicit_this_get() - } - - #[test] - fn one_arg_receiverless_dispatch_only_resets_this_for_non_arrows() { - 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 sentinel = 42.0; - let original = crate::object::js_implicit_this_set(sentinel); - let regular_result = js_closure_call1_receiverless(closure, 0.0); - assert_eq!(regular_result.to_bits(), crate::value::TAG_UNDEFINED); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - - crate::closure::js_register_closure_arrow_function(body); - let arrow_result = js_closure_call1_receiverless(closure, 0.0); - assert_eq!(arrow_result, sentinel); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - crate::object::js_implicit_this_set(original); - } -} - /// `perry_abi::JS_CLOSURE_CALL_ENTRIES` is what codegen DECLARES; these are /// the functions it links to. Each entry must name the function taking /// exactly its index's JS argument count (the coercion below fails to compile @@ -1018,12 +983,11 @@ mod receiverless_tests { mod abi_table_tests { use super::*; - // An ENTRY takes the callee and the JS arguments; unlike a body it does - // not take the receiver (it reads it from the implicit-`this` cell). + // An ENTRY takes the callee, the receiver and the JS arguments. macro_rules! entry { (@f64 $x:tt) => { f64 }; ($f:ident; $($x:tt),*) => {{ - let f: extern "C" fn(*const ClosureHeader $(, entry!(@f64 $x))*) -> f64 = $f; + let f: extern "C" fn(*const ClosureHeader, JsThis $(, entry!(@f64 $x))*) -> f64 = $f; (stringify!($f), f as *const u8) }}; } @@ -1032,7 +996,7 @@ mod abi_table_tests { fn closure_call_entries_match_the_abi_table() { // `js_closure_call0` is `extern "C-unwind"` in unwinding (test) builds. let call0 = { - let f: extern "C-unwind" fn(*const ClosureHeader) -> f64 = js_closure_call0; + let f: extern "C-unwind" fn(*const ClosureHeader, JsThis) -> f64 = js_closure_call0; ("js_closure_call0", f as *const u8) }; let real = [ @@ -1068,3 +1032,36 @@ mod abi_table_tests { } } } + +#[cfg(test)] +mod receiver_binding_tests { + use super::*; + + extern "C" fn observe(_: *const ClosureHeader, this: JsThis, _: f64) -> f64 { + // Stage 1: the body reads the cell; the parameter must agree with it. + let cell = crate::object::js_implicit_this_get(); + assert_eq!(cell.to_bits(), this.bits(), "parameter and cell disagree"); + cell + } + + #[test] + fn an_entry_binds_the_cell_to_the_receiver_it_is_handed_and_restores_it() { + let body = observe as *const u8; + let closure = crate::closure::js_closure_alloc(body, 0); + crate::closure::js_register_closure_arity(body, 1); + + let sentinel = 42.0; + let original = crate::object::js_implicit_this_set(sentinel); + // A plain runtime call passes the cell: the body sees it unchanged. + let plain = js_closure_call1(closure, crate::closure::plain_call_receiver(), 0.0); + assert_eq!(plain, sentinel); + // An explicit receiver is what the body sees, and the cell comes back. + let explicit = js_closure_call1(closure, JsThis::from_f64(7.0), 0.0); + assert_eq!(explicit, 7.0); + assert_eq!(crate::object::js_implicit_this_get(), sentinel); + let undefined = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); + assert_eq!(undefined.to_bits(), crate::value::TAG_UNDEFINED); + assert_eq!(crate::object::js_implicit_this_get(), sentinel); + crate::object::js_implicit_this_set(original); + } +} diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index 39f2bf2ac5..7a7baf5057 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -155,16 +155,14 @@ macro_rules! define_direct_call_site { $(#[$meta:meta])* $site:ident, $arity:literal, $entry:ident, $slow:ident, $($arg:ident),+ ) => { - /// The fallback as a JS body: `$entry` is a call ENTRY (it takes no - /// receiver and reads the implicit-`this` cell itself), so the site - /// stores this adapter, which drops the receiver it is handed — the - /// same one the cell holds (stage 1 of this-as-a-parameter). + /// The fallback, a JS body: the full dispatcher with the receiver it + /// is handed. extern "C" fn $slow( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, $($arg: f64),+ ) -> f64 { - $entry(closure, $($arg),+) + $entry(closure, this, $($arg),+) } $(#[$meta])* @@ -181,12 +179,22 @@ macro_rules! define_direct_call_site { } /// Invoke with the CURRENT closure address (see the module docs on - /// rooting). Falls back to the full dispatcher when the closure - /// did not resolve. The body receives the receiver the - /// implicit-`this` cell holds, read at the call. + /// rooting) and receiver `this` (`plain_call_receiver()` for a + /// plain call). Falls back to the full dispatcher when the closure + /// did not resolve. Stage 1: the implicit-`this` cell holds `this` + /// for the call (`calln::with_receiver`), which the body reads. #[inline] - pub fn call(&self, closure: *const ClosureHeader, $($arg: f64),+) -> f64 { - (self.0)(closure, crate::closure::JsThis::current(), $($arg),+) + pub fn call( + &self, + closure: *const ClosureHeader, + this: crate::closure::JsThis, + $($arg: f64),+ + ) -> f64 { + super::calln::with_receiver(closure, this, || { + // SAFETY: `self.0` is the resolved body of `closure` at + // this arity, or the dispatcher fallback. + unsafe { (self.0)(closure, this, $($arg),+) } + }) } /// Whether the direct target was resolved. Test-only: a "fast @@ -454,10 +462,13 @@ mod tests { "an unregistered, capture-less, non-bound callback must resolve to \ a direct target -- otherwise this whole module is inert" ); - assert_eq!(site.call(c, 1.0, 2.0, 3.0), 123.0); assert_eq!( - site.call(c, 1.0, 2.0, 3.0), - js_closure_call3(c, 1.0, 2.0, 3.0) + site.call(c, crate::closure::plain_call_receiver(), 1.0, 2.0, 3.0), + 123.0 + ); + assert_eq!( + site.call(c, crate::closure::plain_call_receiver(), 1.0, 2.0, 3.0), + js_closure_call3(c, crate::closure::plain_call_receiver(), 1.0, 2.0, 3.0) ); } diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index 3ea50825fe..19b79d7e37 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -1,8 +1,26 @@ -//! Dynamic value-call entry points: `js_native_call_value` (the generic -//! NaN-boxed callee dispatcher), the V8 trampoline bridge `js_closure_call_array`, -//! and the spread-apply bridge `js_closure_call_apply_with_spread`. +//! Dynamic value-call entry points, each taking the receiver after the +//! function: `js_native_call_value` (the generic NaN-boxed callee +//! dispatcher), `js_closure_call_array` (any argument count) and the +//! spread-apply bridge `js_closure_call_apply_with_spread`; plus the V8 +//! trampoline adapter `js_closure_v8_callback`. Stage 1: each binds the +//! implicit-`this` cell to its receiver for the call when the cell holds +//! anything else (`calln::with_receiver`). use super::*; +use crate::closure::JsThis; + +/// The closure a NaN-boxed callee names, for `calln::with_receiver`'s arrow +/// test: null for a non-pointer value (a proxy id, a class ref), which is +/// then bound like any ordinary callee. +#[inline(always)] +fn callee_closure(func_value: f64) -> *const ClosureHeader { + let bits = func_value.to_bits(); + if (bits >> 48) == 0x7FFD { + (bits & 0x0000_FFFF_FFFF_FFFF) as *const ClosureHeader + } else { + std::ptr::null() + } +} /// Call a JavaScript function value with variable arguments /// This is the native implementation for dynamic function dispatch. @@ -30,10 +48,13 @@ use super::*; #[no_mangle] pub unsafe extern "C" fn js_native_call_value( func_value: f64, + this: JsThis, args_ptr: *const f64, args_len: usize, ) -> f64 { - unsafe { js_native_call_value_impl(func_value, args_ptr, args_len) } + super::calln::with_receiver(callee_closure(func_value), this, || unsafe { + js_native_call_value_impl(func_value, args_ptr, args_len) + }) } // Debug/test static archives transport Perry exceptions with Rust unwinding, @@ -43,10 +64,13 @@ pub unsafe extern "C" fn js_native_call_value( #[no_mangle] pub unsafe extern "C-unwind" fn js_native_call_value( func_value: f64, + this: JsThis, args_ptr: *const f64, args_len: usize, ) -> f64 { - unsafe { js_native_call_value_impl(func_value, args_ptr, args_len) } + super::calln::with_receiver(callee_closure(func_value), this, || unsafe { + js_native_call_value_impl(func_value, args_ptr, args_len) + }) } #[inline(always)] @@ -222,13 +246,32 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ // Call with the appropriate arity match dispatch_args_len { - 0 => js_closure_call0(closure), - 1 => js_closure_call1(closure, arg_at(0)), - 2 => js_closure_call2(closure, arg_at(0), arg_at(1)), - 3 => js_closure_call3(closure, arg_at(0), arg_at(1), arg_at(2)), - 4 => js_closure_call4(closure, arg_at(0), arg_at(1), arg_at(2), arg_at(3)), + 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), + 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), arg_at(0)), + 2 => js_closure_call2( + closure, + crate::closure::plain_call_receiver(), + arg_at(0), + arg_at(1), + ), + 3 => js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + arg_at(0), + arg_at(1), + arg_at(2), + ), + 4 => js_closure_call4( + closure, + crate::closure::plain_call_receiver(), + arg_at(0), + arg_at(1), + arg_at(2), + arg_at(3), + ), 5 => js_closure_call5( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -237,6 +280,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 6 => js_closure_call6( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -246,6 +290,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 7 => js_closure_call7( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -256,6 +301,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 8 => js_closure_call8( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -274,6 +320,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ // dispatch must reach them. >16 args fall back to the array path. 9 => js_closure_call9( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -286,6 +333,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 10 => js_closure_call10( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -299,6 +347,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 11 => js_closure_call11( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -313,6 +362,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 12 => js_closure_call12( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -328,6 +378,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 13 => js_closure_call13( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -344,6 +395,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 14 => js_closure_call14( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -361,6 +413,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 15 => js_closure_call15( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -379,6 +432,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ ), 16 => js_closure_call16( closure, + crate::closure::plain_call_receiver(), arg_at(0), arg_at(1), arg_at(2), @@ -403,35 +457,46 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ for i in 0..dispatch_args_len { buf.push(arg_at(i)); } - js_closure_call_array(closure as i64, buf.as_ptr(), buf.len() as i64) + closure_call_array_cell(closure as i64, buf.as_ptr(), buf.len() as i64) } } } -/// Adapter for V8's `native_callback_trampoline` (perry-jsruntime). -/// +/// Call a closure with receiver `this` and `args_len` arguments at +/// `args_ptr` — any count, including more than the 16 `js_closure_callN` +/// covers. Takes the closure pointer already unboxed (an integer register), +/// unlike [`js_native_call_value`]. +#[no_mangle] +pub unsafe extern "C" fn js_closure_call_array( + closure_env: i64, + this: JsThis, + args_ptr: *const f64, + args_len: i64, +) -> f64 { + super::calln::with_receiver(closure_env as *const ClosureHeader, this, || unsafe { + closure_call_array_cell(closure_env, args_ptr, args_len) + }) +} + +/// V8's `native_callback_trampoline` (perry-jsruntime) contract: /// `js_create_callback(func_ptr, closure_env, param_count)` registers a JS /// callable whose trampoline invokes `func_ptr(closure_env, args_ptr, -/// args_len)`. Perry closure bodies have signature -/// `(callee, this, arg0, arg1, ...)` per arity instead -/// (`perry_abi::JS_BODY_*`), so the codegen -/// arm for `Expr::JsCreateCallback` (issue #248 Phase 2B) passes -/// `js_closure_call_array` as the trampoline `func_ptr` and the raw -/// `*const ClosureHeader` (NaN-boxing stripped) as `closure_env`. The -/// trampoline then ends up calling THIS function, which dispatches to -/// the right `js_closure_callN` per `args_len`. -/// -/// Mirrors `js_native_call_value` exactly but takes an i64 closure -/// pointer (already unboxed) instead of an f64 NaN-boxed value, so the -/// SysV-x64 / Win64 first-arg register lands in rdi/rcx (integer) -/// rather than xmm0 — matching the trampoline's `extern "C"` int-arg -/// expectation. +/// args_len)` — with no receiver. The codegen arm for `Expr::JsCreateCallback` +/// (issue #248 Phase 2B) passes THIS function as `func_ptr` and the raw +/// `*const ClosureHeader` as `closure_env`; the closure runs as a plain call +/// (stage 1: with whatever the implicit-`this` cell holds). #[no_mangle] -pub unsafe extern "C" fn js_closure_call_array( +pub unsafe extern "C" fn js_closure_v8_callback( closure_env: i64, args_ptr: *const f64, args_len: i64, ) -> f64 { + unsafe { closure_call_array_cell(closure_env, args_ptr, args_len) } +} + +/// [`js_closure_call_array`]'s dispatch, with the receiver already in the +/// implicit-`this` cell. +unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_len: i64) -> f64 { let closure = closure_env as *const ClosureHeader; if closure.is_null() { throw_not_callable(); @@ -518,17 +583,69 @@ pub unsafe extern "C" fn js_closure_call_array( raw }; match n { - 0 => js_closure_call0(closure), - 1 => js_closure_call1(closure, a(0)), - 2 => js_closure_call2(closure, a(0), a(1)), - 3 => js_closure_call3(closure, a(0), a(1), a(2)), - 4 => js_closure_call4(closure, a(0), a(1), a(2), a(3)), - 5 => js_closure_call5(closure, a(0), a(1), a(2), a(3), a(4)), - 6 => js_closure_call6(closure, a(0), a(1), a(2), a(3), a(4), a(5)), - 7 => js_closure_call7(closure, a(0), a(1), a(2), a(3), a(4), a(5), a(6)), - 8 => js_closure_call8(closure, a(0), a(1), a(2), a(3), a(4), a(5), a(6), a(7)), + 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), + 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), + 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), + 3 => js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + ), + 4 => js_closure_call4( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + ), + 5 => js_closure_call5( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + a(4), + ), + 6 => js_closure_call6( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + ), + 7 => js_closure_call7( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + ), + 8 => js_closure_call8( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + ), 9 => js_closure_call9( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -541,6 +658,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 10 => js_closure_call10( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -554,6 +672,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 11 => js_closure_call11( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -568,6 +687,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 12 => js_closure_call12( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -583,6 +703,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 13 => js_closure_call13( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -599,6 +720,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 14 => js_closure_call14( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -616,6 +738,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 15 => js_closure_call15( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -634,6 +757,7 @@ pub unsafe extern "C" fn js_closure_call_array( ), 16 => js_closure_call16( closure, + crate::closure::plain_call_receiver(), a(0), a(1), a(2), @@ -708,6 +832,7 @@ pub unsafe extern "C" fn js_closure_call_array( #[no_mangle] pub unsafe extern "C" fn js_closure_call_apply_with_spread( closure_box: f64, + this: JsThis, regular_args: *const f64, regular_count: i64, spread_arr_handle: i64, @@ -775,5 +900,5 @@ pub unsafe extern "C" fn js_closure_call_apply_with_spread( heap_buf.as_ptr() }; - js_closure_call_array(closure_ptr as i64, buf_ptr, total as i64) + js_closure_call_array(closure_ptr as i64, this, buf_ptr, total as i64) } diff --git a/crates/perry-runtime/src/closure/dynamic_props.rs b/crates/perry-runtime/src/closure/dynamic_props.rs index a07162789d..a5e0eb3c5f 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -362,7 +362,8 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { let receiver = crate::value::js_nanbox_pointer(ptr as i64); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure); + let result = + crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -444,7 +445,8 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(getter); + let result = + crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -474,7 +476,8 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(getter); + let result = + crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -539,7 +542,8 @@ pub(crate) fn function_prototype_inherited_get( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(getter); + let result = + crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return Some(result); } diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index 40defc0c08..c087098b94 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -35,7 +35,7 @@ pub use alloc::{ pub(crate) use alloc::{ closure_install_boxed_captures, gc_capture_slot_range, singleton_closure_if_cached, }; -pub use body_call::JsThis; +pub use body_call::{current_this, plain_call_receiver, JsThis}; pub use shape::closure_kind_probe; pub(crate) use registry::closure_registry_census; @@ -75,11 +75,11 @@ pub(crate) use dispatch::{ pub use dispatch::{ clean_closure_ptr, dispatch_bound_function, dispatch_bound_method, get_valid_func_ptr, js_closure_call0, js_closure_call1, js_closure_call10, js_closure_call11, js_closure_call12, - js_closure_call13, js_closure_call14, js_closure_call15, js_closure_call16, - js_closure_call1_receiverless, js_closure_call2, js_closure_call3, js_closure_call4, - js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, js_closure_call9, - js_closure_call_apply_with_spread, js_closure_call_array, js_function_bind, - js_native_call_value, throw_not_callable, DirectCall1, DirectCall2, DirectCall3, DirectCall4, + js_closure_call13, js_closure_call14, js_closure_call15, js_closure_call16, js_closure_call2, + js_closure_call3, js_closure_call4, js_closure_call5, js_closure_call6, js_closure_call7, + js_closure_call8, js_closure_call9, js_closure_call_apply_with_spread, js_closure_call_array, + js_function_bind, js_native_call_value, throw_not_callable, DirectCall1, DirectCall2, + DirectCall3, DirectCall4, }; pub use unbox::{js_closure_unbox_callee_checked, js_closure_unbox_callee_checked_rebind}; diff --git a/crates/perry-runtime/src/closure/receiver_param_tests.rs b/crates/perry-runtime/src/closure/receiver_param_tests.rs index 8023153ec6..6c19655924 100644 --- a/crates/perry-runtime/src/closure/receiver_param_tests.rs +++ b/crates/perry-runtime/src/closure/receiver_param_tests.rs @@ -6,7 +6,7 @@ //! Each test binds a receiver in the cell, calls a probe body through one //! dispatch route (exact arity, padded arity, rest bundling, the padded wide //! ladder, a hoisted `DirectCallN`), and checks what the probe received. -//! Sabotage: the funnel passes anything but `JsThis::current()` -> the +//! Sabotage: the funnel passes anything but `current_this()` -> the //! receiver assertions fail; the funnel drops the receiver argument (the //! arguments shift one slot) -> the argument assertions fail. @@ -73,7 +73,15 @@ fn receiver() -> f64 { fn an_exact_arity_call_passes_the_cell_receiver_then_the_arguments() { let closure = js_closure_alloc(probe3 as *const u8, 0); js_register_closure_arity(probe3 as *const u8, 3); - let r = with_receiver(receiver(), || js_closure_call3(closure, 1.0, 2.0, 3.0)); + let r = with_receiver(receiver(), || { + js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + 1.0, + 2.0, + 3.0, + ) + }); assert_eq!(r, 3.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits(), "the body's `this` parameter"); @@ -84,7 +92,9 @@ fn an_exact_arity_call_passes_the_cell_receiver_then_the_arguments() { 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); - with_receiver(receiver(), || js_closure_call1(closure, 9.0)); + with_receiver(receiver(), || { + js_closure_call1(closure, crate::closure::plain_call_receiver(), 9.0) + }); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!(args, vec![9f64.to_bits(), UNDEF, UNDEF]); @@ -94,7 +104,16 @@ fn a_padded_call_passes_the_receiver_and_pads_after_it() { 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); - with_receiver(receiver(), || js_closure_call4(closure, 5.0, 6.0, 7.0, 8.0)); + with_receiver(receiver(), || { + js_closure_call4( + closure, + crate::closure::plain_call_receiver(), + 5.0, + 6.0, + 7.0, + 8.0, + ) + }); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!( @@ -110,7 +129,12 @@ fn a_wide_call_passes_the_receiver_and_every_argument_slot() { js_register_closure_arity(probe_wide as *const u8, 36); let args: Vec = (0..36).map(f64::from).collect(); let r = with_receiver(receiver(), || unsafe { - js_closure_call_array(closure as i64, args.as_ptr(), args.len() as i64) + js_closure_call_array( + closure as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len() as i64, + ) }); assert_eq!(r, 0.0); let (this, seen_args) = seen(); @@ -127,7 +151,15 @@ fn a_hoisted_direct_call_passes_the_cell_receiver() { js_register_closure_arity(probe3 as *const u8, 3); let site = DirectCall3::resolve(closure); assert!(site.is_direct(), "the probe resolves to a direct call"); - with_receiver(receiver(), || site.call(closure, 1.0, 2.0, 3.0)); + with_receiver(receiver(), || { + site.call( + closure, + crate::closure::plain_call_receiver(), + 1.0, + 2.0, + 3.0, + ) + }); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!(args, vec![1f64.to_bits(), 2f64.to_bits(), 3f64.to_bits()]); diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index eaf7eecabf..65603c8443 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -590,6 +590,7 @@ mod dispatch_recent_tests { unsafe { crate::closure::js_closure_call_array( closure as i64, + crate::closure::plain_call_receiver(), args.as_ptr(), args.len() as i64, ) @@ -624,6 +625,7 @@ mod dispatch_recent_tests { unsafe { crate::closure::js_closure_call_array( closure as i64, + crate::closure::plain_call_receiver(), direct_arg.as_ptr(), direct_arg.len() as i64, ) @@ -641,6 +643,7 @@ mod dispatch_recent_tests { unsafe { crate::closure::js_closure_call_array( closure as i64, + crate::closure::plain_call_receiver(), rest_args.as_ptr(), rest_args.len() as i64, ) @@ -1195,11 +1198,11 @@ pub unsafe fn dispatch_rest_bundled( ($($i:tt),* $(,)?) => {{ if let Some(arguments_double) = all_arguments_double { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*, rest_double, arguments_double + func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*, rest_double, arguments_double ) } else { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*, rest_double + func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*, rest_double ) } }}; @@ -1282,7 +1285,7 @@ pub unsafe fn dispatch_with_arity( // `arm!` macro builds the (padded) call args from the arg-index token list. macro_rules! arm { ($($i:tt),* $(,)?) => { - crate::closure::body_call::js_body_call!(func_ptr, closure, crate::closure::JsThis::current() $(, a!($i))*) + crate::closure::body_call::js_body_call!(func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*) }; } match k { diff --git a/crates/perry-runtime/src/closure/shape.rs b/crates/perry-runtime/src/closure/shape.rs index 07ddc49fae..0bf997f13e 100644 --- a/crates/perry-runtime/src/closure/shape.rs +++ b/crates/perry-runtime/src/closure/shape.rs @@ -611,7 +611,12 @@ mod tests { "target, this, partial args, name snapshot, bound length" ); // The bound call still sees target + partial args + call args. - let r = crate::closure::js_closure_call2(b as *const ClosureHeader, 2.0, 3.0); + let r = crate::closure::js_closure_call2( + b as *const ClosureHeader, + crate::closure::plain_call_receiver(), + 2.0, + 3.0, + ); assert_eq!(r, 6.0); } diff --git a/crates/perry-runtime/src/closure/tests.rs b/crates/perry-runtime/src/closure/tests.rs index 70e4f7e7f9..491553e50e 100644 --- a/crates/perry-runtime/src/closure/tests.rs +++ b/crates/perry-runtime/src/closure/tests.rs @@ -12,7 +12,7 @@ extern "C" fn test_closure_func( fn test_closure_basic() { let closure = js_closure_alloc(test_closure_func as *const u8, 1); js_closure_set_capture_f64(closure, 0, 21.0); - let result = js_closure_call0(closure); + let result = js_closure_call0(closure, crate::closure::plain_call_receiver()); assert_eq!(result, 42.0); } diff --git a/crates/perry-runtime/src/closure/wide_call.rs b/crates/perry-runtime/src/closure/wide_call.rs index 6dc56a979e..e39f2b2b91 100644 --- a/crates/perry-runtime/src/closure/wide_call.rs +++ b/crates/perry-runtime/src/closure/wide_call.rs @@ -64,7 +64,7 @@ pub(crate) unsafe fn dispatch_wide_abi( padded!($slots, [$($i,)+ $($i + $step),+], $step * 2, $($more)*) }; ($slots:ident, [$($i:expr),+], $step:expr,) => {{ - crate::closure::body_call::js_body_call_unwind!(func_ptr, closure, crate::closure::JsThis::current() $(, $slots[$i])+) + crate::closure::body_call::js_body_call_unwind!(func_ptr, closure, crate::closure::body_call::current_this() $(, $slots[$i])+) }}; } macro_rules! fill { diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 33772178f1..4dc97c32fc 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -350,7 +350,12 @@ pub(crate) fn cluster_emit_event(event: &str, args: &[f64]) -> bool { let cb = f64::from_bits(listener.callback_bits); js_implicit_this_set(cluster_default_value()); unsafe { - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } js_implicit_this_set(prev.get_nanbox_f64()); } @@ -820,7 +825,7 @@ extern "C" fn cluster_worker_send( } crate::child_process::cp_method_send( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), message, a2, a3, @@ -839,7 +844,7 @@ extern "C" fn cluster_worker_kill( } let _ = crate::child_process::cp_method_kill( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), signal, ); TAG_UNDEFINED_F64 @@ -850,7 +855,11 @@ extern "C" fn cluster_worker_destroy( _this: crate::closure::JsThis, signal: f64, ) -> f64 { - cluster_worker_kill(std::ptr::null(), crate::closure::JsThis::current(), signal) + cluster_worker_kill( + std::ptr::null(), + crate::closure::body_call::current_this(), + signal, + ) } extern "C" fn cluster_worker_disconnect( @@ -864,7 +873,7 @@ extern "C" fn cluster_worker_disconnect( } else { let _ = crate::child_process::cp_method_disconnect( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), ); } worker @@ -1360,7 +1369,12 @@ extern "C" fn cluster_callback_thunk( ) -> f64 { let callback = f64::from_bits(crate::closure::js_closure_get_capture_ptr(closure, 0) as u64); unsafe { - let _ = crate::closure::js_native_call_value(callback, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } TAG_UNDEFINED_F64 } @@ -1454,7 +1468,12 @@ fn invoke_disconnect_callbacks_if_idle() { for bits in callbacks { let callback = f64::from_bits(bits); unsafe { - let _ = crate::closure::js_native_call_value(callback, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } } } @@ -1474,7 +1493,12 @@ fn call_worker_disconnect(worker: f64) { let disconnect = get_field(worker, b"disconnect"); if is_closure_value(disconnect) { unsafe { - let _ = crate::closure::js_native_call_value(disconnect, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + disconnect, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } } } @@ -1533,7 +1557,12 @@ fn emit(target: f64, event: &str, args: &[f64]) -> bool { let cb = crate::array::js_array_get_f64(arr, i); js_implicit_this_set(target); unsafe { - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } js_implicit_this_set(prev.get_nanbox_f64()); fired = true; diff --git a/crates/perry-runtime/src/collection_iter.rs b/crates/perry-runtime/src/collection_iter.rs index 1e64d79e15..4198323c28 100644 --- a/crates/perry-runtime/src/collection_iter.rs +++ b/crates/perry-runtime/src/collection_iter.rs @@ -286,7 +286,12 @@ pub(crate) fn call_with_this_capturing_throw( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_value)); let result = call_capturing_throw(|| unsafe { - crate::closure::js_native_call_value(callee, args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + callee, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result diff --git a/crates/perry-runtime/src/dgram.rs b/crates/perry-runtime/src/dgram.rs index 9512701e6f..d2135769cd 100644 --- a/crates/perry-runtime/src/dgram.rs +++ b/crates/perry-runtime/src/dgram.rs @@ -817,8 +817,14 @@ pub(crate) fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = - unsafe { crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/disposable.rs b/crates/perry-runtime/src/disposable.rs index 7c63eafec3..472bb5a921 100644 --- a/crates/perry-runtime/src/disposable.rs +++ b/crates/perry-runtime/src/disposable.rs @@ -108,7 +108,14 @@ extern "C" fn bound_dispose_thunk( } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(js_implicit_this_set(resource)); - let result = unsafe { js_native_call_value(method, std::ptr::null(), 0) }; + let result = unsafe { + js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; js_implicit_this_set(prev.get_nanbox_f64()); result } @@ -219,7 +226,7 @@ fn call_disposer(callable: f64) { if ptr.is_null() { return; } - js_closure_call0(ptr); + js_closure_call0(ptr, crate::closure::plain_call_receiver()); } /// Run every registered disposer in LIFO order and clear the array. Marks the @@ -258,7 +265,7 @@ extern "C" fn adopt_disposer_thunk( if cb.is_pointer() { let cb_ptr = js_nanbox_get_pointer(on_dispose) as *const ClosureHeader; if !cb_ptr.is_null() { - crate::closure::js_closure_call1(cb_ptr, value); + crate::closure::js_closure_call1(cb_ptr, crate::closure::plain_call_receiver(), value); } } undefined() diff --git a/crates/perry-runtime/src/dyn_eval/bench.rs b/crates/perry-runtime/src/dyn_eval/bench.rs index b206b24260..659b93335d 100644 --- a/crates/perry-runtime/src/dyn_eval/bench.rs +++ b/crates/perry-runtime/src/dyn_eval/bench.rs @@ -21,7 +21,14 @@ use super::dyn_function_from_strings; use super::{root_get, root_push, roots_truncate}; fn call(f: f64, args: &[f64]) -> f64 { - unsafe { crate::closure::js_native_call_value(f, args.as_ptr(), args.len()) } + unsafe { + crate::closure::js_native_call_value( + f, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + } } fn env_usize(key: &str, default: usize) -> usize { diff --git a/crates/perry-runtime/src/dyn_eval/bridge.rs b/crates/perry-runtime/src/dyn_eval/bridge.rs index 56809a4a08..f58b3ab24a 100644 --- a/crates/perry-runtime/src/dyn_eval/bridge.rs +++ b/crates/perry-runtime/src/dyn_eval/bridge.rs @@ -255,7 +255,14 @@ pub(crate) fn set_member(base: f64, name: &str, value: f64) { pub(crate) fn call_function(callee: f64, this: f64, args: &[f64]) -> f64 { let prev = crate::object::js_implicit_this_set(this); let prev_idx = root_push(prev); - let result = unsafe { crate::closure::js_native_call_value(callee, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + callee, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; let result_idx = root_push(result); crate::object::js_implicit_this_set(root_get(prev_idx)); let result = root_get(result_idx); diff --git a/crates/perry-runtime/src/dyn_eval/registry_lifetime_tests.rs b/crates/perry-runtime/src/dyn_eval/registry_lifetime_tests.rs index 4d0956a65e..443592c378 100644 --- a/crates/perry-runtime/src/dyn_eval/registry_lifetime_tests.rs +++ b/crates/perry-runtime/src/dyn_eval/registry_lifetime_tests.rs @@ -9,7 +9,14 @@ fn closure_fn_id(closure: f64) -> u32 { } fn call0(function: f64) -> f64 { - unsafe { crate::closure::js_native_call_value(function, std::ptr::null(), 0) } + unsafe { + crate::closure::js_native_call_value( + function, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } } /// Disable the source cache (its byte cap is "full") for one test body, so diff --git a/crates/perry-runtime/src/dyn_eval/tests.rs b/crates/perry-runtime/src/dyn_eval/tests.rs index a702999818..5b96d8ec9a 100644 --- a/crates/perry-runtime/src/dyn_eval/tests.rs +++ b/crates/perry-runtime/src/dyn_eval/tests.rs @@ -11,7 +11,14 @@ fn dyn_fn(args: &[&str]) -> f64 { } fn call(f: f64, args: &[f64]) -> f64 { - unsafe { crate::closure::js_native_call_value(f, args.as_ptr(), args.len()) } + unsafe { + crate::closure::js_native_call_value( + f, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + } } fn num(n: f64) -> f64 { diff --git a/crates/perry-runtime/src/error.rs b/crates/perry-runtime/src/error.rs index 31dc5081bf..f6f942324d 100644 --- a/crates/perry-runtime/src/error.rs +++ b/crates/perry-runtime/src/error.rs @@ -1306,7 +1306,12 @@ unsafe fn compute_stack_value(receiver: f64) -> f64 { let structured = build_structured_stack(10); let prep_ptr = crate::value::js_nanbox_get_pointer(prep) as *const crate::closure::ClosureHeader; - return crate::closure::js_closure_call2(prep_ptr, receiver, structured); + return crate::closure::js_closure_call2( + prep_ptr, + crate::closure::plain_call_receiver(), + receiver, + structured, + ); } let s = make_stack("Error", ""); crate::value::js_nanbox_string(s as i64) diff --git a/crates/perry-runtime/src/error_subclass_stack.rs b/crates/perry-runtime/src/error_subclass_stack.rs index 2e715cd942..c1d15e8daf 100644 --- a/crates/perry-runtime/src/error_subclass_stack.rs +++ b/crates/perry-runtime/src/error_subclass_stack.rs @@ -87,7 +87,12 @@ extern "C" fn error_subclass_stack_getter( let structured = build_structured_stack(10); let prep_ptr = crate::value::js_nanbox_get_pointer(prep) as *const crate::closure::ClosureHeader; - return crate::closure::js_closure_call2(prep_ptr, receiver, structured); + return crate::closure::js_closure_call2( + prep_ptr, + crate::closure::plain_call_receiver(), + receiver, + structured, + ); } // #9486: capture slot 0 holds the ENCODED capture (native return // addresses, plus the #5247 line when one was recorded), not a diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index 00d559691c..fefbea0806 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -1023,7 +1023,12 @@ pub unsafe extern "C" fn js_event_target_dispatch_event( remove_event_listener_value_with_capture(target, event_name_ptr, callback, capture); } crate::object::js_implicit_this_set(target_value); - let _ = crate::closure::js_native_call_value(callable, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + callable, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if event_bool_field(event_ptr, b"_immediateStopped") { break; @@ -1176,7 +1181,7 @@ extern "C" fn event_proto_prevent_default_thunk( _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_prevent_default_thunk(closure, crate::closure::JsThis::current()) + event_prevent_default_thunk(closure, crate::closure::body_call::current_this()) } extern "C" fn event_proto_stop_propagation_thunk( @@ -1184,7 +1189,7 @@ extern "C" fn event_proto_stop_propagation_thunk( _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_stop_propagation_thunk(closure, crate::closure::JsThis::current()) + event_stop_propagation_thunk(closure, crate::closure::body_call::current_this()) } extern "C" fn event_proto_stop_immediate_propagation_thunk( @@ -1192,7 +1197,7 @@ extern "C" fn event_proto_stop_immediate_propagation_thunk( _this: crate::closure::JsThis, ) -> f64 { event_proto_receiver(); - event_stop_immediate_propagation_thunk(closure, crate::closure::JsThis::current()) + event_stop_immediate_propagation_thunk(closure, crate::closure::body_call::current_this()) } extern "C" fn event_proto_init_event_thunk( diff --git a/crates/perry-runtime/src/frame.rs b/crates/perry-runtime/src/frame.rs index a83f0cd392..a49b847dd4 100644 --- a/crates/perry-runtime/src/frame.rs +++ b/crates/perry-runtime/src/frame.rs @@ -179,7 +179,12 @@ pub extern "C" fn js_frame_tick(timestamp_ms: f64) -> i32 { let previous_roots = crate::async_context::root_snapshot(&scope, &previous); let cb_ptr = cb_handle.get_raw_const_ptr::(); with_frame_uncaught_trap(|| { - js_closure_call2(cb_ptr, timestamp_ms, delta_ms); + js_closure_call2( + cb_ptr, + crate::closure::plain_call_receiver(), + timestamp_ms, + delta_ms, + ); }); crate::async_context::refresh_snapshot_from_roots(&mut previous, &previous_roots); crate::async_context::restore_context(previous); diff --git a/crates/perry-runtime/src/fs/cp.rs b/crates/perry-runtime/src/fs/cp.rs index f69df97c1e..3cf7e8ab10 100644 --- a/crates/perry-runtime/src/fs/cp.rs +++ b/crates/perry-runtime/src/fs/cp.rs @@ -150,7 +150,12 @@ pub(crate) fn copy_filter_allows(src: &Path, dst: &Path, opts: FsCopyOptions) -> let src_value = crate::value::js_nanbox_string(s as i64); let s = js_string_from_bytes(dst_string.as_bytes().as_ptr(), dst_string.len() as u32); let dst_value = crate::value::js_nanbox_string(s as i64); - let result = crate::closure::js_closure_call2(filter, src_value, dst_value); + let result = crate::closure::js_closure_call2( + filter, + crate::closure::plain_call_receiver(), + src_value, + dst_value, + ); unsafe { if let Some(promise) = promise_ptr_from_value(result) { if opts.sync_filter { diff --git a/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs b/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs index 2411355661..ee2ce01b66 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs @@ -749,6 +749,7 @@ fn excluded_by_function(entry: &FsGlobMatch, options: &FsGlobOptions<'_>) -> Res let arg = scope.root_nanbox_f64(arg); crate::closure::js_closure_call1( extract_closure_ptr(callback.get_nanbox_f64()), + crate::closure::plain_call_receiver(), arg.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 1412b1152c..4d5871fa2f 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -416,7 +416,7 @@ fn emit_listener0(object_value: f64, callback: f64) { object_handle.get_nanbox_f64(), )); with_watcher_uncaught_trap(|| { - crate::closure::js_closure_call0(cb); + crate::closure::js_closure_call0(cb, crate::closure::plain_call_receiver()); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -451,7 +451,12 @@ fn emit_fs_watch_event( } crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { - crate::closure::js_closure_call2(cb, refreshed_args[0], refreshed_args[1]); + crate::closure::js_closure_call2( + cb, + crate::closure::plain_call_receiver(), + refreshed_args[0], + refreshed_args[1], + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -488,7 +493,12 @@ fn emit_watch_file_change( } crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { - crate::closure::js_closure_call2(cb, refreshed_args[0], refreshed_args[1]); + crate::closure::js_closure_call2( + cb, + crate::closure::plain_call_receiver(), + refreshed_args[0], + refreshed_args[1], + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -908,7 +918,11 @@ fn emit_fs_watch_error( } crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { - crate::closure::js_closure_call1(cb, err_handle.get_nanbox_f64()); + crate::closure::js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + err_handle.get_nanbox_f64(), + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } diff --git a/crates/perry-runtime/src/fs/fd_ops.rs b/crates/perry-runtime/src/fs/fd_ops.rs index 3b94055f31..4309d106ab 100644 --- a/crates/perry-runtime/src/fs/fd_ops.rs +++ b/crates/perry-runtime/src/fs/fd_ops.rs @@ -1002,17 +1002,28 @@ extern "C" fn dir_finish_read_callback_impl( let kind = crate::closure::js_closure_get_capture_f64(closure, 3) as i32; match kind { 0 => { - crate::closure::js_closure_call2(callback, f64::from_bits(TAG_NULL), value); + crate::closure::js_closure_call2( + callback, + crate::closure::plain_call_receiver(), + f64::from_bits(TAG_NULL), + value, + ); } 1 => { crate::closure::js_closure_call2( callback, + crate::closure::plain_call_receiver(), f64::from_bits(TAG_NULL), f64::from_bits(TAG_NULL), ); } _ => { - crate::closure::js_closure_call2(callback, value, f64::from_bits(TAG_NULL)); + crate::closure::js_closure_call2( + callback, + crate::closure::plain_call_receiver(), + value, + f64::from_bits(TAG_NULL), + ); } } dir_clear_operation_pending(id); @@ -1058,17 +1069,28 @@ pub(crate) extern "C" fn dir_read_impl( } else { match entry { Ok(Some(value)) => { - crate::closure::js_closure_call2(cb, f64::from_bits(TAG_NULL), value); + crate::closure::js_closure_call2( + cb, + crate::closure::plain_call_receiver(), + f64::from_bits(TAG_NULL), + value, + ); } Ok(None) => { crate::closure::js_closure_call2( cb, + crate::closure::plain_call_receiver(), f64::from_bits(TAG_NULL), f64::from_bits(TAG_NULL), ); } Err(err) => { - crate::closure::js_closure_call2(cb, err, f64::from_bits(TAG_NULL)); + crate::closure::js_closure_call2( + cb, + crate::closure::plain_call_receiver(), + err, + f64::from_bits(TAG_NULL), + ); } } } @@ -1097,7 +1119,7 @@ pub(crate) extern "C" fn dir_close_impl( Ok(()) => f64::from_bits(TAG_NULL), Err(err) => err, }; - crate::closure::js_closure_call1(cb, err); + crate::closure::js_closure_call1(cb, crate::closure::plain_call_receiver(), err); return f64::from_bits(crate::value::TAG_UNDEFINED); } match closed { diff --git a/crates/perry-runtime/src/fs/stream.rs b/crates/perry-runtime/src/fs/stream.rs index 056881fd1a..545357b79d 100644 --- a/crates/perry-runtime/src/fs/stream.rs +++ b/crates/perry-runtime/src/fs/stream.rs @@ -649,7 +649,7 @@ fn emit_event0(id: usize, event: &str) { for cb in callbacks { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { - js_closure_call0(cb_ptr); + js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } } bridge_to_stream_listeners(id, event, &[], handled_locally); @@ -662,7 +662,7 @@ fn emit_event1(id: usize, event: &str, arg: f64) { for cb in callbacks { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { - js_closure_call1(cb_ptr, arg); + js_closure_call1(cb_ptr, crate::closure::plain_call_receiver(), arg); } } bridge_to_stream_listeners(id, event, &[arg], handled_locally); @@ -835,7 +835,7 @@ fn call_stream_callback0(callback: f64) { if is_callable_value(callback) { let cb_ptr = extract_closure_ptr(callback); if !cb_ptr.is_null() { - crate::closure::js_closure_call0(cb_ptr); + crate::closure::js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } } } @@ -844,7 +844,7 @@ fn call_stream_callback1(callback: f64, arg: f64) { if is_callable_value(callback) { let cb_ptr = extract_closure_ptr(callback); if !cb_ptr.is_null() { - crate::closure::js_closure_call1(cb_ptr, arg); + crate::closure::js_closure_call1(cb_ptr, crate::closure::plain_call_receiver(), arg); } } } @@ -1644,9 +1644,13 @@ fn stream_on_common(id: usize, event_value: f64, cb: f64, once: bool) { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { if name == "open" || name == "error" { - crate::closure::js_closure_call1(cb_ptr, arg); + crate::closure::js_closure_call1( + cb_ptr, + crate::closure::plain_call_receiver(), + arg, + ); } else { - crate::closure::js_closure_call0(cb_ptr); + crate::closure::js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } } } diff --git a/crates/perry-runtime/src/fs/stream/utf8_stream.rs b/crates/perry-runtime/src/fs/stream/utf8_stream.rs index 41e61ecac0..5d7d99e905 100644 --- a/crates/perry-runtime/src/fs/stream/utf8_stream.rs +++ b/crates/perry-runtime/src/fs/stream/utf8_stream.rs @@ -470,7 +470,7 @@ pub(crate) fn utf8_emit_event0(id: usize, event: &str) { for cb in callbacks { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { - crate::closure::js_closure_call0(cb_ptr); + crate::closure::js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } } } @@ -480,7 +480,7 @@ fn utf8_emit_event1(id: usize, event: &str, arg: f64) { for cb in callbacks { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { - crate::closure::js_closure_call1(cb_ptr, arg); + crate::closure::js_closure_call1(cb_ptr, crate::closure::plain_call_receiver(), arg); } } } @@ -504,7 +504,7 @@ fn utf8_add_listener(id: usize, event: &str, cb: f64, once: bool) { if let Some((_name, _arg)) = immediate { let cb_ptr = extract_closure_ptr(cb); if !cb_ptr.is_null() { - crate::closure::js_closure_call0(cb_ptr); + crate::closure::js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } return; } @@ -802,7 +802,7 @@ fn utf8_stream_flush_by_id(id: usize, callback: f64) -> f64 { if is_callable_value(callback) { let cb = extract_closure_ptr(callback); if !cb.is_null() { - crate::closure::js_closure_call1(cb, err); + crate::closure::js_closure_call1(cb, crate::closure::plain_call_receiver(), err); } return undefined_value(); } @@ -812,7 +812,7 @@ fn utf8_stream_flush_by_id(id: usize, callback: f64) -> f64 { if is_callable_value(callback) { let cb = extract_closure_ptr(callback); if !cb.is_null() { - crate::closure::js_closure_call0(cb); + crate::closure::js_closure_call0(cb, crate::closure::plain_call_receiver()); } } return undefined_value(); @@ -829,13 +829,13 @@ fn utf8_stream_flush_by_id(id: usize, callback: f64) -> f64 { if is_callable_value(callback) { let cb = extract_closure_ptr(callback); if !cb.is_null() { - crate::closure::js_closure_call1(cb, err); + crate::closure::js_closure_call1(cb, crate::closure::plain_call_receiver(), err); } } } else if is_callable_value(callback) { let cb = extract_closure_ptr(callback); if !cb.is_null() { - crate::closure::js_closure_call0(cb); + crate::closure::js_closure_call0(cb, crate::closure::plain_call_receiver()); } } undefined_value() diff --git a/crates/perry-runtime/src/fs/stream/write_file_input.rs b/crates/perry-runtime/src/fs/stream/write_file_input.rs index e8042a059a..18e15f2921 100644 --- a/crates/perry-runtime/src/fs/stream/write_file_input.rs +++ b/crates/perry-runtime/src/fs/stream/write_file_input.rs @@ -152,7 +152,14 @@ fn call_well_known_iterator(value: f64, name: &str) -> Option { let method = well_known_iterator_method(value, name)?; let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); - let iterator = unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let iterator = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots.rs b/crates/perry-runtime/src/gc/tests/runtime_roots.rs index 43073c6402..a0e53c6db4 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots.rs @@ -167,7 +167,10 @@ fn assert_callable_closure(bits: u64) -> usize { assert_eq!(bits & TAG_MASK, POINTER_TAG); let ptr = (bits & POINTER_MASK) as usize; assert_eq!( - crate::closure::js_closure_call0(ptr as *const crate::closure::ClosureHeader), + crate::closure::js_closure_call0( + ptr as *const crate::closure::ClosureHeader, + crate::closure::plain_call_receiver() + ), 0.0 ); ptr 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 7dc918457f..ef33f84aed 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 @@ -303,7 +303,12 @@ fn rest_bundling_roots_the_rest_array_across_the_arguments_array() { // under test here, so the closure pointer is a scoped argument to it — // `with_mut_ptr` is the blessed shape for that instead of a bare read. closure_handle.with_mut_ptr::(|ptr| unsafe { - crate::closure::js_closure_call_array(ptr as i64, args.as_ptr(), args.len() as i64); + crate::closure::js_closure_call_array( + ptr as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len() as i64, + ); }); assert!( 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 431ef6dbc1..546122552e 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 @@ -214,7 +214,8 @@ fn test_json_reviver_treats_closure_property_as_leaf_after_copied_minor_gc() { assert_eq!(stored & TAG_MASK, POINTER_TAG); assert_eq!( crate::closure::js_closure_call0( - (stored & POINTER_MASK) as *const crate::closure::ClosureHeader + (stored & POINTER_MASK) as *const crate::closure::ClosureHeader, + crate::closure::plain_call_receiver() ), 0.0 ); @@ -1262,7 +1263,10 @@ impl Drop for RuntimeCallbackRootGuard { fn assert_moved_callable_closure(bits: u64, original: usize) { let rewritten = assert_moved_closure_ptr(bits, original); assert_eq!( - crate::closure::js_closure_call0(rewritten as *const crate::closure::ClosureHeader), + crate::closure::js_closure_call0( + rewritten as *const crate::closure::ClosureHeader, + crate::closure::plain_call_receiver() + ), 0.0 ); } 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 ded5d05b43..c872fcd4ab 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 @@ -91,7 +91,14 @@ fn test_closure_rest_dispatch_roots_args_during_rest_array_alloc_gc() { force_next_general_arena_alloc_slow(); trigger_guard.make_arena_trigger_due(); let before = gc_collection_count(); - let result = unsafe { crate::closure::js_closure_call_array(closure as i64, args.as_ptr(), 1) }; + let result = unsafe { + crate::closure::js_closure_call_array( + closure as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + }; let result_scope = RuntimeHandleScope::new(); let result_root = result_scope.root_nanbox_f64(result); drain_scheduled_minor_gc(before, "rest-array creation"); @@ -127,6 +134,7 @@ fn test_bound_timer_dispatch_roots_args_during_async_hook_init_gc() { let timer_value = crate::closure::js_closure_call3( bound, + crate::closure::plain_call_receiver(), f64::from_bits(ptr_bits(timer_callback as usize)), 0.0, arg, 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 848beb3eb4..be7b31718b 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 @@ -236,7 +236,12 @@ fn perex_dispatch_generic_prototype_test_coerces_before_getting_exec() { )); let args = [argument.get_nanbox_f64()]; let result = crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_native_call_value(method.get_nanbox_f64(), args.as_ptr(), 1) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) }); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); assert_eq!(result.unwrap().to_bits(), crate::value::TAG_TRUE); 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 0c8c467ff0..ab02984833 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 @@ -585,7 +585,12 @@ extern "C" fn ordered_factory( flags: f64, ) -> f64 { ordered(5); - factory(closure, crate::closure::JsThis::current(), receiver, flags) + factory( + closure, + crate::closure::body_call::current_this(), + receiver, + flags, + ) } extern "C" fn ordered_index( _: *const crate::closure::ClosureHeader, 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 81e0940173..6097b239ce 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 @@ -981,7 +981,7 @@ extern "C" fn primitive_number_hint( hint: f64, ) -> f64 { assert_eq!(bytes(hint), b"number"); - primitive_result(c, crate::closure::JsThis::current()) + primitive_result(c, crate::closure::body_call::current_this()) } extern "C" fn primitive_string_hint( c: *const crate::closure::ClosureHeader, @@ -989,7 +989,7 @@ extern "C" fn primitive_string_hint( hint: f64, ) -> f64 { assert_eq!(bytes(hint), b"string"); - primitive_result(c, crate::closure::JsThis::current()) + primitive_result(c, crate::closure::body_call::current_this()) } #[test] 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 c01012c977..5ceb821087 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 @@ -32,6 +32,7 @@ extern "C" fn test_thenable_then_force_minor_gc( unsafe { crate::closure::js_native_call_value( resolve_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); diff --git a/crates/perry-runtime/src/geisterhand_registry.rs b/crates/perry-runtime/src/geisterhand_registry.rs index fafba63221..d0e6f36531 100644 --- a/crates/perry-runtime/src/geisterhand_registry.rs +++ b/crates/perry-runtime/src/geisterhand_registry.rs @@ -109,8 +109,8 @@ static TREE_CONDVAR: Condvar = Condvar::new(); static TREE_REQUESTED: Mutex = Mutex::new(false); extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: crate::closure::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: crate::closure::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -438,10 +438,10 @@ pub extern "C" fn perry_geisterhand_pump() { unsafe { match args.len() { 0 => { - js_closure_call0(ptr); + js_closure_call0(ptr, crate::closure::plain_call_receiver()); } _ => { - js_closure_call1(ptr, args[0]); + js_closure_call1(ptr, crate::closure::plain_call_receiver(), args[0]); } } } @@ -479,7 +479,7 @@ pub extern "C" fn perry_geisterhand_pump() { let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(ptr, nanboxed); + js_closure_call1(ptr, crate::closure::plain_call_receiver(), nanboxed); } } } diff --git a/crates/perry-runtime/src/intl/subclass.rs b/crates/perry-runtime/src/intl/subclass.rs index 94fee140bf..1af40ded1a 100644 --- a/crates/perry-runtime/src/intl/subclass.rs +++ b/crates/perry-runtime/src/intl/subclass.rs @@ -154,7 +154,12 @@ pub(crate) unsafe fn intl_subclass_super( let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); // #10490: the displaced `new.target` crosses the same call. let prev_nt = this_scope.root_nanbox_f64(crate::object::js_new_target_set(parent_val)); - let instance = crate::closure::js_native_call_value(parent_val, args_ptr, args_len); + let instance = crate::closure::js_native_call_value( + parent_val, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_new_target_set(prev_nt.get_nanbox_f64()); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // Re-home the freshly-built instance's brand + bound methods onto `this`. diff --git a/crates/perry-runtime/src/iterator_helpers.rs b/crates/perry-runtime/src/iterator_helpers.rs index 4d4043494f..bb974fc735 100644 --- a/crates/perry-runtime/src/iterator_helpers.rs +++ b/crates/perry-runtime/src/iterator_helpers.rs @@ -155,7 +155,12 @@ unsafe fn iterator_step(iter_f64: f64) -> (f64, bool) { let next_h = scope.root_nanbox_f64(f64::from_bits(next_val.bits())); let prev_this_h = scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_h.get_nanbox_f64())); - let r = crate::closure::js_native_call_value(next_h.get_nanbox_f64(), std::ptr::null(), 0); + let r = crate::closure::js_native_call_value( + next_h.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); r } else { @@ -271,7 +276,7 @@ unsafe fn helper_next(obj: *mut ObjectHeader) -> f64 { let mapped = if cb.is_null() { v } else { - js_closure_call1(cb, v) + js_closure_call1(cb, crate::closure::plain_call_receiver(), v) }; make_iter_result(JSValue::from_bits(mapped.to_bits()), false) } @@ -285,7 +290,11 @@ unsafe fn helper_next(obj: *mut ObjectHeader) -> f64 { let keep = if cb.is_null() { true } else { - crate::value::js_is_truthy(js_closure_call1(cb, v)) != 0 + crate::value::js_is_truthy(js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + v, + )) != 0 }; if keep { return make_iter_result(JSValue::from_bits(v.to_bits()), false); @@ -340,7 +349,7 @@ unsafe fn helper_next(obj: *mut ObjectHeader) -> f64 { let produced = if cb.is_null() { v } else { - js_closure_call1(cb, v) + js_closure_call1(cb, crate::closure::plain_call_receiver(), v) }; // Per spec each produced value must itself be iterable; wrap it. let inner_iter = get_iterator(produced); @@ -528,7 +537,12 @@ pub unsafe fn dispatch_iterator_helper_method( break; } if !cb.is_null() { - js_closure_call2(cb, v, f64::from_bits(JSValue::number(i).bits())); + js_closure_call2( + cb, + crate::closure::plain_call_receiver(), + v, + f64::from_bits(JSValue::number(i).bits()), + ); } i += 1.0; } @@ -550,7 +564,7 @@ pub unsafe fn dispatch_iterator_helper_method( continue; } if !cb.is_null() { - acc = js_closure_call2(cb, acc, v); + acc = js_closure_call2(cb, crate::closure::plain_call_receiver(), acc, v); } } if !started { @@ -567,7 +581,13 @@ pub unsafe fn dispatch_iterator_helper_method( if done { return f64::from_bits(crate::value::TAG_FALSE); } - if !cb.is_null() && crate::value::js_is_truthy(js_closure_call1(cb, v)) != 0 { + if !cb.is_null() + && crate::value::js_is_truthy(js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + v, + )) != 0 + { return f64::from_bits(TAG_TRUE); } } @@ -579,7 +599,13 @@ pub unsafe fn dispatch_iterator_helper_method( if done { return f64::from_bits(TAG_TRUE); } - if !cb.is_null() && crate::value::js_is_truthy(js_closure_call1(cb, v)) == 0 { + if !cb.is_null() + && crate::value::js_is_truthy(js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + v, + )) == 0 + { return f64::from_bits(crate::value::TAG_FALSE); } } @@ -591,7 +617,13 @@ pub unsafe fn dispatch_iterator_helper_method( if done { return f64::from_bits(TAG_UNDEFINED); } - if !cb.is_null() && crate::value::js_is_truthy(js_closure_call1(cb, v)) != 0 { + if !cb.is_null() + && crate::value::js_is_truthy(js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + v, + )) != 0 + { return f64::from_bits(v.to_bits()); } } diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index e998c0277e..f23ac4ec76 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -319,7 +319,12 @@ fn helper_next_rejects_a_non_helper_iterator_receiver() { ); let rebound_h = scope.root_nanbox_f64(f64::from_bits(rebound)); let result = crate::exception::js_call_catching(|| { - crate::closure::js_native_call_value(rebound_h.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + rebound_h.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }); assert!( result.is_err(), diff --git a/crates/perry-runtime/src/json/replacer.rs b/crates/perry-runtime/src/json/replacer.rs index df6c11a894..6e9ee2549d 100644 --- a/crates/perry-runtime/src/json/replacer.rs +++ b/crates/perry-runtime/src/json/replacer.rs @@ -68,7 +68,12 @@ pub(crate) unsafe fn call_replacer( ) -> f64 { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(holder_f64)); - let result = crate::js_closure_call2(replacer, key_f64, value_f64); + let result = crate::js_closure_call2( + replacer, + crate::closure::plain_call_receiver(), + key_f64, + value_f64, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON` // (#6009 fast-probe cache). diff --git a/crates/perry-runtime/src/json/reviver.rs b/crates/perry-runtime/src/json/reviver.rs index 8280dfe10b..e68808afea 100644 --- a/crates/perry-runtime/src/json/reviver.rs +++ b/crates/perry-runtime/src/json/reviver.rs @@ -716,8 +716,13 @@ unsafe fn call_reviver( let key_arg = key_handle.get_nanbox_f64(); let value_arg = value_handle.get_nanbox_f64(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(holder_arg)); - let result = - crate::js_closure_call3(reviver, key_arg, value_arg, context_handle.get_nanbox_f64()); + let result = crate::js_closure_call3( + reviver, + crate::closure::plain_call_receiver(), + key_arg, + value_arg, + context_handle.get_nanbox_f64(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); let result_bits = result.to_bits(); let revived_bits = if result_bits == value_arg.to_bits() { diff --git a/crates/perry-runtime/src/json/stringify.rs b/crates/perry-runtime/src/json/stringify.rs index c200a700a2..84d6e14485 100644 --- a/crates/perry-runtime/src/json/stringify.rs +++ b/crates/perry-runtime/src/json/stringify.rs @@ -315,7 +315,12 @@ pub(crate) unsafe fn object_get_to_json(ptr: *const u8) -> Option { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = crate::closure::js_native_call_value(f64::from_bits(bound), &key_f64_arg, 1); + let result = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + &key_f64_arg, + 1, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); @@ -368,7 +373,12 @@ pub(crate) unsafe fn array_get_to_json(arr: *const crate::ArrayHeader) -> Option let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( recv_handle.get_nanbox_f64(), )); - let result = crate::closure::js_native_call_value(f64::from_bits(method_bits), &key_f64_arg, 1); + let result = crate::closure::js_native_call_value( + f64::from_bits(method_bits), + crate::closure::plain_call_receiver(), + &key_f64_arg, + 1, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); diff --git a/crates/perry-runtime/src/json/stringify_scalars.rs b/crates/perry-runtime/src/json/stringify_scalars.rs index 7e2cd2fbde..6b73315def 100644 --- a/crates/perry-runtime/src/json/stringify_scalars.rs +++ b/crates/perry-runtime/src/json/stringify_scalars.rs @@ -367,7 +367,12 @@ pub(crate) unsafe fn bigint_apply_to_json(value: f64) -> Option { let key_f64_arg = super::stringify_tojson_probe::current_to_json_key_arg(); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = crate::closure::js_native_call_value(f64::from_bits(method_bits), &key_f64_arg, 1); + let result = crate::closure::js_native_call_value( + f64::from_bits(method_bits), + crate::closure::plain_call_receiver(), + &key_f64_arg, + 1, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); diff --git a/crates/perry-runtime/src/jsx.rs b/crates/perry-runtime/src/jsx.rs index 2493bf6f94..9eb7cdb473 100644 --- a/crates/perry-runtime/src/jsx.rs +++ b/crates/perry-runtime/src/jsx.rs @@ -58,7 +58,14 @@ fn dispatch(type_arg: f64, props: f64) -> f64 { // the value-call path binds. if crate::proxy::js_proxy_is_proxy(type_arg) == 1 { if crate::proxy::proxy_wraps_callable(type_arg) { - return unsafe { crate::closure::js_native_call_value(type_arg, &props, 1) }; + return unsafe { + crate::closure::js_native_call_value( + type_arg, + crate::closure::plain_call_receiver(), + &props, + 1, + ) + }; } return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -68,7 +75,7 @@ fn dispatch(type_arg: f64, props: f64) -> f64 { if jsval.is_pointer() { let ptr = jsval.as_pointer::(); if !ptr.is_null() && is_valid_closure(ptr) { - return js_closure_call1(ptr, props); + return js_closure_call1(ptr, crate::closure::plain_call_receiver(), props); } } diff --git a/crates/perry-runtime/src/map.rs b/crates/perry-runtime/src/map.rs index b0ac88ef19..5b58020a1b 100644 --- a/crates/perry-runtime/src/map.rs +++ b/crates/perry-runtime/src/map.rs @@ -3081,7 +3081,12 @@ fn js_map_foreach_impl( // URLSearchParams.forEach pattern); `js_native_call_value` // dispatches the NaN-boxed callback with the full arg vector. crate::object::js_implicit_this_set(this_v); - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } diff --git a/crates/perry-runtime/src/media_playback.rs b/crates/perry-runtime/src/media_playback.rs index 61c7f83ae7..a75915a093 100644 --- a/crates/perry-runtime/src/media_playback.rs +++ b/crates/perry-runtime/src/media_playback.rs @@ -532,18 +532,27 @@ fn fire_state_callback(closure_d: f64, state: MediaState) { let s = state.as_str(); let header = crate::string::js_string_from_bytes(s.as_ptr(), s.len() as u32); if header.is_null() { - let _ = js_closure_call1(closure, f64::from_bits(TAG_UNDEFINED)); + let _ = js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + f64::from_bits(TAG_UNDEFINED), + ); return; } let str_d = crate::value::js_nanbox_string(header as i64); - let _ = js_closure_call1(closure, str_d); + let _ = js_closure_call1(closure, crate::closure::plain_call_receiver(), str_d); } fn fire_time_callback(closure_d: f64, current: f64, duration: f64) { let Some(closure) = unbox_closure(closure_d) else { return; }; - let _ = js_closure_call2(closure, current, duration); + let _ = js_closure_call2( + closure, + crate::closure::plain_call_receiver(), + current, + duration, + ); } // --------------------------------------------------------------------------- diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index e907af5b23..595f301820 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -326,6 +326,7 @@ fn invoke_message_handler(handler: f64, event: f64, port_box: f64) { unsafe { let _ = crate::closure::js_native_call_value( handler_h.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); diff --git a/crates/perry-runtime/src/module_require.rs b/crates/perry-runtime/src/module_require.rs index d38bce4f54..ee138338e9 100644 --- a/crates/perry-runtime/src/module_require.rs +++ b/crates/perry-runtime/src/module_require.rs @@ -765,6 +765,7 @@ fn require_path(cache: f64, path: &std::path::Path, parent_filename: &str) -> f6 let exports = crate::closure::js_closure_call0( crate::value::js_nanbox_get_pointer(factory_handle.get_nanbox_f64()) as *const ClosureHeader, + crate::closure::plain_call_receiver(), ); if let Some((record, cached)) = cached_record(cache_handle.get_nanbox_f64(), &filename) @@ -877,6 +878,7 @@ fn run_custom_extension(path: &std::path::Path, record: f64, filename: &str) { let filename_value = string_value(&filename); crate::closure::js_closure_call2( handler_ptr as *mut ClosureHeader, + crate::closure::plain_call_receiver(), record_handle.get_nanbox_f64(), filename_value, ); @@ -1525,7 +1527,11 @@ static KEEP_JS_MODULE_AMBIENT_REQUIRE: extern "C" fn() -> f64 = js_module_ambien /// Returns the required value directly (no Promise). #[no_mangle] pub extern "C" fn js_module_ambient_require_apply(spec: f64) -> f64 { - require_thunk(std::ptr::null(), crate::closure::JsThis::current(), spec) + require_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + spec, + ) } /// Keepalive anchor for the auto-optimize whole-program build (generated-code-only diff --git a/crates/perry-runtime/src/node_api_host/functions.rs b/crates/perry-runtime/src/node_api_host/functions.rs index 5f5805b5a1..082aabf803 100644 --- a/crates/perry-runtime/src/node_api_host/functions.rs +++ b/crates/perry-runtime/src/node_api_host/functions.rs @@ -299,6 +299,7 @@ pub unsafe extern "C" fn napi_call_function( let call_result = catch_value_call(env, || { crate::closure::js_native_call_value( f64::from_bits(function_bits), + crate::closure::plain_call_receiver(), arguments.as_ptr(), arguments.len(), ) diff --git a/crates/perry-runtime/src/node_inspector.rs b/crates/perry-runtime/src/node_inspector.rs index a8836a38cf..84538cf82c 100644 --- a/crates/perry-runtime/src/node_inspector.rs +++ b/crates/perry-runtime/src/node_inspector.rs @@ -257,8 +257,14 @@ fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = - unsafe { crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/node_repl.rs b/crates/perry-runtime/src/node_repl.rs index d28e9f9785..1c084c5059 100644 --- a/crates/perry-runtime/src/node_repl.rs +++ b/crates/perry-runtime/src/node_repl.rs @@ -170,8 +170,14 @@ fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { )); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = - unsafe { crate::closure::js_native_call_value(rebound, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + rebound, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/node_stream.rs b/crates/perry-runtime/src/node_stream.rs index 8060d16ef3..8e110cd1db 100644 --- a/crates/perry-runtime/src/node_stream.rs +++ b/crates/perry-runtime/src/node_stream.rs @@ -1198,7 +1198,12 @@ fn invoke_writable_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(write, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + write, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } else { @@ -1214,7 +1219,12 @@ fn invoke_writable_writev(stream: f64, chunks: f64) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(writev, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + writev, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1259,7 +1269,12 @@ fn invoke_transform_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(transform, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + transform, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; @@ -1390,7 +1405,12 @@ fn complete_writable_write(stream: f64, len: f64, callback: f64, err: f64) { }; let args = [arg]; unsafe { - let _ = crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } if has_error { diff --git a/crates/perry-runtime/src/node_stream/async_iterator.rs b/crates/perry-runtime/src/node_stream/async_iterator.rs index 6cfdc990df..f49277c2b3 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator.rs @@ -969,9 +969,9 @@ mod fifo_pending_tests { js_closure_set_capture_f64(data_cb, 0, iterator); set_readable_flowing(stream, f64::from_bits(TAG_TRUE)); - ns_readable_iter_on_data(data_cb, crate::closure::JsThis::current(), 1.0); + ns_readable_iter_on_data(data_cb, crate::closure::body_call::current_this(), 1.0); assert!(readable_is_flowing(stream)); - ns_readable_iter_on_data(data_cb, crate::closure::JsThis::current(), 2.0); + ns_readable_iter_on_data(data_cb, crate::closure::body_call::current_this(), 2.0); assert!(readable_is_paused(stream)); assert_eq!( @@ -1043,8 +1043,11 @@ mod fifo_pending_tests { let rejected = js_closure_alloc(ns_readable_source_iterator_rejected as *const u8, 1); js_closure_set_capture_f64(rejected, 0, iterator); - let result = - ns_readable_source_iterator_rejected(rejected, crate::closure::JsThis::current(), 7.0); + let result = ns_readable_source_iterator_rejected( + rejected, + crate::closure::body_call::current_this(), + 7.0, + ); let promise = crate::value::js_nanbox_get_pointer(result) as *mut crate::promise::Promise; assert!(iterator_is_done(iterator)); assert!(stream_destroyed(stream)); 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 04b09e56c9..95011b1540 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 @@ -9,7 +9,10 @@ fn readable() -> f64 { fn pull(iterator: f64) -> f64 { let next = get_hidden_value(iterator, hidden_key(b"next")).expect("iterator.next"); - crate::closure::js_closure_call0(raw_ptr_from_value(next) as *const ClosureHeader) + crate::closure::js_closure_call0( + raw_ptr_from_value(next) as *const ClosureHeader, + crate::closure::plain_call_receiver(), + ) } fn result_field(promise: f64, name: &[u8]) -> f64 { 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 dd04525405..1a8ef17abc 100644 --- a/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs +++ b/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs @@ -210,7 +210,12 @@ fn call_stream_callback(callback: f64, err: f64) { err }; unsafe { - let _ = crate::closure::js_native_call_value(callback, [arg].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + [arg].as_ptr(), + 1, + ); } } @@ -501,7 +506,7 @@ extern "C" fn fallback_foreign_reader_cancel( _this: crate::closure::JsThis, reason: f64, ) -> f64 { - foreign_readable_to_web_cancel(closure, crate::closure::JsThis::current(), reason) + foreign_readable_to_web_cancel(closure, crate::closure::body_call::current_this(), reason) } extern "C" fn fallback_foreign_get_reader( diff --git a/crates/perry-runtime/src/node_stream_destroy_state.rs b/crates/perry-runtime/src/node_stream_destroy_state.rs index 69a862522d..67e34b9f06 100644 --- a/crates/perry-runtime/src/node_stream_destroy_state.rs +++ b/crates/perry-runtime/src/node_stream_destroy_state.rs @@ -76,7 +76,12 @@ pub(super) fn destroy_stream(stream: f64, err: f64) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(destroy, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + destroy, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index fabc8c94f7..ec56d4eb0d 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -334,7 +334,12 @@ extern "C" fn ns_ee_async_resource_emit_rest( if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_enter(runtime_async_id); } - let result = ns_emit_rest(closure, crate::closure::JsThis::current(), event, rest); + let result = ns_emit_rest( + closure, + crate::closure::body_call::current_this(), + event, + rest, + ); if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_leave(runtime_async_id); } diff --git a/crates/perry-runtime/src/node_stream_event_emitter.rs b/crates/perry-runtime/src/node_stream_event_emitter.rs index 231b8ee699..00b76fc1c7 100644 --- a/crates/perry-runtime/src/node_stream_event_emitter.rs +++ b/crates/perry-runtime/src/node_stream_event_emitter.rs @@ -154,7 +154,12 @@ pub(super) extern "C" fn ns_off2( event: f64, cb: f64, ) -> f64 { - ns_remove_listener2(closure, crate::closure::JsThis::current(), event, cb) + ns_remove_listener2( + closure, + crate::closure::body_call::current_this(), + event, + cb, + ) } pub(super) extern "C" fn ns_remove_all_listeners1( @@ -713,8 +718,14 @@ pub(super) fn call_listener_args(stream: f64, listener: f64, args: &[f64]) -> f6 } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); - let result = - unsafe { crate::closure::js_native_call_value(listener, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + listener, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/node_stream_iter_helpers.rs b/crates/perry-runtime/src/node_stream_iter_helpers.rs index 2bded8877d..051ec5ee35 100644 --- a/crates/perry-runtime/src/node_stream_iter_helpers.rs +++ b/crates/perry-runtime/src/node_stream_iter_helpers.rs @@ -245,7 +245,11 @@ pub(super) fn settle_result(value: f64) -> Result { /// Invoke a single-argument stream callback and settle an async result. #[inline] pub(super) fn call_settled_result(cb: *const ClosureHeader, arg: f64) -> Result { - settle_result(crate::closure::js_closure_call1(cb, arg)) + settle_result(crate::closure::js_closure_call1( + cb, + crate::closure::plain_call_receiver(), + arg, + )) } /// Coerce a `take(n)` / `drop(n)` count argument to a clamped element @@ -584,9 +588,13 @@ extern "C" fn consume_on_next( return f64::from_bits(TAG_UNDEFINED); } let acc = js_closure_get_capture_f64(state, SC_ACC); - catch_pipeline_throw(|| crate::closure::js_closure_call2(cb, acc, value)) + catch_pipeline_throw(|| { + crate::closure::js_closure_call2(cb, crate::closure::plain_call_receiver(), acc, value) + }) } else { - catch_pipeline_throw(|| crate::closure::js_closure_call1(cb, value)) + catch_pipeline_throw(|| { + crate::closure::js_closure_call1(cb, crate::closure::plain_call_receiver(), value) + }) }; match cb_result { @@ -1323,7 +1331,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); + let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!has_truthy_hidden(source, hidden_key(b"returned"))); @@ -1351,7 +1359,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); + let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!stream_destroyed(stream)); @@ -1379,7 +1387,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::JsThis::current(), 2.0); + let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); assert_eq!(readable_hidden_error(result), Some(7.0)); assert_eq!(get_hidden_value(result, hidden_signal_key()), Some(8.0)); diff --git a/crates/perry-runtime/src/node_stream_pipeline.rs b/crates/perry-runtime/src/node_stream_pipeline.rs index c214b00762..2c1ae29b5a 100644 --- a/crates/perry-runtime/src/node_stream_pipeline.rs +++ b/crates/perry-runtime/src/node_stream_pipeline.rs @@ -490,7 +490,12 @@ pub(super) fn call_pipeline_function_stage( } let args = [source.get_nanbox_f64()]; let result = catch_pipeline_throw(|| unsafe { - crate::closure::js_native_call_value(stage.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + stage.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) })?; settle_pipeline_value_with_origin(result) } diff --git a/crates/perry-runtime/src/node_stream_readwrite.rs b/crates/perry-runtime/src/node_stream_readwrite.rs index c4bfa7ff0e..8a07c6d1ca 100644 --- a/crates/perry-runtime/src/node_stream_readwrite.rs +++ b/crates/perry-runtime/src/node_stream_readwrite.rs @@ -710,8 +710,12 @@ pub(super) fn schedule_writable_finish(stream: f64, callback: Option) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = - crate::closure::js_native_call_value(final_callback, [cb_value].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + final_callback, + crate::closure::plain_call_receiver(), + [cb_value].as_ptr(), + 1, + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; @@ -1037,7 +1041,12 @@ pub(super) fn finish_transform_stream(stream: f64, callback: Option) -> boo let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(flush, [cb_value].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + flush, + crate::closure::plain_call_receiver(), + [cb_value].as_ptr(), + 1, + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); true @@ -1314,7 +1323,12 @@ pub(super) fn invoke_construct_callback(stream: f64, opts: f64) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(construct, [cb_value].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + construct, + crate::closure::plain_call_receiver(), + [cb_value].as_ptr(), + 1, + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1349,7 +1363,12 @@ fn invoke_read_once_inner(stream: f64, emit_default_error: bool) { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value(read, [size].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + read, + crate::closure::plain_call_receiver(), + [size].as_ptr(), + 1, + ); } crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } diff --git a/crates/perry-runtime/src/node_stream_tests.rs b/crates/perry-runtime/src/node_stream_tests.rs index b30606d49a..4ef6f15c6c 100644 --- a/crates/perry-runtime/src/node_stream_tests.rs +++ b/crates/perry-runtime/src/node_stream_tests.rs @@ -232,7 +232,12 @@ pub(super) extern "C" fn write_capture( let bytes = js_node_stream_collect_bytes(readable); WRITE_CAPTURED.with(|captured| captured.borrow_mut().push(bytes)); unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -268,7 +273,12 @@ extern "C" fn write_capture_encoding( .push(JSValue::from_bits(chunk.to_bits()).is_any_string()) }); unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -282,7 +292,12 @@ extern "C" fn write_callback_error( ) -> f64 { let err = crate::closure::js_closure_get_capture_f64(closure, 0); unsafe { - let _ = crate::closure::js_native_call_value(cb, [err].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + [err].as_ptr(), + 1, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -314,7 +329,12 @@ extern "C" fn writev_capture( }); } unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -563,7 +583,12 @@ extern "C" fn transform_upper_callback( let upper = String::from_utf8(bytes).unwrap().to_uppercase(); let args = [f64::from_bits(TAG_UNDEFINED), string_value(&upper)]; unsafe { - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } f64::from_bits(TAG_UNDEFINED) } @@ -577,7 +602,12 @@ extern "C" fn transform_identity_callback( ) -> f64 { let args = [f64::from_bits(TAG_UNDEFINED), chunk]; unsafe { - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } f64::from_bits(TAG_UNDEFINED) } @@ -591,7 +621,12 @@ extern "C" fn transform_error_callback( ) -> f64 { let err = crate::closure::js_closure_get_capture_f64(closure, 0); unsafe { - let _ = crate::closure::js_native_call_value(cb, [err].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + [err].as_ptr(), + 1, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -609,10 +644,24 @@ extern "C" fn transform_push_pair_callback( hidden_key(b"push"), ); unsafe { - let _ = crate::closure::js_native_call_value(push, [string_value("a")].as_ptr(), 1); - let _ = crate::closure::js_native_call_value(push, [string_value("b")].as_ptr(), 1); - let _ = - crate::closure::js_native_call_value(cb, [f64::from_bits(TAG_UNDEFINED)].as_ptr(), 1); + let _ = crate::closure::js_native_call_value( + push, + crate::closure::plain_call_receiver(), + [string_value("a")].as_ptr(), + 1, + ); + let _ = crate::closure::js_native_call_value( + push, + crate::closure::plain_call_receiver(), + [string_value("b")].as_ptr(), + 1, + ); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + [f64::from_bits(TAG_UNDEFINED)].as_ptr(), + 1, + ); } f64::from_bits(TAG_UNDEFINED) } @@ -632,7 +681,12 @@ extern "C" fn transform_flush_tail_callback( TRANSFORM_FLUSH_COUNT.with(|count| *count.borrow_mut() += 1); let args = [f64::from_bits(TAG_UNDEFINED), string_value("!")]; unsafe { - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } f64::from_bits(TAG_UNDEFINED) } @@ -766,7 +820,12 @@ fn writable_options_write_callback_is_invoked_by_stub_write() { ); let args = [string_value("chunk"), f64::from_bits(TAG_UNDEFINED)]; unsafe { - let _ = crate::closure::js_native_call_value(write, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + write, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } WRITE_CAPTURED.with(|captured| { @@ -853,8 +912,22 @@ fn transform_pipe_chain_applies_callback_output() { raw_ptr_from_value(upper) as *const ObjectHeader, hidden_key(b"pipe"), ); - let _ = unsafe { crate::closure::js_native_call_value(src_pipe, [upper].as_ptr(), 1) }; - let _ = unsafe { crate::closure::js_native_call_value(upper_pipe, [sink].as_ptr(), 1) }; + let _ = unsafe { + crate::closure::js_native_call_value( + src_pipe, + crate::closure::plain_call_receiver(), + [upper].as_ptr(), + 1, + ) + }; + let _ = unsafe { + crate::closure::js_native_call_value( + upper_pipe, + crate::closure::plain_call_receiver(), + [sink].as_ptr(), + 1, + ) + }; let _ = crate::promise::js_promise_run_microtasks(); READABLE_DATA_CAPTURED.with(|captured| { @@ -1253,7 +1326,7 @@ fn stream_methods_use_implicit_this_without_closure_capture() { let prev_this = crate::object::js_implicit_this_set(stream); let _ = ns_end3( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), @@ -1274,7 +1347,12 @@ fn stream_method_closure_capture_wins_over_stale_implicit_this() { let prev_this = crate::object::js_implicit_this_set(other); unsafe { - let _ = crate::closure::js_native_call_value(end, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + end, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } crate::object::js_implicit_this_set(prev_this); @@ -1301,7 +1379,14 @@ fn stream_methods_dispatch_through_dynamic_method_call() { #[test] fn callable_stream_constructor_autoinstantiates_passthrough() { let ctor = crate::object::bound_native_callable_export_value("stream", "PassThrough"); - let stream = unsafe { crate::closure::js_native_call_value(ctor, std::ptr::null(), 0) }; + let stream = unsafe { + crate::closure::js_native_call_value( + ctor, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert!(raw_ptr_from_value(stream) >= 0x10000); assert!(get_hidden_value(stream, hidden_readable_flag_key()).is_some()); @@ -1324,7 +1409,7 @@ fn readable_pipe_stub_returns_destination_and_rejects_missing_destination() { assert_eq!( ns_pipe2( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), dest, f64::from_bits(TAG_UNDEFINED) ) @@ -1345,7 +1430,14 @@ fn readable_wrap_method_is_present_and_chainable() { let wrapped = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); let args = [wrapped]; - let result = unsafe { crate::closure::js_native_call_value(wrap, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + wrap, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; assert_eq!(result.to_bits(), stream.to_bits()); } @@ -1359,7 +1451,14 @@ fn writable_cork_and_uncork_update_counter_and_return_undefined() { assert_eq!(writable_corked_count(stream), 0.0); - let ret = unsafe { crate::closure::js_native_call_value(cork, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + cork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(writable_corked_count(stream), 1.0); assert_eq!( @@ -1367,15 +1466,36 @@ fn writable_cork_and_uncork_update_counter_and_return_undefined() { 1.0 ); - let ret = unsafe { crate::closure::js_native_call_value(cork, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + cork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(writable_corked_count(stream), 2.0); - let ret = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(writable_corked_count(stream), 1.0); - let ret = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(writable_corked_count(stream), 0.0); @@ -1386,7 +1506,14 @@ fn writable_cork_and_uncork_update_counter_and_return_undefined() { assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(js_node_stream_method_writable_corked(handle), 0.0); - let ret = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), TAG_UNDEFINED); assert_eq!(writable_corked_count(stream), 0.0); } diff --git a/crates/perry-runtime/src/node_stream_tests_extra.rs b/crates/perry-runtime/src/node_stream_tests_extra.rs index 88c53fa42e..ca63e98b03 100644 --- a/crates/perry-runtime/src/node_stream_tests_extra.rs +++ b/crates/perry-runtime/src/node_stream_tests_extra.rs @@ -22,13 +22,34 @@ fn stream_max_listeners_default_and_override_round_trip() { let get_max = js_object_get_field_by_name_f64(obj, hidden_key(b"getMaxListeners")); let set_max = js_object_get_field_by_name_f64(obj, hidden_key(b"setMaxListeners")); - let initial = unsafe { crate::closure::js_native_call_value(get_max, std::ptr::null(), 0) }; + let initial = unsafe { + crate::closure::js_native_call_value( + get_max, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(initial, 10.0); - let returned = unsafe { crate::closure::js_native_call_value(set_max, [25.0].as_ptr(), 1) }; + let returned = unsafe { + crate::closure::js_native_call_value( + set_max, + crate::closure::plain_call_receiver(), + [25.0].as_ptr(), + 1, + ) + }; assert_eq!(returned.to_bits(), stream.to_bits()); - let updated = unsafe { crate::closure::js_native_call_value(get_max, std::ptr::null(), 0) }; + let updated = unsafe { + crate::closure::js_native_call_value( + get_max, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(updated, 25.0); let other = js_node_stream_readable_new(f64::from_bits(TAG_UNDEFINED)); @@ -36,8 +57,14 @@ fn stream_max_listeners_default_and_override_round_trip() { raw_ptr_from_value(other) as *const ObjectHeader, hidden_key(b"getMaxListeners"), ); - let other_initial = - unsafe { crate::closure::js_native_call_value(other_get, std::ptr::null(), 0) }; + let other_initial = unsafe { + crate::closure::js_native_call_value( + other_get, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(other_initial, 10.0); let native_handle = raw_ptr_from_value(other) as i64; @@ -58,14 +85,26 @@ fn stream_event_names_raw_listeners_and_prepend_chainability() { let prepend = js_object_get_field_by_name_f64(obj, hidden_key(b"prependListener")); let raw = unsafe { - crate::closure::js_native_call_value(raw_listeners, [string_value("never")].as_ptr(), 1) + crate::closure::js_native_call_value( + raw_listeners, + crate::closure::plain_call_receiver(), + [string_value("never")].as_ptr(), + 1, + ) }; assert_eq!( crate::array::js_array_length(raw_ptr_from_value(raw) as *const crate::array::ArrayHeader), 0 ); - let names = unsafe { crate::closure::js_native_call_value(event_names, std::ptr::null(), 0) }; + let names = unsafe { + crate::closure::js_native_call_value( + event_names, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!( crate::array::js_array_length(raw_ptr_from_value(names) as *const crate::array::ArrayHeader), 0 @@ -73,12 +112,22 @@ fn stream_event_names_raw_listeners_and_prepend_chainability() { let cb = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); let returned = unsafe { - crate::closure::js_native_call_value(prepend, [string_value("data"), cb].as_ptr(), 2) + crate::closure::js_native_call_value( + prepend, + crate::closure::plain_call_receiver(), + [string_value("data"), cb].as_ptr(), + 2, + ) }; assert_eq!(returned.to_bits(), stream.to_bits()); let raw = unsafe { - crate::closure::js_native_call_value(raw_listeners, [string_value("data")].as_ptr(), 1) + crate::closure::js_native_call_value( + raw_listeners, + crate::closure::plain_call_receiver(), + [string_value("data")].as_ptr(), + 1, + ) }; let raw_arr = raw_ptr_from_value(raw) as *const crate::array::ArrayHeader; assert_eq!(crate::array::js_array_length(raw_arr), 1); @@ -87,7 +136,14 @@ fn stream_event_names_raw_listeners_and_prepend_chainability() { cb.to_bits() ); - let names = unsafe { crate::closure::js_native_call_value(event_names, std::ptr::null(), 0) }; + let names = unsafe { + crate::closure::js_native_call_value( + event_names, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; let names_arr = raw_ptr_from_value(names) as *const crate::array::ArrayHeader; assert_eq!(crate::array::js_array_length(names_arr), 1); assert!(string_value_eq( @@ -128,20 +184,45 @@ fn stream_listener_count_and_listeners_reflect_data_end_storage() { let cb3 = box_pointer(js_closure_alloc(noop_listener as *const u8, 0) as *const u8); unsafe { - let _ = crate::closure::js_native_call_value(on, [string_value("data"), cb1].as_ptr(), 2); - let _ = crate::closure::js_native_call_value(on, [string_value("data"), cb2].as_ptr(), 2); - let _ = crate::closure::js_native_call_value(on, [string_value("end"), cb3].as_ptr(), 2); + let _ = crate::closure::js_native_call_value( + on, + crate::closure::plain_call_receiver(), + [string_value("data"), cb1].as_ptr(), + 2, + ); + let _ = crate::closure::js_native_call_value( + on, + crate::closure::plain_call_receiver(), + [string_value("data"), cb2].as_ptr(), + 2, + ); + let _ = crate::closure::js_native_call_value( + on, + crate::closure::plain_call_receiver(), + [string_value("end"), cb3].as_ptr(), + 2, + ); } let count_for = |event: &str| unsafe { - crate::closure::js_native_call_value(listener_count, [string_value(event)].as_ptr(), 1) + crate::closure::js_native_call_value( + listener_count, + crate::closure::plain_call_receiver(), + [string_value(event)].as_ptr(), + 1, + ) }; assert_eq!(count_for("data"), 2.0); assert_eq!(count_for("end"), 1.0); assert_eq!(count_for("error"), 0.0); let data_listeners = unsafe { - crate::closure::js_native_call_value(listeners, [string_value("data")].as_ptr(), 1) + crate::closure::js_native_call_value( + listeners, + crate::closure::plain_call_receiver(), + [string_value("data")].as_ptr(), + 1, + ) }; let data_arr = raw_ptr_from_value(data_listeners) as *mut crate::array::ArrayHeader; assert_eq!(crate::array::js_array_length(data_arr), 2); @@ -158,7 +239,12 @@ fn stream_listener_count_and_listeners_reflect_data_end_storage() { assert_eq!(count_for("data"), 2.0); let missing_listeners = unsafe { - crate::closure::js_native_call_value(listeners, [string_value("error")].as_ptr(), 1) + crate::closure::js_native_call_value( + listeners, + crate::closure::plain_call_receiver(), + [string_value("error")].as_ptr(), + 1, + ) }; let missing_arr = raw_ptr_from_value(missing_listeners) as *const crate::array::ArrayHeader; assert_eq!(crate::array::js_array_length(missing_arr), 0); @@ -202,7 +288,14 @@ fn pipe_and_unpipe_emit_destination_events_with_source() { let _ = js_node_stream_method_on(dest_handle, string_value("pipe"), listener); let _ = js_node_stream_method_on(dest_handle, string_value("unpipe"), listener); - let ret = unsafe { crate::closure::js_native_call_value(pipe, [dest].as_ptr(), 1) }; + let ret = unsafe { + crate::closure::js_native_call_value( + pipe, + crate::closure::plain_call_receiver(), + [dest].as_ptr(), + 1, + ) + }; assert_eq!(ret.to_bits(), dest.to_bits()); assert_eq!( js_object_get_field_by_name_f64(dest_obj, readable_flowing_key()).to_bits(), @@ -217,7 +310,14 @@ fn pipe_and_unpipe_emit_destination_events_with_source() { TAG_TRUE ); - let ret = unsafe { crate::closure::js_native_call_value(unpipe, [dest].as_ptr(), 1) }; + let ret = unsafe { + crate::closure::js_native_call_value( + unpipe, + crate::closure::plain_call_receiver(), + [dest].as_ptr(), + 1, + ) + }; assert_eq!(ret.to_bits(), src.to_bits()); STREAM_EVENT_ARG_MATCHES.with(|matches| { assert_eq!(matches.borrow().as_slice(), &[true, true]); @@ -248,7 +348,14 @@ fn readable_from_pipe_ends_destination() { let _ = js_node_stream_method_on(dest_handle, string_value("data"), data); let _ = js_node_stream_method_on(dest_handle, string_value("end"), end); - let ret = unsafe { crate::closure::js_native_call_value(pipe, [dest].as_ptr(), 1) }; + let ret = unsafe { + crate::closure::js_native_call_value( + pipe, + crate::closure::plain_call_receiver(), + [dest].as_ptr(), + 1, + ) + }; assert_eq!(ret.to_bits(), dest.to_bits()); let _ = crate::promise::js_promise_run_microtasks(); @@ -287,11 +394,27 @@ fn readable_from_pipe_chain_ends_tail_destination() { let _ = js_node_stream_method_on(sink_handle, string_value("end"), sink_end); assert_eq!( - unsafe { crate::closure::js_native_call_value(src_pipe, [middle].as_ptr(), 1) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + src_pipe, + crate::closure::plain_call_receiver(), + [middle].as_ptr(), + 1, + ) + } + .to_bits(), middle.to_bits() ); assert_eq!( - unsafe { crate::closure::js_native_call_value(middle_pipe, [sink].as_ptr(), 1) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + middle_pipe, + crate::closure::plain_call_receiver(), + [sink].as_ptr(), + 1, + ) + } + .to_bits(), sink.to_bits() ); assert_eq!( @@ -327,7 +450,15 @@ fn pause_resume_track_readable_flowing_and_events() { TAG_NULL ); assert_eq!( - unsafe { crate::closure::js_native_call_value(is_paused, std::ptr::null(), 0) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + is_paused, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } + .to_bits(), TAG_FALSE ); @@ -344,26 +475,56 @@ fn pause_resume_track_readable_flowing_and_events() { js_object_get_field_by_name_f64(obj, readable_flowing_key()).to_bits(), TAG_TRUE ); - let ret = unsafe { crate::closure::js_native_call_value(pause, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + pause, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), stream.to_bits()); assert_eq!( js_object_get_field_by_name_f64(obj, readable_flowing_key()).to_bits(), TAG_FALSE ); assert_eq!( - unsafe { crate::closure::js_native_call_value(is_paused, std::ptr::null(), 0) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + is_paused, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } + .to_bits(), TAG_TRUE ); STREAM_EVENT_ORDER.with(|events| assert_eq!(events.borrow().as_slice(), b"P")); - let ret = unsafe { crate::closure::js_native_call_value(resume, std::ptr::null(), 0) }; + let ret = unsafe { + crate::closure::js_native_call_value( + resume, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(ret.to_bits(), stream.to_bits()); assert_eq!( js_object_get_field_by_name_f64(obj, readable_flowing_key()).to_bits(), TAG_TRUE ); assert_eq!( - unsafe { crate::closure::js_native_call_value(is_paused, std::ptr::null(), 0) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + is_paused, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } + .to_bits(), TAG_FALSE ); STREAM_EVENT_ORDER.with(|events| assert_eq!(events.borrow().as_slice(), b"P")); @@ -727,19 +888,55 @@ fn writable_corked_counter_tracks_cork_balance() { ); assert_eq!( - unsafe { crate::closure::js_native_call_value(cork, std::ptr::null(), 0) }.to_bits(), + unsafe { + crate::closure::js_native_call_value( + cork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + } + .to_bits(), TAG_UNDEFINED ); assert_eq!(js_node_stream_method_writable_corked(handle), 1.0); - let _ = unsafe { crate::closure::js_native_call_value(cork, std::ptr::null(), 0) }; + let _ = unsafe { + crate::closure::js_native_call_value( + cork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(js_node_stream_method_writable_corked(handle), 2.0); - let _ = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; + let _ = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(js_node_stream_method_writable_corked(handle), 1.0); - let _ = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; - let _ = unsafe { crate::closure::js_native_call_value(uncork, std::ptr::null(), 0) }; + let _ = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; + let _ = unsafe { + crate::closure::js_native_call_value( + uncork, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!(js_node_stream_method_writable_corked(handle), 0.0); assert_eq!( js_object_get_field_by_name_f64(obj, hidden_key(b"writableCorked")), @@ -789,7 +986,12 @@ fn writable_backpressure_tracks_length_need_drain_and_drain_event() { let cb = PENDING_WRITE_CALLBACK.with(|pending| pending.borrow_mut().take().unwrap()); unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } assert_eq!(js_node_stream_method_writable_length(handle), 0.0); @@ -862,7 +1064,12 @@ fn writable_end_waits_for_pending_write_before_finish() { let cb = PENDING_WRITE_CALLBACK.with(|pending| pending.borrow_mut().take().unwrap()); unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } let _ = crate::promise::js_promise_run_microtasks(); WRITABLE_FINISH_COUNT.with(|count| assert_eq!(*count.borrow(), 1)); @@ -904,7 +1111,12 @@ fn writable_end_callback_runs_before_finish_and_close_events() { let cb = PENDING_WRITE_CALLBACK.with(|pending| pending.borrow_mut().take().unwrap()); unsafe { - let _ = crate::closure::js_native_call_value(cb, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } let _ = crate::promise::js_promise_run_microtasks(); @@ -1075,7 +1287,12 @@ fn stream_destroy_with_error_marks_errored_state() { assert_eq!(js_node_stream_method_errored(handle()).to_bits(), TAG_NULL); let args = [err.get_nanbox_f64()]; let ret = unsafe { - crate::closure::js_native_call_value(destroy.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + destroy.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }; assert_eq!(ret.to_bits(), stream.get_nanbox_f64().to_bits()); @@ -1118,7 +1335,14 @@ fn readable_exposes_async_dispose_symbol_method() { }; assert!(is_callable_value(method)); - let result = unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_ne!(result.to_bits(), TAG_UNDEFINED); assert_eq!(js_node_stream_method_destroyed(handle).to_bits(), TAG_TRUE); } diff --git a/crates/perry-runtime/src/node_submodules/consumers.rs b/crates/perry-runtime/src/node_submodules/consumers.rs index f00d46c5d0..790cacf8d9 100644 --- a/crates/perry-runtime/src/node_submodules/consumers.rs +++ b/crates/perry-runtime/src/node_submodules/consumers.rs @@ -478,7 +478,7 @@ fn call_collector_method( if let Some(promise) = promise_ptr_from_value(result) { crate::promise::js_promise_then(promise, step, reject); } else { - consumer_collect_step(step, crate::closure::JsThis::current(), result); + consumer_collect_step(step, crate::closure::body_call::current_this(), result); } } @@ -510,7 +510,14 @@ fn call_symbol_async_iterator(stream: f64) -> Option { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); - let iterator = unsafe { crate::closure::js_native_call_value(method, std::ptr::null(), 0) }; + let iterator = unsafe { + crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None diff --git a/crates/perry-runtime/src/node_submodules/diagnostics.rs b/crates/perry-runtime/src/node_submodules/diagnostics.rs index 8badd5e8f7..6966f6537a 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics.rs @@ -996,7 +996,9 @@ pub(crate) fn publish_channel_local(id: i64, data: f64) { // Match Node's safe subscriber behavior for the happy path; exceptions // propagate through Perry's exception mechanism and are catchable. let cb = closure_ptr(subscriber); - if let Err(err) = catch_js(|| js_closure_call2(cb, data, name)) { + if let Err(err) = + catch_js(|| js_closure_call2(cb, crate::closure::plain_call_receiver(), data, name)) + { schedule_uncaught(err); } } @@ -1118,7 +1120,12 @@ pub(crate) fn run_store_wrapped( let rebound = crate::closure::clone_closure_rebind_this(fn_value.to_bits(), this_arg); let cb = (rebound & crate::value::POINTER_MASK) as *const ClosureHeader; with_implicit_this(this_arg, || unsafe { - js_closure_call_array(cb as i64, args.as_ptr(), args.len() as i64) + js_closure_call_array( + cb as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len() as i64, + ) }) } @@ -1220,7 +1227,7 @@ pub(crate) extern "C" fn diag_channel_unbind_store( pub(crate) fn call_store_run(store: f64, context: f64, next: f64) -> f64 { fn run_als(handle: i64, context: f64, next: f64) -> f64 { crate::async_context::push_store(handle, context); - let result = js_closure_call0(closure_ptr(next)); + let result = js_closure_call0(closure_ptr(next), crate::closure::plain_call_receiver()); crate::async_context::pop_store(handle); result } @@ -1240,7 +1247,12 @@ pub(crate) fn call_store_run(store: f64, context: f64, next: f64) -> f64 { } let obj = crate::value::js_nanbox_get_pointer(store) as *mut ObjectHeader; let run = get_field_value(obj, "run"); - js_closure_call2(closure_ptr(run), context, next) + js_closure_call2( + closure_ptr(run), + crate::closure::plain_call_receiver(), + context, + next, + ) } pub(crate) extern "C" fn store_next_thunk( @@ -1369,7 +1381,9 @@ pub(crate) extern "C" fn diag_channel_run_stores( for (store, transform) in stores.into_iter().rev() { let context = match transform { StoreTransform::Callable(t) => { - match catch_js(|| js_closure_call1(closure_ptr(t), data)) { + match catch_js(|| { + js_closure_call1(closure_ptr(t), crate::closure::plain_call_receiver(), data) + }) { Ok(context) => context, Err(err) => { schedule_uncaught(err); @@ -1393,7 +1407,12 @@ pub(crate) extern "C" fn diag_channel_run_stores( js_closure_set_capture_ptr(chain, 2, next_value.to_bits() as i64); next_value = boxed_ptr(chain); } - suppress_uncaught_drain(|| js_closure_call0(closure_ptr(next_value))) + suppress_uncaught_drain(|| { + js_closure_call0( + closure_ptr(next_value), + crate::closure::plain_call_receiver(), + ) + }) } pub(crate) extern "C" fn thunk_diag_channel( @@ -1546,7 +1565,12 @@ pub(crate) fn call_fn_value(fn_value: f64, this_arg: f64, args: &[f64]) -> f64 { let rebound = crate::closure::clone_closure_rebind_this(fn_value.to_bits(), this_arg); let cb = (rebound & crate::value::POINTER_MASK) as *const ClosureHeader; with_implicit_this(this_arg, || unsafe { - js_closure_call_array(cb as i64, args.as_ptr(), args.len() as i64) + js_closure_call_array( + cb as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len() as i64, + ) }) } diff --git a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs index 9adb95c045..1fe9815e30 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics_tail.rs @@ -137,7 +137,9 @@ pub(crate) extern "C" fn diag_channel_with_store_scope( for (store, transform) in stores { let context = match transform { StoreTransform::Callable(t) => { - match catch_js(|| js_closure_call1(closure_ptr(t), data)) { + match catch_js(|| { + js_closure_call1(closure_ptr(t), crate::closure::plain_call_receiver(), data) + }) { Ok(context) => context, Err(err) => { schedule_uncaught(err); diff --git a/crates/perry-runtime/src/node_submodules/fs_promises.rs b/crates/perry-runtime/src/node_submodules/fs_promises.rs index 755931a895..f5b5ac801c 100644 --- a/crates/perry-runtime/src/node_submodules/fs_promises.rs +++ b/crates/perry-runtime/src/node_submodules/fs_promises.rs @@ -78,7 +78,7 @@ fn promise_from_result_value(call: impl FnOnce() -> Result) -> f64 { pub extern "C" fn js_fs_promises_read_file(path: f64, options: f64) -> f64 { thunk_fs_promises_readFile( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), path, options, ) @@ -88,7 +88,7 @@ pub extern "C" fn js_fs_promises_read_file(path: f64, options: f64) -> f64 { pub extern "C" fn js_fs_promises_write_file(path: f64, data: f64, options: f64) -> f64 { thunk_fs_promises_writeFile( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), path, data, options, @@ -99,7 +99,7 @@ pub extern "C" fn js_fs_promises_write_file(path: f64, data: f64, options: f64) pub extern "C" fn js_fs_promises_append_file(path: f64, data: f64, options: f64) -> f64 { thunk_fs_promises_appendFile( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), path, data, options, @@ -110,7 +110,7 @@ pub extern "C" fn js_fs_promises_append_file(path: f64, data: f64, options: f64) pub extern "C" fn js_fs_promises_mkdir(path: f64, options: f64) -> f64 { thunk_fs_promises_mkdir( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), path, options, ) @@ -120,7 +120,7 @@ pub extern "C" fn js_fs_promises_mkdir(path: f64, options: f64) -> f64 { pub extern "C" fn js_fs_promises_rmdir(path: f64, options: f64) -> f64 { thunk_fs_promises_rmdir( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), path, options, ) @@ -648,7 +648,12 @@ fn call_write_value(output: f64, text: &str) { if let Some(write) = object_field(output, b"write").filter(|v| is_callable(*v)) { let args = [chunk]; unsafe { - let _ = js_native_call_value(write, args.as_ptr(), args.len()); + let _ = js_native_call_value( + write, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } } diff --git a/crates/perry-runtime/src/node_submodules/stream_promises.rs b/crates/perry-runtime/src/node_submodules/stream_promises.rs index 9e287140f9..b2d51895a4 100644 --- a/crates/perry-runtime/src/node_submodules/stream_promises.rs +++ b/crates/perry-runtime/src/node_submodules/stream_promises.rs @@ -454,7 +454,14 @@ fn invoke_destination_method(destination: f64, method: &[u8], args: &[f64]) -> f }; let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(destination)); - let result = unsafe { crate::closure::js_native_call_value(func, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + func, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index bd54f0903e..d615d5a44f 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -844,6 +844,7 @@ extern "C" fn mock_function_invoke( let call_result = catch_js(|| unsafe { crate::closure::js_native_call_value( implementation_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), call_args.as_ptr(), call_args.len(), ) @@ -1599,7 +1600,7 @@ pub extern "C" fn js_node_test_mock_fn( ) -> f64 { mock_fn_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), original, implementation_or_options, options, @@ -1615,7 +1616,7 @@ pub extern "C" fn js_node_test_mock_method( ) -> f64 { mock_method_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), target, property, implementation, @@ -1632,7 +1633,7 @@ pub extern "C" fn js_node_test_mock_getter( ) -> f64 { mock_getter_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), target, property, implementation, @@ -1649,7 +1650,7 @@ pub extern "C" fn js_node_test_mock_setter( ) -> f64 { mock_setter_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), target, property, implementation, @@ -1674,19 +1675,19 @@ pub extern "C" fn js_node_test_mock_property_with_presence( #[no_mangle] pub extern "C" fn js_node_test_mock_reset() -> f64 { - mock_reset_thunk(std::ptr::null(), crate::closure::JsThis::current()) + mock_reset_thunk(std::ptr::null(), crate::closure::body_call::current_this()) } #[no_mangle] pub extern "C" fn js_node_test_mock_restore_all() -> f64 { - mock_restore_all_thunk(std::ptr::null(), crate::closure::JsThis::current()) + mock_restore_all_thunk(std::ptr::null(), crate::closure::body_call::current_this()) } #[no_mangle] pub extern "C" fn js_node_test_snapshot_set_default_serializers(serializers: f64) -> f64 { snapshot_set_default_serializers( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), serializers, ) } @@ -1695,34 +1696,46 @@ pub extern "C" fn js_node_test_snapshot_set_default_serializers(serializers: f64 pub extern "C" fn js_node_test_snapshot_set_resolve_snapshot_path(resolver: f64) -> f64 { snapshot_set_resolve_snapshot_path( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), resolver, ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_enable(options: f64) -> f64 { - mock_timers_enable(std::ptr::null(), crate::closure::JsThis::current(), options) + mock_timers_enable( + std::ptr::null(), + crate::closure::body_call::current_this(), + options, + ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_tick(ms: f64) -> f64 { - mock_timers_tick(std::ptr::null(), crate::closure::JsThis::current(), ms) + mock_timers_tick( + std::ptr::null(), + crate::closure::body_call::current_this(), + ms, + ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_run_all() -> f64 { - mock_timers_run_all(std::ptr::null(), crate::closure::JsThis::current()) + mock_timers_run_all(std::ptr::null(), crate::closure::body_call::current_this()) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_set_time(ms: f64) -> f64 { - mock_timers_set_time(std::ptr::null(), crate::closure::JsThis::current(), ms) + mock_timers_set_time( + std::ptr::null(), + crate::closure::body_call::current_this(), + ms, + ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_reset() -> f64 { - mock_timers_reset(std::ptr::null(), crate::closure::JsThis::current()) + mock_timers_reset(std::ptr::null(), crate::closure::body_call::current_this()) } pub(crate) fn decorate_test_export( 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 19b27d08e4..4852eb8bf3 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 @@ -12,7 +12,10 @@ extern "C" fn reentrant_implementation( let recurse = REENTRANT_ACTIVE.with(|active| !active.replace(true)); if recurse { let mock = REENTRANT_MOCK.with(Cell::get); - js_closure_call0(raw_ptr_from_value(mock) as *const ClosureHeader); + js_closure_call0( + raw_ptr_from_value(mock) as *const ClosureHeader, + crate::closure::plain_call_receiver(), + ); REENTRANT_ACTIVE.with(|active| active.set(false)); } 10.0 @@ -128,7 +131,10 @@ fn reentrant_dispatch_uses_completed_call_indices_like_node() { let state = states.iter_mut().find(|state| state.id == id).unwrap(); schedule_mock_implementation_once(state, 1, explicit_once); }); - assert_eq!(js_closure_call0(mock_ptr), 10.0); + assert_eq!( + js_closure_call0(mock_ptr, crate::closure::plain_call_receiver()), + 10.0 + ); MOCK_STATES.with(|states| { let mut states = states.borrow_mut(); @@ -141,7 +147,10 @@ fn reentrant_dispatch_uses_completed_call_indices_like_node() { let current = mock_state_call_count(state); schedule_mock_implementation_once(state, current, scheduled_inside); }); - assert_eq!(js_closure_call0(mock_ptr), 30.0); + assert_eq!( + js_closure_call0(mock_ptr, crate::closure::plain_call_receiver()), + 30.0 + ); MOCK_STATES.with(|states| { let states = states.borrow(); diff --git a/crates/perry-runtime/src/node_submodules/test_property.rs b/crates/perry-runtime/src/node_submodules/test_property.rs index f1784d8e0a..2bbde88869 100644 --- a/crates/perry-runtime/src/node_submodules/test_property.rs +++ b/crates/perry-runtime/src/node_submodules/test_property.rs @@ -387,7 +387,7 @@ extern "C" fn mock_property_implementation( _this: crate::closure::JsThis, value: f64, ) -> f64 { - mock_property_set(closure, crate::closure::JsThis::current(), value) + mock_property_set(closure, crate::closure::body_call::current_this(), value) } extern "C" fn mock_property_implementation_once( @@ -613,7 +613,10 @@ mod tests { let context = proxy_context(proxy); let count = object_property(context, b"accessCount").expect("accessCount"); assert_eq!( - js_closure_call0(raw_ptr_from_value(count) as *const ClosureHeader), + js_closure_call0( + raw_ptr_from_value(count) as *const ClosureHeader, + crate::closure::plain_call_receiver() + ), 1.0 ); restore_all(); @@ -635,12 +638,14 @@ mod tests { object_property(context, b"mockImplementation").expect("mockImplementation"); js_closure_call1( raw_ptr_from_value(implementation) as *const ClosureHeader, + crate::closure::plain_call_receiver(), 9.0, ); let once = object_property(context, b"mockImplementationOnce").expect("mockImplementationOnce"); crate::closure::js_closure_call2( raw_ptr_from_value(once) as *const ClosureHeader, + crate::closure::plain_call_receiver(), 4.0, 1.0, ); @@ -649,7 +654,10 @@ mod tests { assert_eq!(get_property_value(target, "value"), 9.0); let count = object_property(context, b"accessCount").expect("accessCount"); assert_eq!( - js_closure_call0(raw_ptr_from_value(count) as *const ClosureHeader), + js_closure_call0( + raw_ptr_from_value(count) as *const ClosureHeader, + crate::closure::plain_call_receiver() + ), 3.0 ); let accesses = object_property(context, b"accesses").expect("accesses"); diff --git a/crates/perry-runtime/src/node_submodules/test_reporters.rs b/crates/perry-runtime/src/node_submodules/test_reporters.rs index 0c44febc32..f89fd4f5e1 100644 --- a/crates/perry-runtime/src/node_submodules/test_reporters.rs +++ b/crates/perry-runtime/src/node_submodules/test_reporters.rs @@ -101,7 +101,10 @@ extern "C" fn reporter_transform_chunk( crate::node_stream::js_node_stream_method_push(handle, string_value(&output)); } if is_callable_value(callback) { - js_closure_call0(raw_ptr_from_value(callback) as *const ClosureHeader); + js_closure_call0( + raw_ptr_from_value(callback) as *const ClosureHeader, + crate::closure::plain_call_receiver(), + ); } 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 ffe84ce31d..f5778669d6 100644 --- a/crates/perry-runtime/src/node_submodules/test_runner.rs +++ b/crates/perry-runtime/src/node_submodules/test_runner.rs @@ -385,11 +385,16 @@ fn call_test_callback(callback: f64, name: &str) -> Result { let done = scope.root_nanbox_f64(closure_value(test_done as *const u8, 1)); crate::closure::js_closure_call2( callback_ptr, + crate::closure::plain_call_receiver(), context.get_nanbox_f64(), done.get_nanbox_f64(), ) } else { - js_closure_call1(callback_ptr, context.get_nanbox_f64()) + js_closure_call1( + callback_ptr, + crate::closure::plain_call_receiver(), + context.get_nanbox_f64(), + ) } }) } @@ -426,7 +431,13 @@ fn run_hook(callback: f64, context_name: &str) -> Result<(), f64> { let scope = crate::gc::RuntimeHandleScope::new(); let context = scope.root_nanbox_f64(test_context_value(context_name)); let callback_ptr = raw_ptr_from_value(callback) as *const ClosureHeader; - let result = catch_js(|| js_closure_call1(callback_ptr, context.get_nanbox_f64()))?; + let result = catch_js(|| { + js_closure_call1( + callback_ptr, + crate::closure::plain_call_receiver(), + context.get_nanbox_f64(), + ) + })?; await_callback_result(result) } @@ -899,7 +910,7 @@ pub(crate) extern "C" fn thunk_test_run( pub extern "C" fn js_node_test_register(name: f64, options: f64, callback: f64) -> f64 { thunk_test( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name, options, callback, @@ -910,7 +921,7 @@ pub extern "C" fn js_node_test_register(name: f64, options: f64, callback: f64) pub extern "C" fn js_node_test_skip(name: f64, options: f64, callback: f64) -> f64 { thunk_test_skip( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name, options, callback, @@ -921,7 +932,7 @@ pub extern "C" fn js_node_test_skip(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_todo(name: f64, options: f64, callback: f64) -> f64 { thunk_test_todo( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name, options, callback, @@ -932,7 +943,7 @@ pub extern "C" fn js_node_test_todo(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_only(name: f64, options: f64, callback: f64) -> f64 { thunk_test_only( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name, options, callback, @@ -943,7 +954,7 @@ pub extern "C" fn js_node_test_only(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_suite(name: f64, options: f64, callback: f64) -> f64 { thunk_test_suite( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name, options, callback, @@ -954,7 +965,7 @@ pub extern "C" fn js_node_test_suite(name: f64, options: f64, callback: f64) -> pub extern "C" fn js_node_test_before(callback: f64) -> f64 { thunk_test_before( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), callback, ) } @@ -963,7 +974,7 @@ pub extern "C" fn js_node_test_before(callback: f64) -> f64 { pub extern "C" fn js_node_test_after(callback: f64) -> f64 { thunk_test_after( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), callback, ) } @@ -972,7 +983,7 @@ pub extern "C" fn js_node_test_after(callback: f64) -> f64 { pub extern "C" fn js_node_test_before_each(callback: f64) -> f64 { thunk_test_before_each( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), callback, ) } @@ -981,14 +992,18 @@ pub extern "C" fn js_node_test_before_each(callback: f64) -> f64 { pub extern "C" fn js_node_test_after_each(callback: f64) -> f64 { thunk_test_after_each( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), callback, ) } #[no_mangle] pub extern "C" fn js_node_test_run(options: f64) -> f64 { - thunk_test_run(std::ptr::null(), crate::closure::JsThis::current(), options) + thunk_test_run( + std::ptr::null(), + crate::closure::body_call::current_this(), + options, + ) } pub(crate) fn scan_node_test_runner_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { diff --git a/crates/perry-runtime/src/node_submodules/test_snapshot.rs b/crates/perry-runtime/src/node_submodules/test_snapshot.rs index a55e796ad8..53102e35f1 100644 --- a/crates/perry-runtime/src/node_submodules/test_snapshot.rs +++ b/crates/perry-runtime/src/node_submodules/test_snapshot.rs @@ -77,7 +77,11 @@ pub(super) extern "C" fn assert_snapshot( ); } let resolver_ptr = raw_ptr_from_value(resolver) as *const ClosureHeader; - let path_value = js_closure_call1(resolver_ptr, string_value("")); + let path_value = js_closure_call1( + resolver_ptr, + crate::closure::plain_call_receiver(), + string_value(""), + ); let Some(path) = value_to_string(path_value) else { throw_invalid_arg_type("snapshot path", "string", path_value); }; diff --git a/crates/perry-runtime/src/node_submodules/tests.rs b/crates/perry-runtime/src/node_submodules/tests.rs index 5fd197bed5..0dcfc77326 100644 --- a/crates/perry-runtime/src/node_submodules/tests.rs +++ b/crates/perry-runtime/src/node_submodules/tests.rs @@ -602,7 +602,12 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { let end = get_object_property(stream, b"end").expect("stream.end should exist"); let prev_this = crate::object::js_implicit_this_set(stream); unsafe { - let _ = crate::closure::js_native_call_value(end, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + end, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } crate::object::js_implicit_this_set(prev_this); let _ = crate::promise::js_promise_run_microtasks(); @@ -616,7 +621,7 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, boxed_ptr(opts as *const u8), ); @@ -633,7 +638,7 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { fn stream_promises_finished_rejects_invalid_inputs() { let number_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 123.0, undefined_value(), ); @@ -645,7 +650,7 @@ fn stream_promises_finished_rejects_invalid_inputs() { let string_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), string_value("x"), undefined_value(), ); @@ -658,7 +663,7 @@ fn stream_promises_finished_rejects_invalid_inputs() { let object = js_object_alloc(0, 0); let object_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), boxed_ptr(object as *const u8), undefined_value(), ); @@ -677,7 +682,7 @@ fn stream_promises_finished_rejects_hidden_stream_error() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, undefined_value(), ); @@ -696,7 +701,7 @@ fn stream_promises_finished_rejects_later_destroy_error() { crate::node_stream::test_install_manual_read(stream); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, undefined_value(), ); @@ -770,7 +775,7 @@ fn stream_promises_finished_resolves_for_resumed_readable_from() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, undefined_value(), ); @@ -787,7 +792,7 @@ fn stream_promises_finished_duplex_default_waits_after_writable_finish_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, undefined_value(), ); @@ -811,7 +816,7 @@ fn stream_promises_finished_duplex_default_waits_after_readable_end_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, undefined_value(), ); @@ -836,7 +841,7 @@ fn stream_promises_finished_duplex_readable_false_resolves_after_writable_finish let options = finished_bool_option(b"readable", false); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, options, ); @@ -856,7 +861,7 @@ fn stream_promises_finished_duplex_writable_false_resolves_after_readable_end() let options = finished_bool_option(b"writable", false); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, options, ); @@ -923,7 +928,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { let promise_value = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), source, boxed_ptr(sink as *const u8), undefined_value(), @@ -940,7 +945,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { fn stream_promises_pipeline_rejects_missing_streams() { let direct_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined_value(), undefined_value(), undefined_value(), @@ -954,7 +959,7 @@ fn stream_promises_pipeline_rejects_missing_streams() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 123.0, undefined_value(), boxed_ptr(empty_rest as *const u8), @@ -970,7 +975,7 @@ fn stream_promises_pipeline_rejects_missing_streams() { fn stream_promises_pipeline_rejects_invalid_source_body() { let direct_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 123.0, 456.0, undefined_value(), @@ -984,7 +989,7 @@ fn stream_promises_pipeline_rejects_invalid_source_body() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 123.0, 456.0, boxed_ptr(empty_rest as *const u8), @@ -1010,7 +1015,7 @@ fn stream_promises_finished_rejects_when_signal_aborts() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, boxed_ptr(opts as *const u8), ); @@ -1041,14 +1046,19 @@ fn stream_promises_finished_with_signal_resolves_for_finished_writable_side_stub let end = get_object_property(stream, b"end").expect("stream.end should exist"); let prev_this = crate::object::js_implicit_this_set(stream); unsafe { - let _ = crate::closure::js_native_call_value(end, std::ptr::null(), 0); + let _ = crate::closure::js_native_call_value( + end, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } crate::object::js_implicit_this_set(prev_this); let _ = crate::promise::js_promise_run_microtasks(); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), stream, boxed_ptr(opts as *const u8), ); diff --git a/crates/perry-runtime/src/node_submodules/timers.rs b/crates/perry-runtime/src/node_submodules/timers.rs index 1e413cfcfc..453e01e40a 100644 --- a/crates/perry-runtime/src/node_submodules/timers.rs +++ b/crates/perry-runtime/src/node_submodules/timers.rs @@ -160,7 +160,7 @@ pub(crate) extern "C" fn timers_promises_scheduler_wait( ) -> f64 { timers_promises_set_timeout( _closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), delay_ms, f64::from_bits(TAG_UNDEFINED), options, diff --git a/crates/perry-runtime/src/node_submodules/trace_events.rs b/crates/perry-runtime/src/node_submodules/trace_events.rs index 382d2434f6..9e679bf2e7 100644 --- a/crates/perry-runtime/src/node_submodules/trace_events.rs +++ b/crates/perry-runtime/src/node_submodules/trace_events.rs @@ -656,6 +656,7 @@ fn emit_enabled_trace_warning() { unsafe { crate::closure::js_native_call_value( callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), [warning.get_nanbox_f64()].as_ptr(), 1, ); diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index 6f1fc6543f..b3225ae8c6 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -80,7 +80,12 @@ fn evaluate_synthetic_module(module: *mut ObjectHeader) -> f64 { object_value, ))); let outcome = crate::exception::js_call_catching(|| unsafe { - crate::closure::js_native_call_value(callback.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); if let Err(error) = outcome { @@ -443,7 +448,12 @@ pub extern "C" fn js_vm_module_link(module_value: f64, linker: f64) -> f64 { module_request_extra(), ]; let dep = unsafe { - crate::closure::js_native_call_value(linker.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + linker.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }; linked = crate::array::js_array_push_f64(linked, dep); } diff --git a/crates/perry-runtime/src/object/arguments.rs b/crates/perry-runtime/src/object/arguments.rs index d76bb28f2f..8e97856e47 100644 --- a/crates/perry-runtime/src/object/arguments.rs +++ b/crates/perry-runtime/src/object/arguments.rs @@ -697,7 +697,7 @@ pub(crate) unsafe fn arguments_object_get_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::current()); + arguments_throw_type_error(std::ptr::null(), crate::closure::body_call::current_this()); } if let Some(box_ptr) = mapped_box { let value = crate::r#box::js_box_get(box_ptr); @@ -709,7 +709,10 @@ pub(crate) unsafe fn arguments_object_get_field( let closure = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - let value = crate::closure::js_closure_call0(closure); + let value = crate::closure::js_closure_call0( + closure, + crate::closure::plain_call_receiver(), + ); return Some(JSValue::from_bits(value.to_bits())); } } @@ -740,7 +743,7 @@ pub(crate) unsafe fn arguments_object_set_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::current()); + arguments_throw_type_error(std::ptr::null(), crate::closure::body_call::current_this()); } if !super::own_key_present(obj, key) { return false; @@ -750,7 +753,11 @@ pub(crate) unsafe fn arguments_object_set_field( let closure = (acc.set & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - crate::closure::js_closure_call1(closure, value); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value, + ); } } return true; diff --git a/crates/perry-runtime/src/object/assert.rs b/crates/perry-runtime/src/object/assert.rs index 9df6becabd..5906c3d842 100644 --- a/crates/perry-runtime/src/object/assert.rs +++ b/crates/perry-runtime/src/object/assert.rs @@ -322,13 +322,25 @@ fn expected_is_callable(value: f64) -> bool { fn call_validator_capturing(validator: f64, arg: f64) -> Result { crate::exception::catch_js_throw(|| { let args = [arg]; - unsafe { crate::closure::js_native_call_value(validator, args.as_ptr(), 1) } + unsafe { + crate::closure::js_native_call_value( + validator, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + } }) } fn call_block_capturing_throw(block: f64) -> Result { crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_native_call_value(block, std::ptr::null(), 0) + crate::closure::js_native_call_value( + block, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }) } diff --git a/crates/perry-runtime/src/object/async_generator_queue.rs b/crates/perry-runtime/src/object/async_generator_queue.rs index 1e3787e040..95964ae426 100644 --- a/crates/perry-runtime/src/object/async_generator_queue.rs +++ b/crates/perry-runtime/src/object/async_generator_queue.rs @@ -510,7 +510,7 @@ fn call_original(original: *const ClosureHeader, arg: f64) -> f64 { TAG_UNDEFINED, ))); } - js_closure_call1(original, arg) + js_closure_call1(original, crate::closure::plain_call_receiver(), arg) } fn after_initial_result(state_id: usize, result: 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 681878fb2f..99c36e1db1 100644 --- a/crates/perry-runtime/src/object/bound_method_receiver_tests.rs +++ b/crates/perry-runtime/src/object/bound_method_receiver_tests.rs @@ -80,7 +80,12 @@ unsafe fn register_class() -> (f64, f64) { /// `method.call(receiver)`: IMPLICIT_THIS bound to `receiver` for the call. unsafe fn call_with_this(method: f64, receiver: f64) -> f64 { let prev = crate::object::js_implicit_this_set(receiver); - let result = crate::closure::js_native_call_value(method, std::ptr::null(), 0); + let result = crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev); result } diff --git a/crates/perry-runtime/src/object/class_constructors.rs b/crates/perry-runtime/src/object/class_constructors.rs index 2aef916811..af1a650137 100644 --- a/crates/perry-runtime/src/object/class_constructors.rs +++ b/crates/perry-runtime/src/object/class_constructors.rs @@ -937,7 +937,12 @@ pub unsafe extern "C" fn js_super_method_call_dynamic( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(super::IMPLICIT_THIS.with(|c| c.replace(this_value.to_bits()))); - let result = crate::closure::js_native_call_value(method_value, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + method_value, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } @@ -989,7 +994,12 @@ unsafe fn call_displaced_native_base_method( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(super::IMPLICIT_THIS.with(|c| c.replace(this_value.to_bits()))); - let result = crate::closure::js_native_call_value(method_value, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + method_value, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); result } diff --git a/crates/perry-runtime/src/object/class_registry/construct.rs b/crates/perry-runtime/src/object/class_registry/construct.rs index 01f76c129e..f4e6aebcbc 100644 --- a/crates/perry-runtime/src/object/class_registry/construct.rs +++ b/crates/perry-runtime/src/object/class_registry/construct.rs @@ -601,7 +601,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_blob_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), parts, options, ); @@ -622,7 +622,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_file_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), parts, name, options, @@ -654,7 +654,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_headers_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), init, ); } @@ -670,7 +670,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_request_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), input, init, ); @@ -687,7 +687,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_response_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), body, init, ); @@ -871,7 +871,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( "Storage" => { return crate::web_storage::storage_constructor_illegal( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), ); } "BroadcastChannel" => { @@ -1225,7 +1225,12 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( let prev_current_new_target = CURRENT_NEW_TARGET.with(|value| value.replace(func_value.to_bits())); let prev_current_new_target_handle = scope.root_nanbox_u64(prev_current_new_target); - let result = crate::closure::js_native_call_value(func_value, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + func_value, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); CURRENT_NEW_TARGET.with(|value| value.set(prev_current_new_target_handle.get_nanbox_u64())); crate::object::js_new_target_set(prev_new_target_handle.get_nanbox_f64()); crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); @@ -1904,7 +1909,12 @@ pub unsafe extern "C" fn js_new_function_construct_with_new_target( crate::object::js_new_target_set(nt); let prev_current_new_target = CURRENT_NEW_TARGET.with(|value| value.replace(nt.to_bits())); let prev_current_new_target_handle = scope.root_nanbox_u64(prev_current_new_target); - let result = crate::closure::js_native_call_value(func_value, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + func_value, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); CURRENT_NEW_TARGET.with(|value| value.set(prev_current_new_target_handle.get_nanbox_u64())); crate::object::js_new_target_set(prev_new_target_handle.get_nanbox_f64()); crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); 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 7e26e4d905..bf75161281 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static.rs @@ -1690,7 +1690,12 @@ pub unsafe extern "C" fn js_class_static_method_call( if let Some(v) = field_val { let fv = crate::value::JSValue::from_bits(v.to_bits()); if !fv.is_undefined() && !fv.is_null() { - return crate::closure::js_native_call_value(v, args_ptr, args_len); + return crate::closure::js_native_call_value( + v, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); } } cid = get_parent_class_id(cid).unwrap_or(0); @@ -1725,7 +1730,12 @@ pub unsafe extern "C" fn js_class_static_method_call( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(member, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + member, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } @@ -1754,7 +1764,12 @@ pub unsafe extern "C" fn js_class_static_method_call( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(static_val, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + static_val, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } @@ -1833,7 +1848,12 @@ pub unsafe extern "C" fn js_class_static_method_call( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(member, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + member, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } @@ -1851,7 +1871,12 @@ pub unsafe extern "C" fn js_class_static_method_call( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(member, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + member, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } diff --git a/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs b/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs index 892c1b087c..2bc75c26a8 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs @@ -101,7 +101,7 @@ pub(crate) unsafe fn try_static_accessor_value_call( let receiver = receiver_handle.get_nanbox_f64(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); let result = crate::closure::js_native_call_value( - callee_handle.get_nanbox_f64(), + callee_handle.get_nanbox_f64(), crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); diff --git a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs index 518c269fca..9fa7ae0825 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs @@ -22,7 +22,7 @@ fn reflective_map_mutators_keep_subclass_identity() { assert_eq!( map_proto_set_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 1.0, 10.0 ) @@ -30,30 +30,48 @@ fn reflective_map_mutators_keep_subclass_identity() { receiver.get_nanbox_f64().to_bits() ); assert_eq!( - map_proto_get_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0), + map_proto_get_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ), 10.0 ); assert_eq!( - map_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + map_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), 1.0 ); assert_eq!( - map_proto_delete_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + map_proto_delete_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - map_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + map_proto_has_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), crate::value::TAG_FALSE ); map_proto_set_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 2.0, 20.0, ); - map_proto_clear_thunk(std::ptr::null(), crate::closure::JsThis::current(), 0.0); + map_proto_clear_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 0.0, + ); assert_eq!( - map_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + map_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), 0.0 ); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); @@ -68,29 +86,57 @@ fn reflective_set_mutators_keep_subclass_identity() { receiver.get_nanbox_f64(), )); assert_eq!( - set_proto_add_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + set_proto_add_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), receiver.get_nanbox_f64().to_bits() ); assert_eq!( - set_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + set_proto_has_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - set_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + set_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), 1.0 ); assert_eq!( - set_proto_delete_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + set_proto_delete_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - set_proto_has_thunk(std::ptr::null(), crate::closure::JsThis::current(), 1.0).to_bits(), + set_proto_has_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 1.0 + ) + .to_bits(), crate::value::TAG_FALSE ); - set_proto_add_thunk(std::ptr::null(), crate::closure::JsThis::current(), 2.0); - set_proto_clear_thunk(std::ptr::null(), crate::closure::JsThis::current(), 0.0); + set_proto_add_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 2.0, + ); + set_proto_clear_thunk( + std::ptr::null(), + crate::closure::body_call::current_this(), + 0.0, + ); assert_eq!( - set_proto_size_getter_thunk(std::ptr::null(), crate::closure::JsThis::current()), + set_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), 0.0 ); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index f2eeccd5a2..cf380242a4 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -223,7 +223,7 @@ pub(crate) fn date_to_json_value(this: f64) -> f64 { )); let closure = crate::value::js_nanbox_get_pointer(func.get_nanbox_f64()) as *const crate::closure::ClosureHeader; - let r = crate::closure::js_closure_call0(closure); + let r = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return r; } @@ -232,7 +232,7 @@ pub(crate) fn date_to_json_value(this: f64) -> f64 { #[cfg(test)] pub(crate) fn test_date_to_json_current_this() -> f64 { - date_to_json(std::ptr::null(), crate::closure::JsThis::current()) + date_to_json(std::ptr::null(), crate::closure::body_call::current_this()) } /// True iff `value` is an ECMAScript Object (`Type(O) is Object`). Objects are diff --git a/crates/perry-runtime/src/object/descriptor_state.rs b/crates/perry-runtime/src/object/descriptor_state.rs index 000896b7c5..adb1d26fbb 100644 --- a/crates/perry-runtime/src/object/descriptor_state.rs +++ b/crates/perry-runtime/src/object/descriptor_state.rs @@ -1495,7 +1495,7 @@ pub(crate) unsafe fn json_object_getter_value( let receiver = crate::value::js_nanbox_pointer(obj as i64); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure); + let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); js_implicit_this_set(prev.get_nanbox_f64()); Some(result) } diff --git a/crates/perry-runtime/src/object/field_get_set/accessors.rs b/crates/perry-runtime/src/object/field_get_set/accessors.rs index 27d84f4df0..57672e3906 100644 --- a/crates/perry-runtime/src/object/field_get_set/accessors.rs +++ b/crates/perry-runtime/src/object/field_get_set/accessors.rs @@ -520,7 +520,7 @@ pub(crate) unsafe fn invoke_accessor_getter(get_bits: u64, receiver: f64) -> JSV as *const crate::closure::ClosureHeader; let result_f64 = { let _boundary = crate::object::prototype_chain::UserCodeResolutionBoundary::enter(); - crate::closure::js_closure_call0(closure) + crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()) }; super::super::js_implicit_this_set(prev_h.get_nanbox_f64()); JSValue::from_bits(result_f64.to_bits()) @@ -555,7 +555,11 @@ pub(crate) unsafe fn invoke_accessor_setter(set_bits: u64, receiver: f64, value: let prev_h = scope.root_nanbox_f64(super::super::js_implicit_this_set(recv_h.get_nanbox_f64())); let closure = (call_h.get_nanbox_u64() & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; - let _ = crate::closure::js_closure_call1(closure, value_h.get_nanbox_f64()); + let _ = crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value_h.get_nanbox_f64(), + ); super::super::js_implicit_this_set(prev_h.get_nanbox_f64()); } diff --git a/crates/perry-runtime/src/object/field_get_set/field_ops.rs b/crates/perry-runtime/src/object/field_get_set/field_ops.rs index 9a2a664eca..8502632e88 100644 --- a/crates/perry-runtime/src/object/field_get_set/field_ops.rs +++ b/crates/perry-runtime/src/object/field_get_set/field_ops.rs @@ -317,7 +317,11 @@ pub extern "C" fn js_object_set_field_by_index( let closure = (acc.set & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - crate::closure::js_closure_call1(closure, value); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value, + ); } } return; diff --git a/crates/perry-runtime/src/object/field_set_by_name/tail.rs b/crates/perry-runtime/src/object/field_set_by_name/tail.rs index 94bdcb66f3..de898a23fc 100644 --- a/crates/perry-runtime/src/object/field_set_by_name/tail.rs +++ b/crates/perry-runtime/src/object/field_set_by_name/tail.rs @@ -128,7 +128,11 @@ pub(crate) fn set_field_by_name_object_tail( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let previous_this = this_scope.root_nanbox_f64(super::js_implicit_this_set(receiver)); - crate::closure::js_closure_call1(closure, value); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value, + ); super::js_implicit_this_set(previous_this.get_nanbox_f64()); } } else { @@ -770,7 +774,11 @@ pub(crate) fn set_field_by_name_object_tail( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let previous_this = this_scope.root_nanbox_f64(super::js_implicit_this_set(receiver)); - crate::closure::js_closure_call1(closure, value); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value, + ); super::js_implicit_this_set(previous_this.get_nanbox_f64()); } } else { diff --git a/crates/perry-runtime/src/object/global_this/array_error.rs b/crates/perry-runtime/src/object/global_this/array_error.rs index 4ccb0e0d40..47953de2a9 100644 --- a/crates/perry-runtime/src/object/global_this/array_error.rs +++ b/crates/perry-runtime/src/object/global_this/array_error.rs @@ -46,7 +46,14 @@ pub(crate) extern "C" fn function_prototype_call_thunk( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); - let result = unsafe { crate::closure::js_native_call_value(target, args_ptr, args_len) }; + let result = unsafe { + crate::closure::js_native_call_value( + target, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ) + }; IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); result } @@ -611,7 +618,12 @@ pub(crate) extern "C" fn function_prototype_apply_thunk( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); - let result = crate::closure::js_native_call_value(target, args.as_ptr(), args.len()); + let result = crate::closure::js_native_call_value( + target, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); result } 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 2821f672d9..6e39756dfe 100644 --- a/crates/perry-runtime/src/object/global_this/bigint_promise.rs +++ b/crates/perry-runtime/src/object/global_this/bigint_promise.rs @@ -714,7 +714,7 @@ pub(crate) extern "C" fn number_parse_float_thunk( _this: crate::closure::JsThis, value: f64, ) -> f64 { - global_this_parse_float_thunk(closure, crate::closure::JsThis::current(), value) + global_this_parse_float_thunk(closure, crate::closure::body_call::current_this(), value) } pub(crate) extern "C" fn number_parse_int_thunk( @@ -723,7 +723,12 @@ pub(crate) extern "C" fn number_parse_int_thunk( value: f64, radix: f64, ) -> f64 { - global_this_parse_int_thunk(closure, crate::closure::JsThis::current(), value, radix) + global_this_parse_int_thunk( + closure, + crate::closure::body_call::current_this(), + value, + radix, + ) } pub(crate) extern "C" fn typed_array_from_thunk( @@ -767,7 +772,12 @@ pub(crate) extern "C" fn typed_array_from_thunk( return v; } crate::object::js_implicit_this_set(this_arg); - let r = crate::closure::js_closure_call2(map_closure, v, k as f64); + let r = crate::closure::js_closure_call2( + map_closure, + crate::closure::plain_call_receiver(), + v, + k as f64, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); r }; 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 a7a33b5359..3cbb8ce8ea 100644 --- a/crates/perry-runtime/src/object/global_this/builtin_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/builtin_thunks.rs @@ -754,14 +754,21 @@ mod tests { ]); let f = global_this_function_call_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), rest, ); let args = [ f64::from_bits(crate::value::JSValue::number(2.0).bits()), f64::from_bits(crate::value::JSValue::number(3.0).bits()), ]; - let result = unsafe { crate::closure::js_native_call_value(f, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + f, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; assert_eq!(crate::builtins::js_number_coerce(result), 5.0); } } 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 df35ec13b7..adfd1f96a9 100644 --- a/crates/perry-runtime/src/object/global_this/fetch_globals.rs +++ b/crates/perry-runtime/src/object/global_this/fetch_globals.rs @@ -439,7 +439,12 @@ pub(crate) unsafe fn temporal_subclass_super( let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); // #10490: the displaced `new.target` crosses the same call. let prev_nt = this_scope.root_nanbox_f64(crate::object::js_new_target_set(parent_val)); - let cell = crate::closure::js_native_call_value(parent_val, args_ptr, args_len); + let cell = crate::closure::js_native_call_value( + parent_val, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_new_target_set(prev_nt.get_nanbox_f64()); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if crate::temporal::is_temporal_value(cell) { @@ -473,14 +478,14 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( let handle = if kind == 1 { global_this_request_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, ) } else { global_this_response_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, ) @@ -497,7 +502,7 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) -> f64 { let handle = global_this_request_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), input, init, ); @@ -511,7 +516,7 @@ pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) pub extern "C" fn js_response_subclass_init(this_box: f64, body: f64, init: f64) -> f64 { let handle = global_this_response_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), body, init, ); @@ -1031,14 +1036,14 @@ pub unsafe extern "C" fn js_fetch_or_value_super( let handle = if kind == Some("Request") { global_this_request_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, ) } else { global_this_response_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), arg0, arg1, ) @@ -1158,7 +1163,12 @@ pub unsafe extern "C" fn js_fetch_or_value_super( } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); - let r = crate::closure::js_native_call_value(callee, args_ptr, args_len); + let r = crate::closure::js_native_call_value( + callee, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); r } diff --git a/crates/perry-runtime/src/object/global_this/generator.rs b/crates/perry-runtime/src/object/global_this/generator.rs index 21c6236d98..ad959177d4 100644 --- a/crates/perry-runtime/src/object/global_this/generator.rs +++ b/crates/perry-runtime/src/object/global_this/generator.rs @@ -390,7 +390,11 @@ fn generator_proto_method(method: &[u8], arg: f64, is_async: bool) -> f64 { return bad_receiver(method); } match own_method(method) { - Some(own_closure) => crate::closure::js_closure_call1(own_closure, arg), + Some(own_closure) => crate::closure::js_closure_call1( + own_closure, + crate::closure::plain_call_receiver(), + arg, + ), None => bad_receiver(method), } } diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index e3bfb2f84b..9617c83f7f 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -1499,13 +1499,17 @@ mod tests { fn async_members_reject_with_compile_error() { let closure = std::ptr::null(); assert_rejected_with_compile_error( - webassembly_compile_thunk(closure, crate::closure::JsThis::current(), undefined()), + webassembly_compile_thunk( + closure, + crate::closure::body_call::current_this(), + undefined(), + ), "WebAssembly.compile", ); assert_rejected_with_compile_error( webassembly_instantiate_thunk( closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined(), undefined(), ), @@ -1514,7 +1518,7 @@ mod tests { assert_rejected_with_compile_error( webassembly_compile_streaming_thunk( closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined(), ), "WebAssembly.compileStreaming", @@ -1522,7 +1526,7 @@ mod tests { assert_rejected_with_compile_error( webassembly_instantiate_streaming_thunk( closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined(), ), "WebAssembly.instantiateStreaming", @@ -1534,7 +1538,7 @@ mod tests { fn validate_reports_false() { let result = webassembly_validate_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined(), ); assert_eq!( @@ -1549,7 +1553,7 @@ mod tests { let message = string_value("boom"); let compile_err = webassembly_compile_error_ctor_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), message, ); assert_eq!(error_name_bytes(compile_err), b"CompileError".to_vec()); @@ -1559,7 +1563,7 @@ mod tests { // Message coercion mirrors `new Error(v)`. let numbered = webassembly_link_error_ctor_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), 42.0, ); assert_eq!(error_name_bytes(numbered), b"LinkError".to_vec()); @@ -1567,7 +1571,7 @@ mod tests { let bare = webassembly_runtime_error_ctor_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), undefined(), ); assert_eq!(error_name_bytes(bare), b"RuntimeError".to_vec()); @@ -1583,12 +1587,12 @@ mod tests { let compile_err = webassembly_compile_error_ctor_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), string_value("x"), ); let link_err = webassembly_link_error_ctor_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), string_value("x"), ); diff --git a/crates/perry-runtime/src/object/groupby.rs b/crates/perry-runtime/src/object/groupby.rs index 5cfdd22cd5..d93447135e 100644 --- a/crates/perry-runtime/src/object/groupby.rs +++ b/crates/perry-runtime/src/object/groupby.rs @@ -298,7 +298,12 @@ unsafe fn group_by_collect<'scope>( items.push(crate::array::js_array_get_f64(raw, i as u32)); let cb_ptr = crate::value::js_nanbox_get_pointer(callback_handle.get_nanbox_f64()) as *const crate::closure::ClosureHeader; - let key_val = crate::closure::js_closure_call2(cb_ptr, items.get(i), i as f64); + let key_val = crate::closure::js_closure_call2( + cb_ptr, + crate::closure::plain_call_receiver(), + items.get(i), + i as f64, + ); keys.push(key_val); } (keys, items) diff --git a/crates/perry-runtime/src/object/handle_expando.rs b/crates/perry-runtime/src/object/handle_expando.rs index 534eb97687..0e172a2c27 100644 --- a/crates/perry-runtime/src/object/handle_expando.rs +++ b/crates/perry-runtime/src/object/handle_expando.rs @@ -127,7 +127,10 @@ pub fn handle_expando_get(handle: i64, name: &str) -> Option { if closure.is_null() { return Some(f64::from_bits(crate::value::TAG_UNDEFINED)); } - return Some(crate::closure::js_closure_call0(closure)); + return Some(crate::closure::js_closure_call0( + closure, + crate::closure::plain_call_receiver(), + )); } handle_expando_data_get(handle, name) } diff --git a/crates/perry-runtime/src/object/instanceof.rs b/crates/perry-runtime/src/object/instanceof.rs index 8b8bd5cb57..c7b7262ecf 100644 --- a/crates/perry-runtime/src/object/instanceof.rs +++ b/crates/perry-runtime/src/object/instanceof.rs @@ -576,7 +576,14 @@ fn dispatch_own_has_instance(cb: f64, value: f64) -> HasInstanceOutcome { throw_type_error(b"Symbol(Symbol.hasInstance) is not a function"); } let args = [value]; - let r = unsafe { crate::closure::js_native_call_value(cb, args.as_ptr(), 1) }; + let r = unsafe { + crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + }; HasInstanceOutcome::Result(if crate::value::js_is_truthy(r) != 0 { f64::from_bits(crate::value::TAG_TRUE) } else { diff --git a/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs b/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs index f0cc429c6e..d53b45246a 100644 --- a/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs +++ b/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs @@ -32,7 +32,12 @@ pub(super) fn proxy_instanceof(value: f64, constructor: f64) -> f64 { &args, ) } else { - crate::closure::js_native_call_value(method.get_nanbox_f64(), args.as_ptr(), 1) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) } }); js_implicit_this_set(previous_this.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index f2d85a9062..f15d3f9d18 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -566,7 +566,12 @@ pub(crate) unsafe fn call_overridden_iterator_next( let method = scope.root_nanbox_f64(own); super::js_implicit_this_set(iter.get_nanbox_f64()); let result = crate::exception::js_call_catching(|| { - crate::closure::js_native_call_value(method.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }); super::js_implicit_this_set(previous.get_nanbox_f64()); return match result { @@ -649,7 +654,12 @@ pub(crate) unsafe fn call_overridden_iterator_next( let method = scope.root_nanbox_f64(method); super::js_implicit_this_set(iter.get_nanbox_f64()); let result = crate::exception::js_call_catching(|| { - crate::closure::js_native_call_value(method.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }); super::js_implicit_this_set(previous.get_nanbox_f64()); match result { diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index 055c52795a..c209cba7f2 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -363,7 +363,12 @@ unsafe fn call_primitive_closure_value( let bound_h = scope.root_nanbox_u64(bound); let prev_this = crate::object::js_implicit_this_set(this_h.get_nanbox_f64()); let prev_this_h = scope.root_nanbox_f64(prev_this); - let result = crate::closure::js_native_call_value(bound_h.get_nanbox_f64(), args_ptr, args_len); + let result = crate::closure::js_native_call_value( + bound_h.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); Some(result) } @@ -832,7 +837,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value(field, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + field, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } @@ -959,7 +969,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value(field, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + field, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); result } @@ -1425,6 +1440,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let _this_scope = crate::object::ImplicitThisScope::bind(&root_scope, object()); return crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -1801,6 +1817,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( } else { crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), call_args.as_ptr(), call_args.len(), ) @@ -1824,6 +1841,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let call_args = refreshed_args(); return crate::closure::js_native_call_value( ctor.get_nanbox_f64(), + crate::closure::plain_call_receiver(), call_args.as_ptr(), call_args.len(), ); @@ -1865,7 +1883,10 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let prev_getter_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_getter_this_h = prev_getter_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object().to_bits()))); - let method_fn = crate::closure::js_closure_call0(getter); + let method_fn = crate::closure::js_closure_call0( + getter, + crate::closure::plain_call_receiver(), + ); let bound = crate::closure::clone_closure_rebind_this( method_fn.to_bits(), object(), @@ -1878,6 +1899,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let call_args = refreshed_args(); let result = crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), call_args.as_ptr(), call_args.len(), ); @@ -1979,6 +2001,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( } let result = crate::closure::js_native_call_value( method_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -2176,8 +2199,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = - crate::closure::js_native_call_value(f64::from_bits(bound), args_ptr, args_len); + let result = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } @@ -2351,6 +2378,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval().bits()))); let result = crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); @@ -2409,6 +2437,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval().bits()))); let result = crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); @@ -2668,7 +2697,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let args = refreshed_args(); let prev_this = root_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object())); - let result = crate::closure::js_native_call_value(m, args.as_ptr(), args.len()); + let result = crate::closure::js_native_call_value( + m, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } @@ -2737,8 +2771,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object().to_bits()))); - let result = - crate::closure::js_native_call_value(candidate, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + candidate, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } @@ -2768,7 +2806,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( crate::event_target::event_target_method_bind(recv, method_name.as_bytes()) { let args = refreshed_args(); - return crate::closure::js_native_call_value(bound, args.as_ptr(), args.len()); + return crate::closure::js_native_call_value( + bound, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } } @@ -2820,8 +2863,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(receiver.to_bits()))); let args = refreshed_args(); - let result = - crate::closure::js_native_call_value(candidate, args.as_ptr(), args.len()); + let result = crate::closure::js_native_call_value( + candidate, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } diff --git a/crates/perry-runtime/src/object/native_call_method/collection_methods.rs b/crates/perry-runtime/src/object/native_call_method/collection_methods.rs index fca8c802f2..85c214e4bb 100644 --- a/crates/perry-runtime/src/object/native_call_method/collection_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/collection_methods.rs @@ -454,6 +454,7 @@ pub(super) unsafe fn dispatch_raw_pointer( if field_val.is_pointer() { return Some(crate::closure::js_native_call_value( f64::from_bits(field_val.bits()), + crate::closure::plain_call_receiver(), args_ptr, args_len, )); diff --git a/crates/perry-runtime/src/object/native_call_method/common_methods.rs b/crates/perry-runtime/src/object/native_call_method/common_methods.rs index 164a6806f2..cb8d593883 100644 --- a/crates/perry-runtime/src/object/native_call_method/common_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/common_methods.rs @@ -778,7 +778,12 @@ pub(crate) unsafe fn dispatch_function_proto_method( callee_h.get_nanbox_f64(), this_h.get_nanbox_f64(), ); - let result = crate::closure::js_native_call_value(call_target, rest_ptr, rest_len); + let result = crate::closure::js_native_call_value( + call_target, + crate::closure::plain_call_receiver(), + rest_ptr, + rest_len, + ); if static_target { super::static_this_disarm(); } @@ -920,6 +925,7 @@ pub(crate) unsafe fn dispatch_function_proto_method( ); let result = crate::closure::js_native_call_value( apply_target, + crate::closure::plain_call_receiver(), call_args_ptr, call_args_len, ); diff --git a/crates/perry-runtime/src/object/native_call_method/disposal.rs b/crates/perry-runtime/src/object/native_call_method/disposal.rs index b4ea3c8526..16a5cca723 100644 --- a/crates/perry-runtime/src/object/native_call_method/disposal.rs +++ b/crates/perry-runtime/src/object/native_call_method/disposal.rs @@ -35,7 +35,12 @@ pub(super) unsafe fn try_symbol_dispose_dispatch( let prev_scope = crate::gc::RuntimeHandleScope::new(); let prev_h = prev_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value(method, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_h.get_nanbox_u64())); return Some(result); } 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 82bcc6ccd4..5aadd6ac94 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 @@ -135,7 +135,12 @@ unsafe fn dictionary_function_proto_method_call( ); let callee = crate::closure::rebind_explicit_this(value_h.get_nanbox_f64(), receiver_h.get_nanbox_f64()); - let result = crate::closure::js_native_call_value(callee, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + callee, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); Some(result) } @@ -197,7 +202,10 @@ mod tests { (*bound_ptr).func_ptr, crate::closure::BOUND_FUNCTION_FUNC_PTR ); - assert_eq!(crate::closure::js_closure_call0(bound_ptr), 42.0); + assert_eq!( + crate::closure::js_closure_call0(bound_ptr, crate::closure::plain_call_receiver()), + 42.0 + ); crate::closure::shape::FUNCTION_PROTOTYPE_PTR.store(saved, Ordering::Release); } } diff --git a/crates/perry-runtime/src/object/native_call_method/handle_methods.rs b/crates/perry-runtime/src/object/native_call_method/handle_methods.rs index c59e7f2dea..0fff2a5c85 100644 --- a/crates/perry-runtime/src/object/native_call_method/handle_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/handle_methods.rs @@ -47,7 +47,12 @@ unsafe fn dispatch_handle_proto_method( let bound = crate::closure::clone_closure_rebind_this(resolved_bits, object); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object)); - let result = crate::closure::js_native_call_value(f64::from_bits(bound), args_ptr, args_len); + let result = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); Some(result) } @@ -222,8 +227,12 @@ pub(super) unsafe fn dispatch_handle( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = - crate::closure::js_native_call_value(stored, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + stored, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } @@ -992,6 +1001,7 @@ pub(super) unsafe fn dispatch_handle( .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); let result = crate::closure::js_native_call_value( f64::from_bits(field_val.bits()), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); @@ -1226,6 +1236,7 @@ pub(super) unsafe fn dispatch_handle( .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); let result = crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); @@ -1293,6 +1304,7 @@ pub(super) unsafe fn dispatch_handle( .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); let result = crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); @@ -1314,8 +1326,12 @@ pub(super) unsafe fn dispatch_handle( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); - let result = - crate::closure::js_native_call_value(method_value, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + method_value, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } diff --git a/crates/perry-runtime/src/object/native_call_method/namespace_override.rs b/crates/perry-runtime/src/object/native_call_method/namespace_override.rs index 9626968393..4d5e8a2087 100644 --- a/crates/perry-runtime/src/object/native_call_method/namespace_override.rs +++ b/crates/perry-runtime/src/object/native_call_method/namespace_override.rs @@ -85,6 +85,7 @@ pub(super) unsafe fn call_native_namespace_override( let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args_handles); Some(crate::closure::js_native_call_value( bound_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), )) diff --git a/crates/perry-runtime/src/object/native_call_method/object_proto.rs b/crates/perry-runtime/src/object/native_call_method/object_proto.rs index 8a88e9f98c..716e08c823 100644 --- a/crates/perry-runtime/src/object/native_call_method/object_proto.rs +++ b/crates/perry-runtime/src/object/native_call_method/object_proto.rs @@ -35,7 +35,12 @@ pub(super) unsafe fn call_object_to_string_method(object: f64) -> Option { let bound = crate::closure::clone_closure_rebind_this(method_bits, receiver); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let result = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); Some(result) } diff --git a/crates/perry-runtime/src/object/native_call_method/patched_proto.rs b/crates/perry-runtime/src/object/native_call_method/patched_proto.rs index 85f623fea9..180cec634f 100644 --- a/crates/perry-runtime/src/object/native_call_method/patched_proto.rs +++ b/crates/perry-runtime/src/object/native_call_method/patched_proto.rs @@ -118,7 +118,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method_patched_proto( // `clone_closure_rebind_this` only covers a closure that captured `this`. // Restored on the way out, including when the callee throws (#10490). let _this_scope = crate::object::ImplicitThisScope::bind(&scope, receiver.get_nanbox_f64()); - crate::closure::js_native_call_value(bound.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + bound.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) } /// Spread form of [`js_native_call_method_patched_proto`]: `args_array_handle` diff --git a/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs b/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs index c66c4b9c01..86de6f6433 100644 --- a/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs @@ -125,6 +125,7 @@ pub(super) unsafe fn dispatch_primitive( ); let result = crate::closure::js_native_call_value( prop_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -357,6 +358,7 @@ pub(super) unsafe fn dispatch_primitive( let args = refreshed_args(); return Some(crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), )); @@ -400,6 +402,7 @@ pub(super) unsafe fn dispatch_primitive( let args = refreshed_args(); return Some(crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), )); @@ -443,7 +446,12 @@ pub(super) unsafe fn dispatch_primitive( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv.to_bits()))); - let result = crate::closure::js_native_call_value(v, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + v, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } @@ -508,7 +516,12 @@ pub(super) unsafe fn dispatch_primitive( let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(promise_val.to_bits()))); let result = unsafe { - crate::closure::js_native_call_value(own_then, args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + own_then, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }; IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); @@ -525,7 +538,12 @@ pub(super) unsafe fn dispatch_primitive( let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(promise_val.to_bits()))); let result = unsafe { - crate::closure::js_native_call_value(proto_method, args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + proto_method, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }; IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); @@ -649,6 +667,7 @@ pub(super) unsafe fn dispatch_primitive( ); let result = crate::closure::js_native_call_value( prop_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -734,7 +753,12 @@ pub(super) unsafe fn dispatch_primitive( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = crate::closure::js_native_call_value(v, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + v, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } @@ -757,8 +781,12 @@ pub(super) unsafe fn dispatch_primitive( let prev_this_scope = crate::gc::RuntimeHandleScope::new(); let prev_this_h = prev_this_scope .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = - crate::closure::js_native_call_value(value_f64, args_ptr, args_len); + let result = crate::closure::js_native_call_value( + value_f64, + crate::closure::plain_call_receiver(), + args_ptr, + args_len, + ); IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } diff --git a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs index e19cf2b2c0..f7f1be1f5a 100644 --- a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs @@ -165,7 +165,7 @@ fn object_prototype_builtin_invokes_to_string_without_self_redispatch() { let previous = crate::object::js_implicit_this_set(receiver); let result = crate::object::global_this::object_prototype_to_locale_string_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), ); crate::object::js_implicit_this_set(previous); 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 9097cd3050..57b496b337 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 @@ -46,7 +46,7 @@ extern "C" fn fast_buffer_constructor_thunk( } super::buffer_constructor_thunk( closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), value, byte_offset, length, 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 2cb5648801..3746f55e92 100644 --- a/crates/perry-runtime/src/object/native_module/namespace_builders.rs +++ b/crates/perry-runtime/src/object/native_module/namespace_builders.rs @@ -673,8 +673,12 @@ pub unsafe extern "C" fn js_https_global_agent_emit( let ptr = (callback_bits & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if crate::closure::is_closure_ptr(ptr as usize) { - let _ = - crate::closure::js_closure_call2(ptr, arg0.get_nanbox_f64(), arg1.get_nanbox_f64()); + let _ = crate::closure::js_closure_call2( + ptr, + crate::closure::plain_call_receiver(), + arg0.get_nanbox_f64(), + arg1.get_nanbox_f64(), + ); } } } 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 1dd64d8b60..fbe7154a36 100644 --- a/crates/perry-runtime/src/object/native_module/web_locks.rs +++ b/crates/perry-runtime/src/object/native_module/web_locks.rs @@ -660,7 +660,7 @@ pub extern "C" fn js_worker_threads_locks_request( ) -> f64 { worker_threads_locks_request( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), name_value, options_or_callback, maybe_callback, @@ -677,5 +677,5 @@ extern "C" fn worker_threads_locks_query( #[no_mangle] pub extern "C" fn js_worker_threads_locks_query() -> f64 { - worker_threads_locks_query(std::ptr::null(), crate::closure::JsThis::current()) + worker_threads_locks_query(std::ptr::null(), crate::closure::body_call::current_this()) } diff --git a/crates/perry-runtime/src/object/native_module_stream.rs b/crates/perry-runtime/src/object/native_module_stream.rs index 2be44fa0b7..3c55cb71c0 100644 --- a/crates/perry-runtime/src/object/native_module_stream.rs +++ b/crates/perry-runtime/src/object/native_module_stream.rs @@ -491,7 +491,12 @@ mod tests { )); let readable_to_web = static_method_value(readable, "toWeb"); let web_readable = unsafe { - crate::closure::js_native_call_value(readable_to_web, [node_readable].as_ptr(), 1) + crate::closure::js_native_call_value( + readable_to_web, + crate::closure::plain_call_receiver(), + [node_readable].as_ptr(), + 1, + ) }; assert_object_property_function(web_readable, b"getReader"); @@ -500,7 +505,12 @@ mod tests { )); let writable_to_web = static_method_value(writable, "toWeb"); let web_writable = unsafe { - crate::closure::js_native_call_value(writable_to_web, [node_writable].as_ptr(), 1) + crate::closure::js_native_call_value( + writable_to_web, + crate::closure::plain_call_receiver(), + [node_writable].as_ptr(), + 1, + ) }; assert_object_property_function(web_writable, b"getWriter"); @@ -509,7 +519,12 @@ mod tests { )); let duplex_to_web = static_method_value(duplex, "toWeb"); let web_pair = unsafe { - crate::closure::js_native_call_value(duplex_to_web, [node_duplex].as_ptr(), 1) + crate::closure::js_native_call_value( + duplex_to_web, + crate::closure::plain_call_receiver(), + [node_duplex].as_ptr(), + 1, + ) }; let web_pair_readable = unsafe { property(web_pair, b"readable") }; let web_pair_writable = unsafe { property(web_pair, b"writable") }; diff --git a/crates/perry-runtime/src/object/own_override.rs b/crates/perry-runtime/src/object/own_override.rs index f97de1bbf0..f42a91bbc9 100644 --- a/crates/perry-runtime/src/object/own_override.rs +++ b/crates/perry-runtime/src/object/own_override.rs @@ -398,6 +398,7 @@ unsafe fn invoke_own_user_method(own: f64, recv: f64, args: &[f64]) -> f64 { ); let result = crate::closure::js_native_call_value( method_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), refreshed.as_ptr(), refreshed.len(), ); diff --git a/crates/perry-runtime/src/object/property_key.rs b/crates/perry-runtime/src/object/property_key.rs index 05ca3527aa..0619395fdd 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -130,8 +130,12 @@ unsafe fn ordinary_to_primitive_string_key(value: f64) -> Option { } let bound = crate::closure::clone_closure_rebind_this(method_bits, receiver); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let result = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if js_value_is_not_object(result) { return Some(result); @@ -550,6 +554,7 @@ pub unsafe extern "C" fn js_object_super_call( let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); let result = crate::closure::js_native_call_value( f64::from_bits(bound_handle.get_nanbox_u64()), + crate::closure::plain_call_receiver(), args_ptr, args_len, ); diff --git a/crates/perry-runtime/src/object/tests.rs b/crates/perry-runtime/src/object/tests.rs index 92acc8a542..ee67b06886 100644 --- a/crates/perry-runtime/src/object/tests.rs +++ b/crates/perry-runtime/src/object/tests.rs @@ -1577,7 +1577,12 @@ fn this_method_snapshot_survives_own_property_replacement() { }); let result = unsafe { - crate::closure::js_native_call_value(captured.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + captured.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; assert_eq!( result.to_bits(), @@ -1643,7 +1648,14 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { let class_ref = super::native_module::class_constructor_ref_value(LEX_METHOD_TEST_CLASS_ID); let bound = super::native_module::js_class_method_bind(class_ref, NAME.as_ptr(), NAME.len()); - let result = unsafe { crate::closure::js_native_call_value(bound, std::ptr::null(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + bound, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!( result, 42.0, "a method value extracted off the CONSTRUCTOR ref must dispatch the \ @@ -1655,8 +1667,14 @@ fn constructor_ref_method_value_resolves_static_over_instance_method() { let proto_ref = super::native_module::class_prototype_ref_value(LEX_METHOD_TEST_CLASS_ID); let bound_proto = super::native_module::js_class_method_bind(proto_ref, NAME.as_ptr(), NAME.len()); - let result_proto = - unsafe { crate::closure::js_native_call_value(bound_proto, std::ptr::null(), 0) }; + let result_proto = unsafe { + crate::closure::js_native_call_value( + bound_proto, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; assert_eq!( result_proto, 7.0, "a method value extracted off the PROTOTYPE ref must still dispatch \ 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 fddc88000c..5324d54c83 100644 --- a/crates/perry-runtime/src/object/typed_array_proto_thunks.rs +++ b/crates/perry-runtime/src/object/typed_array_proto_thunks.rs @@ -666,7 +666,7 @@ macro_rules! rooted_callback { fn call(&self, $($arg: f64),+) -> f64 { self.handle .with_const_ptr::(|cb| { - self.site.call(cb, $($arg),+) + self.site.call(cb, crate::closure::plain_call_receiver(), $($arg),+) }) } } diff --git a/crates/perry-runtime/src/object/util_types.rs b/crates/perry-runtime/src/object/util_types.rs index 16ce3f781c..4102eecaf8 100644 --- a/crates/perry-runtime/src/object/util_types.rs +++ b/crates/perry-runtime/src/object/util_types.rs @@ -435,7 +435,7 @@ mod buffer_view_tests { assert_eq!( super::super::global_this::array_buffer_is_view_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), value ) .to_bits(), diff --git a/crates/perry-runtime/src/object/view_brand_tests.rs b/crates/perry-runtime/src/object/view_brand_tests.rs index 60db49c473..dd8c155532 100644 --- a/crates/perry-runtime/src/object/view_brand_tests.rs +++ b/crates/perry-runtime/src/object/view_brand_tests.rs @@ -34,7 +34,7 @@ fn row(value: f64) -> Row { is_view_direct: is_true(crate::object::js_util_types_is_array_buffer_view(value)), is_view_value: is_true(array_buffer_is_view_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), value, )), typed: is_true(crate::object::js_util_types_is_typed_array(value)), diff --git a/crates/perry-runtime/src/os/os_process_emitter.rs b/crates/perry-runtime/src/os/os_process_emitter.rs index 5f3985e734..a3f747065d 100644 --- a/crates/perry-runtime/src/os/os_process_emitter.rs +++ b/crates/perry-runtime/src/os/os_process_emitter.rs @@ -166,13 +166,23 @@ extern "C" fn process_once_raw_wrapper( let rest_jv = crate::value::JSValue::from_bits(rest_args.to_bits()); if !rest_jv.is_pointer() { return unsafe { - crate::closure::js_closure_call_array(target_ptr as i64, std::ptr::null(), 0) + crate::closure::js_closure_call_array( + target_ptr as i64, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; } let arr = rest_jv.as_pointer::(); if arr.is_null() { return unsafe { - crate::closure::js_closure_call_array(target_ptr as i64, std::ptr::null(), 0) + crate::closure::js_closure_call_array( + target_ptr as i64, + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; } let len = crate::array::js_array_length(arr) as usize; @@ -181,7 +191,12 @@ extern "C" fn process_once_raw_wrapper( args.push(crate::array::js_array_get_f64(arr, i as u32)); } unsafe { - crate::closure::js_closure_call_array(target_ptr as i64, args.as_ptr(), args.len() as i64) + crate::closure::js_closure_call_array( + target_ptr as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len() as i64, + ) } } @@ -379,6 +394,7 @@ pub(crate) fn emit_process_event(event: &str, args: &[f64]) -> bool { unsafe { crate::closure::js_closure_call_array( listener.callback as i64, + crate::closure::plain_call_receiver(), args.as_ptr(), args.len() as i64, ); diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index b67afa7478..c340f46521 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -861,7 +861,12 @@ extern "C" fn process_stdin_add_listener( } return stdin_this_value(); } - process_stdin_on(closure, crate::closure::JsThis::current(), event, callback) + process_stdin_on( + closure, + crate::closure::body_call::current_this(), + event, + callback, + ) } /// `process.stdin.once(event, cb)` reached as an object method. @@ -879,7 +884,12 @@ extern "C" fn process_stdin_add_listener_once( } return stdin_this_value(); } - process_stdin_once(closure, crate::closure::JsThis::current(), event, callback) + process_stdin_once( + closure, + crate::closure::body_call::current_this(), + event, + callback, + ) } /// `process.stdin.removeListener(event, cb)` / `.off(...)`. @@ -1452,7 +1462,11 @@ fn pump_stdin_data_chunks() { let this = stdin_this_value(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call1(closure, arg_handle.get_nanbox_f64()); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + arg_handle.get_nanbox_f64(), + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1474,7 +1488,7 @@ fn pump_stdin_data_chunks() { let this = stdin_this_value(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call0(closure); + crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1522,7 +1536,11 @@ fn maybe_fire_stdin_end() { let this = stdin_this_value(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call1(closure, flush_handle.get_nanbox_f64()); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + flush_handle.get_nanbox_f64(), + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1559,7 +1577,7 @@ fn maybe_fire_stdin_end() { let this = stdin_this_value(); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call0(closure); + crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } diff --git a/crates/perry-runtime/src/perf_hooks/timerify.rs b/crates/perry-runtime/src/perf_hooks/timerify.rs index 361aedca30..9e810fa125 100644 --- a/crates/perry-runtime/src/perf_hooks/timerify.rs +++ b/crates/perry-runtime/src/perf_hooks/timerify.rs @@ -113,7 +113,12 @@ extern "C" fn perf_timerify_wrapper( if crate::object::class_ref_id(target).is_some() { throw_type_error("Class constructor cannot be invoked without 'new'"); } - crate::closure::js_native_call_value(target, call_args.as_ptr(), call_args.len()) + crate::closure::js_native_call_value( + target, + crate::closure::plain_call_receiver(), + call_args.as_ptr(), + call_args.len(), + ) }; let result_handle = scope.root_nanbox_f64(result); diff --git a/crates/perry-runtime/src/plugin.rs b/crates/perry-runtime/src/plugin.rs index b36b8c2d2c..3ccc9e803e 100644 --- a/crates/perry-runtime/src/plugin.rs +++ b/crates/perry-runtime/src/plugin.rs @@ -458,7 +458,7 @@ unsafe fn call_closure_1(handler_bits: u64, arg: f64) -> f64 { if closure_ptr.is_null() { return f64::from_bits(JSValue::undefined().bits()); } - crate::closure::js_closure_call1(closure_ptr, arg) + crate::closure::js_closure_call1(closure_ptr, crate::closure::plain_call_receiver(), arg) } // ============================================================================ @@ -694,7 +694,7 @@ pub extern "C" fn perry_plugin_unregister_service(api_handle: i64, name: f64) -> reg.services.remove(idx); } else { drop(reg); - crate::closure::js_closure_call0(stop_ptr); + crate::closure::js_closure_call0(stop_ptr, crate::closure::plain_call_receiver()); let mut reg = REGISTRY.lock().unwrap(); if let Some(idx2) = reg .services diff --git a/crates/perry-runtime/src/process/finalization.rs b/crates/perry-runtime/src/process/finalization.rs index 8601555658..93b7acf947 100644 --- a/crates/perry-runtime/src/process/finalization.rs +++ b/crates/perry-runtime/src/process/finalization.rs @@ -164,7 +164,12 @@ fn run_process_finalization_callbacks(kind: ProcessFinalizationKind) { } let args = [obj_handle.get_nanbox_f64(), event_handle.get_nanbox_f64()]; unsafe { - crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()); + crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } } diff --git a/crates/perry-runtime/src/process/ipc.rs b/crates/perry-runtime/src/process/ipc.rs index 696d4dbe95..2afe84db9e 100644 --- a/crates/perry-runtime/src/process/ipc.rs +++ b/crates/perry-runtime/src/process/ipc.rs @@ -587,7 +587,14 @@ extern "C" fn process_ipc_send_callback_thunk( channel_closed_error() }; let args = [arg]; - unsafe { js_native_call_value(cb, args.as_ptr(), args.len()) }; + unsafe { + js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; undefined_value() } diff --git a/crates/perry-runtime/src/process/node_module.rs b/crates/perry-runtime/src/process/node_module.rs index 9e7326ed9e..91245dbce2 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -85,6 +85,7 @@ pub(crate) fn js_module_instance_require(specifier: f64) -> f64 { crate::closure::js_closure_call1( crate::value::js_nanbox_get_pointer(require.get_nanbox_f64()) as *const crate::closure::ClosureHeader, + crate::closure::plain_call_receiver(), specifier.get_nanbox_f64(), ) } @@ -1043,7 +1044,14 @@ extern "C" fn module_loader_hook_chain( let callback = js_closure_get_capture_f64(closure, 0); let next = js_closure_get_capture_f64(closure, 1); let args = [value, context, next]; - unsafe { crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()) } + unsafe { + crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + } } fn module_loader_hook_chain_function(callback: f64, next: f64, name: &str) -> f64 { @@ -1130,6 +1138,7 @@ pub extern "C" fn js_module_dynamic_import_apply_hooks(specifier: f64) -> f64 { let resolved = unsafe { crate::closure::js_native_call_value( resolve_chain.get_nanbox_f64(), + crate::closure::plain_call_receiver(), resolve_args.as_ptr(), resolve_args.len(), ) @@ -1154,6 +1163,7 @@ pub extern "C" fn js_module_dynamic_import_apply_hooks(specifier: f64) -> f64 { unsafe { crate::closure::js_native_call_value( load_chain.get_nanbox_f64(), + crate::closure::plain_call_receiver(), load_args.as_ptr(), load_args.len(), ); diff --git a/crates/perry-runtime/src/promise/assimilate.rs b/crates/perry-runtime/src/promise/assimilate.rs index b916d6a430..3d06d67085 100644 --- a/crates/perry-runtime/src/promise/assimilate.rs +++ b/crates/perry-runtime/src/promise/assimilate.rs @@ -393,7 +393,12 @@ extern "C" fn promise_resolve_thenable_job( let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(thenable)); let result = combinator_catch_js(|| unsafe { - crate::closure::js_native_call_value(then_action, args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + then_action, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if let Err(reason) = result { @@ -495,6 +500,7 @@ pub(super) fn assimilate_via_then_property(value: f64) -> f64 { unsafe { crate::closure::js_native_call_value( then_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); diff --git a/crates/perry-runtime/src/promise/async_step.rs b/crates/perry-runtime/src/promise/async_step.rs index a4a86f497d..7ec8e4d487 100644 --- a/crates/perry-runtime/src/promise/async_step.rs +++ b/crates/perry-runtime/src/promise/async_step.rs @@ -1088,7 +1088,12 @@ fn call_async_step_body( // `step` is needed only by this call and is never used afterward. Any // future post-call use must root it and re-read its relocated address. - let result = crate::closure::js_closure_call2(step, value, is_error); + let result = crate::closure::js_closure_call2( + step, + crate::closure::plain_call_receiver(), + value, + is_error, + ); let result_h = scope.root_nanbox_f64(result); INLINE_TRAP.with(|c| { c.set(InlineTrap { @@ -1376,7 +1381,7 @@ fn array_from_async_call_next( // result so the iteration loop continues without going through .then. array_from_async_step( chain_closure as *const _, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), next_result, ); } @@ -1713,7 +1718,7 @@ mod tests { ]) }); - async_step_fulfill_thunk(thunk, crate::closure::JsThis::current(), 41.0); + async_step_fulfill_thunk(thunk, crate::closure::body_call::current_this(), 41.0); assert_eq!( (*captured_to).state, diff --git a/crates/perry-runtime/src/promise/checked_dispatch.rs b/crates/perry-runtime/src/promise/checked_dispatch.rs index 54ac22139c..268487d88f 100644 --- a/crates/perry-runtime/src/promise/checked_dispatch.rs +++ b/crates/perry-runtime/src/promise/checked_dispatch.rs @@ -52,8 +52,14 @@ pub extern "C" fn js_promise_then_checked( let args = [on_fulfilled, on_rejected]; let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = - unsafe { crate::closure::js_native_call_value(own_then, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + own_then, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -67,7 +73,7 @@ pub extern "C" fn js_promise_then_checked( let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); let result = promise_prototype_then_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), on_fulfilled, on_rejected, ); @@ -93,7 +99,7 @@ pub extern "C" fn js_promise_catch_checked(promise_val: f64, on_rejected: f64) - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); let result = promise_prototype_catch_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), on_rejected, ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); @@ -114,7 +120,7 @@ pub extern "C" fn js_promise_finally_checked(promise_val: f64, on_finally: f64) let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); let result = promise_prototype_finally_thunk( std::ptr::null(), - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), on_finally, ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index 0a16aa087c..d57831632d 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -223,7 +223,12 @@ fn promise_try_call(callback: f64, args_ptr: *const f64, args_len: usize) -> Res }; crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_closure_call_array(closure as i64, args_ptr, args_len as i64) + crate::closure::js_closure_call_array( + closure as i64, + crate::closure::plain_call_receiver(), + args_ptr, + args_len as i64, + ) }) } @@ -693,12 +698,14 @@ pub extern "C" fn js_promise_new_with_executor( if let Err(reason) = combinator_catch_js(|| { js_closure_call2( rooted_executor(), + crate::closure::plain_call_receiver(), resolve_h.get_nanbox_f64(), reject_h.get_nanbox_f64(), ) }) { crate::closure::js_closure_call1( ptr_of(&reject_h) as *mut crate::closure::ClosureHeader, + crate::closure::plain_call_receiver(), reason, ); } @@ -706,6 +713,7 @@ pub extern "C" fn js_promise_new_with_executor( // Non-callable: preserve the prior synchronous TypeError (uncaught). js_closure_call2( rooted_executor(), + crate::closure::plain_call_receiver(), resolve_h.get_nanbox_f64(), reject_h.get_nanbox_f64(), ); @@ -1740,257 +1748,5 @@ extern "C" fn promise_any_reject_handler( } #[cfg(test)] -mod tests { - use super::*; - use crate::array::{js_array_alloc, js_array_get_f64, js_array_set_f64}; - use crate::closure::{ - js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64, - }; - use crate::object::{js_object_alloc, js_object_get_field, js_object_set_field_by_name}; - use crate::value::js_nanbox_pointer; - - fn reset_promise_test_state() { - TASK_QUEUE.with(|q| q.borrow_mut().clear()); - PROMISE_ALL_STATES.with(|s| s.borrow_mut().clear()); - } - - fn thenable_value(func: *const u8, 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); - let key = crate::string::js_string_from_bytes(b"then".as_ptr(), 4); - js_object_set_field_by_name(obj, key, js_nanbox_pointer(then as i64)); - js_nanbox_pointer(obj as i64) - } - - #[test] - fn promise_probe_rejects_pointer_tagged_native_handles() { - let fetch_family_handle = js_nanbox_pointer(0x40001); - assert_eq!(js_value_is_promise(fetch_family_handle), 0); - } - - // #5226: small typed arrays / buffers are system-`alloc`'d off the GC heap - // with NO 8-byte GcHeader prefix and are tracked only in side tables. The - // type-dispatch probes (`js_value_is_promise`, `is_date_cell_addr`, …) must - // recognize them via the side table and must NOT back-read - // `ptr - GC_HEADER_SIZE` — a read on a block at the start of a freshly - // mapped region crosses into the (unmapped) preceding page and segfaults. - // Reproduce that worst case with a guarded mapping: without the side-table - // skip these probes SIGSEGV; with it, they classify the block correctly. - #[cfg(unix)] - #[test] - fn type_probes_skip_offheap_typed_array_with_unmapped_preceding_page() { - unsafe { - let page = libc::sysconf(libc::_SC_PAGESIZE) as usize; - let total = page * 2; - let base = libc::mmap( - std::ptr::null_mut(), - total, - libc::PROT_READ | libc::PROT_WRITE, - libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, - -1, - 0, - ); - assert_ne!(base, libc::MAP_FAILED); - // Guard the first page so any `ptr - GC_HEADER_SIZE` back-read faults. - assert_eq!(libc::mprotect(base, page, libc::PROT_NONE), 0); - let ta = (base as *mut u8).add(page) as *const crate::typedarray::TypedArrayHeader; - crate::typedarray::register_typed_array(ta, 0); - - let value = js_nanbox_pointer(ta as i64); - assert_eq!(js_value_is_promise(value), 0); - assert!(!crate::date::is_date_cell_addr(ta as usize)); - - crate::typedarray::unregister_typed_array(ta); - assert_eq!(libc::munmap(base, total), 0); - } - } - - extern "C" fn test_thenable_resolve_twice( - closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, - on_fulfilled: f64, - _on_rejected: f64, - ) -> f64 { - let value = js_closure_get_capture_f64(closure, 0); - let unexpected = value + 1000.0; - unsafe { - crate::closure::js_native_call_value(on_fulfilled, [value].as_ptr(), 1); - crate::closure::js_native_call_value(on_fulfilled, [unexpected].as_ptr(), 1); - } - 0.0 - } - - extern "C" fn test_thenable_reject( - closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, - _on_fulfilled: f64, - on_rejected: f64, - ) -> f64 { - let reason = js_closure_get_capture_f64(closure, 0); - unsafe { - crate::closure::js_native_call_value(on_rejected, [reason].as_ptr(), 1); - } - 0.0 - } - - #[test] - fn promise_all_assimilates_thenable_and_guards_double_resolve() { - unsafe { - reset_promise_test_state(); - let arr = js_array_alloc(1); - (*arr).length = 1; - js_array_set_f64( - arr, - 0, - thenable_value(test_thenable_resolve_twice as *const u8, 7.0), - ); - - let all = js_promise_all(arr); - assert_eq!((*all).state, PromiseState::Pending); - - crate::promise::js_promise_run_microtasks(); - - assert_eq!((*all).state, PromiseState::Fulfilled); - let results = crate::value::js_nanbox_get_pointer((*all).value) - as *const crate::array::ArrayHeader; - assert_eq!(js_array_get_f64(results, 0), 7.0); - } - } - - #[test] - fn promise_all_rejects_from_thenable_job() { - unsafe { - reset_promise_test_state(); - let arr = js_array_alloc(1); - (*arr).length = 1; - js_array_set_f64( - arr, - 0, - thenable_value(test_thenable_reject as *const u8, 13.0), - ); - - let all = js_promise_all(arr); - assert_eq!((*all).state, PromiseState::Pending); - - crate::promise::js_promise_run_microtasks(); - - assert_eq!((*all).state, PromiseState::Rejected); - assert_eq!((*all).reason, 13.0); - } - } - - #[test] - fn promise_all_settled_assimilates_thenables_in_input_order() { - unsafe { - reset_promise_test_state(); - let arr = js_array_alloc(2); - (*arr).length = 2; - js_array_set_f64( - arr, - 0, - thenable_value(test_thenable_reject as *const u8, 1.0), - ); - js_array_set_f64( - arr, - 1, - thenable_value(test_thenable_resolve_twice as *const u8, 2.0), - ); - - let settled = js_promise_all_settled(arr); - assert_eq!((*settled).state, PromiseState::Pending); - - crate::promise::js_promise_run_microtasks(); - - assert_eq!((*settled).state, PromiseState::Fulfilled); - let results = crate::value::js_nanbox_get_pointer((*settled).value) - as *const crate::array::ArrayHeader; - let first = crate::value::js_nanbox_get_pointer(js_array_get_f64(results, 0)) - as *const crate::object::ObjectHeader; - let second = crate::value::js_nanbox_get_pointer(js_array_get_f64(results, 1)) - as *const crate::object::ObjectHeader; - assert_eq!(js_object_get_field(first, 1).bits(), 1.0f64.to_bits()); - assert_eq!(js_object_get_field(second, 1).bits(), 2.0f64.to_bits()); - } - } - - #[test] - fn promise_result_resolution_assimilates_thenable_objects() { - unsafe { - reset_promise_test_state(); - let promise = js_promise_new(); - promise_resolve_assimilating( - promise, - thenable_value(test_thenable_resolve_twice as *const u8, 21.0), - ); - assert_eq!((*promise).state, PromiseState::Pending); - - crate::promise::js_promise_run_microtasks(); - - assert_eq!((*promise).state, PromiseState::Fulfilled); - assert_eq!((*promise).value, 21.0); - } - } - - /// Regression: a pending promise reused as input to two `Promise.all` - /// calls must settle BOTH all-promises when it resolves. Pre-fix, - /// `promise_all_take_handler` only popped the first matching state - /// (via `swap_remove`), so the second `Promise.all` hung forever. - #[test] - fn promise_all_with_shared_pending_input_resolves_both() { - unsafe { - // Pending promise that will be shared across two Promise.all calls. - let shared = js_promise_new(); - - // Second input for each all() — a pre-resolved promise so the - // remaining counter only needs `shared` to settle. - let other_a = js_promise_new(); - js_promise_resolve(other_a, 100.0); - let other_b = js_promise_new(); - js_promise_resolve(other_b, 200.0); - - // Build [shared, other_a] - let arr_a = js_array_alloc(2); - (*arr_a).length = 2; - js_array_set_f64(arr_a, 0, js_nanbox_pointer(shared as i64)); - js_array_set_f64(arr_a, 1, js_nanbox_pointer(other_a as i64)); - let all_a = js_promise_all(arr_a); - - // Build [shared, other_b] - let arr_b = js_array_alloc(2); - (*arr_b).length = 2; - js_array_set_f64(arr_b, 0, js_nanbox_pointer(shared as i64)); - js_array_set_f64(arr_b, 1, js_nanbox_pointer(other_b as i64)); - let all_b = js_promise_all(arr_b); - - // Both all() results should still be pending. - assert_eq!((*all_a).state, PromiseState::Pending); - assert_eq!((*all_b).state, PromiseState::Pending); - - // PROMISE_ALL_STATES must hold TWO entries keyed on `shared`. - let registered = - PROMISE_ALL_STATES.with(|s| s.borrow_mut().count_for_key(shared as usize)); - assert_eq!( - registered, 2, - "expected two Promise.all states keyed on the shared pending promise" - ); - - // Settle the shared promise; drain microtasks so PromiseAll tasks - // run and update both result arrays. - js_promise_resolve(shared, 42.0); - crate::promise::js_promise_run_microtasks(); - - // Both Promise.all results must now be Fulfilled. - assert_eq!( - (*all_a).state, - PromiseState::Fulfilled, - "first Promise.all should have settled" - ); - assert_eq!( - (*all_b).state, - PromiseState::Fulfilled, - "second Promise.all should have settled (was hanging pre-fix)" - ); - } - } -} +#[path = "combinators_tests.rs"] +mod tests; diff --git a/crates/perry-runtime/src/promise/combinators_tests.rs b/crates/perry-runtime/src/promise/combinators_tests.rs new file mode 100644 index 0000000000..718755fa76 --- /dev/null +++ b/crates/perry-runtime/src/promise/combinators_tests.rs @@ -0,0 +1,265 @@ +//! Unit tests for `combinators.rs` (split out for the file-size gate). +use super::*; +use crate::array::{js_array_alloc, js_array_get_f64, js_array_set_f64}; +use crate::closure::{js_closure_alloc, js_closure_get_capture_f64, js_closure_set_capture_f64}; +use crate::object::{js_object_alloc, js_object_get_field, js_object_set_field_by_name}; +use crate::value::js_nanbox_pointer; + +fn reset_promise_test_state() { + TASK_QUEUE.with(|q| q.borrow_mut().clear()); + PROMISE_ALL_STATES.with(|s| s.borrow_mut().clear()); +} + +fn thenable_value(func: *const u8, 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); + let key = crate::string::js_string_from_bytes(b"then".as_ptr(), 4); + js_object_set_field_by_name(obj, key, js_nanbox_pointer(then as i64)); + js_nanbox_pointer(obj as i64) +} + +#[test] +fn promise_probe_rejects_pointer_tagged_native_handles() { + let fetch_family_handle = js_nanbox_pointer(0x40001); + assert_eq!(js_value_is_promise(fetch_family_handle), 0); +} + +// #5226: small typed arrays / buffers are system-`alloc`'d off the GC heap +// with NO 8-byte GcHeader prefix and are tracked only in side tables. The +// type-dispatch probes (`js_value_is_promise`, `is_date_cell_addr`, …) must +// recognize them via the side table and must NOT back-read +// `ptr - GC_HEADER_SIZE` — a read on a block at the start of a freshly +// mapped region crosses into the (unmapped) preceding page and segfaults. +// Reproduce that worst case with a guarded mapping: without the side-table +// skip these probes SIGSEGV; with it, they classify the block correctly. +#[cfg(unix)] +#[test] +fn type_probes_skip_offheap_typed_array_with_unmapped_preceding_page() { + unsafe { + let page = libc::sysconf(libc::_SC_PAGESIZE) as usize; + let total = page * 2; + let base = libc::mmap( + std::ptr::null_mut(), + total, + libc::PROT_READ | libc::PROT_WRITE, + libc::MAP_PRIVATE | libc::MAP_ANONYMOUS, + -1, + 0, + ); + assert_ne!(base, libc::MAP_FAILED); + // Guard the first page so any `ptr - GC_HEADER_SIZE` back-read faults. + assert_eq!(libc::mprotect(base, page, libc::PROT_NONE), 0); + let ta = (base as *mut u8).add(page) as *const crate::typedarray::TypedArrayHeader; + crate::typedarray::register_typed_array(ta, 0); + + let value = js_nanbox_pointer(ta as i64); + assert_eq!(js_value_is_promise(value), 0); + assert!(!crate::date::is_date_cell_addr(ta as usize)); + + crate::typedarray::unregister_typed_array(ta); + assert_eq!(libc::munmap(base, total), 0); + } +} + +extern "C" fn test_thenable_resolve_twice( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + on_fulfilled: f64, + _on_rejected: f64, +) -> f64 { + let value = js_closure_get_capture_f64(closure, 0); + let unexpected = value + 1000.0; + unsafe { + crate::closure::js_native_call_value( + on_fulfilled, + crate::closure::plain_call_receiver(), + [value].as_ptr(), + 1, + ); + crate::closure::js_native_call_value( + on_fulfilled, + crate::closure::plain_call_receiver(), + [unexpected].as_ptr(), + 1, + ); + } + 0.0 +} + +extern "C" fn test_thenable_reject( + closure: *const crate::closure::ClosureHeader, + _this: crate::closure::JsThis, + _on_fulfilled: f64, + on_rejected: f64, +) -> f64 { + let reason = js_closure_get_capture_f64(closure, 0); + unsafe { + crate::closure::js_native_call_value( + on_rejected, + crate::closure::plain_call_receiver(), + [reason].as_ptr(), + 1, + ); + } + 0.0 +} + +#[test] +fn promise_all_assimilates_thenable_and_guards_double_resolve() { + unsafe { + reset_promise_test_state(); + let arr = js_array_alloc(1); + (*arr).length = 1; + js_array_set_f64( + arr, + 0, + thenable_value(test_thenable_resolve_twice as *const u8, 7.0), + ); + + let all = js_promise_all(arr); + assert_eq!((*all).state, PromiseState::Pending); + + crate::promise::js_promise_run_microtasks(); + + assert_eq!((*all).state, PromiseState::Fulfilled); + let results = + crate::value::js_nanbox_get_pointer((*all).value) as *const crate::array::ArrayHeader; + assert_eq!(js_array_get_f64(results, 0), 7.0); + } +} + +#[test] +fn promise_all_rejects_from_thenable_job() { + unsafe { + reset_promise_test_state(); + let arr = js_array_alloc(1); + (*arr).length = 1; + js_array_set_f64( + arr, + 0, + thenable_value(test_thenable_reject as *const u8, 13.0), + ); + + let all = js_promise_all(arr); + assert_eq!((*all).state, PromiseState::Pending); + + crate::promise::js_promise_run_microtasks(); + + assert_eq!((*all).state, PromiseState::Rejected); + assert_eq!((*all).reason, 13.0); + } +} + +#[test] +fn promise_all_settled_assimilates_thenables_in_input_order() { + unsafe { + reset_promise_test_state(); + let arr = js_array_alloc(2); + (*arr).length = 2; + js_array_set_f64( + arr, + 0, + thenable_value(test_thenable_reject as *const u8, 1.0), + ); + js_array_set_f64( + arr, + 1, + thenable_value(test_thenable_resolve_twice as *const u8, 2.0), + ); + + let settled = js_promise_all_settled(arr); + assert_eq!((*settled).state, PromiseState::Pending); + + crate::promise::js_promise_run_microtasks(); + + assert_eq!((*settled).state, PromiseState::Fulfilled); + let results = crate::value::js_nanbox_get_pointer((*settled).value) + as *const crate::array::ArrayHeader; + let first = crate::value::js_nanbox_get_pointer(js_array_get_f64(results, 0)) + as *const crate::object::ObjectHeader; + let second = crate::value::js_nanbox_get_pointer(js_array_get_f64(results, 1)) + as *const crate::object::ObjectHeader; + assert_eq!(js_object_get_field(first, 1).bits(), 1.0f64.to_bits()); + assert_eq!(js_object_get_field(second, 1).bits(), 2.0f64.to_bits()); + } +} + +#[test] +fn promise_result_resolution_assimilates_thenable_objects() { + unsafe { + reset_promise_test_state(); + let promise = js_promise_new(); + promise_resolve_assimilating( + promise, + thenable_value(test_thenable_resolve_twice as *const u8, 21.0), + ); + assert_eq!((*promise).state, PromiseState::Pending); + + crate::promise::js_promise_run_microtasks(); + + assert_eq!((*promise).state, PromiseState::Fulfilled); + assert_eq!((*promise).value, 21.0); + } +} + +/// Regression: a pending promise reused as input to two `Promise.all` +/// calls must settle BOTH all-promises when it resolves. Pre-fix, +/// `promise_all_take_handler` only popped the first matching state +/// (via `swap_remove`), so the second `Promise.all` hung forever. +#[test] +fn promise_all_with_shared_pending_input_resolves_both() { + unsafe { + // Pending promise that will be shared across two Promise.all calls. + let shared = js_promise_new(); + + // Second input for each all() — a pre-resolved promise so the + // remaining counter only needs `shared` to settle. + let other_a = js_promise_new(); + js_promise_resolve(other_a, 100.0); + let other_b = js_promise_new(); + js_promise_resolve(other_b, 200.0); + + // Build [shared, other_a] + let arr_a = js_array_alloc(2); + (*arr_a).length = 2; + js_array_set_f64(arr_a, 0, js_nanbox_pointer(shared as i64)); + js_array_set_f64(arr_a, 1, js_nanbox_pointer(other_a as i64)); + let all_a = js_promise_all(arr_a); + + // Build [shared, other_b] + let arr_b = js_array_alloc(2); + (*arr_b).length = 2; + js_array_set_f64(arr_b, 0, js_nanbox_pointer(shared as i64)); + js_array_set_f64(arr_b, 1, js_nanbox_pointer(other_b as i64)); + let all_b = js_promise_all(arr_b); + + // Both all() results should still be pending. + assert_eq!((*all_a).state, PromiseState::Pending); + assert_eq!((*all_b).state, PromiseState::Pending); + + // PROMISE_ALL_STATES must hold TWO entries keyed on `shared`. + let registered = PROMISE_ALL_STATES.with(|s| s.borrow_mut().count_for_key(shared as usize)); + assert_eq!( + registered, 2, + "expected two Promise.all states keyed on the shared pending promise" + ); + + // Settle the shared promise; drain microtasks so PromiseAll tasks + // run and update both result arrays. + js_promise_resolve(shared, 42.0); + crate::promise::js_promise_run_microtasks(); + + // Both Promise.all results must now be Fulfilled. + assert_eq!( + (*all_a).state, + PromiseState::Fulfilled, + "first Promise.all should have settled" + ); + assert_eq!( + (*all_b).state, + PromiseState::Fulfilled, + "second Promise.all should have settled (was hanging pre-fix)" + ); + } +} diff --git a/crates/perry-runtime/src/promise/microtasks.rs b/crates/perry-runtime/src/promise/microtasks.rs index f460c190b8..48a522ee9c 100644 --- a/crates/perry-runtime/src/promise/microtasks.rs +++ b/crates/perry-runtime/src/promise/microtasks.rs @@ -655,6 +655,7 @@ fn pump_protected(mode: MicrotaskDrainMode, reentrant: bool, landed: bool, ran: // above allocate. let result = crate::closure::js_closure_call1( rooted_closure(&callback_handle), + crate::closure::plain_call_receiver(), value_handle.get_nanbox_f64(), ); // Keep the callback result rooted across `after()` (which @@ -810,8 +811,11 @@ fn pump_protected(mode: MicrotaskDrainMode, reentrant: bool, landed: bool, ran: }; crate::v8::promise_hook_before(rooted_promise(&next_handle)); let callback = rooted_closure(&callback_handle); - let result = - crate::closure::js_closure_call1(callback, value_handle.get_nanbox_f64()); + let result = crate::closure::js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + value_handle.get_nanbox_f64(), + ); CURRENT_MICROTASK_VALUE.with(|c| c.set(result)); let next_for_after = CURRENT_MICROTASK_NEXT.with(|c| c.get()); crate::v8::promise_hook_after(next_for_after); @@ -875,7 +879,10 @@ fn pump_protected(mode: MicrotaskDrainMode, reentrant: bool, landed: bool, ran: CURRENT_MICROTASK_VALUE.with(|c| c.set(0.0)); CURRENT_MICROTASK_NEXT.with(|c| c.set(std::ptr::null_mut())); crate::async_hooks::before(async_id, trigger_async_id); - crate::closure::js_closure_call0(rooted_closure(&callback_handle)); + crate::closure::js_closure_call0( + rooted_closure(&callback_handle), + crate::closure::plain_call_receiver(), + ); crate::async_hooks::after(async_id); crate::async_hooks::destroy(async_id); CURRENT_MICROTASK_PROMISE @@ -1192,7 +1199,7 @@ fn call_async_step_direct( crate::closure::body_call::js_body_call!( func_ptr, step_closure, - crate::closure::JsThis::current(), + crate::closure::body_call::current_this(), value, is_error_bits ) diff --git a/crates/perry-runtime/src/promise/spec_combinators.rs b/crates/perry-runtime/src/promise/spec_combinators.rs index 7dcb248480..c8301afec5 100644 --- a/crates/perry-runtime/src/promise/spec_combinators.rs +++ b/crates/perry-runtime/src/promise/spec_combinators.rs @@ -302,8 +302,9 @@ fn call_with_this(func: f64, this_arg: f64, args: &[f64]) -> Result { }; let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_arg)); - let result = - combinator_catch_js(|| unsafe { crate::closure::js_native_call_value(func, ptr, len) }); + let result = combinator_catch_js(|| unsafe { + crate::closure::js_native_call_value(func, crate::closure::plain_call_receiver(), ptr, len) + }); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } @@ -1342,7 +1343,12 @@ mod fast_arm_tests { ) -> f64 { let v = crate::closure::js_closure_get_capture_f64(closure, 0); unsafe { - crate::closure::js_native_call_value(on_fulfilled, [v].as_ptr(), 1); + crate::closure::js_native_call_value( + on_fulfilled, + crate::closure::plain_call_receiver(), + [v].as_ptr(), + 1, + ); } undef() } diff --git a/crates/perry-runtime/src/promise/subclass.rs b/crates/perry-runtime/src/promise/subclass.rs index 5ad228b020..490c4b7400 100644 --- a/crates/perry-runtime/src/promise/subclass.rs +++ b/crates/perry-runtime/src/promise/subclass.rs @@ -152,11 +152,16 @@ pub extern "C" fn js_promise_subclass_init(this: f64, executor: f64) -> f64 { // closures and raw-pointer-bits closures, so `executor` is passed as-is. let args = [resolve.get_nanbox_f64(), reject.get_nanbox_f64()]; if let Err(reason) = super::combinators::combinator_catch_js(|| unsafe { - crate::closure::js_native_call_value(executor.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + executor.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }) { let reject = crate::value::js_nanbox_get_pointer(reject.get_nanbox_f64()) as *const crate::closure::ClosureHeader; - crate::closure::js_closure_call1(reject, reason); + crate::closure::js_closure_call1(reject, crate::closure::plain_call_receiver(), reason); } // #7795: arm the probe gate before the field exists, so no reader can diff --git a/crates/perry-runtime/src/promise/then.rs b/crates/perry-runtime/src/promise/then.rs index 2a7f15cf05..54b070a421 100644 --- a/crates/perry-runtime/src/promise/then.rs +++ b/crates/perry-runtime/src/promise/then.rs @@ -1030,8 +1030,14 @@ fn call_receiver_then(receiver: f64, args: &[f64]) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - unsafe { crate::closure::js_native_call_value(then_fn, args.as_ptr(), args.len()) }; + let result = unsafe { + crate::closure::js_native_call_value( + then_fn, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) + }; crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } @@ -1224,7 +1230,11 @@ extern "C" fn then_cap_fulfill_fn( (value, false) } else { match crate::exception::catch_js_throw(|| { - crate::closure::js_closure_call1(on_ful_cl, value) + crate::closure::js_closure_call1( + on_ful_cl, + crate::closure::plain_call_receiver(), + value, + ) }) { Ok(ret) => (ret, false), Err(exc) => (exc, true), @@ -1233,7 +1243,14 @@ extern "C" fn then_cap_fulfill_fn( let func = if threw { cap_reject } else { cap_resolve }; let args = [result]; - let _ = unsafe { js_native_call_value(func, args.as_ptr(), 1) }; + let _ = unsafe { + js_native_call_value( + func, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + }; undef } @@ -1259,7 +1276,11 @@ extern "C" fn then_cap_reject_fn( (reason, true) // passthrough rejection } else { match crate::exception::catch_js_throw(|| { - crate::closure::js_closure_call1(on_rej_cl, reason) + crate::closure::js_closure_call1( + on_rej_cl, + crate::closure::plain_call_receiver(), + reason, + ) }) { Ok(ret) => (ret, false), Err(exc) => (exc, true), @@ -1268,7 +1289,14 @@ extern "C" fn then_cap_reject_fn( let func = if threw { cap_reject } else { cap_resolve }; let args = [result]; - let _ = unsafe { js_native_call_value(func, args.as_ptr(), 1) }; + let _ = unsafe { + js_native_call_value( + func, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + }; undef } @@ -1377,7 +1405,7 @@ extern "C" fn spec_then_finally_fn( let result = if on_finally_cl.is_null() { undef } else { - crate::closure::js_closure_call0(on_finally_cl) + crate::closure::js_closure_call0(on_finally_cl, crate::closure::plain_call_receiver()) }; // Build valueThunk = () => value @@ -1414,7 +1442,7 @@ extern "C" fn spec_catch_finally_fn( let result = if on_finally_cl.is_null() { undef } else { - crate::closure::js_closure_call0(on_finally_cl) + crate::closure::js_closure_call0(on_finally_cl, crate::closure::plain_call_receiver()) }; // Build thrower = () => { throw reason; } @@ -1666,17 +1694,18 @@ fn finally_wrapper_common( // report 1, failing every finally test that asserts a zero-arg invocation. // (Armed in a C trampoline frame, #9305; the rejection below runs after // the trap is popped, as before.) - let ret = - match crate::exception::catch_js_throw(|| crate::closure::js_closure_call0(on_finally)) { - Ok(ret) => ret, - Err(exc) => { - // onFinally threw — reject `next` with the thrown value. - if !next.is_null() { - js_promise_reject(next, exc); - } - return undef; + let ret = match crate::exception::catch_js_throw(|| { + crate::closure::js_closure_call0(on_finally, crate::closure::plain_call_receiver()) + }) { + Ok(ret) => ret, + Err(exc) => { + // onFinally threw — reject `next` with the thrown value. + if !next.is_null() { + js_promise_reject(next, exc); } - }; + return undef; + } + }; // If onFinally returned a Promise/thenable, adopt it: wait for it before // settling `next`. `js_assimilate_thenable` returns a native Promise for diff --git a/crates/perry-runtime/src/proxy.rs b/crates/perry-runtime/src/proxy.rs index ea2fda15aa..ce5afe4863 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -718,11 +718,24 @@ fn call_trap(handler: f64, trap: f64, args: &[f64]) -> f64 { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(handler)); let result = match args.len() { - 0 => js_closure_call0(closure), - 1 => js_closure_call1(closure, a(0)), - 2 => js_closure_call2(closure, a(0), a(1)), - 3 => js_closure_call3(closure, a(0), a(1), a(2)), - _ => crate::closure::js_closure_call4(closure, a(0), a(1), a(2), a(3)), + 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), + 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), + 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), + 3 => js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + ), + _ => crate::closure::js_closure_call4( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + ), }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result @@ -1044,17 +1057,35 @@ fn dispatch_with_explicit_this(f: f64, this_arg: f64, args: &[f64]) -> f64 { .unwrap_or(f64::from_bits(TAG_UNDEFINED)) }; let result = match args.len() { - 0 => js_closure_call0(closure), - 1 => js_closure_call1(closure, a(0)), - 2 => js_closure_call2(closure, a(0), a(1)), - 3 => js_closure_call3(closure, a(0), a(1), a(2)), - 4 => crate::closure::js_closure_call4(closure, a(0), a(1), a(2), a(3)), + 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), + 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), + 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), + 3 => js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + ), + 4 => crate::closure::js_closure_call4( + closure, + crate::closure::plain_call_receiver(), + a(0), + a(1), + a(2), + a(3), + ), // #10425: this arm was `_ => js_closure_call4(…)`, so every argument // after the fourth was dropped. The variadic entry point owns // arbitrary-arity dispatch, rest bundling included. (`call_trap` // above keeps its catch-all: a proxy trap receives at most four.) n => unsafe { - crate::closure::js_closure_call_array(closure as i64, args.as_ptr(), n as i64) + crate::closure::js_closure_call_array( + closure as i64, + crate::closure::plain_call_receiver(), + args.as_ptr(), + n as i64, + ) }, }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); @@ -1704,7 +1735,7 @@ fn call_setter_with_receiver(setter_bits: u64, receiver: f64, value: f64) -> boo } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let _ = js_closure_call1(closure, value); + let _ = js_closure_call1(closure, crate::closure::plain_call_receiver(), value); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); true } diff --git a/crates/perry-runtime/src/proxy/apply_construct.rs b/crates/perry-runtime/src/proxy/apply_construct.rs index 866aeb6604..d2c8695c61 100644 --- a/crates/perry-runtime/src/proxy/apply_construct.rs +++ b/crates/perry-runtime/src/proxy/apply_construct.rs @@ -127,7 +127,12 @@ fn forward_apply(target: f64, this_arg: f64, args_array: f64) -> f64 { // Keep the native argument owner above the trap. A JS longjmp would // otherwise bypass Vec::drop on every throwing forwarded proxy call. let result = crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_native_call_value(target.get_nanbox_f64(), ptr, n) + crate::closure::js_native_call_value( + target.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + ptr, + n, + ) }); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); drop(buf); @@ -251,6 +256,7 @@ pub extern "C" fn js_proxy_apply(proxy_boxed: f64, this_arg: f64, args_array: f6 let result = crate::exception::catch_js_throw(|| { js_closure_call3( closure, + crate::closure::plain_call_receiver(), target.get_nanbox_f64(), this_arg.get_nanbox_f64(), args_array.get_nanbox_f64(), @@ -372,6 +378,7 @@ pub extern "C" fn js_proxy_construct(proxy_boxed: f64, args_array: f64, new_targ let result = crate::exception::catch_js_throw(|| { js_closure_call3( closure, + crate::closure::plain_call_receiver(), target.get_nanbox_f64(), args_array.get_nanbox_f64(), nt.get_nanbox_f64(), diff --git a/crates/perry-runtime/src/proxy/json.rs b/crates/perry-runtime/src/proxy/json.rs index ea29a912bc..eec74e1cb9 100644 --- a/crates/perry-runtime/src/proxy/json.rs +++ b/crates/perry-runtime/src/proxy/json.rs @@ -64,7 +64,11 @@ pub(crate) fn js_proxy_own_keys_for_json(proxy_boxed: f64) -> f64 { } let trap = handler_trap(handler, "ownKeys"); if is_callable(trap) { - return crate::closure::js_closure_call1(closure_from(trap), target); + return crate::closure::js_closure_call1( + closure_from(trap), + crate::closure::plain_call_receiver(), + target, + ); } if lookup(target).is_some() { return js_proxy_own_keys_for_json(target); diff --git a/crates/perry-runtime/src/proxy/reflect.rs b/crates/perry-runtime/src/proxy/reflect.rs index c61a1724ec..4dbce4e54e 100644 --- a/crates/perry-runtime/src/proxy/reflect.rs +++ b/crates/perry-runtime/src/proxy/reflect.rs @@ -85,7 +85,7 @@ pub extern "C" fn js_reflect_get(target: f64, key: f64, receiver: f64) -> f64 { receiver_handle.get_nanbox_f64() }; let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = js_closure_call0(closure); + let result = js_closure_call0(closure, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return result; } @@ -450,6 +450,7 @@ pub extern "C" fn js_reflect_get_own_property_descriptor(target: f64, key: f64) )); let result = js_closure_call2( closure, + crate::closure::plain_call_receiver(), inner_handle.get_nanbox_f64(), property_key_handle.get_nanbox_f64(), ); diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index 5d73a37808..cba24827bd 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -100,6 +100,7 @@ mod platform_impl { unsafe { let _ = js_native_call_value( cb.get_nanbox_f64(), + crate::closure::plain_call_receiver(), current_args.as_ptr(), current_args.len(), ); @@ -267,8 +268,12 @@ mod platform_impl { closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - pty_method_resume(closure, crate::closure::JsThis::current()); - pty_method_kill(closure, crate::closure::JsThis::current(), cp_undefined()) + pty_method_resume(closure, crate::closure::body_call::current_this()); + pty_method_kill( + closure, + crate::closure::body_call::current_this(), + cp_undefined(), + ) } /// `kill([signal])` — node-pty defaults to `SIGHUP` (a hangup is how a diff --git a/crates/perry-runtime/src/pty/tests.rs b/crates/perry-runtime/src/pty/tests.rs index 56950891db..fa6f6c9360 100644 --- a/crates/perry-runtime/src/pty/tests.rs +++ b/crates/perry-runtime/src/pty/tests.rs @@ -47,7 +47,12 @@ fn callback(f: crate::closure::body_call::js_body_fn_ty!(a)) -> f64 { fn call(target: f64, name: &[u8], args: &[f64]) -> f64 { unsafe { - crate::closure::js_native_call_value(cp_get_field(target, name), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + cp_get_field(target, name), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) } } @@ -99,7 +104,12 @@ fn aliases_share_native_spawn_and_paused_output_precedes_exit() { opts.get_nanbox_f64(), ]; let term = scope.root_nanbox_f64(unsafe { - crate::closure::js_native_call_value(spawn.get_nanbox_f64(), args.as_ptr(), args.len()) + crate::closure::js_native_call_value( + spawn.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }); assert!(cp_get_field(term.get_nanbox_f64(), b"pid") > 0.0); let handle = pty_handle_of(term.get_nanbox_f64()).unwrap(); diff --git a/crates/perry-runtime/src/regex/perex_dispatch.rs b/crates/perry-runtime/src/regex/perex_dispatch.rs index f22949f98c..8f1e755a6f 100644 --- a/crates/perry-runtime/src/regex/perex_dispatch.rs +++ b/crates/perry-runtime/src/regex/perex_dispatch.rs @@ -86,7 +86,12 @@ pub(crate) fn call_one( } else { let args = [argument.get_nanbox_f64()]; unsafe { - crate::closure::js_native_call_value(method.get_nanbox_f64(), args.as_ptr(), 1) + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) } } }); diff --git a/crates/perry-runtime/src/regex/perex_replace_storage.rs b/crates/perry-runtime/src/regex/perex_replace_storage.rs index 6b466c0344..af8c222ff4 100644 --- a/crates/perry-runtime/src/regex/perex_replace_storage.rs +++ b/crates/perry-runtime/src/regex/perex_replace_storage.rs @@ -149,6 +149,7 @@ pub(super) fn call( crate::object::js_implicit_this_set(receiver.get_nanbox_f64()); crate::closure::js_native_call_value( method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), slots.as_ptr().cast(), slots.len(), ) @@ -234,6 +235,7 @@ pub(super) fn call_native( crate::object::js_implicit_this_set(receiver.get_nanbox_f64()); crate::closure::js_native_call_value( method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), slots.as_ptr().cast(), slots.len(), ) diff --git a/crates/perry-runtime/src/regex/replace_fn.rs b/crates/perry-runtime/src/regex/replace_fn.rs index 786e93c0db..a4700e0cd2 100644 --- a/crates/perry-runtime/src/regex/replace_fn.rs +++ b/crates/perry-runtime/src/regex/replace_fn.rs @@ -9,7 +9,12 @@ pub(super) unsafe fn call_replace_callback(callback: f64, args: &[f64]) -> Strin let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(f64::from_bits( crate::value::TAG_UNDEFINED, ))); - let ret = crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()); + let ret = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); // §22.1.3.19 step "Let replacement be ? ToString(? Call(replaceValue, …))": // the callback result is ToString-coerced — `undefined` renders as diff --git a/crates/perry-runtime/src/regex/site_test.rs b/crates/perry-runtime/src/regex/site_test.rs index c1e709385f..8d98e75250 100644 --- a/crates/perry-runtime/src/regex/site_test.rs +++ b/crates/perry-runtime/src/regex/site_test.rs @@ -323,7 +323,12 @@ fn call_value_at_site(site_key: usize, callee: f64, this_value: Option) -> .map(|value| crate::object::ImplicitThisScope::bind(&scope, value.get_nanbox_f64())); let active = ActiveFactoryGuard::push(site_key, expected_identity); let result = unsafe { - crate::closure::js_native_call_value(callee.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + callee.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; if !active.handled() { let reason = if lookup(site_key).is_some() { @@ -476,7 +481,14 @@ fn site_test_dispatch_impl(receiver: f64, method: f64, argument: f64) -> f64 { let method = scope.root_nanbox_f64(method); let _this_guard = crate::object::ImplicitThisScope::bind(&scope, receiver.get_nanbox_f64()); let args = [argument.get_nanbox_f64()]; - unsafe { crate::closure::js_native_call_value(method.get_nanbox_f64(), args.as_ptr(), 1) } + unsafe { + crate::closure::js_native_call_value( + method.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + 1, + ) + } } /// Strong root for every cached header. The visitor rewrites entries in @@ -675,7 +687,7 @@ mod tests { // Keep this observably distinct from `exact_factory` under release // function merging while modeling a non-literal wrapper with effects. NESTED_WRAPPER_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - exact_factory(std::ptr::null(), crate::closure::JsThis::current()) + exact_factory(std::ptr::null(), crate::closure::body_call::current_this()) } fn closure(function: *const u8) -> f64 { diff --git a/crates/perry-runtime/src/set.rs b/crates/perry-runtime/src/set.rs index cd26fbd539..e5c931052a 100644 --- a/crates/perry-runtime/src/set.rs +++ b/crates/perry-runtime/src/set.rs @@ -2141,7 +2141,12 @@ fn js_set_foreach_impl( let cb = callback_handle.get_nanbox_f64(); let this_v = this_handle.get_nanbox_f64(); crate::object::js_implicit_this_set(this_v); - let _ = crate::closure::js_native_call_value(cb, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } diff --git a/crates/perry-runtime/src/symbol/accessors.rs b/crates/perry-runtime/src/symbol/accessors.rs index 6fe61d1690..4d8ace6d90 100644 --- a/crates/perry-runtime/src/symbol/accessors.rs +++ b/crates/perry-runtime/src/symbol/accessors.rs @@ -282,7 +282,11 @@ pub(super) unsafe fn invoke_symbol_accessor_setter( let scope = crate::gc::RuntimeHandleScope::new(); let value_h = scope.root_nanbox_f64(value); let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - crate::closure::js_closure_call1(closure, value_h.get_nanbox_f64()); + crate::closure::js_closure_call1( + closure, + crate::closure::plain_call_receiver(), + value_h.get_nanbox_f64(), + ); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); value_h.get_nanbox_f64() } @@ -297,7 +301,7 @@ pub(super) unsafe fn invoke_symbol_accessor_getter(get_bits: u64, receiver: f64) } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure); + let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/symbol/iterator.rs b/crates/perry-runtime/src/symbol/iterator.rs index 0ca5f2d9a1..6babfa587f 100644 --- a/crates/perry-runtime/src/symbol/iterator.rs +++ b/crates/perry-runtime/src/symbol/iterator.rs @@ -267,7 +267,10 @@ pub extern "C" fn js_get_iterator(val_f64: f64) -> f64 { let rebound = crate::closure::clone_closure_rebind_this(iter_fn.to_bits(), val_f64); let rebound_ptr = crate::value::js_nanbox_get_pointer(f64::from_bits(rebound)) as *const crate::closure::ClosureHeader; - let iter = crate::closure::js_closure_call0(rebound_ptr); + let iter = crate::closure::js_closure_call0( + rebound_ptr, + crate::closure::plain_call_receiver(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if !is_object_value(iter) { throw_iterator_result_not_object(); @@ -458,7 +461,8 @@ pub extern "C" fn js_get_iterator(val_f64: f64) -> f64 { let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(val_f64)); - let iter = crate::closure::js_closure_call0(fn_ptr); + let iter = + crate::closure::js_closure_call0(fn_ptr, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // Several Perry host-backed collections expose iterator // helpers as eager arrays for direct `.entries()` parity. When @@ -660,7 +664,11 @@ pub unsafe extern "C" fn js_to_primitive(value: f64, hint: i32) -> f64 { // Spec says the return value must be a primitive; if it's still an // object pointer, that's a TypeError in JS, but we just return it // as-is and let the caller fall back. - let result = crate::closure::js_closure_call1(closure_ptr, hint_f64); + let result = crate::closure::js_closure_call1( + closure_ptr, + crate::closure::plain_call_receiver(), + hint_f64, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/thread.rs b/crates/perry-runtime/src/thread.rs index a84ede5248..65a848beae 100644 --- a/crates/perry-runtime/src/thread.rs +++ b/crates/perry-runtime/src/thread.rs @@ -1210,7 +1210,11 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn(local_closure, crate::closure::JsThis::current(), arg); + let result = call_fn( + local_closure, + crate::closure::body_call::current_this(), + arg, + ); results.push(serialize_nanbox_for_thread(result.to_bits())); } @@ -1295,7 +1299,7 @@ unsafe fn single_thread_map( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, crate::closure::JsThis::current(), arg); + let result = call_fn(closure, crate::closure::body_call::current_this(), arg); let result_arr = result_handle.get_raw_mut_ptr::(); // GC_STORE_AUDIT(BARRIERED): single-thread map result slot uses the shared array slot-store helper. store_thread_array_slot(result_arr, i, result.to_bits()); @@ -1463,7 +1467,11 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn(local_closure, crate::closure::JsThis::current(), arg); + let result = call_fn( + local_closure, + crate::closure::body_call::current_this(), + arg, + ); let keep = is_truthy_bits(result.to_bits()); if keep { kept.push(serialize_nanbox_for_thread(arg.to_bits())); @@ -1543,7 +1551,7 @@ unsafe fn single_thread_filter( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, crate::closure::JsThis::current(), arg); + let result = call_fn(closure, crate::closure::body_call::current_this(), arg); let keep = is_truthy_bits(result.to_bits()); if keep { let result_arr = result_handle.get_raw_mut_ptr::(); @@ -1684,7 +1692,7 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { unsafe { crate::closure::body_call::js_body_fn!(func_usize as *const u8;) }; let local_closure = closure_handle.get_raw_mut_ptr::() as *const ClosureHeader; - call_fn(local_closure, crate::closure::JsThis::current()) + call_fn(local_closure, crate::closure::body_call::current_this()) })); match call_result { diff --git a/crates/perry-runtime/src/timer.rs b/crates/perry-runtime/src/timer.rs index aa1c918b49..d501d83e07 100644 --- a/crates/perry-runtime/src/timer.rs +++ b/crates/perry-runtime/src/timer.rs @@ -470,7 +470,12 @@ fn call_timer_callback( let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(timer_handle_value(id))); with_timer_uncaught_trap(|| unsafe { - crate::closure::js_closure_call_array(cb as i64, a.as_ptr(), a.len() as i64); + crate::closure::js_closure_call_array( + cb as i64, + crate::closure::plain_call_receiver(), + a.as_ptr(), + a.len() as i64, + ); }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); crate::async_context::refresh_snapshot_from_roots(&mut previous, &previous_roots); @@ -507,7 +512,12 @@ fn call_timer_callback_entry( let a = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); let cb = callback_handle.get_raw_const_ptr::(); unsafe { - crate::closure::js_closure_call_array(cb as i64, a.as_ptr(), a.len() as i64); + crate::closure::js_closure_call_array( + cb as i64, + crate::closure::plain_call_receiver(), + a.as_ptr(), + a.len() as i64, + ); } }); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/tls.rs b/crates/perry-runtime/src/tls.rs index 85af04c95a..04b6e7a81e 100644 --- a/crates/perry-runtime/src/tls.rs +++ b/crates/perry-runtime/src/tls.rs @@ -579,6 +579,7 @@ pub extern "C" fn js_tls_client_check_identity(handle: i64, certificate: f64) -> let host = string_value(metadata.servername.as_deref().unwrap_or_default()); crate::closure::js_closure_call2( metadata.check_server_identity as *const crate::ClosureHeader, + crate::closure::plain_call_receiver(), host, certificate, ) @@ -1087,7 +1088,11 @@ pub extern "C" fn js_tls_prepare_connect() { let callback = (create_context.to_bits() & crate::value::POINTER_MASK) as *const crate::ClosureHeader; if !callback.is_null() { - crate::closure::js_closure_call1(callback, ptr_value(options)); + crate::closure::js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + ptr_value(options), + ); } } diff --git a/crates/perry-runtime/src/tty.rs b/crates/perry-runtime/src/tty.rs index 01d96dfe8b..1464800312 100644 --- a/crates/perry-runtime/src/tty.rs +++ b/crates/perry-runtime/src/tty.rs @@ -1262,7 +1262,7 @@ pub extern "C" fn js_tty_resize_drain() -> i32 { let cb = RESIZE_CALLBACK.with(|c| *c.borrow()); if let Some(cb_i64) = cb { let closure = cb_i64 as *const ClosureHeader; - js_closure_call0(closure); + js_closure_call0(closure, crate::closure::plain_call_receiver()); return 1; } 0 diff --git a/crates/perry-runtime/src/tui/hooks.rs b/crates/perry-runtime/src/tui/hooks.rs index b2625ec3de..712def873b 100644 --- a/crates/perry-runtime/src/tui/hooks.rs +++ b/crates/perry-runtime/src/tui/hooks.rs @@ -529,7 +529,10 @@ pub extern "C" fn js_perry_tui_use_effect(fn_closure: i64, deps_array: i64) -> f // but possible) doesn't deadlock. drop(s); if fn_closure != 0 { - js_closure_call0(fn_closure as *const ClosureHeader); + js_closure_call0( + fn_closure as *const ClosureHeader, + crate::closure::plain_call_receiver(), + ); } } f64::from_bits(TAG_UNDEFINED) @@ -601,7 +604,10 @@ pub extern "C" fn js_perry_tui_use_memo(fn_closure: i64, deps_array: i64) -> f64 if should_compute { drop(s); let value = if fn_closure != 0 { - js_closure_call0(fn_closure as *const ClosureHeader) + js_closure_call0( + fn_closure as *const ClosureHeader, + crate::closure::plain_call_receiver(), + ) } else { f64::from_bits(TAG_UNDEFINED) }; diff --git a/crates/perry-runtime/src/tui/input.rs b/crates/perry-runtime/src/tui/input.rs index 7300eaba0a..ea9c41a7a6 100644 --- a/crates/perry-runtime/src/tui/input.rs +++ b/crates/perry-runtime/src/tui/input.rs @@ -273,7 +273,7 @@ pub fn drain_input() -> i32 { let s_ptr = js_string_from_bytes(chunk.as_ptr(), chunk.len() as u32); let arg = f64::from_bits(JSValue::string_ptr(s_ptr).bits()); let closure = handler as *const ClosureHeader; - js_closure_call1(closure, arg); + js_closure_call1(closure, crate::closure::plain_call_receiver(), arg); } count += 1; i = chunk_end; diff --git a/crates/perry-runtime/src/tui/run.rs b/crates/perry-runtime/src/tui/run.rs index 1a5efcc52d..6b6cf9a173 100644 --- a/crates/perry-runtime/src/tui/run.rs +++ b/crates/perry-runtime/src/tui/run.rs @@ -62,7 +62,7 @@ pub extern "C" fn js_perry_tui_run(component: i64) -> f64 { super::hooks::reset_hook_index(); // Call the component to get a fresh widget tree. - let widget_v = js_closure_call0(component_closure); + let widget_v = js_closure_call0(component_closure, crate::closure::plain_call_receiver()); // #340/#341: the component returns a widget handle OBJECT, so resolve // it through the brand instead of masking the tag off whatever came // back. A component that returns a number or a string now paints diff --git a/crates/perry-runtime/src/typed_feedback/tests.rs b/crates/perry-runtime/src/typed_feedback/tests.rs index 1e1daa95b3..d84f98cd23 100644 --- a/crates/perry-runtime/src/typed_feedback/tests.rs +++ b/crates/perry-runtime/src/typed_feedback/tests.rs @@ -3001,7 +3001,14 @@ fn function_source_array_literal_keeps_the_array_index_fast_path_armed() { .map(|s| s.to_string()) .collect(); let f = crate::dyn_eval::dyn_function_from_strings(&source); - let result = unsafe { crate::closure::js_native_call_value(f, [].as_ptr(), 0) }; + let result = unsafe { + crate::closure::js_native_call_value( + f, + crate::closure::plain_call_receiver(), + [].as_ptr(), + 0, + ) + }; let result = crate::value::JSValue::from_bits(result.to_bits()); assert_eq!( if result.is_int32() { diff --git a/crates/perry-runtime/src/typedarray/construct.rs b/crates/perry-runtime/src/typedarray/construct.rs index 7492afcfa6..68dc086bd8 100644 --- a/crates/perry-runtime/src/typedarray/construct.rs +++ b/crates/perry-runtime/src/typedarray/construct.rs @@ -224,8 +224,12 @@ unsafe fn typed_array_plain_object_values(val: f64) -> Vec { rooted.get_nanbox_f64(), ); let bound_rooted = scope.root_nanbox_u64(bound); - let iter = - crate::closure::js_native_call_value(bound_rooted.get_nanbox_f64(), ptr::null(), 0); + let iter = crate::closure::js_native_call_value( + bound_rooted.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + ptr::null(), + 0, + ); let iter_rooted = scope.root_nanbox_f64(iter); let mut raw = Vec::new(); while let Some(v) = diff --git a/crates/perry-runtime/src/typedarray/iterate.rs b/crates/perry-runtime/src/typedarray/iterate.rs index af0ab949d6..39e97fb2b6 100644 --- a/crates/perry-runtime/src/typedarray/iterate.rs +++ b/crates/perry-runtime/src/typedarray/iterate.rs @@ -43,7 +43,13 @@ pub extern "C" fn js_typed_array_map( }; for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); crate::typedarray_props::species_result_store(result_addr, i, r); } species::result_as_ptr(result) @@ -75,7 +81,13 @@ pub extern "C" fn js_typed_array_filter( let mut kept: Vec = Vec::new(); for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { kept.push(v); } @@ -111,7 +123,13 @@ pub extern "C" fn js_typed_array_every( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) == 0 { return f64::from_bits(crate::value::TAG_FALSE); } @@ -139,7 +157,13 @@ pub extern "C" fn js_typed_array_some( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { return f64::from_bits(crate::value::TAG_TRUE); } @@ -165,7 +189,13 @@ pub extern "C" fn js_typed_array_for_each( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in 0..len { let v = load_at(ta, i); - let _ = cb_site.call(callback, v, i as f64, recv); + let _ = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); } } } @@ -191,7 +221,13 @@ pub extern "C" fn js_typed_array_find( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { return v; } @@ -219,7 +255,13 @@ pub extern "C" fn js_typed_array_find_index( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in 0..len { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { return i as f64; } @@ -284,7 +326,14 @@ pub extern "C" fn js_typed_array_reduce( let acc = scope.root_nanbox_f64(accumulator); for i in start_idx..len { let v = load_at(ta, i); - let next = cb_site.call(callback, acc.get_nanbox_f64(), v, i as f64, recv); + let next = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + acc.get_nanbox_f64(), + v, + i as f64, + recv, + ); acc.set_nanbox_f64(next); } acc.get_nanbox_f64() @@ -345,7 +394,14 @@ pub extern "C" fn js_typed_array_reduce_right( if start_idx > 0 { for i in (0..start_idx).rev() { let v = load_at(ta, i); - let next = cb_site.call(callback, acc.get_nanbox_f64(), v, i as f64, recv); + let next = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + acc.get_nanbox_f64(), + v, + i as f64, + recv, + ); acc.set_nanbox_f64(next); } } diff --git a/crates/perry-runtime/src/typedarray/species.rs b/crates/perry-runtime/src/typedarray/species.rs index f52723635a..2e687fa42d 100644 --- a/crates/perry-runtime/src/typedarray/species.rs +++ b/crates/perry-runtime/src/typedarray/species.rs @@ -112,6 +112,7 @@ pub(crate) unsafe fn prototype_constructor_patch(kind: u8, owner: usize) -> Opti let bound = crate::closure::clone_closure_rebind_this(desc.get, owner_value); return Some(crate::closure::js_native_call_value( f64::from_bits(bound), + crate::closure::plain_call_receiver(), std::ptr::null(), 0, )); diff --git a/crates/perry-runtime/src/typedarray/transform.rs b/crates/perry-runtime/src/typedarray/transform.rs index 6c6f168dfe..39580a5257 100644 --- a/crates/perry-runtime/src/typedarray/transform.rs +++ b/crates/perry-runtime/src/typedarray/transform.rs @@ -134,6 +134,7 @@ unsafe fn bigint_lane_compare( let b_handle = scope.root_nanbox_f64(box_lane(b_bits)); let r = site.call( comparator, + crate::closure::plain_call_receiver(), a_handle.get_nanbox_f64(), b_handle.get_nanbox_f64(), ); @@ -233,7 +234,9 @@ pub extern "C" fn js_typed_array_sort_with_comparator( // `with_const_ptr`: an argument-position read, which `across_*` // cannot express. The handle is re-read per comparison, so a // relocation during comparison `i` is reflected in `i + 1`. - let r = cmp_handle.with_const_ptr::(|cmp| cmp_site.call(cmp, *a, *b)); + let r = cmp_handle.with_const_ptr::(|cmp| { + cmp_site.call(cmp, crate::closure::plain_call_receiver(), *a, *b) + }); if r < 0.0 { std::cmp::Ordering::Less } else if r > 0.0 { @@ -322,7 +325,9 @@ pub extern "C" fn js_typed_array_to_sorted_with_comparator( // `with_const_ptr`: an argument-position read, which `across_*` // cannot express. The handle is re-read per comparison, so a // relocation during comparison `i` is reflected in `i + 1`. - let r = cmp_handle.with_const_ptr::(|cmp| cmp_site.call(cmp, *a, *b)); + let r = cmp_handle.with_const_ptr::(|cmp| { + cmp_site.call(cmp, crate::closure::plain_call_receiver(), *a, *b) + }); if r < 0.0 { std::cmp::Ordering::Less } else if r > 0.0 { @@ -397,7 +402,13 @@ pub extern "C" fn js_typed_array_find_last( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in (0..len).rev() { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { return v; } @@ -425,7 +436,13 @@ pub extern "C" fn js_typed_array_find_last_index( let cb_site = crate::closure::DirectCall3::resolve(callback); for i in (0..len).rev() { let v = load_at(ta, i); - let r = cb_site.call(callback, v, i as f64, recv); + let r = cb_site.call( + callback, + crate::closure::plain_call_receiver(), + v, + i as f64, + recv, + ); if crate::value::js_is_truthy(r) != 0 { return i as f64; } diff --git a/crates/perry-runtime/src/typedarray_props.rs b/crates/perry-runtime/src/typedarray_props.rs index 45d9638740..18e3ef7615 100644 --- a/crates/perry-runtime/src/typedarray_props.rs +++ b/crates/perry-runtime/src/typedarray_props.rs @@ -238,7 +238,7 @@ fn invoke_typed_array_accessor_getter(get_bits: u64, receiver: f64) -> f64 { } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure); + let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result } @@ -250,7 +250,7 @@ fn invoke_typed_array_accessor_setter(set_bits: u64, receiver: f64, value: f64) } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - crate::closure::js_closure_call1(closure, value); + crate::closure::js_closure_call1(closure, crate::closure::plain_call_receiver(), value); crate::object::js_implicit_this_set(prev.get_nanbox_f64()); } @@ -1139,7 +1139,12 @@ pub(crate) unsafe fn typed_array_own_to_primitive_number(owner: usize, value: f6 continue; } let bound = crate::closure::clone_closure_rebind_this(mbits, value); - let r = crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let r = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); let rb = r.to_bits(); let is_object = (rb >> 48) == 0x7FFD && crate::symbol::js_is_symbol(r) == 0 diff --git a/crates/perry-runtime/src/url/abort.rs b/crates/perry-runtime/src/url/abort.rs index 8500413d1d..25c4465148 100644 --- a/crates/perry-runtime/src/url/abort.rs +++ b/crates/perry-runtime/src/url/abort.rs @@ -202,7 +202,7 @@ fn fire_abort_listeners(signal: *mut ObjectHeader) { continue; }; if !cb_ptr.is_null() { - crate::closure::js_closure_call0(cb_ptr); + crate::closure::js_closure_call0(cb_ptr, crate::closure::plain_call_receiver()); } } } diff --git a/crates/perry-runtime/src/url/search_params.rs b/crates/perry-runtime/src/url/search_params.rs index 909be365e9..b5ddcc62ff 100644 --- a/crates/perry-runtime/src/url/search_params.rs +++ b/crates/perry-runtime/src/url/search_params.rs @@ -939,7 +939,12 @@ pub extern "C" fn js_url_search_params_for_each( ]; unsafe { crate::object::js_implicit_this_set(this_arg); - let _ = crate::closure::js_native_call_value(callback, args.as_ptr(), args.len()); + let _ = crate::closure::js_native_call_value( + callback, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } diff --git a/crates/perry-runtime/src/util_debuglog.rs b/crates/perry-runtime/src/util_debuglog.rs index f3ad5151a7..fc1f082bb1 100644 --- a/crates/perry-runtime/src/util_debuglog.rs +++ b/crates/perry-runtime/src/util_debuglog.rs @@ -165,7 +165,11 @@ fn call_enabled_callback_once(scope: &crate::gc::RuntimeHandleScope, closure: *m let logger_value = nanbox_closure(closure); let logger_handle = scope.root_nanbox_f64(logger_value); - js_closure_call1(callback_ptr, logger_handle.get_nanbox_f64()); + js_closure_call1( + callback_ptr, + crate::closure::plain_call_receiver(), + logger_handle.get_nanbox_f64(), + ); } fn write_stderr_line(line: &str) { @@ -186,7 +190,11 @@ fn write_stderr_line(line: &str) { ); let write_f64 = f64::from_bits(write_value.bits()); if let Some(write_ptr) = closure_ptr(write_f64) { - js_closure_call1(write_ptr, line_handle.get_nanbox_f64()); + js_closure_call1( + write_ptr, + crate::closure::plain_call_receiver(), + line_handle.get_nanbox_f64(), + ); return; } } diff --git a/crates/perry-runtime/src/util_promisify.rs b/crates/perry-runtime/src/util_promisify.rs index 31aa4112b3..d5df3ad0b6 100644 --- a/crates/perry-runtime/src/util_promisify.rs +++ b/crates/perry-runtime/src/util_promisify.rs @@ -404,7 +404,12 @@ extern "C" fn outer_thunk( }; let n = js_array_length(arr) as usize; unsafe { - crate::closure::js_native_call_value(fn_handle.get_nanbox_f64(), data, n); + crate::closure::js_native_call_value( + fn_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + data, + n, + ); } } else { // #7341: `js_get_exception` can allocate, so pair it with the @@ -533,7 +538,12 @@ extern "C" fn gkp_outer_thunk( }; let n = js_array_length(arr) as usize; unsafe { - crate::closure::js_native_call_value(fn_handle.get_nanbox_f64(), data, n); + crate::closure::js_native_call_value( + fn_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + data, + n, + ); } } else { // #7341: `js_get_exception` can allocate, so pair it with the @@ -665,7 +675,14 @@ extern "C" fn deprecate_outer_thunk( } }; - unsafe { crate::closure::js_native_call_value(fn_handle.get_nanbox_f64(), rest_data, rest_len) } + unsafe { + crate::closure::js_native_call_value( + fn_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + rest_data, + rest_len, + ) + } } /// Outer callbackify body: `(closure, rest_array_value) -> undefined`. @@ -724,7 +741,12 @@ extern "C" fn callbackify_outer_thunk( let returned = unsafe { let arr = original_args_handle.get_raw_const_ptr::(); let data = crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64; - crate::closure::js_native_call_value(fn_handle.get_nanbox_f64(), data, original_arg_len) + crate::closure::js_native_call_value( + fn_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + data, + original_arg_len, + ) }; // #9539: `returned` is a heap value that outlives two closure allocations, // an interned-key lookup and `js_assimilate_thenable` (which runs the @@ -800,6 +822,7 @@ extern "C" fn callbackify_outer_thunk( unsafe { crate::closure::js_native_call_value( then_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -874,7 +897,12 @@ extern "C" fn callbackify_fulfilled_thunk( let callback_value = js_closure_get_capture_f64(closure, 0); let args = [TAG_NULL_F64, value]; unsafe { - crate::closure::js_native_call_value(callback_value, args.as_ptr(), args.len()); + crate::closure::js_native_call_value( + callback_value, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } TAG_UNDEFINED_F64 } @@ -900,7 +928,12 @@ fn call_callback_rejected(callback_value: f64, reason: f64) { }; let args = [err]; unsafe { - crate::closure::js_native_call_value(callback_value, args.as_ptr(), args.len()); + crate::closure::js_native_call_value( + callback_value, + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } diff --git a/crates/perry-runtime/src/v8.rs b/crates/perry-runtime/src/v8.rs index a34a9c34c5..2ec5cd165a 100644 --- a/crates/perry-runtime/src/v8.rs +++ b/crates/perry-runtime/src/v8.rs @@ -299,7 +299,12 @@ pub(crate) fn promise_hook_init(promise: *mut Promise, parent: *mut Promise) { } else { promise_value(parent_ptr) }; - js_closure_call2(callback_handle.get_raw_const_ptr(), promise_arg, parent_arg); + js_closure_call2( + callback_handle.get_raw_const_ptr(), + crate::closure::plain_call_receiver(), + promise_arg, + parent_arg, + ); }); } @@ -316,6 +321,7 @@ pub(crate) fn promise_hook_before(promise: *mut Promise) { let callback_handle = scope.root_raw_const_ptr(callbacks.before); js_closure_call1( callback_handle.get_raw_const_ptr(), + crate::closure::plain_call_receiver(), promise_value(promise_handle.get_raw_mut_ptr::()), ); }); @@ -334,6 +340,7 @@ pub(crate) fn promise_hook_after(promise: *mut Promise) { let callback_handle = scope.root_raw_const_ptr(callbacks.after); js_closure_call1( callback_handle.get_raw_const_ptr(), + crate::closure::plain_call_receiver(), promise_value(promise_handle.get_raw_mut_ptr::()), ); }); @@ -352,6 +359,7 @@ pub(crate) fn promise_hook_settled(promise: *mut Promise) { let callback_handle = scope.root_raw_const_ptr(callbacks.settled); js_closure_call1( callback_handle.get_raw_const_ptr(), + crate::closure::plain_call_receiver(), promise_value(promise_handle.get_raw_mut_ptr::()), ); }); diff --git a/crates/perry-runtime/src/value/dynamic_array.rs b/crates/perry-runtime/src/value/dynamic_array.rs index 68009c7351..1392300def 100644 --- a/crates/perry-runtime/src/value/dynamic_array.rs +++ b/crates/perry-runtime/src/value/dynamic_array.rs @@ -113,7 +113,11 @@ pub extern "C" fn js_dynamic_array_find( let length = js_dynamic_array_length(arr_value); for i in 0..length { let element = js_dynamic_array_get(arr_value, i); - let result = crate::closure::js_closure_call1(callback, element); + let result = crate::closure::js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + element, + ); // Proper truthy check: handles NaN-boxed booleans if js_is_truthy(result) != 0 { return element; @@ -148,7 +152,11 @@ pub extern "C" fn js_dynamic_array_findIndex( let length = js_dynamic_array_length(arr_value); for i in 0..length { let element = js_dynamic_array_get(arr_value, i); - let result = crate::closure::js_closure_call1(callback, element); + let result = crate::closure::js_closure_call1( + callback, + crate::closure::plain_call_receiver(), + element, + ); // Proper truthy check: handles NaN-boxed booleans if js_is_truthy(result) != 0 { return i as f64; diff --git a/crates/perry-runtime/src/value/to_string_array.rs b/crates/perry-runtime/src/value/to_string_array.rs index 3e40ba6e98..5345fd4867 100644 --- a/crates/perry-runtime/src/value/to_string_array.rs +++ b/crates/perry-runtime/src/value/to_string_array.rs @@ -85,8 +85,12 @@ pub(crate) unsafe fn array_prototype_to_string_override(value: f64) -> ArrayToSt let bound_handle = scope.root_nanbox_u64(bound); let prev_this = crate::object::js_implicit_this_set(receiver); let prev_this_handle = scope.root_nanbox_f64(prev_this); - let ret = - crate::closure::js_native_call_value(bound_handle.get_nanbox_f64(), std::ptr::null(), 0); + let ret = crate::closure::js_native_call_value( + bound_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); if is_primitive_value(ret) { ArrayToStringOutcome::Primitive(ret) @@ -151,6 +155,7 @@ pub(crate) fn call_array_prototype_to_string_method( let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(arg_handles); let result = crate::closure::js_native_call_value( rebound_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); @@ -189,7 +194,12 @@ pub(crate) fn array_prototype_to_string(value: f64) -> f64 { let previous = crate::object::js_implicit_this_set(receiver); let previous_handle = scope.root_nanbox_f64(previous); let result = unsafe { - crate::closure::js_native_call_value(rebound_handle.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + rebound_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; crate::object::js_implicit_this_set(previous_handle.get_nanbox_f64()); result diff --git a/crates/perry-runtime/src/value/to_string_class_ref.rs b/crates/perry-runtime/src/value/to_string_class_ref.rs index 005bc8336f..6d7a896f12 100644 --- a/crates/perry-runtime/src/value/to_string_class_ref.rs +++ b/crates/perry-runtime/src/value/to_string_class_ref.rs @@ -65,7 +65,12 @@ pub(crate) unsafe fn custom_to_primitive(value: f64, hint: &[u8]) -> CustomToPri } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value(method, &hint, 1); + let result = crate::closure::js_native_call_value( + method, + crate::closure::plain_call_receiver(), + &hint, + 1, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result }; diff --git a/crates/perry-runtime/src/value/to_string_primitive.rs b/crates/perry-runtime/src/value/to_string_primitive.rs index 88ba593ef0..6e6d3d738d 100644 --- a/crates/perry-runtime/src/value/to_string_primitive.rs +++ b/crates/perry-runtime/src/value/to_string_primitive.rs @@ -203,6 +203,7 @@ pub(crate) unsafe fn ordinary_to_primitive_for_toprimitive( crate::object::js_implicit_this_set(recv); let result = crate::closure::js_native_call_value( method_handle.get_nanbox_f64(), + crate::closure::plain_call_receiver(), std::ptr::null(), 0, ); @@ -461,7 +462,12 @@ pub(crate) unsafe fn call_own_method(method: f64, receiver: f64) -> Option let bound = crate::closure::clone_closure_rebind_this(bits, receiver); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let ret = crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let ret = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); Some(ret) } @@ -634,7 +640,12 @@ unsafe fn call_method_for_primitive( let recv = value_handle.get_nanbox_f64(); let bound = crate::closure::clone_closure_rebind_this(method_bits, recv); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let ret = crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let ret = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); let ret_jsv = JSValue::from_bits(ret.to_bits()); let is_primitive = ret_jsv.is_any_string() @@ -689,7 +700,12 @@ unsafe fn call_function_method( let method_handle = scope.root_nanbox_f64(method); let bound = crate::closure::clone_closure_rebind_this(method_handle.get_nanbox_u64(), recv); let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let ret = crate::closure::js_native_call_value(f64::from_bits(bound), std::ptr::null(), 0); + let ret = crate::closure::js_native_call_value( + f64::from_bits(bound), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); FunctionMethodOutcome::Value(ret) diff --git a/crates/perry-runtime/src/wasi.rs b/crates/perry-runtime/src/wasi.rs index b9b5767579..08cbb10b5a 100644 --- a/crates/perry-runtime/src/wasi.rs +++ b/crates/perry-runtime/src/wasi.rs @@ -807,7 +807,14 @@ pub extern "C" fn js_wasi_start( invalid_undefined_property("instance.exports._initialize", initialize) } WASI_EXIT_CODE.with(|slot| slot.set(None)); - unsafe { crate::closure::js_native_call_value(start.get_nanbox_f64(), std::ptr::null(), 0) }; + unsafe { + crate::closure::js_native_call_value( + start.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) + }; if let Some(code) = WASI_EXIT_CODE.with(|slot| slot.take()) { return code as f64; } @@ -854,7 +861,12 @@ pub extern "C" fn js_wasi_initialize( } if !is_undefined(initialize.get_nanbox_f64()) { unsafe { - crate::closure::js_native_call_value(initialize.get_nanbox_f64(), std::ptr::null(), 0) + crate::closure::js_native_call_value( + initialize.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ) }; } undefined() diff --git a/crates/perry-runtime/src/webassembly.rs b/crates/perry-runtime/src/webassembly.rs index 63452a83dd..39a8c4c2cd 100644 --- a/crates/perry-runtime/src/webassembly.rs +++ b/crates/perry-runtime/src/webassembly.rs @@ -961,8 +961,12 @@ unsafe extern "C" fn call_wasm_import( _ => f64::from_bits(TAG_UNDEFINED), }) .collect(); - let result = - crate::closure::js_native_call_value(callback.get_nanbox_f64(), args.as_ptr(), args.len()); + let result = crate::closure::js_native_call_value( + callback.get_nanbox_f64(), + crate::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); let result_kinds = if result_count == 0 { &[] diff --git a/crates/perry-runtime/src/webassembly_calls.rs b/crates/perry-runtime/src/webassembly_calls.rs index c18fc70e89..3d23bf1f5d 100644 --- a/crates/perry-runtime/src/webassembly_calls.rs +++ b/crates/perry-runtime/src/webassembly_calls.rs @@ -14,6 +14,7 @@ extern "C" fn js_wasm_instance_result_then( let outcome = crate::exception::catch_js_throw(|| unsafe { crate::closure::js_native_call_value( on_fulfilled.get_nanbox_f64(), + crate::closure::plain_call_receiver(), [value.get_nanbox_f64()].as_ptr(), 1, ) diff --git a/crates/perry-runtime/src/yoga.rs b/crates/perry-runtime/src/yoga.rs index f47e097b51..7f0edf7f2b 100644 --- a/crates/perry-runtime/src/yoga.rs +++ b/crates/perry-runtime/src/yoga.rs @@ -615,7 +615,14 @@ fn measure_leaf( let (h, hmode) = measure_axis(known_dims.height, available_space.height); let args = [w, wmode, h, hmode]; - let result = unsafe { crate::closure::js_native_call_value(cb, args.as_ptr(), 4) }; + let result = unsafe { + crate::closure::js_native_call_value( + cb, + crate::closure::plain_call_receiver(), + args.as_ptr(), + 4, + ) + }; // result is a NaN-boxed object {width, height}; read both numbers. let bits = result.to_bits(); diff --git a/crates/perry-stdlib/src/async_local_storage.rs b/crates/perry-stdlib/src/async_local_storage.rs index 2c482b0c51..c077e6241c 100644 --- a/crates/perry-stdlib/src/async_local_storage.rs +++ b/crates/perry-stdlib/src/async_local_storage.rs @@ -56,7 +56,12 @@ unsafe fn validate_callback(callback: f64) -> *const ClosureHeader { unsafe fn call_with_forwarded_args(cb: *const ClosureHeader, args_array: i64) -> f64 { let closure_env = cb as i64; if args_array == 0 { - return js_closure_call_array(closure_env, std::ptr::null(), 0); + return js_closure_call_array( + closure_env, + perry_runtime::closure::plain_call_receiver(), + std::ptr::null(), + 0, + ); } let arr = args_array as *const ArrayHeader; let len = js_array_length(arr) as i64; @@ -65,7 +70,12 @@ unsafe fn call_with_forwarded_args(cb: *const ClosureHeader, args_array: i64) -> } else { perry_runtime::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 }; - js_closure_call_array(closure_env, data, len) + js_closure_call_array( + closure_env, + perry_runtime::closure::plain_call_receiver(), + data, + len, + ) } /// AsyncLocalStorage handle. Store stacks live in perry-runtime's active diff --git a/crates/perry-stdlib/src/bun_sql.rs b/crates/perry-stdlib/src/bun_sql.rs index 44469ddc6e..bb2289b2f9 100644 --- a/crates/perry-stdlib/src/bun_sql.rs +++ b/crates/perry-stdlib/src/bun_sql.rs @@ -215,18 +215,23 @@ extern "C" fn bun_sql_begin( let callback_ptr = closure_ptr_from_value(callback.get_nanbox_f64()) .expect("the rooted transaction callback remains callable"); - let callback_result = - match js_call_catching(|| js_closure_call1(callback_ptr, client.get_nanbox_f64())) { - Ok(value) => value, - Err(error) => { - let error = scope.root_nanbox_f64(error); - let _ = js_call_catching(|| { - finish_transaction(db_handle, nested, false); - undefined_f64() - }); - return rejected_promise(error.get_nanbox_f64()); - } - }; + let callback_result = match js_call_catching(|| { + js_closure_call1( + callback_ptr, + perry_runtime::closure::plain_call_receiver(), + client.get_nanbox_f64(), + ) + }) { + Ok(value) => value, + Err(error) => { + let error = scope.root_nanbox_f64(error); + let _ = js_call_catching(|| { + finish_transaction(db_handle, nested, false); + undefined_f64() + }); + return rejected_promise(error.get_nanbox_f64()); + } + }; let callback_scope = RuntimeHandleScope::new(); let callback_result = callback_scope.root_nanbox_f64(callback_result); let callback_promise = match js_promise_resolved_catching(callback_result.get_nanbox_f64()) @@ -295,8 +300,15 @@ unsafe fn call_onclose(callback_value: f64, client: f64, error: f64) -> Result<( let Some(callback) = closure_ptr_from_value(callback_value.get_nanbox_f64()) else { return Ok(()); }; - js_call_catching(|| js_closure_call2(callback, client.get_nanbox_f64(), error.get_nanbox_f64())) - .map(|_| ()) + js_call_catching(|| { + js_closure_call2( + callback, + perry_runtime::closure::plain_call_receiver(), + client.get_nanbox_f64(), + error.get_nanbox_f64(), + ) + }) + .map(|_| ()) } extern "C" fn bun_sql_close( @@ -487,7 +499,11 @@ pub unsafe extern "C" fn js_bun_sql_new(config_value: f64, options_value: f64) - if let Some(onconnect) = onconnect { if let Some(callback) = closure_ptr_from_value(onconnect.get_nanbox_f64()) { - js_closure_call1(callback, client.get_nanbox_f64()); + js_closure_call1( + callback, + perry_runtime::closure::plain_call_receiver(), + client.get_nanbox_f64(), + ); } } client.get_nanbox_f64() diff --git a/crates/perry-stdlib/src/common/net_method_values.rs b/crates/perry-stdlib/src/common/net_method_values.rs index ce123f9818..1ec76dc3b7 100644 --- a/crates/perry-stdlib/src/common/net_method_values.rs +++ b/crates/perry-stdlib/src/common/net_method_values.rs @@ -491,6 +491,7 @@ pub(crate) unsafe fn dispatch_external_server_method( if callback >= 0x1000 { perry_runtime::closure::js_closure_call2( callback as *const perry_runtime::ClosureHeader, + perry_runtime::closure::plain_call_receiver(), null(), js_net_server_get_connections(handle), ); diff --git a/crates/perry-stdlib/src/crypto/hash_handles.rs b/crates/perry-stdlib/src/crypto/hash_handles.rs index 1e5fda0346..6a49926bfd 100644 --- a/crates/perry-stdlib/src/crypto/hash_handles.rs +++ b/crates/perry-stdlib/src/crypto/hash_handles.rs @@ -229,13 +229,20 @@ unsafe fn digest_value(bytes: &[u8], encoding: Option<&str>) -> f64 { unsafe fn emit_callback0(cb: i64) { if cb != 0 { - js_closure_call0(cb as *const ClosureHeader); + js_closure_call0( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ); } } unsafe fn emit_callback1(cb: i64, arg: f64) { if cb != 0 { - js_closure_call1(cb as *const ClosureHeader, arg); + js_closure_call1( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + arg, + ); } } diff --git a/crates/perry-stdlib/src/crypto/keys.rs b/crates/perry-stdlib/src/crypto/keys.rs index 8ffa4087e0..176350d2c2 100644 --- a/crates/perry-stdlib/src/crypto/keys.rs +++ b/crates/perry-stdlib/src/crypto/keys.rs @@ -97,6 +97,7 @@ pub(super) unsafe fn call_node_style_callback2(callback_bits: f64, err: f64, val } perry_runtime::closure::js_closure_call2( raw as *const perry_runtime::ClosureHeader, + perry_runtime::closure::plain_call_receiver(), err, value, ); diff --git a/crates/perry-stdlib/src/domain.rs b/crates/perry-stdlib/src/domain.rs index 6b51485d89..5511d51945 100644 --- a/crates/perry-stdlib/src/domain.rs +++ b/crates/perry-stdlib/src/domain.rs @@ -311,6 +311,7 @@ unsafe fn emit_domain_event(handle: Handle, event: &str, args: &[f64]) -> bool { perry_runtime::object::js_implicit_this_set(receiver); let _ = perry_runtime::closure::js_native_call_value( listener_handle.get_nanbox_f64(), + perry_runtime::closure::plain_call_receiver(), live_args.as_ptr(), live_args.len(), ); @@ -335,7 +336,12 @@ unsafe fn call_with_domain(handle: Handle, callback: f64, args: &[f64]) -> f64 { // Armed in a C trampoline frame (#9305); the error emit below runs // after the trap is popped, exactly as before. let outcome = perry_runtime::exception::catch_js_throw(|| unsafe { - perry_runtime::closure::js_native_call_value(callback, args.as_ptr(), args.len()) + perry_runtime::closure::js_native_call_value( + callback, + perry_runtime::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ) }); exit_domain(handle); match outcome { diff --git a/crates/perry-stdlib/src/events.rs b/crates/perry-stdlib/src/events.rs index 24cf4a5069..c93d8f93d3 100644 --- a/crates/perry-stdlib/src/events.rs +++ b/crates/perry-stdlib/src/events.rs @@ -473,6 +473,7 @@ impl EventEmitterHandle { let closure_ptr = handle.get_heap_word_u64() as *const ClosureHeader; js_closure_call2( closure_ptr, + perry_runtime::closure::plain_call_receiver(), event_arg_h.get_nanbox_f64(), listener_arg_h.get_nanbox_f64(), ); @@ -853,9 +854,13 @@ unsafe fn dispatch_error_monitor(emitter: &mut EventEmitterHandle, arg: Option f64 { as *const perry_runtime::ClosureHeader; perry_runtime::js_closure_call3( closure, + perry_runtime::closure::plain_call_receiver(), value_handle.get_nanbox_f64(), name_handle.get_nanbox_f64(), handle, diff --git a/crates/perry-stdlib/src/fetch/headers.rs b/crates/perry-stdlib/src/fetch/headers.rs index f46e88dd65..c5845e78a1 100644 --- a/crates/perry-stdlib/src/fetch/headers.rs +++ b/crates/perry-stdlib/src/fetch/headers.rs @@ -662,7 +662,12 @@ pub extern "C" fn js_headers_for_each(handle: f64, callback: f64) -> f64 { let k_nan = JSValue::string_ptr(k_ptr).bits(); let closure = perry_runtime::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const perry_runtime::ClosureHeader; - perry_runtime::js_closure_call2(closure, v_handle.get_nanbox_f64(), f64::from_bits(k_nan)); + perry_runtime::js_closure_call2( + closure, + perry_runtime::closure::plain_call_receiver(), + v_handle.get_nanbox_f64(), + f64::from_bits(k_nan), + ); } f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-stdlib/src/querystring.rs b/crates/perry-stdlib/src/querystring.rs index d363ee4b18..e41617377a 100644 --- a/crates/perry-stdlib/src/querystring.rs +++ b/crates/perry-stdlib/src/querystring.rs @@ -390,7 +390,7 @@ unsafe fn apply_decode_codec(callback: *const ClosureHeader, raw: &str) -> Optio /// Call a user-supplied codec closure with `raw` and return its JS value. unsafe fn call_codec(callback: *const ClosureHeader, raw: &str) -> f64 { let arg = nanbox_string(intern_string(raw)); - js_closure_call1(callback, arg) + js_closure_call1(callback, perry_runtime::closure::plain_call_receiver(), arg) } unsafe fn apply_encode_codec(callback: *const ClosureHeader, raw: &str) -> String { diff --git a/crates/perry-stdlib/src/readline/mod.rs b/crates/perry-stdlib/src/readline/mod.rs index c1dc7bb8fc..ac2f1f2211 100644 --- a/crates/perry-stdlib/src/readline/mod.rs +++ b/crates/perry-stdlib/src/readline/mod.rs @@ -821,7 +821,12 @@ fn call_write_value(output: f64, text: &str) { if let Some(write) = object_field(output, b"write").filter(|v| is_callable(*v)) { let args = [chunk]; unsafe { - let _ = js_native_call_value(write, args.as_ptr(), args.len()); + let _ = js_native_call_value( + write, + perry_runtime::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } return; } @@ -906,7 +911,10 @@ fn close_custom_interface(handle: i64) { }) .flatten(); if let Some(cb_i64) = cb { - js_closure_call0(cb_i64 as *const ClosureHeader); + js_closure_call0( + cb_i64 as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ); // Release the slot once the close notification has an observer. If the // custom stream completed before user code could attach `rl.on` // listeners, retain the closed state temporarily; `js_readline_on` @@ -958,7 +966,11 @@ fn append_custom_input(handle: i64, chunk: f64) { }) .flatten(); if let Some(cb_i64) = cb { - js_closure_call1(cb_i64 as *const ClosureHeader, callback_arg(&line)); + js_closure_call1( + cb_i64 as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + callback_arg(&line), + ); } else { let _ = with_interface_mut(handle, |state| { state.buffered_lines.push_back(line); @@ -1217,8 +1229,12 @@ fn attach_custom_input(handle: i64, input: f64) { // slice from the handles every iteration. let args = [event.get_nanbox_f64(), cb.get_nanbox_f64()]; unsafe { - let _ = - js_native_call_value(on_handle.get_nanbox_f64(), args.as_ptr(), args.len()); + let _ = js_native_call_value( + on_handle.get_nanbox_f64(), + perry_runtime::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } } } @@ -1460,14 +1476,21 @@ pub extern "C" fn js_readline_on( }; let cb = with_interface(handle, |state| state.line_callback).flatten(); if let Some(cb_i64) = cb { - js_closure_call1(cb_i64 as *const ClosureHeader, callback_arg(&line)); + js_closure_call1( + cb_i64 as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + callback_arg(&line), + ); } } } if replay_close { let cb = with_interface(handle, |state| state.close_callback).flatten(); if let Some(cb_i64) = cb { - js_closure_call0(cb_i64 as *const ClosureHeader); + js_closure_call0( + cb_i64 as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ); } READLINE_INTERFACES.with(|interfaces| { if let Some(slot) = interfaces.borrow_mut().get_mut(handle as usize) { @@ -1524,7 +1547,7 @@ pub extern "C" fn js_readline_close(_handle: i64) -> f64 { let cb = CLOSE_CALLBACK.with(|c| c.borrow_mut().take()); if let Some(cb_i64) = cb { let closure = cb_i64 as *const ClosureHeader; - js_closure_call0(closure); + js_closure_call0(closure, perry_runtime::closure::plain_call_receiver()); } } undefined() diff --git a/crates/perry-stdlib/src/readline/pump.rs b/crates/perry-stdlib/src/readline/pump.rs index 3ce52dc444..0c3d9faa70 100644 --- a/crates/perry-stdlib/src/readline/pump.rs +++ b/crates/perry-stdlib/src/readline/pump.rs @@ -433,7 +433,7 @@ pub extern "C" fn js_readline_process_pending() -> i32 { .collect(); for callback in callback_handles { let closure = callback.get_raw_const_ptr::(); - js_closure_call0(closure); + js_closure_call0(closure, perry_runtime::closure::plain_call_receiver()); fired += 1; } } @@ -482,7 +482,11 @@ pub extern "C" fn js_readline_process_pending() -> i32 { let arg_handle = arg_scope.root_nanbox_f64(arg); for callback in &data_callback_handles { let closure = callback.get_raw_const_ptr::(); - js_closure_call1(closure, arg_handle.get_nanbox_f64()); + js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + arg_handle.get_nanbox_f64(), + ); fired += 1; } } @@ -500,7 +504,12 @@ pub extern "C" fn js_readline_process_pending() -> i32 { scope.root_nanbox_f64(f64::from_bits(JSValue::string_ptr(seq_str).bits())); let arg2 = build_keypress_object(&name, ctrl, shift, meta, &seq); let closure = callback.get_raw_const_ptr::(); - js_closure_call2(closure, arg1.get_nanbox_f64(), arg2); + js_closure_call2( + closure, + perry_runtime::closure::plain_call_receiver(), + arg1.get_nanbox_f64(), + arg2, + ); fired += 1; } } @@ -524,14 +533,14 @@ pub extern "C" fn js_readline_process_pending() -> i32 { let q_cb = QUESTION_CALLBACK.with(|cb| cb.borrow_mut().take()); if let Some(cb_i64) = q_cb { let closure = cb_i64 as *const ClosureHeader; - js_closure_call1(closure, arg); + js_closure_call1(closure, perry_runtime::closure::plain_call_receiver(), arg); fired += 1; continue; } let line_cb = LINE_CALLBACK.with(|cb| *cb.borrow()); if let Some(cb_i64) = line_cb { let closure = cb_i64 as *const ClosureHeader; - js_closure_call1(closure, arg); + js_closure_call1(closure, perry_runtime::closure::plain_call_receiver(), arg); fired += 1; } } @@ -570,7 +579,11 @@ pub extern "C" fn js_readline_process_pending() -> i32 { let arg_handle = flush_scope.root_nanbox_f64(flushed); for callback in &flush_handles { let closure = callback.get_raw_const_ptr::(); - js_closure_call1(closure, arg_handle.get_nanbox_f64()); + js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + arg_handle.get_nanbox_f64(), + ); fired += 1; } } @@ -579,7 +592,7 @@ pub extern "C" fn js_readline_process_pending() -> i32 { let cb = CLOSE_CALLBACK.with(|c| c.borrow_mut().take()); if let Some(cb_i64) = cb { let closure = cb_i64 as *const ClosureHeader; - js_closure_call0(closure); + js_closure_call0(closure, perry_runtime::closure::plain_call_receiver()); fired += 1; } } @@ -592,7 +605,10 @@ pub extern "C" fn js_readline_process_pending() -> i32 { .map(|mut v| std::mem::take(&mut *v)) .unwrap_or_default(); for cb_i64 in end_cbs { - js_closure_call0(cb_i64 as *const ClosureHeader); + js_closure_call0( + cb_i64 as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ); fired += 1; } } diff --git a/crates/perry-stdlib/src/sqlite/backup.rs b/crates/perry-stdlib/src/sqlite/backup.rs index 9e1985090e..c6fdebb8db 100644 --- a/crates/perry-stdlib/src/sqlite/backup.rs +++ b/crates/perry-stdlib/src/sqlite/backup.rs @@ -326,6 +326,7 @@ pub(crate) unsafe fn call_backup_progress( ); js_closure_call1( progress, + perry_runtime::closure::plain_call_receiver(), f64::from_bits(JSValue::object_ptr(info as *mut u8).bits()), ); } diff --git a/crates/perry-stdlib/src/sqlite/better.rs b/crates/perry-stdlib/src/sqlite/better.rs index 863478d437..c5057c2918 100644 --- a/crates/perry-stdlib/src/sqlite/better.rs +++ b/crates/perry-stdlib/src/sqlite/better.rs @@ -429,7 +429,11 @@ pub(crate) unsafe extern "C" fn sqlite_tx_wrapper( js_sqlite_begin_transaction(db_handle); // Call original closure with argument - let result = js_closure_call1(original_closure, arg0); + let result = js_closure_call1( + original_closure, + perry_runtime::closure::plain_call_receiver(), + arg0, + ); // COMMIT js_sqlite_commit(db_handle); diff --git a/crates/perry-stdlib/src/sqlite/bind.rs b/crates/perry-stdlib/src/sqlite/bind.rs index e472b12fa1..c7167cf732 100644 --- a/crates/perry-stdlib/src/sqlite/bind.rs +++ b/crates/perry-stdlib/src/sqlite/bind.rs @@ -510,6 +510,7 @@ pub(crate) unsafe fn node_sqlite_call_closure(callback: f64, args: &[f64]) -> f6 }; js_closure_call_array( closure as i64, + perry_runtime::closure::plain_call_receiver(), if args.is_empty() { std::ptr::null() } else { diff --git a/crates/perry-stdlib/src/sqlite/bun.rs b/crates/perry-stdlib/src/sqlite/bun.rs index 05357935ac..17c80825a1 100644 --- a/crates/perry-stdlib/src/sqlite/bun.rs +++ b/crates/perry-stdlib/src/sqlite/bun.rs @@ -234,6 +234,7 @@ unsafe extern "C" fn bun_sqlite_transaction_wrapper( match perry_runtime::exception::js_call_catching(|| { js_closure_call_array( callback as i64, + perry_runtime::closure::plain_call_receiver(), if args.is_empty() { std::ptr::null() } else { diff --git a/crates/perry-stdlib/src/sqlite/node_stmt_session.rs b/crates/perry-stdlib/src/sqlite/node_stmt_session.rs index b454d6f7e5..b6a41619cb 100644 --- a/crates/perry-stdlib/src/sqlite/node_stmt_session.rs +++ b/crates/perry-stdlib/src/sqlite/node_stmt_session.rs @@ -485,7 +485,11 @@ pub(crate) unsafe extern "C" fn node_sqlite_changeset_filter( CStr::from_ptr(table).to_str().unwrap_or("") }; let table_value = JSValue::string_ptr(js_string_from_bytes(table.as_ptr(), table.len() as u32)); - let result = js_closure_call1(filter, f64::from_bits(table_value.bits())); + let result = js_closure_call1( + filter, + perry_runtime::closure::plain_call_receiver(), + f64::from_bits(table_value.bits()), + ); (perry_runtime::value::js_is_truthy(result) != 0) as c_int } @@ -500,6 +504,7 @@ pub(crate) unsafe extern "C" fn node_sqlite_changeset_conflict( }; let result = js_closure_call1( on_conflict, + perry_runtime::closure::plain_call_receiver(), f64::from_bits(JSValue::number(conflict as f64).bits()), ); let result = value_from_f64(result); diff --git a/crates/perry-stdlib/src/streams.rs b/crates/perry-stdlib/src/streams.rs index cbd65dbb7c..fc9adb3959 100644 --- a/crates/perry-stdlib/src/streams.rs +++ b/crates/perry-stdlib/src/streams.rs @@ -25,6 +25,7 @@ //! into `desiredSize`) live in `streams/byob.rs` and the queue helpers on //! `ReadableStreamData` (#4915). +use perry_runtime::closure::JsThis; use perry_runtime::{ArrayHeader, ClosureHeader, JSValue, ObjectHeader, Promise, StringHeader}; use std::collections::{HashMap, VecDeque}; use std::sync::Mutex; @@ -53,11 +54,16 @@ extern "C" { #[link_name = "js_closure_alloc"] fn provider_js_closure_alloc(function: *const u8, capture_count: u32) -> *mut ClosureHeader; #[link_name = "js_closure_call0"] - fn provider_js_closure_call0(closure: *const ClosureHeader) -> f64; + fn provider_js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64; #[link_name = "js_closure_call1"] - fn provider_js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> f64; + fn provider_js_closure_call1(closure: *const ClosureHeader, this: JsThis, arg0: f64) -> f64; #[link_name = "js_closure_call2"] - fn provider_js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64; + fn provider_js_closure_call2( + closure: *const ClosureHeader, + this: JsThis, + arg0: f64, + arg1: f64, + ) -> f64; #[link_name = "js_closure_get_capture_ptr"] fn provider_js_closure_get_capture_ptr(closure: *const ClosureHeader, index: u32) -> i64; #[link_name = "js_closure_set_capture_ptr"] @@ -86,11 +92,14 @@ extern "C" { #[link_name = "js_native_call_value"] fn provider_js_native_call_value( function: f64, + this: JsThis, arguments: *const f64, argument_count: usize, ) -> f64; #[link_name = "js_implicit_this_set"] fn provider_js_implicit_this_set(value: f64) -> f64; + #[link_name = "js_implicit_this_get"] + fn provider_js_implicit_this_get() -> f64; #[link_name = "js_ffi_root_scope_enter"] fn provider_js_ffi_root_scope_enter() -> usize; #[link_name = "js_ffi_root_push_nanbox"] @@ -173,16 +182,27 @@ fn js_closure_alloc(function: *const u8, capture_count: u32) -> *mut ClosureHead provider_call!(provider_js_closure_alloc(function, capture_count)) } +// Stage 1 of this-as-a-parameter: these plain calls run with whatever the +// implicit-`this` cell holds, which is the receiver they pass. fn js_closure_call0(closure: *const ClosureHeader) -> f64 { - provider_call!(provider_js_closure_call0(closure)) + provider_call!(provider_js_closure_call0(closure, plain_call_receiver())) } fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> f64 { - provider_call!(provider_js_closure_call1(closure, arg0)) + provider_call!(provider_js_closure_call1( + closure, + plain_call_receiver(), + arg0 + )) } fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64 { - provider_call!(provider_js_closure_call2(closure, arg0, arg1)) + provider_call!(provider_js_closure_call2( + closure, + plain_call_receiver(), + arg0, + arg1 + )) } fn js_closure_get_capture_ptr(closure: *const ClosureHeader, index: u32) -> i64 { @@ -224,11 +244,19 @@ fn js_promise_mark_internally_handled(promise: *mut Promise) { fn js_native_call_value(function: f64, arguments: *const f64, argument_count: usize) -> f64 { provider_call!(provider_js_native_call_value( function, + plain_call_receiver(), arguments, argument_count )) } +/// The receiver of a plain call in stage 1 of this-as-a-parameter: the +/// implicit-`this` cell's current value, read through the provider's C ABI +/// like every other runtime access in this file. +fn plain_call_receiver() -> JsThis { + JsThis(provider_call!(provider_js_implicit_this_get()).to_bits()) +} + fn js_implicit_this_set(value: f64) -> f64 { provider_call!(provider_js_implicit_this_set(value)) } diff --git a/crates/perry-stdlib/src/streams/pipe.rs b/crates/perry-stdlib/src/streams/pipe.rs index 2cf8312da5..e9ffb61453 100644 --- a/crates/perry-stdlib/src/streams/pipe.rs +++ b/crates/perry-stdlib/src/streams/pipe.rs @@ -882,7 +882,7 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( state.abort_listener, ); if perry_runtime::url::js_abort_signal_is_aborted(signal_ptr) != 0 { - readable_stream_pipe_to_aborted(listener, perry_runtime::closure::JsThis::current()); + readable_stream_pipe_to_aborted(listener, perry_runtime::closure::current_this()); return promise; } } diff --git a/crates/perry-stdlib/src/streams/tee.rs b/crates/perry-stdlib/src/streams/tee.rs index a9adad834b..b8bc0353af 100644 --- a/crates/perry-stdlib/src/streams/tee.rs +++ b/crates/perry-stdlib/src/streams/tee.rs @@ -235,9 +235,12 @@ pub(super) unsafe fn tee_source_enqueue( g.get(&id).map(|s| s.strategy_size_cb).unwrap_or(0) }; let size = if size_cb != 0 { - let size = super::readable_strategy_size_to_number( - perry_runtime::closure::js_closure_call1(size_cb as *const ClosureHeader, chunk), - ); + let size = + super::readable_strategy_size_to_number(perry_runtime::closure::js_closure_call1( + size_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + chunk, + )); if size.is_nan() || size < 0.0 || size.is_infinite() { super::throw_invalid_readable_strategy_size(id, size); } diff --git a/crates/perry-stdlib/src/tls.rs b/crates/perry-stdlib/src/tls.rs index 4bcaad7591..093e951de5 100644 --- a/crates/perry-stdlib/src/tls.rs +++ b/crates/perry-stdlib/src/tls.rs @@ -1256,6 +1256,7 @@ pub unsafe extern "C" fn js_tls_client_preflight( perry_runtime::closure::js_closure_set_capture_f64(completion, 1, nanbox_str(&servername)); js_closure_call2( sni_callback as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), nanbox_str(&servername), js_nanbox_pointer(completion as i64), ); @@ -1303,6 +1304,7 @@ pub unsafe extern "C" fn js_tls_client_preflight( .root_nanbox_f64(perry_runtime::object::js_implicit_this_set(callback_socket)); let selected = js_closure_call1( rebound_callback as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), js_nanbox_pointer(argument as i64), ); perry_runtime::object::js_implicit_this_set(previous_this.get_nanbox_f64()); diff --git a/crates/perry-stdlib/src/tls/event_pump.rs b/crates/perry-stdlib/src/tls/event_pump.rs index c6998ef151..2a94ab441a 100644 --- a/crates/perry-stdlib/src/tls/event_pump.rs +++ b/crates/perry-stdlib/src/tls/event_pump.rs @@ -53,7 +53,7 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call0(cb); + js_closure_call0(cb, perry_runtime::closure::plain_call_receiver()); } } drain_once_listeners(server_id, "listening"); @@ -69,7 +69,11 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call1(cb, socket); + js_closure_call1( + cb, + perry_runtime::closure::plain_call_receiver(), + socket, + ); } } drain_once_listeners(server_id, event_name); @@ -90,7 +94,7 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call0(cb); + js_closure_call0(cb, perry_runtime::closure::plain_call_receiver()); } } if let Some(server) = servers().lock().unwrap().remove(&server_id) { @@ -109,7 +113,11 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call1(cb, err.get_nanbox_f64()); + js_closure_call1( + cb, + perry_runtime::closure::plain_call_receiver(), + err.get_nanbox_f64(), + ); } } drain_once_listeners(server_id, "error"); @@ -126,7 +134,12 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call2(cb, err.get_nanbox_f64(), socket.get_nanbox_f64()); + js_closure_call2( + cb, + perry_runtime::closure::plain_call_receiver(), + err.get_nanbox_f64(), + socket.get_nanbox_f64(), + ); } } drain_once_listeners(server_id, "tlsClientError"); @@ -141,7 +154,11 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call1(cb, data.get_nanbox_f64()); + js_closure_call1( + cb, + perry_runtime::closure::plain_call_receiver(), + data.get_nanbox_f64(), + ); } } drain_once_listeners(socket_id, "data"); @@ -155,7 +172,7 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call0(cb); + js_closure_call0(cb, perry_runtime::closure::plain_call_receiver()); } } drain_once_listeners(socket_id, "end"); @@ -169,7 +186,7 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call0(cb); + js_closure_call0(cb, perry_runtime::closure::plain_call_receiver()); } } if let Some(socket) = sockets().lock().unwrap().remove(&socket_id) { @@ -188,7 +205,11 @@ pub unsafe extern "C" fn js_tls_process_pending() -> i32 { for cb in callbacks { let cb = cb.get_raw_const_ptr::(); if !cb.is_null() { - js_closure_call1(cb, err.get_nanbox_f64()); + js_closure_call1( + cb, + perry_runtime::closure::plain_call_receiver(), + err.get_nanbox_f64(), + ); } } drain_once_listeners(socket_id, "error"); diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index bd02f6de90..60cc71eeda 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -936,7 +936,11 @@ fn call_callback1(callback_bits: u64, this_bits: u64, arg: f64) { let prev_this = this_scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_set( f64::from_bits(this_bits), )); - perry_runtime::closure::js_closure_call1(closure, arg); + perry_runtime::closure::js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + arg, + ); perry_runtime::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } @@ -1068,7 +1072,7 @@ extern "C" fn worker_remove_event_listener( ) -> f64 { worker_off( closure, - perry_runtime::closure::JsThis::current(), + perry_runtime::closure::current_this(), event, callback, ) diff --git a/crates/perry-stdlib/src/worker_threads/worker_pump.rs b/crates/perry-stdlib/src/worker_threads/worker_pump.rs index 056b18243a..b4e36b3b1f 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_pump.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_pump.rs @@ -26,7 +26,11 @@ pub(super) fn deliver_parent_port_message(message: &SerializedValue) { if let Some(callback_ptr) = MESSAGE_CALLBACK.with(|cb| *cb.borrow()) { let closure = callback_ptr as *const ClosureHeader; - perry_runtime::closure::js_closure_call1(closure, value_h.get_nanbox_f64()); + perry_runtime::closure::js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + value_h.get_nanbox_f64(), + ); } // Root the listener closures BEFORE allocating the `MessageEvent`: that @@ -59,7 +63,11 @@ pub(super) fn deliver_parent_port_message(message: &SerializedValue) { let callback_ptr = perry_runtime::value::js_nanbox_get_pointer(callback_h.get_nanbox_f64()); let closure = callback_ptr as *const ClosureHeader; - perry_runtime::closure::js_closure_call1(closure, event_h.get_nanbox_f64()); + perry_runtime::closure::js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + event_h.get_nanbox_f64(), + ); } } } @@ -180,7 +188,11 @@ pub extern "C" fn js_worker_threads_process_pending() -> i32 { // Call the message callback with the parsed value let closure = callback_ptr as *const ClosureHeader; - perry_runtime::closure::js_closure_call1(closure, parsed); + perry_runtime::closure::js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + parsed, + ); processed += 1; } } @@ -191,7 +203,10 @@ pub extern "C" fn js_worker_threads_process_pending() -> i32 { let close_cb = CLOSE_CALLBACK.with(|cb| cb.borrow_mut().take()); if let Some(callback_ptr) = close_cb { let closure = callback_ptr as *const ClosureHeader; - perry_runtime::closure::js_closure_call0(closure); + perry_runtime::closure::js_closure_call0( + closure, + perry_runtime::closure::plain_call_receiver(), + ); } } @@ -342,9 +357,16 @@ fn dispatch_worker_event(worker_id: u64, event: &str, arg: Option) { arg_handle.as_ref().map(|h| h.get_nanbox_f64()) }; if let Some(arg) = call_arg { - perry_runtime::closure::js_closure_call1(closure, arg); + perry_runtime::closure::js_closure_call1( + closure, + perry_runtime::closure::plain_call_receiver(), + arg, + ); } else { - perry_runtime::closure::js_closure_call0(closure); + perry_runtime::closure::js_closure_call0( + closure, + perry_runtime::closure::plain_call_receiver(), + ); } } js_undefined() diff --git a/crates/perry-stdlib/src/worker_threads/worker_surface.rs b/crates/perry-stdlib/src/worker_threads/worker_surface.rs index 9261fa1a33..2f22cc541c 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_surface.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_surface.rs @@ -415,8 +415,12 @@ fn stream_emit_event(event: f64, arg: f64) -> f64 { perry_runtime::object::js_implicit_this_set(this_h.get_nanbox_f64()); unsafe { let args = [arg_h.get_nanbox_f64()]; - let _ = - perry_runtime::closure::js_native_call_value(callback, args.as_ptr(), args.len()); + let _ = perry_runtime::closure::js_native_call_value( + callback, + perry_runtime::closure::plain_call_receiver(), + args.as_ptr(), + args.len(), + ); } perry_runtime::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } diff --git a/crates/perry-stdlib/src/zlib.rs b/crates/perry-stdlib/src/zlib.rs index 43e1b2eef3..e0311abee5 100644 --- a/crates/perry-stdlib/src/zlib.rs +++ b/crates/perry-stdlib/src/zlib.rs @@ -1535,7 +1535,11 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { for callback in callbacks { let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call1(callback, buffer.get_nanbox_f64()); + js_closure_call1( + callback, + perry_runtime::closure::plain_call_receiver(), + buffer.get_nanbox_f64(), + ); } } } @@ -1570,13 +1574,19 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { for callback in end_callbacks { let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call0(callback); + js_closure_call0( + callback, + perry_runtime::closure::plain_call_receiver(), + ); } } for callback in finish_callbacks { let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call0(callback); + js_closure_call0( + callback, + perry_runtime::closure::plain_call_receiver(), + ); } } for destination in destinations { @@ -1585,7 +1595,10 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { for callback in close_callbacks { let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call0(callback); + js_closure_call0( + callback, + perry_runtime::closure::plain_call_receiver(), + ); } } } @@ -1594,7 +1607,7 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { let callback = scope.root_raw_const_ptr(cb as *const ClosureHeader); let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call0(callback); + js_closure_call0(callback, perry_runtime::closure::plain_call_receiver()); } } ZlibEvent::OneShotCallback(cb, result, ids) => { @@ -1623,6 +1636,7 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { let buffer = scope.root_nanbox_f64(buf_f64); js_closure_call2( callback.get_raw_const_ptr::(), + perry_runtime::closure::plain_call_receiver(), f64::from_bits(JSValue::null().bits()), buffer.get_nanbox_f64(), ); @@ -1632,6 +1646,7 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { )); js_closure_call2( callback.get_raw_const_ptr::(), + perry_runtime::closure::plain_call_receiver(), error.get_nanbox_f64(), f64::from_bits(JSValue::undefined().bits()), ); @@ -1641,6 +1656,7 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { let error = scope.root_nanbox_f64(build_zlib_error(&msg)); js_closure_call2( callback.get_raw_const_ptr::(), + perry_runtime::closure::plain_call_receiver(), error.get_nanbox_f64(), f64::from_bits(JSValue::undefined().bits()), ); @@ -1667,7 +1683,11 @@ pub unsafe extern "C" fn js_zlib_process_pending() -> i32 { for callback in callbacks { let callback = callback.get_raw_const_ptr::(); if !callback.is_null() { - js_closure_call1(callback, error.get_nanbox_f64()); + js_closure_call1( + callback, + perry_runtime::closure::plain_call_receiver(), + error.get_nanbox_f64(), + ); } } } diff --git a/crates/perry-ui-android/src/app.rs b/crates/perry-ui-android/src/app.rs index 103febeb3a..e792e279df 100644 --- a/crates/perry-ui-android/src/app.rs +++ b/crates/perry-ui-android/src/app.rs @@ -350,7 +350,7 @@ pub fn add_keyboard_shortcut(_key_ptr: *const u8, _modifiers: f64, _callback: f6 } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -419,7 +419,7 @@ pub fn handle_activate() { return; } unsafe { - js_closure_call1(ptr, 0.0); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, 0.0); } } }); @@ -435,7 +435,7 @@ pub fn handle_terminate() { return; } unsafe { - js_closure_call1(ptr, 0.0); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, 0.0); } } }); diff --git a/crates/perry-ui-android/src/callback.rs b/crates/perry-ui-android/src/callback.rs index e09d0959f8..ca66d257db 100644 --- a/crates/perry-ui-android/src/callback.rs +++ b/crates/perry-ui-android/src/callback.rs @@ -3,10 +3,17 @@ use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Mutex; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; - fn js_closure_call4(closure: *const u8, arg0: f64, arg1: f64, arg2: f64, arg3: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; + fn js_closure_call4( + closure: *const u8, + this: perry_ffi::JsThis, + arg0: f64, + arg1: f64, + arg2: f64, + arg3: f64, + ) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -119,7 +126,7 @@ pub fn invoke0(key: i64) { key, closure_ptr, ); - js_closure_call0(closure_ptr); + js_closure_call0(closure_ptr, perry_ffi::JsThis::UNDEFINED); __android_log_print( 3, b"PerryCallback\0".as_ptr(), @@ -149,7 +156,7 @@ pub fn invoke1(key: i64, arg: f64) { // Unbox NaN-boxed closure — see invoke0 for details. let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } @@ -164,7 +171,7 @@ pub fn invoke2(key: i64, arg1: f64, arg2: f64) { // Unbox NaN-boxed closure — see invoke0 for details. let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call2(closure_ptr, arg1, arg2); + js_closure_call2(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg1, arg2); } } } @@ -180,7 +187,14 @@ pub fn invoke4(key: i64, arg0: f64, arg1: f64, arg2: f64, arg3: f64) { // Unbox NaN-boxed closure — see invoke0 for details. let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call4(closure_ptr, arg0, arg1, arg2, arg3); + js_closure_call4( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + arg0, + arg1, + arg2, + arg3, + ); } } } @@ -206,7 +220,7 @@ pub fn invoke_with_string_array(key: i64, paths: &[String]) { arr = js_array_push_f64(arr, nb_str); } let nb_arr = js_nanbox_pointer(arr as i64); - js_closure_call1(closure_ptr, nb_arr); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb_arr); } } } diff --git a/crates/perry-ui-android/src/dialog.rs b/crates/perry-ui-android/src/dialog.rs index 1b7b261d15..abbf2c2614 100644 --- a/crates/perry-ui-android/src/dialog.rs +++ b/crates/perry-ui-android/src/dialog.rs @@ -7,7 +7,7 @@ use jni::JValue; use perry_ffi::copy_string_from_raw as str_from_header; extern "C" { - fn js_closure_call1(closure: f64, arg: f64) -> f64; + fn js_closure_call1(closure: f64, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_string_from_bytes(ptr: *const u8, len: usize) -> *const u8; fn js_nanbox_string(ptr: *const u8) -> f64; } @@ -25,7 +25,7 @@ pub fn save_file_dialog( let s = unsafe { js_string_from_bytes(empty.as_ptr(), 0) }; let val = unsafe { js_nanbox_string(s) }; unsafe { - js_closure_call1(callback, val); + js_closure_call1(callback, perry_ffi::JsThis::UNDEFINED, val); } } } diff --git a/crates/perry-ui-android/src/drag_drop.rs b/crates/perry-ui-android/src/drag_drop.rs index 5e12619f87..c00bd5cb5f 100644 --- a/crates/perry-ui-android/src/drag_drop.rs +++ b/crates/perry-ui-android/src/drag_drop.rs @@ -77,8 +77,8 @@ extern "C" { fn js_array_push_f64(arr: *mut c_void, value: f64) -> *mut c_void; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; // Converts an arbitrary JS value to a runtime `StringHeader` (typed here as // `*const u8` so it feeds `str_from_header`, the same way `clipboard.rs` @@ -279,7 +279,7 @@ pub extern "C" fn Java_com_perry_app_PerryBridge_nativeInvokeDropCallback( js_object_set_field_by_name(obj, js_key(b"urls"), js_nanbox_pointer(arr as i64)); } let payload = js_nanbox_pointer(obj as i64); - js_closure_call1(closure_ptr, payload); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, payload); } pump_microtasks(); }); @@ -312,7 +312,7 @@ fn drag_provider_payload(key: i64) -> Option { return None; } unsafe { - let ret = js_closure_call0(closure_ptr); + let ret = js_closure_call0(closure_ptr, perry_ffi::JsThis::UNDEFINED); if ret.to_bits() == TAG_UNDEFINED { return None; } diff --git a/crates/perry-ui-android/src/media_playback.rs b/crates/perry-ui-android/src/media_playback.rs index 57a09dba6d..3ec4df8361 100644 --- a/crates/perry-ui-android/src/media_playback.rs +++ b/crates/perry-ui-android/src/media_playback.rs @@ -36,8 +36,8 @@ static MEDIA_SESSION: OnceLock>> = OnceLock::new(); extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; fn js_string_new_sso(data: *const u8, len: u32) -> f64; fn js_run_stdlib_pump(); @@ -803,7 +803,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -812,7 +816,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-android/src/pointer.rs b/crates/perry-ui-android/src/pointer.rs index db28bc7245..03a1c20a50 100644 --- a/crates/perry-ui-android/src/pointer.rs +++ b/crates/perry-ui-android/src/pointer.rs @@ -19,7 +19,7 @@ use crate::jni_bridge; use jni::JValue; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -49,7 +49,7 @@ pub extern "C" fn Java_com_perry_app_PerryBridge_nativeInvokePointerCallback( } unsafe { let pe = js_pointer_event_new(x, y, button.max(0) as u32, POINTER_TYPE_TOUCH); - js_closure_call1(closure_ptr, pe); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, pe); } } diff --git a/crates/perry-ui-android/src/state.rs b/crates/perry-ui-android/src/state.rs index 31122a4ee2..bb0a94ea43 100644 --- a/crates/perry-ui-android/src/state.rs +++ b/crates/perry-ui-android/src/state.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_get_string_pointer_unified(value: f64) -> *const u8; } @@ -239,7 +239,7 @@ pub fn state_set(handle: i64, value: f64) { // Do not use to_bits() as a raw pointer — leaves the 0x7ffd tag. let closure_ptr = unsafe { js_nanbox_get_pointer(callback_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } @@ -298,7 +298,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { // Closure f64 is raw pointer bits bitcast to f64, not NaN-boxed let closure_ptr = closure.to_bits() as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; // Widget handle returned as i64 bitcast to f64 let child_handle = child_f64.to_bits() as i64; widgets::add_child(container, child_handle); diff --git a/crates/perry-ui-android/src/widgets/lazyvstack.rs b/crates/perry-ui-android/src/widgets/lazyvstack.rs index 8b75bc3391..40dbb213b9 100644 --- a/crates/perry-ui-android/src/widgets/lazyvstack.rs +++ b/crates/perry-ui-android/src/widgets/lazyvstack.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: f64, arg: f64) -> f64; + fn js_closure_call1(closure: f64, this: perry_ffi::JsThis, arg: f64) -> f64; } struct LazyState { @@ -49,7 +49,8 @@ pub fn create(count: f64, render_closure: f64) -> i64 { // Render initial items let n = count as i64; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(render_closure, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(render_closure, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = child_f64.to_bits() as i64; if child_handle > 0 { super::add_child(container_handle, child_handle); @@ -71,7 +72,8 @@ pub fn update(handle: i64, count: i64) { // Re-render all items for i in 0..count { - let child_f64 = unsafe { js_closure_call1(closure, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = child_f64.to_bits() as i64; if child_handle > 0 { super::add_child(container, child_handle); diff --git a/crates/perry-ui-android/src/widgets/table.rs b/crates/perry-ui-android/src/widgets/table.rs index d4b5104947..ead75a9a74 100644 --- a/crates/perry-ui-android/src/widgets/table.rs +++ b/crates/perry-ui-android/src/widgets/table.rs @@ -6,7 +6,7 @@ use jni::{ }; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, row: f64, col: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, row: f64, col: f64) -> f64; fn js_string_from_bytes(bytes: *const u8, len: i64) -> *const u8; } fn with_table(handle: i64, default: R, f: impl FnOnce(&mut Env, &JObject) -> R) -> R { @@ -76,6 +76,7 @@ pub extern "system" fn Java_com_perry_app_PerryBridge_nativeTableRenderCell( let value = unsafe { js_closure_call2( js_nanbox_get_pointer(closure) as *const u8, + perry_ffi::JsThis::UNDEFINED, row as f64, col as f64, ) diff --git a/crates/perry-ui-android/src/widgets/webview.rs b/crates/perry-ui-android/src/widgets/webview.rs index 5c336838ed..c2d1d22f4f 100644 --- a/crates/perry-ui-android/src/widgets/webview.rs +++ b/crates/perry-ui-android/src/widgets/webview.rs @@ -22,7 +22,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_is_truthy(value: f64) -> i32; @@ -386,7 +386,7 @@ pub extern "system" fn Java_com_perry_app_PerryBridge_nativeWebViewShouldNavigat let result_cell = StdCell::new(f64::from_bits(0x7FFC_0000_0000_0001)); let result_cell_ref = &result_cell; let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let r = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let r = unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) }; result_cell_ref.set(r); })); let result = result_cell.get(); @@ -420,13 +420,13 @@ pub extern "system" fn Java_com_perry_app_PerryBridge_nativeWebViewLoaded( } let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let nb = nanbox_str(&url_str); - unsafe { js_closure_call1(closure_ptr, nb) }; + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb) }; })); }); } extern "C" { - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; } #[no_mangle] @@ -454,7 +454,14 @@ pub extern "system" fn Java_com_perry_app_PerryBridge_nativeWebViewError( } let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let msg_nb = nanbox_str(&msg); - unsafe { js_closure_call2(closure_ptr, code as f64, msg_nb) }; + unsafe { + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + code as f64, + msg_nb, + ) + }; })); }); } @@ -490,7 +497,7 @@ pub extern "system" fn Java_com_perry_app_PerryBridge_nativeWebViewEvalResult( } let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let nb = nanbox_str(&s); - unsafe { js_closure_call1(closure_ptr, nb) }; + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb) }; })); }); } diff --git a/crates/perry-ui-gtk4/src/app.rs b/crates/perry-ui-gtk4/src/app.rs index 30758956db..6980f1ad04 100644 --- a/crates/perry-ui-gtk4/src/app.rs +++ b/crates/perry-ui-gtk4/src/app.rs @@ -77,7 +77,7 @@ struct AppEntry { } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -352,7 +352,7 @@ pub fn app_run(_app_handle: i64) { if let Some(callback) = *cb.borrow() { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); @@ -385,7 +385,7 @@ pub fn app_run(_app_handle: i64) { if let Some(callback) = *cb.borrow() { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); @@ -667,7 +667,7 @@ fn install_shortcuts_on_window(window: &ApplicationWindow) { if let Some(callback) = matched { let closure_ptr = unsafe { js_nanbox_get_pointer(callback) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } glib::Propagation::Stop } else { @@ -718,7 +718,7 @@ fn install_timer(interval_ms: f64, callback: f64) { if let Some(callback) = callback { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } #[cfg(feature = "geisterhand")] diff --git a/crates/perry-ui-gtk4/src/audio.rs b/crates/perry-ui-gtk4/src/audio.rs index a755b5f06e..b498632443 100644 --- a/crates/perry-ui-gtk4/src/audio.rs +++ b/crates/perry-ui-gtk4/src/audio.rs @@ -207,7 +207,7 @@ extern "C" { fn js_string_from_bytes(ptr: *const u8, len: i32) -> i64; fn js_array_create() -> i64; fn js_array_push_f64(array_ptr: i64, value: f64); - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64); + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64); fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; } @@ -432,6 +432,11 @@ pub fn invoke_audio_callback(samples_ptr: *const f32, num_samples: usize) { let num_samples_val = num_samples as f64; unsafe { - js_closure_call2(callback_ptr, samples_val, num_samples_val); + js_closure_call2( + callback_ptr, + perry_ffi::JsThis::UNDEFINED, + samples_val, + num_samples_val, + ); } } diff --git a/crates/perry-ui-gtk4/src/camera.rs b/crates/perry-ui-gtk4/src/camera.rs index 74ea2a856c..0ca906e3d0 100644 --- a/crates/perry-ui-gtk4/src/camera.rs +++ b/crates/perry-ui-gtk4/src/camera.rs @@ -14,7 +14,13 @@ thread_local! { } extern "C" { - fn js_closure_call3(closure: *const u8, arg1: f64, arg2: f64, arg3: f64) -> f64; + fn js_closure_call3( + closure: *const u8, + this: perry_ffi::JsThis, + arg1: f64, + arg2: f64, + arg3: f64, + ) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; } @@ -255,6 +261,7 @@ fn schedule_frame_processing(handle: i64) { let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; js_closure_call3( closure_ptr, + perry_ffi::JsThis::UNDEFINED, js_nanbox_pointer(frame_bytes.as_ptr() as i64), w as f64, h as f64, diff --git a/crates/perry-ui-gtk4/src/dialog.rs b/crates/perry-ui-gtk4/src/dialog.rs index dc6b1d4068..d5cf73728a 100644 --- a/crates/perry-ui-gtk4/src/dialog.rs +++ b/crates/perry-ui-gtk4/src/dialog.rs @@ -2,7 +2,7 @@ use gtk4::prelude::*; use gtk4::{FileChooserAction, FileChooserDialog, ResponseType, Window}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -42,7 +42,7 @@ pub fn save_file_dialog(callback: f64, default_name_ptr: *const u8, _allowed_typ unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64) }; let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; unsafe { - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } dialog.close(); return; @@ -50,7 +50,11 @@ pub fn save_file_dialog(callback: f64, default_name_ptr: *const u8, _allowed_typ } } unsafe { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } dialog.close(); }); @@ -109,7 +113,7 @@ pub fn alert(title_ptr: *const u8, message_ptr: *const u8, buttons_ptr: *const u }; let closure_ptr = unsafe { js_nanbox_get_pointer(callback_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, index); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, index); } dialog.close(); }); diff --git a/crates/perry-ui-gtk4/src/drag_drop.rs b/crates/perry-ui-gtk4/src/drag_drop.rs index 7e0b663b4a..81dd546313 100644 --- a/crates/perry-ui-gtk4/src/drag_drop.rs +++ b/crates/perry-ui-gtk4/src/drag_drop.rs @@ -44,8 +44,8 @@ use std::collections::HashMap; use std::ffi::c_void; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; @@ -105,7 +105,7 @@ unsafe fn call_provider(cb: f64) -> Option { if p.is_null() { return None; } - let ret = js_closure_call0(p); + let ret = js_closure_call0(p, perry_ffi::JsThis::UNDEFINED); let sh = js_jsvalue_to_string(ret); if sh.is_null() { None @@ -216,7 +216,7 @@ fn deliver_drop(handle: i64, value: &glib::Value) { let payload = js_nanbox_pointer(obj as i64); let cb_ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call1(cb_ptr, payload); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, payload); } } diff --git a/crates/perry-ui-gtk4/src/file_dialog.rs b/crates/perry-ui-gtk4/src/file_dialog.rs index 35f5ceca17..1d2710633f 100644 --- a/crates/perry-ui-gtk4/src/file_dialog.rs +++ b/crates/perry-ui-gtk4/src/file_dialog.rs @@ -2,7 +2,7 @@ use gtk4::prelude::*; use gtk4::{FileChooserAction, FileChooserDialog, ResponseType, Window}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -62,7 +62,7 @@ fn open_dialog_with_action( unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64) }; let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; unsafe { - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } dialog.close(); return; @@ -72,7 +72,11 @@ fn open_dialog_with_action( // User cancelled or no file selected unsafe { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } dialog.close(); }); diff --git a/crates/perry-ui-gtk4/src/issue_552_stub.rs b/crates/perry-ui-gtk4/src/issue_552_stub.rs index 2fc3d23ca6..41951f2989 100644 --- a/crates/perry-ui-gtk4/src/issue_552_stub.rs +++ b/crates/perry-ui-gtk4/src/issue_552_stub.rs @@ -6,7 +6,7 @@ //! picker callback receives an empty array. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -30,7 +30,7 @@ pub extern "C" fn perry_system_geolocation_get_current(_on_success: f64, on_erro } let nb = unsupported_string(); let cb = js_nanbox_get_pointer(on_error) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -47,7 +47,7 @@ pub extern "C" fn perry_system_geolocation_request_permission(callback: f64) { unsafe { let nb = unsupported_string(); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -61,7 +61,7 @@ pub extern "C" fn perry_system_image_picker_pick( let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-gtk4/src/location.rs b/crates/perry-ui-gtk4/src/location.rs index 38bcfbb42f..f278d25a7f 100644 --- a/crates/perry-ui-gtk4/src/location.rs +++ b/crates/perry-ui-gtk4/src/location.rs @@ -8,7 +8,7 @@ use gtk4::glib; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -27,10 +27,10 @@ pub fn request_location(callback: f64) { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; match result { Some((lat, lon)) => unsafe { - js_closure_call2(ptr, lat, lon); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, lat, lon); }, None => unsafe { - js_closure_call2(ptr, f64::NAN, f64::NAN); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, f64::NAN, f64::NAN); }, } }); diff --git a/crates/perry-ui-gtk4/src/media_playback.rs b/crates/perry-ui-gtk4/src/media_playback.rs index 701042a0f5..1e583a5a6d 100644 --- a/crates/perry-ui-gtk4/src/media_playback.rs +++ b/crates/perry-ui-gtk4/src/media_playback.rs @@ -30,8 +30,8 @@ use std::sync::Arc; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; fn js_string_from_bytes(data: *const u8, len: i32) -> i64; fn js_string_new_sso(data: *const u8, len: u32) -> f64; fn js_run_stdlib_pump(); @@ -502,7 +502,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -511,7 +515,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-gtk4/src/menu.rs b/crates/perry-ui-gtk4/src/menu.rs index 838f24b324..56dca9e4c6 100644 --- a/crates/perry-ui-gtk4/src/menu.rs +++ b/crates/perry-ui-gtk4/src/menu.rs @@ -48,7 +48,7 @@ pub(crate) fn scan_gtk4_menu_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_> } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -231,7 +231,10 @@ fn build_gio_menu(menu_handle: i64, app: >k4::Application) -> gio::Menu { if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + ); } } }); @@ -422,7 +425,7 @@ pub fn set_context_menu(widget_handle: i64, menu_handle: i64) { if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/network_stub.rs b/crates/perry-ui-gtk4/src/network_stub.rs index 0ca620f5a8..a29356026e 100644 --- a/crates/perry-ui-gtk4/src/network_stub.rs +++ b/crates/perry-ui-gtk4/src/network_stub.rs @@ -7,7 +7,7 @@ //! true reachability detection should target iOS / macOS / Android. extern "C" { - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -28,7 +28,12 @@ pub extern "C" fn perry_system_network_get_status(callback: f64) { if cb.is_null() { return; } - js_closure_call2(cb, f64::from_bits(TAG_TRUE), nb_str("unknown")); + js_closure_call2( + cb, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(TAG_TRUE), + nb_str("unknown"), + ); } } diff --git a/crates/perry-ui-gtk4/src/state.rs b/crates/perry-ui-gtk4/src/state.rs index 3035eeefb3..267e6c065c 100644 --- a/crates/perry-ui-gtk4/src/state.rs +++ b/crates/perry-ui-gtk4/src/state.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -260,7 +260,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } @@ -328,7 +328,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-gtk4/src/toolbar.rs b/crates/perry-ui-gtk4/src/toolbar.rs index fd22c7d77f..d13db9bde5 100644 --- a/crates/perry-ui-gtk4/src/toolbar.rs +++ b/crates/perry-ui-gtk4/src/toolbar.rs @@ -18,7 +18,7 @@ pub(crate) fn scan_gtk4_toolbar_gc_roots(visitor: &mut perry_ffi::GcRootVisitor< } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -69,7 +69,7 @@ pub fn add_item(toolbar_handle: i64, label_ptr: *const u8, icon_ptr: *const u8, if let Some(closure_f64) = closure_f64 { let ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/tray.rs b/crates/perry-ui-gtk4/src/tray.rs index 6b7ded95e5..7a6806c206 100644 --- a/crates/perry-ui-gtk4/src/tray.rs +++ b/crates/perry-ui-gtk4/src/tray.rs @@ -54,7 +54,7 @@ use ksni::{Handle, MenuItem, ToolTip, TrayMethods}; use std::sync::{Arc, Mutex, OnceLock}; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -176,7 +176,7 @@ fn invoke_callback_on_main(callback: f64) { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; if !ptr.is_null() { unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/bottom_nav.rs b/crates/perry-ui-gtk4/src/widgets/bottom_nav.rs index 4cffe56991..3a725b178d 100644 --- a/crates/perry-ui-gtk4/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-gtk4/src/widgets/bottom_nav.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -114,7 +114,7 @@ pub fn add_item(handle: i64, icon_ptr: *const u8, label_ptr: *const u8) { if on_select != 0.0 { unsafe { let ptr = js_nanbox_get_pointer(on_select) as *const u8; - js_closure_call1(ptr, item_index as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, item_index as f64); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/button.rs b/crates/perry-ui-gtk4/src/widgets/button.rs index 0ccb1cdece..2b624a69c4 100644 --- a/crates/perry-ui-gtk4/src/widgets/button.rs +++ b/crates/perry-ui-gtk4/src/widgets/button.rs @@ -18,7 +18,7 @@ pub(crate) fn scan_gtk4_button_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<' } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -47,7 +47,7 @@ pub fn create(label_ptr: *const u8, on_press: f64) -> i64 { if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/calendar.rs b/crates/perry-ui-gtk4/src/widgets/calendar.rs index 10a8dd27e6..36d7cbc915 100644 --- a/crates/perry-ui-gtk4/src/widgets/calendar.rs +++ b/crates/perry-ui-gtk4/src/widgets/calendar.rs @@ -12,7 +12,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -54,7 +54,7 @@ pub fn create(year: i64, month: i64, on_change: f64) -> i64 { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } }); } diff --git a/crates/perry-ui-gtk4/src/widgets/combobox.rs b/crates/perry-ui-gtk4/src/widgets/combobox.rs index 40a417f364..f35b6f7c0d 100644 --- a/crates/perry-ui-gtk4/src/widgets/combobox.rs +++ b/crates/perry-ui-gtk4/src/widgets/combobox.rs @@ -15,7 +15,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -33,7 +33,7 @@ fn fire_change(callback: f64, text: &str) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-gtk4/src/widgets/command_palette.rs b/crates/perry-ui-gtk4/src/widgets/command_palette.rs index a075cfb798..1ec18c82f3 100644 --- a/crates/perry-ui-gtk4/src/widgets/command_palette.rs +++ b/crates/perry-ui-gtk4/src/widgets/command_palette.rs @@ -15,7 +15,7 @@ use std::cell::RefCell; use std::rc::Rc; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -205,7 +205,7 @@ pub fn show() { if on_run != 0.0 { unsafe { let closure_ptr = js_nanbox_get_pointer(on_run) as *const u8; - js_closure_call0(closure_ptr); + js_closure_call0(closure_ptr, perry_ffi::JsThis::UNDEFINED); } } hide(); diff --git a/crates/perry-ui-gtk4/src/widgets/date_picker.rs b/crates/perry-ui-gtk4/src/widgets/date_picker.rs index d68753cb0c..2f599315d6 100644 --- a/crates/perry-ui-gtk4/src/widgets/date_picker.rs +++ b/crates/perry-ui-gtk4/src/widgets/date_picker.rs @@ -13,7 +13,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -55,7 +55,7 @@ pub fn create(year: i64, month: i64, on_change: f64) -> i64 { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } }); } diff --git a/crates/perry-ui-gtk4/src/widgets/image_gallery.rs b/crates/perry-ui-gtk4/src/widgets/image_gallery.rs index ded036f81e..1e9181ac50 100644 --- a/crates/perry-ui-gtk4/src/widgets/image_gallery.rs +++ b/crates/perry-ui-gtk4/src/widgets/image_gallery.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -70,7 +70,7 @@ pub fn create(on_index_change: f64) -> i64 { if cb != 0.0 { unsafe { let ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call1(ptr, new_index as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, new_index as f64); } } } diff --git a/crates/perry-ui-gtk4/src/widgets/lazyvstack.rs b/crates/perry-ui-gtk4/src/widgets/lazyvstack.rs index e0399716df..12ff08e089 100644 --- a/crates/perry-ui-gtk4/src/widgets/lazyvstack.rs +++ b/crates/perry-ui-gtk4/src/widgets/lazyvstack.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -44,7 +44,8 @@ pub fn create(count: f64, render_closure: f64) -> i64 { let closure_ptr = unsafe { js_nanbox_get_pointer(render_closure) } as *const u8; let n = count as i64; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; if let Some(child) = super::get_widget(child_handle) { if child.parent().is_some() { @@ -79,7 +80,9 @@ pub fn update(handle: i64, count: i64) { // Re-render let closure_ptr = unsafe { js_nanbox_get_pointer(state.render_closure) } as *const u8; for i in 0..count { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = unsafe { + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) + }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; if let Some(child) = super::get_widget(child_handle) { if child.parent().is_some() { diff --git a/crates/perry-ui-gtk4/src/widgets/mod.rs b/crates/perry-ui-gtk4/src/widgets/mod.rs index 02828b4de3..287784e029 100644 --- a/crates/perry-ui-gtk4/src/widgets/mod.rs +++ b/crates/perry-ui-gtk4/src/widgets/mod.rs @@ -458,7 +458,7 @@ pub fn set_background_gradient( /// don't satisfy `=== true` checks. pub fn set_on_hover(handle: i64, callback: f64) { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } // Perry's NaN-box payload for `true` / `false`. Mirrors @@ -472,14 +472,14 @@ pub fn set_on_hover(handle: i64, callback: f64) { motion.connect_enter(move |_ctrl, _x, _y| { let ptr = unsafe { js_nanbox_get_pointer(cb) } as *const u8; unsafe { - js_closure_call1(ptr, f64::from_bits(TAG_TRUE)); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, f64::from_bits(TAG_TRUE)); } }); let cb2 = callback; motion.connect_leave(move |_ctrl| { let ptr = unsafe { js_nanbox_get_pointer(cb2) } as *const u8; unsafe { - js_closure_call1(ptr, f64::from_bits(TAG_FALSE)); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, f64::from_bits(TAG_FALSE)); } }); widget.add_controller(motion); @@ -489,7 +489,7 @@ pub fn set_on_hover(handle: i64, callback: f64) { /// Set a double-click callback on a widget. pub fn set_on_double_click(handle: i64, callback: f64) { extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } if let Some(widget) = get_widget(handle) { @@ -500,7 +500,7 @@ pub fn set_on_double_click(handle: i64, callback: f64) { if n_press == 2 { let ptr = unsafe { js_nanbox_get_pointer(cb) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); @@ -511,7 +511,7 @@ pub fn set_on_double_click(handle: i64, callback: f64) { /// Set a single-click callback on a widget. pub fn set_on_click(handle: i64, callback: f64) { extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } if let Some(widget) = get_widget(handle) { @@ -522,7 +522,7 @@ pub fn set_on_click(handle: i64, callback: f64) { if n_press == 1 { let ptr = unsafe { js_nanbox_get_pointer(cb) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); @@ -542,7 +542,7 @@ pub fn set_on_click(handle: i64, callback: f64) { /// convention `0`/`1`/`2` (web `MouseEvent.button`). pub fn set_on_mouse_down(handle: i64, callback: f64) { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -567,7 +567,7 @@ pub fn set_on_mouse_down(handle: i64, callback: f64) { } let pe = unsafe { js_pointer_event_new(x, y, js_button, 0) }; unsafe { - js_closure_call1(ptr, pe); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, pe); } }); widget.add_controller(gesture); @@ -576,7 +576,7 @@ pub fn set_on_mouse_down(handle: i64, callback: f64) { pub fn set_on_mouse_up(handle: i64, callback: f64) { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -600,7 +600,7 @@ pub fn set_on_mouse_up(handle: i64, callback: f64) { } let pe = unsafe { js_pointer_event_new(x, y, js_button, 0) }; unsafe { - js_closure_call1(ptr, pe); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, pe); } }); widget.add_controller(gesture); @@ -609,7 +609,7 @@ pub fn set_on_mouse_up(handle: i64, callback: f64) { pub fn set_on_mouse_move(handle: i64, callback: f64) { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -625,7 +625,7 @@ pub fn set_on_mouse_move(handle: i64, callback: f64) { // per the web `MouseEvent.button` spec for mousemove. let pe = unsafe { js_pointer_event_new(x, y, 0, 0) }; unsafe { - js_closure_call1(ptr, pe); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, pe); } }); widget.add_controller(motion); diff --git a/crates/perry-ui-gtk4/src/widgets/picker.rs b/crates/perry-ui-gtk4/src/widgets/picker.rs index b44abce14c..3a643254bc 100644 --- a/crates/perry-ui-gtk4/src/widgets/picker.rs +++ b/crates/perry-ui-gtk4/src/widgets/picker.rs @@ -21,7 +21,7 @@ pub(crate) fn scan_gtk4_picker_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<' } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -51,7 +51,11 @@ pub fn create(_label_ptr: *const u8, on_change: f64, _style: i64) -> i64 { let selected = dd.selected() as f64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, selected); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + selected, + ); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/rich_text.rs b/crates/perry-ui-gtk4/src/widgets/rich_text.rs index ee06f33c5e..da7b7b16f9 100644 --- a/crates/perry-ui-gtk4/src/widgets/rich_text.rs +++ b/crates/perry-ui-gtk4/src/widgets/rich_text.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -80,7 +80,7 @@ pub fn create(width: f64, height: f64, on_change: f64) -> i64 { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } }); } diff --git a/crates/perry-ui-gtk4/src/widgets/scrollview.rs b/crates/perry-ui-gtk4/src/widgets/scrollview.rs index b770639e15..c49ef8ccc5 100644 --- a/crates/perry-ui-gtk4/src/widgets/scrollview.rs +++ b/crates/perry-ui-gtk4/src/widgets/scrollview.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -146,7 +146,7 @@ pub fn set_scroll_end_callback(scroll_handle: i64, callback: f64, threshold_px: if should_fire && closure != 0.0 { unsafe { let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/securefield.rs b/crates/perry-ui-gtk4/src/widgets/securefield.rs index df7abcbdac..ca5bda58de 100644 --- a/crates/perry-ui-gtk4/src/widgets/securefield.rs +++ b/crates/perry-ui-gtk4/src/widgets/securefield.rs @@ -17,7 +17,7 @@ pub(crate) fn scan_gtk4_securefield_gc_roots(visitor: &mut perry_ffi::GcRootVisi } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -53,7 +53,11 @@ pub fn create(placeholder_ptr: *const u8, on_change: f64) -> i64 { let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + nanboxed, + ); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/slider.rs b/crates/perry-ui-gtk4/src/widgets/slider.rs index 9e6bb305ea..83193da2e6 100644 --- a/crates/perry-ui-gtk4/src/widgets/slider.rs +++ b/crates/perry-ui-gtk4/src/widgets/slider.rs @@ -18,7 +18,7 @@ pub(crate) fn scan_gtk4_slider_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<' } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -56,7 +56,11 @@ pub fn create(min: f64, max: f64, initial: f64, on_change: f64) -> i64 { let value = scale.value(); let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + value, + ); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/textarea.rs b/crates/perry-ui-gtk4/src/widgets/textarea.rs index 794a9a133f..aca6da8961 100644 --- a/crates/perry-ui-gtk4/src/widgets/textarea.rs +++ b/crates/perry-ui-gtk4/src/widgets/textarea.rs @@ -18,7 +18,7 @@ pub(crate) fn scan_gtk4_textarea_gc_roots(visitor: &mut perry_ffi::GcRootVisitor } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -72,7 +72,11 @@ pub fn create(placeholder_ptr: *const u8, on_change: f64) -> i64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + nanboxed, + ); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/textfield.rs b/crates/perry-ui-gtk4/src/widgets/textfield.rs index 57b895edba..8666cf0ff8 100644 --- a/crates/perry-ui-gtk4/src/widgets/textfield.rs +++ b/crates/perry-ui-gtk4/src/widgets/textfield.rs @@ -20,8 +20,8 @@ pub(crate) fn scan_gtk4_textfield_gc_roots(visitor: &mut perry_ffi::GcRootVisito } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -60,7 +60,11 @@ pub fn create(placeholder_ptr: *const u8, on_change: f64) -> i64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + nanboxed, + ); } } }); @@ -137,7 +141,11 @@ pub fn set_on_submit(handle: i64, on_submit: f64) { let closure_ptr = unsafe { js_nanbox_get_pointer(on_submit) }; if closure_ptr != 0 { unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + nanboxed, + ); } } }); @@ -155,7 +163,7 @@ pub fn set_on_focus(handle: i64, on_focus: f64) { let closure_ptr = unsafe { js_nanbox_get_pointer(on_focus) }; if closure_ptr != 0 { unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/toggle.rs b/crates/perry-ui-gtk4/src/widgets/toggle.rs index 3275e539c4..1f8b4cb372 100644 --- a/crates/perry-ui-gtk4/src/widgets/toggle.rs +++ b/crates/perry-ui-gtk4/src/widgets/toggle.rs @@ -24,7 +24,7 @@ const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -71,7 +71,11 @@ pub fn create(label_ptr: *const u8, on_change: f64) -> i64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + value, + ); } } }); diff --git a/crates/perry-ui-gtk4/src/widgets/tree_view.rs b/crates/perry-ui-gtk4/src/widgets/tree_view.rs index 8560f6cd89..d513b515d8 100644 --- a/crates/perry-ui-gtk4/src/widgets/tree_view.rs +++ b/crates/perry-ui-gtk4/src/widgets/tree_view.rs @@ -16,7 +16,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -115,7 +115,7 @@ pub fn create(root_node: i64, on_select: f64) -> i64 { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } }); } diff --git a/crates/perry-ui-gtk4/src/widgets/webview.rs b/crates/perry-ui-gtk4/src/widgets/webview.rs index 611f930d22..8313ba8806 100644 --- a/crates/perry-ui-gtk4/src/widgets/webview.rs +++ b/crates/perry-ui-gtk4/src/widgets/webview.rs @@ -14,8 +14,8 @@ use std::collections::HashMap; use webkit6::prelude::*; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_is_truthy(value: f64) -> i32; @@ -174,7 +174,8 @@ fn install_signal_handlers(handle: i64, webview: &webkit6::WebView) { let url_nb = nanbox_str(&url); let closure_ptr = unsafe { js_nanbox_get_pointer(on_should) } as *const u8; if !closure_ptr.is_null() { - let result = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let result = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) }; let bits = result.to_bits(); let is_undefined = bits == 0x7FFC_0000_0000_0001; let allow = is_undefined || unsafe { js_is_truthy(result) != 0 }; @@ -206,7 +207,7 @@ fn install_signal_handlers(handle: i64, webview: &webkit6::WebView) { let closure_ptr = unsafe { js_nanbox_get_pointer(on_loaded) } as *const u8; if !closure_ptr.is_null() { unsafe { - js_closure_call1(closure_ptr, url_nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb); } } }); @@ -226,7 +227,7 @@ fn install_signal_handlers(handle: i64, webview: &webkit6::WebView) { let closure_ptr = unsafe { js_nanbox_get_pointer(on_error) } as *const u8; if !closure_ptr.is_null() { unsafe { - js_closure_call2(closure_ptr, code, msg_nb); + js_closure_call2(closure_ptr, perry_ffi::JsThis::UNDEFINED, code, msg_nb); } } false @@ -299,7 +300,7 @@ pub fn evaluate_js(handle: i64, js_ptr: *const u8, callback: f64) { let closure_ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; if !closure_ptr.is_null() { unsafe { - js_closure_call1(closure_ptr, nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb); } } }); diff --git a/crates/perry-ui-gtk4/src/window.rs b/crates/perry-ui-gtk4/src/window.rs index 657b10feb4..a87d03626f 100644 --- a/crates/perry-ui-gtk4/src/window.rs +++ b/crates/perry-ui-gtk4/src/window.rs @@ -86,7 +86,7 @@ pub fn set_size(window_handle: i64, width: f64, height: f64) { pub fn on_focus_lost(window_handle: i64, callback: f64) { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; } WINDOWS.with(|w| { if let Some(window) = w.borrow().get(&window_handle) { @@ -95,7 +95,7 @@ pub fn on_focus_lost(window_handle: i64, callback: f64) { if !window.is_active() { let ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; unsafe { - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } }); diff --git a/crates/perry-ui-ios/src/adaptive_layout.rs b/crates/perry-ui-ios/src/adaptive_layout.rs index 9a5d199b74..cecbaaa9a1 100644 --- a/crates/perry-ui-ios/src/adaptive_layout.rs +++ b/crates/perry-ui-ios/src/adaptive_layout.rs @@ -25,7 +25,7 @@ extern "C" { fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -308,7 +308,11 @@ unsafe fn invoke_listener(callback: f64, snapshot: &LayoutSnapshot) { return; } js_run_stdlib_pump(); - js_closure_call1(closure, js_nanbox_pointer(object)); + js_closure_call1( + closure, + perry_ffi::JsThis::UNDEFINED, + js_nanbox_pointer(object), + ); js_promise_run_microtasks(); } diff --git a/crates/perry-ui-ios/src/app.rs b/crates/perry-ui-ios/src/app.rs index 68444bd995..59d84813b3 100644 --- a/crates/perry-ui-ios/src/app.rs +++ b/crates/perry-ui-ios/src/app.rs @@ -662,7 +662,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_callback_timer_tick() -> i32; fn js_interval_timer_tick() -> i32; fn js_frame_pump_default() -> i32; @@ -982,7 +982,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-ios/src/audio_playback.rs b/crates/perry-ui-ios/src/audio_playback.rs index 2c755519d4..14badc4fbc 100644 --- a/crates/perry-ui-ios/src/audio_playback.rs +++ b/crates/perry-ui-ios/src/audio_playback.rs @@ -16,9 +16,9 @@ use std::cell::RefCell; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; #[allow(dead_code)] - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -653,7 +653,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -712,7 +712,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-ui-ios/src/background.rs b/crates/perry-ui-ios/src/background.rs index 65125b6a2b..8297ed65be 100644 --- a/crates/perry-ui-ios/src/background.rs +++ b/crates/perry-ui-ios/src/background.rs @@ -23,7 +23,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_value_is_promise(value: f64) -> i32; } @@ -76,7 +76,7 @@ unsafe fn invoke_handler(handler: f64, task: *mut AnyObject) { js_run_stdlib_pump(); js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(handler) as *const u8; - let result = js_closure_call0(ptr); + let result = js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); js_promise_run_microtasks(); // If the handler returned a Promise, we'd ideally tie completion to its diff --git a/crates/perry-ui-ios/src/camera.rs b/crates/perry-ui-ios/src/camera.rs index d62f7c0faa..766a29b0f0 100644 --- a/crates/perry-ui-ios/src/camera.rs +++ b/crates/perry-ui-ios/src/camera.rs @@ -15,8 +15,8 @@ use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use crate::widgets; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -254,7 +254,12 @@ fn register_tap_handler_class() -> *const c_void { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, norm_x, norm_y); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + norm_x, + norm_y, + ); } }); } diff --git a/crates/perry-ui-ios/src/deeplinks.rs b/crates/perry-ui-ios/src/deeplinks.rs index f4d04b4ad2..09c3216e4f 100644 --- a/crates/perry-ui-ios/src/deeplinks.rs +++ b/crates/perry-ui-ios/src/deeplinks.rs @@ -28,7 +28,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; } @@ -69,7 +69,7 @@ unsafe fn invoke_handler(url: &str, source: &str) { if !ptr.is_null() { let url_v = nanbox_str(url); let src_v = nanbox_str(source); - js_closure_call2(ptr, url_v, src_v); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, url_v, src_v); } } } diff --git a/crates/perry-ui-ios/src/drag_drop.rs b/crates/perry-ui-ios/src/drag_drop.rs index 95f7e0f71e..b1105457a6 100644 --- a/crates/perry-ui-ios/src/drag_drop.rs +++ b/crates/perry-ui-ios/src/drag_drop.rs @@ -30,8 +30,8 @@ use std::ffi::c_void; use std::rc::Rc; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; @@ -80,7 +80,7 @@ unsafe fn call_provider(cb: f64) -> Option { if p.is_null() { return None; } - let ret = js_closure_call0(p); + let ret = js_closure_call0(p, perry_ffi::JsThis::UNDEFINED); let sh = js_jsvalue_to_string(ret); if sh.is_null() { None @@ -198,7 +198,7 @@ unsafe fn perform_drop_impl(interaction: *mut AnyObject, session: *mut AnyObject } let payload = js_nanbox_pointer(obj as i64); let cb_ptr = js_nanbox_get_pointer(a.cb) as *const u8; - js_closure_call1(cb_ptr, payload); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, payload); } // text diff --git a/crates/perry-ui-ios/src/file_dialog.rs b/crates/perry-ui-ios/src/file_dialog.rs index 1a3bf1fe7f..299693d226 100644 --- a/crates/perry-ui-ios/src/file_dialog.rs +++ b/crates/perry-ui-ios/src/file_dialog.rs @@ -1,5 +1,5 @@ extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -9,6 +9,10 @@ pub fn open_dialog(callback: f64) { unsafe { let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; // Call callback with TAG_UNDEFINED (user "cancelled") - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } diff --git a/crates/perry-ui-ios/src/geolocation.rs b/crates/perry-ui-ios/src/geolocation.rs index 1f93bf9c0b..097af727de 100644 --- a/crates/perry-ui-ios/src/geolocation.rs +++ b/crates/perry-ui-ios/src/geolocation.rs @@ -12,8 +12,15 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call4(closure: *const u8, arg0: f64, arg1: f64, arg2: f64, arg3: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call4( + closure: *const u8, + this: perry_ffi::JsThis, + arg0: f64, + arg1: f64, + arg2: f64, + arg3: f64, + ) -> f64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; } @@ -111,7 +118,14 @@ unsafe fn pump() { unsafe fn invoke_success(closure_f64: f64, lat: f64, lng: f64, accuracy: f64, timestamp_ms: f64) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; - js_closure_call4(ptr, lat, lng, accuracy, timestamp_ms); + js_closure_call4( + ptr, + perry_ffi::JsThis::UNDEFINED, + lat, + lng, + accuracy, + timestamp_ms, + ); } unsafe fn invoke_error(closure_f64: f64, message: &str) { @@ -121,14 +135,14 @@ unsafe fn invoke_error(closure_f64: f64, message: &str) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; let msg = nanbox_str(message); - js_closure_call1(ptr, msg); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, msg); } unsafe fn invoke_status(closure_f64: f64, status: &str) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; let msg = nanbox_str(status); - js_closure_call1(ptr, msg); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, msg); } fn auth_status_string(status: i32) -> &'static str { diff --git a/crates/perry-ui-ios/src/image_picker.rs b/crates/perry-ui-ios/src/image_picker.rs index 694b47293e..662fc591b7 100644 --- a/crates/perry-ui-ios/src/image_picker.rs +++ b/crates/perry-ui-ios/src/image_picker.rs @@ -7,7 +7,7 @@ //! sandboxed Photos asset URLs to NSTemporaryDirectory. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_array_alloc(capacity: u32) -> *mut std::ffi::c_void; fn js_nanbox_pointer(ptr: i64) -> f64; @@ -18,6 +18,6 @@ pub fn pick(_max_count: f64, _allow_multiple: f64, callback: f64) { let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb_ptr, nb); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-ios/src/location.rs b/crates/perry-ui-ios/src/location.rs index cf3cae5569..f98a49838f 100644 --- a/crates/perry-ui-ios/src/location.rs +++ b/crates/perry-ui-ios/src/location.rs @@ -7,7 +7,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; } // Raw ObjC runtime FFI for dynamic class registration @@ -51,7 +51,12 @@ unsafe fn invoke_callback(lat: f64, lon: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, lat, lon); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + lat, + lon, + ); } } diff --git a/crates/perry-ui-ios/src/media_playback.rs b/crates/perry-ui-ios/src/media_playback.rs index 636d95fe06..cb70889b4e 100644 --- a/crates/perry-ui-ios/src/media_playback.rs +++ b/crates/perry-ui-ios/src/media_playback.rs @@ -30,8 +30,8 @@ use std::sync::OnceLock; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; // Signature matches the rest of perry-ui-macos (see audio.rs:133, // lib.rs:113/118/1662). Runtime returns `*mut StringHeader` which is // i64-sized on Apple platforms. @@ -935,7 +935,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -944,7 +948,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-ios/src/menu.rs b/crates/perry-ui-ios/src/menu.rs index c784b41742..3ea8dd17c3 100644 --- a/crates/perry-ui-ios/src/menu.rs +++ b/crates/perry-ui-ios/src/menu.rs @@ -9,7 +9,7 @@ use objc2::runtime::{AnyClass, AnyObject, Sel}; use std::cell::RefCell; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -435,7 +435,7 @@ pub(crate) unsafe fn dispatch_menu_action(sender: *mut AnyObject) { }); if let Some(cb) = callback { let ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-ios/src/network.rs b/crates/perry-ui-ios/src/network.rs index 5cfa3cd0e9..5208a8bd5f 100644 --- a/crates/perry-ui-ios/src/network.rs +++ b/crates/perry-ui-ios/src/network.rs @@ -22,7 +22,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; } @@ -107,7 +107,7 @@ unsafe fn invoke_callback(closure_f64: f64, status: Status) { } let connected = bool_to_jsvalue(status.connected); let kind = nanbox_str(status.kind); - js_closure_call2(ptr, connected, kind); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, connected, kind); } fn classify(path: *mut c_void) -> Status { diff --git a/crates/perry-ui-ios/src/notifications.rs b/crates/perry-ui-ios/src/notifications.rs index a318c3cd2b..5bd0addad2 100644 --- a/crates/perry-ui-ios/src/notifications.rs +++ b/crates/perry-ui-ios/src/notifications.rs @@ -60,8 +60,8 @@ extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_string_from_bytes(data: *const u8, len: u32) -> *mut perry_runtime::string::StringHeader; - fn js_closure_call1(closure: *const u8, arg0: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg0: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_json_parse(text_ptr: *const perry_runtime::string::StringHeader) -> u64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -149,7 +149,7 @@ unsafe fn dispatch_tap(response: &AnyObject) { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call2(ptr, id_value, action_value); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, id_value, action_value); } } @@ -324,7 +324,7 @@ pub unsafe fn dispatch_device_token(device_token: *mut AnyObject) { let boxed = js_nanbox_string(str_ptr as i64); let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, boxed); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, boxed); } } @@ -406,7 +406,7 @@ pub unsafe fn dispatch_remote_payload_with_completion( } return; } - let result = js_closure_call1(cb_ptr, payload_value); + let result = js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, payload_value); let Some(block) = completion_block else { // Caller passed a null completion (e.g., direct invocation from a @@ -512,7 +512,7 @@ pub unsafe fn dispatch_remote_payload(user_info: *mut AnyObject) { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, parsed_f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, parsed_f64); } } diff --git a/crates/perry-ui-ios/src/pointer.rs b/crates/perry-ui-ios/src/pointer.rs index 26d9cf2f4d..8529d3679f 100644 --- a/crates/perry-ui-ios/src/pointer.rs +++ b/crates/perry-ui-ios/src/pointer.rs @@ -23,7 +23,7 @@ use std::collections::HashMap; use crate::widgets::get_widget; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -142,7 +142,7 @@ fn dispatch_phase(recog: &PerryPointerRecognizer, touches: &AnyObject, phase: u3 return; } let pe = js_pointer_event_new(local.x, local.y, 0, POINTER_TYPE_TOUCH); - js_closure_call1(closure_ptr as *const u8, pe); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, pe); } } diff --git a/crates/perry-ui-ios/src/state.rs b/crates/perry-ui-ios/src/state.rs index 13b70e46e4..eded1088c3 100644 --- a/crates/perry-ui-ios/src/state.rs +++ b/crates/perry-ui-ios/src/state.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -270,7 +270,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -308,7 +308,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-ios/src/widgets/alert.rs b/crates/perry-ui-ios/src/widgets/alert.rs index a8eea1bb3b..9357fcd3e3 100644 --- a/crates/perry-ui-ios/src/widgets/alert.rs +++ b/crates/perry-ui-ios/src/widgets/alert.rs @@ -5,7 +5,7 @@ use objc2::runtime::{AnyClass, AnyObject}; use objc2_foundation::NSString; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_array_get_length(arr: i64) -> i64; fn js_array_get_element(arr: i64, index: i64) -> f64; @@ -26,7 +26,7 @@ unsafe extern "C" fn alert_callback_trampoline(ctx: *mut std::ffi::c_void) { let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); } }); } diff --git a/crates/perry-ui-ios/src/widgets/bottom_nav.rs b/crates/perry-ui-ios/src/widgets/bottom_nav.rs index 03e0e769ca..9090e3a565 100644 --- a/crates/perry-ui-ios/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-ios/src/widgets/bottom_nav.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -92,7 +92,7 @@ unsafe extern "C" fn trampoline(ctx: *mut std::ffi::c_void) { let pkg = Box::from_raw(ctx as *mut (f64, i64)); let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); }); } diff --git a/crates/perry-ui-ios/src/widgets/button.rs b/crates/perry-ui-ios/src/widgets/button.rs index 1fd2e04c0b..69df4bbe72 100644 --- a/crates/perry-ui-ios/src/widgets/button.rs +++ b/crates/perry-ui-ios/src/widgets/button.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_ios_button_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_ } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; // dispatch_get_main_queue() is a macro; the actual symbol is _dispatch_main_q static _dispatch_main_q: std::ffi::c_void; @@ -39,7 +39,7 @@ unsafe extern "C" fn button_callback_trampoline(context: *mut std::ffi::c_void) let _ = std::panic::catch_unwind(|| { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); }); } diff --git a/crates/perry-ui-ios/src/widgets/calendar.rs b/crates/perry-ui-ios/src/widgets/calendar.rs index 4e9d325cde..e4fba24015 100644 --- a/crates/perry-ui-ios/src/widgets/calendar.rs +++ b/crates/perry-ui-ios/src/widgets/calendar.rs @@ -17,7 +17,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -63,7 +63,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-ios/src/widgets/combobox.rs b/crates/perry-ui-ios/src/widgets/combobox.rs index 44cb323acd..aeb43b4fb9 100644 --- a/crates/perry-ui-ios/src/widgets/combobox.rs +++ b/crates/perry-ui-ios/src/widgets/combobox.rs @@ -24,7 +24,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -60,7 +60,7 @@ fn fire_callback(handle: i64, value: &str) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); }), ); } diff --git a/crates/perry-ui-ios/src/widgets/date_picker.rs b/crates/perry-ui-ios/src/widgets/date_picker.rs index f894f3bfd4..7395289bfb 100644 --- a/crates/perry-ui-ios/src/widgets/date_picker.rs +++ b/crates/perry-ui-ios/src/widgets/date_picker.rs @@ -18,7 +18,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -64,7 +64,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-ios/src/widgets/image_gallery.rs b/crates/perry-ui-ios/src/widgets/image_gallery.rs index 35362479a2..421a810c15 100644 --- a/crates/perry-ui-ios/src/widgets/image_gallery.rs +++ b/crates/perry-ui-ios/src/widgets/image_gallery.rs @@ -17,7 +17,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -84,7 +84,7 @@ define_class!( }); if closure != 0.0 { let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call1(ptr, new_index as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, new_index as f64); } } } diff --git a/crates/perry-ui-ios/src/widgets/mod.rs b/crates/perry-ui-ios/src/widgets/mod.rs index 6740e21648..fa5ccf8ad9 100644 --- a/crates/perry-ui-ios/src/widgets/mod.rs +++ b/crates/perry-ui-ios/src/widgets/mod.rs @@ -734,7 +734,7 @@ pub fn set_hugging_priority(handle: i64, priority: f64) { use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } diff --git a/crates/perry-ui-ios/src/widgets/picker.rs b/crates/perry-ui-ios/src/widgets/picker.rs index 3df9261023..7b134ebe44 100644 --- a/crates/perry-ui-ios/src/widgets/picker.rs +++ b/crates/perry-ui-ios/src/widgets/picker.rs @@ -21,7 +21,7 @@ pub(crate) fn scan_ios_picker_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_ } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; // dispatch_get_main_queue() is a macro; the actual symbol is _dispatch_main_q static _dispatch_main_q: std::ffi::c_void; @@ -43,7 +43,11 @@ unsafe extern "C" fn picker_callback_trampoline(context: *mut std::ffi::c_void) let _ = std::panic::catch_unwind(|| { let payload = Box::from_raw(context as *mut PickerDispatch); let closure_ptr = js_nanbox_get_pointer(payload.closure_f64); - js_closure_call1(closure_ptr as *const u8, payload.index); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + payload.index, + ); }); } diff --git a/crates/perry-ui-ios/src/widgets/rich_text.rs b/crates/perry-ui-ios/src/widgets/rich_text.rs index fc3db8961b..43b3a750e8 100644 --- a/crates/perry-ui-ios/src/widgets/rich_text.rs +++ b/crates/perry-ui-ios/src/widgets/rich_text.rs @@ -19,7 +19,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -74,7 +74,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-ios/src/widgets/scrollview.rs b/crates/perry-ui-ios/src/widgets/scrollview.rs index da9d5ee2ea..76e956598c 100644 --- a/crates/perry-ui-ios/src/widgets/scrollview.rs +++ b/crates/perry-ui-ios/src/widgets/scrollview.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use super::scroll_mutation_gate::ScrollMutationGate; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -24,7 +24,7 @@ unsafe extern "C" fn refresh_callback_trampoline(context: *mut std::ffi::c_void) let _ = std::panic::catch_unwind(|| { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); }); } diff --git a/crates/perry-ui-ios/src/widgets/securefield.rs b/crates/perry-ui-ios/src/widgets/securefield.rs index 28b4a6daa5..5b8f79a638 100644 --- a/crates/perry-ui-ios/src/widgets/securefield.rs +++ b/crates/perry-ui-ios/src/widgets/securefield.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_ios_securefield_gc_roots(visitor: &mut perry_ffi::GcRootVisit } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -50,7 +50,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-ios/src/widgets/slider.rs b/crates/perry-ui-ios/src/widgets/slider.rs index 4c08d89080..87c3f6be9a 100644 --- a/crates/perry-ui-ios/src/widgets/slider.rs +++ b/crates/perry-ui-ios/src/widgets/slider.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_ios_slider_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_ } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -43,7 +43,7 @@ define_class!( let value: f32 = unsafe { msg_send![sender, value] }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value as f64); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value as f64); } } }); diff --git a/crates/perry-ui-ios/src/widgets/tabbar.rs b/crates/perry-ui-ios/src/widgets/tabbar.rs index 5125bf1543..0284a744a9 100644 --- a/crates/perry-ui-ios/src/widgets/tabbar.rs +++ b/crates/perry-ui-ios/src/widgets/tabbar.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -116,7 +116,11 @@ unsafe extern "C" fn tab_callback_trampoline(context: *mut std::ffi::c_void) { let packed = Box::from_raw(context as *mut (f64, i64)); let (closure_f64, tab_index) = *packed; let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call1(closure_ptr as *const u8, tab_index as f64); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + tab_index as f64, + ); }); } diff --git a/crates/perry-ui-ios/src/widgets/textarea.rs b/crates/perry-ui-ios/src/widgets/textarea.rs index 871396d53c..e16b93b383 100644 --- a/crates/perry-ui-ios/src/widgets/textarea.rs +++ b/crates/perry-ui-ios/src/widgets/textarea.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_ios_textarea_gc_roots(visitor: &mut perry_ffi::GcRootVisitor< } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -51,7 +51,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-ios/src/widgets/textfield.rs b/crates/perry-ui-ios/src/widgets/textfield.rs index 19c03c410f..e773462380 100644 --- a/crates/perry-ui-ios/src/widgets/textfield.rs +++ b/crates/perry-ui-ios/src/widgets/textfield.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_ios_textfield_gc_roots(visitor: &mut perry_ffi::GcRootVisitor } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -55,7 +55,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); @@ -247,7 +247,7 @@ define_class!( let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-ios/src/widgets/toggle.rs b/crates/perry-ui-ios/src/widgets/toggle.rs index 51368f87ab..8fc9033cd7 100644 --- a/crates/perry-ui-ios/src/widgets/toggle.rs +++ b/crates/perry-ui-ios/src/widgets/toggle.rs @@ -23,7 +23,7 @@ const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -53,7 +53,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value); } } }); diff --git a/crates/perry-ui-ios/src/widgets/tree_view.rs b/crates/perry-ui-ios/src/widgets/tree_view.rs index 56fc6593a4..dfb4cc1336 100644 --- a/crates/perry-ui-ios/src/widgets/tree_view.rs +++ b/crates/perry-ui-ios/src/widgets/tree_view.rs @@ -27,7 +27,7 @@ use objc2_ui_kit::UIView; use std::cell::{Cell, RefCell}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -311,7 +311,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on_select) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); }), ); } diff --git a/crates/perry-ui-ios/src/widgets/webview.rs b/crates/perry-ui-ios/src/widgets/webview.rs index fc0ccb74a4..920aa1c238 100644 --- a/crates/perry-ui-ios/src/widgets/webview.rs +++ b/crates/perry-ui-ios/src/widgets/webview.rs @@ -16,8 +16,8 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -153,7 +153,7 @@ define_class!( crate::catch_callback_panic( "webview onShouldNavigate", std::panic::AssertUnwindSafe(|| { - let r = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let r = unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) }; result_cell_ref.set(r); }), ); @@ -202,7 +202,7 @@ define_class!( let url_nb = nanbox_str(&url_str); let closure_ptr = js_nanbox_get_pointer(on_loaded) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, url_nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb); } }), ); @@ -264,7 +264,12 @@ impl PerryWebViewDelegate { let msg_nb = nanbox_str(&msg); let closure_ptr = js_nanbox_get_pointer(on_error) as *const u8; if !closure_ptr.is_null() { - js_closure_call2(closure_ptr, code as f64, msg_nb); + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + code as f64, + msg_nb, + ); } }), ); @@ -415,7 +420,7 @@ pub fn evaluate_js(handle: i64, js_ptr: *const u8, callback: f64) { let nb = nanbox_str(&s); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb); } }), ); diff --git a/crates/perry-ui-ios/src/widgets/wheel_picker.rs b/crates/perry-ui-ios/src/widgets/wheel_picker.rs index de264cb2a7..8c652ebb7c 100644 --- a/crates/perry-ui-ios/src/widgets/wheel_picker.rs +++ b/crates/perry-ui-ios/src/widgets/wheel_picker.rs @@ -26,7 +26,7 @@ pub(crate) fn scan_ios_wheel_picker_gc_roots(visitor: &mut perry_ffi::GcRootVisi } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -45,7 +45,7 @@ unsafe extern "C" fn callback_trampoline(context: *mut std::ffi::c_void) { let _ = std::panic::catch_unwind(|| { let payload = Box::from_raw(context as *mut WheelPickerDispatch); let closure = js_nanbox_get_pointer(payload.closure) as *const u8; - js_closure_call1(closure, payload.index); + js_closure_call1(closure, perry_ffi::JsThis::UNDEFINED, payload.index); }); } diff --git a/crates/perry-ui-macos/src/app.rs b/crates/perry-ui-macos/src/app.rs index a55843c89a..3918417f91 100644 --- a/crates/perry-ui-macos/src/app.rs +++ b/crates/perry-ui-macos/src/app.rs @@ -786,14 +786,14 @@ fn invoke_global_hotkey_callback(key: usize) { let callback_ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; if !callback_ptr.is_null() { unsafe { - js_closure_call0(callback_ptr); + js_closure_call0(callback_ptr, perry_ffi::JsThis::UNDEFINED); } } } } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -818,7 +818,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); @@ -1306,7 +1306,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); @@ -1535,7 +1535,7 @@ define_class!( fn invoke_terminate_callback() { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } let cb = ON_TERMINATE_CALLBACK.with(|c| *c.borrow()); @@ -1543,7 +1543,11 @@ fn invoke_terminate_callback() { unsafe { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, f64::from_bits(0x7FFC_0000_0000_0001)); // undefined + js_closure_call1( + ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); // undefined } } } @@ -1551,7 +1555,7 @@ fn invoke_terminate_callback() { fn invoke_activate_callback() { extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } let cb = ON_ACTIVATE_CALLBACK.with(|c| *c.borrow()); @@ -1559,7 +1563,11 @@ fn invoke_activate_callback() { unsafe { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, f64::from_bits(0x7FFC_0000_0000_0001)); // undefined + js_closure_call1( + ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); // undefined } } } @@ -1772,7 +1780,7 @@ thread_local! { pub fn window_on_focus_lost(window_handle: i64, callback: f64) { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; } WINDOWS.with(|w| { let windows = w.borrow(); @@ -1791,7 +1799,7 @@ pub fn window_on_focus_lost(window_handle: i64, callback: f64) { .with(|callbacks| callbacks.borrow().get(&window_handle).copied()); if let Some(callback) = callback { let ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } }); let _: Retained = objc2::msg_send![ diff --git a/crates/perry-ui-macos/src/audio_playback.rs b/crates/perry-ui-macos/src/audio_playback.rs index c980817b32..05892f31b3 100644 --- a/crates/perry-ui-macos/src/audio_playback.rs +++ b/crates/perry-ui-macos/src/audio_playback.rs @@ -18,9 +18,9 @@ use std::cell::RefCell; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; #[allow(dead_code)] - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -650,7 +650,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -709,7 +709,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-ui-macos/src/background.rs b/crates/perry-ui-macos/src/background.rs index 5f727bd419..4855c0502b 100644 --- a/crates/perry-ui-macos/src/background.rs +++ b/crates/perry-ui-macos/src/background.rs @@ -34,7 +34,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_value_is_promise(value: f64) -> i32; } @@ -87,7 +87,7 @@ unsafe fn invoke_handler(handler: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(handler) as *const u8; - let result = js_closure_call0(ptr); + let result = js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); js_promise_run_microtasks(); let _ = js_value_is_promise(result); } diff --git a/crates/perry-ui-macos/src/deeplinks.rs b/crates/perry-ui-macos/src/deeplinks.rs index b113296360..7bee49c088 100644 --- a/crates/perry-ui-macos/src/deeplinks.rs +++ b/crates/perry-ui-macos/src/deeplinks.rs @@ -33,7 +33,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -72,7 +72,7 @@ unsafe fn invoke_handler(url: &str, source: &str) { if !ptr.is_null() { let url_v = nanbox_str(url); let src_v = nanbox_str(source); - js_closure_call2(ptr, url_v, src_v); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, url_v, src_v); } } } diff --git a/crates/perry-ui-macos/src/drag_drop.rs b/crates/perry-ui-macos/src/drag_drop.rs index 261da4b368..29cfdcbaad 100644 --- a/crates/perry-ui-macos/src/drag_drop.rs +++ b/crates/perry-ui-macos/src/drag_drop.rs @@ -36,8 +36,8 @@ use std::collections::HashMap; use std::ffi::{c_void, CString}; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; @@ -97,7 +97,7 @@ unsafe fn call_provider(cb: f64) -> Option { if p.is_null() { return None; } - let ret = js_closure_call0(p); + let ret = js_closure_call0(p, perry_ffi::JsThis::UNDEFINED); let sh = js_jsvalue_to_string(ret); if sh.is_null() { None @@ -241,7 +241,7 @@ extern "C-unwind" fn perform_drag(this: *mut AnyObject, _cmd: Sel, sender: *mut let payload = js_nanbox_pointer(obj as i64); let cb_ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call1(cb_ptr, payload); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, payload); } Bool::YES } diff --git a/crates/perry-ui-macos/src/file_dialog.rs b/crates/perry-ui-macos/src/file_dialog.rs index d5191d5b61..76c17cf137 100644 --- a/crates/perry-ui-macos/src/file_dialog.rs +++ b/crates/perry-ui-macos/src/file_dialog.rs @@ -4,7 +4,7 @@ use objc2_app_kit::NSOpenPanel; use objc2_foundation::{MainThreadMarker, NSString}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -36,13 +36,21 @@ pub fn open_dialog(callback: f64) { let bytes = rust_str.as_bytes(); let str_ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } else { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } else { // User cancelled - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } } @@ -72,12 +80,20 @@ pub fn open_folder_dialog(callback: f64) { let bytes = rust_str.as_bytes(); let str_ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } else { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } else { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } } @@ -113,12 +129,20 @@ pub fn save_dialog(callback: f64, default_name_ptr: *const u8, _allowed_types_pt let bytes = rust_str.as_bytes(); let str_ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } else { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } else { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } } diff --git a/crates/perry-ui-macos/src/geolocation.rs b/crates/perry-ui-macos/src/geolocation.rs index d629e5992a..384e263099 100644 --- a/crates/perry-ui-macos/src/geolocation.rs +++ b/crates/perry-ui-macos/src/geolocation.rs @@ -18,8 +18,15 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call4(closure: *const u8, arg0: f64, arg1: f64, arg2: f64, arg3: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call4( + closure: *const u8, + this: perry_ffi::JsThis, + arg0: f64, + arg1: f64, + arg2: f64, + arg3: f64, + ) -> f64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -113,7 +120,14 @@ unsafe fn pump() { unsafe fn invoke_success(closure_f64: f64, lat: f64, lng: f64, accuracy: f64, timestamp_ms: f64) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; - js_closure_call4(ptr, lat, lng, accuracy, timestamp_ms); + js_closure_call4( + ptr, + perry_ffi::JsThis::UNDEFINED, + lat, + lng, + accuracy, + timestamp_ms, + ); } unsafe fn invoke_error(closure_f64: f64, message: &str) { @@ -123,14 +137,14 @@ unsafe fn invoke_error(closure_f64: f64, message: &str) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; let msg = nanbox_str(message); - js_closure_call1(ptr, msg); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, msg); } unsafe fn invoke_status(closure_f64: f64, status: &str) { pump(); let ptr = js_nanbox_get_pointer(closure_f64) as *const u8; let msg = nanbox_str(status); - js_closure_call1(ptr, msg); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, msg); } fn auth_status_string(status: i32) -> &'static str { diff --git a/crates/perry-ui-macos/src/image_picker.rs b/crates/perry-ui-macos/src/image_picker.rs index 9a3b908a05..614038e3f0 100644 --- a/crates/perry-ui-macos/src/image_picker.rs +++ b/crates/perry-ui-macos/src/image_picker.rs @@ -11,7 +11,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; fn js_array_alloc(capacity: u32) -> *mut std::ffi::c_void; fn js_nanbox_pointer(ptr: i64) -> f64; @@ -82,6 +82,6 @@ pub fn pick(max_count: f64, allow_multiple: f64, callback: f64) { let nb_arr = js_nanbox_pointer(cur as i64); let cb_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb_ptr, nb_arr); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, nb_arr); } } diff --git a/crates/perry-ui-macos/src/location.rs b/crates/perry-ui-macos/src/location.rs index db4ab121b8..ef3c5940fd 100644 --- a/crates/perry-ui-macos/src/location.rs +++ b/crates/perry-ui-macos/src/location.rs @@ -9,7 +9,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; } // Raw ObjC runtime FFI for dynamic class registration @@ -46,7 +46,12 @@ unsafe fn invoke_callback(lat: f64, lon: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, lat, lon); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + lat, + lon, + ); } } diff --git a/crates/perry-ui-macos/src/media_playback.rs b/crates/perry-ui-macos/src/media_playback.rs index 23ac2a5106..76e1e0a635 100644 --- a/crates/perry-ui-macos/src/media_playback.rs +++ b/crates/perry-ui-macos/src/media_playback.rs @@ -29,8 +29,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; // Signature matches the rest of perry-ui-macos (see audio.rs:133, // lib.rs:113/118/1662). Runtime returns `*mut StringHeader` which is // i64-sized on Apple platforms. @@ -740,7 +740,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -749,7 +753,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-macos/src/menu.rs b/crates/perry-ui-macos/src/menu.rs index 9bdc0fd1b3..bd15b9a22e 100644 --- a/crates/perry-ui-macos/src/menu.rs +++ b/crates/perry-ui-macos/src/menu.rs @@ -23,7 +23,7 @@ pub(crate) fn scan_macos_menu_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_ } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -51,7 +51,7 @@ define_class!( if let Some(cf) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(cf) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); diff --git a/crates/perry-ui-macos/src/network.rs b/crates/perry-ui-macos/src/network.rs index 127eca7803..f741fd69f9 100644 --- a/crates/perry-ui-macos/src/network.rs +++ b/crates/perry-ui-macos/src/network.rs @@ -18,7 +18,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -98,7 +98,7 @@ unsafe fn invoke_callback(closure_f64: f64, status: Status) { } let connected = bool_to_jsvalue(status.connected); let kind = nanbox_str(status.kind); - js_closure_call2(ptr, connected, kind); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, connected, kind); } fn classify(path: *mut c_void) -> Status { diff --git a/crates/perry-ui-macos/src/notifications.rs b/crates/perry-ui-macos/src/notifications.rs index 5bcea40b28..5cf71d7a79 100644 --- a/crates/perry-ui-macos/src/notifications.rs +++ b/crates/perry-ui-macos/src/notifications.rs @@ -38,8 +38,8 @@ pub(crate) fn scan_macos_notifications_gc_roots(visitor: &mut perry_ffi::GcRootV extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; - fn js_closure_call1(closure: *const u8, arg0: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg0: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_json_parse(text_ptr: *const perry_ffi::StringHeader) -> u64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -142,7 +142,7 @@ unsafe fn dispatch_tap(response: &AnyObject) { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call2(ptr, id_value, action_value); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, id_value, action_value); } } @@ -301,7 +301,7 @@ pub unsafe fn dispatch_device_token(device_token: *mut AnyObject) { let boxed = js_nanbox_string(str_ptr as i64); let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, boxed); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, boxed); } } @@ -368,7 +368,7 @@ pub unsafe fn dispatch_remote_payload(user_info: *mut AnyObject) { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, parsed_f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, parsed_f64); } } diff --git a/crates/perry-ui-macos/src/pointer.rs b/crates/perry-ui-macos/src/pointer.rs index 6e19ff7392..d610ab1e9e 100644 --- a/crates/perry-ui-macos/src/pointer.rs +++ b/crates/perry-ui-macos/src/pointer.rs @@ -29,7 +29,7 @@ use std::collections::HashMap; use crate::widgets::get_widget; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -296,7 +296,7 @@ unsafe fn handle_event(event: *mut AnyObject) { continue; } let pe = js_pointer_event_new(local_x, local_y, button, POINTER_TYPE_MOUSE); - js_closure_call1(closure_ptr as *const u8, pe); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, pe); } } @@ -340,7 +340,11 @@ unsafe fn dispatch_hover_transitions(event_window: *mut AnyObject, win_loc: CGPo continue; } let bool_bits = if inside { TAG_TRUE } else { TAG_FALSE }; - js_closure_call1(closure_ptr as *const u8, f64::from_bits(bool_bits)); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(bool_bits), + ); } } diff --git a/crates/perry-ui-macos/src/state.rs b/crates/perry-ui-macos/src/state.rs index 92fb232e1e..5b7e5c2afa 100644 --- a/crates/perry-ui-macos/src/state.rs +++ b/crates/perry-ui-macos/src/state.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -276,7 +276,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -317,7 +317,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-macos/src/tray.rs b/crates/perry-ui-macos/src/tray.rs index e53a58ca32..c345220e95 100644 --- a/crates/perry-ui-macos/src/tray.rs +++ b/crates/perry-ui-macos/src/tray.rs @@ -30,7 +30,7 @@ pub(crate) fn scan_macos_tray_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<'_ } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -58,7 +58,7 @@ define_class!( if let Some(cf) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(cf) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); diff --git a/crates/perry-ui-macos/src/widgets/alert.rs b/crates/perry-ui-macos/src/widgets/alert.rs index d3a168435d..fba4b6f8c3 100644 --- a/crates/perry-ui-macos/src/widgets/alert.rs +++ b/crates/perry-ui-macos/src/widgets/alert.rs @@ -5,7 +5,7 @@ use objc2::runtime::{AnyClass, AnyObject}; use objc2_foundation::{MainThreadMarker, NSString}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -60,7 +60,7 @@ pub fn show(title_ptr: *const u8, message_ptr: *const u8, arr_ptr: i64, callback let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, button_index); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, button_index); } } } diff --git a/crates/perry-ui-macos/src/widgets/bottom_nav.rs b/crates/perry-ui-macos/src/widgets/bottom_nav.rs index c330907798..f6dc02a17c 100644 --- a/crates/perry-ui-macos/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-macos/src/widgets/bottom_nav.rs @@ -20,7 +20,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -87,7 +87,7 @@ define_class!( if on_select != 0.0 { unsafe { let closure_ptr = js_nanbox_get_pointer(on_select) as *const u8; - js_closure_call1(closure_ptr, item_index as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, item_index as f64); } } } diff --git a/crates/perry-ui-macos/src/widgets/button.rs b/crates/perry-ui-macos/src/widgets/button.rs index a8fbe9475e..bd3cfcce42 100644 --- a/crates/perry-ui-macos/src/widgets/button.rs +++ b/crates/perry-ui-macos/src/widgets/button.rs @@ -20,7 +20,7 @@ pub(crate) fn scan_macos_button_gc_roots(visitor: &mut perry_ffi::GcRootVisitor< } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -46,7 +46,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); diff --git a/crates/perry-ui-macos/src/widgets/calendar.rs b/crates/perry-ui-macos/src/widgets/calendar.rs index 4f90bd2c1a..c143e6d2bc 100644 --- a/crates/perry-ui-macos/src/widgets/calendar.rs +++ b/crates/perry-ui-macos/src/widgets/calendar.rs @@ -21,7 +21,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -67,7 +67,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } })); } diff --git a/crates/perry-ui-macos/src/widgets/combobox.rs b/crates/perry-ui-macos/src/widgets/combobox.rs index 08a13554a3..a90d0cc7fe 100644 --- a/crates/perry-ui-macos/src/widgets/combobox.rs +++ b/crates/perry-ui-macos/src/widgets/combobox.rs @@ -31,7 +31,7 @@ pub(crate) fn scan_macos_combobox_gc_roots(visitor: &mut perry_ffi::GcRootVisito } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -83,7 +83,7 @@ fn fire_callback(target_addr: usize, handle: i64) { let header_ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let arg = js_nanbox_string(header_ptr as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } }), ); diff --git a/crates/perry-ui-macos/src/widgets/command_palette.rs b/crates/perry-ui-macos/src/widgets/command_palette.rs index f6c15d0b86..6d7e889cdb 100644 --- a/crates/perry-ui-macos/src/widgets/command_palette.rs +++ b/crates/perry-ui-macos/src/widgets/command_palette.rs @@ -22,7 +22,7 @@ use objc2_foundation::{NSObject, NSString}; use std::cell::{Cell, RefCell}; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -185,7 +185,7 @@ unsafe fn invoke_row(row: i64) { let on_run = COMMANDS.with(|c| c.borrow().get(cmd_idx).map(|cmd| cmd.on_run).unwrap_or(0.0)); if on_run != 0.0 { let closure_ptr = js_nanbox_get_pointer(on_run) as *const u8; - js_closure_call0(closure_ptr); + js_closure_call0(closure_ptr, perry_ffi::JsThis::UNDEFINED); } hide(); } diff --git a/crates/perry-ui-macos/src/widgets/date_picker.rs b/crates/perry-ui-macos/src/widgets/date_picker.rs index 7890d6ab13..3d2b6edfd8 100644 --- a/crates/perry-ui-macos/src/widgets/date_picker.rs +++ b/crates/perry-ui-macos/src/widgets/date_picker.rs @@ -21,7 +21,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -67,7 +67,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } })); } diff --git a/crates/perry-ui-macos/src/widgets/foreach_registry.rs b/crates/perry-ui-macos/src/widgets/foreach_registry.rs index fd37a49e08..bd64bc0730 100644 --- a/crates/perry-ui-macos/src/widgets/foreach_registry.rs +++ b/crates/perry-ui-macos/src/widgets/foreach_registry.rs @@ -22,7 +22,7 @@ //! and call `widgets::add_child`. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -48,7 +48,8 @@ pub extern "C" fn render_handler(container_handle: i64, render_closure: f64, cou return; } for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; if child_handle != 0 { crate::widgets::add_child(container_handle, child_handle); diff --git a/crates/perry-ui-macos/src/widgets/image_gallery.rs b/crates/perry-ui-macos/src/widgets/image_gallery.rs index 0d255e8331..aa5e6839b3 100644 --- a/crates/perry-ui-macos/src/widgets/image_gallery.rs +++ b/crates/perry-ui-macos/src/widgets/image_gallery.rs @@ -22,7 +22,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -264,7 +264,7 @@ fn recompute_index(handle: i64) { if new_index >= 0 && closure != 0.0 { unsafe { let closure_ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call1(closure_ptr, new_index as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, new_index as f64); } } } diff --git a/crates/perry-ui-macos/src/widgets/lazyvstack.rs b/crates/perry-ui-macos/src/widgets/lazyvstack.rs index 08b187d922..70cff63bc3 100644 --- a/crates/perry-ui-macos/src/widgets/lazyvstack.rs +++ b/crates/perry-ui-macos/src/widgets/lazyvstack.rs @@ -8,8 +8,8 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -69,7 +69,7 @@ define_class!( return std::ptr::null_mut(); } let render_ptr = unsafe { js_nanbox_get_pointer(render_closure) } as *const u8; - let child_f64 = unsafe { js_closure_call1(render_ptr, row as f64) }; + let child_f64 = unsafe { js_closure_call1(render_ptr, perry_ffi::JsThis::UNDEFINED, row as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; if let Some(view) = super::get_widget(child_handle) { Retained::as_ptr(&view) as *mut NSView @@ -334,7 +334,7 @@ fn check_scroll_end(handle: i64) { if should_fire { unsafe { let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-macos/src/widgets/mod.rs b/crates/perry-ui-macos/src/widgets/mod.rs index 7e38de29ed..abd2e7c4e8 100644 --- a/crates/perry-ui-macos/src/widgets/mod.rs +++ b/crates/perry-ui-macos/src/widgets/mod.rs @@ -1081,7 +1081,7 @@ pub fn match_parent_width(child_handle: i64) { // ============================================================================= extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -1254,7 +1254,7 @@ objc2::define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); diff --git a/crates/perry-ui-macos/src/widgets/picker.rs b/crates/perry-ui-macos/src/widgets/picker.rs index 5df1bf3a5d..5d674f5d6b 100644 --- a/crates/perry-ui-macos/src/widgets/picker.rs +++ b/crates/perry-ui-macos/src/widgets/picker.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_macos_picker_gc_roots(visitor: &mut perry_ffi::GcRootVisitor< } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -45,7 +45,7 @@ define_class!( let index: i64 = unsafe { msg_send![&*view, indexOfSelectedItem] }; let closure_ptr = unsafe { js_nanbox_get_pointer(callback) } as *const u8; unsafe { - js_closure_call1(closure_ptr, index as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, index as f64); } } } diff --git a/crates/perry-ui-macos/src/widgets/rich_text.rs b/crates/perry-ui-macos/src/widgets/rich_text.rs index da9f19c367..d15d17185a 100644 --- a/crates/perry-ui-macos/src/widgets/rich_text.rs +++ b/crates/perry-ui-macos/src/widgets/rich_text.rs @@ -26,7 +26,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -85,7 +85,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } })); } diff --git a/crates/perry-ui-macos/src/widgets/scrollview.rs b/crates/perry-ui-macos/src/widgets/scrollview.rs index cac77febf2..2534865cd6 100644 --- a/crates/perry-ui-macos/src/widgets/scrollview.rs +++ b/crates/perry-ui-macos/src/widgets/scrollview.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use std::sync::Once; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -298,7 +298,7 @@ fn check_scroll_end(handle: i64) { if should_fire { unsafe { let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-macos/src/widgets/securefield.rs b/crates/perry-ui-macos/src/widgets/securefield.rs index 589af841cd..ad00e42bef 100644 --- a/crates/perry-ui-macos/src/widgets/securefield.rs +++ b/crates/perry-ui-macos/src/widgets/securefield.rs @@ -22,7 +22,7 @@ pub(crate) fn scan_macos_securefield_gc_roots(visitor: &mut perry_ffi::GcRootVis } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -68,7 +68,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-macos/src/widgets/slider.rs b/crates/perry-ui-macos/src/widgets/slider.rs index 6ae847ad55..1bb71d877c 100644 --- a/crates/perry-ui-macos/src/widgets/slider.rs +++ b/crates/perry-ui-macos/src/widgets/slider.rs @@ -20,7 +20,7 @@ pub(crate) fn scan_macos_slider_gc_roots(visitor: &mut perry_ffi::GcRootVisitor< } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -45,7 +45,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value); } } }); diff --git a/crates/perry-ui-macos/src/widgets/table.rs b/crates/perry-ui-macos/src/widgets/table.rs index dbc77dbf9d..e2c8dc55d0 100644 --- a/crates/perry-ui-macos/src/widgets/table.rs +++ b/crates/perry-ui-macos/src/widgets/table.rs @@ -8,8 +8,8 @@ use objc2_foundation::{MainThreadMarker, NSObject, NSString}; use std::cell::RefCell; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -129,7 +129,7 @@ define_class!( return std::ptr::null_mut(); } let render_ptr = unsafe { js_nanbox_get_pointer(render_closure) } as *const u8; - let child_f64 = unsafe { js_closure_call2(render_ptr, row as f64, col as f64) }; + let child_f64 = unsafe { js_closure_call2(render_ptr, perry_ffi::JsThis::UNDEFINED, row as f64, col as f64) }; // Issue #556: tag-validate before treating the closure // return as a widget handle. The closure SHOULD return a // Widget (POINTER_TAG = 0x7FFD), but if user code @@ -198,7 +198,7 @@ define_class!( let ascending: objc2::runtime::Bool = msg_send![first, ascending]; let asc_f = if ascending.as_bool() { 1.0 } else { 0.0 }; let closure_ptr = js_nanbox_get_pointer(sort_closure) as *const u8; - js_closure_call2(closure_ptr, col_index as f64, asc_f); + js_closure_call2(closure_ptr, perry_ffi::JsThis::UNDEFINED, col_index as f64, asc_f); } })); } @@ -225,7 +225,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(select_closure) } as *const u8; unsafe { - js_closure_call1(closure_ptr, selected_row as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, selected_row as f64); } } })); diff --git a/crates/perry-ui-macos/src/widgets/textarea.rs b/crates/perry-ui-macos/src/widgets/textarea.rs index 24344e5f4c..c55a7aafbf 100644 --- a/crates/perry-ui-macos/src/widgets/textarea.rs +++ b/crates/perry-ui-macos/src/widgets/textarea.rs @@ -22,7 +22,7 @@ pub(crate) fn scan_macos_textarea_gc_roots(visitor: &mut perry_ffi::GcRootVisito } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -65,7 +65,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-macos/src/widgets/textfield.rs b/crates/perry-ui-macos/src/widgets/textfield.rs index eab0bc16d0..a0907e6ca6 100644 --- a/crates/perry-ui-macos/src/widgets/textfield.rs +++ b/crates/perry-ui-macos/src/widgets/textfield.rs @@ -39,7 +39,7 @@ pub(crate) fn scan_macos_textfield_gc_roots(visitor: &mut perry_ffi::GcRootVisit } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -119,7 +119,7 @@ define_class!( b.suppress.set(true); } unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } if let Some(b) = binding { b.suppress.set(false); @@ -183,7 +183,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); @@ -460,7 +460,7 @@ define_class!( } else { return; } let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, 0.0); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, 0.0); } } }); diff --git a/crates/perry-ui-macos/src/widgets/toggle.rs b/crates/perry-ui-macos/src/widgets/toggle.rs index eef55a5378..185a1abc81 100644 --- a/crates/perry-ui-macos/src/widgets/toggle.rs +++ b/crates/perry-ui-macos/src/widgets/toggle.rs @@ -28,7 +28,7 @@ const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -58,7 +58,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value); } } }); diff --git a/crates/perry-ui-macos/src/widgets/toolbar.rs b/crates/perry-ui-macos/src/widgets/toolbar.rs index f7f3776384..71d2ad1368 100644 --- a/crates/perry-ui-macos/src/widgets/toolbar.rs +++ b/crates/perry-ui-macos/src/widgets/toolbar.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -67,7 +67,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } })); diff --git a/crates/perry-ui-macos/src/widgets/tree_view.rs b/crates/perry-ui-macos/src/widgets/tree_view.rs index ee303662b1..653dfb5209 100644 --- a/crates/perry-ui-macos/src/widgets/tree_view.rs +++ b/crates/perry-ui-macos/src/widgets/tree_view.rs @@ -24,7 +24,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -247,7 +247,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let arg = js_nanbox_string(header as i64); let closure = js_nanbox_get_pointer(on_select) as *const u8; - js_closure_call1(closure, arg); + js_closure_call1(closure, perry_ffi::JsThis::UNDEFINED, arg); } })); } diff --git a/crates/perry-ui-macos/src/widgets/webview.rs b/crates/perry-ui-macos/src/widgets/webview.rs index fcd3fb66da..7b01eb011d 100644 --- a/crates/perry-ui-macos/src/widgets/webview.rs +++ b/crates/perry-ui-macos/src/widgets/webview.rs @@ -32,9 +32,9 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_is_truthy(value: f64) -> i32; @@ -347,7 +347,7 @@ define_class!( if closure_ptr.is_null() { return; // default ALLOW } - let result = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let result = unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) }; // Per the API: undefined / no return = allow (matches JS // "implicit allow"); explicit `false` / 0 / null = cancel. @@ -425,7 +425,7 @@ define_class!( let url_nb = nanbox_str(&url_str); let closure_ptr = js_nanbox_get_pointer(on_loaded) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, url_nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb); } }), ); @@ -490,7 +490,12 @@ impl PerryWebViewDelegate { let msg_nb = nanbox_str(&msg); let closure_ptr = js_nanbox_get_pointer(on_error) as *const u8; if !closure_ptr.is_null() { - js_closure_call2(closure_ptr, code as f64, msg_nb); + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + code as f64, + msg_nb, + ); } }), ); @@ -683,7 +688,7 @@ pub fn evaluate_js(handle: i64, js_ptr: *const u8, callback: f64) { let nb = nanbox_str(&s); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb); } }), ); @@ -834,5 +839,5 @@ fn webview_for_handle(handle: i64) -> Option<*mut AnyObject> { /// future event hooks the design names but doesn't ship in Phase 1. #[allow(dead_code)] fn _used_js_closure_call0() { - let _ = js_closure_call0 as unsafe extern "C" fn(_) -> _; + let _ = js_closure_call0 as unsafe extern "C" fn(_, _) -> _; } diff --git a/crates/perry-ui-tvos/src/app.rs b/crates/perry-ui-tvos/src/app.rs index c31b325369..aa27f3d0ca 100644 --- a/crates/perry-ui-tvos/src/app.rs +++ b/crates/perry-ui-tvos/src/app.rs @@ -451,7 +451,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_callback_timer_tick() -> i32; fn js_interval_timer_tick() -> i32; fn js_frame_pump_default() -> i32; @@ -576,7 +576,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-tvos/src/audio_playback.rs b/crates/perry-ui-tvos/src/audio_playback.rs index 8d8f5a12e3..b651533bce 100644 --- a/crates/perry-ui-tvos/src/audio_playback.rs +++ b/crates/perry-ui-tvos/src/audio_playback.rs @@ -16,9 +16,9 @@ use std::cell::RefCell; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; #[allow(dead_code)] - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -653,7 +653,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -712,7 +712,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-ui-tvos/src/background.rs b/crates/perry-ui-tvos/src/background.rs index 0d49ff7daa..090c2a51f9 100644 --- a/crates/perry-ui-tvos/src/background.rs +++ b/crates/perry-ui-tvos/src/background.rs @@ -23,7 +23,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_value_is_promise(value: f64) -> i32; } @@ -67,7 +67,7 @@ unsafe fn invoke_handler(handler: f64, task: *mut AnyObject) { js_run_stdlib_pump(); js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(handler) as *const u8; - let result = js_closure_call0(ptr); + let result = js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); js_promise_run_microtasks(); let _ = js_value_is_promise(result); let _: () = msg_send![task, setTaskCompletedWithSuccess: true]; diff --git a/crates/perry-ui-tvos/src/file_dialog.rs b/crates/perry-ui-tvos/src/file_dialog.rs index 1a3bf1fe7f..299693d226 100644 --- a/crates/perry-ui-tvos/src/file_dialog.rs +++ b/crates/perry-ui-tvos/src/file_dialog.rs @@ -1,5 +1,5 @@ extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -9,6 +9,10 @@ pub fn open_dialog(callback: f64) { unsafe { let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; // Call callback with TAG_UNDEFINED (user "cancelled") - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } diff --git a/crates/perry-ui-tvos/src/issue_552_stub.rs b/crates/perry-ui-tvos/src/issue_552_stub.rs index fdfb77da44..bf54a94c58 100644 --- a/crates/perry-ui-tvos/src/issue_552_stub.rs +++ b/crates/perry-ui-tvos/src/issue_552_stub.rs @@ -6,7 +6,7 @@ //! picker callback receives an empty array. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -30,7 +30,7 @@ pub extern "C" fn perry_system_geolocation_get_current(_on_success: f64, on_erro } let nb = unsupported_string(); let cb = js_nanbox_get_pointer(on_error) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -47,7 +47,7 @@ pub extern "C" fn perry_system_geolocation_request_permission(callback: f64) { unsafe { let nb = unsupported_string(); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -61,6 +61,6 @@ pub extern "C" fn perry_system_image_picker_pick( let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-tvos/src/location.rs b/crates/perry-ui-tvos/src/location.rs index e6da382ae8..cb7b37dfa2 100644 --- a/crates/perry-ui-tvos/src/location.rs +++ b/crates/perry-ui-tvos/src/location.rs @@ -7,7 +7,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; } // Raw ObjC runtime FFI for dynamic class registration @@ -51,7 +51,12 @@ unsafe fn invoke_callback(lat: f64, lon: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, lat, lon); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + lat, + lon, + ); } } diff --git a/crates/perry-ui-tvos/src/media_playback.rs b/crates/perry-ui-tvos/src/media_playback.rs index 3bc91c408a..1bfe58dd75 100644 --- a/crates/perry-ui-tvos/src/media_playback.rs +++ b/crates/perry-ui-tvos/src/media_playback.rs @@ -27,8 +27,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; // Signature matches the rest of perry-ui-macos (see audio.rs:133, // lib.rs:113/118/1662). Runtime returns `*mut StringHeader` which is // i64-sized on Apple platforms. @@ -775,7 +775,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -784,7 +788,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-tvos/src/menu.rs b/crates/perry-ui-tvos/src/menu.rs index 8f01a1d04e..002a0d7f01 100644 --- a/crates/perry-ui-tvos/src/menu.rs +++ b/crates/perry-ui-tvos/src/menu.rs @@ -9,7 +9,7 @@ use objc2::runtime::{AnyClass, AnyObject, Sel}; use std::cell::RefCell; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -400,7 +400,7 @@ pub(crate) unsafe fn dispatch_menu_action(sender: *mut AnyObject) { }); if let Some(cb) = callback { let ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-tvos/src/network_stub.rs b/crates/perry-ui-tvos/src/network_stub.rs index 0ca620f5a8..a29356026e 100644 --- a/crates/perry-ui-tvos/src/network_stub.rs +++ b/crates/perry-ui-tvos/src/network_stub.rs @@ -7,7 +7,7 @@ //! true reachability detection should target iOS / macOS / Android. extern "C" { - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -28,7 +28,12 @@ pub extern "C" fn perry_system_network_get_status(callback: f64) { if cb.is_null() { return; } - js_closure_call2(cb, f64::from_bits(TAG_TRUE), nb_str("unknown")); + js_closure_call2( + cb, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(TAG_TRUE), + nb_str("unknown"), + ); } } diff --git a/crates/perry-ui-tvos/src/pointer.rs b/crates/perry-ui-tvos/src/pointer.rs index 8a05a2ba5a..ca61e96c9a 100644 --- a/crates/perry-ui-tvos/src/pointer.rs +++ b/crates/perry-ui-tvos/src/pointer.rs @@ -23,7 +23,7 @@ use std::collections::HashMap; use crate::widgets::get_widget; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -142,7 +142,7 @@ fn dispatch_phase(recog: &PerryPointerRecognizer, touches: &AnyObject, phase: u3 return; } let pe = js_pointer_event_new(local.x, local.y, 0, POINTER_TYPE_TOUCH); - js_closure_call1(closure_ptr as *const u8, pe); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, pe); } } diff --git a/crates/perry-ui-tvos/src/state.rs b/crates/perry-ui-tvos/src/state.rs index e753c76e85..fd3701c631 100644 --- a/crates/perry-ui-tvos/src/state.rs +++ b/crates/perry-ui-tvos/src/state.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -236,7 +236,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -274,7 +274,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-tvos/src/widgets/alert.rs b/crates/perry-ui-tvos/src/widgets/alert.rs index a8eea1bb3b..9357fcd3e3 100644 --- a/crates/perry-ui-tvos/src/widgets/alert.rs +++ b/crates/perry-ui-tvos/src/widgets/alert.rs @@ -5,7 +5,7 @@ use objc2::runtime::{AnyClass, AnyObject}; use objc2_foundation::NSString; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_array_get_length(arr: i64) -> i64; fn js_array_get_element(arr: i64, index: i64) -> f64; @@ -26,7 +26,7 @@ unsafe extern "C" fn alert_callback_trampoline(ctx: *mut std::ffi::c_void) { let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); } }); } diff --git a/crates/perry-ui-tvos/src/widgets/bottom_nav.rs b/crates/perry-ui-tvos/src/widgets/bottom_nav.rs index 8673f4ef15..330aea4ac9 100644 --- a/crates/perry-ui-tvos/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-tvos/src/widgets/bottom_nav.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -92,7 +92,7 @@ unsafe extern "C" fn trampoline(ctx: *mut std::ffi::c_void) { let pkg = Box::from_raw(ctx as *mut (f64, i64)); let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); }); } diff --git a/crates/perry-ui-tvos/src/widgets/button.rs b/crates/perry-ui-tvos/src/widgets/button.rs index c061c76fcc..99cf3e5f52 100644 --- a/crates/perry-ui-tvos/src/widgets/button.rs +++ b/crates/perry-ui-tvos/src/widgets/button.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_tvos_button_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<' } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; // dispatch_get_main_queue() is a macro; the actual symbol is _dispatch_main_q static _dispatch_main_q: std::ffi::c_void; @@ -38,7 +38,7 @@ extern "C" { unsafe extern "C" fn button_callback_trampoline(context: *mut std::ffi::c_void) { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } pub struct PerryButtonTargetIvars { diff --git a/crates/perry-ui-tvos/src/widgets/mod.rs b/crates/perry-ui-tvos/src/widgets/mod.rs index f17811f160..4fa401dafe 100644 --- a/crates/perry-ui-tvos/src/widgets/mod.rs +++ b/crates/perry-ui-tvos/src/widgets/mod.rs @@ -522,7 +522,7 @@ pub fn set_hugging_priority(handle: i64, priority: f64) { use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } diff --git a/crates/perry-ui-tvos/src/widgets/scrollview.rs b/crates/perry-ui-tvos/src/widgets/scrollview.rs index 7e0fcb0e0f..6956364560 100644 --- a/crates/perry-ui-tvos/src/widgets/scrollview.rs +++ b/crates/perry-ui-tvos/src/widgets/scrollview.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -21,7 +21,7 @@ extern "C" { unsafe extern "C" fn refresh_callback_trampoline(context: *mut std::ffi::c_void) { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } thread_local! { diff --git a/crates/perry-ui-tvos/src/widgets/securefield.rs b/crates/perry-ui-tvos/src/widgets/securefield.rs index b21b18f335..bc23c94c52 100644 --- a/crates/perry-ui-tvos/src/widgets/securefield.rs +++ b/crates/perry-ui-tvos/src/widgets/securefield.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_tvos_securefield_gc_roots(visitor: &mut perry_ffi::GcRootVisi } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -50,7 +50,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-tvos/src/widgets/slider.rs b/crates/perry-ui-tvos/src/widgets/slider.rs index 9c42b0138b..f906192ea3 100644 --- a/crates/perry-ui-tvos/src/widgets/slider.rs +++ b/crates/perry-ui-tvos/src/widgets/slider.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_tvos_slider_gc_roots(visitor: &mut perry_ffi::GcRootVisitor<' } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -43,7 +43,7 @@ define_class!( let value: f32 = unsafe { msg_send![sender, value] }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value as f64); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value as f64); } } }); diff --git a/crates/perry-ui-tvos/src/widgets/tabbar.rs b/crates/perry-ui-tvos/src/widgets/tabbar.rs index cd7f3f12e2..36ad72d261 100644 --- a/crates/perry-ui-tvos/src/widgets/tabbar.rs +++ b/crates/perry-ui-tvos/src/widgets/tabbar.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -115,7 +115,11 @@ unsafe extern "C" fn tab_callback_trampoline(context: *mut std::ffi::c_void) { let packed = Box::from_raw(context as *mut (f64, i64)); let (closure_f64, tab_index) = *packed; let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call1(closure_ptr as *const u8, tab_index as f64); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + tab_index as f64, + ); } // ── Public API ──────────────────────────────────────────────── diff --git a/crates/perry-ui-tvos/src/widgets/textarea.rs b/crates/perry-ui-tvos/src/widgets/textarea.rs index 5b9d37728e..4743d6c7df 100644 --- a/crates/perry-ui-tvos/src/widgets/textarea.rs +++ b/crates/perry-ui-tvos/src/widgets/textarea.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_tvos_textarea_gc_roots(visitor: &mut perry_ffi::GcRootVisitor } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -50,7 +50,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-tvos/src/widgets/textfield.rs b/crates/perry-ui-tvos/src/widgets/textfield.rs index cf395aa6c0..658fd598f2 100644 --- a/crates/perry-ui-tvos/src/widgets/textfield.rs +++ b/crates/perry-ui-tvos/src/widgets/textfield.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_tvos_textfield_gc_roots(visitor: &mut perry_ffi::GcRootVisito } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -55,7 +55,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); @@ -236,7 +236,7 @@ define_class!( let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-tvos/src/widgets/toggle.rs b/crates/perry-ui-tvos/src/widgets/toggle.rs index 15d7075c1a..8bf899f845 100644 --- a/crates/perry-ui-tvos/src/widgets/toggle.rs +++ b/crates/perry-ui-tvos/src/widgets/toggle.rs @@ -23,7 +23,7 @@ const TAG_TRUE: u64 = 0x7FFC_0000_0000_0004; const TAG_FALSE: u64 = 0x7FFC_0000_0000_0003; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -53,7 +53,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value); } } }); diff --git a/crates/perry-ui-visionos/src/app.rs b/crates/perry-ui-visionos/src/app.rs index d19a7c8647..83f8115fb3 100644 --- a/crates/perry-ui-visionos/src/app.rs +++ b/crates/perry-ui-visionos/src/app.rs @@ -302,7 +302,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_callback_timer_tick() -> i32; fn js_interval_timer_tick() -> i32; fn js_frame_pump_default() -> i32; @@ -386,7 +386,7 @@ define_class!( if let Some(closure_f64) = closure_f64 { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-visionos/src/audio_playback.rs b/crates/perry-ui-visionos/src/audio_playback.rs index e03194ca2e..312d78de33 100644 --- a/crates/perry-ui-visionos/src/audio_playback.rs +++ b/crates/perry-ui-visionos/src/audio_playback.rs @@ -16,9 +16,9 @@ use std::cell::RefCell; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; #[allow(dead_code)] - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -653,7 +653,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -712,7 +712,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-ui-visionos/src/background.rs b/crates/perry-ui-visionos/src/background.rs index 8789bc8ae4..708c0b802f 100644 --- a/crates/perry-ui-visionos/src/background.rs +++ b/crates/perry-ui-visionos/src/background.rs @@ -24,7 +24,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_value_is_promise(value: f64) -> i32; } @@ -68,7 +68,7 @@ unsafe fn invoke_handler(handler: f64, task: *mut AnyObject) { js_run_stdlib_pump(); js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(handler) as *const u8; - let result = js_closure_call0(ptr); + let result = js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); js_promise_run_microtasks(); let _ = js_value_is_promise(result); let _: () = msg_send![task, setTaskCompletedWithSuccess: true]; diff --git a/crates/perry-ui-visionos/src/camera.rs b/crates/perry-ui-visionos/src/camera.rs index e7bcb1a9d1..a41ef3e2df 100644 --- a/crates/perry-ui-visionos/src/camera.rs +++ b/crates/perry-ui-visionos/src/camera.rs @@ -15,8 +15,8 @@ use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use crate::widgets; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -254,7 +254,12 @@ fn register_tap_handler_class() -> *const c_void { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, norm_x, norm_y); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + norm_x, + norm_y, + ); } }); } diff --git a/crates/perry-ui-visionos/src/drag_drop.rs b/crates/perry-ui-visionos/src/drag_drop.rs index 674aa1bcbe..252bcce226 100644 --- a/crates/perry-ui-visionos/src/drag_drop.rs +++ b/crates/perry-ui-visionos/src/drag_drop.rs @@ -36,8 +36,8 @@ use std::ffi::c_void; use std::rc::Rc; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; @@ -86,7 +86,7 @@ unsafe fn call_provider(cb: f64) -> Option { if p.is_null() { return None; } - let ret = js_closure_call0(p); + let ret = js_closure_call0(p, perry_ffi::JsThis::UNDEFINED); let sh = js_jsvalue_to_string(ret); if sh.is_null() { None @@ -204,7 +204,7 @@ unsafe fn perform_drop_impl(interaction: *mut AnyObject, session: *mut AnyObject } let payload = js_nanbox_pointer(obj as i64); let cb_ptr = js_nanbox_get_pointer(a.cb) as *const u8; - js_closure_call1(cb_ptr, payload); + js_closure_call1(cb_ptr, perry_ffi::JsThis::UNDEFINED, payload); } // text diff --git a/crates/perry-ui-visionos/src/file_dialog.rs b/crates/perry-ui-visionos/src/file_dialog.rs index 1a3bf1fe7f..299693d226 100644 --- a/crates/perry-ui-visionos/src/file_dialog.rs +++ b/crates/perry-ui-visionos/src/file_dialog.rs @@ -1,5 +1,5 @@ extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -9,6 +9,10 @@ pub fn open_dialog(callback: f64) { unsafe { let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; // Call callback with TAG_UNDEFINED (user "cancelled") - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } diff --git a/crates/perry-ui-visionos/src/issue_552_stub.rs b/crates/perry-ui-visionos/src/issue_552_stub.rs index fdfb77da44..bf54a94c58 100644 --- a/crates/perry-ui-visionos/src/issue_552_stub.rs +++ b/crates/perry-ui-visionos/src/issue_552_stub.rs @@ -6,7 +6,7 @@ //! picker callback receives an empty array. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -30,7 +30,7 @@ pub extern "C" fn perry_system_geolocation_get_current(_on_success: f64, on_erro } let nb = unsupported_string(); let cb = js_nanbox_get_pointer(on_error) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -47,7 +47,7 @@ pub extern "C" fn perry_system_geolocation_request_permission(callback: f64) { unsafe { let nb = unsupported_string(); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -61,6 +61,6 @@ pub extern "C" fn perry_system_image_picker_pick( let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-visionos/src/location.rs b/crates/perry-ui-visionos/src/location.rs index f8ccab51f5..ff98f6adaf 100644 --- a/crates/perry-ui-visionos/src/location.rs +++ b/crates/perry-ui-visionos/src/location.rs @@ -7,7 +7,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; } // Raw ObjC runtime FFI for dynamic class registration @@ -51,7 +51,12 @@ unsafe fn invoke_callback(lat: f64, lon: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call2(closure_ptr as *const u8, lat, lon); + js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + lat, + lon, + ); } } diff --git a/crates/perry-ui-visionos/src/media_playback.rs b/crates/perry-ui-visionos/src/media_playback.rs index 3236c1bc67..a20d24aa40 100644 --- a/crates/perry-ui-visionos/src/media_playback.rs +++ b/crates/perry-ui-visionos/src/media_playback.rs @@ -27,8 +27,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; // Signature matches the rest of perry-ui-macos (see audio.rs:133, // lib.rs:113/118/1662). Runtime returns `*mut StringHeader` which is // i64-sized on Apple platforms. @@ -775,7 +775,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -784,7 +788,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-visionos/src/menu.rs b/crates/perry-ui-visionos/src/menu.rs index d88b0e08de..cb78c86e1f 100644 --- a/crates/perry-ui-visionos/src/menu.rs +++ b/crates/perry-ui-visionos/src/menu.rs @@ -9,7 +9,7 @@ use objc2::runtime::{AnyClass, AnyObject, Sel}; use std::cell::RefCell; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -435,7 +435,7 @@ pub(crate) unsafe fn dispatch_menu_action(sender: *mut AnyObject) { }); if let Some(cb) = callback { let ptr = js_nanbox_get_pointer(cb) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-visionos/src/network_stub.rs b/crates/perry-ui-visionos/src/network_stub.rs index 0ca620f5a8..a29356026e 100644 --- a/crates/perry-ui-visionos/src/network_stub.rs +++ b/crates/perry-ui-visionos/src/network_stub.rs @@ -7,7 +7,7 @@ //! true reachability detection should target iOS / macOS / Android. extern "C" { - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -28,7 +28,12 @@ pub extern "C" fn perry_system_network_get_status(callback: f64) { if cb.is_null() { return; } - js_closure_call2(cb, f64::from_bits(TAG_TRUE), nb_str("unknown")); + js_closure_call2( + cb, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(TAG_TRUE), + nb_str("unknown"), + ); } } diff --git a/crates/perry-ui-visionos/src/pointer.rs b/crates/perry-ui-visionos/src/pointer.rs index 6f756bd7b8..ed7e3ed743 100644 --- a/crates/perry-ui-visionos/src/pointer.rs +++ b/crates/perry-ui-visionos/src/pointer.rs @@ -23,7 +23,7 @@ use std::collections::HashMap; use crate::widgets::get_widget; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -142,7 +142,7 @@ fn dispatch_phase(recog: &PerryPointerRecognizer, touches: &AnyObject, phase: u3 return; } let pe = js_pointer_event_new(local.x, local.y, 0, POINTER_TYPE_TOUCH); - js_closure_call1(closure_ptr as *const u8, pe); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, pe); } } diff --git a/crates/perry-ui-visionos/src/state.rs b/crates/perry-ui-visionos/src/state.rs index f2511612e2..c59134a08a 100644 --- a/crates/perry-ui-visionos/src/state.rs +++ b/crates/perry-ui-visionos/src/state.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -236,7 +236,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -274,7 +274,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-visionos/src/widgets/alert.rs b/crates/perry-ui-visionos/src/widgets/alert.rs index a8eea1bb3b..9357fcd3e3 100644 --- a/crates/perry-ui-visionos/src/widgets/alert.rs +++ b/crates/perry-ui-visionos/src/widgets/alert.rs @@ -5,7 +5,7 @@ use objc2::runtime::{AnyClass, AnyObject}; use objc2_foundation::NSString; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_array_get_length(arr: i64) -> i64; fn js_array_get_element(arr: i64, index: i64) -> f64; @@ -26,7 +26,7 @@ unsafe extern "C" fn alert_callback_trampoline(ctx: *mut std::ffi::c_void) { let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; if !ptr.is_null() { - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); } }); } diff --git a/crates/perry-ui-visionos/src/widgets/bottom_nav.rs b/crates/perry-ui-visionos/src/widgets/bottom_nav.rs index e44995efc1..282bdbbd09 100644 --- a/crates/perry-ui-visionos/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-visionos/src/widgets/bottom_nav.rs @@ -14,7 +14,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -92,7 +92,7 @@ unsafe extern "C" fn trampoline(ctx: *mut std::ffi::c_void) { let pkg = Box::from_raw(ctx as *mut (f64, i64)); let (closure, idx) = *pkg; let ptr = js_nanbox_get_pointer(closure) as *const u8; - js_closure_call1(ptr, idx as f64); + js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); }); } diff --git a/crates/perry-ui-visionos/src/widgets/button.rs b/crates/perry-ui-visionos/src/widgets/button.rs index d9006ba353..6f2f6171d8 100644 --- a/crates/perry-ui-visionos/src/widgets/button.rs +++ b/crates/perry-ui-visionos/src/widgets/button.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_visionos_button_gc_roots(visitor: &mut perry_ffi::GcRootVisit } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; // dispatch_get_main_queue() is a macro; the actual symbol is _dispatch_main_q static _dispatch_main_q: std::ffi::c_void; @@ -39,7 +39,7 @@ unsafe extern "C" fn button_callback_trampoline(context: *mut std::ffi::c_void) let _ = std::panic::catch_unwind(|| { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); }); } diff --git a/crates/perry-ui-visionos/src/widgets/calendar.rs b/crates/perry-ui-visionos/src/widgets/calendar.rs index d6de68a04c..c23720e398 100644 --- a/crates/perry-ui-visionos/src/widgets/calendar.rs +++ b/crates/perry-ui-visionos/src/widgets/calendar.rs @@ -17,7 +17,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -63,7 +63,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-visionos/src/widgets/combobox.rs b/crates/perry-ui-visionos/src/widgets/combobox.rs index 648d4a255e..8a4fa47a37 100644 --- a/crates/perry-ui-visionos/src/widgets/combobox.rs +++ b/crates/perry-ui-visionos/src/widgets/combobox.rs @@ -24,7 +24,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -60,7 +60,7 @@ fn fire_callback(handle: i64, value: &str) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); }), ); } diff --git a/crates/perry-ui-visionos/src/widgets/date_picker.rs b/crates/perry-ui-visionos/src/widgets/date_picker.rs index f98d080e70..0732894774 100644 --- a/crates/perry-ui-visionos/src/widgets/date_picker.rs +++ b/crates/perry-ui-visionos/src/widgets/date_picker.rs @@ -18,7 +18,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -64,7 +64,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-visionos/src/widgets/mod.rs b/crates/perry-ui-visionos/src/widgets/mod.rs index 00c455a7e4..3d242cbf50 100644 --- a/crates/perry-ui-visionos/src/widgets/mod.rs +++ b/crates/perry-ui-visionos/src/widgets/mod.rs @@ -681,7 +681,7 @@ pub fn set_hugging_priority(handle: i64, priority: f64) { use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } diff --git a/crates/perry-ui-visionos/src/widgets/rich_text.rs b/crates/perry-ui-visionos/src/widgets/rich_text.rs index ec25f9b50c..53967dc256 100644 --- a/crates/perry-ui-visionos/src/widgets/rich_text.rs +++ b/crates/perry-ui-visionos/src/widgets/rich_text.rs @@ -19,7 +19,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -74,7 +74,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } } diff --git a/crates/perry-ui-visionos/src/widgets/scrollview.rs b/crates/perry-ui-visionos/src/widgets/scrollview.rs index 02dcea5d7c..712fc02eea 100644 --- a/crates/perry-ui-visionos/src/widgets/scrollview.rs +++ b/crates/perry-ui-visionos/src/widgets/scrollview.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -22,7 +22,7 @@ unsafe extern "C" fn refresh_callback_trampoline(context: *mut std::ffi::c_void) let _ = std::panic::catch_unwind(|| { let closure_f64 = f64::from_bits(context as u64); let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); }); } diff --git a/crates/perry-ui-visionos/src/widgets/securefield.rs b/crates/perry-ui-visionos/src/widgets/securefield.rs index bba267f864..5a75394c6b 100644 --- a/crates/perry-ui-visionos/src/widgets/securefield.rs +++ b/crates/perry-ui-visionos/src/widgets/securefield.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_visionos_securefield_gc_roots(visitor: &mut perry_ffi::GcRoot } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -50,7 +50,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-visionos/src/widgets/slider.rs b/crates/perry-ui-visionos/src/widgets/slider.rs index bcd3ed880c..92be2f2ba2 100644 --- a/crates/perry-ui-visionos/src/widgets/slider.rs +++ b/crates/perry-ui-visionos/src/widgets/slider.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_visionos_slider_gc_roots(visitor: &mut perry_ffi::GcRootVisit } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -43,7 +43,7 @@ define_class!( let value: f32 = unsafe { msg_send![sender, value] }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, value as f64); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, value as f64); } } }); diff --git a/crates/perry-ui-visionos/src/widgets/tabbar.rs b/crates/perry-ui-visionos/src/widgets/tabbar.rs index 070e02f1d5..bb04515e8d 100644 --- a/crates/perry-ui-visionos/src/widgets/tabbar.rs +++ b/crates/perry-ui-visionos/src/widgets/tabbar.rs @@ -7,7 +7,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -116,7 +116,11 @@ unsafe extern "C" fn tab_callback_trampoline(context: *mut std::ffi::c_void) { let packed = Box::from_raw(context as *mut (f64, i64)); let (closure_f64, tab_index) = *packed; let closure_ptr = js_nanbox_get_pointer(closure_f64); - js_closure_call1(closure_ptr as *const u8, tab_index as f64); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + tab_index as f64, + ); }); } diff --git a/crates/perry-ui-visionos/src/widgets/textarea.rs b/crates/perry-ui-visionos/src/widgets/textarea.rs index da1c81e07e..ffba5c2196 100644 --- a/crates/perry-ui-visionos/src/widgets/textarea.rs +++ b/crates/perry-ui-visionos/src/widgets/textarea.rs @@ -19,7 +19,7 @@ pub(crate) fn scan_visionos_textarea_gc_roots(visitor: &mut perry_ffi::GcRootVis } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -51,7 +51,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-visionos/src/widgets/textfield.rs b/crates/perry-ui-visionos/src/widgets/textfield.rs index d3486c1f0d..b859cc507a 100644 --- a/crates/perry-ui-visionos/src/widgets/textfield.rs +++ b/crates/perry-ui-visionos/src/widgets/textfield.rs @@ -24,7 +24,7 @@ pub(crate) fn scan_visionos_textfield_gc_roots(visitor: &mut perry_ffi::GcRootVi } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -55,7 +55,7 @@ define_class!( let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); @@ -247,7 +247,7 @@ define_class!( let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) }; unsafe { - js_closure_call1(closure_ptr as *const u8, nanboxed); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, nanboxed); } } }); diff --git a/crates/perry-ui-visionos/src/widgets/tree_view.rs b/crates/perry-ui-visionos/src/widgets/tree_view.rs index d339ff74aa..1e1c192d2c 100644 --- a/crates/perry-ui-visionos/src/widgets/tree_view.rs +++ b/crates/perry-ui-visionos/src/widgets/tree_view.rs @@ -27,7 +27,7 @@ use objc2_ui_kit::UIView; use std::cell::{Cell, RefCell}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -311,7 +311,7 @@ define_class!( let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on_select) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); }), ); } diff --git a/crates/perry-ui-visionos/src/widgets/webview.rs b/crates/perry-ui-visionos/src/widgets/webview.rs index fedf77d062..8983e730b5 100644 --- a/crates/perry-ui-visionos/src/widgets/webview.rs +++ b/crates/perry-ui-visionos/src/widgets/webview.rs @@ -16,8 +16,8 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -153,7 +153,7 @@ define_class!( crate::catch_callback_panic( "webview onShouldNavigate", std::panic::AssertUnwindSafe(|| { - let r = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let r = unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) }; result_cell_ref.set(r); }), ); @@ -202,7 +202,7 @@ define_class!( let url_nb = nanbox_str(&url_str); let closure_ptr = js_nanbox_get_pointer(on_loaded) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, url_nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb); } }), ); @@ -264,7 +264,12 @@ impl PerryWebViewDelegate { let msg_nb = nanbox_str(&msg); let closure_ptr = js_nanbox_get_pointer(on_error) as *const u8; if !closure_ptr.is_null() { - js_closure_call2(closure_ptr, code as f64, msg_nb); + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + code as f64, + msg_nb, + ); } }), ); @@ -415,7 +420,7 @@ pub fn evaluate_js(handle: i64, js_ptr: *const u8, callback: f64) { let nb = nanbox_str(&s); let closure_ptr = js_nanbox_get_pointer(callback) as *const u8; if !closure_ptr.is_null() { - js_closure_call1(closure_ptr, nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb); } }), ); diff --git a/crates/perry-ui-visionos/src/widgets/wheel_picker.rs b/crates/perry-ui-visionos/src/widgets/wheel_picker.rs index 6504ad2968..4b7cb65b83 100644 --- a/crates/perry-ui-visionos/src/widgets/wheel_picker.rs +++ b/crates/perry-ui-visionos/src/widgets/wheel_picker.rs @@ -25,7 +25,7 @@ pub(crate) fn scan_visionos_wheel_picker_gc_roots(visitor: &mut perry_ffi::GcRoo } extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; static _dispatch_main_q: std::ffi::c_void; fn dispatch_async_f( @@ -44,7 +44,7 @@ unsafe extern "C" fn callback_trampoline(context: *mut std::ffi::c_void) { let _ = std::panic::catch_unwind(|| { let payload = Box::from_raw(context as *mut WheelPickerDispatch); let closure = js_nanbox_get_pointer(payload.closure) as *const u8; - js_closure_call1(closure, payload.index); + js_closure_call1(closure, perry_ffi::JsThis::UNDEFINED, payload.index); }); } diff --git a/crates/perry-ui-watchos/src/audio_playback.rs b/crates/perry-ui-watchos/src/audio_playback.rs index 5665812756..106acb899f 100644 --- a/crates/perry-ui-watchos/src/audio_playback.rs +++ b/crates/perry-ui-watchos/src/audio_playback.rs @@ -16,9 +16,9 @@ use std::cell::RefCell; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; #[allow(dead_code)] - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; } @@ -653,7 +653,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -712,7 +712,7 @@ fn drain_pending_callbacks() { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call0(ptr as *const u8); + let _ = js_closure_call0(ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } if should_clean { diff --git a/crates/perry-ui-watchos/src/background.rs b/crates/perry-ui-watchos/src/background.rs index 335bfa88a4..113afc2581 100644 --- a/crates/perry-ui-watchos/src/background.rs +++ b/crates/perry-ui-watchos/src/background.rs @@ -29,7 +29,7 @@ extern "C" { fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_value_is_promise(value: f64) -> i32; } @@ -56,7 +56,7 @@ unsafe fn invoke_handler(handler: f64) { js_run_stdlib_pump(); js_promise_run_microtasks(); let ptr = js_nanbox_get_pointer(handler) as *const u8; - let result = js_closure_call0(ptr); + let result = js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); js_promise_run_microtasks(); let _ = js_value_is_promise(result); } diff --git a/crates/perry-ui-watchos/src/issue_552_stub.rs b/crates/perry-ui-watchos/src/issue_552_stub.rs index fdfb77da44..bf54a94c58 100644 --- a/crates/perry-ui-watchos/src/issue_552_stub.rs +++ b/crates/perry-ui-watchos/src/issue_552_stub.rs @@ -6,7 +6,7 @@ //! picker callback receives an empty array. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -30,7 +30,7 @@ pub extern "C" fn perry_system_geolocation_get_current(_on_success: f64, on_erro } let nb = unsupported_string(); let cb = js_nanbox_get_pointer(on_error) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -47,7 +47,7 @@ pub extern "C" fn perry_system_geolocation_request_permission(callback: f64) { unsafe { let nb = unsupported_string(); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -61,6 +61,6 @@ pub extern "C" fn perry_system_image_picker_pick( let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-watchos/src/media_playback.rs b/crates/perry-ui-watchos/src/media_playback.rs index 35b1a0e330..80e4def65b 100644 --- a/crates/perry-ui-watchos/src/media_playback.rs +++ b/crates/perry-ui-watchos/src/media_playback.rs @@ -55,8 +55,8 @@ use std::sync::atomic::{AtomicBool, Ordering}; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; // Signature matches the rest of perry-ui-watchos (see audio.rs's // string callers — and the canonical `str_from_header` in lib.rs). // Runtime returns `*mut StringHeader` which is i64-sized on Apple @@ -798,7 +798,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -807,7 +811,12 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-watchos/src/network_stub.rs b/crates/perry-ui-watchos/src/network_stub.rs index 0ca620f5a8..a29356026e 100644 --- a/crates/perry-ui-watchos/src/network_stub.rs +++ b/crates/perry-ui-watchos/src/network_stub.rs @@ -7,7 +7,7 @@ //! true reachability detection should target iOS / macOS / Android. extern "C" { - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -28,7 +28,12 @@ pub extern "C" fn perry_system_network_get_status(callback: f64) { if cb.is_null() { return; } - js_closure_call2(cb, f64::from_bits(TAG_TRUE), nb_str("unknown")); + js_closure_call2( + cb, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(TAG_TRUE), + nb_str("unknown"), + ); } } diff --git a/crates/perry-ui-watchos/src/notifications.rs b/crates/perry-ui-watchos/src/notifications.rs index e56874f92c..5bdeca9b41 100644 --- a/crates/perry-ui-watchos/src/notifications.rs +++ b/crates/perry-ui-watchos/src/notifications.rs @@ -32,7 +32,7 @@ extern "C" { // as `i64` here (32-bit ptr zero-extended on arm64_32). Keeping the same // signature avoids a `clashing_extern_declarations` warning. fn js_string_from_bytes(data: *const u8, len: i32) -> i64; - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_is_truthy(value: f64) -> i32; fn js_run_stdlib_pump(); fn js_promise_run_microtasks() -> i32; @@ -134,7 +134,7 @@ unsafe fn dispatch_tap(response: &AnyObject) { let ptr = js_nanbox_get_pointer(callback) as *const u8; if !ptr.is_null() { - js_closure_call2(ptr, id_value, action_value); + js_closure_call2(ptr, perry_ffi::JsThis::UNDEFINED, id_value, action_value); } } diff --git a/crates/perry-ui-watchos/src/state.rs b/crates/perry-ui-watchos/src/state.rs index e8575cca72..ea9f6a477b 100644 --- a/crates/perry-ui-watchos/src/state.rs +++ b/crates/perry-ui-watchos/src/state.rs @@ -9,7 +9,7 @@ use std::ffi::CString; use crate::tree; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -144,7 +144,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; tree::add_child(container, child_handle); } @@ -259,7 +260,7 @@ pub fn state_set(handle: i64, value: f64) { for closure_f64 in callbacks_snapshot { let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } diff --git a/crates/perry-ui-watchos/src/tree.rs b/crates/perry-ui-watchos/src/tree.rs index 48fbf8a341..c04d2a6d01 100644 --- a/crates/perry-ui-watchos/src/tree.rs +++ b/crates/perry-ui-watchos/src/tree.rs @@ -321,11 +321,11 @@ pub extern "C" fn perry_watchos_handle_action(id: i64) { if let Some(closure_f64) = closure { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; } let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, 0.0); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, 0.0); } } } @@ -547,11 +547,11 @@ pub extern "C" fn perry_watchos_slider_changed(id: i64, value: f64) { if let Some(closure_f64) = closure { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; } let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, value); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -567,11 +567,15 @@ pub extern "C" fn perry_watchos_toggle_changed(id: i64, on: bool) { if let Some(closure_f64) = closure { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; } let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, if on { 1.0 } else { 0.0 }); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + if on { 1.0 } else { 0.0 }, + ); } } } @@ -587,11 +591,11 @@ pub extern "C" fn perry_watchos_picker_changed(id: i64, index: i64) { if let Some(closure_f64) = closure { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; } let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, index as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, index as f64); } } } @@ -601,7 +605,7 @@ pub extern "C" fn perry_watchos_picker_changed(id: i64, index: i64) { pub extern "C" fn perry_watchos_textarea_changed(id: i64, text_ptr: *const std::ffi::c_char) { extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; } @@ -624,7 +628,7 @@ pub extern "C" fn perry_watchos_textarea_changed(id: i64, text_ptr: *const std:: let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; let closure_ptr = unsafe { js_nanbox_get_pointer(closure_f64) } as *const u8; unsafe { - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); } } } diff --git a/crates/perry-ui-windows-winui/src/app.rs b/crates/perry-ui-windows-winui/src/app.rs index 8c18fb45f2..25a25e86ac 100644 --- a/crates/perry-ui-windows-winui/src/app.rs +++ b/crates/perry-ui-windows-winui/src/app.rs @@ -12,7 +12,7 @@ use crate::winui::backend::{self, RenderBackend}; extern "C" { fn js_callback_timer_tick() -> i32; - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_frame_pump_default() -> i32; fn js_gc_step_us(budget_us: u64, out: *mut u8) -> u32; fn js_interval_timer_tick() -> i32; @@ -89,7 +89,7 @@ fn invoke_app_callback(slot: &'static std::thread::LocalKey f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_string_from_bytes(data: *const u8, len: u32) -> *mut u8; @@ -247,7 +247,7 @@ fn invoke_node0(handle: i64, slot: CallbackSlot) { let callback = node_callback(handle, slot); if callback != 0 { unsafe { - js_closure_call0(callback as *const u8); + js_closure_call0(callback as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -256,7 +256,7 @@ fn invoke_node1(handle: i64, slot: CallbackSlot, value: f64) { let callback = node_callback(handle, slot); if callback != 0 { unsafe { - js_closure_call1(callback as *const u8, value); + js_closure_call1(callback as *const u8, perry_ffi::JsThis::UNDEFINED, value); } } } @@ -269,7 +269,7 @@ fn invoke_node_string(handle: i64, slot: CallbackSlot, value: &str) { // so even that collection's rewritten slot is observed. let callback = node_callback(handle, slot); if callback != 0 { - js_closure_call1(callback as *const u8, boxed); + js_closure_call1(callback as *const u8, perry_ffi::JsThis::UNDEFINED, boxed); } } } diff --git a/crates/perry-ui-windows/src/app.rs b/crates/perry-ui-windows/src/app.rs index bc45b50132..ecbe619496 100644 --- a/crates/perry-ui-windows/src/app.rs +++ b/crates/perry-ui-windows/src/app.rs @@ -35,7 +35,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; // `docs/src/platforms/windows-7.md`. extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_callback_timer_tick() -> i32; fn js_interval_timer_tick() -> i32; @@ -293,7 +293,7 @@ unsafe fn dispatch_ui_message(msg: &MSG) { GLOBAL_HOTKEY_CALLBACKS.with(|cbs| { if let Some(cb_ptr) = cbs.borrow().get(&hotkey_id) { unsafe { - js_closure_call0(*cb_ptr); + js_closure_call0(*cb_ptr, perry_ffi::JsThis::UNDEFINED); } } }); @@ -992,7 +992,7 @@ fn try_handle_shortcut(vk: u16) -> bool { let shortcuts = shortcuts.borrow(); for s in shortcuts.iter() { if s.vk == vk && s.ctrl == ctrl_down && s.shift == shift_down && s.alt == alt_down { - unsafe { js_closure_call0(s.callback_ptr) }; + unsafe { js_closure_call0(s.callback_ptr, perry_ffi::JsThis::UNDEFINED) }; return true; } } @@ -1384,7 +1384,7 @@ pub fn handle_timer(hwnd: HWND, timer_id: usize) { let timers = t.borrow(); let idx = timer_id - 1; if idx < timers.len() { - unsafe { js_closure_call0(timers[idx].callback_ptr) }; + unsafe { js_closure_call0(timers[idx].callback_ptr, perry_ffi::JsThis::UNDEFINED) }; } }); } @@ -1395,7 +1395,7 @@ pub fn handle_activate(activating: bool) { if activating { ON_ACTIVATE_CALLBACK.with(|c| { if let Some(ptr) = *c.borrow() { - unsafe { js_closure_call0(ptr) }; + unsafe { js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED) }; } }); } @@ -1406,7 +1406,7 @@ pub fn handle_activate(activating: bool) { pub fn handle_terminate() { ON_TERMINATE_CALLBACK.with(|c| { if let Some(ptr) = *c.borrow() { - unsafe { js_closure_call0(ptr) }; + unsafe { js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED) }; } }); } diff --git a/crates/perry-ui-windows/src/dialog.rs b/crates/perry-ui-windows/src/dialog.rs index 0d17d254f6..5aaa270f35 100644 --- a/crates/perry-ui-windows/src/dialog.rs +++ b/crates/perry-ui-windows/src/dialog.rs @@ -1,7 +1,7 @@ //! Save file dialog and alert dialog (Win32) extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -46,7 +46,7 @@ pub fn save_file_dialog(callback: f64, default_name_ptr: *const u8, _allowed_typ let bytes = path_str.as_bytes(); let str_ptr = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(closure_ptr, nanboxed); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); CoTaskMemFree(Some(path.0 as *const _)); return; } @@ -54,14 +54,22 @@ pub fn save_file_dialog(callback: f64, default_name_ptr: *const u8, _allowed_typ } } // Cancelled or error - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } #[cfg(not(target_os = "windows"))] { unsafe { - js_closure_call1(closure_ptr, f64::from_bits(0x7FFC_0000_0000_0001)); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(0x7FFC_0000_0000_0001), + ); } } } @@ -140,7 +148,7 @@ pub fn alert(title_ptr: *const u8, message_ptr: *const u8, buttons_ptr: *const u IDNO => 1.0, _ => 0.0, }; - js_closure_call1(closure_ptr, index); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, index); } } @@ -148,7 +156,7 @@ pub fn alert(title_ptr: *const u8, message_ptr: *const u8, buttons_ptr: *const u { let _ = (title, message, buttons_ptr); unsafe { - js_closure_call1(closure_ptr, 0.0); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, 0.0); } } } diff --git a/crates/perry-ui-windows/src/drag_drop.rs b/crates/perry-ui-windows/src/drag_drop.rs index d2e69db88c..955887d800 100644 --- a/crates/perry-ui-windows/src/drag_drop.rs +++ b/crates/perry-ui-windows/src/drag_drop.rs @@ -49,8 +49,8 @@ use std::sync::Once; // about unused items. #[cfg(target_os = "windows")] extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_pointer(ptr: i64) -> f64; fn js_nanbox_string(ptr: i64) -> f64; @@ -101,7 +101,7 @@ unsafe fn call_provider(cb: f64) -> Option { if p == 0 { return None; } - let ret = js_closure_call0(p as *const u8); + let ret = js_closure_call0(p as *const u8, perry_ffi::JsThis::UNDEFINED); let sh = js_jsvalue_to_string(ret); if sh.is_null() { None @@ -455,7 +455,7 @@ mod imp { let payload = js_nanbox_pointer(obj as i64); let cb_ptr = js_nanbox_get_pointer(cb); if cb_ptr != 0 { - js_closure_call1(cb_ptr as *const u8, payload); + js_closure_call1(cb_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, payload); } } diff --git a/crates/perry-ui-windows/src/file_dialog.rs b/crates/perry-ui-windows/src/file_dialog.rs index cb5bd63dda..a698c43201 100644 --- a/crates/perry-ui-windows/src/file_dialog.rs +++ b/crates/perry-ui-windows/src/file_dialog.rs @@ -1,7 +1,7 @@ //! File open dialog — COM-based IFileOpenDialog extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -34,7 +34,7 @@ pub fn open_dialog(callback: f64) { bytes.len() as u32, ); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(callback_ptr, nanboxed); + js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); CoTaskMemFree(Some(path.0 as *const _)); return; } @@ -44,13 +44,13 @@ pub fn open_dialog(callback: f64) { // Cancelled or error — call with undefined let undefined = f64::from_bits(0x7FFC_0000_0000_0001); - js_closure_call1(callback_ptr, undefined); + js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, undefined); } } #[cfg(not(target_os = "windows"))] { let undefined = f64::from_bits(0x7FFC_0000_0000_0001); - unsafe { js_closure_call1(callback_ptr, undefined) }; + unsafe { js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, undefined) }; } } diff --git a/crates/perry-ui-windows/src/folder_dialog.rs b/crates/perry-ui-windows/src/folder_dialog.rs index df820a642b..1ea439205f 100644 --- a/crates/perry-ui-windows/src/folder_dialog.rs +++ b/crates/perry-ui-windows/src/folder_dialog.rs @@ -1,7 +1,7 @@ //! Folder open dialog — COM-based IFileOpenDialog with FOS_PICKFOLDERS extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -39,7 +39,7 @@ pub fn open_dialog(callback: f64) { bytes.len() as u32, ); let nanboxed = js_nanbox_string(str_ptr as i64); - js_closure_call1(callback_ptr, nanboxed); + js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, nanboxed); CoTaskMemFree(Some(path.0 as *const _)); return; } @@ -49,13 +49,13 @@ pub fn open_dialog(callback: f64) { // Cancelled or error — call with undefined let undefined = f64::from_bits(0x7FFC_0000_0000_0001); - js_closure_call1(callback_ptr, undefined); + js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, undefined); } } #[cfg(not(target_os = "windows"))] { let undefined = f64::from_bits(0x7FFC_0000_0000_0001); - unsafe { js_closure_call1(callback_ptr, undefined) }; + unsafe { js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, undefined) }; } } diff --git a/crates/perry-ui-windows/src/issue_552_stub.rs b/crates/perry-ui-windows/src/issue_552_stub.rs index ee638b0bee..c2bbf09ebf 100644 --- a/crates/perry-ui-windows/src/issue_552_stub.rs +++ b/crates/perry-ui-windows/src/issue_552_stub.rs @@ -6,7 +6,7 @@ //! picker callback receives an empty array. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -30,7 +30,7 @@ pub extern "C" fn perry_system_geolocation_get_current(_on_success: f64, on_erro } let nb = unsupported_string(); let cb = js_nanbox_get_pointer(on_error) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -47,7 +47,7 @@ pub extern "C" fn perry_system_geolocation_request_permission(callback: f64) { unsafe { let nb = unsupported_string(); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } @@ -61,7 +61,7 @@ pub extern "C" fn perry_system_image_picker_pick( let arr = js_array_alloc(0); let nb = js_nanbox_pointer(arr as i64); let cb = js_nanbox_get_pointer(callback) as *const u8; - js_closure_call1(cb, nb); + js_closure_call1(cb, perry_ffi::JsThis::UNDEFINED, nb); } } diff --git a/crates/perry-ui-windows/src/media_playback.rs b/crates/perry-ui-windows/src/media_playback.rs index 442fa645d9..beaee6a4bb 100644 --- a/crates/perry-ui-windows/src/media_playback.rs +++ b/crates/perry-ui-windows/src/media_playback.rs @@ -27,8 +27,8 @@ use windows::Storage::Streams::RandomAccessStreamReference; extern "C" { fn js_nanbox_get_pointer(value: f64) -> i64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; fn js_string_from_bytes(data: *const u8, len: i32) -> i64; fn js_string_new_sso(data: *const u8, len: u32) -> f64; fn js_run_stdlib_pump(); @@ -624,7 +624,11 @@ fn fire_state_callback(closure_f64: f64, state: MediaState) { let s = state.as_str(); let str_f64 = js_string_new_sso(s.as_ptr(), s.len() as u32); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call1(closure_ptr as *const u8, str_f64); + let _ = js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + str_f64, + ); } } @@ -633,6 +637,11 @@ fn fire_time_callback(closure_f64: f64, current: f64, duration: f64) { js_run_stdlib_pump(); let _ = js_promise_run_microtasks(); let closure_ptr = js_nanbox_get_pointer(closure_f64); - let _ = js_closure_call2(closure_ptr as *const u8, current, duration); + let _ = js_closure_call2( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + current, + duration, + ); } } diff --git a/crates/perry-ui-windows/src/menu.rs b/crates/perry-ui-windows/src/menu.rs index 0e24edbd97..2d7d60517c 100644 --- a/crates/perry-ui-windows/src/menu.rs +++ b/crates/perry-ui-windows/src/menu.rs @@ -8,7 +8,7 @@ use windows::Win32::Foundation::*; use windows::Win32::UI::WindowsAndMessaging::*; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -476,6 +476,6 @@ pub fn dispatch_menu_item(id: u16) { None }); if let Some(ptr) = callback_ptr { - unsafe { js_closure_call0(ptr) }; + unsafe { js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED) }; } } diff --git a/crates/perry-ui-windows/src/network_stub.rs b/crates/perry-ui-windows/src/network_stub.rs index 0ca620f5a8..a29356026e 100644 --- a/crates/perry-ui-windows/src/network_stub.rs +++ b/crates/perry-ui-windows/src/network_stub.rs @@ -7,7 +7,7 @@ //! true reachability detection should target iOS / macOS / Android. extern "C" { - fn js_closure_call2(closure: *const u8, arg0: f64, arg1: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg0: f64, arg1: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: u32) -> *mut u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -28,7 +28,12 @@ pub extern "C" fn perry_system_network_get_status(callback: f64) { if cb.is_null() { return; } - js_closure_call2(cb, f64::from_bits(TAG_TRUE), nb_str("unknown")); + js_closure_call2( + cb, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(TAG_TRUE), + nb_str("unknown"), + ); } } diff --git a/crates/perry-ui-windows/src/pointer.rs b/crates/perry-ui-windows/src/pointer.rs index 8fbe6fff24..8a2e1ccaf2 100644 --- a/crates/perry-ui-windows/src/pointer.rs +++ b/crates/perry-ui-windows/src/pointer.rs @@ -36,8 +36,8 @@ use windows::Win32::UI::WindowsAndMessaging::{ }; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_pointer_event_new(x: f64, y: f64, button: u32, pointer_type: u32) -> f64; } @@ -273,7 +273,7 @@ fn fire_click(handle: i64) { unsafe { let closure_ptr = js_nanbox_get_pointer(cb_f64); if closure_ptr != 0 { - js_closure_call0(closure_ptr as *const u8); + js_closure_call0(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED); } } } @@ -286,7 +286,7 @@ fn fire_pointer_event(cb_f64: f64, x: f64, y: f64, button: u32) { return; } let pe = js_pointer_event_new(x, y, button, POINTER_TYPE_MOUSE); - js_closure_call1(closure_ptr as *const u8, pe); + js_closure_call1(closure_ptr as *const u8, perry_ffi::JsThis::UNDEFINED, pe); } } @@ -301,7 +301,11 @@ fn fire_hover(handle: i64, is_hovering: bool) { return; } let bits = if is_hovering { TAG_TRUE } else { TAG_FALSE }; - js_closure_call1(closure_ptr as *const u8, f64::from_bits(bits)); + js_closure_call1( + closure_ptr as *const u8, + perry_ffi::JsThis::UNDEFINED, + f64::from_bits(bits), + ); } } diff --git a/crates/perry-ui-windows/src/state.rs b/crates/perry-ui-windows/src/state.rs index 1816d2af07..17b81151ee 100644 --- a/crates/perry-ui-windows/src/state.rs +++ b/crates/perry-ui-windows/src/state.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::widgets; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -257,7 +257,7 @@ pub fn state_set(handle: i64, value: f64) { .unwrap_or_default() }); for callback_ptr in onchange_snapshot { - unsafe { js_closure_call1(callback_ptr, value) }; + unsafe { js_closure_call1(callback_ptr, perry_ffi::JsThis::UNDEFINED, value) }; } // Update textfield bindings (state → textfield direction) @@ -319,7 +319,8 @@ fn render_for_each(container: i64, closure: f64, count: f64) { let n = count as i64; let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; widgets::add_child(container, child_handle); } diff --git a/crates/perry-ui-windows/src/toolbar.rs b/crates/perry-ui-windows/src/toolbar.rs index d9a8e6580a..dcae976762 100644 --- a/crates/perry-ui-windows/src/toolbar.rs +++ b/crates/perry-ui-windows/src/toolbar.rs @@ -29,7 +29,7 @@ pub(crate) fn scan_windows_toolbar_gc_roots(visitor: &mut perry_ffi::GcRootVisit } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } diff --git a/crates/perry-ui-windows/src/tray.rs b/crates/perry-ui-windows/src/tray.rs index eac515e4ad..e612bfa48f 100644 --- a/crates/perry-ui-windows/src/tray.rs +++ b/crates/perry-ui-windows/src/tray.rs @@ -28,7 +28,7 @@ use windows::Win32::UI::WindowsAndMessaging::{ }; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -378,7 +378,7 @@ pub fn handle_callback_message(wparam: usize, lparam: isize) { WM_LBUTTONUP => { if !callback_ptr.is_null() { unsafe { - js_closure_call0(callback_ptr); + js_closure_call0(callback_ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-windows/src/widgets/bottom_nav.rs b/crates/perry-ui-windows/src/widgets/bottom_nav.rs index 65dd306bc6..4b1c9291ca 100644 --- a/crates/perry-ui-windows/src/widgets/bottom_nav.rs +++ b/crates/perry-ui-windows/src/widgets/bottom_nav.rs @@ -24,7 +24,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -290,7 +290,7 @@ pub fn set_selected(handle: i64, index: i64) { return; } unsafe { - js_closure_call1(closure_ptr, index as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, index as f64); } } diff --git a/crates/perry-ui-windows/src/widgets/button.rs b/crates/perry-ui-windows/src/widgets/button.rs index 8d3e5b20c4..8546db4326 100644 --- a/crates/perry-ui-windows/src/widgets/button.rs +++ b/crates/perry-ui-windows/src/widgets/button.rs @@ -18,7 +18,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -153,7 +153,7 @@ pub fn handle_click(handle: i64) { callbacks.get(&handle).copied() }); if let Some(ptr) = ptr { - unsafe { js_closure_call0(ptr) }; + unsafe { js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED) }; } } diff --git a/crates/perry-ui-windows/src/widgets/calendar.rs b/crates/perry-ui-windows/src/widgets/calendar.rs index 33190b79d5..46acc4bd48 100644 --- a/crates/perry-ui-windows/src/widgets/calendar.rs +++ b/crates/perry-ui-windows/src/widgets/calendar.rs @@ -19,7 +19,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -209,6 +209,6 @@ pub fn handle_selection_change(handle: i64) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-windows/src/widgets/combobox.rs b/crates/perry-ui-windows/src/widgets/combobox.rs index 3e2d207c34..9a8e225f87 100644 --- a/crates/perry-ui-windows/src/widgets/combobox.rs +++ b/crates/perry-ui-windows/src/widgets/combobox.rs @@ -20,7 +20,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -198,7 +198,7 @@ pub fn handle_change(handle: i64) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-windows/src/widgets/command_palette.rs b/crates/perry-ui-windows/src/widgets/command_palette.rs index cfbcc9e365..e87c7807c5 100644 --- a/crates/perry-ui-windows/src/widgets/command_palette.rs +++ b/crates/perry-ui-windows/src/widgets/command_palette.rs @@ -30,7 +30,7 @@ use windows::Win32::UI::Input::KeyboardAndMouse::SetFocus; use windows::Win32::UI::WindowsAndMessaging::*; extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -301,7 +301,7 @@ fn run_selected() { unsafe { let closure_ptr = js_nanbox_get_pointer(on_run) as *const u8; if !closure_ptr.is_null() { - js_closure_call0(closure_ptr); + js_closure_call0(closure_ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-windows/src/widgets/date_picker.rs b/crates/perry-ui-windows/src/widgets/date_picker.rs index 5f7d6b7dee..75fab080eb 100644 --- a/crates/perry-ui-windows/src/widgets/date_picker.rs +++ b/crates/perry-ui-windows/src/widgets/date_picker.rs @@ -23,7 +23,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -231,6 +231,6 @@ pub fn handle_selection_change(handle: i64) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-windows/src/widgets/foreach_registry.rs b/crates/perry-ui-windows/src/widgets/foreach_registry.rs index 376d74ccb1..db0a282496 100644 --- a/crates/perry-ui-windows/src/widgets/foreach_registry.rs +++ b/crates/perry-ui-windows/src/widgets/foreach_registry.rs @@ -23,7 +23,7 @@ //! `js_nanbox_get_pointer` and call `widgets::add_child`. extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -43,7 +43,8 @@ pub extern "C" fn render_handler(container_handle: i64, render_closure: f64, cou return; } for i in 0..n { - let child_f64 = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_f64 = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child_handle = unsafe { js_nanbox_get_pointer(child_f64) }; if child_handle != 0 { super::add_child(container_handle, child_handle); diff --git a/crates/perry-ui-windows/src/widgets/image_gallery.rs b/crates/perry-ui-windows/src/widgets/image_gallery.rs index 369364c673..18eea7325c 100644 --- a/crates/perry-ui-windows/src/widgets/image_gallery.rs +++ b/crates/perry-ui-windows/src/widgets/image_gallery.rs @@ -17,7 +17,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -161,7 +161,7 @@ pub fn set_index(handle: i64, index: i64) { let closure_ptr = unsafe { js_nanbox_get_pointer(closure) } as *const u8; if !closure_ptr.is_null() { unsafe { - js_closure_call1(closure_ptr, idx as f64); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, idx as f64); } } } diff --git a/crates/perry-ui-windows/src/widgets/lazyvstack.rs b/crates/perry-ui-windows/src/widgets/lazyvstack.rs index b6b6573dfb..1c8e303a97 100644 --- a/crates/perry-ui-windows/src/widgets/lazyvstack.rs +++ b/crates/perry-ui-windows/src/widgets/lazyvstack.rs @@ -5,7 +5,7 @@ use std::cell::RefCell; use std::collections::HashMap; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -45,7 +45,8 @@ pub fn create(count: f64, render_closure: f64) -> i64 { // Render all items for i in 0..item_count { - let child_handle = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_handle = + unsafe { js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) }; let child = child_handle as i64; if child > 0 { super::add_child(vstack_handle, child); @@ -81,7 +82,9 @@ pub fn update(handle: i64, count: i64) { // Re-render all items for i in 0..count { - let child_handle = unsafe { js_closure_call1(closure_ptr, i as f64) }; + let child_handle = unsafe { + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, i as f64) + }; let child = child_handle as i64; if child > 0 { super::add_child(vstack_handle, child); diff --git a/crates/perry-ui-windows/src/widgets/mod.rs b/crates/perry-ui-windows/src/widgets/mod.rs index 11f025f390..e474c8a530 100644 --- a/crates/perry-ui-windows/src/widgets/mod.rs +++ b/crates/perry-ui-windows/src/widgets/mod.rs @@ -1054,7 +1054,7 @@ pub fn handle_scroll(_wparam: usize, _lparam: isize) {} // ============================================================================= extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } diff --git a/crates/perry-ui-windows/src/widgets/picker.rs b/crates/perry-ui-windows/src/widgets/picker.rs index 924038e420..4450890c86 100644 --- a/crates/perry-ui-windows/src/widgets/picker.rs +++ b/crates/perry-ui-windows/src/widgets/picker.rs @@ -13,7 +13,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -202,7 +202,7 @@ pub fn handle_selchange(handle: i64) { PICKER_CALLBACKS.with(|cb| { let callbacks = cb.borrow(); if let Some(&ptr) = callbacks.get(&handle) { - unsafe { js_closure_call1(ptr, index as f64) }; + unsafe { js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, index as f64) }; } }); } diff --git a/crates/perry-ui-windows/src/widgets/rich_text.rs b/crates/perry-ui-windows/src/widgets/rich_text.rs index 631bead6a4..ded5768b8e 100644 --- a/crates/perry-ui-windows/src/widgets/rich_text.rs +++ b/crates/perry-ui-windows/src/widgets/rich_text.rs @@ -23,7 +23,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -360,6 +360,6 @@ pub fn handle_change(handle: i64) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-windows/src/widgets/scrollview.rs b/crates/perry-ui-windows/src/widgets/scrollview.rs index 31986b33e3..6cd99643c9 100644 --- a/crates/perry-ui-windows/src/widgets/scrollview.rs +++ b/crates/perry-ui-windows/src/widgets/scrollview.rs @@ -52,7 +52,7 @@ pub(crate) fn scan_windows_scrollview_gc_roots(visitor: &mut perry_ffi::GcRootVi } extern "C" { - fn js_closure_call0(closure: *const u8) -> f64; + fn js_closure_call0(closure: *const u8, this: perry_ffi::JsThis) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -84,7 +84,7 @@ fn check_scroll_end(handle: i64) { if closure_to_fire != 0.0 { unsafe { let ptr = js_nanbox_get_pointer(closure_to_fire) as *const u8; - js_closure_call0(ptr); + js_closure_call0(ptr, perry_ffi::JsThis::UNDEFINED); } } } diff --git a/crates/perry-ui-windows/src/widgets/securefield.rs b/crates/perry-ui-windows/src/widgets/securefield.rs index 5892a8e118..d5f2767706 100644 --- a/crates/perry-ui-windows/src/widgets/securefield.rs +++ b/crates/perry-ui-windows/src/widgets/securefield.rs @@ -15,7 +15,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -132,7 +132,7 @@ pub fn handle_change(handle: i64) { bytes.len() as u32, ); let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; - unsafe { js_closure_call1(ptr, nanboxed) }; + unsafe { js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, nanboxed) }; } }); } diff --git a/crates/perry-ui-windows/src/widgets/slider.rs b/crates/perry-ui-windows/src/widgets/slider.rs index 10c58e93f7..8633695084 100644 --- a/crates/perry-ui-windows/src/widgets/slider.rs +++ b/crates/perry-ui-windows/src/widgets/slider.rs @@ -15,7 +15,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -210,7 +210,9 @@ pub fn handle_scroll(handle: i64) { let info = info.borrow(); if let Some(si) = info.get(&handle) { let value = pos_to_value(pos, si.min, si.max); - unsafe { js_closure_call1(si.callback_ptr, value) }; + unsafe { + js_closure_call1(si.callback_ptr, perry_ffi::JsThis::UNDEFINED, value) + }; } }); } diff --git a/crates/perry-ui-windows/src/widgets/table.rs b/crates/perry-ui-windows/src/widgets/table.rs index 0b377f87fd..58dea15f75 100644 --- a/crates/perry-ui-windows/src/widgets/table.rs +++ b/crates/perry-ui-windows/src/widgets/table.rs @@ -46,8 +46,8 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -133,7 +133,14 @@ fn render_cell(handle: i64, row: i64, col: i64) -> CellContent { // handle (POINTER_TAG'd small integer) or a string-typed widget for the // simplest Text-only path. The string-typed return shape (top16 == // 0x7FFF) is the common case — extract the bytes directly. - let result = unsafe { js_closure_call2(closure_ptr, row as f64, col as f64) }; + let result = unsafe { + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + row as f64, + col as f64, + ) + }; let bits = result.to_bits(); let top16 = (bits >> 48) as u16; if top16 == 0x7FFF { @@ -838,7 +845,11 @@ pub fn handle_itemchanged(handle: i64, lparam: LPARAM) { return; } unsafe { - js_closure_call1(closure_ptr, info.i_item as f64); + js_closure_call1( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + info.i_item as f64, + ); } } @@ -883,7 +894,12 @@ pub fn handle_columnclick(handle: i64, lparam: LPARAM) { return; } unsafe { - js_closure_call2(closure_ptr, col as f64, if ascending { 1.0 } else { 0.0 }); + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + col as f64, + if ascending { 1.0 } else { 0.0 }, + ); } // Clear the cache so next paint re-renders post-sort. let old_contents = drain_cell_contents(handle); diff --git a/crates/perry-ui-windows/src/widgets/textarea.rs b/crates/perry-ui-windows/src/widgets/textarea.rs index 8ea538600f..0c317236dd 100644 --- a/crates/perry-ui-windows/src/widgets/textarea.rs +++ b/crates/perry-ui-windows/src/widgets/textarea.rs @@ -20,7 +20,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -179,7 +179,7 @@ pub fn handle_change(handle: i64) { let str_ptr = perry_runtime::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; - unsafe { js_closure_call1(ptr, nanboxed) }; + unsafe { js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, nanboxed) }; } } } diff --git a/crates/perry-ui-windows/src/widgets/textfield.rs b/crates/perry-ui-windows/src/widgets/textfield.rs index 1e47ff4361..bf673038ef 100644 --- a/crates/perry-ui-windows/src/widgets/textfield.rs +++ b/crates/perry-ui-windows/src/widgets/textfield.rs @@ -21,7 +21,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; } @@ -160,7 +160,7 @@ pub fn handle_change(handle: i64) { let str_ptr = perry_runtime::string::js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32); let nanboxed = unsafe { js_nanbox_string(str_ptr as i64) }; - unsafe { js_closure_call1(ptr, nanboxed) }; + unsafe { js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, nanboxed) }; } } } diff --git a/crates/perry-ui-windows/src/widgets/toggle.rs b/crates/perry-ui-windows/src/widgets/toggle.rs index 5791fe9fdc..151c9eff4a 100644 --- a/crates/perry-ui-windows/src/widgets/toggle.rs +++ b/crates/perry-ui-windows/src/widgets/toggle.rs @@ -15,7 +15,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -111,7 +111,7 @@ pub fn handle_click(handle: i64) { callbacks.get(&handle).copied() }); if let Some(ptr) = ptr { - unsafe { js_closure_call1(ptr, value) }; + unsafe { js_closure_call1(ptr, perry_ffi::JsThis::UNDEFINED, value) }; } } } diff --git a/crates/perry-ui-windows/src/widgets/tree_view.rs b/crates/perry-ui-windows/src/widgets/tree_view.rs index 8a23ddfb17..c89373c45b 100644 --- a/crates/perry-ui-windows/src/widgets/tree_view.rs +++ b/crates/perry-ui-windows/src/widgets/tree_view.rs @@ -19,7 +19,7 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_string_from_bytes(ptr: *const u8, len: i64) -> *const u8; fn js_nanbox_string(ptr: i64) -> f64; @@ -381,7 +381,7 @@ pub fn handle_selection_change(handle: i64) { let header = js_string_from_bytes(bytes.as_ptr(), bytes.len() as i64); let arg = js_nanbox_string(header as i64); let closure_ptr = js_nanbox_get_pointer(on) as *const u8; - js_closure_call1(closure_ptr, arg); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, arg); } } diff --git a/crates/perry-ui-windows/src/widgets/webview.rs b/crates/perry-ui-windows/src/widgets/webview.rs index 026f09c584..baeaa99371 100644 --- a/crates/perry-ui-windows/src/widgets/webview.rs +++ b/crates/perry-ui-windows/src/widgets/webview.rs @@ -49,8 +49,8 @@ use windows::Win32::UI::WindowsAndMessaging::*; use super::{alloc_control_id, register_widget, WidgetKind}; extern "C" { - fn js_closure_call1(closure: *const u8, arg: f64) -> f64; - fn js_closure_call2(closure: *const u8, arg1: f64, arg2: f64) -> f64; + fn js_closure_call1(closure: *const u8, this: perry_ffi::JsThis, arg: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, arg1: f64, arg2: f64) -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; fn js_nanbox_string(ptr: i64) -> f64; fn js_is_truthy(value: f64) -> i32; @@ -490,7 +490,9 @@ fn install_navigation_handlers(handle: i64, webview: &ICoreWebView2) { catch_panic( "webview onShouldNavigate", std::panic::AssertUnwindSafe(|| { - let r = unsafe { js_closure_call1(closure_ptr, url_nb) }; + let r = unsafe { + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb) + }; result_cell_ref.set(r); }), ); @@ -547,7 +549,7 @@ fn install_navigation_handlers(handle: i64, webview: &ICoreWebView2) { catch_panic( "webview onLoaded", std::panic::AssertUnwindSafe(|| unsafe { - js_closure_call1(closure_ptr, url_nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, url_nb); }), ); } @@ -568,7 +570,12 @@ fn install_navigation_handlers(handle: i64, webview: &ICoreWebView2) { catch_panic( "webview onError", std::panic::AssertUnwindSafe(|| unsafe { - js_closure_call2(closure_ptr, status as f64, msg_nb); + js_closure_call2( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + status as f64, + msg_nb, + ); }), ); } @@ -788,7 +795,7 @@ pub fn evaluate_js(handle: i64, js_ptr: *const u8, callback: f64) { catch_panic( "webview evaluateJs callback", std::panic::AssertUnwindSafe(|| unsafe { - js_closure_call1(closure_ptr, nb); + js_closure_call1(closure_ptr, perry_ffi::JsThis::UNDEFINED, nb); }), ); } diff --git a/crates/perry-ui/src/key_dispatch.rs b/crates/perry-ui/src/key_dispatch.rs index 1cd47cdebd..b7c7644fb5 100644 --- a/crates/perry-ui/src/key_dispatch.rs +++ b/crates/perry-ui/src/key_dispatch.rs @@ -31,8 +31,9 @@ use std::collections::HashMap; use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; extern "C" { - fn js_closure_call2(closure: *const u8, a: f64, b: f64) -> f64; - fn js_closure_call3(closure: *const u8, a: f64, b: f64, c: f64) -> f64; + fn js_closure_call2(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64) -> f64; + fn js_closure_call3(closure: *const u8, this: perry_ffi::JsThis, a: f64, b: f64, c: f64) + -> f64; fn js_nanbox_get_pointer(value: f64) -> i64; } @@ -216,9 +217,15 @@ fn dispatch(callback: f64, code: KeyCode, mods: u32, is_down: bool, is_repeat: b unsafe { if is_down { let repeat_f = if is_repeat { 1.0 } else { 0.0 }; - js_closure_call3(closure_ptr, key_f, mods_f, repeat_f); + js_closure_call3( + closure_ptr, + perry_ffi::JsThis::UNDEFINED, + key_f, + mods_f, + repeat_f, + ); } else { - js_closure_call2(closure_ptr, key_f, mods_f); + js_closure_call2(closure_ptr, perry_ffi::JsThis::UNDEFINED, key_f, mods_f); } } } diff --git a/scripts/check_js_body_call_funnel.py b/scripts/check_js_body_call_funnel.py index 4626a3588d..afa3a10cac 100755 --- a/scripts/check_js_body_call_funnel.py +++ b/scripts/check_js_body_call_funnel.py @@ -26,8 +26,20 @@ 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_*`, perry-ffi's `alloc_closure` / - `register_closure_arity`) declares the receiver. + (`js_closure_alloc*`, `js_register_closure_*`) declares the receiver. + +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: + +* every definition AND every `extern` declaration of an entry, in any crate + (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. """ from __future__ import annotations @@ -138,7 +150,8 @@ def fn_defs(text: str): def call_entries(root: Path) -> set[str]: """`perry_abi::JS_CALL_ENTRIES`: runtime entry points that take a closure - as an ordinary argument (and read the receiver from the cell).""" + as an ordinary argument (and take the receiver as a parameter or bind + `undefined`).""" text = (root / "crates/perry-abi/src/lib.rs").read_text(encoding="utf-8") m = re.search(r"pub const JS_CALL_ENTRIES:[^=]*=\s*\[(.*?)\];", text, re.S) if not m: @@ -206,6 +219,75 @@ def definition_violations(root: Path): return out +# The V8 callback trampoline adapter keeps V8's receiverless contract. +ENTRY_WITHOUT_RECEIVER = {"js_closure_v8_callback"} +# `fn NAME(params) -> ret;` inside an extern block (no body), with an optional +# `#[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_CRATES_ALLOWED = ("crates/perry-runtime/", "crates/perry-stdlib/", "crates/perry-ffi/") +FFI_CLOSURE = "crates/perry-ffi/src" + + +def fn_items(text: str): + """Yield (name, params_text, is_declaration, start) for every `fn` item; + a declaration ends in `;` after its signature (an extern-block item).""" + for m in DECL_RE.finditer(text): + depth, i = 1, m.end() + while i < len(text) and depth: + depth += {"(": 1, ")": -1}.get(text[i], 0) + i += 1 + if depth: + continue + j = i + while j < len(text) and text[j] not in "{;": + j += 1 + yield m.group(1), text[m.end() : i - 1], j < len(text) and text[j] == ";", m.start() + + +def entry_abi_violations(root: Path): + entries = call_entries(root) - ENTRY_WITHOUT_RECEIVER + out = [] + for path in sorted((root / "crates").glob("*/src/**/*.rs")): + rel = path.relative_to(root).as_posix() + text = path.read_text(encoding="utf-8") + for name, params, is_decl, start in fn_items(text): + # This item's own attributes: back to the previous item's end. + head = text[max(text.rfind(";", 0, start), text.rfind("{", 0, start), text.rfind("}", 0, start)) + 1 : start] + ln = LINK_NAME_RE.search(head) if is_decl else None + symbol = ln.group(1) if ln else name + line = text.count("\n", 0, start) + 1 + if symbol in entries and (is_decl or "extern" in text[max(0, start - 40) : start]): + types = param_types(params) + if len(types) < 2 or not JS_THIS_RE.match(types[1]): + what = "declares" if is_decl else "defines" + out.append( + 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 ( + rel.startswith(FFI_CLOSURE) + and not is_decl + and re.match(r"^(?:alloc_closure|register_closure_\w+)$", name) + and "*const u8" in params + ): + out.append( + f"{rel}:{line}: perry-ffi's `{name}` takes an untyped `*const u8` body; " + "take `F: JsBody`" + ) + return out + + def exempt(lines, line: int) -> bool: """A native (non-JS) callback with a body-shaped type — a Rust helper another crate registers — is exempt when one of the three lines above the @@ -276,7 +358,7 @@ def self_test() -> int: "def_macro_no_this": ("crates/perry-runtime/src/x.rs", 'macro_rules! m { ($n:ident) => { extern "C" fn $n(_c: *const ClosureHeader, $($a: f64),*) -> f64 { 0.0 } }; }', True), "def_entry_ok": ("crates/perry-runtime/src/x.rs", - 'pub extern "C" fn js_closure_call2(c: *const ClosureHeader, a: f64, b: f64) -> f64 { a }', False), + 'pub extern "C" fn js_closure_call2(c: *const ClosureHeader, this: JsThis, a: f64, b: f64) -> f64 { a }', False), "def_exempt_ok": ("crates/perry-runtime/src/x.rs", '// NOT-A-JS-BODY: a helper taking a closure argument.\nextern "C" fn helper(c: *const ClosureHeader, a: f64) -> f64 { a }', False), "def_macro_rep_no_this": ("crates/perry-runtime/src/x.rs", @@ -290,6 +372,20 @@ def self_test() -> int: "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), "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), + "entry_decl_no_this": ("crates/perry-ui-zz/src/x.rs", + 'extern "C" {\n fn js_closure_call2(c: *const u8, a: f64, b: f64) -> f64;\n}', True), + "entry_decl_ok": ("crates/perry-ui-zz/src/x.rs", + '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), + "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), } failed = 0 for name, (rel, text, expect_red) in cases.items(): @@ -304,7 +400,10 @@ def self_test() -> int: p.parent.mkdir(parents=True, exist_ok=True) p.write_text(text, encoding="utf-8") red = bool( - runtime_violations(root) or codegen_violations(root) or definition_violations(root) + runtime_violations(root) + or codegen_violations(root) + or definition_violations(root) + or entry_abi_violations(root) ) if red != expect_red: print(f"self-test {name}: expected {'red' if expect_red else 'green'}, got {'red' if red else 'green'}") @@ -321,12 +420,17 @@ def main() -> int: args = ap.parse_args() if args.self_test: return self_test() - violations = runtime_violations(REPO) + codegen_violations(REPO) + definition_violations(REPO) + violations = ( + runtime_violations(REPO) + + codegen_violations(REPO) + + definition_violations(REPO) + + entry_abi_violations(REPO) + ) if violations: print("\n".join(violations)) print(f"\n{len(violations)} violation(s): JS body calls outside the funnels " "(runtime: closure/body_call.rs macros; codegen: expr::body_call::emit_js_body_call) " - "or JS body definitions without the receiver") + "or JS body / call-entry signatures without the receiver") return 1 print( "check_js_body_call_funnel: every JS body call goes through its funnel " diff --git a/scripts/publish_perry_ffi.sh b/scripts/publish_perry_ffi.sh index 32f7ccdfd1..bb5cf1ff7c 100755 --- a/scripts/publish_perry_ffi.sh +++ b/scripts/publish_perry_ffi.sh @@ -4,23 +4,26 @@ # `cargo login` once if missing). # # **Prerequisites — publish dependency packages before perry-ffi.** The -# required perry-native-registration dependency and the optional perry-runtime -# dependency (runtime-link) must both resolve on crates.io. Cargo checks the -# optional edge even when external wrappers leave that feature off. -# For dependency versions not already available, the order is: +# required perry-native-registration and perry-abi dependencies and the +# optional perry-runtime dependency (runtime-link) must all resolve on +# crates.io. Cargo checks the optional edge even when external wrappers leave +# that feature off. For dependency versions not already available, the order +# is: # # 1. cargo publish --dry-run -p perry-native-registration # cargo publish -p perry-native-registration +# cargo publish -p perry-abi (the JS body ABI perry-ffi re-exports) # 2. cargo publish -p perry-runtime # (Its own workspace dependencies need prior publication as appropriate; # dependency publication remains a manual maintainer prerequisite.) # 3. ./scripts/publish_perry_ffi.sh (perry-ffi dry run) # ./scripts/publish_perry_ffi.sh --really-publish (perry-ffi publication) # -# Run from the workspace root. Ships whatever the -# `[workspace.package].version` currently in `Cargo.toml` says, so -# make sure the changeset + Cargo.toml were updated for this release -# first (the standard workflow already covers that). +# Run from the workspace root. perry-ffi carries its OWN version +# (`crates/perry-ffi/Cargo.toml`, 0.6 since the receiver became a body +# parameter); its dependencies ship at the `[workspace.package].version` +# currently in `Cargo.toml`, so make sure the changeset + Cargo.toml were +# updated for this release first (the standard workflow already covers that). set -euo pipefail WORKSPACE_ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -28,7 +31,7 @@ cd "$WORKSPACE_ROOT" VERSION="$(grep -E '^version = "0\.5\.' Cargo.toml | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" echo "Workspace version: ${VERSION}" -echo "Prerequisites: publish perry-native-registration, then perry-runtime, before perry-ffi." +echo "Prerequisites: publish perry-native-registration and perry-abi, then perry-runtime, before perry-ffi." echo "The dependency versions required by Cargo.toml must already resolve on crates.io." # Verify the package builds and would publish cleanly. `--allow-dirty` diff --git a/scripts/raw_handle_debt_files.txt b/scripts/raw_handle_debt_files.txt index aa559be145..5fd1f68588 100644 --- a/scripts/raw_handle_debt_files.txt +++ b/scripts/raw_handle_debt_files.txt @@ -69,7 +69,8 @@ 31 crates/perry-runtime/src/array/iterator.rs 12 crates/perry-runtime/src/array/push_pop.rs 12 crates/perry-runtime/src/array/sort.rs -12 crates/perry-runtime/src/async_hooks.rs +11 crates/perry-runtime/src/async_hooks.rs +1 crates/perry-runtime/src/async_hooks/context_snapshots.rs # moved-from: crates/perry-runtime/src/async_hooks.rs 5 crates/perry-runtime/src/atomics.rs 7 crates/perry-runtime/src/builtins/console.rs 12 crates/perry-runtime/src/builtins/globals.rs diff --git a/scripts/unrooted_local_shape_baseline.json b/scripts/unrooted_local_shape_baseline.json index e6bcb04335..282981087e 100644 --- a/scripts/unrooted_local_shape_baseline.json +++ b/scripts/unrooted_local_shape_baseline.json @@ -2,9 +2,8 @@ "per_file": { "crates/perry-ext-better-sqlite3/src/lib.rs": 13, "crates/perry-ext-cheerio/src/lib.rs": 7, - "crates/perry-ext-events/src/lib.rs": 12, + "crates/perry-ext-events/src/lib.rs": 5, "crates/perry-ext-events/src/module_iterators.rs": 2, - "crates/perry-ext-events/src/module_on.rs": 3, "crates/perry-ext-events/src/tests.rs": 2, "crates/perry-ext-http/src/agent.rs": 3, "crates/perry-ext-http/src/client_request_surface.rs": 2, @@ -40,7 +39,7 @@ "crates/perry-stdlib/src/sqlite/dispatch.rs": 6, "crates/perry-stdlib/src/sqlite/node_stmt_session.rs": 4, "crates/perry-stdlib/src/sqlite/node_tag_store.rs": 3, - "crates/perry-stdlib/src/streams.rs": 25, + "crates/perry-stdlib/src/streams.rs": 24, "crates/perry-stdlib/src/streams/byob.rs": 12, "crates/perry-stdlib/src/streams/pipe.rs": 13, "crates/perry-stdlib/src/streams/strategy.rs": 9, @@ -61,5 +60,5 @@ "crates/perry-stdlib/src/zlib.rs": 3 }, "schema_version": 3, - "total": 380 + "total": 369 } From 1321a447bdb18f2945322f61c6d8c8d2238e2d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 02:49:14 +0000 Subject: [PATCH 04/17] =?UTF-8?q?perf:=20this-as-a-parameter=20stage=202?= =?UTF-8?q?=20=E2=80=94=20bodies=20read=20their=20receiver=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A body that reads its dynamic `this` now reads its receiver parameter instead of the thread-local implicit-`this` cell: function expressions and object-literal methods store `%js_this` into their rooted entry `this` slot (sloppy bodies apply OrdinaryCallBindThis once), top-level functions that read `this` compile to `perry_fn_X$this` behind a receiver-less public forwarder, `__perry_wrap_` forwards its receiver, and the runtime's native bodies read their `this` parameter. The cell is still written by every caller, so this stage can be measured on its own; stage 3 deletes it. Instructions per call against stage 1 (LTO-off build, same host): an object-literal method reading `this` 227 -> 143, an ES5 prototype method 217 -> 134, `forEach` with a `thisArg` 828 -> 744. --- changelog.d/this-as-a-parameter-stage-2.md | 22 +++ crates/perry-codegen/src/codegen/artifacts.rs | 22 +-- crates/perry-codegen/src/codegen/closure.rs | 26 ++- .../src/codegen/export_value_wrappers.rs | 46 ++++-- crates/perry-codegen/src/codegen/function.rs | 91 +++++++++- .../src/codegen/literal_method_this_tests.rs | 71 +++++--- crates/perry-codegen/src/codegen/mod.rs | 10 +- crates/perry-codegen/src/expr/body_call.rs | 41 +++++ .../perry-codegen/src/expr/object_literal.rs | 11 +- .../src/runtime_decls/strings_part2.rs | 3 + .../perry-codegen/src/wasm32/runtime_abi.tsv | 27 +-- crates/perry-runtime/src/async_hooks.rs | 4 +- crates/perry-runtime/src/bun_compat/spawn.rs | 8 +- .../src/child_process/emitter.rs | 46 +++--- .../src/child_process/failed_spawn.rs | 6 +- .../src/child_process/reactor.rs | 8 +- .../src/child_process/value_util.rs | 9 +- .../src/closure/dispatch/calln.rs | 34 ++++ crates/perry-runtime/src/cluster.rs | 48 ++---- crates/perry-runtime/src/dns.rs | 8 +- crates/perry-runtime/src/dyn_eval/interp.rs | 16 +- crates/perry-runtime/src/error.rs | 4 +- .../perry-runtime/src/error_subclass_stack.rs | 8 +- crates/perry-runtime/src/event_target.rs | 42 ++--- .../src/fs/dir_glob_watch/watch.rs | 7 +- crates/perry-runtime/src/fs/stream.rs | 40 ++--- .../src/fs/stream/utf8_stream.rs | 16 +- .../gc/tests/runtime_roots/perex_dispatch.rs | 6 +- .../perry-runtime/src/intl/date_collator.rs | 20 +-- .../src/intl/date_collator/range.rs | 8 +- .../perry-runtime/src/intl/display_names.rs | 8 +- .../perry-runtime/src/intl/duration_format.rs | 12 +- .../src/intl/list_relative_plural.rs | 36 ++-- crates/perry-runtime/src/intl/locale.rs | 88 +++++----- .../perry-runtime/src/intl/number_format.rs | 28 ++-- crates/perry-runtime/src/intl/segmenter.rs | 20 +-- crates/perry-runtime/src/messaging.rs | 32 ++-- .../src/node_api_host/functions.rs | 4 +- crates/perry-runtime/src/node_inspector.rs | 40 ++--- crates/perry-runtime/src/node_repl.rs | 28 ++-- crates/perry-runtime/src/node_stream.rs | 8 +- .../node_stream_constructors/web_adapter.rs | 4 +- .../perry-runtime/src/node_stream_dispatch.rs | 9 +- .../src/node_stream_event_emitter.rs | 9 +- .../src/node_stream_state_view.rs | 15 +- .../perry-runtime/src/node_submodules/blob.rs | 8 +- .../perry-runtime/src/node_submodules/test.rs | 4 +- .../src/node_submodules/test_property.rs | 4 +- .../src/node_submodules/test_reporters.rs | 4 +- .../src/node_submodules/timers.rs | 4 +- .../src/node_submodules/trace_events.rs | 20 +-- crates/perry-runtime/src/node_vm.rs | 20 +-- .../src/object/class_registry/registration.rs | 8 +- .../src/object/collection_proto_thunks.rs | 156 +++++++++++------- .../src/object/date_proto_thunks.rs | 16 +- .../src/object/disposable_proto_thunks.rs | 56 ++++--- .../src/object/global_this/array_error.rs | 128 +++++++------- .../src/object/global_this/bigint_promise.rs | 78 +++++---- .../src/object/global_this/ctor_thunks.rs | 16 +- .../src/object/global_this/generator.rs | 41 +++-- .../src/object/global_this/install_static.rs | 32 ++-- .../src/object/global_this/math_temporal.rs | 16 +- .../src/object/global_this/proto_methods.rs | 27 +-- .../src/object/global_this/typed_array.rs | 20 +-- .../src/object/global_this_webassembly.rs | 37 ++--- crates/perry-runtime/src/object/instanceof.rs | 4 +- .../src/object/iterator_prototypes.rs | 4 +- crates/perry-runtime/src/object/mod.rs | 2 +- .../native_module/async_hooks_exports.rs | 4 +- .../object/native_module/callable_exports.rs | 20 +-- .../callable_exports/buffer_species.rs | 10 +- .../callable_exports/module_cjs.rs | 8 +- .../src/object/regex_proto_thunks.rs | 33 ++-- .../src/object/string_proto_thunks.rs | 59 ++++--- .../src/object/temporal_proto.rs | 8 +- .../perry-runtime/src/object/this_binding.rs | 17 +- .../src/object/weakref_proto_thunks.rs | 19 ++- .../perry-runtime/src/os_process_streams.rs | 42 ++--- .../src/perf_hooks/prototypes.rs | 32 ++-- .../prototypes/performance_methods.rs | 60 +++---- .../perry-runtime/src/process/node_module.rs | 12 +- .../src/process/node_module/source_map.rs | 20 +-- crates/perry-runtime/src/promise/then.rs | 12 +- crates/perry-runtime/src/pty/mod.rs | 30 ++-- crates/perry-runtime/src/regex/match_all.rs | 4 +- .../perry-runtime/src/regex/perex_dispatch.rs | 8 +- .../src/regex/perex_match_search.rs | 16 +- .../perry-runtime/src/regex/perex_replace.rs | 8 +- crates/perry-runtime/src/regex/perex_split.rs | 4 +- crates/perry-runtime/src/regex/site_test.rs | 4 +- crates/perry-runtime/src/text.rs | 28 ++-- .../perry-runtime/src/timer/handle_object.rs | 24 +-- crates/perry-runtime/src/tui/handle_object.rs | 52 +++--- crates/perry-runtime/src/wasi.rs | 12 +- crates/perry-runtime/src/web_storage.rs | 28 ++-- crates/perry-runtime/src/webassembly.rs | 12 +- .../src/common/dispatch/emitter_als.rs | 4 +- crates/perry-stdlib/src/string_decoder.rs | 28 ++-- crates/perry-stdlib/src/worker_threads.rs | 9 +- .../src/worker_threads/worker_surface.rs | 8 +- 100 files changed, 1305 insertions(+), 1084 deletions(-) create mode 100644 changelog.d/this-as-a-parameter-stage-2.md diff --git a/changelog.d/this-as-a-parameter-stage-2.md b/changelog.d/this-as-a-parameter-stage-2.md new file mode 100644 index 0000000000..2e1faa2ef3 --- /dev/null +++ b/changelog.d/this-as-a-parameter-stage-2.md @@ -0,0 +1,22 @@ +A body that reads its dynamic `this` now reads its receiver PARAMETER +(stage 2 of passing `this` as a parameter). The thread-local implicit-`this` +cell is still written by every caller in this commit, so stage 2 can be +measured on its own; stage 3 deletes it. + +- A function expression or object-literal method that reads `this` stores + `%js_this` into its rooted entry `this` slot before the body's first + safepoint. A sloppy body applies OrdinaryCallBindThis once, in place: an + object receiver is tested inline, anything else goes through + `js_this_coerce_sloppy` (nullish -> globalThis, primitive -> wrapper). +- A top-level function that reads `this` is compiled as + `perry_fn_X$this(i64 %js_this, ...)`. The public `perry_fn_X` symbol that + direct calls and other modules name is a forwarder passing `undefined`; the + function's value wrapper passes the receiver it was given. Such functions + are not arena-threaded. +- `__perry_wrap_` forwards its `this` parameter as the method's + receiver, and the runtime's native bodies read their `this` parameter + instead of the cell. + +Instructions per call (LTO-off build, same host) against stage 1: an +object-literal method reading `this` 227 -> 143, an ES5 prototype method +217 -> 134, `forEach` with a `thisArg` 828 -> 744. diff --git a/crates/perry-codegen/src/codegen/artifacts.rs b/crates/perry-codegen/src/codegen/artifacts.rs index d9ad91e4e6..6ffa41789b 100644 --- a/crates/perry-codegen/src/codegen/artifacts.rs +++ b/crates/perry-codegen/src/codegen/artifacts.rs @@ -304,20 +304,19 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { // so `Expr::SuperPropertyGet` (value-form `super.`) can // materialize them via `js_closure_alloc_singleton(@__perry_wrap_)`. // Methods have signature `perry_method_<...>(this_box, args...)`; - // the JS body ABI is `(i64 closure, i64 this, double a0, ...)`. The - // receiver still comes from IMPLICIT_THIS (stage 1 of this-as-a-parameter: - // the `this` parameter carries the same value, which witness builds - // check), set by the dispatcher right before the call: + // the JS body ABI is `(i64 closure, i64 this, double a0, ...)`, so the + // wrapper forwards its receiver parameter as the method's `this`. The + // receiver is the call site's: // * A method-style invocation of a stored super-method value // (`this._complete = super._complete; obj._complete()` — rxjs's // `OperatorSubscriber` forwarding `complete`/`error`/`next` to the - // base `Subscriber` when no override callback was supplied) sets - // IMPLICIT_THIS to the receiver, so the base method runs with the + // base `Subscriber` when no override callback was supplied) passes + // the receiver, so the base method runs with the // right `this`. Pre-fix this hardcoded `this=undefined`, so the // forwarded `complete` never reached `this.destination.complete()` // and the pipeline stalled (top-level await never settled, #5138). - // * A bare call (`const fn = super.greet; fn(x)`) leaves - // IMPLICIT_THIS undefined, matching strict-mode `this`. + // * A bare call (`const fn = super.greet; fn(x)`) passes `undefined`, + // matching strict-mode `this`. let mut emitted_wrappers: std::collections::HashSet = std::collections::HashSet::new(); for class in &hir.classes { for method in &class.methods { @@ -339,9 +338,10 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); - // Forward the call-site receiver (IMPLICIT_THIS) as `this`, - // then the args. See the block comment above (#5138). - let this_box = blk.call(DOUBLE, "js_implicit_this_get", &[]); + // Forward the call-site receiver (the `this` parameter) as the + // method's `this`, then the args. See the block comment above + // (#5138). + let this_box = blk.bitcast_i64_to_double(crate::expr::body_call::JS_BODY_THIS); let mut call_args: Vec<(LlvmType, String)> = Vec::with_capacity(arity + 1); call_args.push((DOUBLE, this_box)); for i in 0..arity { diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index ace6be5cd5..d3a1ba9f07 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -804,10 +804,11 @@ pub(super) fn compile_closure( )); blk.store(DOUBLE, &class_ref, &slot); } else if entry_bound_this { - // A valid non-pointer until the prologue's receiver read below - // fills it, so the slot is safe to bind here with the others. - let undef = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); - blk.store(DOUBLE, &undef, &slot); + // The receiver parameter, stored before the body's first + // safepoint so the bind below makes it a rewritten root; a + // sloppy body coerces it in place in the prologue. + let receiver = blk.bitcast_i64_to_double(crate::expr::body_call::JS_BODY_THIS); + blk.store(DOUBLE, &receiver, &slot); } else { blk.store(DOUBLE, "0.0", &slot); } @@ -1344,23 +1345,18 @@ pub(super) fn compile_closure( super::arguments::ArgumentsCallee::CurrentClosure, ); - // #10906: read the dynamic receiver into the entry `this` slot. It runs + // #10906: the entry `this` slot holds the receiver parameter (stored + // at entry, above). A sloppy body applies OrdinaryCallBindThis to it here, // after every parameter, capture and `arguments` root is bound, because - // the sloppy read can allocate a primitive wrapper — and ahead of the - // first statement, so no user code can have rebound IMPLICIT_THIS yet. - if entry_bound_this { - let helper = if is_strict { - "js_implicit_this_get" - } else { - "js_implicit_this_get_sloppy" - }; + // the coercion can allocate a primitive wrapper — and ahead of the first + // statement. A strict body uses the receiver as passed. + if entry_bound_this && !is_strict { let slot = ctx .this_stack .last() .cloned() .expect("entry-bound `this` has a slot"); - let receiver = ctx.block().call(DOUBLE, helper, &[]); - ctx.block().store(DOUBLE, &receiver, &slot); + crate::expr::body_call::emit_sloppy_receiver_coercion(&mut ctx, &slot); } // #9060 follow-up: resolve loop-called immutable callee bindings once at diff --git a/crates/perry-codegen/src/codegen/export_value_wrappers.rs b/crates/perry-codegen/src/codegen/export_value_wrappers.rs index f650f35ca5..74746d5f73 100644 --- a/crates/perry-codegen/src/codegen/export_value_wrappers.rs +++ b/crates/perry-codegen/src/codegen/export_value_wrappers.rs @@ -129,10 +129,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); - // Call the underlying function with just the arg doubles. - let call_args: Vec<(LlvmType, &str)> = - arg_names.iter().map(|n| (DOUBLE, n.as_str())).collect(); - let mut result = blk.call(DOUBLE, &original_name, &call_args); + // Call the underlying function with the arg doubles — a this-reading + // function's receiver-taking body with the receiver first. + let mut result = call_function_body(blk, f, &original_name, &arg_names); if function_body_returns_generator_object(&f.body) { result = blk.call( DOUBLE, @@ -260,11 +259,8 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { .get(&f.id) .cloned() .unwrap_or_else(|| scoped_fn_name(module_prefix, &f.name)); - let call_args: Vec<(LlvmType, String)> = - (0..arity).map(|i| (DOUBLE, format!("%a{}", i))).collect(); - let call_args_ref: Vec<(LlvmType, &str)> = - call_args.iter().map(|(t, s)| (*t, s.as_str())).collect(); - let result = blk.call(DOUBLE, &target, &call_args_ref); + let arg_names: Vec = (0..arity).map(|i| format!("%a{}", i)).collect(); + let result = call_function_body(blk, f, &target, &arg_names); blk.ret(DOUBLE, &result); } else { // Variable / class / type rename — no callable function @@ -491,11 +487,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { .get(&f.id) .cloned() .unwrap_or_else(|| scoped_fn_name(module_prefix, &f.name)); - let call_args: Vec<(LlvmType, String)> = - (0..arity).map(|i| (DOUBLE, format!("%a{}", i))).collect(); - let call_args_ref: Vec<(LlvmType, &str)> = - call_args.iter().map(|(t, s)| (*t, s.as_str())).collect(); - let result = blk.call(DOUBLE, &target, &call_args_ref); + let arg_names: Vec = + (0..arity).map(|i| format!("%a{}", i)).collect(); + let result = call_function_body(blk, f, &target, &arg_names); blk.ret(DOUBLE, &result); } else { let wf = llmod.define_function( @@ -514,3 +508,27 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { } } } + +/// Call top-level function `f` (public symbol `target`) from one of its +/// value wrappers with the wrapper's JS arguments. A function whose body +/// reads its dynamic `this` is entered through its receiver-taking body +/// (`codegen/function.rs`) with the wrapper's receiver parameter; any other +/// through its public symbol. +fn call_function_body( + blk: &mut crate::block::LlBlock, + f: &perry_hir::Function, + target: &str, + arg_names: &[String], +) -> String { + let mut call_args: Vec<(LlvmType, &str)> = Vec::with_capacity(arg_names.len() + 1); + let receiver_body; + let callee = if perry_hir::analysis::body_reads_dynamic_this(&f.body) { + call_args.push((I64, crate::expr::body_call::JS_BODY_THIS)); + receiver_body = crate::expr::body_call::receiver_body_name(target); + receiver_body.as_str() + } else { + target + }; + call_args.extend(arg_names.iter().map(|n| (DOUBLE, n.as_str()))); + blk.call(DOUBLE, callee, &call_args) +} diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index 113306c2de..fe6dc5ff5e 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -663,6 +663,28 @@ pub(super) fn compile_function( } else { public_llvm_name.clone() }; + // This-as-a-parameter: a body that reads its dynamic `this` binds it at + // entry into a rooted `this` slot. The ordinary public body becomes a + // receiver-taking body (`perry_fn_*$this`, leading `i64 %js_this`) behind + // a public forwarder that passes `undefined` — what every direct call + // binds — and the value wrapper passes the receiver it was given. A + // specialized entry is only ever called directly, so it binds + // `undefined`. (This-reading functions get no typed clone, spec plan or + // arena threading, so the other variants never meet this.) + let reads_this = cross_module.funcs_reading_dynamic_this.contains(&f.id); + let receiver_body = reads_this && spec_entry.is_none(); + debug_assert!( + !receiver_body + || (typed_public_trampoline.is_none() + && guarded_public_plan.is_none() + && !arena_threaded), + "a this-reading function has no public variant but the plain body" + ); + let llvm_name = if receiver_body { + crate::expr::body_call::receiver_body_name(&public_llvm_name) + } else { + llvm_name + }; // Phase A assumes all user-function params are `double`. Parameter // registers are named `%arg{LocalId}` so the body can store them into @@ -685,6 +707,9 @@ pub(super) fn compile_function( if arena_threaded { params.push((PTR, "%perry_arena_state".to_string())); } + if receiver_body { + params.insert(0, (I64, crate::expr::body_call::JS_BODY_THIS.to_string())); + } let ic_base = llmod.ic_counter; let buffer_alias_base = llmod.buffer_alias_counter; @@ -698,6 +723,7 @@ pub(super) fn compile_function( || guarded_public_plan.is_some() || spec_entry.is_some() || arena_threaded + || receiver_body { lf.linkage = "internal".to_string(); } @@ -717,13 +743,16 @@ pub(super) fn compile_function( cross_module.flat_const_arrays.keys().copied().collect(); let m = crate::collectors::collect_pointer_typed_locals(&f.params, &f.body, &flat_const_ids); + // One more slot roots the entry `this` slot of a this-reading body, + // exactly as `codegen/method.rs` reserves one for `%this_arg`. + let this_root_slots = usize::from(reads_this); crate::codegen::helpers::maybe_spill_roots_to_shadow_frame( lf, &llvm_name, - m.len(), + m.len() + this_root_slots, &f.body, ); - lf.enable_shadow_frame(m.len() as u32); + lf.enable_shadow_frame((m.len() + this_root_slots) as u32); m } else { std::collections::HashMap::new() @@ -867,6 +896,31 @@ pub(super) fn compile_function( }; super::arguments::release_boxed_param_slots_at_exit(lf, &f.params, &boxed_vars, &locals); + // The entry `this` slot: the receiver parameter (or `undefined` for a + // directly-called specialized entry), stored before the body's first + // safepoint and bound so a moving collection rewrites it. A sloppy body + // coerces it in place once the parameters are bound (below). + let this_stack: Vec = if reads_this { + let this_shadow_slot_idx = shadow_slot_map.len() as u32; + let blk = lf.block_mut(0).unwrap(); + let this_slot = blk.alloca(DOUBLE); + let receiver = if receiver_body { + blk.bitcast_i64_to_double(crate::expr::body_call::JS_BODY_THIS) + } else { + crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)) + }; + blk.store(DOUBLE, &receiver, &this_slot); + if precise_root_analysis_enabled() { + blk.call_void( + "js_shadow_slot_bind", + &[(I32, &this_shadow_slot_idx.to_string()), (PTR, &this_slot)], + ); + } + vec![this_slot] + } else { + Vec::new() + }; + // Param types feed local_types so type-aware dispatch (e.g. string // concat detection on a `: string` parameter) works inside the body. // Also seed with module global types so functions that access module @@ -1161,7 +1215,7 @@ pub(super) fn compile_function( label_targets: HashMap::new(), pending_labels: Vec::new(), classes, - this_stack: Vec::new(), + this_stack, super_called_stack: Vec::new(), shared_super_scope_active: false, lexical_this_uses_derived_binding: false, @@ -1394,6 +1448,10 @@ pub(super) fn compile_function( Some(&f.body), super::arguments::ArgumentsCallee::FunctionWrapper(&wrapper_name), ); + if reads_this && !f.is_strict { + let slot = ctx.this_stack[0].clone(); + crate::expr::body_call::emit_sloppy_receiver_coercion(&mut ctx, &slot); + } // Issue #92 follow-up: pre-register `buffer_data_slots` entries for // `Buffer`-typed function parameters so that the readInt32BE/etc. @@ -1594,6 +1652,33 @@ pub(super) fn compile_function( } } else if arena_threaded { emit_public_arena_threaded_wrapper(llmod, f, &public_llvm_name, &llvm_name); + } else if receiver_body { + emit_public_receiverless_forwarder(llmod, f, &public_llvm_name, &llvm_name); } Ok(()) } + +/// The public `perry_fn_*` symbol of a this-reading function: every direct +/// call binds no receiver, so it forwards to the receiver-taking body with +/// `undefined`. +fn emit_public_receiverless_forwarder( + llmod: &mut LlModule, + f: &Function, + public_name: &str, + receiver_body_name: &str, +) { + let params: Vec<(LlvmType, String)> = f + .params + .iter() + .map(|p| (DOUBLE, format!("%arg{}", p.id))) + .collect(); + let wf = llmod.define_function(public_name, DOUBLE, params.clone()); + wf.force_inline = true; + let _ = wf.create_block("entry"); + let mut call_args: Vec<(LlvmType, &str)> = + vec![(I64, crate::expr::body_call::JS_THIS_UNDEFINED)]; + call_args.extend(params.iter().map(|(ty, a)| (*ty, a.as_str()))); + let blk = wf.block_mut(0).unwrap(); + let value = blk.call(DOUBLE, receiver_body_name, &call_args); + blk.ret(DOUBLE, &value); +} diff --git a/crates/perry-codegen/src/codegen/literal_method_this_tests.rs b/crates/perry-codegen/src/codegen/literal_method_this_tests.rs index ef1c263a63..af33419e5a 100644 --- a/crates/perry-codegen/src/codegen/literal_method_this_tests.rs +++ b/crates/perry-codegen/src/codegen/literal_method_this_tests.rs @@ -3,8 +3,8 @@ //! HIR lowers `{ a: 1, m() { … this.a … } }` to //! `new __AnonShape_(1, )`. These pin the two //! halves of the fix: the closure binds its receiver ONCE at entry into a -//! rooted `this` slot (it used to call `js_implicit_this_get_sloppy` at every -//! `this`), and `this.field` nominates the literal's shape class for the +//! rooted `this` slot (it used to read a thread-local receiver cell at every +//! `this`; it now stores its `%js_this` parameter), and `this.field` nominates the literal's shape class for the //! guarded class-field path instead of the generic per-site IC. use crate::{compile_module, CompileOptions}; @@ -176,7 +176,44 @@ fn closure_body(ir: &str, func_id: u32) -> &str { } fn calls_to(body: &str, callee: &str) -> usize { - body.matches(&format!("@{callee}()")).count() + body.matches(&format!("@{callee}(")).count() +} + +/// The register holding the receiver parameter as a double, and the slot it +/// is stored into at entry. +fn entry_receiver_slot(body: &str) -> (String, String) { + let conv = body + .lines() + .find(|l| l.contains("bitcast i64 %js_this to double")) + .unwrap_or_else(|| panic!("the receiver parameter is never read:\n{body}")); + let value = conv.split('=').next().unwrap().trim().to_string(); + // The store is either `store double %v, ptr %slot` or, once the + // root-lowering pass rewrote the slot, the same value through its + // `bitcast` / `inttoptr` pair into `store ptr addrspace(1) %s, ptr %slot`. + let mut names = vec![value.clone()]; + for l in body.lines() { + let l = l.trim(); + let Some((lhs, rhs)) = l.split_once(" = ") else { + continue; + }; + if (rhs.starts_with("bitcast ") || rhs.starts_with("inttoptr ")) + && names.iter().any(|n| rhs.contains(&format!(" {n} to "))) + { + names.push(lhs.trim().to_string()); + } + } + let slot = body + .lines() + .find_map(|l| { + let l = l.trim(); + names.iter().find_map(|n| { + l.strip_prefix(&format!("store double {n}, ptr ")) + .or_else(|| l.strip_prefix(&format!("store ptr addrspace(1) {n}, ptr "))) + .map(|s| s.trim().to_string()) + }) + }) + .unwrap_or_else(|| panic!("receiver {value} is never stored:\n{body}")); + (value, slot) } #[test] @@ -186,27 +223,19 @@ fn literal_method_reads_its_receiver_once_at_entry_into_a_rooted_slot() { let body = closure_body(&ir, METHOD); assert_eq!( - calls_to(body, "js_implicit_this_get_sloppy"), + body.matches("%js_this").count(), + 2, + "three `this` uses must share ONE entry read of the parameter:\n{body}" + ); + assert_eq!( + calls_to(body, "js_this_coerce_sloppy"), 1, - "three `this` uses must share ONE entry read:\n{body}" + "the sloppy coercion runs once, in the prologue:\n{body}" ); - assert_eq!(calls_to(body, "js_implicit_this_get"), 0, "{body}"); // The receiver lands in a slot the shadow frame roots, so an evacuating // minor inside the body rewrites it along with every other root. - let read = body - .lines() - .find(|l| l.contains("@js_implicit_this_get_sloppy()")) - .unwrap(); - let value = read.split('=').next().unwrap().trim(); - let slot = body - .lines() - .find_map(|l| { - l.trim() - .strip_prefix(&format!("store double {value}, ptr ")) - .map(str::trim) - }) - .unwrap_or_else(|| panic!("entry read {value} is never stored:\n{body}")); + let (_, slot) = entry_receiver_slot(body); assert!( body.lines().any(|l| l.contains("@js_shadow_slot_bind(") && l.trim_end().ends_with(&format!("ptr {slot})"))), @@ -218,8 +247,8 @@ fn literal_method_reads_its_receiver_once_at_entry_into_a_rooted_slot() { fn strict_literal_method_binds_the_raw_receiver() { let ir = compile_ir(&literal_module(method(METHOD, false, true))); let body = closure_body(&ir, METHOD); - assert_eq!(calls_to(body, "js_implicit_this_get"), 1, "{body}"); - assert_eq!(calls_to(body, "js_implicit_this_get_sloppy"), 0, "{body}"); + let _ = entry_receiver_slot(body); + assert_eq!(calls_to(body, "js_this_coerce_sloppy"), 0, "{body}"); } #[test] diff --git a/crates/perry-codegen/src/codegen/mod.rs b/crates/perry-codegen/src/codegen/mod.rs index 093bd6b023..15723d9932 100644 --- a/crates/perry-codegen/src/codegen/mod.rs +++ b/crates/perry-codegen/src/codegen/mod.rs @@ -2471,6 +2471,14 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result> llmod.add_external_module_state_global(shape, I32); } + // A function whose body reads its dynamic `this` gets a receiver-taking + // body (`codegen/function.rs`); the arena-threaded wrapper scheme does + // not compose with it, so those stay ordinary. + let arena_threaded_functions: std::collections::HashSet = + crate::collectors::collect_self_recursive_allocators(hir) + .into_iter() + .filter(|id| !funcs_reading_dynamic_this.contains(id)) + .collect(); let mut cross_module = CrossModuleCtx { namespace_imports: opts.namespace_imports.iter().cloned().collect(), namespace_member_nested: opts.namespace_member_nested.iter().cloned().collect(), @@ -2573,7 +2581,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result> // no call-site cap (the cap prices `inlinehint`'s duplication, which // the inline bump allocator does not incur), plus direct recursion. alloc_hot_functions: crate::collectors::collect_alloc_hot_functions(hir), - arena_threaded_functions: crate::collectors::collect_self_recursive_allocators(hir), + arena_threaded_functions, clamp3_functions: hir .functions .iter() diff --git a/crates/perry-codegen/src/expr/body_call.rs b/crates/perry-codegen/src/expr/body_call.rs index 2e606b4646..bf48e75407 100644 --- a/crates/perry-codegen/src/expr/body_call.rs +++ b/crates/perry-codegen/src/expr/body_call.rs @@ -158,3 +158,44 @@ pub(crate) fn emit_this_param_witness(blk: &mut LlBlock, site: Option<&(String, ], ); } + +/// The receiver prologue of a SLOPPY body that reads `this` (stage 2 of +/// this-as-a-parameter): `slot` is the body's rooted entry `this` slot, which +/// already holds the receiver parameter's bits (stored before the body's +/// first safepoint, so a moving collection rewrites it). Applies +/// OrdinaryCallBindThis once, in place: an object receiver is kept inline; +/// anything else (`undefined`, `null`, a primitive, a class ref) goes through +/// `js_this_coerce_sloppy`, the only safepoint here. A strict body needs no +/// prologue: its receiver is the parameter as passed. +pub(crate) fn emit_sloppy_receiver_coercion(ctx: &mut crate::expr::FnCtx<'_>, slot: &str) { + let (value, is_object) = { + let blk = ctx.block(); + let value = blk.load(DOUBLE, slot); + let bits = blk.bitcast_double_to_i64(&value); + let top16 = blk.lshr(I64, &bits, "48"); + let is_object = blk.icmp_eq(I64, &top16, crate::nanbox::POINTER_TAG_TOP16_I64); + (value, is_object) + }; + let slow_idx = ctx.new_block("this_prologue.coerce"); + let done_idx = ctx.new_block("this_prologue.done"); + let slow_label = ctx.block_label(slow_idx); + let done_label = ctx.block_label(done_idx); + ctx.block().cond_br(&is_object, &done_label, &slow_label); + ctx.current_block = slow_idx; + { + let blk = ctx.block(); + let coerced = blk.call(DOUBLE, "js_this_coerce_sloppy", &[(DOUBLE, &value)]); + blk.store(DOUBLE, &coerced, slot); + blk.br(&done_label); + } + ctx.current_block = done_idx; +} + +/// The body of a top-level function that reads its dynamic `this`: the +/// function compiled with the receiver as a leading `i64 %js_this` +/// parameter. Its value wrapper passes the receiver it was given; the public +/// `perry_fn_*` symbol every direct call (and every other module) names is a +/// forwarder passing `undefined` — a direct call binds no receiver. +pub(crate) fn receiver_body_name(public_name: &str) -> String { + format!("{public_name}$this") +} diff --git a/crates/perry-codegen/src/expr/object_literal.rs b/crates/perry-codegen/src/expr/object_literal.rs index 1df756c909..82cf8ae4a6 100644 --- a/crates/perry-codegen/src/expr/object_literal.rs +++ b/crates/perry-codegen/src/expr/object_literal.rs @@ -756,11 +756,20 @@ mod by_name_method_closure_tests { /// The `build` function's body, and only it — the closure bodies are /// separate `define`s and contain none of what is asserted below. pub(super) fn build_fn(ir: &str) -> String { + // `build` reads its dynamic `this` (the analysis counts the methods' + // `captures_this`), so it is compiled as a receiver-taking + // `__build$this` body behind a forwarding `__build`; the lowering + // under test is in the body. + let marker = if ir.contains("__build$this(") { + "__build$this(" + } else { + "__build(" + }; let mut body = Vec::new(); let mut inside = false; for line in ir.lines() { if line.starts_with("define ") { - inside = line.contains("__build("); + inside = line.contains(marker); continue; } if inside { diff --git a/crates/perry-codegen/src/runtime_decls/strings_part2.rs b/crates/perry-codegen/src/runtime_decls/strings_part2.rs index 568f069485..b540e7e5df 100644 --- a/crates/perry-codegen/src/runtime_decls/strings_part2.rs +++ b/crates/perry-codegen/src/runtime_decls/strings_part2.rs @@ -15,6 +15,9 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) { // by a `PERRY_THIS_WITNESS=1` build; declared unconditionally for the same // reason. module.declare_function("js_this_param_witness", VOID, &[I64, PTR, I64]); + // OrdinaryCallBindThis for a sloppy body's receiver parameter + // (`expr::body_call::emit_receiver_prologue`). Can allocate: a safepoint. + module.declare_function("js_this_coerce_sloppy", DOUBLE, &[DOUBLE]); // RegExp exec module.declare_function("js_regexp_exec", I64, &[I64, I64]); module.declare_function("js_number_to_precision", I64, &[DOUBLE, DOUBLE]); diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index 10767fb42c..c6e3e6a04a 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -617,26 +617,9 @@ js_closure_alloc ptr ptr,i32u js_closure_alloc_init ptr ptr,i32u,ptr js_closure_alloc_singleton ptr ptr js_closure_alloc_with_captures_singleton ptr ptr,i32u,ptr -js_closure_call0 f64 ptr -js_closure_call1 f64 ptr,f64 -js_closure_call10 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call11 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call12 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call13 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call14 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call15 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call16 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call1_receiverless f64 ptr,f64 -js_closure_call2 f64 ptr,f64,f64 -js_closure_call3 f64 ptr,f64,f64,f64 -js_closure_call4 f64 ptr,f64,f64,f64,f64 -js_closure_call5 f64 ptr,f64,f64,f64,f64,f64 -js_closure_call6 f64 ptr,f64,f64,f64,f64,f64,f64 -js_closure_call7 f64 ptr,f64,f64,f64,f64,f64,f64,f64 -js_closure_call8 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call9 f64 ptr,f64,f64,f64,f64,f64,f64,f64,f64,f64 -js_closure_call_apply_with_spread f64 f64,ptr,i64,i64 -js_closure_call_array f64 i64,ptr,i64 +js_closure_call0 f64 ptr,i64 +js_closure_call_apply_with_spread f64 f64,i64,ptr,i64,i64 +js_closure_call_array f64 i64,i64,ptr,i64 js_closure_exact_func_guard i64 f64,ptr js_closure_get_capture_bits i64 ptr,i32u js_closure_get_capture_f64 f64 ptr,i32u @@ -650,6 +633,7 @@ js_closure_set_capture_f64 void ptr,i32u,f64 js_closure_set_capture_ptr void ptr,i32u,i64 js_closure_unbox_callee_checked i64 f64 js_closure_unbox_callee_checked_rebind i64 f64,f64 +js_closure_v8_callback f64 i64,ptr,i64 js_cluster_disconnect f64 f64 js_cluster_emit f64 f64,ptr js_cluster_event_names f64 @@ -1986,7 +1970,7 @@ js_native_call_method_patched_proto_apply f64 f64,ptr,usize,i64 js_native_call_method_str_key f64 f64,i64,ptr,usize js_native_call_method_value f64 f64,f64,ptr,usize js_native_call_method_value_apply f64 f64,f64,i64 -js_native_call_value f64 f64,ptr,usize +js_native_call_value f64 f64,i64,ptr,usize js_native_handle_dispose i32s f64 js_native_handle_mark_main_thread void js_native_handle_new_borrowed f64 i64,i64,i32s,i32s,ptr,i64 @@ -3699,6 +3683,7 @@ js_text_encoder_encode_llvm i64 f64 js_text_encoder_new i64 js_text_encoder_stream_new f64 js_text_encoding_stream_new f64 +js_this_coerce_sloppy f64 f64 js_this_param_witness void i64,ptr,i64 js_thread_has_pending i32s js_thread_parallel_filter f64 f64,f64 diff --git a/crates/perry-runtime/src/async_hooks.rs b/crates/perry-runtime/src/async_hooks.rs index fd185a537a..461a10b713 100644 --- a/crates/perry-runtime/src/async_hooks.rs +++ b/crates/perry-runtime/src/async_hooks.rs @@ -1664,7 +1664,7 @@ pub extern "C" fn js_async_resource_run_in_async_scope( /// the bound resource's async id active. extern "C" fn async_resource_bind_trampoline( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { if closure.is_null() { @@ -1677,7 +1677,7 @@ extern "C" fn async_resource_bind_trampoline( return TAG_UNDEFINED_F64; } if JSValue::from_bits(this_arg.to_bits()).is_undefined() { - this_arg = crate::object::js_implicit_this_get(); + this_arg = this.as_f64(); } let args_array_ptr = ptr_from_nanboxed(rest) as i64; js_async_resource_run_in_async_scope(handle, callback, this_arg, args_array_ptr) diff --git a/crates/perry-runtime/src/bun_compat/spawn.rs b/crates/perry-runtime/src/bun_compat/spawn.rs index a5d075ac14..5f604b35cd 100644 --- a/crates/perry-runtime/src/bun_compat/spawn.rs +++ b/crates/perry-runtime/src/bun_compat/spawn.rs @@ -933,13 +933,9 @@ extern "C" fn bun_pty_subprocess_unref( #[cfg(unix)] extern "C" fn bun_pty_subprocess_dispose( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let _ = bun_pty_subprocess_kill( - closure, - crate::closure::body_call::current_this(), - cp_undefined(), - ); + let _ = bun_pty_subprocess_kill(closure, this, cp_undefined()); cp_undefined() } diff --git a/crates/perry-runtime/src/child_process/emitter.rs b/crates/perry-runtime/src/child_process/emitter.rs index 2e9c48441c..8c3d58e170 100644 --- a/crates/perry-runtime/src/child_process/emitter.rs +++ b/crates/perry-runtime/src/child_process/emitter.rs @@ -122,7 +122,7 @@ pub(crate) extern "C" fn cp_method_on( event: f64, cb: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); cp_register(this, event, cb); this } @@ -132,7 +132,7 @@ pub(crate) extern "C" fn cp_method_emit( event: f64, arg: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); let name = match cp_value_to_string(event) { Some(n) => n, None => return TAG_FALSE_F64, @@ -145,16 +145,16 @@ pub(crate) extern "C" fn cp_method_emit( } pub(crate) extern "C" fn cp_method_this0( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - cp_this(closure) + cp_this(this, closure) } pub(crate) extern "C" fn cp_method_this1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - cp_this(closure) + cp_this(this, closure) } /// Keep a live child attached to the event loop. Calls are idempotent, matching @@ -163,7 +163,7 @@ pub(crate) extern "C" fn cp_method_ref( closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_live_set_refed(handle, true); } @@ -177,7 +177,7 @@ pub(crate) extern "C" fn cp_method_unref( closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_live_set_refed(handle, false); } @@ -189,11 +189,11 @@ pub(crate) extern "C" fn cp_method_unref( /// constructed idle object inert after its setup checks. pub(crate) extern "C" fn cp_method_child_spawn( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, options: f64, ) -> f64 { crate::child_process::validate::cp_validate_child_process_spawn(options); - cp_this(closure) + cp_this(this, closure) } /// `child.stdout.setEncoding(encoding)` switches emitted chunks from Buffers /// to decoded strings. The reactor reads this field when it delivers data. @@ -202,7 +202,7 @@ pub(crate) extern "C" fn cp_method_set_encoding( _this: crate::closure::JsThis, encoding: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); cp_set_field(this, b"__cpEncoding", encoding); this } @@ -212,7 +212,7 @@ pub(crate) extern "C" fn cp_method_kill( signal: f64, ) -> f64 { cp_validate_signal(signal); - let this = cp_this(closure); + let this = cp_this(_this, closure); cp_set_field(this, b"killed", TAG_TRUE_F64); // #1934: signal the live child if one is still running. `__cpHandle` is the // reactor registry key set by `spawn`. Returns true when the signal was @@ -229,13 +229,9 @@ pub(crate) extern "C" fn cp_method_kill( /// scope exit. #2556. pub(crate) extern "C" fn cp_method_dispose( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let _ = cp_method_kill( - closure, - crate::closure::body_call::current_this(), - cp_undefined(), - ); + let _ = cp_method_kill(closure, this, cp_undefined()); cp_undefined() } pub(crate) fn js_fork_child(args_len: usize) -> f64 { @@ -255,7 +251,7 @@ pub(crate) extern "C" fn cp_method_remove_listener( event: f64, cb: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(name) = cp_value_to_string(event) { let key = cp_listener_key(&name); if let Some(arr) = cp_array_ptr(cp_get_field(this, &key)) { @@ -280,7 +276,7 @@ pub(crate) extern "C" fn cp_method_remove_all_listeners( _this: crate::closure::JsThis, event: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(name) = cp_value_to_string(event) { let key = cp_listener_key(&name); let empty = crate::array::js_array_alloc(0); @@ -323,7 +319,7 @@ pub(crate) extern "C" fn cp_method_pipe( _this: crate::closure::JsThis, dest: f64, ) -> f64 { - let this = cp_this(closure); + 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); @@ -455,7 +451,7 @@ pub(crate) extern "C" fn cp_method_stdin_write( arg2: f64, arg3: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); let scope = crate::gc::RuntimeHandleScope::new(); let callback = cp_stream_callback(arg2, arg3).map(|cb| scope.root_nanbox_f64(cb)); let mut result = TAG_TRUE_F64; @@ -526,7 +522,7 @@ pub(crate) extern "C" fn cp_method_send( "ERR_INVALID_ARG_TYPE", ); } - let this = cp_this(closure); + let this = cp_this(_this, closure); // The callback is the last argument when it is a function. dispatch pads // missing slots with `undefined`, so scan slots 4→2 for a closure. @@ -617,7 +613,7 @@ pub(crate) extern "C" fn cp_method_disconnect( closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(handle) = cp_handle_of(this) { reactor::cp_ipc_disconnect(handle); } @@ -649,7 +645,7 @@ pub(crate) extern "C" fn cp_method_stdin_end( arg2: f64, arg3: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); let scope = crate::gc::RuntimeHandleScope::new(); let callback = cp_stream_callback(arg2, arg3) .or_else(|| (!crate::fs::extract_closure_ptr(chunk).is_null()).then_some(chunk)) diff --git a/crates/perry-runtime/src/child_process/failed_spawn.rs b/crates/perry-runtime/src/child_process/failed_spawn.rs index 45c8946e95..0679ea9d77 100644 --- a/crates/perry-runtime/src/child_process/failed_spawn.rs +++ b/crates/perry-runtime/src/child_process/failed_spawn.rs @@ -7,11 +7,11 @@ use super::*; /// Keep fork's separate failure contract unchanged by using this for spawn only. pub(super) extern "C" fn emit_error_then_close( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let cp = scope.root_nanbox_f64(cp_this(closure)); - reactor::cp_emit_spawn_error(closure, crate::closure::body_call::current_this()); + 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); 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); diff --git a/crates/perry-runtime/src/child_process/reactor.rs b/crates/perry-runtime/src/child_process/reactor.rs index 1eed1dc748..cb28861886 100644 --- a/crates/perry-runtime/src/child_process/reactor.rs +++ b/crates/perry-runtime/src/child_process/reactor.rs @@ -1023,10 +1023,10 @@ pub extern "C" fn js_child_process_spawn_streams( /// captures the ChildProcess value. pub(super) extern "C" fn cp_emit_spawn_error( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let cp = scope.root_nanbox_f64(cp_this(closure)); + let cp = scope.root_nanbox_f64(cp_this(this, closure)); let err = scope.root_nanbox_f64(cp_get_field(cp.get_nanbox_f64(), b"__cpError")); if !JSValue::from_bits(err.get_nanbox_f64().to_bits()).is_undefined() { cp_emit(cp.get_nanbox_f64(), "error", &[err.get_nanbox_f64()]); @@ -1037,10 +1037,10 @@ pub(super) extern "C" fn cp_emit_spawn_error( pub(super) extern "C" fn cp_emit_spawn_close( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let cp = scope.root_nanbox_f64(cp_this(closure)); + let cp = scope.root_nanbox_f64(cp_this(this, closure)); super::failed_spawn::finish_outputs(cp.get_nanbox_f64()); let code = cp_get_field(cp.get_nanbox_f64(), b"exitCode"); cp_emit(cp.get_nanbox_f64(), "close", &[code, TAG_NULL_F64]); diff --git a/crates/perry-runtime/src/child_process/value_util.rs b/crates/perry-runtime/src/child_process/value_util.rs index 18dbb2f640..9b5ce088fc 100644 --- a/crates/perry-runtime/src/child_process/value_util.rs +++ b/crates/perry-runtime/src/child_process/value_util.rs @@ -1,10 +1,7 @@ use super::*; use crate::closure::{js_closure_get_capture_ptr, ClosureHeader}; -use crate::object::{ - js_implicit_this_get, js_object_get_field_by_name_f64, js_object_set_field_by_name, - ObjectHeader, -}; +use crate::object::{js_object_get_field_by_name_f64, js_object_set_field_by_name, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; use crate::value::JSValue; @@ -20,9 +17,9 @@ pub(crate) fn cp_box_ptr(ptr: *const u8) -> f64 { /// Recover the host object value captured in closure slot 0 by `cp_build_object`. #[inline] -pub(crate) fn cp_this(closure: *const ClosureHeader) -> f64 { +pub(crate) fn cp_this(this: crate::closure::JsThis, closure: *const ClosureHeader) -> f64 { if closure.is_null() { - return js_implicit_this_get(); + return this.as_f64(); } f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64) } diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index 039ff3d526..b15ad134f0 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -975,6 +975,40 @@ fn closure_call16_cell( } } +#[cfg(test)] +mod plain_receiver_tests { + use super::*; + + extern "C" fn observe_dynamic_this( + _: *const ClosureHeader, + this: crate::closure::JsThis, + _: f64, + ) -> f64 { + this.as_f64() + } + + #[test] + fn an_undefined_receiver_is_bound_for_non_arrows_only() { + 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 sentinel = 42.0; + let original = crate::object::js_implicit_this_set(sentinel); + let regular_result = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); + assert_eq!(regular_result.to_bits(), crate::value::TAG_UNDEFINED); + assert_eq!(crate::object::js_implicit_this_get(), sentinel); + + crate::closure::js_register_closure_arrow_function(body); + // An arrow keeps its lexical `this`: the entry binds nothing, and the + // body is handed the cell as it stands. + let arrow_result = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); + assert_eq!(arrow_result, sentinel); + assert_eq!(crate::object::js_implicit_this_get(), sentinel); + crate::object::js_implicit_this_set(original); + } +} + /// `perry_abi::JS_CLOSURE_CALL_ENTRIES` is what codegen DECLARES; these are /// the functions it links to. Each entry must name the function taking /// exactly its index's JS argument count (the coercion below fails to compile diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 4dc97c32fc..23362863ab 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -814,76 +814,58 @@ pub(crate) fn is_worker_instance_value(value: f64) -> bool { extern "C" fn cluster_worker_send( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, message: f64, a2: f64, a3: f64, a4: f64, ) -> f64 { - if is_self_worker_value(crate::object::js_implicit_this_get()) { + if is_self_worker_value(this.as_f64()) { return crate::process::process_ipc_send_call(message, a2, a3, a4); } - crate::child_process::cp_method_send( - std::ptr::null(), - crate::closure::body_call::current_this(), - message, - a2, - a3, - a4, - ) + crate::child_process::cp_method_send(std::ptr::null(), this, message, a2, a3, a4) } extern "C" fn cluster_worker_kill( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, signal: f64, ) -> f64 { - if is_self_worker_value(crate::object::js_implicit_this_get()) { + if is_self_worker_value(this.as_f64()) { let _ = crate::os::js_process_kill(crate::os::js_process_pid(), signal); return TAG_UNDEFINED_F64; } - let _ = crate::child_process::cp_method_kill( - std::ptr::null(), - crate::closure::body_call::current_this(), - signal, - ); + let _ = crate::child_process::cp_method_kill(std::ptr::null(), this, signal); TAG_UNDEFINED_F64 } extern "C" fn cluster_worker_destroy( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, signal: f64, ) -> f64 { - cluster_worker_kill( - std::ptr::null(), - crate::closure::body_call::current_this(), - signal, - ) + cluster_worker_kill(std::ptr::null(), this, signal) } extern "C" fn cluster_worker_disconnect( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let worker = crate::object::js_implicit_this_get(); + let worker = this.as_f64(); set_field(worker, b"exitedAfterDisconnect", TAG_TRUE_F64); if is_self_worker_value(worker) { let _ = crate::process::process_ipc_disconnect_call(); } else { - let _ = crate::child_process::cp_method_disconnect( - std::ptr::null(), - crate::closure::body_call::current_this(), - ); + let _ = crate::child_process::cp_method_disconnect(std::ptr::null(), this); } worker } extern "C" fn cluster_worker_is_connected( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let worker = crate::object::js_implicit_this_get(); + let worker = this.as_f64(); let connected = if is_self_worker_value(worker) { crate::process::ipc::process_ipc_property("connected").unwrap_or(TAG_FALSE_F64) } else { @@ -898,9 +880,9 @@ extern "C" fn cluster_worker_is_connected( extern "C" fn cluster_worker_is_dead( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if is_worker_dead(crate::object::js_implicit_this_get()) { + if is_worker_dead(this.as_f64()) { TAG_TRUE_F64 } else { TAG_FALSE_F64 diff --git a/crates/perry-runtime/src/dns.rs b/crates/perry-runtime/src/dns.rs index 3adebe5a5d..ac25e4c635 100644 --- a/crates/perry-runtime/src/dns.rs +++ b/crates/perry-runtime/src/dns.rs @@ -1221,9 +1221,9 @@ extern "C" fn dns_noop2_thunk( extern "C" fn dns_resolver_get_servers_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); let Some(obj) = resolver_object_from_value(this_value) else { return empty_array_value(); }; @@ -1232,10 +1232,10 @@ extern "C" fn dns_resolver_get_servers_thunk( extern "C" fn dns_resolver_set_servers_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, servers_value: f64, ) -> f64 { - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); let Some(obj) = resolver_object_from_value(this_value) else { return dns_promises_set_servers_value(servers_value); }; diff --git a/crates/perry-runtime/src/dyn_eval/interp.rs b/crates/perry-runtime/src/dyn_eval/interp.rs index 9b63f153a2..58f74b7c93 100644 --- a/crates/perry-runtime/src/dyn_eval/interp.rs +++ b/crates/perry-runtime/src/dyn_eval/interp.rs @@ -275,28 +275,32 @@ fn ensure_thunk_registered() { #[cfg(not(panic = "abort"))] extern "C-unwind" fn interp_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, raw_args: f64, ) -> f64 { - interp_thunk_impl(closure, raw_args) + interp_thunk_impl(this, closure, raw_args) } #[cfg(panic = "abort")] extern "C" fn interp_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, raw_args: f64, ) -> f64 { - interp_thunk_impl(closure, raw_args) + interp_thunk_impl(this, closure, raw_args) } -fn interp_thunk_impl(closure: *const crate::closure::ClosureHeader, raw_args: f64) -> f64 { +fn interp_thunk_impl( + this: crate::closure::JsThis, + closure: *const crate::closure::ClosureHeader, + raw_args: f64, +) -> f64 { let fn_id = crate::closure::js_closure_get_capture_f64(closure, 0) as u32; let def_env = f64::from_bits(crate::closure::js_closure_get_capture_bits(closure, 1)); let this_bits = crate::closure::js_closure_get_capture_bits(closure, 2); let is_arrow = this_bits != NO_LEXICAL_THIS; let this = if this_bits == NO_LEXICAL_THIS { - crate::object::js_implicit_this_get() + this.as_f64() } else { f64::from_bits(this_bits) }; diff --git a/crates/perry-runtime/src/error.rs b/crates/perry-runtime/src/error.rs index f6f942324d..aab1c8e5c2 100644 --- a/crates/perry-runtime/src/error.rs +++ b/crates/perry-runtime/src/error.rs @@ -1322,9 +1322,9 @@ unsafe fn compute_stack_value(receiver: f64) -> f64 { /// consulted at access time, not capture time). extern "C" fn error_stack_lazy_getter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); unsafe { compute_stack_value(receiver) } } diff --git a/crates/perry-runtime/src/error_subclass_stack.rs b/crates/perry-runtime/src/error_subclass_stack.rs index c1d15e8daf..9224b05191 100644 --- a/crates/perry-runtime/src/error_subclass_stack.rs +++ b/crates/perry-runtime/src/error_subclass_stack.rs @@ -79,9 +79,9 @@ unsafe fn error_subclass_stack_head(receiver: f64) -> String { /// getter. extern "C" fn error_subclass_stack_getter( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); unsafe { if let Some(prep) = error_prepare_stack_trace_override() { let structured = build_structured_stack(10); @@ -130,10 +130,10 @@ extern "C" fn error_subclass_stack_getter( /// enumerability, different reflection — a deliberate simplification.) extern "C" fn error_subclass_stack_setter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let ptr = crate::value::js_nanbox_get_pointer(receiver); if ptr != 0 && crate::value::addr_class::is_above_handle_band(ptr as usize) diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index fefbea0806..eb2b44622a 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -169,9 +169,9 @@ fn event_bool_field(event: *mut ObjectHeader, name: &[u8]) -> bool { extern "C" fn event_prevent_default_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); let Some(event) = value_as_ptr::(this_value) else { return undefined_value(); }; @@ -183,9 +183,9 @@ extern "C" fn event_prevent_default_thunk( extern "C" fn event_stop_propagation_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); if let Some(event) = value_as_ptr::(this_value) { set_event_field(event, b"_stopped", bool_value(true)); } @@ -194,9 +194,9 @@ extern "C" fn event_stop_propagation_thunk( extern "C" fn event_stop_immediate_propagation_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); if let Some(event) = value_as_ptr::(this_value) { set_event_field(event, b"_stopped", bool_value(true)); set_event_field(event, b"_immediateStopped", bool_value(true)); @@ -1159,8 +1159,8 @@ unsafe fn bound_event_target(closure: *const crate::closure::ClosureHeader) -> * crate::value::js_nanbox_get_pointer(f64::from_bits(bits)) as *mut ObjectHeader } -fn event_proto_receiver() -> *mut ObjectHeader { - let receiver = crate::object::js_implicit_this_get(); +fn event_proto_receiver(this: crate::closure::JsThis) -> *mut ObjectHeader { + let receiver = this.as_f64(); if let Some(event) = value_as_ptr::(receiver) { let valid = unsafe { crate::value::addr_class::try_read_gc_header(event as usize) @@ -1178,37 +1178,37 @@ fn event_proto_receiver() -> *mut ObjectHeader { extern "C" fn event_proto_prevent_default_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - event_proto_receiver(); - event_prevent_default_thunk(closure, crate::closure::body_call::current_this()) + event_proto_receiver(this); + event_prevent_default_thunk(closure, this) } extern "C" fn event_proto_stop_propagation_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - event_proto_receiver(); - event_stop_propagation_thunk(closure, crate::closure::body_call::current_this()) + event_proto_receiver(this); + event_stop_propagation_thunk(closure, this) } extern "C" fn event_proto_stop_immediate_propagation_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - event_proto_receiver(); - event_stop_immediate_propagation_thunk(closure, crate::closure::body_call::current_this()) + event_proto_receiver(this); + event_stop_immediate_propagation_thunk(closure, this) } extern "C" fn event_proto_init_event_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event_type: f64, bubbles: f64, cancelable: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let event = scope.root_raw_mut_ptr(event_proto_receiver()); + let event = scope.root_raw_mut_ptr(event_proto_receiver(this)); let event_type = scope.root_nanbox_f64(event_type); let phase_key = key(b"eventPhase"); if event @@ -1243,10 +1243,10 @@ extern "C" fn event_proto_init_event_thunk( extern "C" fn event_proto_composed_path_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let event = scope.root_raw_mut_ptr(event_proto_receiver()); + let event = scope.root_raw_mut_ptr(event_proto_receiver(this)); let current_key = key(b"currentTarget"); let current = event.with_mut_ptr::(|event| { js_object_get_field_by_name_f64(event, current_key) 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 4d5871fa2f..f7ea4396d7 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -463,6 +463,7 @@ fn emit_fs_watch_event( } fn emit_watch_file_change( + this: crate::closure::JsThis, object_value: f64, callbacks: Vec, curr: &StatSnapshot, @@ -485,7 +486,7 @@ fn emit_watch_file_change( crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&callback_handles); let refreshed_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let prev_this = scope.root_nanbox_f64(this.as_f64()); for callback in refreshed_callbacks { let cb = extract_closure_ptr(callback); if cb.is_null() { @@ -939,7 +940,7 @@ fn start_promise_watcher(id: usize, state: &mut PromiseWatchState) { extern "C" fn watch_file_poll_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let delivery = WATCH_FILE_STATES.with(|states| { @@ -958,7 +959,7 @@ extern "C" fn watch_file_poll_impl( Some((state.object_value, callbacks, curr, prev, state.bigint)) }); if let Some((object_value, callbacks, curr, prev, bigint)) = delivery { - emit_watch_file_change(object_value, callbacks, &curr, &prev, bigint); + emit_watch_file_change(this, object_value, callbacks, &curr, &prev, bigint); } undefined_value() } diff --git a/crates/perry-runtime/src/fs/stream.rs b/crates/perry-runtime/src/fs/stream.rs index 545357b79d..b9f1e9b72d 100644 --- a/crates/perry-runtime/src/fs/stream.rs +++ b/crates/perry-runtime/src/fs/stream.rs @@ -436,8 +436,8 @@ fn object_ptr_from_value(value: f64) -> Option<*mut ObjectHeader> { } } -fn current_receiver_value() -> f64 { - let this_value = crate::object::js_implicit_this_get(); +fn current_receiver_value(this: crate::closure::JsThis) -> f64 { + let this_value = this.as_f64(); if object_ptr_from_value(this_value).is_some() { this_value } else { @@ -1159,7 +1159,7 @@ pub(crate) extern "C" fn write_stream_write_impl( pub(crate) extern "C" fn write_stream_end_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, encoding: f64, cb: f64, @@ -1199,27 +1199,27 @@ pub(crate) extern "C" fn write_stream_end_impl( } else { emit_stored_error(id); } - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn write_stream_on_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { stream_on_common(stream_id_of(closure), event, cb, false); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn write_stream_once_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { stream_on_common(stream_id_of(closure), event, cb, true); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn stream_emit_impl( @@ -1255,7 +1255,7 @@ pub(crate) use utf8_stream::*; pub(crate) extern "C" fn write_stream_close_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, cb: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1272,7 +1272,7 @@ pub(crate) extern "C" fn write_stream_close_impl( } }); maybe_close_stream(id, true); - current_receiver_value() + current_receiver_value(this) } fn read_chunk_value(bytes: &[u8], encoding: Option<&str>) -> f64 { @@ -1489,7 +1489,7 @@ fn read_stream_pump(id: usize) { pub(crate) extern "C" fn read_stream_on_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1503,12 +1503,12 @@ pub(crate) extern "C" fn read_stream_on_impl( }); schedule_read_stream_turn(id); } - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn read_stream_once_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { @@ -1522,7 +1522,7 @@ pub(crate) extern "C" fn read_stream_once_impl( }); schedule_read_stream_turn(id); } - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn read_stream_pipe_impl( @@ -1548,19 +1548,19 @@ pub(crate) extern "C" fn read_stream_pipe_impl( pub(crate) extern "C" fn read_stream_pause_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { STREAM_REGISTRY.with(|registry| { if let Some(state) = registry.borrow_mut().get_mut(&stream_id_of(closure)) { state.paused = true; } }); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn read_stream_resume_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let id = stream_id_of(closure); STREAM_REGISTRY.with(|registry| { @@ -1569,7 +1569,7 @@ pub(crate) extern "C" fn read_stream_resume_impl( } }); schedule_read_stream_turn(id); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn read_stream_is_paused_impl( @@ -1588,7 +1588,7 @@ pub(crate) extern "C" fn read_stream_is_paused_impl( pub(crate) extern "C" fn read_stream_close_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, cb: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1603,7 +1603,7 @@ pub(crate) extern "C" fn read_stream_close_impl( } }); maybe_close_stream(id, true); - current_receiver_value() + current_receiver_value(this) } fn stream_on_common(id: usize, event_value: f64, cb: f64, once: bool) { diff --git a/crates/perry-runtime/src/fs/stream/utf8_stream.rs b/crates/perry-runtime/src/fs/stream/utf8_stream.rs index 5d7d99e905..d6d2ab85ff 100644 --- a/crates/perry-runtime/src/fs/stream/utf8_stream.rs +++ b/crates/perry-runtime/src/fs/stream/utf8_stream.rs @@ -1118,39 +1118,39 @@ pub extern "C" fn js_fs_utf8_stream_reopen(stream_value: f64, file_value: f64) - pub(crate) extern "C" fn utf8_stream_on_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { let id = stream_id_of(closure); utf8_add_listener(id, &event_name(event), cb, false); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn utf8_stream_once_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { let id = stream_id_of(closure); utf8_add_listener(id, &event_name(event), cb, true); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn utf8_stream_off_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { utf8_remove_listener(stream_id_of(closure), &event_name(event), cb); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn utf8_stream_remove_all_impl( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { let id = stream_id_of(closure); @@ -1163,7 +1163,7 @@ pub(crate) extern "C" fn utf8_stream_remove_all_impl( } } }); - current_receiver_value() + current_receiver_value(this) } pub(crate) extern "C" fn utf8_stream_listener_count_impl( 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 be7b31718b..35451e471a 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 @@ -248,7 +248,11 @@ fn perex_dispatch_generic_prototype_test_coerces_before_getting_exec() { assert_eq!(ORDER.with(Cell::get), 123); ORDER.with(|n| n.set(0)); assert!(matches!( - dispatch::test_value(1.0, argument.get_nanbox_f64()), + dispatch::test_value( + crate::closure::body_call::current_this(), + 1.0, + argument.get_nanbox_f64() + ), Err(EngineError::Type(_)) )); assert_eq!( diff --git a/crates/perry-runtime/src/intl/date_collator.rs b/crates/perry-runtime/src/intl/date_collator.rs index fe09d0b3d1..ce1bebe390 100644 --- a/crates/perry-runtime/src/intl/date_collator.rs +++ b/crates/perry-runtime/src/intl/date_collator.rs @@ -79,18 +79,18 @@ pub(crate) extern "C" fn date_time_format_bound_format_thunk( /// construction with name `""` and length 1). pub(crate) extern "C" fn date_time_format_format_getter_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("format", KIND_DATE_TIME); + let obj = this_intl_object(this, "format", KIND_DATE_TIME); get_field(obj, KEY_DTF_BOUND_FORMAT) } pub(crate) extern "C" fn date_time_format_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("formatToParts", KIND_DATE_TIME); + let obj = this_intl_object(this, "formatToParts", KIND_DATE_TIME); if let Some(kind) = crate::temporal::temporal_kind(value) { validate_temporal_dtf_overlap(kind, obj); } @@ -1385,9 +1385,9 @@ pub(crate) use range::*; pub(crate) extern "C" fn date_time_format_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_DATE_TIME); + let obj = this_intl_object(this, "resolvedOptions", KIND_DATE_TIME); date_time_format_resolved_options_object(obj) } @@ -1706,9 +1706,9 @@ pub(crate) extern "C" fn collator_bound_compare_thunk( /// anonymous built-in shape required by ECMA-402 (`name: ""`, `length: 2`). pub(crate) extern "C" fn collator_compare_getter_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("compare", KIND_COLLATOR); + let obj = this_intl_object(this, "compare", KIND_COLLATOR); get_field(obj, KEY_COL_BOUND_COMPARE) } @@ -1777,9 +1777,9 @@ pub(crate) fn collator_compare_object(obj: *const ObjectHeader, left: f64, right pub(crate) extern "C" fn collator_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_COLLATOR); + let obj = this_intl_object(this, "resolvedOptions", KIND_COLLATOR); collator_resolved_options_object(obj) } diff --git a/crates/perry-runtime/src/intl/date_collator/range.rs b/crates/perry-runtime/src/intl/date_collator/range.rs index 755aa50476..f158344262 100644 --- a/crates/perry-runtime/src/intl/date_collator/range.rs +++ b/crates/perry-runtime/src/intl/date_collator/range.rs @@ -182,11 +182,11 @@ pub(crate) fn date_time_format_range_parts_value( pub(crate) extern "C" fn date_time_format_range_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - let obj = this_intl_object("formatRange", KIND_DATE_TIME); + let obj = this_intl_object(this, "formatRange", KIND_DATE_TIME); if let Some(kind) = crate::temporal::temporal_kind(start) { validate_temporal_dtf_overlap(kind, obj); } @@ -208,11 +208,11 @@ pub(crate) extern "C" fn date_time_format_bound_range_thunk( pub(crate) extern "C" fn date_time_format_range_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - let obj = this_intl_object("formatRangeToParts", KIND_DATE_TIME); + let obj = this_intl_object(this, "formatRangeToParts", KIND_DATE_TIME); if let Some(kind) = crate::temporal::temporal_kind(start) { validate_temporal_dtf_overlap(kind, obj); } diff --git a/crates/perry-runtime/src/intl/display_names.rs b/crates/perry-runtime/src/intl/display_names.rs index 06175e0551..11a11620d9 100644 --- a/crates/perry-runtime/src/intl/display_names.rs +++ b/crates/perry-runtime/src/intl/display_names.rs @@ -198,9 +198,9 @@ extern "C" fn bound_resolved_options_thunk( pub(super) extern "C" fn resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", super::KIND_DISPLAY_NAMES); + let obj = this_intl_object(this, "resolvedOptions", super::KIND_DISPLAY_NAMES); resolved_options_object(obj) } @@ -356,10 +356,10 @@ extern "C" fn bound_of_thunk( pub(super) extern "C" fn of_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, code: f64, ) -> f64 { - let obj = this_intl_object("of", super::KIND_DISPLAY_NAMES); + let obj = this_intl_object(this, "of", super::KIND_DISPLAY_NAMES); of_value(obj, code) } diff --git a/crates/perry-runtime/src/intl/duration_format.rs b/crates/perry-runtime/src/intl/duration_format.rs index ca12889569..d71791d8fa 100644 --- a/crates/perry-runtime/src/intl/duration_format.rs +++ b/crates/perry-runtime/src/intl/duration_format.rs @@ -369,9 +369,9 @@ fn resolved_options_object(obj: *const ObjectHeader) -> f64 { pub(super) extern "C" fn resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", super::KIND_DURATION_FORMAT); + let obj = this_intl_object(this, "resolvedOptions", super::KIND_DURATION_FORMAT); resolved_options_object(obj) } @@ -886,19 +886,19 @@ pub(super) fn format_temporal_duration(duration: f64, locales: f64, options: f64 pub(super) extern "C" fn format_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, duration: f64, ) -> f64 { - let obj = this_intl_object("format", super::KIND_DURATION_FORMAT); + let obj = this_intl_object(this, "format", super::KIND_DURATION_FORMAT); format_value(obj, duration) } pub(super) extern "C" fn to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, duration: f64, ) -> f64 { - let obj = this_intl_object("formatToParts", super::KIND_DURATION_FORMAT); + let obj = this_intl_object(this, "formatToParts", super::KIND_DURATION_FORMAT); let vals = to_duration_record(duration); df_parts_to_js_array(&partition(obj, &vals)) } diff --git a/crates/perry-runtime/src/intl/list_relative_plural.rs b/crates/perry-runtime/src/intl/list_relative_plural.rs index 764f454c63..9e60b5e81b 100644 --- a/crates/perry-runtime/src/intl/list_relative_plural.rs +++ b/crates/perry-runtime/src/intl/list_relative_plural.rs @@ -204,10 +204,10 @@ pub(crate) fn list_format_instance_parts( pub(crate) extern "C" fn list_format_format_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("format", KIND_LIST_FORMAT); + let obj = this_intl_object(this, "format", KIND_LIST_FORMAT); string_value( &list_format_instance_parts(obj, value) .iter() @@ -232,10 +232,10 @@ pub(crate) extern "C" fn list_format_bound_format_thunk( pub(crate) extern "C" fn list_format_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("formatToParts", KIND_LIST_FORMAT); + let obj = this_intl_object(this, "formatToParts", KIND_LIST_FORMAT); parts_to_js_array(&list_format_instance_parts(obj, value)) } @@ -270,9 +270,9 @@ pub(crate) fn list_format_resolved_options_object(obj: *const ObjectHeader) -> f pub(crate) extern "C" fn list_format_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_LIST_FORMAT); + let obj = this_intl_object(this, "resolvedOptions", KIND_LIST_FORMAT); list_format_resolved_options_object(obj) } @@ -593,11 +593,11 @@ fn rtf_parts_to_js_array(parts: &[(&'static str, String)], unit: &str) -> f64 { pub(crate) extern "C" fn rtf_format_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { - let obj = this_intl_object("format", KIND_RELATIVE_TIME); + let obj = this_intl_object(this, "format", KIND_RELATIVE_TIME); string_value( &rtf_instance_parts(obj, value, unit) .iter() @@ -623,11 +623,11 @@ pub(crate) extern "C" fn rtf_bound_format_thunk( pub(crate) extern "C" fn rtf_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, unit: f64, ) -> f64 { - let obj = this_intl_object("formatToParts", KIND_RELATIVE_TIME); + let obj = this_intl_object(this, "formatToParts", KIND_RELATIVE_TIME); let (parts, unit) = rtf_instance_parts_and_unit(obj, value, unit); rtf_parts_to_js_array(&parts, unit) } @@ -672,9 +672,9 @@ pub(crate) fn rtf_resolved_options_object(obj: *const ObjectHeader) -> f64 { pub(crate) extern "C" fn rtf_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_RELATIVE_TIME); + let obj = this_intl_object(this, "resolvedOptions", KIND_RELATIVE_TIME); rtf_resolved_options_object(obj) } @@ -1005,10 +1005,10 @@ mod plural_category_tests { pub(crate) extern "C" fn plural_rules_select_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("select", KIND_PLURAL_RULES); + let obj = this_intl_object(this, "select", KIND_PLURAL_RULES); plural_rules_select(obj, value) } @@ -1023,11 +1023,11 @@ pub(crate) extern "C" fn plural_rules_bound_select_thunk( pub(crate) extern "C" fn plural_rules_select_range_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - let _obj = this_intl_object("selectRange", KIND_PLURAL_RULES); + let _obj = this_intl_object(this, "selectRange", KIND_PLURAL_RULES); plural_select_range(start, end) } @@ -1124,9 +1124,9 @@ pub(crate) fn plural_rules_resolved_options_object(obj: *const ObjectHeader) -> pub(crate) extern "C" fn plural_rules_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_PLURAL_RULES); + let obj = this_intl_object(this, "resolvedOptions", KIND_PLURAL_RULES); plural_rules_resolved_options_object(obj) } diff --git a/crates/perry-runtime/src/intl/locale.rs b/crates/perry-runtime/src/intl/locale.rs index 9e22b04931..0d8f477175 100644 --- a/crates/perry-runtime/src/intl/locale.rs +++ b/crates/perry-runtime/src/intl/locale.rs @@ -746,8 +746,8 @@ pub(super) extern "C" fn locale_constructor_thunk( // ---- prototype methods & getters ------------------------------------------ -fn locale_this(method: &str) -> *mut ObjectHeader { - let this = crate::object::js_implicit_this_get(); +fn locale_this(this: crate::closure::JsThis, method: &str) -> *mut ObjectHeader { + let this = this.as_f64(); let Some(obj) = object_ptr_from_value(this) else { throw_type_error(&format!( "Intl.Locale.prototype.{method} called on incompatible receiver" @@ -772,67 +772,67 @@ fn field_or_undefined(obj: *const ObjectHeader, key: &str) -> f64 { extern "C" fn locale_to_string_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = locale_this("toString"); + let obj = locale_this(this, "toString"); string_value(&get_string_field(obj, KEY_FULL).unwrap_or_default()) } extern "C" fn locale_maximize_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - transform_instance(locale_this("maximize"), likely_subtags::maximize) + transform_instance(locale_this(this, "maximize"), likely_subtags::maximize) } extern "C" fn locale_minimize_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - transform_instance(locale_this("minimize"), likely_subtags::minimize) + transform_instance(locale_this(this, "minimize"), likely_subtags::minimize) } extern "C" fn locale_get_calendars_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - calendars_of(locale_this("getCalendars")) + calendars_of(locale_this(this, "getCalendars")) } extern "C" fn locale_get_collations_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - collations_of(locale_this("getCollations")) + collations_of(locale_this(this, "getCollations")) } extern "C" fn locale_get_hour_cycles_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - hour_cycles_of(locale_this("getHourCycles")) + hour_cycles_of(locale_this(this, "getHourCycles")) } extern "C" fn locale_get_numbering_systems_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - numbering_systems_of(locale_this("getNumberingSystems")) + numbering_systems_of(locale_this(this, "getNumberingSystems")) } extern "C" fn locale_get_time_zones_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - time_zones_of(locale_this("getTimeZones")) + time_zones_of(locale_this(this, "getTimeZones")) } extern "C" fn locale_get_text_info_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - text_info_of(locale_this("getTextInfo")) + text_info_of(locale_this(this, "getTextInfo")) } extern "C" fn locale_get_week_info_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - week_info_of(locale_this("getWeekInfo")) + week_info_of(locale_this(this, "getWeekInfo")) } // ---- Intl.Locale-info computations ----------------------------------------- @@ -960,44 +960,44 @@ fn parsed_from_instance(obj: *const ObjectHeader) -> ParsedLocale { parse_language_tag(&full).unwrap_or_default() } -extern "C" fn getter_base_name(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("baseName"), KEY_BASENAME) +extern "C" fn getter_base_name(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "baseName"), KEY_BASENAME) } -extern "C" fn getter_language(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("language"), KEY_LANGUAGE) +extern "C" fn getter_language(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "language"), KEY_LANGUAGE) } -extern "C" fn getter_script(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("script"), KEY_SCRIPT) +extern "C" fn getter_script(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "script"), KEY_SCRIPT) } -extern "C" fn getter_region(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("region"), KEY_REGION) +extern "C" fn getter_region(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "region"), KEY_REGION) } -extern "C" fn getter_calendar(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("calendar"), KEY_CALENDAR) +extern "C" fn getter_calendar(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "calendar"), KEY_CALENDAR) } -extern "C" fn getter_case_first(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("caseFirst"), KEY_CASEFIRST) +extern "C" fn getter_case_first(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "caseFirst"), KEY_CASEFIRST) } -extern "C" fn getter_collation(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("collation"), KEY_COLLATION) +extern "C" fn getter_collation(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "collation"), KEY_COLLATION) } -extern "C" fn getter_hour_cycle(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - field_or_undefined(locale_this("hourCycle"), KEY_HOURCYCLE) +extern "C" fn getter_hour_cycle(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + field_or_undefined(locale_this(this, "hourCycle"), KEY_HOURCYCLE) } extern "C" fn getter_numbering_system( _c: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_or_undefined(locale_this("numberingSystem"), KEY_NUMBERINGSYSTEM) + field_or_undefined(locale_this(this, "numberingSystem"), KEY_NUMBERINGSYSTEM) } extern "C" fn getter_first_day_of_week( _c: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_or_undefined(locale_this("firstDayOfWeek"), KEY_FIRSTDAYOFWEEK) + field_or_undefined(locale_this(this, "firstDayOfWeek"), KEY_FIRSTDAYOFWEEK) } -extern "C" fn getter_numeric(_c: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - let obj = locale_this("numeric"); +extern "C" fn getter_numeric(_c: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + let obj = locale_this(this, "numeric"); let value = get_field(obj, KEY_NUMERIC); if value.to_bits() == crate::value::TAG_TRUE { bool_value(true) diff --git a/crates/perry-runtime/src/intl/number_format.rs b/crates/perry-runtime/src/intl/number_format.rs index 990c21dbae..5a8bc2acdd 100644 --- a/crates/perry-runtime/src/intl/number_format.rs +++ b/crates/perry-runtime/src/intl/number_format.rs @@ -1309,8 +1309,12 @@ pub(crate) fn parts_to_js_array(parts: &[(&'static str, String)]) -> f64 { js_nanbox_pointer(arr as i64) } -pub(crate) fn this_intl_object(method: &str, expected_kind: &str) -> *mut ObjectHeader { - let this_value = crate::object::js_implicit_this_get(); +pub(crate) fn this_intl_object( + this: crate::closure::JsThis, + method: &str, + expected_kind: &str, +) -> *mut ObjectHeader { + let this_value = this.as_f64(); intl_object_from_value(this_value, method, expected_kind) } @@ -1387,9 +1391,9 @@ fn unwrap_legacy_constructed(value: f64) -> Option { /// property can't change what the accessor returns. pub(crate) extern "C" fn number_format_format_getter_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("format", KIND_NUMBER); + let obj = this_intl_object(this, "format", KIND_NUMBER); get_field(obj, KEY_NF_BOUND_FORMAT) } @@ -1420,9 +1424,9 @@ pub(crate) fn number_format_format_object(obj: *const ObjectHeader, value: f64) pub(crate) extern "C" fn number_format_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_NUMBER); + let obj = this_intl_object(this, "resolvedOptions", KIND_NUMBER); number_format_resolved_options_object(obj) } @@ -1436,10 +1440,10 @@ pub(crate) extern "C" fn number_format_bound_resolved_options_thunk( pub(crate) extern "C" fn number_format_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("formatToParts", KIND_NUMBER); + let obj = this_intl_object(this, "formatToParts", KIND_NUMBER); let number = nf_coerce_number(value); parts_to_js_array(&number_instance_parts(obj, number)) } @@ -1542,21 +1546,21 @@ pub(crate) fn number_format_range_parts_value( pub(crate) extern "C" fn number_format_range_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - let obj = this_intl_object("formatRange", KIND_NUMBER); + let obj = this_intl_object(this, "formatRange", KIND_NUMBER); number_format_range_value(obj, "formatRange", start, end) } pub(crate) extern "C" fn number_format_range_to_parts_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - let obj = this_intl_object("formatRangeToParts", KIND_NUMBER); + let obj = this_intl_object(this, "formatRangeToParts", KIND_NUMBER); number_format_range_parts_value(obj, "formatRangeToParts", start, end) } diff --git a/crates/perry-runtime/src/intl/segmenter.rs b/crates/perry-runtime/src/intl/segmenter.rs index d7ca05779d..4ba34920fb 100644 --- a/crates/perry-runtime/src/intl/segmenter.rs +++ b/crates/perry-runtime/src/intl/segmenter.rs @@ -422,17 +422,17 @@ fn install_segments_iterator(segments: &crate::gc::RuntimeHandle<'_>) { extern "C" fn segments_iterator_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let segments = scope.root_raw_const_ptr(segments_from_this()); + let segments = scope.root_raw_const_ptr(segments_from_this(this)); segments.with_const_ptr(|segments: *const crate::ArrayHeader| { crate::array::array_values_iter(js_nanbox_pointer(segments as i64)) }) } -fn segments_from_this() -> *const crate::ArrayHeader { - let this_value = crate::object::js_implicit_this_get(); +fn segments_from_this(this: crate::closure::JsThis) -> *const crate::ArrayHeader { + let this_value = this.as_f64(); let Some(segments) = array_ptr_from_value(this_value) else { throw_type_error("Intl.Segments.prototype.containing called on incompatible receiver"); }; @@ -445,10 +445,10 @@ fn segments_from_this() -> *const crate::ArrayHeader { pub(crate) extern "C" fn segmenter_containing_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, index: f64, ) -> f64 { - let segments = segments_from_this(); + let segments = segments_from_this(this); let input_len = get_number_field(segments as *const ObjectHeader, KEY_SEGMENTS_LENGTH).unwrap_or(0.0); @@ -496,10 +496,10 @@ pub(crate) extern "C" fn segmenter_containing_thunk( pub(crate) extern "C" fn segmenter_segment_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = this_intl_object("segment", KIND_SEGMENTER); + let obj = this_intl_object(this, "segment", KIND_SEGMENTER); segmenter_segment_object(obj, value) } @@ -520,9 +520,9 @@ pub(crate) fn segmenter_segment_object(obj: *const ObjectHeader, value: f64) -> pub(crate) extern "C" fn segmenter_resolved_options_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = this_intl_object("resolvedOptions", KIND_SEGMENTER); + let obj = this_intl_object(this, "resolvedOptions", KIND_SEGMENTER); segmenter_resolved_options_object(obj) } diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index 595f301820..4e4aa42d77 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -268,8 +268,8 @@ fn with_port_states(f: impl FnOnce(&mut HashMap) -> R) -> R } /// Resolve the current method receiver (`this`) to a port object pointer. -fn this_port_ptr() -> usize { - let this = object::js_implicit_this_get(); +fn this_port_ptr(this: crate::closure::JsThis) -> usize { + let this = this.as_f64(); crate::value::js_nanbox_get_pointer(this) as usize } @@ -394,11 +394,11 @@ fn schedule_delivery(port_ptr: usize) { /// `port.postMessage(data[, transferList])`. extern "C" fn port_post_message( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, data: f64, _transfer: f64, ) -> f64 { - let self_ptr = this_port_ptr(); + let self_ptr = this_port_ptr(this); if self_ptr == 0 { return js_undefined(); } @@ -442,13 +442,13 @@ fn start_port(self_ptr: usize) { } } -extern "C" fn port_start(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - start_port(this_port_ptr()); +extern "C" fn port_start(_closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + start_port(this_port_ptr(this)); js_undefined() } -extern "C" fn port_close(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - let self_ptr = this_port_ptr(); +extern "C" fn port_close(_closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + let self_ptr = this_port_ptr(this); if self_ptr != 0 { with_port_states(|map| { let Some(state) = map.get_mut(&self_ptr) else { @@ -478,9 +478,9 @@ extern "C" fn port_close(_closure: *const ClosureHeader, _this: crate::closure:: /// `port.onmessage` getter — return the stored handler (null if unset). extern "C" fn port_onmessage_get( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let self_ptr = this_port_ptr(); + let self_ptr = this_port_ptr(this); if self_ptr == 0 { return js_null(); } @@ -491,10 +491,10 @@ extern "C" fn port_onmessage_get( /// start the port, flushing any queued messages. extern "C" fn port_onmessage_set( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let self_ptr = this_port_ptr(); + let self_ptr = this_port_ptr(this); if self_ptr == 0 { return js_undefined(); } @@ -507,11 +507,11 @@ extern "C" fn port_onmessage_set( extern "C" fn port_add_event_listener( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, type_value: f64, listener: f64, ) -> f64 { - let self_ptr = this_port_ptr(); + let self_ptr = this_port_ptr(this); if self_ptr == 0 || !is_message_type(type_value) || !value_is_callable(listener) { return js_undefined(); } @@ -539,11 +539,11 @@ extern "C" fn port_add_event_listener( extern "C" fn port_remove_event_listener( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, type_value: f64, listener: f64, ) -> f64 { - let self_ptr = this_port_ptr(); + let self_ptr = this_port_ptr(this); if self_ptr == 0 || !is_message_type(type_value) { return js_undefined(); } diff --git a/crates/perry-runtime/src/node_api_host/functions.rs b/crates/perry-runtime/src/node_api_host/functions.rs index 082aabf803..e23a3964f1 100644 --- a/crates/perry-runtime/src/node_api_host/functions.rs +++ b/crates/perry-runtime/src/node_api_host/functions.rs @@ -37,7 +37,7 @@ fn current_callback_record(index: usize) -> Option { extern "C" fn napi_callback_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arguments: f64, ) -> f64 { let env = current_env(); @@ -67,7 +67,7 @@ extern "C" fn napi_callback_thunk( } } } - let this_bits = crate::object::js_implicit_this_get().to_bits(); + let this_bits = this.as_f64().to_bits(); let this_value = add_handle(env, this_bits).unwrap_or(std::ptr::null_mut()); let new_target_bits = crate::object::js_new_target_get().to_bits(); let new_target = if JSValue::from_bits(new_target_bits).is_undefined() { diff --git a/crates/perry-runtime/src/node_inspector.rs b/crates/perry-runtime/src/node_inspector.rs index 84538cf82c..079f9682c1 100644 --- a/crates/perry-runtime/src/node_inspector.rs +++ b/crates/perry-runtime/src/node_inspector.rs @@ -1363,47 +1363,47 @@ pub extern "C" fn js_node_inspector_wait_for_debugger() -> f64 { extern "C" fn session_connect_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_connect(raw_ptr_from_value(this) as i64) } extern "C" fn session_connect_main_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_connect_to_main_thread(raw_ptr_from_value(this) as i64) } extern "C" fn session_disconnect_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_disconnect(raw_ptr_from_value(this) as i64) } extern "C" fn session_post_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, method: f64, params: f64, callback: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_post(raw_ptr_from_value(this) as i64, method, params, callback) } extern "C" fn promises_session_post_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, method: f64, params: f64, callback: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_promises_session_post( raw_ptr_from_value(this) as i64, method, @@ -1414,49 +1414,49 @@ extern "C" fn promises_session_post_thunk( extern "C" fn session_on_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, listener: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_on(raw_ptr_from_value(this) as i64, event, listener) } extern "C" fn session_once_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, listener: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_once(raw_ptr_from_value(this) as i64, event, listener) } extern "C" fn session_off_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, listener: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_off(raw_ptr_from_value(this) as i64, event, listener) } extern "C" fn session_listener_count_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_listener_count(raw_ptr_from_value(this) as i64, event) } extern "C" fn session_remove_all_listeners_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); js_node_inspector_session_remove_all_listeners(raw_ptr_from_value(this) as i64, event) } diff --git a/crates/perry-runtime/src/node_repl.rs b/crates/perry-runtime/src/node_repl.rs index 1c084c5059..c2868b294b 100644 --- a/crates/perry-runtime/src/node_repl.rs +++ b/crates/perry-runtime/src/node_repl.rs @@ -374,34 +374,34 @@ fn fn_value(func: *const u8, name: &str, arity: u32) -> f64 { extern "C" fn repl_on_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, listener: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); add_listener(server, event, listener, false); server } extern "C" fn repl_once_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, listener: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); add_listener(server, event, listener, true); server } extern "C" fn repl_emit_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, arg0: f64, arg1: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); let Some(event_name) = string_to_rust(event) else { return bool_value(false); }; @@ -415,10 +415,10 @@ extern "C" fn repl_emit_thunk( extern "C" fn repl_display_prompt_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _preserve_cursor: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); display_prompt_for(server); undefined() } @@ -432,11 +432,11 @@ extern "C" fn repl_clear_buffered_command_thunk( extern "C" fn repl_setup_history_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _path: f64, callback: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); if let Some(obj) = object_ptr_from_value(server) { set_field(obj, "history", array_value(crate::array::js_array_alloc(0))); set_field(obj, "historySize", 30.0); @@ -447,11 +447,11 @@ extern "C" fn repl_setup_history_thunk( extern "C" fn repl_define_command_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, keyword: f64, command: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); let Some(name) = string_to_rust(keyword) else { return undefined(); }; @@ -464,10 +464,10 @@ extern "C" fn repl_define_command_thunk( extern "C" fn repl_write_thunk( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, ) -> f64 { - let server = crate::object::js_implicit_this_get(); + let server = this.as_f64(); let input = string_to_rust(chunk).unwrap_or_default(); for line in input.split_inclusive('\n') { let line = line.strip_suffix('\n').unwrap_or(line); diff --git a/crates/perry-runtime/src/node_stream.rs b/crates/perry-runtime/src/node_stream.rs index 8e110cd1db..60c890aca4 100644 --- a/crates/perry-runtime/src/node_stream.rs +++ b/crates/perry-runtime/src/node_stream.rs @@ -850,18 +850,18 @@ fn push_chunk_backpressure_result(stream: f64, total: f64) -> f64 { /// `readable.push(chunk)` for the untyped/`as any` object-method path. extern "C" fn ns_push1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, ) -> f64 { - let stream = readable_push_receiver(closure); + let stream = readable_push_receiver(this, closure); if get_hidden_value(stream, hidden_readable_flag_key()).is_none() { throw_readable_push_invalid_receiver(); } push_chunk(stream, chunk) } -fn readable_push_receiver(closure: *const ClosureHeader) -> f64 { - let implicit = crate::object::js_implicit_this_get(); +fn readable_push_receiver(this: crate::closure::JsThis, closure: *const ClosureHeader) -> f64 { + let implicit = this.as_f64(); if implicit.to_bits() != TAG_UNDEFINED { return implicit; } 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 1a8ef17abc..81d357b50c 100644 --- a/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs +++ b/crates/perry-runtime/src/node_stream_constructors/web_adapter.rs @@ -503,10 +503,10 @@ extern "C" fn fallback_foreign_reader_read( extern "C" fn fallback_foreign_reader_cancel( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, reason: f64, ) -> f64 { - foreign_readable_to_web_cancel(closure, crate::closure::body_call::current_this(), reason) + foreign_readable_to_web_cancel(closure, this, reason) } extern "C" fn fallback_foreign_get_reader( diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index ec56d4eb0d..42aca14f32 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -320,7 +320,7 @@ fn require_event_emitter_async_resource_receiver( extern "C" fn ns_ee_async_resource_emit_rest( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, rest: f64, ) -> f64 { @@ -334,12 +334,7 @@ extern "C" fn ns_ee_async_resource_emit_rest( if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_enter(runtime_async_id); } - let result = ns_emit_rest( - closure, - crate::closure::body_call::current_this(), - event, - rest, - ); + let result = ns_emit_rest(closure, this, event, rest); if runtime_async_id != 0 { crate::async_hooks::js_async_hooks_provider_leave(runtime_async_id); } diff --git a/crates/perry-runtime/src/node_stream_event_emitter.rs b/crates/perry-runtime/src/node_stream_event_emitter.rs index 00b76fc1c7..fe80e1dc4a 100644 --- a/crates/perry-runtime/src/node_stream_event_emitter.rs +++ b/crates/perry-runtime/src/node_stream_event_emitter.rs @@ -150,16 +150,11 @@ pub(super) extern "C" fn ns_remove_listener2( pub(super) extern "C" fn ns_off2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - ns_remove_listener2( - closure, - crate::closure::body_call::current_this(), - event, - cb, - ) + ns_remove_listener2(closure, this, event, cb) } pub(super) extern "C" fn ns_remove_all_listeners1( diff --git a/crates/perry-runtime/src/node_stream_state_view.rs b/crates/perry-runtime/src/node_stream_state_view.rs index 6b8d623231..47298536b3 100644 --- a/crates/perry-runtime/src/node_stream_state_view.rs +++ b/crates/perry-runtime/src/node_stream_state_view.rs @@ -121,8 +121,8 @@ fn bool_bits(value: bool) -> f64 { } /// The stream a state view belongs to, from the accessor's receiver. -fn view_owner() -> Option { - let this = crate::object::js_implicit_this_get(); +fn view_owner(this: crate::closure::JsThis) -> Option { + let this = this.as_f64(); get_hidden_value(this, hidden_key(STREAM_STATE_OWNER_KEY)) } @@ -232,14 +232,11 @@ fn captured_field(closure: *const ClosureHeader) -> Option<(usize, &'static str) field_name(id as usize) } -extern "C" fn stream_state_get( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, -) -> f64 { +extern "C" fn stream_state_get(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { let Some((kind, field)) = captured_field(closure) else { return f64::from_bits(TAG_UNDEFINED); }; - let Some(stream) = view_owner() else { + let Some(stream) = view_owner(this) else { return f64::from_bits(TAG_UNDEFINED); }; let scope = crate::gc::RuntimeHandleScope::new(); @@ -253,14 +250,14 @@ extern "C" fn stream_state_get( extern "C" fn stream_state_set( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { let Some((_, field)) = captured_field(closure) else { return f64::from_bits(TAG_UNDEFINED); }; if field == "dataEmitted" { - if let Some(stream) = view_owner() { + if let Some(stream) = view_owner(this) { let scope = crate::gc::RuntimeHandleScope::new(); let stream = scope.root_nanbox_f64(stream); let emitted = crate::value::js_is_truthy(value) != 0; diff --git a/crates/perry-runtime/src/node_submodules/blob.rs b/crates/perry-runtime/src/node_submodules/blob.rs index d9ba4b48cd..b447044bde 100644 --- a/crates/perry-runtime/src/node_submodules/blob.rs +++ b/crates/perry-runtime/src/node_submodules/blob.rs @@ -274,18 +274,18 @@ extern "C" fn file_blob_stream_return_method( extern "C" fn file_blob_stream_values_method( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _options: f64, ) -> f64 { let _ = closure; - crate::object::js_implicit_this_get() + this.as_f64() } extern "C" fn file_blob_stream_async_iterator_method( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } extern "C" fn file_blob_stream_undefined_method( diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index d615d5a44f..a458797be6 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -795,7 +795,7 @@ mod metadata_tests; extern "C" fn mock_function_invoke( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { let id = closure_id(closure); @@ -811,7 +811,7 @@ extern "C" fn mock_function_invoke( ) }); - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); if JSValue::from_bits(implementation.to_bits()).is_undefined() { if is_symbol_method { let scope = crate::gc::RuntimeHandleScope::new(); diff --git a/crates/perry-runtime/src/node_submodules/test_property.rs b/crates/perry-runtime/src/node_submodules/test_property.rs index 2bbde88869..cc8818ffad 100644 --- a/crates/perry-runtime/src/node_submodules/test_property.rs +++ b/crates/perry-runtime/src/node_submodules/test_property.rs @@ -384,10 +384,10 @@ extern "C" fn mock_property_reset_accesses( extern "C" fn mock_property_implementation( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - mock_property_set(closure, crate::closure::body_call::current_this(), value) + mock_property_set(closure, this, value) } extern "C" fn mock_property_implementation_once( diff --git a/crates/perry-runtime/src/node_submodules/test_reporters.rs b/crates/perry-runtime/src/node_submodules/test_reporters.rs index f89fd4f5e1..8f6877931a 100644 --- a/crates/perry-runtime/src/node_submodules/test_reporters.rs +++ b/crates/perry-runtime/src/node_submodules/test_reporters.rs @@ -69,7 +69,7 @@ fn reporter_transform(kind: i32) -> f64 { extern "C" fn reporter_transform_chunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, _encoding: f64, callback: f64, @@ -96,7 +96,7 @@ extern "C" fn reporter_transform_chunk( }); } if !output.is_empty() { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let handle = (this.to_bits() & POINTER_MASK) as i64; crate::node_stream::js_node_stream_method_push(handle, string_value(&output)); } diff --git a/crates/perry-runtime/src/node_submodules/timers.rs b/crates/perry-runtime/src/node_submodules/timers.rs index 453e01e40a..570c177ad3 100644 --- a/crates/perry-runtime/src/node_submodules/timers.rs +++ b/crates/perry-runtime/src/node_submodules/timers.rs @@ -154,13 +154,13 @@ pub(crate) extern "C" fn timers_promises_set_immediate( pub(crate) extern "C" fn timers_promises_scheduler_wait( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, delay_ms: f64, options: f64, ) -> f64 { timers_promises_set_timeout( _closure, - crate::closure::body_call::current_this(), + this, delay_ms, f64::from_bits(TAG_UNDEFINED), options, diff --git a/crates/perry-runtime/src/node_submodules/trace_events.rs b/crates/perry-runtime/src/node_submodules/trace_events.rs index 9e679bf2e7..5af7a42a0d 100644 --- a/crates/perry-runtime/src/node_submodules/trace_events.rs +++ b/crates/perry-runtime/src/node_submodules/trace_events.rs @@ -202,8 +202,8 @@ fn trace_id_symbol() -> f64 { }) } -fn this_trace_id() -> i64 { - let this_value = crate::object::js_implicit_this_get(); +fn this_trace_id(this: crate::closure::JsThis) -> i64 { + let this_value = this.as_f64(); let Some(_) = object_ptr_from_value(this_value) else { throw_invalid_this(); }; @@ -319,25 +319,25 @@ extern "C" fn trace_tracing_constructor( extern "C" fn trace_tracing_enable( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - set_trace_enabled(this_trace_id(), true); + set_trace_enabled(this_trace_id(this), true); undefined() } extern "C" fn trace_tracing_disable( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - set_trace_enabled(this_trace_id(), false); + set_trace_enabled(this_trace_id(this), false); undefined() } extern "C" fn trace_categories_getter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let id = this_trace_id(); + let id = this_trace_id(this); let categories = trace_state_value(id, |state| state.categories); let scope = crate::gc::RuntimeHandleScope::new(); let categories = scope.root_nanbox_f64(categories); @@ -346,9 +346,9 @@ extern "C" fn trace_categories_getter( extern "C" fn trace_enabled_getter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let id = this_trace_id(); + let id = this_trace_id(this); trace_state_value(id, |state| bool_value(state.enabled)) } diff --git a/crates/perry-runtime/src/node_vm.rs b/crates/perry-runtime/src/node_vm.rs index a41eb5c4c0..8c2f22c6fa 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -1323,8 +1323,8 @@ fn install_script_method( }); } -fn script_receiver() -> f64 { - crate::object::js_implicit_this_get() +fn script_receiver(this: crate::closure::JsThis) -> f64 { + this.as_f64() } pub(crate) fn install_script_prototypes(constructor: f64) { @@ -1467,11 +1467,11 @@ fn make_script(code: String, options: f64) -> f64 { extern "C" fn vm_script_create_cached_data_method( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _unused1: f64, _unused2: f64, ) -> f64 { - let script = script_receiver(); + let script = script_receiver(this); let Some(metadata) = script_metadata(script) else { return cached_data_buffer(CACHE_KIND_SCRIPT, 0); }; @@ -1483,11 +1483,11 @@ extern "C" fn vm_script_create_cached_data_method( extern "C" fn vm_script_run_in_this_context_method( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _options: f64, _unused: f64, ) -> f64 { - let script = script_receiver(); + let script = script_receiver(this); let Some(metadata) = script_metadata(script) else { return undefined_value(); }; @@ -1504,11 +1504,11 @@ extern "C" fn vm_script_run_in_this_context_method( extern "C" fn vm_script_run_in_context_method( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, contextified_object: f64, _options: f64, ) -> f64 { - let script = script_receiver(); + let script = script_receiver(this); let Some(metadata) = script_metadata(script) else { return undefined_value(); }; @@ -1524,11 +1524,11 @@ extern "C" fn vm_script_run_in_context_method( extern "C" fn vm_script_run_in_new_context_method( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, context_object: f64, options: f64, ) -> f64 { - let script = script_receiver(); + let script = script_receiver(this); let Some(metadata) = script_metadata(script) else { return undefined_value(); }; diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index bdd74db2c4..070271317b 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -208,13 +208,13 @@ pub(crate) fn class_own_static_accessor_ptrs(class_id: u32, name: &str) -> Optio /// by the method-call dispatch the closure value travels through. extern "C" fn class_accessor_getter_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let raw = crate::closure::js_closure_get_capture_ptr(closure, 0) as usize; if raw == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let f = unsafe { crate::closure::body_call::js_method_body_fn!(raw as *const u8;) }; f(this) } @@ -222,14 +222,14 @@ extern "C" fn class_accessor_getter_thunk( /// Trampoline for a raw vtable setter func_ptr (`fn(this, value) -> f64`). extern "C" fn class_accessor_setter_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { let raw = crate::closure::js_closure_get_capture_ptr(closure, 0) as usize; if raw == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let f = unsafe { crate::closure::body_call::js_method_body_fn!(raw as *const u8; a0) }; f(this, value) } diff --git a/crates/perry-runtime/src/object/collection_proto_thunks.rs b/crates/perry-runtime/src/object/collection_proto_thunks.rs index a38f2d13b0..b57bbf6ee3 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks.rs @@ -414,8 +414,8 @@ fn throw_incompatible_receiver(proto: &str, method: &str, receiver_bits: u64) -> } #[inline] -fn set_receiver_or_throw(method: &str) -> *mut crate::set::SetHeader { - let bits = IMPLICIT_THIS.with(|c| c.get()); +fn set_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> *mut crate::set::SetHeader { + let bits = this.bits(); let set = crate::set::set_ptr_from_receiver_bits(bits).or_else(|| { match super::map_set_subclass::subclass_backing_of(f64::from_bits(bits)) { Some(super::map_set_subclass::CollectionBacking::Set(set)) => Some(set), @@ -429,8 +429,8 @@ fn set_receiver_or_throw(method: &str) -> *mut crate::set::SetHeader { } #[inline] -fn map_receiver_or_throw(method: &str) -> *mut crate::map::MapHeader { - let bits = IMPLICIT_THIS.with(|c| c.get()); +fn map_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> *mut crate::map::MapHeader { + let bits = this.bits(); let map = crate::map::map_ptr_from_receiver_bits(bits).or_else(|| { match super::map_set_subclass::subclass_backing_of(f64::from_bits(bits)) { Some(super::map_set_subclass::CollectionBacking::Map(map)) => Some(map), @@ -446,8 +446,8 @@ fn map_receiver_or_throw(method: &str) -> *mut crate::map::MapHeader { /// Read the original receiver after its brand has been checked. Some native /// callers publish a raw pointer in IMPLICIT_THIS; normalize it before rooting /// or exposing it to JavaScript. The backing collection is not the receiver. -fn collection_this_value() -> f64 { - let bits = IMPLICIT_THIS.with(|c| c.get()); +fn collection_this_value(this: crate::closure::JsThis) -> f64 { + let bits = this.bits(); if bits >> 48 == 0 { crate::value::js_nanbox_pointer(bits as i64) } else { @@ -456,8 +456,13 @@ fn collection_this_value() -> f64 { } #[inline] -fn weak_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { - let receiver = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn weak_receiver_or_throw( + this: crate::closure::JsThis, + expected: u32, + proto: &str, + method: &str, +) -> f64 { + let receiver = f64::from_bits(this.bits()); match super::weak_class_id_from_receiver(receiver) { Some(cid) if cid == expected => receiver, _ => throw_incompatible_receiver(proto, method, receiver.to_bits()), @@ -466,69 +471,69 @@ fn weak_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { pub(super) extern "C" fn set_proto_add_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { - let set = set_receiver_or_throw("add"); + let set = set_receiver_or_throw(this, "add"); let scope = crate::gc::RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(collection_this_value()); + let receiver = scope.root_nanbox_f64(collection_this_value(this)); crate::set::js_set_add(set, v); receiver.get_nanbox_f64() } pub(super) extern "C" fn set_proto_has_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { - let set = set_receiver_or_throw("has"); + let set = set_receiver_or_throw(this, "has"); f64::from_bits(crate::value::JSValue::bool(crate::set::js_set_has(set, v) != 0).bits()) } pub(super) extern "C" fn set_proto_size_getter_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let set = set_receiver_or_throw("size"); + let set = set_receiver_or_throw(this, "size"); crate::set::js_set_size(set) as f64 } pub(super) extern "C" fn set_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { - let set = set_receiver_or_throw("delete"); + let set = set_receiver_or_throw(this, "delete"); f64::from_bits(crate::value::JSValue::bool(crate::set::js_set_delete(set, v) != 0).bits()) } pub(super) extern "C" fn set_proto_clear_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let set = set_receiver_or_throw("clear"); + let set = set_receiver_or_throw(this, "clear"); crate::set::js_set_clear(set); f64::from_bits(crate::value::TAG_UNDEFINED) } pub(super) extern "C" fn set_proto_foreach_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, cb: f64, this_arg: f64, ) -> f64 { - let set = set_receiver_or_throw("forEach"); - crate::set::js_set_foreach_with_collection(set, cb, this_arg, collection_this_value()); + let set = set_receiver_or_throw(this, "forEach"); + crate::set::js_set_foreach_with_collection(set, cb, this_arg, collection_this_value(this)); f64::from_bits(crate::value::TAG_UNDEFINED) } pub(super) extern "C" fn set_proto_values_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let set = set_receiver_or_throw("values"); + let set = set_receiver_or_throw(this, "values"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_set_values_iter_obj(set) as *mut u8 @@ -539,10 +544,10 @@ pub(super) extern "C" fn set_proto_values_thunk( pub(super) extern "C" fn set_proto_keys_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let set = set_receiver_or_throw("keys"); + let set = set_receiver_or_throw(this, "keys"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_set_values_iter_obj(set) as *mut u8 @@ -553,10 +558,10 @@ pub(super) extern "C" fn set_proto_keys_thunk( pub(super) extern "C" fn set_proto_entries_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let set = set_receiver_or_throw("entries"); + let set = set_receiver_or_throw(this, "entries"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_set_entries_iter_obj(set) as *mut u8 @@ -567,79 +572,79 @@ pub(super) extern "C" fn set_proto_entries_thunk( pub(super) extern "C" fn map_proto_get_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { - let map = map_receiver_or_throw("get"); + let map = map_receiver_or_throw(this, "get"); crate::map::js_map_get(map, k) } pub(super) extern "C" fn map_proto_set_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, v: f64, ) -> f64 { - let map = map_receiver_or_throw("set"); + let map = map_receiver_or_throw(this, "set"); let scope = crate::gc::RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(collection_this_value()); + let receiver = scope.root_nanbox_f64(collection_this_value(this)); crate::map::js_map_set(map, k, v); receiver.get_nanbox_f64() } pub(super) extern "C" fn map_proto_has_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { - let map = map_receiver_or_throw("has"); + let map = map_receiver_or_throw(this, "has"); f64::from_bits(crate::value::JSValue::bool(crate::map::js_map_has(map, k) != 0).bits()) } pub(super) extern "C" fn map_proto_size_getter_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let map = map_receiver_or_throw("size"); + let map = map_receiver_or_throw(this, "size"); crate::map::js_map_size(map) as f64 } pub(super) extern "C" fn map_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { - let map = map_receiver_or_throw("delete"); + let map = map_receiver_or_throw(this, "delete"); f64::from_bits(crate::value::JSValue::bool(crate::map::js_map_delete(map, k) != 0).bits()) } pub(super) extern "C" fn map_proto_clear_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let map = map_receiver_or_throw("clear"); + let map = map_receiver_or_throw(this, "clear"); crate::map::js_map_clear(map); f64::from_bits(crate::value::TAG_UNDEFINED) } pub(super) extern "C" fn map_proto_foreach_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, cb: f64, this_arg: f64, ) -> f64 { - let map = map_receiver_or_throw("forEach"); - crate::map::js_map_foreach_with_collection(map, cb, this_arg, collection_this_value()); + let map = map_receiver_or_throw(this, "forEach"); + crate::map::js_map_foreach_with_collection(map, cb, this_arg, collection_this_value(this)); f64::from_bits(crate::value::TAG_UNDEFINED) } pub(super) extern "C" fn map_proto_keys_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let map = map_receiver_or_throw("keys"); + let map = map_receiver_or_throw(this, "keys"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_map_keys_iter_obj(map) as *mut u8 @@ -650,10 +655,10 @@ pub(super) extern "C" fn map_proto_keys_thunk( pub(super) extern "C" fn map_proto_values_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let map = map_receiver_or_throw("values"); + let map = map_receiver_or_throw(this, "values"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_map_values_iter_obj(map) as *mut u8 @@ -664,10 +669,10 @@ pub(super) extern "C" fn map_proto_values_thunk( pub(super) extern "C" fn map_proto_entries_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _v: f64, ) -> f64 { - let map = map_receiver_or_throw("entries"); + let map = map_receiver_or_throw(this, "entries"); f64::from_bits( crate::value::JSValue::pointer( crate::collection_iter_object::js_map_entries_iter_obj(map) as *mut u8 @@ -678,28 +683,39 @@ pub(super) extern "C" fn map_proto_entries_thunk( pub(super) extern "C" fn weakset_proto_add_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { - let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKSET, "WeakSet.prototype", "add"); + let r = weak_receiver_or_throw( + this, + crate::weakref::CLASS_ID_WEAKSET, + "WeakSet.prototype", + "add", + ); crate::weakref::js_weakset_add(r, v) } pub(super) extern "C" fn weakset_proto_has_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { - let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKSET, "WeakSet.prototype", "has"); + let r = weak_receiver_or_throw( + this, + crate::weakref::CLASS_ID_WEAKSET, + "WeakSet.prototype", + "has", + ); crate::weakref::js_weakmap_has(r, v) } pub(super) extern "C" fn weakset_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, v: f64, ) -> f64 { let r = weak_receiver_or_throw( + this, crate::weakref::CLASS_ID_WEAKSET, "WeakSet.prototype", "delete", @@ -709,38 +725,54 @@ pub(super) extern "C" fn weakset_proto_delete_thunk( pub(super) extern "C" fn weakmap_proto_get_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { - let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "get"); + let r = weak_receiver_or_throw( + this, + crate::weakref::CLASS_ID_WEAKMAP, + "WeakMap.prototype", + "get", + ); crate::weakref::js_weakmap_get(r, k) } pub(super) extern "C" fn weakmap_proto_set_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, v: f64, ) -> f64 { - let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "set"); + let r = weak_receiver_or_throw( + this, + crate::weakref::CLASS_ID_WEAKMAP, + "WeakMap.prototype", + "set", + ); crate::weakref::js_weakmap_set(r, k, v) } pub(super) extern "C" fn weakmap_proto_has_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { - let r = weak_receiver_or_throw(crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "has"); + let r = weak_receiver_or_throw( + this, + crate::weakref::CLASS_ID_WEAKMAP, + "WeakMap.prototype", + "has", + ); crate::weakref::js_weakmap_has(r, k) } pub(super) extern "C" fn weakmap_proto_delete_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, k: f64, ) -> f64 { let r = weak_receiver_or_throw( + this, crate::weakref::CLASS_ID_WEAKMAP, "WeakMap.prototype", "delete", diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index cf380242a4..d334d65c77 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -80,9 +80,9 @@ date_getter_thunk!( /// the time value is `NaN`). extern "C" fn date_to_iso_string( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); if !crate::date::is_date_value(this) { super::object_ops::throw_object_type_error(b"this is not a Date object."); } @@ -95,9 +95,9 @@ extern "C" fn date_to_iso_string( /// then format as "Sun, 23 Mar 2014 00:00:00 GMT" (or "Invalid Date"). extern "C" fn date_to_utc_string( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); if !crate::date::is_date_value(this) { super::object_ops::throw_object_type_error(b"this is not a Date object."); } @@ -131,9 +131,9 @@ extern "C" fn date_to_temporal_instant( /// `Number(-Infinity)` wrapper (→ `null`). extern "C" fn date_to_json( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); date_to_json_value(this) } @@ -265,10 +265,10 @@ fn value_is_object(value: f64) -> bool { /// (TypeError), matching test262 `hint-invalid.js`. extern "C" fn date_to_primitive( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, hint: f64, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); // Step 1: brand-check `Type(O) is Object` (throws for undefined/null/86/''/true). if !value_is_object(this) { super::object_ops::throw_object_type_error( diff --git a/crates/perry-runtime/src/object/disposable_proto_thunks.rs b/crates/perry-runtime/src/object/disposable_proto_thunks.rs index c29c7d4391..2c6f7f6005 100644 --- a/crates/perry-runtime/src/object/disposable_proto_thunks.rs +++ b/crates/perry-runtime/src/object/disposable_proto_thunks.rs @@ -31,8 +31,12 @@ fn throw_incompatible(proto: &str, method: &str) -> ! { /// Resolve `IMPLICIT_THIS` to a stack `ObjectHeader` of the expected class id, /// or throw a `TypeError`. -fn stack_receiver_or_throw(want_async: bool, method: &str) -> *mut ObjectHeader { - let bits = IMPLICIT_THIS.with(|c| c.get()); +fn stack_receiver_or_throw( + this: crate::closure::JsThis, + want_async: bool, + method: &str, +) -> *mut ObjectHeader { + let bits = this.bits(); let value = f64::from_bits(bits); let proto = if want_async { "AsyncDisposableStack.prototype" @@ -55,53 +59,53 @@ fn stack_receiver_or_throw(want_async: bool, method: &str) -> *mut ObjectHeader pub(super) extern "C" fn ds_proto_use_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, resource: f64, ) -> f64 { - let stack = stack_receiver_or_throw(false, "use"); + let stack = stack_receiver_or_throw(this, false, "use"); js_disposable_stack_use(stack, resource) } pub(super) extern "C" fn ds_proto_adopt_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, on_dispose: f64, ) -> f64 { - let stack = stack_receiver_or_throw(false, "adopt"); + let stack = stack_receiver_or_throw(this, false, "adopt"); js_disposable_stack_adopt(stack, value, on_dispose) } pub(super) extern "C" fn ds_proto_defer_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, on_dispose: f64, ) -> f64 { - let stack = stack_receiver_or_throw(false, "defer"); + let stack = stack_receiver_or_throw(this, false, "defer"); js_disposable_stack_defer(stack, on_dispose) } pub(super) extern "C" fn ds_proto_dispose_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(false, "dispose"); + let stack = stack_receiver_or_throw(this, false, "dispose"); js_disposable_stack_dispose(stack) } pub(super) extern "C" fn ds_proto_move_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(false, "move"); + let stack = stack_receiver_or_throw(this, false, "move"); js_disposable_stack_move(stack) } pub(super) extern "C" fn ds_proto_disposed_getter_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(false, "disposed"); + let stack = stack_receiver_or_throw(this, false, "disposed"); js_disposable_stack_disposed(stack) } @@ -109,53 +113,53 @@ pub(super) extern "C" fn ds_proto_disposed_getter_thunk( pub(super) extern "C" fn ads_proto_use_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, resource: f64, ) -> f64 { - let stack = stack_receiver_or_throw(true, "use"); + let stack = stack_receiver_or_throw(this, true, "use"); js_async_disposable_stack_use(stack, resource) } pub(super) extern "C" fn ads_proto_adopt_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, on_dispose: f64, ) -> f64 { - let stack = stack_receiver_or_throw(true, "adopt"); + let stack = stack_receiver_or_throw(this, true, "adopt"); js_disposable_stack_adopt(stack, value, on_dispose) } pub(super) extern "C" fn ads_proto_defer_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, on_dispose: f64, ) -> f64 { - let stack = stack_receiver_or_throw(true, "defer"); + let stack = stack_receiver_or_throw(this, true, "defer"); js_disposable_stack_defer(stack, on_dispose) } pub(super) extern "C" fn ads_proto_dispose_async_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(true, "disposeAsync"); + let stack = stack_receiver_or_throw(this, true, "disposeAsync"); js_async_disposable_stack_dispose_async(stack) } pub(super) extern "C" fn ads_proto_move_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(true, "move"); + let stack = stack_receiver_or_throw(this, true, "move"); js_disposable_stack_move(stack) } pub(super) extern "C" fn ads_proto_disposed_getter_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stack = stack_receiver_or_throw(true, "disposed"); + let stack = stack_receiver_or_throw(this, true, "disposed"); js_disposable_stack_disposed(stack) } diff --git a/crates/perry-runtime/src/object/global_this/array_error.rs b/crates/perry-runtime/src/object/global_this/array_error.rs index 47953de2a9..a5ba24ead0 100644 --- a/crates/perry-runtime/src/object/global_this/array_error.rs +++ b/crates/perry-runtime/src/object/global_this/array_error.rs @@ -17,11 +17,11 @@ pub(crate) fn global_this_rest_array_values(rest: f64) -> Vec { pub(crate) extern "C" fn function_prototype_call_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, this_arg: f64, rest: f64, ) -> f64 { - let target = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let target = f64::from_bits(this.bits()); // The generic value-call bridge treats a proxy invocation as a bare // call. Preserve the explicit receiver of Function.prototype.call. if crate::proxy::js_proxy_is_proxy(target) == 1 { @@ -71,11 +71,11 @@ pub(crate) extern "C" fn function_prototype_call_thunk( /// hit exactly this: `Reflect.apply(bind, call, [fn])` yielded `undefined`. pub(crate) extern "C" fn function_prototype_bind_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, this_arg: f64, rest: f64, ) -> f64 { - let target = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let target = f64::from_bits(this.bits()); let mut args: Vec = Vec::with_capacity(1); args.push(this_arg); args.extend(global_this_rest_array_values(rest)); @@ -198,7 +198,7 @@ pub(crate) extern "C" fn global_this_queue_microtask_thunk( /// `undefined`, so the `.call` access threw before the IIFE body ran. pub(crate) extern "C" fn object_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { // Delegate to the canonical `js_object_to_string` so this callable form // (`const f = Object.prototype.toString; f.call(x)`) shares the full brand @@ -206,16 +206,16 @@ pub(crate) extern "C" fn object_prototype_to_string_thunk( // buffers/…). Previously this thunk duplicated a coarse discrimination that // mis-tagged typed arrays as `[object Number]` and everything beyond // Array/Error/Date as `[object Object]`. - let this_bits = IMPLICIT_THIS.with(|c| c.get()); + let this_bits = this.bits(); unsafe { crate::object::js_object_to_string(f64::from_bits(this_bits)) } } pub(crate) extern "C" fn object_prototype_is_prototype_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); // Spec 20.1.3.3 step order: if V is not an Object, return false FIRST — // `Object.prototype.isPrototypeOf.call(undefined, 1)` is `false`, not a // TypeError. Symbols are POINTER_TAG'd in Perry but are primitives. @@ -266,9 +266,9 @@ pub(crate) fn is_native_error_subclass_constructor(name: &str) -> bool { pub(crate) extern "C" fn date_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); // ECMA-262 21.4.4.41 Date.prototype.toString does `thisTimeValue(this)`, // which throws a TypeError when the receiver has no [[DateValue]] slot // (`Date.prototype.toString.call(0)`, `.call({})`, `Date.prototype.toString()` @@ -282,19 +282,19 @@ pub(crate) extern "C" fn date_prototype_to_string_thunk( pub(crate) extern "C" fn object_prototype_has_own_property_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::object_ops::js_object_has_own(this_value, key) } pub(crate) extern "C" fn object_prototype_property_is_enumerable_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::js_object_property_is_enumerable(this_value, key) } @@ -303,47 +303,47 @@ pub(crate) extern "C" fn object_prototype_property_is_enumerable_thunk( // not just the direct `o.__defineGetter__(...)` native-dispatch path. pub(crate) extern "C" fn object_prototype_define_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, getter: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::js_object_define_getter(this_value, key, getter) } pub(crate) extern "C" fn object_prototype_define_setter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, setter: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::js_object_define_setter(this_value, key, setter) } pub(crate) extern "C" fn object_prototype_lookup_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::js_object_lookup_getter(this_value, key) } pub(crate) extern "C" fn object_prototype_lookup_setter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); super::super::js_object_lookup_setter(this_value, key) } pub(crate) extern "C" fn error_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); let this_jsv = crate::value::JSValue::from_bits(this_value.to_bits()); // ECMA-262 20.5.3.4 step 2: `If Type(O) is not Object, throw a TypeError`. // A Symbol is a POINTER_TAG value (registered-symbol id), so it slips past @@ -407,17 +407,17 @@ unsafe fn string_header_to_owned(ptr: *const crate::StringHeader) -> String { pub(crate) extern "C" fn object_prototype_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); unsafe { super::super::js_object_default_value_of(this_value) } } pub(crate) extern "C" fn object_prototype_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this_value = f64::from_bits(this.bits()); unsafe { super::super::js_object_prototype_to_locale_string(this_value) } } @@ -598,12 +598,12 @@ pub(crate) unsafe fn function_apply_proxy(target: f64, receiver: f64, args_array pub(crate) extern "C" fn function_prototype_apply_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, this_arg: f64, args_array: f64, ) -> f64 { unsafe { - let target = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let target = f64::from_bits(this.bits()); if crate::proxy::js_proxy_is_proxy(target) == 1 { return function_apply_proxy(target, this_arg, args_array); } @@ -639,9 +639,9 @@ pub(crate) extern "C" fn function_prototype_apply_thunk( /// `Object.prototype.toString` (which keeps its own real thunk). pub(crate) extern "C" fn function_prototype_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_bits = IMPLICIT_THIS.with(|c| c.get()); + let this_bits = this.bits(); let this_jsv = JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -697,12 +697,12 @@ pub(crate) extern "C" fn function_prototype_to_string_thunk( /// `Cannot read properties of undefined (reading 'call')` at module init. pub(crate) extern "C" fn array_prototype_slice_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start_val: f64, end_val: f64, ) -> f64 { use crate::value::JSValue; - let this_bits = IMPLICIT_THIS.with(|c| c.get()); + let this_bits = this.bits(); let this_jsv = JSValue::from_bits(this_bits); let arr_ptr = if this_jsv.is_pointer() { this_jsv.as_pointer::() @@ -774,78 +774,78 @@ pub(crate) extern "C" fn array_prototype_slice_thunk( /// iterate the receiver they are given. pub(crate) extern "C" fn array_prototype_values_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::array_values_iter(this) } pub(crate) extern "C" fn array_prototype_flat_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, depth: f64, ) -> f64 { - crate::array::js_arraylike_flat(crate::object::js_implicit_this_get(), depth) + crate::array::js_arraylike_flat(this.as_f64(), depth) } pub(crate) extern "C" fn array_prototype_pop_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::array_proto_mutator(this, "pop", std::ptr::null(), 0) } pub(crate) extern "C" fn array_prototype_shift_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::array_proto_mutator(this, "shift", std::ptr::null(), 0) } pub(crate) extern "C" fn array_prototype_reverse_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::array_proto_mutator(this, "reverse", std::ptr::null(), 0) } pub(crate) extern "C" fn array_prototype_push_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); crate::array::array_proto_mutator(this, "push", args.as_ptr(), args.len()) } pub(crate) extern "C" fn array_prototype_unshift_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); crate::array::array_proto_mutator(this, "unshift", args.as_ptr(), args.len()) } pub(crate) extern "C" fn array_prototype_splice_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); crate::array::array_proto_mutator(this, "splice", args.as_ptr(), args.len()) } pub(crate) extern "C" fn array_prototype_sort_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, comparator: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::js_arraylike_sort(this, comparator) } /// `fill` / `copyWithin` were noop-backed until #6908, so a Proxy receiver @@ -859,10 +859,10 @@ pub(crate) extern "C" fn array_prototype_sort_thunk( /// the `end` params the spec resolves `undefined` to `len`). pub(crate) extern "C" fn array_prototype_fill_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); let undefined = f64::from_bits(crate::value::TAG_UNDEFINED); let value = args.first().copied().unwrap_or(undefined); @@ -878,10 +878,10 @@ pub(crate) extern "C" fn array_prototype_fill_thunk( } pub(crate) extern "C" fn array_prototype_copy_within_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); let undefined = f64::from_bits(crate::value::TAG_UNDEFINED); let target = args.first().copied().unwrap_or(undefined); @@ -1007,18 +1007,18 @@ array_proto_arraylike_search_thunk!( pub(crate) extern "C" fn array_proto_at_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, idx: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::js_arraylike_at(this, idx) } pub(crate) extern "C" fn array_proto_join_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, sep: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::array::js_arraylike_join(this, sep) } @@ -1029,18 +1029,18 @@ pub(crate) extern "C" fn array_proto_join_thunk( /// receiver semantics; rewriting it to `x.toString()` loses the Array method. pub(crate) extern "C" fn array_prototype_to_string_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); crate::value::array_prototype_to_string(this) } pub(crate) extern "C" fn array_prototype_concat_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); crate::array::js_arraylike_concat(this, args.as_ptr(), args.len() as i32) } 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 6e39756dfe..baeb01b590 100644 --- a/crates/perry-runtime/src/object/global_this/bigint_promise.rs +++ b/crates/perry-runtime/src/object/global_this/bigint_promise.rs @@ -226,7 +226,7 @@ pub(crate) extern "C" fn array_is_array_thunk( pub(crate) extern "C" fn array_from_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { // Reflective `Array.from.call(C, items)` / `Array.from.apply(C, [items])` @@ -235,21 +235,21 @@ pub(crate) extern "C" fn array_from_thunk( // the result is built via `Construct(C)`. A plain reflective call (no // explicit receiver) leaves `this` as undefined / a non-constructor, so // the default `%Array%` path is taken. - let c = crate::object::js_implicit_this_get(); + let c = this.as_f64(); let undefined = f64::from_bits(crate::value::TAG_UNDEFINED); crate::array::array_from_full(c, value, undefined, undefined) } pub(crate) extern "C" fn array_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { // Reflective `Array.of.call(C, ...items)` binds `C` as the implicit `this`. // Read it FIRST (before any nested call can overwrite it); when `C // IsConstructor` the result is built via `Construct(C, «len»)`, otherwise the // default `%Array%` path is taken. See `array_of_full` (ECMA-262 §23.1.2.3). - let c = crate::object::js_implicit_this_get(); + let c = this.as_f64(); let vals = global_this_rest_array_values(rest); crate::array::array_of_full(c, &vals) } @@ -624,74 +624,74 @@ pub(crate) extern "C" fn string_from_code_point_static( // thunks intentionally use the native Promise machinery regardless of `this`. extern "C" fn promise_resolve_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_resolve_spec(this_ctor, value) } extern "C" fn promise_reject_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, reason: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_reject_spec(this_ctor, reason) } extern "C" fn promise_all_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, iterable: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_all_spec(this_ctor, iterable) } extern "C" fn promise_race_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, iterable: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_race_spec(this_ctor, iterable) } extern "C" fn promise_all_settled_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, iterable: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_all_settled_spec(this_ctor, iterable) } extern "C" fn promise_any_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, iterable: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_any_spec(this_ctor, iterable) } extern "C" fn promise_with_resolvers_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_with_resolvers_spec(this_ctor) } // `Promise.try(fn, ...args)`: call-arity 1 (callback) + rest (forwarded args). extern "C" fn promise_try_static( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { - let this_ctor = crate::object::js_implicit_this_get(); + let this_ctor = this.as_f64(); crate::promise::js_promise_try_spec(this_ctor, callback, rest) } @@ -711,29 +711,24 @@ pub(crate) extern "C" fn string_raw_static( pub(crate) extern "C" fn number_parse_float_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - global_this_parse_float_thunk(closure, crate::closure::body_call::current_this(), value) + global_this_parse_float_thunk(closure, this, value) } pub(crate) extern "C" fn number_parse_int_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, radix: f64, ) -> f64 { - global_this_parse_int_thunk( - closure, - crate::closure::body_call::current_this(), - value, - radix, - ) + global_this_parse_int_thunk(closure, this, value, radix) } pub(crate) extern "C" fn typed_array_from_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, source: f64, map_fn: f64, this_arg: f64, @@ -744,9 +739,9 @@ pub(crate) extern "C" fn typed_array_from_thunk( // (not a constructor), so this must fire even though a source is supplied // (test262 `from/invoked-as-func`). A concrete TA `this` (kind known) is a // constructor by definition. - let kind_opt = typed_array_constructor_this_kind(); + let kind_opt = typed_array_constructor_this_kind(this); if kind_opt.is_none() { - require_typed_array_from_of_constructor(); + require_typed_array_from_of_constructor(this); } // Spec order: validate the map callback BEFORE the source is read. let mapped = map_fn.to_bits() != crate::value::TAG_UNDEFINED; @@ -766,7 +761,7 @@ pub(crate) extern "C" fn typed_array_from_thunk( // (test262 from/set-value-abrupt-completion). let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let prev = this_scope.root_nanbox_f64(this.as_f64()); let map_at = |k: usize, v: f64| -> f64 { if map_closure.is_null() { return v; @@ -795,7 +790,7 @@ pub(crate) extern "C" fn typed_array_from_thunk( let len_arg = [f64::from_bits( crate::value::JSValue::number(len as f64).bits(), )]; - let ctor = crate::object::js_implicit_this_get(); + let ctor = this.as_f64(); let target = unsafe { super::super::js_new_function_construct(ctor, len_arg.as_ptr(), 1) }; let addr = crate::typedarray_props::typed_array_addr_from_value(target).unwrap_or_else(|| { super::super::object_ops::throw_object_type_error( @@ -819,8 +814,8 @@ pub(crate) extern "C" fn typed_array_from_thunk( /// `%TypedArray%.from`/`.of` step "If IsConstructor(`this`) is false, throw a /// TypeError". Only called when the `this` value is not a concrete typed-array /// constructor (kind unknown); a user constructor passes, anything else throws. -fn require_typed_array_from_of_constructor() { - let this_ctor = crate::object::js_implicit_this_get(); +fn require_typed_array_from_of_constructor(this: crate::closure::JsThis) { + let this_ctor = this.as_f64(); if !value_is_constructor(this_ctor) { super::super::object_ops::throw_object_type_error( b"TypedArray.from/of called with a `this` that is not a constructor", @@ -862,6 +857,7 @@ fn value_is_constructor(value: f64) -> bool { /// that returns an arbitrary (sufficiently long) typed array is used verbatim /// (test262 `from/of` `custom-ctor*`). fn typed_array_create_from_values( + this: crate::closure::JsThis, kind_opt: Option, arr: *mut crate::array::ArrayHeader, ) -> f64 { @@ -869,7 +865,7 @@ fn typed_array_create_from_values( let ta = crate::typedarray::js_typed_array_new_from_array(kind as i32, arr); return crate::value::js_nanbox_pointer(ta as i64); } - let ctor = crate::object::js_implicit_this_get(); + let ctor = this.as_f64(); let len = crate::array::js_array_length(arr) as usize; let len_arg = [f64::from_bits( crate::value::JSValue::number(len as f64).bits(), @@ -901,12 +897,12 @@ fn typed_array_create_from_values( pub(crate) extern "C" fn typed_array_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let kind_opt = typed_array_constructor_this_kind(); + let kind_opt = typed_array_constructor_this_kind(this); if kind_opt.is_none() { - require_typed_array_from_of_constructor(); + require_typed_array_from_of_constructor(this); } let vals = global_this_rest_array_values(rest); let len = vals.len() as u32; @@ -917,7 +913,7 @@ pub(crate) extern "C" fn typed_array_of_thunk( crate::array::js_array_set_f64(arr, i as u32, v); } } - typed_array_create_from_values(kind_opt, arr) + typed_array_create_from_values(this, kind_opt, arr) } pub(crate) fn promise_static_function_spec(name: &str) -> Option<(*const u8, u32, u32, bool)> { 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 82c120e9f2..7405bdcd5f 100644 --- a/crates/perry-runtime/src/object/global_this/ctor_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/ctor_thunks.rs @@ -319,8 +319,8 @@ pub(crate) extern "C" fn global_this_crypto_getter_thunk( super::super::native_module::webcrypto_namespace() } -fn require_webcrypto_this() -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn require_webcrypto_this(this: crate::closure::JsThis) -> f64 { + let this_value = f64::from_bits(this.bits()); let jv = crate::value::JSValue::from_bits(this_value.to_bits()); if jv.is_pointer() { let obj = jv.as_pointer::(); @@ -340,10 +340,10 @@ fn require_webcrypto_this() -> f64 { pub(crate) extern "C" fn webcrypto_get_random_values_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, array: f64, ) -> f64 { - let this_value = require_webcrypto_this(); + let this_value = require_webcrypto_this(this); unsafe { js_native_call_method( this_value, @@ -361,9 +361,9 @@ pub(crate) extern "C" fn webcrypto_get_random_values_thunk( /// name through the whole native-call tower on every UUID. pub(crate) extern "C" fn webcrypto_random_uuid_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_webcrypto_this(); + require_webcrypto_this(this); let ptr = crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); if ptr.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -381,9 +381,9 @@ pub(crate) extern "C" fn webcrypto_random_uuid_thunk( pub(crate) extern "C" fn webcrypto_subtle_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_webcrypto_this(); + require_webcrypto_this(this); super::super::native_module::subtle_crypto_namespace() } diff --git a/crates/perry-runtime/src/object/global_this/generator.rs b/crates/perry-runtime/src/object/global_this/generator.rs index ad959177d4..d1ad04f48f 100644 --- a/crates/perry-runtime/src/object/global_this/generator.rs +++ b/crates/perry-runtime/src/object/global_this/generator.rs @@ -336,7 +336,12 @@ fn generator_receiver_type_error_value(method: &[u8]) -> f64 { /// `is_async` selects the spec's incompatible-receiver behaviour: sync /// generators throw a `TypeError` synchronously, async generators return a /// rejected promise (their methods always return promises). (#3664) -fn generator_proto_method(method: &[u8], arg: f64, is_async: bool) -> f64 { +fn generator_proto_method( + this: crate::closure::JsThis, + method: &[u8], + arg: f64, + is_async: bool, +) -> f64 { let bad_receiver = |method: &[u8]| -> f64 { let errv = generator_receiver_type_error_value(method); if is_async { @@ -346,7 +351,7 @@ fn generator_proto_method(method: &[u8], arg: f64, is_async: bool) -> f64 { crate::exception::js_throw(errv) } }; - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let jv = JSValue::from_bits(this.to_bits()); if !jv.is_pointer() { return bad_receiver(method); @@ -401,45 +406,45 @@ fn generator_proto_method(method: &[u8], arg: f64, is_async: bool) -> f64 { extern "C" fn generator_proto_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"next", arg, false) + generator_proto_method(this, b"next", arg, false) } extern "C" fn generator_proto_return_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"return", arg, false) + generator_proto_method(this, b"return", arg, false) } extern "C" fn generator_proto_throw_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"throw", arg, false) + generator_proto_method(this, b"throw", arg, false) } extern "C" fn async_generator_proto_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"next", arg, true) + generator_proto_method(this, b"next", arg, true) } extern "C" fn async_generator_proto_return_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"return", arg, true) + generator_proto_method(this, b"return", arg, true) } extern "C" fn async_generator_proto_throw_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - generator_proto_method(b"throw", arg, true) + generator_proto_method(this, b"throw", arg, true) } /// `%AsyncGenerator.prototype%[Symbol.asyncIterator]()` returns `this` (spec @@ -448,10 +453,10 @@ extern "C" fn async_generator_proto_throw_thunk( /// iterator and either throws or silently produces nothing. extern "C" fn async_generator_proto_async_iterator_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } /// `%Generator.prototype%[Symbol.iterator]()` returns `this` (spec inherits this @@ -459,10 +464,10 @@ extern "C" fn async_generator_proto_async_iterator_thunk( /// note in `build_generator_tower` for why the sync prototype now carries this. extern "C" fn generator_proto_iterator_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } /// Install a well-known-symbol-keyed method (returning `this`) on a 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 b5255d3f9c..844ab0f115 100644 --- a/crates/perry-runtime/src/object/global_this/install_static.rs +++ b/crates/perry-runtime/src/object/global_this/install_static.rs @@ -163,8 +163,8 @@ fn rejected_type_error_with_code_promise(message: &str, code: &'static str) -> f crate::value::js_nanbox_pointer(promise as i64) } -fn subtle_crypto_dispatch_rest(method_name: &str, rest: f64) -> f64 { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn subtle_crypto_dispatch_rest(this: crate::closure::JsThis, method_name: &str, rest: f64) -> f64 { + let this_value = f64::from_bits(this.bits()); if !is_subtle_crypto_this(this_value) { return rejected_type_error_with_code_promise( "Value of \"this\" must be of type SubtleCrypto", @@ -191,34 +191,34 @@ fn subtle_crypto_dispatch_rest(method_name: &str, rest: f64) -> f64 { pub(crate) extern "C" fn subtle_crypto_encapsulate_bits_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - subtle_crypto_dispatch_rest("encapsulateBits", rest) + subtle_crypto_dispatch_rest(this, "encapsulateBits", rest) } pub(crate) extern "C" fn subtle_crypto_decapsulate_bits_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - subtle_crypto_dispatch_rest("decapsulateBits", rest) + subtle_crypto_dispatch_rest(this, "decapsulateBits", rest) } pub(crate) extern "C" fn subtle_crypto_encapsulate_key_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - subtle_crypto_dispatch_rest("encapsulateKey", rest) + subtle_crypto_dispatch_rest(this, "encapsulateKey", rest) } pub(crate) extern "C" fn subtle_crypto_decapsulate_key_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - subtle_crypto_dispatch_rest("decapsulateKey", rest) + subtle_crypto_dispatch_rest(this, "decapsulateKey", rest) } /// Install a single callable static method on a constructor closure as a @@ -995,24 +995,24 @@ pub(crate) fn install_noop_proto_methods(proto_obj: *mut ObjectHeader, methods: pub(crate) extern "C" fn url_pattern_test_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, rest: f64, ) -> f64 { let base = rest_first_arg(rest); - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); let pattern = crate::value::js_nanbox_get_pointer(this_value) as *mut ObjectHeader; crate::url::js_url_pattern_test(pattern, input, base) } pub(crate) extern "C" fn url_pattern_exec_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, rest: f64, ) -> f64 { let base = rest_first_arg(rest); - let this_value = crate::object::js_implicit_this_get(); + let this_value = this.as_f64(); let pattern = crate::value::js_nanbox_get_pointer(this_value) as *mut ObjectHeader; crate::url::js_url_pattern_exec(pattern, input, base) } @@ -1035,9 +1035,9 @@ fn rest_first_arg(rest: f64) -> f64 { /// 27.2.4.8, 22.2.5.2, 23.2.2.4). pub(crate) extern "C" fn builtin_species_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - f64::from_bits(IMPLICIT_THIS.with(|c| c.get())) + f64::from_bits(this.bits()) } /// Install the standard own `get [Symbol.species]` accessor 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 39fdf6b921..697dced395 100644 --- a/crates/perry-runtime/src/object/global_this/math_temporal.rs +++ b/crates/perry-runtime/src/object/global_this/math_temporal.rs @@ -415,9 +415,9 @@ fn build_temporal_now_namespace() -> f64 { #[cfg(feature = "temporal")] extern "C" fn temporal_proto_getter_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let recv = super::super::js_implicit_this_get(); + let recv = this.as_f64(); let cl = closure as usize; let kind = crate::closure::closure_get_dynamic_prop(cl, "__tkind"); let expected = crate::value::JSValue::from_bits(kind.to_bits()).to_number() as u8; @@ -442,10 +442,10 @@ extern "C" fn temporal_proto_getter_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_proto_method_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let recv = super::super::js_implicit_this_get(); + let recv = this.as_f64(); let cl = closure as usize; let kind = crate::closure::closure_get_dynamic_prop(cl, "__tkind"); let expected = crate::value::JSValue::from_bits(kind.to_bits()).to_number() as u8; @@ -641,9 +641,9 @@ fn temporal_brand_type_error(type_name: &str, member: &str) -> ! { #[cfg(feature = "temporal")] extern "C" fn temporal_zdt_proto_getter_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); // The accessor's name is `"get "`; recover the bare property. let name = temporal_closure_name(closure); let prop = name.strip_prefix("get ").unwrap_or(&name); @@ -660,10 +660,10 @@ extern "C" fn temporal_zdt_proto_getter_thunk( #[cfg(feature = "temporal")] extern "C" fn temporal_zdt_proto_method_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); let name = temporal_closure_name(closure); if crate::temporal::temporal_kind(this) != Some(crate::temporal::TemporalKind::ZonedDateTime) { temporal_brand_type_error("Temporal.ZonedDateTime", &name); 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 c387e709f6..d85b98a8da 100644 --- a/crates/perry-runtime/src/object/global_this/proto_methods.rs +++ b/crates/perry-runtime/src/object/global_this/proto_methods.rs @@ -4,8 +4,8 @@ use super::*; // `array_proto_*_thunk` without routing through the trunk re-exports. use super::array_error::*; -fn web_method_receiver(name: &str) -> *mut ObjectHeader { - let receiver = crate::object::js_implicit_this_get(); +fn web_method_receiver(this: crate::closure::JsThis, name: &str) -> *mut ObjectHeader { + let receiver = this.as_f64(); if crate::object::web_builtin_to_string_tag(receiver) == Some(name) { return crate::value::js_nanbox_get_pointer(receiver) as *mut ObjectHeader; } @@ -17,25 +17,28 @@ fn web_method_receiver(name: &str) -> *mut ObjectHeader { extern "C" fn url_prototype_href_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - crate::url::js_url_get_href(web_method_receiver("URL")) + crate::url::js_url_get_href(web_method_receiver(this, "URL")) } extern "C" fn abort_controller_prototype_abort_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, reason: f64, ) -> f64 { - crate::url::js_abort_controller_abort_reason(web_method_receiver("AbortController"), reason); + crate::url::js_abort_controller_abort_reason( + web_method_receiver(this, "AbortController"), + reason, + ); f64::from_bits(crate::value::TAG_UNDEFINED) } extern "C" fn abort_signal_prototype_throw_if_aborted_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - crate::url::js_abort_signal_throw_if_aborted(web_method_receiver("AbortSignal")) + crate::url::js_abort_signal_throw_if_aborted(web_method_receiver(this, "AbortSignal")) } fn web_method_enumerable(proto_obj: *mut ObjectHeader, name: &str) { @@ -232,23 +235,23 @@ fn install_object_prototype_dunder_proto(proto_obj: *mut ObjectHeader) { extern "C" fn object_prototype_dunder_proto_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { // Spec (Annex B §B.3.1 `get __proto__`): `ToObject(this).[[GetPrototypeOf]]()`. // `js_object_get_prototype_of` already implements exactly this shape — // wrapper-prototype resolution for primitives, Proxy/Temporal/handle // receivers, and a throw on `null`/`undefined` (the `ToObject` failure // case) — so the getter is a direct delegation, not a reimplementation. - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); crate::object::js_object_get_prototype_of(receiver) } extern "C" fn object_prototype_dunder_proto_setter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); crate::proxy::legacy_dunder_proto_set(receiver, value); f64::from_bits(crate::value::TAG_UNDEFINED) } 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 e275311e47..b00dd7cf43 100644 --- a/crates/perry-runtime/src/object/global_this/typed_array.rs +++ b/crates/perry-runtime/src/object/global_this/typed_array.rs @@ -42,8 +42,8 @@ pub(crate) extern "C" fn array_buffer_byte_length_getter_thunk( /// getter. Mirrors `array_buffer_receiver_addr` but accepts only buffers in the /// shared registry, so the getter rejects a plain `ArrayBuffer` `this` /// (test262 SharedArrayBuffer/prototype/byteLength/this-is-arraybuffer). -fn shared_array_buffer_receiver_addr() -> Option { - let this_bits = IMPLICIT_THIS.with(|c| c.get()); +fn shared_array_buffer_receiver_addr(this: crate::closure::JsThis) -> Option { + let this_bits = this.bits(); let this_jsv = JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -61,9 +61,9 @@ fn shared_array_buffer_receiver_addr() -> Option { pub(crate) extern "C" fn shared_array_buffer_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match shared_array_buffer_receiver_addr() { + match shared_array_buffer_receiver_addr(this) { Some(addr) => { let buf = addr as *const crate::buffer::BufferHeader; f64::from_bits( @@ -83,11 +83,11 @@ pub(crate) extern "C" fn shared_array_buffer_byte_length_getter_thunk( /// is shared with the instance dispatch in `buffer_dispatch`. pub(crate) extern "C" fn shared_array_buffer_slice_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - match shared_array_buffer_receiver_addr() { + match shared_array_buffer_receiver_addr(this) { Some(addr) => unsafe { let args = [start, end]; super::super::buffer_dispatch::dispatch_buffer_method(addr, "slice", args.as_ptr(), 2) @@ -405,8 +405,8 @@ fn string_value_to_owned(value: f64) -> Option { } } -pub(crate) fn typed_array_constructor_this_kind() -> Option { - let this_value = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +pub(crate) fn typed_array_constructor_this_kind(this: crate::closure::JsThis) -> Option { + let this_value = f64::from_bits(this.bits()); let ptr = crate::value::js_nanbox_get_pointer(this_value) as usize; if ptr == 0 || !crate::closure::is_closure_ptr(ptr) { return None; @@ -485,9 +485,9 @@ extern "C" fn typed_array_buffer_getter_thunk( /// `Cannot read properties of undefined (reading 'get')`. extern "C" fn typed_array_to_string_tag_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); match crate::object::typed_array_to_string_tag_name(this) { Some(name) => { let s = crate::string::js_string_from_bytes(name.as_ptr(), name.len() as u32); diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index 9617c83f7f..62d1aa4df2 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -601,7 +601,7 @@ extern "C" fn webassembly_module_ctor_thunk( extern "C" fn webassembly_instance_ctor_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, module: f64, imports: f64, ) -> f64 { @@ -610,22 +610,18 @@ extern "C" fn webassembly_instance_ctor_thunk( } #[cfg(feature = "wasm-host")] { - crate::webassembly::js_webassembly_instance_new( - module, - imports, - crate::object::js_implicit_this_get(), - ) + crate::webassembly::js_webassembly_instance_new(module, imports, this.as_f64()) } #[cfg(not(feature = "wasm-host"))] { - let _ = (module, imports); + let _ = (this, module, imports); crate::exception::js_throw(wasm_unsupported_error(b"LinkError", "WebAssembly.Instance")); } } extern "C" fn webassembly_table_ctor_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, descriptor: f64, ) -> f64 { if !invoked_as_constructor(closure) { @@ -633,21 +629,18 @@ extern "C" fn webassembly_table_ctor_thunk( } #[cfg(feature = "wasm-host")] { - crate::webassembly::js_webassembly_table_new( - descriptor, - crate::object::js_implicit_this_get(), - ) + crate::webassembly::js_webassembly_table_new(descriptor, this.as_f64()) } #[cfg(not(feature = "wasm-host"))] { - let _ = descriptor; + let _ = (this, descriptor); crate::exception::js_throw(wasm_unsupported_error(b"RuntimeError", "WebAssembly.Table")); } } extern "C" fn webassembly_global_ctor_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, descriptor: f64, initial: f64, ) -> f64 { @@ -656,15 +649,11 @@ extern "C" fn webassembly_global_ctor_thunk( } #[cfg(feature = "wasm-host")] { - crate::webassembly::js_webassembly_global_new( - descriptor, - initial, - crate::object::js_implicit_this_get(), - ) + crate::webassembly::js_webassembly_global_new(descriptor, initial, this.as_f64()) } #[cfg(not(feature = "wasm-host"))] { - let _ = (descriptor, initial); + let _ = (this, descriptor, initial); crate::exception::js_throw(wasm_unsupported_error( b"RuntimeError", "WebAssembly.Global", @@ -745,7 +734,7 @@ fn wasm_memory_new_buffer(pages: u32) -> f64 { extern "C" fn webassembly_memory_ctor_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, descriptor: f64, ) -> f64 { if !invoked_as_constructor(closure) { @@ -764,7 +753,7 @@ extern "C" fn webassembly_memory_ctor_thunk( }; // The dynamic construct path pre-allocated the receiver with // `Memory.prototype` linked (so `instanceof` works); fill it in place. - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); #[cfg(feature = "wasm-host")] { crate::webassembly::js_webassembly_memory_new( @@ -873,10 +862,10 @@ fn wasm_memory_grow_on(this: f64, delta: f64) -> Result { extern "C" fn webassembly_memory_grow_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, delta: f64, ) -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let this = f64::from_bits(this.bits()); match wasm_memory_grow_on(this, delta) { Ok(old_pages) => old_pages as f64, Err(MemoryCtorError::Type(msg)) => { diff --git a/crates/perry-runtime/src/object/instanceof.rs b/crates/perry-runtime/src/object/instanceof.rs index c7b7262ecf..0d0aa2b168 100644 --- a/crates/perry-runtime/src/object/instanceof.rs +++ b/crates/perry-runtime/src/object/instanceof.rs @@ -462,10 +462,10 @@ fn throw_invalid_instanceof_rhs(type_ref: f64) -> ! { /// through `IMPLICIT_THIS` set by the `.call`/member dispatch. pub(crate) extern "C" fn function_prototype_has_instance_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let constructor = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let constructor = f64::from_bits(this.bits()); let result = ordinary_has_instance(constructor, value); f64::from_bits(if result { crate::value::TAG_TRUE diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index f15d3f9d18..420751d115 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -296,10 +296,10 @@ extern "C" fn iterator_helper_next_thunk( /// `%IteratorPrototype%[Symbol.iterator]()` returns `this` (the iterator). extern "C" fn iterator_proto_symbol_iterator_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - super::js_implicit_this_get() + this.as_f64() } /// Set `obj[Symbol.toStringTag] = tag` with the spec descriptor diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index 1d033c5f60..fc83014ed5 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -380,7 +380,7 @@ pub(crate) use this_binding::{ pub use this_binding::{ js_implicit_this_get, js_implicit_this_get_sloppy, js_implicit_this_set, js_new_target_get, js_new_target_set, js_static_this_arm_classref, js_static_this_arm_value, - js_static_this_resolve, js_this_param_witness, ImplicitThisScope, + js_static_this_resolve, js_this_coerce_sloppy, js_this_param_witness, ImplicitThisScope, }; pub use to_string_tag::js_object_to_string; pub(crate) use to_string_tag::typed_array_to_string_tag_name; 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 7742f85996..8d207a36ee 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 @@ -28,13 +28,13 @@ const ASYNC_RESOURCE_METHODS: &[(&str, u32)] = &[ /// `run`, `exit`, and `runInAsyncScope`. extern "C" fn async_hooks_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { unsafe { let name_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *const i8; let name_len = crate::closure::js_closure_get_capture_ptr(closure, 1) as usize; - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let name = std::slice::from_raw_parts(name_ptr as *const u8, name_len); // Node's enterWith/disable implementations do not brand-check an 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 757b000d49..a378f6acaf 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports.rs @@ -437,8 +437,8 @@ extern "C" fn buffer_prototype_method_thunk( /// `.offset`/`.parent` directly — see `get_field_by_name_tail.rs`); this only /// matters for reflection (`Object.getOwnPropertyDescriptor(Buffer.prototype, /// "offset").get.call(x)`) and enumeration. -fn buffer_prototype_this_addr() -> Option { - let this_bits = crate::object::IMPLICIT_THIS.with(|c| c.get()); +fn buffer_prototype_this_addr(this: crate::closure::JsThis) -> Option { + let this_bits = this.bits(); let jv = crate::value::JSValue::from_bits(this_bits); if !jv.is_pointer() { return None; @@ -455,9 +455,9 @@ fn buffer_prototype_this_addr() -> Option { /// Node's `Buffer.prototype`. extern "C" fn buffer_prototype_parent_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match buffer_prototype_this_addr() { + match buffer_prototype_this_addr(this) { Some(addr) => { crate::value::js_nanbox_pointer(crate::buffer::buffer_backing_array_buffer(addr) as i64) } @@ -469,9 +469,9 @@ extern "C" fn buffer_prototype_parent_getter_thunk( /// `.byteOffset`, still a real own accessor on Node's `Buffer.prototype`. extern "C" fn buffer_prototype_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match buffer_prototype_this_addr() { + match buffer_prototype_this_addr(this) { Some(addr) => crate::buffer::buffer_byte_offset(addr) as f64, None => f64::from_bits(crate::value::TAG_UNDEFINED), } @@ -767,7 +767,7 @@ fn attach_assert_prototype(constructor_value: f64) { extern "C" fn sqlite_database_sync_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg0: f64, arg1: f64, arg2: f64, @@ -775,7 +775,7 @@ extern "C" fn sqlite_database_sync_prototype_method_thunk( unsafe { let method_name_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *const i8; let method_name_len = crate::closure::js_closure_get_capture_ptr(closure, 1) as usize; - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let args = [arg0, arg1, arg2]; crate::object::js_native_call_method( receiver, @@ -1354,13 +1354,13 @@ pub(crate) fn scan_tls_derived_prototype_roots_mut( extern "C" fn tls_prototype_method_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { unsafe { let name_ptr = crate::closure::js_closure_get_capture_ptr(closure, 0) as *const i8; let name_len = crate::closure::js_closure_get_capture_ptr(closure, 1) as usize; - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let args_array = crate::value::js_nanbox_get_pointer(rest); crate::object::js_native_call_method_apply(receiver, name_ptr, name_len, args_array) } 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 57b496b337..2a6cdd1eff 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 @@ -29,7 +29,7 @@ use super::*; /// memory-sharing `ArrayBuffer` view. extern "C" fn fast_buffer_constructor_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, byte_offset: f64, length: f64, @@ -44,13 +44,7 @@ extern "C" fn fast_buffer_constructor_thunk( let buf = crate::buffer::js_buffer_alloc(size, 0); return crate::value::js_nanbox_pointer(buf as i64); } - super::buffer_constructor_thunk( - closure, - crate::closure::body_call::current_this(), - value, - byte_offset, - length, - ) + super::buffer_constructor_thunk(closure, this, value, byte_offset, length) } /// `get [Symbol.species]` on `Buffer`: answers the `FastBuffer` held in the 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 9308d85403..f37b0aa091 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 @@ -255,22 +255,22 @@ extern "C" fn module_prototype_method_thunk( extern "C" fn module_prototype_load_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, filename: f64, _b: f64, _c: f64, ) -> f64 { - crate::process::js_module_instance_load(filename) + crate::process::js_module_instance_load(this, filename) } extern "C" fn module_prototype_require_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, specifier: f64, _b: f64, _c: f64, ) -> f64 { - crate::process::js_module_instance_require(specifier) + crate::process::js_module_instance_require(this, specifier) } fn module_prototype_method(name: &str, length: u32) -> f64 { diff --git a/crates/perry-runtime/src/object/regex_proto_thunks.rs b/crates/perry-runtime/src/object/regex_proto_thunks.rs index 03755946ac..44006fa4d3 100644 --- a/crates/perry-runtime/src/object/regex_proto_thunks.rs +++ b/crates/perry-runtime/src/object/regex_proto_thunks.rs @@ -158,18 +158,17 @@ pub(super) extern "C" fn regex_proto_source_getter( /// order `d g i m s u v y`. Throws `TypeError` only if `this` is not an Object. pub(super) extern "C" fn regex_proto_flags_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { #[cfg(feature = "regex-engine")] { - let value = crate::regex::perex_api::finish(crate::regex::perex_match_search::flags( - crate::object::js_implicit_this_get(), - )); + let value = + crate::regex::perex_api::finish(crate::regex::perex_match_search::flags(this.as_f64())); crate::value::js_nanbox_string(value as i64) } #[cfg(not(feature = "regex-engine"))] { - let receiver = crate::value::JSValue::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let receiver = crate::value::JSValue::from_bits(this.bits()); // Type(R) must be Object. Pointer-tagged values are objects EXCEPT Symbols // (which are also pointer-tagged via the symbol side-table); a Symbol `this` // must throw a TypeError, not silently assemble "". @@ -242,10 +241,10 @@ fn install_getter(proto_obj: *mut ObjectHeader, name: &str, func_ptr: *const u8) #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_exec_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - let re = regex_instance_or_throw("exec"); + let re = regex_instance_or_throw(this, "exec"); let scope = crate::gc::RuntimeHandleScope::new(); let re = scope.root_raw_const_ptr(re); let s = crate::value::js_jsvalue_to_string_coerce(arg); @@ -276,11 +275,12 @@ pub(crate) fn is_builtin_regexp_exec(value: f64) -> bool { #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_test_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { let matched = crate::regex::perex_api::finish(crate::regex::perex_dispatch::test_value( - crate::object::js_implicit_this_get(), + this, + this.as_f64(), arg, )); f64::from_bits(crate::value::JSValue::bool(matched).bits()) @@ -296,11 +296,11 @@ pub(super) extern "C" fn regex_proto_test_thunk( #[cfg(feature = "regex-engine")] pub(super) extern "C" fn regex_proto_compile_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, pattern: f64, flags: f64, ) -> f64 { - let re = regex_instance_or_throw("compile"); + let re = regex_instance_or_throw(this, "compile"); crate::regex::js_regexp_compile_value(re as *mut crate::regex::RegExpHeader, pattern, flags) } @@ -310,9 +310,9 @@ pub(super) extern "C" fn regex_proto_compile_thunk( /// `RegExp.prototype.toString.call({ source: "x", flags: "g" })` works. pub(super) extern "C" fn regex_proto_to_string_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let receiver = crate::value::JSValue::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let receiver = crate::value::JSValue::from_bits(this.bits()); if !receiver.is_pointer() || crate::symbol::is_registered_symbol(receiver.as_pointer::() as usize) { @@ -343,8 +343,11 @@ pub(super) extern "C" fn regex_proto_to_string_thunk( /// throwing `TypeError` otherwise. Unlike the flag/`source` getters, this does /// NOT treat `RegExp.prototype` specially — builtin exec requires a matcher. #[cfg(feature = "regex-engine")] -fn regex_instance_or_throw(method: &str) -> *const crate::regex::RegExpHeader { - let receiver = crate::value::JSValue::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn regex_instance_or_throw( + this: crate::closure::JsThis, + method: &str, +) -> *const crate::regex::RegExpHeader { + let receiver = crate::value::JSValue::from_bits(this.bits()); if receiver.is_pointer() { let ptr = receiver.as_pointer::() as usize; if crate::regex::is_registered_regex(ptr) { diff --git a/crates/perry-runtime/src/object/string_proto_thunks.rs b/crates/perry-runtime/src/object/string_proto_thunks.rs index b00de6314a..a931fffb58 100644 --- a/crates/perry-runtime/src/object/string_proto_thunks.rs +++ b/crates/perry-runtime/src/object/string_proto_thunks.rs @@ -163,7 +163,7 @@ fn install_generic_string_proto_methods(proto_obj: *mut ObjectHeader) { /// own method name off the closure. Args arrive as a rest array. pub(super) extern "C" fn string_proto_generic_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { // Copy the method name to an owned String — `string_this_or_throw` may run @@ -174,7 +174,7 @@ pub(super) extern "C" fn string_proto_generic_thunk( let name: String = unsafe { super::has_own_helpers::str_from_string_header(name_hdr) } .map(|s| s.to_string()) .unwrap_or_default(); - let s = string_this_or_throw(&name); + let s = string_this_or_throw(this, &name); let s_val = f64::from_bits(crate::value::JSValue::string_ptr(s).bits()); let args = super::global_this::global_this_rest_array_values(rest); let (args_ptr, args_len) = if args.is_empty() { @@ -253,8 +253,11 @@ fn throw_string_proto_nullish(method: &str) -> ! { /// everything else (a primitive string, a boxed `String`/`Boolean`/`Number` /// object, a `{ toString }` object) coerces via the shared `js_string_coerce`, /// which runs user `toString`/`valueOf`. -fn string_this_or_throw(method: &str) -> *mut crate::string::StringHeader { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn string_this_or_throw( + this: crate::closure::JsThis, + method: &str, +) -> *mut crate::string::StringHeader { + let this = f64::from_bits(this.bits()); let jv = crate::value::JSValue::from_bits(this.to_bits()); if jv.is_undefined() || jv.is_null() { throw_string_proto_nullish(method); @@ -271,10 +274,10 @@ fn string_this_or_throw(method: &str) -> *mut crate::string::StringHeader { pub(super) extern "C" fn string_proto_char_at_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, pos: f64, ) -> f64 { - let s = string_this_or_throw("charAt"); + let s = string_this_or_throw(this, "charAt"); let idx = crate::string::js_string_index_to_i32(pos); let r = crate::string::js_string_char_at(s, idx); f64::from_bits(crate::value::JSValue::string_ptr(r).bits()) @@ -282,97 +285,93 @@ pub(super) extern "C" fn string_proto_char_at_thunk( pub(super) extern "C" fn string_proto_char_code_at_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, pos: f64, ) -> f64 { - let s = string_this_or_throw("charCodeAt"); + let s = string_this_or_throw(this, "charCodeAt"); let idx = crate::string::js_string_index_to_i32(pos); crate::string::js_string_char_code_at(s, idx) } pub(super) extern "C" fn string_proto_code_point_at_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, pos: f64, ) -> f64 { - let s = string_this_or_throw("codePointAt"); + let s = string_this_or_throw(this, "codePointAt"); let idx = crate::string::js_string_index_to_i32(pos); crate::string::js_string_code_point_at(s, idx) } pub(super) extern "C" fn string_proto_at_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, index: f64, ) -> f64 { - let s = string_this_or_throw("at"); + let s = string_this_or_throw(this, "at"); let idx = crate::string::js_string_index_to_i32(index); crate::string::js_string_at(s, idx) } pub(super) extern "C" fn string_proto_symbol_iterator_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let s = string_this_or_throw("[Symbol.iterator]"); + let s = string_this_or_throw(this, "[Symbol.iterator]"); crate::string::string_values_iter(s) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_match_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - crate::regex::js_string_match_js(crate::object::js_implicit_this_get(), arg) + crate::regex::js_string_match_js(this.as_f64(), arg) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_search_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - crate::regex::js_string_search_js(crate::object::js_implicit_this_get(), arg) + crate::regex::js_string_search_js(this.as_f64(), arg) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_match_all_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - crate::regex::js_string_match_all_js(crate::object::js_implicit_this_get(), arg) + crate::regex::js_string_match_all_js(this.as_f64(), arg) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_replace_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, search: f64, replacement: f64, ) -> f64 { - crate::regex::js_string_replace_js(crate::object::js_implicit_this_get(), search, replacement) + crate::regex::js_string_replace_js(this.as_f64(), search, replacement) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_replace_all_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, search: f64, replacement: f64, ) -> f64 { - crate::regex::js_string_replace_all_js( - crate::object::js_implicit_this_get(), - search, - replacement, - ) + crate::regex::js_string_replace_all_js(this.as_f64(), search, replacement) } #[cfg(feature = "regex-engine")] extern "C" fn string_proto_split_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, separator: f64, limit: f64, ) -> f64 { - crate::regex::js_string_split_js(crate::object::js_implicit_this_get(), separator, limit) + crate::regex::js_string_split_js(this.as_f64(), separator, limit) } diff --git a/crates/perry-runtime/src/object/temporal_proto.rs b/crates/perry-runtime/src/object/temporal_proto.rs index 805a065c3f..42b2604c98 100644 --- a/crates/perry-runtime/src/object/temporal_proto.rs +++ b/crates/perry-runtime/src/object/temporal_proto.rs @@ -48,9 +48,9 @@ fn throw_brand(member: &str) -> ! { /// `"hour"`), and routes to the brand router; a non-Temporal `this` throws. pub(super) extern "C" fn temporal_proto_getter_thunk( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let recv = f64::from_bits(IMPLICIT_THIS.with(|x| x.get())); + let recv = f64::from_bits(this.bits()); let full = closure_name(c); let prop = full.strip_prefix("get ").unwrap_or(&full); match tdispatch::get_property(recv, prop) { @@ -64,10 +64,10 @@ pub(super) extern "C" fn temporal_proto_getter_thunk( /// receiver, and routes to the brand router with the rest-array args. pub(super) extern "C" fn temporal_proto_method_thunk( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let recv = f64::from_bits(IMPLICIT_THIS.with(|x| x.get())); + let recv = f64::from_bits(this.bits()); let name = closure_name(c); if crate::temporal::temporal_kind(recv).is_none() { throw_brand(&name); diff --git a/crates/perry-runtime/src/object/this_binding.rs b/crates/perry-runtime/src/object/this_binding.rs index 738ec47437..c41fce4c9c 100644 --- a/crates/perry-runtime/src/object/this_binding.rs +++ b/crates/perry-runtime/src/object/this_binding.rs @@ -256,6 +256,12 @@ extern "C" fn this_witness_report() { #[used(compiler)] static KEEP_JS_THIS_PARAM_WITNESS: extern "C" fn(u64, *const u8, u64) = js_this_param_witness; +/// Keepalive anchor — `js_this_coerce_sloppy` is called only from generated +/// code (a sloppy body's receiver prologue). +#[cfg(feature = "keepalive-anchors")] +#[used(compiler)] +static KEEP_JS_THIS_COERCE_SLOPPY: extern "C" fn(f64) -> f64 = js_this_coerce_sloppy; + /// Read the current implicit `this` (issue #519). #[no_mangle] pub extern "C" fn js_implicit_this_get() -> f64 { @@ -265,7 +271,16 @@ pub extern "C" fn js_implicit_this_get() -> f64 { /// Read implicit `this` using ordinary (non-strict) function binding rules. #[no_mangle] pub extern "C" fn js_implicit_this_get_sloppy() -> f64 { - let value = js_implicit_this_get(); + js_this_coerce_sloppy(js_implicit_this_get()) +} + +/// OrdinaryCallBindThis for a non-strict body: `undefined`/`null` become +/// `globalThis`, a boolean/string/number its wrapper object; objects and +/// class refs are unchanged. Emitted at the entry of a sloppy body that reads +/// its receiver (the `this` parameter, `perry_abi::JS_BODY_THIS_PARAM`) — +/// only on the non-object path. Can allocate: a GC safepoint. +#[no_mangle] +pub extern "C" fn js_this_coerce_sloppy(value: f64) -> f64 { let jv = crate::value::JSValue::from_bits(value.to_bits()); if jv.is_undefined() || jv.is_null() { return js_get_global_this(); diff --git a/crates/perry-runtime/src/object/weakref_proto_thunks.rs b/crates/perry-runtime/src/object/weakref_proto_thunks.rs index ee535c98d8..6a027983c9 100644 --- a/crates/perry-runtime/src/object/weakref_proto_thunks.rs +++ b/crates/perry-runtime/src/object/weakref_proto_thunks.rs @@ -245,8 +245,13 @@ fn throw_incompatible(proto: &str, method: &str) -> ! { /// Resolve `IMPLICIT_THIS` to a receiver of the expected weak-wrapper class id, /// or throw a `TypeError`. Mirrors `collection_proto_thunks`' /// `weak_receiver_or_throw` for the WeakRef/FinalizationRegistry pair. -fn wrapper_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { - let receiver = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn wrapper_receiver_or_throw( + this: crate::closure::JsThis, + expected: u32, + proto: &str, + method: &str, +) -> f64 { + let receiver = f64::from_bits(this.bits()); if is_weak_wrapper(receiver, expected) { receiver } else { @@ -256,20 +261,21 @@ fn wrapper_receiver_or_throw(expected: u32, proto: &str, method: &str) -> f64 { pub(super) extern "C" fn weakref_proto_deref_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let r = wrapper_receiver_or_throw(CLASS_ID_WEAKREF, "WeakRef.prototype", "deref"); + let r = wrapper_receiver_or_throw(this, CLASS_ID_WEAKREF, "WeakRef.prototype", "deref"); js_weakref_deref(r) } pub(super) extern "C" fn finreg_proto_register_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, target: f64, held: f64, token: f64, ) -> f64 { let r = wrapper_receiver_or_throw( + this, CLASS_ID_FINALIZATION_REGISTRY, "FinalizationRegistry.prototype", "register", @@ -279,10 +285,11 @@ pub(super) extern "C" fn finreg_proto_register_thunk( pub(super) extern "C" fn finreg_proto_unregister_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, token: f64, ) -> f64 { let r = wrapper_receiver_or_throw( + this, CLASS_ID_FINALIZATION_REGISTRY, "FinalizationRegistry.prototype", "unregister", diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index c340f46521..1f3c4e5595 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -110,10 +110,10 @@ extern "C" fn process_stream_on_once_stub( /// Node's `this`-returning contract. Encoding-aware reads remain future work. extern "C" fn process_stream_set_encoding_stub( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } /// #3962: set when a TUI tears down stdin via `process.stdin.destroy()` or @@ -178,11 +178,11 @@ extern "C" fn process_stdin_detach_stub( /// delivery (#9676). Node's contract: an unref'd stdin still emits `'data'`. extern "C" fn process_stdin_unref_stub( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_UNREFED.store(true, std::sync::atomic::Ordering::Release); - crate::object::js_implicit_this_get() + this.as_f64() } /// `process.stdin.ref()` — restore the event-loop hold (#9676). Was a no-op @@ -193,11 +193,11 @@ extern "C" fn process_stdin_unref_stub( /// stream. `resume()` remains the one call that restarts a stopped reader. extern "C" fn process_stdin_ref_stub( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_UNREFED.store(false, std::sync::atomic::Ordering::Release); - crate::object::js_implicit_this_get() + this.as_f64() } thread_local! { @@ -849,7 +849,7 @@ pub fn enable_process_stdin_keypress_events(callback: i64) { /// `process.stdin.addListener(event, cb)` / `.on(...)` reached as an object method. extern "C" fn process_stdin_add_listener( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -861,18 +861,13 @@ extern "C" fn process_stdin_add_listener( } return stdin_this_value(); } - process_stdin_on( - closure, - crate::closure::body_call::current_this(), - event, - callback, - ) + process_stdin_on(closure, this, event, callback) } /// `process.stdin.once(event, cb)` reached as an object method. extern "C" fn process_stdin_add_listener_once( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -884,12 +879,7 @@ extern "C" fn process_stdin_add_listener_once( } return stdin_this_value(); } - process_stdin_once( - closure, - crate::closure::body_call::current_this(), - event, - callback, - ) + process_stdin_once(closure, this, event, callback) } /// `process.stdin.removeListener(event, cb)` / `.off(...)`. @@ -1218,7 +1208,7 @@ fn register_generic_stdin_keypress_listener(event: f64, callback: f64, once: boo /// listener and starts the reader. Returns `this` so callers can chain. extern "C" fn process_stdin_on( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1252,13 +1242,13 @@ extern "C" fn process_stdin_on( _ => {} } } - crate::object::js_implicit_this_get() + this.as_f64() } /// `process.stdin.once(event, cb)` — fires the listener exactly once. extern "C" fn process_stdin_once( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { @@ -1290,7 +1280,7 @@ extern "C" fn process_stdin_once( _ => {} } } - crate::object::js_implicit_this_get() + this.as_f64() } /// `process.stdin.read([size])` — returns buffered input as a string (stdin is @@ -1347,7 +1337,7 @@ pub fn reset_process_stdin_liveness_for_tests() { /// paused stdin can resume. extern "C" fn process_stdin_resume( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { STDIN_DETACHED.store(false, std::sync::atomic::Ordering::Release); @@ -1361,7 +1351,7 @@ extern "C" fn process_stdin_resume( resume(); } ensure_stdin_reader(); - crate::object::js_implicit_this_get() + this.as_f64() } /// Drain buffered stdin and fire `data`/`readable` listeners. Called once per diff --git a/crates/perry-runtime/src/perf_hooks/prototypes.rs b/crates/perry-runtime/src/perf_hooks/prototypes.rs index 28d926c217..b2a3f3d430 100644 --- a/crates/perry-runtime/src/perf_hooks/prototypes.rs +++ b/crates/perry-runtime/src/perf_hooks/prototypes.rs @@ -30,10 +30,10 @@ fn invalid_perf_receiver(class_name: &str) -> ! { extern "C" fn perf_entry_field_getter_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { unsafe { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let Some(obj) = as_object_ptr(this) else { invalid_perf_receiver("PerformanceEntry"); }; @@ -57,10 +57,10 @@ extern "C" fn perf_entry_field_getter_thunk( extern "C" fn perf_entry_to_json_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { unsafe { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let Some(obj) = as_object_ptr(this) else { invalid_perf_receiver("PerformanceEntry"); }; @@ -93,10 +93,10 @@ pub(crate) fn perf_supported_entry_types_value() -> f64 { extern "C" fn perf_observer_observe_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, options: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_value(this) { invalid_perf_receiver("PerformanceObserver"); } @@ -105,9 +105,9 @@ extern "C" fn perf_observer_observe_thunk( extern "C" fn perf_observer_disconnect_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_value(this) { invalid_perf_receiver("PerformanceObserver"); } @@ -116,9 +116,9 @@ extern "C" fn perf_observer_disconnect_thunk( extern "C" fn perf_observer_take_records_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_value(this) { invalid_perf_receiver("PerformanceObserver"); } @@ -127,9 +127,9 @@ extern "C" fn perf_observer_take_records_thunk( extern "C" fn perf_list_get_entries_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_list_value(this) { invalid_perf_receiver("PerformanceObserverEntryList"); } @@ -138,10 +138,10 @@ extern "C" fn perf_list_get_entries_thunk( extern "C" fn perf_list_get_by_type_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, entry_type: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_list_value(this) { invalid_perf_receiver("PerformanceObserverEntryList"); } @@ -150,10 +150,10 @@ extern "C" fn perf_list_get_by_type_thunk( extern "C" fn perf_list_get_by_name_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); if !is_perf_observer_list_value(this) { invalid_perf_receiver("PerformanceObserverEntryList"); } diff --git a/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs b/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs index ffc9bbcb70..9ade00bac3 100644 --- a/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs +++ b/crates/perry-runtime/src/perf_hooks/prototypes/performance_methods.rs @@ -2,124 +2,124 @@ use super::*; -fn require_performance_receiver() { - if !is_performance_namespace_value(crate::object::js_implicit_this_get()) { +fn require_performance_receiver(this: crate::closure::JsThis) { + if !is_performance_namespace_value(this.as_f64()) { invalid_perf_receiver("Performance"); } } pub(super) extern "C" fn clear_marks( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_clear_marks(name) } pub(super) extern "C" fn clear_measures( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_clear_measures(name) } pub(super) extern "C" fn clear_resource_timings( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_clear_resource_timings() } pub(super) extern "C" fn get_entries( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_get_entries() } pub(super) extern "C" fn get_entries_by_name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, entry_type: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_get_entries_by_name(name, entry_type) } pub(super) extern "C" fn get_entries_by_type( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, entry_type: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_get_entries_by_type(entry_type) } pub(super) extern "C" fn mark( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, options: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_mark(name, options) } pub(super) extern "C" fn measure( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, name: f64, start_or_options: f64, end: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_measure(name, start_or_options, end) } pub(super) extern "C" fn now( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); crate::date::js_performance_now() } pub(super) extern "C" fn set_resource_timing_buffer_size( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, size: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_set_resource_timing_buffer_size(size) } pub(super) extern "C" fn to_json( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_to_json() } pub(super) extern "C" fn event_loop_utilization( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, utilization1: f64, utilization2: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_event_loop_utilization(utilization1, utilization2) } pub(super) extern "C" fn mark_resource_timing( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, timing_info: f64, requested_url: f64, initiator_type: f64, @@ -129,7 +129,7 @@ pub(super) extern "C" fn mark_resource_timing( response_status: f64, delivery_type: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_mark_resource_timing( timing_info, requested_url, @@ -144,10 +144,10 @@ pub(super) extern "C" fn mark_resource_timing( pub(super) extern "C" fn timerify( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, function: f64, options: f64, ) -> f64 { - require_performance_receiver(); + require_performance_receiver(this); js_perf_timerify(function, options) } diff --git a/crates/perry-runtime/src/process/node_module.rs b/crates/perry-runtime/src/process/node_module.rs index 91245dbce2..6e56d0516a 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -57,15 +57,15 @@ pub extern "C" fn js_module_constants() -> f64 { extern "C" fn module_require_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, specifier: f64, ) -> f64 { - js_module_instance_require(specifier) + js_module_instance_require(this, specifier) } -pub(crate) fn js_module_instance_require(specifier: f64) -> f64 { +pub(crate) fn js_module_instance_require(this: crate::closure::JsThis, specifier: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); let specifier = scope.root_nanbox_f64(specifier); let Some(_) = module_object_ptr(receiver.get_nanbox_f64()) else { module_throw_plain_type_error("Module.prototype.require called on incompatible receiver"); @@ -444,8 +444,8 @@ pub extern "C" fn js_module_load(request: f64, _parent: f64, _is_main: f64) -> f module_undefined() } -pub extern "C" fn js_module_instance_load(filename: f64) -> f64 { - let receiver = crate::object::js_implicit_this_get(); +pub extern "C" fn js_module_instance_load(this: crate::closure::JsThis, filename: f64) -> f64 { + let receiver = this.as_f64(); let scope = crate::gc::RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(receiver); let Some(_) = module_object_ptr(receiver.get_nanbox_f64()) else { 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 5ba92d6592..79903ac512 100644 --- a/crates/perry-runtime/src/process/node_module/source_map.rs +++ b/crates/perry-runtime/src/process/node_module/source_map.rs @@ -99,9 +99,9 @@ fn source_map_method(name: &str, thunk: SourceMapThunk) -> f64 { extern "C" fn source_map_payload_getter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = source_map_receiver(); + let obj = source_map_receiver(this); let scope = crate::gc::RuntimeHandleScope::new(); let payload = scope.root_nanbox_f64(f64::from_bits( crate::object::js_object_get_field(obj, 0).bits(), @@ -111,9 +111,9 @@ extern "C" fn source_map_payload_getter( extern "C" fn source_map_line_lengths_getter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = source_map_receiver(); + let obj = source_map_receiver(this); let scope = crate::gc::RuntimeHandleScope::new(); let value = scope.root_nanbox_f64(f64::from_bits( crate::object::js_object_get_field(obj, 1).bits(), @@ -144,8 +144,8 @@ fn source_map_getter(name: &str, thunk: crate::closure::body_call::js_body_fn_ty crate::value::js_nanbox_pointer(closure as i64) } -fn source_map_receiver() -> *const crate::object::ObjectHeader { - let receiver = crate::object::js_implicit_this_get(); +fn source_map_receiver(this: crate::closure::JsThis) -> *const crate::object::ObjectHeader { + let receiver = this.as_f64(); let Some(obj) = module_object_ptr(receiver) else { module_throw_plain_type_error("Receiver must be an instance of SourceMap"); }; @@ -607,11 +607,11 @@ fn source_map_origin_object( /// originalColumn, name? }`. Returns `{}` when no entry precedes the query. extern "C" fn source_map_find_entry_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { let _ = closure; - let receiver = source_map_receiver(); + let receiver = source_map_receiver(this); let scope = crate::gc::RuntimeHandleScope::new(); let payload = scope.root_nanbox_f64(f64::from_bits( crate::object::js_object_get_field(receiver, 0).bits(), @@ -659,11 +659,11 @@ extern "C" fn source_map_find_entry_thunk( /// which Node returns an empty object. extern "C" fn source_map_find_origin_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { let _ = closure; - let receiver = source_map_receiver(); + let receiver = source_map_receiver(this); let scope = crate::gc::RuntimeHandleScope::new(); let payload = scope.root_nanbox_f64(f64::from_bits( crate::object::js_object_get_field(receiver, 0).bits(), diff --git a/crates/perry-runtime/src/promise/then.rs b/crates/perry-runtime/src/promise/then.rs index 54b070a421..c6411c50a2 100644 --- a/crates/perry-runtime/src/promise/then.rs +++ b/crates/perry-runtime/src/promise/then.rs @@ -1481,12 +1481,12 @@ fn ensure_spec_finally_arities_registered() { pub(crate) extern "C" fn promise_prototype_then_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, on_fulfilled: f64, on_rejected: f64, ) -> f64 { ensure_spec_finally_arities_registered(); - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let promise = if js_value_is_promise(receiver) != 0 { crate::value::js_nanbox_get_pointer(receiver) as *mut Promise } else if let Some(backing) = super::subclass::subclass_backing_promise(receiver) { @@ -1538,10 +1538,10 @@ pub(crate) extern "C" fn promise_prototype_then_thunk( pub(crate) extern "C" fn promise_prototype_catch_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, on_rejected: f64, ) -> f64 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); let args = [f64::from_bits(crate::value::TAG_UNDEFINED), on_rejected]; call_receiver_then(receiver, &args) } @@ -1552,13 +1552,13 @@ pub(crate) extern "C" fn promise_prototype_catch_thunk( pub(crate) extern "C" fn promise_prototype_finally_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, on_finally: f64, ) -> f64 { use crate::closure::{js_closure_alloc, js_closure_set_capture_f64}; ensure_spec_finally_arities_registered(); - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); // receiver must be a JS Object — pointer-tagged, not a registered symbol, not a handle. if !is_promise_species_object(receiver) { diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index cba24827bd..6334267c5c 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -144,7 +144,7 @@ mod platform_impl { closure: *const ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); let target = cp_get_field(this, b"__ptyTarget"); let cb = cp_get_field(this, b"__ptyCb"); if let Some(event) = cp_value_to_string(cp_get_field(this, b"__ptyEvent")) { @@ -174,7 +174,7 @@ mod platform_impl { _this: crate::closure::JsThis, cb: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); pty_register(this, "data", cb); pty_make_disposable(this, "data", cb) } @@ -184,7 +184,7 @@ mod platform_impl { _this: crate::closure::JsThis, cb: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); pty_register(this, "exit", cb); pty_make_disposable(this, "exit", cb) } @@ -194,7 +194,7 @@ mod platform_impl { _this: crate::closure::JsThis, data: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(handle) = pty_handle_of(this) { let bytes = cp_value_to_bytes(data); if !bytes.is_empty() { @@ -210,7 +210,7 @@ mod platform_impl { cols: f64, rows: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); let cols_i = pty_arg_i32(cols); let rows_i = pty_arg_i32(rows); if cols_i <= 0 || rows_i <= 0 || cols_i > u16::MAX as i32 || rows_i > u16::MAX as i32 { @@ -234,7 +234,7 @@ mod platform_impl { _this: crate::closure::JsThis, signal: f64, ) -> f64 { - let this = cp_this(closure); + let this = cp_this(_this, closure); if let Some(handle) = pty_handle_of(this) { reactor::pty_live_kill(handle, pty_parse_kill_signal(signal)); } @@ -243,9 +243,9 @@ mod platform_impl { extern "C" fn pty_method_pause( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if let Some(handle) = pty_handle_of(cp_this(closure)) { + if let Some(handle) = pty_handle_of(cp_this(this, closure)) { reactor::pty_live_set_paused(handle, true); } cp_undefined() @@ -253,9 +253,9 @@ mod platform_impl { extern "C" fn pty_method_resume( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if let Some(handle) = pty_handle_of(cp_this(closure)) { + if let Some(handle) = pty_handle_of(cp_this(this, closure)) { reactor::pty_live_set_paused(handle, false); } cp_undefined() @@ -266,14 +266,10 @@ mod platform_impl { /// kill with the default SIGHUP. extern "C" fn pty_method_dispose( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - pty_method_resume(closure, crate::closure::body_call::current_this()); - pty_method_kill( - closure, - crate::closure::body_call::current_this(), - cp_undefined(), - ) + pty_method_resume(closure, this); + pty_method_kill(closure, this, cp_undefined()) } /// `kill([signal])` — node-pty defaults to `SIGHUP` (a hangup is how a diff --git a/crates/perry-runtime/src/regex/match_all.rs b/crates/perry-runtime/src/regex/match_all.rs index 4ad2297fd3..f9aa3e2a55 100644 --- a/crates/perry-runtime/src/regex/match_all.rs +++ b/crates/perry-runtime/src/regex/match_all.rs @@ -141,10 +141,10 @@ pub(crate) fn regexp(receiver: f64, argument: f64) -> Result { pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, argument: f64, ) -> f64 { - api::finish(regexp(crate::object::js_implicit_this_get(), argument)) + api::finish(regexp(this.as_f64(), argument)) } fn string(receiver: f64, pattern: f64) -> Result { diff --git a/crates/perry-runtime/src/regex/perex_dispatch.rs b/crates/perry-runtime/src/regex/perex_dispatch.rs index 8f1e755a6f..7f0027d372 100644 --- a/crates/perry-runtime/src/regex/perex_dispatch.rs +++ b/crates/perry-runtime/src/regex/perex_dispatch.rs @@ -375,12 +375,16 @@ pub(crate) fn test_string(receiver: f64, input: *const StringHeader) -> Result Result { +pub(crate) fn test_value( + this: crate::closure::JsThis, + receiver: f64, + argument: f64, +) -> Result { require_object(receiver)?; let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(receiver); let argument = scope.root_nanbox_f64(argument); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let previous = scope.root_nanbox_f64(this.as_f64()); let input = api::caught(|| crate::value::js_jsvalue_to_string_coerce(argument.get_nanbox_f64())); crate::object::js_implicit_this_set(previous.get_nanbox_f64()); diff --git a/crates/perry-runtime/src/regex/perex_match_search.rs b/crates/perry-runtime/src/regex/perex_match_search.rs index 5c0ee32b7b..133ddda4b2 100644 --- a/crates/perry-runtime/src/regex/perex_match_search.rs +++ b/crates/perry-runtime/src/regex/perex_match_search.rs @@ -356,23 +356,15 @@ pub(crate) fn string( pub(crate) extern "C" fn match_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - api::finish(regexp( - Operation::Match, - crate::object::js_implicit_this_get(), - arg, - )) + api::finish(regexp(Operation::Match, this.as_f64(), arg)) } pub(crate) extern "C" fn search_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - api::finish(regexp( - Operation::Search, - crate::object::js_implicit_this_get(), - arg, - )) + api::finish(regexp(Operation::Search, this.as_f64(), arg)) } diff --git a/crates/perry-runtime/src/regex/perex_replace.rs b/crates/perry-runtime/src/regex/perex_replace.rs index bc21b07d78..911dfabcd4 100644 --- a/crates/perry-runtime/src/regex/perex_replace.rs +++ b/crates/perry-runtime/src/regex/perex_replace.rs @@ -328,15 +328,11 @@ pub(crate) fn string( pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, replacement: f64, ) -> f64 { - api::finish(regexp( - crate::object::js_implicit_this_get(), - input, - replacement, - )) + api::finish(regexp(this.as_f64(), input, replacement)) } #[no_mangle] diff --git a/crates/perry-runtime/src/regex/perex_split.rs b/crates/perry-runtime/src/regex/perex_split.rs index 1cb37aa06b..3289e8ba3c 100644 --- a/crates/perry-runtime/src/regex/perex_split.rs +++ b/crates/perry-runtime/src/regex/perex_split.rs @@ -608,11 +608,11 @@ fn string_via_engine(receiver: f64, separator: f64, limit_value: f64) -> Result< pub(crate) extern "C" fn regexp_thunk( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, limit: f64, ) -> f64 { - api::finish(regexp(crate::object::js_implicit_this_get(), input, limit)) + api::finish(regexp(this.as_f64(), input, limit)) } #[no_mangle] diff --git a/crates/perry-runtime/src/regex/site_test.rs b/crates/perry-runtime/src/regex/site_test.rs index 8d98e75250..e50b6af7fd 100644 --- a/crates/perry-runtime/src/regex/site_test.rs +++ b/crates/perry-runtime/src/regex/site_test.rs @@ -682,12 +682,12 @@ mod tests { #[inline(never)] extern "C" fn nested_factory_wrapper( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { // Keep this observably distinct from `exact_factory` under release // function merging while modeling a non-literal wrapper with effects. NESTED_WRAPPER_CALLS.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - exact_factory(std::ptr::null(), crate::closure::body_call::current_this()) + exact_factory(std::ptr::null(), this) } fn closure(function: *const u8) -> f64 { diff --git a/crates/perry-runtime/src/text.rs b/crates/perry-runtime/src/text.rs index 227d271af6..b8127ff02f 100644 --- a/crates/perry-runtime/src/text.rs +++ b/crates/perry-runtime/src/text.rs @@ -804,10 +804,10 @@ fn require_text_brand(value: f64, class_id: u32, message: &[u8]) { #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_decode_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_DECODER_CLASS_ID, @@ -820,9 +820,9 @@ pub(crate) extern "C" fn text_decoder_decode_thunk( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_encoding_getter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_DECODER_CLASS_ID, @@ -835,9 +835,9 @@ pub(crate) extern "C" fn text_decoder_encoding_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_fatal_getter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_DECODER_CLASS_ID, @@ -849,9 +849,9 @@ pub(crate) extern "C" fn text_decoder_fatal_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_decoder_ignore_bom_getter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_DECODER_CLASS_ID, @@ -863,9 +863,9 @@ pub(crate) extern "C" fn text_decoder_ignore_bom_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encoding_getter( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_ENCODER_CLASS_ID, @@ -878,10 +878,10 @@ pub(crate) extern "C" fn text_encoder_encoding_getter( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encode_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_ENCODER_CLASS_ID, @@ -893,11 +893,11 @@ pub(crate) extern "C" fn text_encoder_encode_thunk( #[cfg(feature = "global-text")] pub(crate) extern "C" fn text_encoder_encode_into_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, source: f64, dest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); require_text_brand( this, TEXT_ENCODER_CLASS_ID, diff --git a/crates/perry-runtime/src/timer/handle_object.rs b/crates/perry-runtime/src/timer/handle_object.rs index d5cf6936b6..990a7637a0 100644 --- a/crates/perry-runtime/src/timer/handle_object.rs +++ b/crates/perry-runtime/src/timer/handle_object.rs @@ -142,8 +142,8 @@ fn throw_timer_type_error(message: &[u8]) -> ! { /// So a foreign receiver is answered, not refused — the opposite of the text /// family, whose WebIDL accessors throw. Each thunk below returns node's answer /// for `None` and never touches timer state in that case. -fn timer_receiver() -> (f64, Option) { - let this = crate::object::js_implicit_this_get(); +fn timer_receiver(this: crate::closure::JsThis) -> (f64, Option) { + let this = this.as_f64(); let id = timer_handle_id(this); (this, id) } @@ -152,7 +152,7 @@ extern "C" fn timer_proto_ref_thunk( _c: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let (this, id) = timer_receiver(); + let (this, id) = timer_receiver(_this); if let Some(id) = id { js_timer_ref(id); } @@ -163,7 +163,7 @@ extern "C" fn timer_proto_unref_thunk( _c: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let (this, id) = timer_receiver(); + let (this, id) = timer_receiver(_this); if let Some(id) = id { js_timer_unref(id); } @@ -172,9 +172,9 @@ extern "C" fn timer_proto_unref_thunk( extern "C" fn timer_proto_has_ref_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let (_, id) = timer_receiver(); + let (_, id) = timer_receiver(this); match id { Some(id) if js_timer_has_ref(id) != 0 => { f64::from_bits(crate::value::JSValue::bool(true).bits()) @@ -189,7 +189,7 @@ extern "C" fn timer_proto_refresh_thunk( _c: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let (this, id) = timer_receiver(); + let (this, id) = timer_receiver(_this); if let Some(id) = id { js_timer_refresh(id); } @@ -309,7 +309,7 @@ extern "C" fn timer_proto_close_thunk( _c: *const crate::closure::ClosureHeader, _this: crate::closure::JsThis, ) -> f64 { - let (this, id) = timer_receiver(); + let (this, id) = timer_receiver(_this); if let Some(id) = id { clear_every_kind(id); } @@ -319,9 +319,9 @@ extern "C" fn timer_proto_close_thunk( /// `t[Symbol.dispose]()` — `using t = setTimeout(...)` clears the timer (#1213). extern "C" fn timer_proto_dispose_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let (_, id) = timer_receiver(); + let (_, id) = timer_receiver(this); if let Some(id) = id { clear_every_kind(id); } @@ -333,10 +333,10 @@ extern "C" fn timer_proto_dispose_thunk( /// conversion, so `+setImmediate(...)` must stay `NaN` (#10542). extern "C" fn timer_proto_to_primitive_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _hint: f64, ) -> f64 { - let (_, id) = timer_receiver(); + let (_, id) = timer_receiver(this); match id { Some(id) => id as f64, None => f64::from_bits(crate::value::TAG_UNDEFINED), diff --git a/crates/perry-runtime/src/tui/handle_object.rs b/crates/perry-runtime/src/tui/handle_object.rs index 55b7074039..30fde83188 100644 --- a/crates/perry-runtime/src/tui/handle_object.rs +++ b/crates/perry-runtime/src/tui/handle_object.rs @@ -227,8 +227,8 @@ fn tui_handle_parts_value(value: f64) -> Option<(TuiKind, i64)> { /// timer methods, and the one that cannot turn a working program into a /// throwing one. It is never read as an id of this kind: that is what the /// class-id brand prevents. -fn receiver_id(kind: TuiKind) -> Option { - let this = crate::object::js_implicit_this_get(); +fn receiver_id(this: crate::closure::JsThis, kind: TuiKind) -> Option { + let this = this.as_f64(); match tui_handle_parts_value(this) { Some((k, id)) if k == kind => Some(id), _ => None, @@ -239,9 +239,9 @@ const UNDEFINED: u64 = crate::value::TAG_UNDEFINED; extern "C" fn state_proto_get_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match receiver_id(TuiKind::State) { + match receiver_id(this, TuiKind::State) { Some(id) => super::state::state_get_by_id(id), None => f64::from_bits(UNDEFINED), } @@ -249,10 +249,10 @@ extern "C" fn state_proto_get_thunk( extern "C" fn state_proto_set_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - if let Some(id) = receiver_id(TuiKind::State) { + if let Some(id) = receiver_id(this, TuiKind::State) { super::state::state_set_by_id(id, value); } f64::from_bits(UNDEFINED) @@ -260,9 +260,9 @@ extern "C" fn state_proto_set_thunk( extern "C" fn ref_proto_get_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match receiver_id(TuiKind::RefBox) { + match receiver_id(this, TuiKind::RefBox) { Some(id) => super::hooks::ref_get_by_id(id), None => f64::from_bits(UNDEFINED), } @@ -270,10 +270,10 @@ extern "C" fn ref_proto_get_thunk( extern "C" fn ref_proto_set_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - if let Some(id) = receiver_id(TuiKind::RefBox) { + if let Some(id) = receiver_id(this, TuiKind::RefBox) { super::hooks::ref_set_by_id(id, value); } f64::from_bits(UNDEFINED) @@ -281,9 +281,9 @@ extern "C" fn ref_proto_set_thunk( extern "C" fn app_proto_exit_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if receiver_id(TuiKind::App).is_some() { + if receiver_id(this, TuiKind::App).is_some() { super::input::EXIT_FLAG.store(true, Ordering::Release); } f64::from_bits(UNDEFINED) @@ -291,9 +291,9 @@ extern "C" fn app_proto_exit_thunk( extern "C" fn app_proto_wait_until_exit_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if receiver_id(TuiKind::App).is_some() { + if receiver_id(this, TuiKind::App).is_some() { super::hooks::wait_until_exit_blocking(); } f64::from_bits(UNDEFINED) @@ -301,10 +301,10 @@ extern "C" fn app_proto_wait_until_exit_thunk( extern "C" fn stdout_proto_write_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - if receiver_id(TuiKind::Stdout).is_some() { + if receiver_id(this, TuiKind::Stdout).is_some() { let s = crate::value::js_jsvalue_to_string_coerce(value); super::hooks::stdout_write_string_ptr(s); } @@ -313,9 +313,9 @@ extern "C" fn stdout_proto_write_thunk( extern "C" fn stdout_proto_columns_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match receiver_id(TuiKind::Stdout) { + match receiver_id(this, TuiKind::Stdout) { Some(_) => super::hooks::js_perry_tui_stdout_columns(0), None => f64::from_bits(UNDEFINED), } @@ -323,9 +323,9 @@ extern "C" fn stdout_proto_columns_thunk( extern "C" fn stdout_proto_rows_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match receiver_id(TuiKind::Stdout) { + match receiver_id(this, TuiKind::Stdout) { Some(_) => super::hooks::js_perry_tui_stdout_rows(0), None => f64::from_bits(UNDEFINED), } @@ -333,9 +333,9 @@ extern "C" fn stdout_proto_rows_thunk( extern "C" fn focus_proto_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if receiver_id(TuiKind::FocusManager).is_some() { + if receiver_id(this, TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus_next(); } f64::from_bits(UNDEFINED) @@ -343,9 +343,9 @@ extern "C" fn focus_proto_next_thunk( extern "C" fn focus_proto_previous_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - if receiver_id(TuiKind::FocusManager).is_some() { + if receiver_id(this, TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus_previous(); } f64::from_bits(UNDEFINED) @@ -353,10 +353,10 @@ extern "C" fn focus_proto_previous_thunk( extern "C" fn focus_proto_focus_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, id: f64, ) -> f64 { - if receiver_id(TuiKind::FocusManager).is_some() { + if receiver_id(this, TuiKind::FocusManager).is_some() { super::hooks::js_perry_tui_focus(id); } f64::from_bits(UNDEFINED) diff --git a/crates/perry-runtime/src/wasi.rs b/crates/perry-runtime/src/wasi.rs index 08cbb10b5a..5332041eb7 100644 --- a/crates/perry-runtime/src/wasi.rs +++ b/crates/perry-runtime/src/wasi.rs @@ -742,10 +742,10 @@ pub(crate) unsafe fn js_wasi_init_subclass(this_box: f64, options: f64) { #[no_mangle] pub extern "C" fn js_wasi_get_import_object( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let this = scope.root_nanbox_f64(this.as_f64()); if heap_object_ptr(this.get_nanbox_f64()).is_none() || !is_wasi_instance(this.get_nanbox_f64()) { let wrapper = scope.root_raw_mut_ptr(crate::object::js_object_alloc(0, 0)); @@ -1026,8 +1026,8 @@ fn finalize_memory_option(options: f64) -> f64 { object_field(options, b"memory") } -fn import_from_closure(closure: *const ClosureHeader) -> f64 { - let this = crate::object::js_implicit_this_get(); +fn import_from_closure(this: crate::closure::JsThis, closure: *const ClosureHeader) -> f64 { + let this = this.as_f64(); if let Some(obj) = heap_object_ptr(this) { if is_wasi_import_object(obj) { return this; @@ -1164,14 +1164,14 @@ fn import_function_name(closure: *const ClosureHeader) -> &'static str { #[no_mangle] pub extern "C" fn js_wasi_import_stub( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg0: f64, arg1: f64, arg2: f64, arg3: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let import = scope.root_nanbox_f64(import_from_closure(closure)); + let import = scope.root_nanbox_f64(import_from_closure(this, closure)); match import_function_name(closure).trim_start_matches("bound ") { "args_sizes_get" => { if !is_undefined(arg2) || argument(arg0).is_none() || argument(arg1).is_none() { diff --git a/crates/perry-runtime/src/web_storage.rs b/crates/perry-runtime/src/web_storage.rs index 4828dd70f6..3d0eaaf541 100644 --- a/crates/perry-runtime/src/web_storage.rs +++ b/crates/perry-runtime/src/web_storage.rs @@ -57,9 +57,9 @@ extern "C" fn storage_session_global_getter( extern "C" fn storage_length_getter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let Some(kind) = receiver_kind() else { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; with_store(kind, |store| store.len() as f64) @@ -67,10 +67,10 @@ extern "C" fn storage_length_getter( extern "C" fn storage_get_item( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let Some(kind) = receiver_kind() else { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; storage_get_item_impl(kind, key) @@ -78,11 +78,11 @@ extern "C" fn storage_get_item( extern "C" fn storage_set_item( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, value: f64, ) -> f64 { - let Some(kind) = receiver_kind() else { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; storage_set_item_impl(kind, key, value) @@ -90,10 +90,10 @@ extern "C" fn storage_set_item( extern "C" fn storage_remove_item( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, key: f64, ) -> f64 { - let Some(kind) = receiver_kind() else { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; storage_remove_item_impl(kind, key) @@ -101,17 +101,17 @@ extern "C" fn storage_remove_item( extern "C" fn storage_key( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, index: f64, ) -> f64 { - let Some(kind) = receiver_kind() else { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; storage_key_impl(kind, index) } -extern "C" fn storage_clear(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - let Some(kind) = receiver_kind() else { +extern "C" fn storage_clear(_closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + let Some(kind) = receiver_kind(this) else { return throw_type_error("Illegal invocation"); }; storage_clear_impl(kind) @@ -388,8 +388,8 @@ fn set_global_storage_property(global: *mut ObjectHeader, name: &str, value: *mu } } -fn receiver_kind() -> Option { - let this = crate::object::js_implicit_this_get(); +fn receiver_kind(this: crate::closure::JsThis) -> Option { + let this = this.as_f64(); storage_kind_from_value(this) } diff --git a/crates/perry-runtime/src/webassembly.rs b/crates/perry-runtime/src/webassembly.rs index 39a8c4c2cd..71ec7f104c 100644 --- a/crates/perry-runtime/src/webassembly.rs +++ b/crates/perry-runtime/src/webassembly.rs @@ -1080,8 +1080,8 @@ fn make_export_function( }) } -fn global_handle_from_receiver() -> Option<*mut c_void> { - let receiver = JSValue::from_bits(crate::object::js_implicit_this_get().to_bits()); +fn global_handle_from_receiver(this: crate::closure::JsThis) -> Option<*mut c_void> { + let receiver = JSValue::from_bits(this.as_f64().to_bits()); if !receiver.is_pointer() { return None; } @@ -1094,9 +1094,9 @@ fn global_handle_from_receiver() -> Option<*mut c_void> { extern "C" fn js_wasm_global_get( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let Some(handle) = global_handle_from_receiver() else { + let Some(handle) = global_handle_from_receiver(this) else { return nanbox_undefined(); }; let mut kind = WASM_VAL_KIND_NONE; @@ -1109,10 +1109,10 @@ extern "C" fn js_wasm_global_get( extern "C" fn js_wasm_global_set( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let Some(handle) = global_handle_from_receiver() else { + let Some(handle) = global_handle_from_receiver(this) else { return nanbox_undefined(); }; let mut kind = WASM_VAL_KIND_NONE; diff --git a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs index 3dc3e1f62f..d29886b708 100644 --- a/crates/perry-stdlib/src/common/dispatch/emitter_als.rs +++ b/crates/perry-stdlib/src/common/dispatch/emitter_als.rs @@ -48,7 +48,7 @@ fn async_local_storage_method_name_static(property: &str) -> Option<&'static [u8 extern "C" fn async_local_storage_unbound_method_thunk( closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, rest: f64, ) -> f64 { unsafe { @@ -58,7 +58,7 @@ extern "C" fn async_local_storage_unbound_method_thunk( let name_str = std::str::from_utf8(name).unwrap_or(""); let scope = perry_runtime::gc::RuntimeHandleScope::new(); let rest = scope.root_nanbox_f64(rest); - let receiver_handle = scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_get()); + let receiver_handle = scope.root_nanbox_f64(this.as_f64()); let receiver = receiver_handle.get_nanbox_f64(); let receiver_raw = if receiver.to_bits() >> 48 == 0x7FFD { (receiver.to_bits() & POINTER_MASK_BITS) as i64 diff --git a/crates/perry-stdlib/src/string_decoder.rs b/crates/perry-stdlib/src/string_decoder.rs index 1dc5345b64..7643000a5d 100644 --- a/crates/perry-stdlib/src/string_decoder.rs +++ b/crates/perry-stdlib/src/string_decoder.rs @@ -192,8 +192,8 @@ unsafe fn string_decoder_constructor_value() -> f64 { ) } -unsafe fn this_string_decoder_handle() -> i64 { - let this_value = perry_runtime::object::js_implicit_this_get(); +unsafe fn this_string_decoder_handle(this: perry_runtime::closure::JsThis) -> i64 { + let this_value = this.as_f64(); let bits = this_value.to_bits(); let handle = if bits >> 48 == 0x7FFD { (bits & 0x0000_FFFF_FFFF_FFFF) as i64 @@ -213,48 +213,48 @@ unsafe fn this_string_decoder_handle() -> i64 { extern "C" fn string_decoder_proto_write( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, buf: f64, ) -> f64 { - unsafe { dispatch_string_decoder(this_string_decoder_handle(), "write", &[buf]) } + unsafe { dispatch_string_decoder(this_string_decoder_handle(this), "write", &[buf]) } } extern "C" fn string_decoder_proto_end( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, buf: f64, ) -> f64 { - unsafe { dispatch_string_decoder(this_string_decoder_handle(), "end", &[buf]) } + unsafe { dispatch_string_decoder(this_string_decoder_handle(this), "end", &[buf]) } } extern "C" fn string_decoder_proto_text( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, buf: f64, _offset: f64, ) -> f64 { - unsafe { dispatch_string_decoder(this_string_decoder_handle(), "write", &[buf]) } + unsafe { dispatch_string_decoder(this_string_decoder_handle(this), "write", &[buf]) } } extern "C" fn string_decoder_last_char_getter( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, ) -> f64 { - unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastChar") } + unsafe { dispatch_string_decoder_property(this_string_decoder_handle(this), "lastChar") } } extern "C" fn string_decoder_last_need_getter( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, ) -> f64 { - unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastNeed") } + unsafe { dispatch_string_decoder_property(this_string_decoder_handle(this), "lastNeed") } } extern "C" fn string_decoder_last_total_getter( _closure: *const perry_runtime::closure::ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, ) -> f64 { - unsafe { dispatch_string_decoder_property(this_string_decoder_handle(), "lastTotal") } + unsafe { dispatch_string_decoder_property(this_string_decoder_handle(this), "lastTotal") } } pub unsafe fn string_decoder_prototype_value() -> f64 { diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index 60cc71eeda..305d5fb853 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -1066,16 +1066,11 @@ extern "C" fn worker_add_event_listener( /// `worker.removeEventListener(type, listener)`. extern "C" fn worker_remove_event_listener( closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { - worker_off( - closure, - perry_runtime::closure::current_this(), - event, - callback, - ) + worker_off(closure, this, event, callback) } fn worker_add_listener( diff --git a/crates/perry-stdlib/src/worker_threads/worker_surface.rs b/crates/perry-stdlib/src/worker_threads/worker_surface.rs index 2f22cc541c..c3aa68696d 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_surface.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_surface.rs @@ -384,7 +384,7 @@ fn stream_register(event: f64, callback: f64) -> f64 { this } -fn stream_emit_event(event: f64, arg: f64) -> f64 { +fn stream_emit_event(receiver: perry_runtime::closure::JsThis, event: f64, arg: f64) -> f64 { let this = stream_this(); let Some(event) = string_value_to_string(event) else { return js_bool(false); @@ -408,7 +408,7 @@ fn stream_emit_event(event: f64, arg: f64) -> f64 { let this_h = scope.root_nanbox_f64(this); let arr_h = scope.root_raw_mut_ptr(arr); let arg_h = scope.root_nanbox_f64(arg); - let prev_this = scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_get()); + let prev_this = scope.root_nanbox_f64(receiver.as_f64()); let len = perry_runtime::array::js_array_length(arr_h.get_raw_mut_ptr()); for i in 0..len { let callback = perry_runtime::array::js_array_get_f64(arr_h.get_raw_mut_ptr(), i); @@ -465,11 +465,11 @@ extern "C" fn stream_on( extern "C" fn stream_emit( _closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, event: f64, arg: f64, ) -> f64 { - stream_emit_event(event, arg) + stream_emit_event(this, event, arg) } extern "C" fn stream_off( From 0857f6d3e7af688a3ac2ed56f5ce25e41c7be9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 09:58:13 +0000 Subject: [PATCH 05/17] =?UTF-8?q?perf:=20this-as-a-parameter=20stage=203?= =?UTF-8?q?=20=E2=80=94=20delete=20the=20implicit-this=20cell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A body's `this` parameter is now the only way it learns its receiver, so no caller saves, sets or restores an ambient `this` around a call. - Deleted: the per-agent IMPLICIT_THIS cell, js_implicit_this_get / _get_sloppy / _set, ImplicitThisScope, its exception savepoint, its HotTls field and agent-pointer slot, the codegen save/restore funnel (rooting::implicit_this_*), and the stage-1 witness (PERRY_THIS_WITNESS). - The receiver-taking entries (js_closure_call{N}, js_native_call_value, js_closure_call_array, js_closure_call_apply_with_spread) pass their receiver and bind nothing; a plain call passes undefined (plain_call_receiver()). - Every runtime, stdlib and ext route that carried a receiver through the cell passes it: listeners get their emitter, util.promisify / callbackify / deprecate call the original with the wrapper's receiver, the legacy Intl.NumberFormat / DateTimeFormat chain sees its receiver, N-API napi_call_function passes recv. An accessor on an arguments object now runs with it as `this` (it never had it). - Async and generator function expressions bind their receiver at entry; module top-level `this` is undefined (strict) / globalThis (sloppy). - A method's entry-resolved callback target (js_closure_resolve_plain_direct_call) admits ordinary functions too. Instructions per call (LTO-off build, same host), main -> this change: closure value call 145 -> 46, two-argument closure call 160 -> 59, object-literal method-site hit 99 -> 79, object-literal method reading `this` 223 -> 120, ES5 prototype method 214 -> 110. --- changelog.d/perry-ffi-0.6-typed-js-bodies.md | 8 +- changelog.d/this-as-a-parameter-stage-3.md | 38 + crates/perry-abi/src/lib.rs | 19 +- crates/perry-codegen/src/codegen/artifacts.rs | 4 +- crates/perry-codegen/src/codegen/closure.rs | 33 +- crates/perry-codegen/src/codegen/entry.rs | 4 +- .../src/codegen/export_value_wrappers.rs | 9 - crates/perry-codegen/src/codegen/function.rs | 2 +- crates/perry-codegen/src/codegen/helpers.rs | 8 +- .../codegen/hoisted_callback_method_tests.rs | 10 +- .../src/codegen/literal_method_this_tests.rs | 7 +- crates/perry-codegen/src/codegen/method.rs | 8 +- .../src/codegen/method_static.rs | 6 +- .../src/collectors/hoisted_callback_calls.rs | 10 +- crates/perry-codegen/src/expr/body_call.rs | 66 +- crates/perry-codegen/src/expr/call_spread.rs | 5 +- crates/perry-codegen/src/expr/closure.rs | 41 +- crates/perry-codegen/src/expr/hot_tls.rs | 19 +- crates/perry-codegen/src/expr/method_site.rs | 14 +- crates/perry-codegen/src/expr/mod.rs | 6 +- .../perry-codegen/src/expr/object_literal.rs | 6 +- crates/perry-codegen/src/expr/super_method.rs | 14 +- .../perry-codegen/src/expr/this_super_call.rs | 22 +- crates/perry-codegen/src/gc_call_effects.rs | 27 +- .../perry-codegen/src/lower_call/atomics.rs | 8 +- .../src/lower_call/console_promise.rs | 120 +- .../src/lower_call/early_branches.rs | 96 +- .../perry-codegen/src/lower_call/func_ref.rs | 46 +- .../src/lower_call/method_override.rs | 13 +- crates/perry-codegen/src/lower_call/mod.rs | 17 +- .../src/lower_call/pipeline_call_tests.rs | 11 +- .../property_get/dynamic_dispatch.rs | 33 +- .../property_get/imported_object.rs | 9 +- .../property_get/static_dispatch.rs | 15 +- crates/perry-codegen/src/rooting/group.rs | 181 +-- crates/perry-codegen/src/rooting/ledger.rs | 5 + crates/perry-codegen/src/rooting/mod.rs | 15 +- crates/perry-codegen/src/rooting/temp_root.rs | 7 +- .../src/runtime_decls/objects.rs | 1 - .../runtime_decls/stdlib_ffi/language_core.rs | 3 - .../src/runtime_decls/strings.rs | 2 +- .../src/runtime_decls/strings_part2.rs | 4 - .../perry-codegen/src/wasm32/runtime_abi.tsv | 23 +- crates/perry-runtime/src/agent_ptrs.rs | 38 +- crates/perry-runtime/src/array/generic.rs | 65 +- .../perry-runtime/src/array/generic_object.rs | 3 +- .../perry-runtime/src/array/iter_methods.rs | 37 +- crates/perry-runtime/src/array/iterator.rs | 108 +- .../perry-runtime/src/array/reduce_right.rs | 6 +- crates/perry-runtime/src/array/sort.rs | 6 +- crates/perry-runtime/src/async_hooks.rs | 18 +- .../src/async_hooks/context_snapshots.rs | 12 +- .../src/async_hooks/provider_ffi.rs | 12 +- crates/perry-runtime/src/bun_compat/plugin.rs | 15 +- .../src/bun_compat/self_global.rs | 6 +- crates/perry-runtime/src/bun_compat/spawn.rs | 13 +- .../src/child_process/emitter.rs | 29 +- .../child_process/emitter/relocation_tests.rs | 4 +- crates/perry-runtime/src/closure/body_call.rs | 59 +- crates/perry-runtime/src/closure/dispatch.rs | 1 + .../src/closure/dispatch/bound.rs | 55 +- .../src/closure/dispatch/calln.rs | 1405 ++++++----------- .../src/closure/dispatch/direct.rs | 54 +- .../src/closure/dispatch/validate.rs | 9 +- .../src/closure/dispatch/value_call.rs | 540 +------ .../src/closure/dynamic_props.rs | 53 +- crates/perry-runtime/src/closure/mod.rs | 3 +- .../src/closure/receiver_param_tests.rs | 110 +- crates/perry-runtime/src/closure/registry.rs | 16 +- crates/perry-runtime/src/closure/unbox.rs | 2 +- crates/perry-runtime/src/closure/wide_call.rs | 47 +- crates/perry-runtime/src/cluster.rs | 26 +- crates/perry-runtime/src/collection_iter.rs | 12 +- crates/perry-runtime/src/dgram.rs | 22 +- crates/perry-runtime/src/dgram/thunks.rs | 116 +- crates/perry-runtime/src/disposable.rs | 20 +- crates/perry-runtime/src/dyn_eval/bridge.rs | 19 +- crates/perry-runtime/src/event_target.rs | 33 +- crates/perry-runtime/src/exception.rs | 21 +- .../perry-runtime/src/exception/savepoints.rs | 29 +- .../src/exception/savepoints/tests.rs | 4 - .../src/fs/dir_glob_watch/glob.rs | 2 - .../src/fs/dir_glob_watch/watch.rs | 31 +- .../src/fs/stream/write_file_input.rs | 7 +- crates/perry-runtime/src/gc/mod.rs | 13 +- .../src/gc/tests/runtime_roots.rs | 1 - .../runtime_roots/call_argument_lists.rs | 9 +- .../runtime_roots/implicit_this_scope.rs | 152 -- .../gc/tests/runtime_roots/perex_dispatch.rs | 40 +- .../gc/tests/runtime_roots/perex_match_all.rs | 37 +- .../tests/runtime_roots/perex_match_search.rs | 12 +- .../gc/tests/runtime_roots/perex_public.rs | 7 +- .../gc/tests/runtime_roots/perex_replace.rs | 25 +- .../src/gc/tests/runtime_roots/perex_split.rs | 76 +- .../runtime_roots/side_table_scanners.rs | 61 - crates/perry-runtime/src/intl.rs | 116 +- .../src/intl/constructor_thunks.rs | 96 ++ crates/perry-runtime/src/intl/ctor_guard.rs | 2 +- crates/perry-runtime/src/intl/subclass.rs | 10 +- crates/perry-runtime/src/iterator_helpers.rs | 27 +- .../src/iterator_helpers/tests.rs | 10 +- crates/perry-runtime/src/json/replacer.rs | 11 +- crates/perry-runtime/src/json/reviver.rs | 6 +- crates/perry-runtime/src/json/stringify.rs | 15 +- .../src/json/stringify_scalars.rs | 8 +- crates/perry-runtime/src/map.rs | 16 +- crates/perry-runtime/src/messaging.rs | 6 +- crates/perry-runtime/src/module_require.rs | 2 +- .../src/node_api_host/functions.rs | 7 +- crates/perry-runtime/src/node_inspector.rs | 12 +- crates/perry-runtime/src/node_repl.rs | 15 +- crates/perry-runtime/src/node_stream.rs | 122 +- .../src/node_stream/async_iterator.rs | 42 +- .../src/node_stream_constructors/builders.rs | 4 +- .../src/node_stream_destroy_state.rs | 11 +- .../perry-runtime/src/node_stream_dispatch.rs | 13 +- .../src/node_stream_event_emitter.rs | 60 +- .../src/node_stream_iter_helpers.rs | 66 +- .../perry-runtime/src/node_stream_pipeline.rs | 2 - .../src/node_stream_readwrite.rs | 28 +- crates/perry-runtime/src/node_stream_tests.rs | 52 +- .../src/node_submodules/consumers.rs | 9 +- .../src/node_submodules/diagnostics.rs | 30 +- .../src/node_submodules/fs_promises.rs | 10 +- .../perry-runtime/src/node_submodules/mod.rs | 2 +- .../src/node_submodules/stream_promises.rs | 12 +- .../perry-runtime/src/node_submodules/test.rs | 41 +- .../src/node_submodules/test_runner.rs | 20 +- .../src/node_submodules/tests.rs | 48 +- .../src/node_submodules/trace_events.rs | 8 +- crates/perry-runtime/src/node_vm/modules.rs | 9 +- crates/perry-runtime/src/object/arguments.rs | 21 +- .../src/object/bound_method_receiver_tests.rs | 11 +- .../src/object/class_constructors.rs | 38 +- .../src/object/class_gc_roots.rs | 4 +- .../src/object/class_registry/construct.rs | 44 +- .../construct/rooted_arguments.rs | 4 +- .../object/class_registry/decl_accessors.rs | 12 +- .../object/class_registry/parent_static.rs | 59 +- .../parent_static/private_and_dynamic.rs | 4 - .../parent_static/static_accessor_call.rs | 11 +- .../class_registry/prototype_objects.rs | 11 +- .../src/object/class_registry/registration.rs | 2 +- .../src/object/class_registry/state.rs | 2 +- .../src/object/collection_proto_thunks.rs | 4 +- .../collection_proto_thunks/subclass_tests.rs | 52 +- .../src/object/dataview_proto_thunks.rs | 50 +- .../src/object/date_proto_thunks.rs | 76 +- .../src/object/descriptor_state.rs | 9 +- .../src/object/disposable_proto_thunks.rs | 4 +- .../src/object/field_get_set/accessors.rs | 31 +- .../field_get_set/class_object_props.rs | 2 +- .../object/field_get_set/get_field_by_name.rs | 4 +- .../src/object/field_set_by_name/tail.rs | 12 +- .../src/object/global_this/array_error.rs | 60 +- .../src/object/global_this/bigint_promise.rs | 20 +- .../src/object/global_this/builtin_thunks.rs | 2 +- .../src/object/global_this/ctor_thunks.rs | 24 +- .../src/object/global_this/fetch_globals.rs | 42 +- .../src/object/global_this/generator.rs | 8 +- .../src/object/global_this/math_temporal.rs | 6 +- .../src/object/global_this/populate.rs | 8 +- .../src/object/global_this/proto_methods.rs | 4 +- .../src/object/global_this/typed_array.rs | 68 +- .../src/object/global_this_tables.rs | 4 +- .../src/object/global_this_webassembly.rs | 24 +- crates/perry-runtime/src/object/instanceof.rs | 2 +- .../src/object/instanceof/proxy_rhs.rs | 7 +- .../src/object/iterator_prototypes.rs | 57 +- crates/perry-runtime/src/object/mod.rs | 22 +- .../src/object/native_call_method.rs | 211 +-- .../closure_override_tests.rs | 4 +- .../native_call_method/common_methods.rs | 26 +- .../src/object/native_call_method/disposal.rs | 20 +- .../native_call_method/function_shape.rs | 11 +- .../native_call_method/handle_methods.rs | 95 +- .../native_call_method/namespace_override.rs | 6 +- .../object/native_call_method/object_proto.rs | 12 +- .../native_call_method/patched_proto.rs | 8 +- .../native_call_method/primitive_methods.rs | 108 +- .../native_call_method/proto_dispatch.rs | 13 +- .../to_locale_string_tests.rs | 4 +- .../perry-runtime/src/object/native_module.rs | 33 +- .../native_module/async_hooks_exports.rs | 2 +- .../native_module/buffer_pool_size_tests.rs | 14 + .../object/native_module/callable_exports.rs | 2 +- .../src/object/native_module/web_locks.rs | 4 +- .../src/object/nm_namespace_hooks.rs | 3 +- .../perry-runtime/src/object/own_override.rs | 16 +- .../src/object/primitive_proto_thunks.rs | 84 +- .../perry-runtime/src/object/property_key.rs | 24 +- .../src/object/prototype_chain.rs | 8 +- .../src/object/regex_proto_thunks.rs | 50 +- .../src/object/string_proto_thunks.rs | 2 +- .../src/object/temporal_proto.rs | 4 +- crates/perry-runtime/src/object/tests.rs | 44 +- .../perry-runtime/src/object/this_binding.rs | 302 +--- .../src/object/typed_array_proto_thunks.rs | 57 +- crates/perry-runtime/src/object/util_types.rs | 2 +- .../src/object/view_brand_tests.rs | 2 +- .../src/object/weakref_proto_thunks.rs | 2 +- .../perry-runtime/src/os_process_streams.rs | 26 +- .../perry-runtime/src/promise/assimilate.rs | 19 +- .../perry-runtime/src/promise/async_step.rs | 13 +- .../src/promise/checked_dispatch.rs | 34 +- .../perry-runtime/src/promise/combinators.rs | 26 +- .../perry-runtime/src/promise/microtasks.rs | 2 +- .../src/promise/spec_combinators.rs | 9 +- crates/perry-runtime/src/promise/then.rs | 14 +- crates/perry-runtime/src/proxy.rs | 100 +- .../src/proxy/apply_construct.rs | 34 +- crates/perry-runtime/src/proxy/get.rs | 4 - crates/perry-runtime/src/proxy/reflect.rs | 40 +- crates/perry-runtime/src/pty/mod.rs | 10 +- .../perry-runtime/src/regex/perex_dispatch.rs | 47 +- .../src/regex/perex_replace_storage.rs | 20 +- .../src/regex/perex_substitution.rs | 2 - crates/perry-runtime/src/regex/replace_fn.rs | 7 +- crates/perry-runtime/src/regex/site_test.rs | 21 +- crates/perry-runtime/src/set.rs | 15 +- crates/perry-runtime/src/symbol/accessors.rs | 10 +- .../src/symbol/inherited_accessor_tests.rs | 8 +- crates/perry-runtime/src/symbol/iterator.rs | 37 +- crates/perry-runtime/src/text.rs | 4 +- crates/perry-runtime/src/thread.rs | 18 +- crates/perry-runtime/src/timer.rs | 12 +- crates/perry-runtime/src/tls_hot.rs | 16 +- crates/perry-runtime/src/tty.rs | 72 +- crates/perry-runtime/src/tui/handle_object.rs | 3 +- .../perry-runtime/src/typedarray/iterate.rs | 12 +- .../perry-runtime/src/typedarray/transform.rs | 12 +- crates/perry-runtime/src/typedarray_props.rs | 11 +- crates/perry-runtime/src/url/prototype.rs | 12 +- crates/perry-runtime/src/url/search_params.rs | 11 +- crates/perry-runtime/src/util_promisify.rs | 36 +- .../src/value/to_string_array.rs | 29 +- .../src/value/to_string_class_ref.rs | 10 +- .../src/value/to_string_primitive.rs | 30 +- crates/perry-runtime/src/wasi.rs | 23 +- crates/perry-stdlib/src/domain.rs | 12 +- crates/perry-stdlib/src/events.rs | 13 +- crates/perry-stdlib/src/events/warnings.rs | 11 +- crates/perry-stdlib/src/streams.rs | 107 +- crates/perry-stdlib/src/streams/pipe.rs | 2 +- crates/perry-stdlib/src/streams/tests.rs | 60 +- crates/perry-stdlib/src/streams/transform.rs | 8 +- crates/perry-stdlib/src/streams/writable.rs | 43 +- crates/perry-stdlib/src/tls.rs | 18 +- crates/perry-stdlib/src/worker_threads.rs | 8 +- .../src/worker_threads/worker_surface.rs | 67 +- .../src/commands/compile/object_cache.rs | 6 - .../object_cache/object_cache_tests.rs | 1 - .../perry/tests/fixtures/this_param_routes.ts | 39 +- .../tests/source_graph_export_regressions.rs | 4 +- crates/perry/tests/this_param_abi.rs | 70 +- scripts/check_runtime_symbols.sh | 2 +- scripts/gc_root_dominance_check.py | 24 +- scripts/gc_runtime_root_holders.json | 4 +- scripts/local_binding_type_allowlist.json | 2 +- scripts/unrooted_local_shape_baseline.json | 4 +- .../test_gap_this_param_receiver_routes.ts | 30 + 261 files changed, 3080 insertions(+), 5706 deletions(-) create mode 100644 changelog.d/this-as-a-parameter-stage-3.md delete mode 100644 crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs create mode 100644 crates/perry-runtime/src/intl/constructor_thunks.rs create mode 100644 crates/perry-runtime/src/object/native_module/buffer_pool_size_tests.rs create mode 100644 test-files/test_gap_this_param_receiver_routes.ts diff --git a/changelog.d/perry-ffi-0.6-typed-js-bodies.md b/changelog.d/perry-ffi-0.6-typed-js-bodies.md index f8d45b6e0c..4941f32a1c 100644 --- a/changelog.d/perry-ffi-0.6-typed-js-bodies.md +++ b/changelog.d/perry-ffi-0.6-typed-js-bodies.md @@ -15,12 +15,8 @@ defined once, in perry-abi (`js_body_fn_ty!`, `JsBody0`..`JsBody16`, `JsClosure::call_slice(this, &args)` and `perry_ffi::call_value(func, this, &args)`. The runtime's entries match: `js_closure_call{N}(closure, this, ...)`, `js_native_call_value(func, this, args, len)`, - `js_closure_call_array(closure, this, args, len)`. Addon code no longer - reads or writes the runtime's implicit-`this` cell (the ext crates' own - `js_implicit_this_set` save/restore pairs are gone). While bodies still - read that cell (this-as-a-parameter stage 1), an entry handed a receiver - other than the cell's binds the cell to it for the call; the runtime's own - plain calls pass the cell's value and bind nothing. + `js_closure_call_array(closure, this, args, len)`. No native code reads or + writes an ambient `this`. - perry-ffi now carries its own version (0.6.0) and depends on perry-abi, which is published before it (`scripts/publish_perry_ffi.sh`). - `scripts/check_js_body_call_funnel.py` refuses an `extern` declaration of a diff --git a/changelog.d/this-as-a-parameter-stage-3.md b/changelog.d/this-as-a-parameter-stage-3.md new file mode 100644 index 0000000000..12bdcc815e --- /dev/null +++ b/changelog.d/this-as-a-parameter-stage-3.md @@ -0,0 +1,38 @@ +The implicit-`this` cell is gone (stage 3 of passing `this` as a parameter): +a body's `this` parameter is the only way it learns its receiver, so no +caller saves, sets or restores anything around a call. + +- Deleted: the per-agent `IMPLICIT_THIS` cell, `js_implicit_this_get`, + `_get_sloppy` and `_set`, `ImplicitThisScope`, its exception savepoint, + its `HotTls` field and agent-pointer slot, the codegen save/restore + funnel (`rooting::implicit_this_*`), the stage-1 witness + (`PERRY_THIS_WITNESS`), and `js_closure_call1_receiverless` (identical to + `js_closure_call1` once no cell exists). +- A plain call passes `undefined`: `js_closure_call0..16`, + `js_native_call_value` and `js_closure_call_array`. A call with a + receiver uses the new `js_closure_call_this0..16`, + `js_native_call_value_this` and `js_closure_call_array_this`. perry-ffi + gains `JsClosure::call_this0..4` and `JsThis::{UNDEFINED, as_f64, + from_f64}` (additive). +- Runtime routes that used to carry a receiver through the cell now pass it: + `util.promisify`/`callbackify`/`deprecate` wrappers call the original with + their own receiver (Node's `ReflectApply(original, this, args)`), the + legacy `Intl.NumberFormat.call(obj)` / `DateTimeFormat` chain, event + listeners (the emitter), stream and socket methods, N-API + `napi_call_function`. One route never had it and is fixed: an accessor + defined on an `arguments` object now runs with that object as `this` + (`test-files/test_gap_this_param_receiver_routes.ts`). +- Async and generator function expressions bind their receiver at entry like + any other body. They run once per call; the step closures that run across + resumptions capture it lexically. +- `this` in module top-level code is `undefined` in strict code and + globalThis in sloppy code, the value the cell held when nothing set it. +- A method's entry-resolved callback target + (`js_closure_resolve_plain_direct_call`, formerly `_arrow_`) now admits + ordinary functions too: the call passes the plain-call `undefined` + receiver itself, which is all `js_closure_callN` did for them. + +Instructions per call (LTO-off build, same host), main -> this change: a +closure value call 145 -> 46, a two-argument closure call 160 -> 59, a +method-site hit on an object literal 99 -> 79, an object-literal method +reading `this` 223 -> 120, an ES5 prototype method 214 -> 111. diff --git a/crates/perry-abi/src/lib.rs b/crates/perry-abi/src/lib.rs index 1200931a79..3907646ad7 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -10,14 +10,13 @@ pub const ARRAY_HEADER_SIZE: usize = 8; /// `agent_ptrs::PERRY_AGENT_PTRS`: the number of per-agent pointer slots. -/// Slot 0 is reserved (the megamorphic follow-up's shape-record directory). +/// Slot 0 is reserved (the megamorphic follow-up's shape-record directory); +/// slot 1 held the implicit-`this` cell's address until this-as-a-parameter +/// deleted the cell, and is free. pub const AGENT_PTR_SLOTS: usize = 4; -/// Slot 1: the address of this agent's implicit-`this` cell -/// (`tls_hot::HotTls::implicit_this`), which a direct method call binds. -pub const AGENT_PTR_IMPLICIT_THIS: usize = 1; -/// `tls_hot::HotTls::agent_ptrs` (Apple aarch64 TSD path; LP64): directly -/// after `implicit_this` (128), behind fixed-size fields only. -pub const HOT_TLS_AGENT_PTRS_OFFSET: usize = 136; +/// `tls_hot::HotTls::agent_ptrs` (Apple aarch64 TSD path; LP64): the first +/// inline value, behind fixed-size fields only. +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 @@ -56,10 +55,8 @@ pub const GC_HEADER_SIZE: usize = 8; /// `closure/body_call.rs`; emitted code only through /// `expr::body_call::emit_js_body_call`. /// -/// While the implicit-`this` cell still exists (this-as-a-parameter stage 1), -/// bodies read the cell: every call entry passes its `this` argument as the -/// body's receiver parameter AND binds the cell to it for the call when the -/// cell holds anything else, so the parameter and the cell never disagree. +/// The `this` parameter is the only way a body learns its receiver: a +/// method-style caller passes the receiver, a plain call `undefined`. pub const JS_BODY_CALLEE_PARAM: usize = 0; /// Native parameter index of the receiver (`this`) bits. pub const JS_BODY_THIS_PARAM: usize = 1; diff --git a/crates/perry-codegen/src/codegen/artifacts.rs b/crates/perry-codegen/src/codegen/artifacts.rs index 6ffa41789b..5819ae5312 100644 --- a/crates/perry-codegen/src/codegen/artifacts.rs +++ b/crates/perry-codegen/src/codegen/artifacts.rs @@ -116,7 +116,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { crate::collectors::spec_abi_sites::single_binding_closure_locals(hir) .into_iter() // A closure with a trusted-box clone is better served by the - // ENTRY-RESOLVED path: `js_closure_resolve_arrow_direct_call` + // ENTRY-RESOLVED path: `js_closure_resolve_plain_direct_call` // hands back the trusted clone with its entry-cached box-capture // pointers, which beats the known arm's public/typed call for // capturing bodies (measured: 2.5 vs 5.1 ns). Seeding such an id @@ -333,11 +333,9 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let arity = method.params.len(); let wrap_params = crate::expr::body_call::js_body_params((0..arity).map(|i| format!("%a{}", i))); - let witness_site = crate::expr::body_call::this_witness_site(llmod, &wrap_name); let wf = llmod.define_function(&wrap_name, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); - crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); // Forward the call-site receiver (the `this` parameter) as the // method's `this`, then the args. See the block comment above // (#5138). diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index d3a1ba9f07..d3b871d631 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -553,19 +553,6 @@ pub(super) fn compile_closure( let llvm_params = crate::expr::body_call::js_body_params(params.iter().map(|p| format!("%arg{}", p.id))); - // Stage-1 witness: a body that reads the implicit-`this` cell (it binds - // no lexical `this` of its own) checks, at entry, that its caller passed - // the cell's receiver as `%js_this`. Witness builds only. - let reads_this_cell = !captures_this - && enclosing_class.is_none() - && !is_arrow - && perry_hir::analysis::body_reads_dynamic_this(body); - let witness_site = if reads_this_cell { - crate::expr::body_call::this_witness_site(llmod, &llvm_name) - } else { - None - }; - let ic_base = llmod.ic_counter; let buffer_alias_base = llmod.buffer_alias_counter; let lf = llmod.define_function(&llvm_name, DOUBLE, llvm_params); @@ -582,23 +569,21 @@ pub(super) fn compile_closure( // #10906: a non-arrow closure with its OWN `this` binding (a function // expression, or an object-literal method — the closed-shape literal path // lowers every method to exactly this) used to call - // `js_implicit_this_get{,_sloppy}` at EVERY `this` in its body. Bind the + // a thread-local receiver-cell read at EVERY `this` in its body. Bind the // receiver once instead, at entry, into a rooted `this` slot that every // `Expr::This` loads — what OrdinaryCallBindThis does, and what a class // method gets from `%this_arg`. The sloppy conversion (nullish -> // globalThis, primitive -> wrapper) then also runs once, so `this === this` // holds for a primitive receiver instead of boxing a fresh wrapper per read. // - // Async and generator bodies are excluded: their statements run across - // resumptions, and the entry read has not been audited against re-entry. - // Arrows have lexical `this`, which is `captures_this`'s job. + // Async and generator bodies bind it the same way: such a body runs ONCE + // per call (it returns a promise or a generator object), and the step + // closures that do run across resumptions capture the receiver lexically + // (the generator transform sets their `captures_this`). Arrows have + // lexical `this`, which is `captures_this`'s job. let entry_bound_this = !captures_this && enclosing_class.is_none() && !is_arrow - && !is_async - && !is_generator - && !cross_module.local_generator_funcs.contains(&func_id) - && !cross_module.async_step_closures.contains(&func_id) && perry_hir::analysis::body_reads_dynamic_this(body); // gh #6206 / #6081: closures/arrows compiled WITHOUT a shadow frame left @@ -643,10 +628,6 @@ pub(super) fn compile_closure( crate::collectors::collect_shadow_slot_clear_points(body, &shadow_slot_map); let _ = lf.create_block("entry"); - crate::expr::body_call::emit_this_param_witness( - lf.block_mut(0).expect("closure body has an entry block"), - witness_site.as_ref(), - ); let versioned_loop_deopt_context = versioned_loop_callback.then(|| { let scratch_param = params @@ -1152,7 +1133,7 @@ pub(super) fn compile_closure( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures, versioned_loop_deopt_context, diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index 8eb22ff514..824d0e17cc 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -720,7 +720,7 @@ pub(super) fn compile_module_entry( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures: false, versioned_loop_deopt_context: None, @@ -1580,7 +1580,7 @@ pub(super) fn compile_module_entry( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures: false, versioned_loop_deopt_context: None, diff --git a/crates/perry-codegen/src/codegen/export_value_wrappers.rs b/crates/perry-codegen/src/codegen/export_value_wrappers.rs index 74746d5f73..ea3c44f725 100644 --- a/crates/perry-codegen/src/codegen/export_value_wrappers.rs +++ b/crates/perry-codegen/src/codegen/export_value_wrappers.rs @@ -117,18 +117,9 @@ pub(super) fn emit_export_value_wrappers(c: ExportValueWrapperCtx<'_>) { let arg_names: Vec = (0..arity).map(|i| format!("%a{}", i)).collect(); let wrap_params = crate::expr::body_call::js_body_params(arg_names.iter().cloned()); let wrap_name = format!("__perry_wrap_{}", original_name); - // Stage-1 witness: the function body reads its receiver from the - // implicit-`this` cell; its value wrapper is the entry a function - // object calls, so it checks the receiver it was passed. - let witness_site = if perry_hir::analysis::body_reads_dynamic_this(&f.body) { - crate::expr::body_call::this_witness_site(llmod, &wrap_name) - } else { - None - }; let wf = llmod.define_function(&wrap_name, DOUBLE, wrap_params); let _ = wf.create_block("entry"); let blk = wf.block_mut(0).unwrap(); - crate::expr::body_call::emit_this_param_witness(blk, witness_site.as_ref()); // Call the underlying function with the arg doubles — a this-reading // function's receiver-taking body with the receiver first. let mut result = call_function_body(blk, f, &original_name, &arg_names); diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index fe6dc5ff5e..981ce54912 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -1259,7 +1259,7 @@ pub(super) fn compile_function( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures: false, versioned_loop_deopt_context: None, diff --git a/crates/perry-codegen/src/codegen/helpers.rs b/crates/perry-codegen/src/codegen/helpers.rs index 05fd2c8f89..00d34f724d 100644 --- a/crates/perry-codegen/src/codegen/helpers.rs +++ b/crates/perry-codegen/src/codegen/helpers.rs @@ -1829,7 +1829,7 @@ pub(super) fn callee_binding_resolution_enabled() -> bool { }) } -/// Populate `resolved_arrow_callback_targets` for loop-called immutable callee +/// Populate `resolved_plain_callback_targets` for loop-called immutable callee /// bindings — the generalization of `codegen/method.rs`'s callback-parameter /// resolution to plain function and closure bodies, and to captured bindings /// and module globals. @@ -1878,7 +1878,7 @@ pub(super) fn emit_callee_binding_resolutions( ); for (id, arity) in candidates { if ctx - .resolved_arrow_callback_targets + .resolved_plain_callback_targets .contains_key(&(id, arity)) { continue; @@ -1938,10 +1938,10 @@ pub(super) fn emit_callee_binding_resolutions( let handle = crate::expr::unbox_to_i64(ctx.block(), &value_box); let fn_ptr = ctx.block().call( PTR, - "js_closure_resolve_arrow_direct_call", + "js_closure_resolve_plain_direct_call", &[(I64, &handle), (I32, &arity.to_string())], ); - ctx.resolved_arrow_callback_targets + ctx.resolved_plain_callback_targets .insert((id, arity), fn_ptr); } } diff --git a/crates/perry-codegen/src/codegen/hoisted_callback_method_tests.rs b/crates/perry-codegen/src/codegen/hoisted_callback_method_tests.rs index 80c129fc5d..54352a61e2 100644 --- a/crates/perry-codegen/src/codegen/hoisted_callback_method_tests.rs +++ b/crates/perry-codegen/src/codegen/hoisted_callback_method_tests.rs @@ -137,7 +137,7 @@ fn immutable_alias_resolves_once_without_an_identity_guard_and_keeps_the_fallbac "@perry_method_hoisted_callback_method_ts__Iterator__visit(", ); assert_eq!( - body.matches("@js_closure_resolve_arrow_direct_call(") + body.matches("@js_closure_resolve_plain_direct_call(") .count(), 1, "the method must resolve one target before all callback calls:\n{body}" @@ -168,7 +168,7 @@ fn direct_parameter_call_needs_no_alias_identity_guard() { "@perry_method_hoisted_callback_method_ts__Iterator__visit(", ); assert_eq!( - body.matches("@js_closure_resolve_arrow_direct_call(") + body.matches("@js_closure_resolve_plain_direct_call(") .count(), 1 ); @@ -195,7 +195,7 @@ fn reassigned_callback_parameter_is_not_hoisted() { &ir, "@perry_method_hoisted_callback_method_ts__Iterator__visit(", ); - assert!(!body.contains("@js_closure_resolve_arrow_direct_call(")); + assert!(!body.contains("@js_closure_resolve_plain_direct_call(")); assert!(body.contains("@js_closure_call3(")); } @@ -207,7 +207,7 @@ fn more_than_four_call_arities_declines_the_parameter() { &ir, "@perry_method_hoisted_callback_method_ts__Iterator__visit(", ); - assert!(!body.contains("@js_closure_resolve_arrow_direct_call(")); + assert!(!body.contains("@js_closure_resolve_plain_direct_call(")); for arity in 0..=4 { assert!(body.contains(&format!("@js_closure_call{arity}("))); } @@ -244,5 +244,5 @@ fn nested_closure_calls_are_not_assigned_the_outer_methods_ssa_target() { &ir, "@perry_method_hoisted_callback_method_ts__Iterator__visit(", ); - assert!(!body.contains("@js_closure_resolve_arrow_direct_call(")); + assert!(!body.contains("@js_closure_resolve_plain_direct_call(")); } diff --git a/crates/perry-codegen/src/codegen/literal_method_this_tests.rs b/crates/perry-codegen/src/codegen/literal_method_this_tests.rs index af33419e5a..b913ec19ec 100644 --- a/crates/perry-codegen/src/codegen/literal_method_this_tests.rs +++ b/crates/perry-codegen/src/codegen/literal_method_this_tests.rs @@ -273,10 +273,13 @@ fn literal_method_this_field_takes_the_guarded_class_field_path() { #[test] fn arrow_keeps_its_per_use_receiver_reads() { // An arrow with no captured `this` is outside the change: lexical `this` - // is `captures_this`'s job, and this body must lower exactly as before. + // is `captures_this`'s job. It never reads the receiver parameter; with + // no enclosing receiver its `this` is module code's (sloppy: globalThis), + // per use. let ir = compile_ir(&literal_module(method(METHOD, true, false))); let body = closure_body(&ir, METHOD); - assert_eq!(calls_to(body, "js_implicit_this_get_sloppy"), 3, "{body}"); + assert!(!body.contains("bitcast i64 %js_this"), "{body}"); + assert_eq!(calls_to(body, "js_this_coerce_sloppy"), 3, "{body}"); assert!(!body.contains("@js_class_field_get_ic("), "{body}"); } diff --git a/crates/perry-codegen/src/codegen/method.rs b/crates/perry-codegen/src/codegen/method.rs index 2fe16f21ed..ded590542a 100644 --- a/crates/perry-codegen/src/codegen/method.rs +++ b/crates/perry-codegen/src/codegen/method.rs @@ -612,7 +612,7 @@ pub(super) fn compile_method( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures: false, versioned_loop_deopt_context: None, @@ -803,7 +803,7 @@ pub(super) fn compile_method( }; // Resolve each immutable callback parameter/arity once, before the method - // body. The runtime answers null unless the actual value is a plain arrow + // body. The runtime answers null unless the actual value is a closure // whose declared/rest shape can use this exact call ABI. Exact immutable // aliases (`const cb = callback`) reuse the same answer: every successful // read has that identity by construction, while a pre-initialisation read @@ -823,7 +823,7 @@ pub(super) fn compile_method( let source_handle = crate::expr::unbox_to_i64(ctx.block(), &source_box); let fn_ptr = ctx.block().call( PTR, - "js_closure_resolve_arrow_direct_call", + "js_closure_resolve_plain_direct_call", &[(I64, &source_handle), (I32, &arity.to_string())], ); resolved_callback_ptrs.insert((source_param, arity), fn_ptr); @@ -841,7 +841,7 @@ pub(super) fn compile_method( else { continue; }; - ctx.resolved_arrow_callback_targets + ctx.resolved_plain_callback_targets .insert((call.callee_local, call.arity), fn_ptr); if let Some(versioned_fn_ptr) = resolved_versioned_callback_ptrs .get(&(call.source_param, call.arity)) diff --git a/crates/perry-codegen/src/codegen/method_static.rs b/crates/perry-codegen/src/codegen/method_static.rs index c04c558683..ecc68b100e 100644 --- a/crates/perry-codegen/src/codegen/method_static.rs +++ b/crates/perry-codegen/src/codegen/method_static.rs @@ -85,8 +85,8 @@ pub(in crate::codegen) fn compile_static_method( // INT32-tagged class-id value `Expr::ClassRef` lowers to) stored in a // `this` slot so `this.x` / `this.#x()` / `this[k]` inside the body // resolve against the class object via the normal dynamic-dispatch - // path. (Previously `this` fell through to `js_implicit_this_get` and - // read back `undefined`.) + // path. (Previously `this` fell through to a receiver-less read and came + // back `undefined`.) let class_ref_cid = class_ids.get(&class.name).copied().unwrap_or(class.id); let class_ref_lit = { let bits = crate::nanbox::INT32_TAG | (class_ref_cid as u64 & 0xFFFF_FFFF); @@ -276,7 +276,7 @@ pub(in crate::codegen) fn compile_static_method( local_closure_func_ids: HashMap::new(), guard_free_closure_bindings: std::collections::HashSet::new(), local_closure_param_counts: HashMap::new(), - resolved_arrow_callback_targets: HashMap::new(), + resolved_plain_callback_targets: HashMap::new(), resolved_versioned_loop_callback_targets: HashMap::new(), trusted_box_captures: false, versioned_loop_deopt_context: None, diff --git a/crates/perry-codegen/src/collectors/hoisted_callback_calls.rs b/crates/perry-codegen/src/collectors/hoisted_callback_calls.rs index c4396e8288..a2405ebb8f 100644 --- a/crates/perry-codegen/src/collectors/hoisted_callback_calls.rs +++ b/crates/perry-codegen/src/collectors/hoisted_callback_calls.rs @@ -1,8 +1,8 @@ //! Immutable method callbacks whose dispatch can be resolved once at entry. //! //! A TypeScript function annotation only nominates a possible callback. The -//! runtime still validates the actual value and admits only plain arrow -//! closures whose arity needs no padding or rest bundling. This collector's +//! runtime still validates the actual value and admits only closures whose +//! arity needs no padding or rest bundling (and no bound-function routing). This collector's //! job is narrower: find direct call arities of immutable callback parameters //! and their immutable local aliases, without entering nested closure bodies. @@ -194,8 +194,8 @@ fn immutable_aliases(root: u32, edges: &[(u32, u32)]) -> HashSet { } /// Find callback parameter/alias call sites that may reuse one entry-resolved -/// arrow target. Runtime validation remains mandatory; annotations never prove -/// the incoming value's class, arity, rest shape, or arrow semantics. +/// target. Runtime validation remains mandatory; annotations never prove the +/// incoming value's class, arity or rest shape. pub(crate) fn collect_hoisted_callback_calls(method: &Function) -> Vec { if method.is_async || method.is_generator @@ -260,7 +260,7 @@ pub(crate) fn collect_hoisted_callback_calls(method: &Function) -> Vec) -> String { - if let Some(cell) = crate::rooting::implicit_this_cell_ptr(ctx) { - return ctx.block().load(I64, &cell); +/// The value of `this` in code that has no receiver binding of its own and no +/// `this` slot: module top-level code. `undefined` in strict code; in sloppy +/// code OrdinaryCallBindThis's `undefined -> globalThis`, through the same +/// `js_this_coerce_sloppy` a sloppy body's receiver prologue calls (a +/// safepoint). A function, method or closure body that reads `this` must bind +/// it from its receiver parameter (or a lexical capture) at entry instead: a +/// body reaching this has lost the receiver its caller passed. +pub(crate) fn unbound_this_value(ctx: &mut crate::expr::FnCtx<'_>) -> String { + let undefined = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); + if ctx.is_strict_fn { + return undefined; } - let blk = ctx.block(); - let value = blk.call(DOUBLE, "js_implicit_this_get", &[]); - blk.bitcast_double_to_i64(&value) -} - -/// Whether this compile emits the stage-1 witness (`PERRY_THIS_WITNESS=1`, a -/// COMPILE-time knob — a product build emits nothing and pays nothing). -pub(crate) fn this_witness_enabled() -> bool { - std::env::var_os("PERRY_THIS_WITNESS").is_some_and(|v| v == "1") -} - -/// The site-name constant a witness call names its body by, added to -/// `llmod` BEFORE the body is defined (a definition borrows the module): -/// `Some((global, byte_len))` in a witness build, `None` otherwise. -pub(crate) fn this_witness_site( - llmod: &mut crate::module::LlModule, - site: &str, -) -> Option<(String, usize)> { - this_witness_enabled().then(|| llmod.add_string_constant(site)) -} - -/// In a witness build (`site` from [`this_witness_site`]), emit -/// `js_this_param_witness(%js_this, site, len)` at the entry of a body that -/// reads the implicit-`this` cell: the runtime compares the parameter with -/// the cell and names every body whose caller passed a different receiver. -/// A GC leaf. Nothing when `site` is `None`. -pub(crate) fn emit_this_param_witness(blk: &mut LlBlock, site: Option<&(String, usize)>) { - let Some((global, len)) = site else { - return; - }; - blk.call_void( - "js_this_param_witness", - &[ - (I64, JS_BODY_THIS), - (crate::types::PTR, &format!("@{global}")), - (I64, &len.to_string()), - ], - ); + ctx.block() + .call(DOUBLE, "js_this_coerce_sloppy", &[(DOUBLE, &undefined)]) } /// The receiver prologue of a SLOPPY body that reads `this` (stage 2 of diff --git a/crates/perry-codegen/src/expr/call_spread.rs b/crates/perry-codegen/src/expr/call_spread.rs index 0550edde59..41fb582883 100644 --- a/crates/perry-codegen/src/expr/call_spread.rs +++ b/crates/perry-codegen/src/expr/call_spread.rs @@ -590,14 +590,13 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // Re-read below every allocation above: the slot is a mutable root // an evacuating cycle rewrites in place. let cb_box = callee_group.reread(ctx, cb_root)?; - // Stage 1: `f(...xs)` runs with the implicit-`this` cell's value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); let result = ctx.block().call( DOUBLE, "js_closure_call_apply_with_spread", &[ (DOUBLE, &cb_box), - (I64, &this_bits), + // `f(...xs)`: a plain call. + (I64, crate::expr::body_call::JS_THIS_UNDEFINED), (crate::types::PTR, ®s_ptr), (I64, ®s_len), (I64, &spread_handle), diff --git a/crates/perry-codegen/src/expr/closure.rs b/crates/perry-codegen/src/expr/closure.rs index b1d735834e..b82be07315 100644 --- a/crates/perry-codegen/src/expr/closure.rs +++ b/crates/perry-codegen/src/expr/closure.rs @@ -358,12 +358,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { Some(if let Some(slot) = this_slot { ctx.block().load(DOUBLE, &slot) } else { - // Issue #1845: empty `this_stack` => dynamically-bound - // `this` (computed-key method / function-expression - // receiver). Capture the runtime's `IMPLICIT_THIS`, not - // a 0.0 sentinel. See the matching comment on the - // post-create patch path below. - ctx.block().call(DOUBLE, "js_implicit_this_get", &[]) + // Issue #1845: never a 0.0 sentinel. See the matching + // comment on the post-create patch path below. + crate::expr::body_call::unbound_this_value(ctx) }) } else { None @@ -503,32 +500,24 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // `Expr::This` reads back 0.0 and any `this.field` access in // the body crashes. // - // Issue #1845: when `this_stack` is empty the enclosing - // function still has a dynamically-bound `this` — this is the - // case for a computed-key method (`[expr](){…}`) which lowers - // to a function-expression closure field (see - // `lower_computed_key_method_as_field`): its body reads `this` - // via the runtime's `IMPLICIT_THIS` (set by `recv[k]()` - // dispatch), NOT via `this_stack`. A direct `Expr::This` in - // such a body already falls back to `js_implicit_this_get` - // (see `this_super_call.rs`); a *nested* arrow that captures - // `this` must capture that same dynamic receiver, otherwise it - // snapshots a bogus 0.0 sentinel. effect's fiber-runtime - // op-dispatch hit this: `[OP_WITH_RUNTIME](op){ internalCall(() - // => op.i0(this, …)) }` passed `this = 0.0` (a raw number) into - // the WithRuntime handler, so `fiber.currentContext` read - // `undefined` and `Effect.all`/`Effect.forEach` died with a - // `{}` FiberFailure. Falling back to `js_implicit_this_get` - // here matches the direct-read path exactly: top-level arrows - // that legitimately have no `this` see `undefined` (the - // `IMPLICIT_THIS` default), not 0.0 — both are non-crashing. + // Issue #1845: an arrow captures its enclosing body's `this`, + // never a 0.0 sentinel. effect's fiber-runtime op-dispatch hit + // the sentinel: `[OP_WITH_RUNTIME](op){ internalCall(() => + // op.i0(this, …)) }` passed `this = 0.0` (a raw number) into the + // WithRuntime handler, so `fiber.currentContext` read `undefined` + // and `Effect.all`/`Effect.forEach` died with a `{}` + // FiberFailure. A body that reads `this` (a computed-key method + // lowered to a function-expression field included) binds it from + // its receiver parameter at entry, so the slot is the enclosing + // `this`; code with no receiver (module top level) takes exactly + // what a direct `Expr::This` there reads. if *captures_this { let this_idx = this_capture_idx.to_string(); let this_slot = ctx.this_stack.last().cloned(); let this_value = if let Some(slot) = this_slot { ctx.block().load(DOUBLE, &slot) } else { - ctx.block().call(DOUBLE, "js_implicit_this_get", &[]) + crate::expr::body_call::unbound_this_value(ctx) }; let blk = ctx.block(); let this_bits = blk.bitcast_double_to_i64(&this_value); diff --git a/crates/perry-codegen/src/expr/hot_tls.rs b/crates/perry-codegen/src/expr/hot_tls.rs index cce6559c8c..2b96d0e429 100644 --- a/crates/perry-codegen/src/expr/hot_tls.rs +++ b/crates/perry-codegen/src/expr/hot_tls.rs @@ -3,11 +3,11 @@ //! The runtime keeps its hottest per-thread state in one `HotTls` block whose //! address every thread publishes under a pthread key (`tls_hot.rs`); on this //! platform the pthread TSD array is addressable straight from -//! `TPIDRRO_EL0`, which is how `pthread_getspecific` itself works. Two runtime -//! entries generated code calls on every dynamically-dispatched method call — -//! `js_implicit_this_set` (the save and the restore) — and the once-per- -//! allocating-activation `js_inline_arena_state` do nothing but that lookup -//! plus one load or store, so the lookup is emitted here instead: +//! `TPIDRRO_EL0`, which is how `pthread_getspecific` itself works. Runtime +//! entries that do nothing but that lookup plus one load or store — the +//! once-per-allocating-activation `js_inline_arena_state`, and the agent +//! pointer block read (`agent_ptr.rs`) — have the lookup emitted here +//! instead: //! //! ```text //! key = load @PERRY_HOT_TSD_KEY ; -1 = no direct path @@ -33,8 +33,6 @@ use crate::types::{I64, I8, PTR}; /// `HotTls::inline_state` — see `tls_hot::HOT_TLS_INLINE_STATE_OFFSET`. pub(crate) const HOT_TLS_INLINE_STATE_OFFSET: &str = "8"; -/// `HotTls::implicit_this` — see `tls_hot::HOT_TLS_IMPLICIT_THIS_OFFSET`. -pub(crate) const HOT_TLS_IMPLICIT_THIS_OFFSET: &str = "128"; /// Is the inline hot-cache access available for this compile's target? pub(crate) fn inline_hot_tls_enabled(ctx: &FnCtx<'_>) -> bool { @@ -120,12 +118,5 @@ mod layout_tie_tests { runtime_offset("HOT_TLS_INLINE_STATE_OFFSET"), "codegen emits a stale HotTls::inline_state offset", ); - assert_eq!( - super::HOT_TLS_IMPLICIT_THIS_OFFSET - .parse::() - .unwrap(), - runtime_offset("HOT_TLS_IMPLICIT_THIS_OFFSET"), - "codegen emits a stale HotTls::implicit_this offset", - ); } } diff --git a/crates/perry-codegen/src/expr/method_site.rs b/crates/perry-codegen/src/expr/method_site.rs index 1aa1253802..c904238cb6 100644 --- a/crates/perry-codegen/src/expr/method_site.rs +++ b/crates/perry-codegen/src/expr/method_site.rs @@ -397,7 +397,7 @@ pub(crate) fn emit_method_site( blk.cond_br(&valid, &call_l, &miss_l); (h, f, end) }; - // call: bind `this`, call the body directly, restore. + // call: the body directly, with the receiver as its `this` parameter. ctx.current_block = call_idx; let handle = ctx .block() @@ -406,11 +406,6 @@ pub(crate) fn emit_method_site( .block() .phi(I64, &[(&own_func, &own_end), (&inh_func, &inh_end)]); let fptr = ctx.block().inttoptr(I64, &func); - let cell = crate::rooting::implicit_this_cell_ptr(ctx); - let saved = match &cell { - Some(cell) => crate::rooting::implicit_this_save_at(ctx, cell, recv_box), - None => crate::rooting::implicit_this_save(ctx, recv_box), - }; let mut call_args: Vec = lowered_args.to_vec(); // Pad with `undefined` up to the arity the prime admits, so a body that // declares a few more parameters than this call passes is entered @@ -419,8 +414,7 @@ pub(crate) fn emit_method_site( let undefined = crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); let pad = crate::runtime_abi::method_site_padded_argc(lowered_args.len()) - lowered_args.len(); call_args.extend(std::iter::repeat_n(undefined, pad)); - // The body gets the receiver as its `this` parameter — the same value - // bound in the cell above, which bodies still read (stage 1). + // The body gets the receiver as its `this` parameter. let recv_bits = ctx.block().bitcast_double_to_i64(recv_box); let hit_value = crate::expr::body_call::emit_js_body_call( ctx.block(), @@ -429,10 +423,6 @@ pub(crate) fn emit_method_site( &recv_bits, &call_args, ); - match &cell { - Some(cell) => crate::rooting::implicit_this_restore_at(ctx, cell, saved), - None => crate::rooting::implicit_this_restore(ctx, saved), - } let hit_end = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_l); diff --git a/crates/perry-codegen/src/expr/mod.rs b/crates/perry-codegen/src/expr/mod.rs index 54e9bd367d..d56909ed51 100644 --- a/crates/perry-codegen/src/expr/mod.rs +++ b/crates/perry-codegen/src/expr/mod.rs @@ -31,6 +31,10 @@ use crate::types::{DOUBLE, F32, I1, I16, I32, I64, I8, PTR}; // `lower_expr` and the foundational types (`FnCtx`, `FlatConstInfo`) // remain here. `pub(crate) use` keeps the public surface stable so // existing `crate::expr::X` paths resolve unchanged. +// No emitted reader since the implicit-`this` cell (slot 1) was deleted; kept +// for slot 0, reserved for the megamorphic follow-up's shape-record directory +// (`perry_abi::AGENT_PTR_SLOTS`). +#[allow(dead_code)] pub(crate) mod agent_ptr; pub(crate) mod array_length; mod array_literal; @@ -675,7 +679,7 @@ pub(crate) struct FnCtx<'a> { /// Nullable code pointers resolved once from immutable method callback /// parameters, indexed by callback local (including exact const aliases) /// and call arity. - pub resolved_arrow_callback_targets: std::collections::HashMap<(u32, usize), String>, + pub resolved_plain_callback_targets: std::collections::HashMap<(u32, usize), String>, /// Nullable compiler-private callback targets whose guarded cold arms /// poison a versioned loop before they can run user code. pub resolved_versioned_loop_callback_targets: std::collections::HashMap<(u32, usize), String>, diff --git a/crates/perry-codegen/src/expr/object_literal.rs b/crates/perry-codegen/src/expr/object_literal.rs index 82cf8ae4a6..37c0a38459 100644 --- a/crates/perry-codegen/src/expr/object_literal.rs +++ b/crates/perry-codegen/src/expr/object_literal.rs @@ -898,9 +898,9 @@ mod by_name_method_closure_tests { /// the first place. /// /// Counting patch calls over the WHOLE function would be vacuous: closure - /// construction emits `js_closure_set_capture_bits` too (that is how the - /// reserved `this` slot is seeded from `js_implicit_this_get`), so the - /// count only means something in the tail. + /// construction emits `js_closure_set_capture_bits` too (that is how a + /// capturing closure's reserved `this` slot is seeded), so the count only + /// means something in the tail. #[test] fn the_this_patches_run_below_every_property_store() { let _pin = pin_routing("0"); diff --git a/crates/perry-codegen/src/expr/super_method.rs b/crates/perry-codegen/src/expr/super_method.rs index b9fa7f041f..e2643c5489 100644 --- a/crates/perry-codegen/src/expr/super_method.rs +++ b/crates/perry-codegen/src/expr/super_method.rs @@ -307,12 +307,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let recv_v = if let Some(this_slot) = ctx.this_stack.last().cloned() { ctx.block().load(DOUBLE, &this_slot) } else { - let helper = if ctx.is_strict_fn { - "js_implicit_this_get" - } else { - "js_implicit_this_get_sloppy" - }; - ctx.block().call(DOUBLE, helper, &[]) + crate::expr::body_call::unbound_this_value(ctx) }; let key_idx = ctx.strings.intern(property); let key_handle_global = format!("@{}", ctx.strings.entry(key_idx).handle_global); @@ -380,12 +375,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let recv_v = if let Some(this_slot) = ctx.this_stack.last().cloned() { ctx.block().load(DOUBLE, &this_slot) } else { - let helper = if ctx.is_strict_fn { - "js_implicit_this_get" - } else { - "js_implicit_this_get_sloppy" - }; - ctx.block().call(DOUBLE, helper, &[]) + crate::expr::body_call::unbound_this_value(ctx) }; let key_v = lower_expr(ctx, key)?; let value_v = lower_expr(ctx, value)?; diff --git a/crates/perry-codegen/src/expr/this_super_call.rs b/crates/perry-codegen/src/expr/this_super_call.rs index 4e144085f2..18c24033f3 100644 --- a/crates/perry-codegen/src/expr/this_super_call.rs +++ b/crates/perry-codegen/src/expr/this_super_call.rs @@ -178,12 +178,7 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { if let Some(slot) = ctx.this_stack.last().cloned() { Ok(ctx.block().load(DOUBLE, &slot)) } else { - let helper = if ctx.is_strict_fn { - "js_implicit_this_get" - } else { - "js_implicit_this_get_sloppy" - }; - Ok(ctx.block().call(DOUBLE, helper, &[])) + Ok(crate::expr::body_call::unbound_this_value(ctx)) } } Expr::NewTarget => { @@ -718,12 +713,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { (ptr_reg, user_arg_count.to_string()) }; - // Bind IMPLICIT_THIS to the current `this` so the parent - // function body's `this.x = ...` writes land on the - // subclass instance (non-arrow functions read `this` via - // `js_implicit_this_get` when their this_stack is empty). - // Save the prior IMPLICIT_THIS and restore it after — see - // the #519 pattern in console_promise.rs / method_override.rs. + // The parent function is called with the current `this` + // as its receiver, so the parent body's `this.x = ...` + // writes land on the subclass instance. let this_box = match ctx.this_stack.last().cloned() { Some(slot) => ctx.block().load(DOUBLE, &slot), None => { @@ -737,9 +729,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // `GlobalRequest = global.Request`), it allocates the // native fetch handle and stashes it on `this` so // inherited body methods resolve; otherwise it falls - // back to the ordinary implicit-`this`-bound - // `js_native_call_value` (unchanged behavior for - // every other runtime-value parent). + // back to an ordinary call of the parent with + // `this_box` as its receiver (unchanged behavior + // for every other runtime-value parent). let nt_save = crate::rooting::new_target_save_for_super(ctx); let parent_result = ctx.block().call( DOUBLE, diff --git a/crates/perry-codegen/src/gc_call_effects.rs b/crates/perry-codegen/src/gc_call_effects.rs index bdbb5f0950..872c378837 100644 --- a/crates/perry-codegen/src/gc_call_effects.rs +++ b/crates/perry-codegen/src/gc_call_effects.rs @@ -269,17 +269,10 @@ pub(crate) fn classify_direct_callee(name: &str) -> GcCallEffect { // code. Kept in the dominance checker and root-reload authorities. | "js_object_get_class_id" | "js_object_get_own_field_or_undef" - // TLS dynamic-call context only. #8596 adds the `_get` reader — a bare - // `IMPLICIT_THIS.with(|c| f64::from_bits(c.get()))` (`object/this_binding.rs`), - // the exact shape of the already-admitted `_set` and `js_new_target_get`. - // The `_sloppy` reader is deliberately ABSENT: it boxes booleans/strings - // and reads globalThis, which allocates. - | "js_implicit_this_set" - | "js_implicit_this_get" - // Witness builds only (`PERRY_THIS_WITNESS=1`): compares its argument - // with the same cell `_get` reads, bumps two atomics and may write - // stderr. No Perry allocation, no re-entry into generated code. - | "js_this_param_witness" + // TLS dynamic-call context only: the `new.target` cell + // (`object/this_binding.rs`). `js_this_coerce_sloppy` is deliberately + // ABSENT: it boxes booleans/strings and reads globalThis, which + // allocates. | "js_new_target_get" | "js_new_target_set" // #8596: TDZ-suppression window depth (`box.rs`). Each is a single @@ -964,8 +957,6 @@ mod tests { /// registry access or feedback recording (#8775). /// - `js_object_own_method_cache_miss` — cold shape/key/closure /// validation plus a compiler-private token store (#8775). - /// - `js_implicit_this_get` — a bare `IMPLICIT_THIS` `Cell` read, the - /// shape of the already-admitted `js_implicit_this_set`. /// - `js_tdz_suppress_begin`/`_end` — a thread-local `Cell` inc/dec. #[test] fn issue_8596_tls_and_feedback_guards_cannot_collect() { @@ -973,7 +964,6 @@ mod tests { "js_typed_feedback_closure_direct_call_guard", "js_closure_exact_func_guard", "js_object_own_method_cache_miss", - "js_implicit_this_get", "js_tdz_suppress_begin", "js_tdz_suppress_end", ] { @@ -987,15 +977,14 @@ mod tests { /// The discriminating negatives for #8596: near-neighbours of the four /// admitted above that DO allocate or throw, so they must stay - /// `Unknown` (a safepoint). `js_implicit_this_get_sloppy` boxes - /// booleans/strings and reads globalThis; the ordinary `_get` it wraps is - /// the only leaf-safe half. + /// `Unknown` (a safepoint). `js_this_coerce_sloppy` boxes + /// booleans/strings and reads globalThis. #[test] fn issue_8596_allocating_lookalike_stays_a_safepoint() { assert_eq!( - classify_direct_callee("js_implicit_this_get_sloppy"), + classify_direct_callee("js_this_coerce_sloppy"), GcCallEffect::Unknown, - "the sloppy reader boxes primitives / reads globalThis and must remain a safepoint" + "the sloppy coercion boxes primitives / reads globalThis and must remain a safepoint" ); } diff --git a/crates/perry-codegen/src/lower_call/atomics.rs b/crates/perry-codegen/src/lower_call/atomics.rs index b01a101c82..586168d325 100644 --- a/crates/perry-codegen/src/lower_call/atomics.rs +++ b/crates/perry-codegen/src/lower_call/atomics.rs @@ -60,14 +60,14 @@ pub fn try_lower_atomics_static_call( } // The `js_atomics_*` natives are JS bodies (the function objects - // `Atomics.load` &c. run): no environment, and this direct call does not - // bind the implicit-`this` cell, so they get the cell's receiver. - let this_bits = crate::expr::body_call::current_this_bits(ctx); + // `Atomics.load` &c. run): no environment, and they never read their + // receiver. + let this_bits = crate::expr::body_call::JS_THIS_UNDEFINED; Ok(Some(crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(runtime_fn), "0", - &this_bits, + this_bits, &lowered, ))) } diff --git a/crates/perry-codegen/src/lower_call/console_promise.rs b/crates/perry-codegen/src/lower_call/console_promise.rs index ffc6e68d16..2c14ec7c2f 100644 --- a/crates/perry-codegen/src/lower_call/console_promise.rs +++ b/crates/perry-codegen/src/lower_call/console_promise.rs @@ -14,8 +14,7 @@ use perry_hir::types::Type as HirType; use perry_hir::Expr; use crate::rooting::{ - any_operand_may_collect, implicit_this_restore, implicit_this_save, operand_may_collect, - with_operands_rooted, with_rooted_group, + any_operand_may_collect, operand_may_collect, with_operands_rooted, with_rooted_group, }; use crate::expr::{ @@ -1364,21 +1363,17 @@ pub fn try_lower_closure_call_fallthrough( // The runtime checks the closure header on its own — if the value // isn't actually a closure, js_closure_call handles the error. // Issue #519: when the callee shape is `recv.method(args)` (a - // PropertyGet) — i.e. a method-style invocation — bind the - // receiver as the implicit `this` for the duration of the call. - // Non-arrow function bodies (including FuncRef wrappers) read - // `this` via `js_implicit_this_get` when their lexical - // this_stack is empty (codegen Expr::This fallback). Without - // this save/set/restore, hono's `RegExpRouter.match = match` - // (where `match` is an imported function declaration whose - // body does `this.buildAllMatchers()`) sees `this = undefined` - // and TypeErrors out at the first chained method call. + // PropertyGet) — i.e. a method-style invocation — pass the receiver as + // the body's `this` (`js_closure_call{N}(closure, this, ..)`). Without it, hono's + // `RegExpRouter.match = match` (where `match` is an imported function + // declaration whose body does `this.buildAllMatchers()`) sees + // `this = undefined` and TypeErrors out at the first chained method call. // // We evaluate `object` once into a fresh slot so that // (a) it's only side-effect-evaluated once, and // (b) the lowered `callee` (which re-reads `object` to get the - // property) and the IMPLICIT_THIS save/set both see the - // same receiver value. + // property) and the receiver passed as `this` are the same + // value. // // The `this`-binding / closure-unbox setup below is arity-independent; // only the final dispatch differs. Arities 0..=16 use the per-arity @@ -1578,67 +1573,6 @@ fn lower_closure_call_rooted<'a>( None => None, }; let recv_box = group.reread(ctx, callee_slot)?; - // One-argument receiverless calls pass `undefined` to the call entry, - // which binds the implicit-`this` cell to it only for a non-arrow callee - // (arrow-ness comes from the existing closure-strategy cache). Other - // arities retain the generated save/restore path below. - let receiverless_one_arg = - method_recv.is_none() && !matches!(callee, Expr::PropertyGet { .. }) && args.len() == 1; - - // #7211: the value `js_implicit_this_set` hands back is the PREVIOUS - // implicit `this`, read straight out of the `IMPLICIT_THIS` cell — which - // `object/this_binding.rs:176` registers as a scanned MUTABLE root the - // collector rewrites in place (`scan_implicit_this_roots_mut`). The swap - // has already overwritten the cell by the time we hold it, so this - // register is now the only copy this frame has, and it stays live across - // the allocating rebind unbox below AND the entire user-code dispatch. - // - // Two ways that hurts, and the second is the one that makes this worse - // than an ordinary stale read: - // - // * the enclosing frame still roots the same object (its own operand - // group, one temp-root frame down), so an evacuating minor inside the - // callee MOVES it and rewrites that root — leaving this register - // naming from-space. The restore then publishes a pre-move address - // back INTO a root the collector scans, so the corruption outlives the - // call that caused it and surfaces in whatever reads `this` next. - // * where no other root holds it, the object is simply collected. - // - // It WAS invisible to `scripts/gc_root_dominance_check.py` at both ends, - // which is how it survived #7206 and #7214: `js_implicit_this_set` was - // NONCOLLECTING but not in `ROOT_READ_CALLS`, so the register had no - // recognised heap-value source, and the restore is not a `RECEIVER_SINKS` - // fatal sink, so it would not have ranked even if it had. Being - // non-collecting is precisely what makes a call a root READ — the same - // rule `js_closure_get_capture_bits` is listed under — and it is now - // classified that way, so the checker reports any lowering that - // reintroduces this. - // - // Unconditional, unlike the operand groups above: the window is the user - // call itself, so `operand_protection`'s "can this window collect?" test - // has exactly one answer here and there is nothing to gate on. - // - // Six sibling lowerings emit the same pair; `implicit_this_save` / - // `implicit_this_restore` is the shared form, so a seventh cannot - // reintroduce this by copy-paste. - let prev_this_root = if let Some(ref this_val) = method_recv { - Some(implicit_this_save(ctx, this_val)) - } else if !matches!(callee, Expr::PropertyGet { .. }) && !receiverless_one_arg { - // Receiverless closure-value call (`fn()`, IIFE, `curry(1)(2)`): - // OrdinaryCallBindThis binds `this` to undefined — without the - // reset the enclosing method dispatch's IMPLICIT_THIS leaks into - // the callee (#3576). Member-shaped callees keep their existing - // receiver/skip behavior above. - // - // The value pushed here is the ENCLOSING method's receiver, not - // `undefined`: this arm is the one that runs for `helper()` called - // from inside `o.m()`, and dropping that object is #3576's leak with - // the sign flipped. - let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); - Some(implicit_this_save(ctx, &undef)) - } else { - None - }; // #5247: record the source location right before the throw-capable // dispatch — after the receiver/args are lowered, so a nested-call @@ -1654,7 +1588,7 @@ fn lower_closure_call_rooted<'a>( // a function` for any non-`POINTER_TAG` value. // #6475: a member-shaped call (`o.m(args)`) must rebind an // object-literal method's baked `this` capture slot to the receiver — - // the slot wins over the IMPLICIT_THIS cell set above, so a method + // the slot wins over the `this` parameter passed below, so a method // inherited via `Object.setPrototypeOf(obj, proto)` otherwise runs // with `this` bound to the proto literal (effect's Pipeable // `TagClass.pipe(...)` composed against the wrong `this` and @@ -1689,16 +1623,16 @@ fn lower_closure_call_rooted<'a>( lowered_args.push(group.reread(ctx, arg_base + i)?); } - let result = if receiverless_one_arg { - // A plain call binds `undefined` (OrdinaryCallBindThis); the entry - // binds the implicit-`this` cell to it unless the callee is an arrow. - super::emit_closure_handle_call_this( - ctx, - &closure_handle, - crate::expr::body_call::JS_THIS_UNDEFINED, - &lowered_args, - ) + let result = if recv_slot.is_some() { + // A member-shaped call binds its receiver: re-read it from its root + // below the (allocating) rebind unbox and pass it as the body's + // `this`. + let this_val = group.reread(ctx, 0)?; + let this_bits = ctx.block().bitcast_double_to_i64(&this_val); + super::emit_closure_handle_call_this(ctx, &closure_handle, &this_bits, &lowered_args) } else { + // A plain call: `js_closure_call{N}` hands the body `undefined` + // (OrdinaryCallBindThis; a sloppy body coerces it itself). // #3527: > 16 args marshal into an entry-block `[N x double]` buffer and // dispatch through the variadic `js_closure_call_array`. // @@ -1710,24 +1644,6 @@ fn lower_closure_call_rooted<'a>( super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args) }; - // #7211: re-read the saved implicit `this` from its slot. Mandatory, not - // defensive, and for the same reason the operand re-reads above are: the - // temp-root slot is a MUTABLE root, so an evacuating cycle anywhere inside - // the dispatch rewrote the slot and left the register that was pushed - // naming from-space. Restoring the register instead of the slot is the - // whole bug. - // - // Ordered inner-to-outer: this slot was pushed above `roots`' first slot, - // so it is dropped first and `roots.release` then drops the group below - // it. `js_gc_temp_root_truncate` drops everything at or above its - // argument, so `roots.release` alone would in fact take this slot with it - // — but only when `roots` actually pushed one, and a receiverless call on - // inert arguments pushes nothing at all. Releasing this one explicitly is - // what makes the order correct in both shapes rather than in the common - // one. - if let Some(prev) = prev_this_root { - implicit_this_restore(ctx, prev); - } // The group is released by `with_rooted_group` AFTER this returns, which is // after the dispatch — the dispatcher allocates while it reads these values. Ok(Some(result)) diff --git a/crates/perry-codegen/src/lower_call/early_branches.rs b/crates/perry-codegen/src/lower_call/early_branches.rs index 769a7bfbf5..7e4c72875d 100644 --- a/crates/perry-codegen/src/lower_call/early_branches.rs +++ b/crates/perry-codegen/src/lower_call/early_branches.rs @@ -389,7 +389,7 @@ pub fn try_lower_closure_typed_local_call( // enters it. if matches!(ctx.local_type_hint(id), Some(HirType::Function(_))) || ctx - .resolved_arrow_callback_targets + .resolved_plain_callback_targets .contains_key(&(*id, args.len())) { // #7803: the callee outlives the arguments here too, and this is @@ -448,13 +448,10 @@ pub fn try_lower_closure_typed_local_call( let blk = ctx.block(); unbox_to_i64(blk, &recv_box) }; - let undef_this = - crate::nanbox::double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); // A method callback parameter can be resolved once at entry when - // its actual value is a directly callable arrow. Keep the full - // dispatcher as a nullable-target fallback: TypeScript's function - // annotation is not a runtime proof, and ordinary functions must - // still receive receiverless `this === undefined` semantics. + // its actual value is directly callable. Keep the full dispatcher + // as a nullable-target fallback: TypeScript's function annotation + // is not a runtime proof. // // Exact immutable aliases (`const cb = callback`) have the same // identity whenever their read succeeds. A TDZ read throws while @@ -495,22 +492,22 @@ pub fn try_lower_closure_typed_local_call( } } if let Some(target) = ctx - .resolved_arrow_callback_targets + .resolved_plain_callback_targets .get(&(*id, lowered_args.len())) .cloned() { let fast_ok = ctx.block().icmp_ne(PTR, &target, "null"); - let fast_idx = ctx.new_block("callback_arrow_direct.fast"); - let fallback_idx = ctx.new_block("callback_arrow_direct.fallback"); - let merge_idx = ctx.new_block("callback_arrow_direct.merge"); + let fast_idx = ctx.new_block("callback_plain_direct.fast"); + let fallback_idx = ctx.new_block("callback_plain_direct.fallback"); + let merge_idx = ctx.new_block("callback_plain_direct.merge"); let fast_label = ctx.block_label(fast_idx); let fallback_label = ctx.block_label(fallback_idx); let merge_label = ctx.block_label(merge_idx); ctx.block().cond_br(&fast_ok, &fast_label, &fallback_label); ctx.current_block = fast_idx; - // The resolved target is an arrow (lexical `this`): the - // receiver parameter is unread. + // A plain call: the receiver is `undefined`, as + // `js_closure_callN` would pass it. let fast_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Pointer(&target), @@ -524,10 +521,9 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = fallback_idx; - let prev_this = crate::rooting::implicit_this_save(ctx, &undef_this); + // A plain call: the body receives `undefined`. let fallback_value = super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); - crate::rooting::implicit_this_restore(ctx, prev_this); let after_fallback = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); @@ -544,19 +540,10 @@ pub fn try_lower_closure_typed_local_call( callee_group.release(ctx); return Ok(Some(merged)); } - // Receiverless call of a closure-typed local: bind `this` to - // undefined for the duration of the call (OrdinaryCallBindThis, - // #3576) so an enclosing method dispatch's IMPLICIT_THIS does - // not leak into the callee body. Like the FuncRef path, the - // reset is gated on the statically-known callee actually reading - // dynamic `this`, so a hot-loop call of a plain helper closure - // pays nothing (#5030). When the typed-feedback guard falls back - // (the receiver is NOT the statically-mapped closure), the - // fallback block does its own reset — that callee is unknown. + // Receiverless call of a closure-typed local: a plain call, the + // body receives `undefined` as `this` (OrdinaryCallBindThis, + // #3576) — no enclosing receiver can leak into it. let known_func_id = ctx.local_closure_func_ids.get(id).copied(); - let callee_reads_this = known_func_id - .map(|fid| ctx.funcs_reading_dynamic_this.contains(&fid)) - .unwrap_or(true); if let Some(func_id) = known_func_id { let declared_count = ctx .local_closure_param_counts @@ -573,16 +560,6 @@ pub fn try_lower_closure_typed_local_call( &format!("closure:{}", func_id), TypedFeedbackContract::closure_direct_call(), ); - // #7211: rooted save/restore. The displaced value is the - // enclosing method's receiver and it is live across the - // callee body; the restore below sits in the merge block, - // so the slot index crosses the diamond exactly as the - // bare register used to. - let prev_this = if callee_reads_this { - Some(crate::rooting::implicit_this_save(ctx, &undef_this)) - } else { - None - }; let expected_arity = declared_count.to_string(); let call_arity = lowered_args.len().to_string(); let fast_idx = ctx.new_block("closure_direct.fast"); @@ -694,14 +671,8 @@ pub fn try_lower_closure_typed_local_call( } ctx.current_block = fast_idx; - // The body is handed the receiver the implicit-`this` cell - // holds for this call (stage 1): `undefined` when this site - // bound it above, else whatever the cell already holds. - let direct_this_bits = if prev_this.is_some() { - crate::expr::body_call::JS_THIS_UNDEFINED.to_string() - } else { - crate::expr::body_call::current_this_bits(ctx) - }; + // A plain call: the body receives `undefined`. + let direct_this_bits = crate::expr::body_call::JS_THIS_UNDEFINED; let typed_f64_param_reps = if ctx.typed_f64_closures.contains(&func_id) { ctx.typed_i1_closure_param_reps .get(&func_id) @@ -816,7 +787,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, - &direct_this_bits, + direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -917,7 +888,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, - &direct_this_bits, + direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1021,7 +992,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, - &direct_this_bits, + direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1128,7 +1099,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&generic_closure_fn), &closure_handle, - &direct_this_bits, + direct_this_bits, &lowered_args, ); let after_generic = ctx.block().label.clone(); @@ -1168,7 +1139,7 @@ pub fn try_lower_closure_typed_local_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_fn), &closure_handle, - &direct_this_bits, + direct_this_bits, &lowered_args, ) }; @@ -1183,22 +1154,9 @@ pub fn try_lower_closure_typed_local_call( "js_typed_feedback_record_fallback_call", &[(I64, &site_id)], ); - // Guard failed: the receiver is some OTHER closure whose - // body codegen never saw — reset `this` here (and only - // here) when the static gating skipped the outer reset. - let fallback_prev_this = if prev_this.is_none() { - Some(crate::rooting::implicit_this_save(ctx, &undef_this)) - } else { - None - }; + // Guard failed: some OTHER closure, a plain call. let fallback_value = super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); - // Inner save, released inside its own arm — so the outer - // slot (restored in the merge block) is still live and the - // temp-root depth matches on both paths into the merge. - if let Some(prev) = fallback_prev_this { - crate::rooting::implicit_this_restore(ctx, prev); - } let after_fallback = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); @@ -1212,9 +1170,6 @@ pub fn try_lower_closure_typed_local_call( (fallback_value.as_str(), after_fallback.as_str()), ], ); - if let Some(prev) = prev_this { - crate::rooting::implicit_this_restore(ctx, prev); - } // Below both arms' calls, in the merge that post-dominates // them — which is why this group is `open_rooted_group`. callee_group.release(ctx); @@ -1222,12 +1177,9 @@ pub fn try_lower_closure_typed_local_call( } } // Generic js_closure_callN dispatch (unknown func id, rest - // params, or arity mismatch): the runtime-resolved callee may - // read `this`, so the reset is unconditional here. - // #7211: rooted save/restore across the runtime-resolved callee. - let prev_this = crate::rooting::implicit_this_save(ctx, &undef_this); + // params, or arity mismatch): a plain call, the body receives + // `undefined`. let result = super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); - crate::rooting::implicit_this_restore(ctx, prev_this); callee_group.release(ctx); return Ok(Some(result)); } diff --git a/crates/perry-codegen/src/lower_call/func_ref.rs b/crates/perry-codegen/src/lower_call/func_ref.rs index 2b41338717..26cc673d26 100644 --- a/crates/perry-codegen/src/lower_call/func_ref.rs +++ b/crates/perry-codegen/src/lower_call/func_ref.rs @@ -1013,30 +1013,20 @@ pub fn try_lower_func_ref_call( lowered.iter().map(|s| (DOUBLE, s.as_str())).collect(); // OrdinaryCallBindThis for a receiverless call: `f()` binds `this` to - // undefined (sloppy bodies then substitute globalThis at the read). - // Without the reset, a bare call inside a method body leaks the - // enclosing dispatch's IMPLICIT_THIS into the callee — a nested - // `function inner(){ return this; }` called as `inner()` inside - // `o.m()` must NOT see `o` (#3576). Gated on the callee actually - // reading dynamic `this` so ordinary helper calls pay nothing. Args - // are lowered BEFORE the reset: `this` inside an argument expression - // still sees the enclosing binding. - let resets_this = ctx.funcs_reading_dynamic_this.contains(fid); - // #7211: rooted save/restore. The value displaced here is the ENCLOSING - // method's receiver, held across the callee body — arbitrary user code. - let prev_this = if resets_this { - let undef = double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED)); - Some(crate::rooting::implicit_this_save(ctx, &undef)) - } else { - None - }; + // undefined — a this-reading function's public symbol is a forwarder that + // passes `undefined` to its receiver-taking body (`codegen/function.rs`), + // so a nested `function inner(){ return this; }` called as `inner()` + // inside `o.m()` does not see `o` (#3576). Such a callee keeps to that + // public entry: the specialized and typed-clone arms below are not taken + // for it (unchanged from when this site reset a receiver cell). + let callee_reads_this = ctx.funcs_reading_dynamic_this.contains(fid); // Representation-selection Phase 2: specialized-ABI dispatch. Tier A // (static, guard-free) fires only when every slot's proof holds AT THIS // SITE; Tier B keeps the guarded-diamond shape. Any mismatch falls // through to the existing typed/generic chain — the public boxed entry is // the permanent fallback ABI. let spec_result: Option = if crate::codegen::spec_abi_enabled() - && !resets_this + && !callee_reads_this && !has_rest && !ctx.func_synthetic_arguments.contains(fid) && declared_count == args.len() @@ -1066,7 +1056,7 @@ pub fn try_lower_func_ref_call( // No `spec_result.is_none()` gate on the typed-clone candidate arms: plan // selection makes the spec and typed-clone sets mutually exclusive, and // the `if let` chain below consumes `spec_result` first anyway. - let typed_f64_call_param_reps = if !resets_this + let typed_f64_call_param_reps = if !callee_reads_this && !has_rest && !ctx.func_synthetic_arguments.contains(fid) && ctx.typed_f64_functions.contains(fid) @@ -1079,7 +1069,7 @@ pub fn try_lower_func_ref_call( } else { None }; - let typed_i32_call_param_reps = if !resets_this + let typed_i32_call_param_reps = if !callee_reads_this && !has_rest && !ctx.func_synthetic_arguments.contains(fid) && ctx.typed_i32_functions.contains(fid) @@ -1092,7 +1082,7 @@ pub fn try_lower_func_ref_call( } else { None }; - let typed_string_call_param_reps = if !resets_this + let typed_string_call_param_reps = if !callee_reads_this && !has_rest && !ctx.func_synthetic_arguments.contains(fid) && ctx.typed_string_functions.contains(fid) @@ -1105,7 +1095,7 @@ pub fn try_lower_func_ref_call( } else { None }; - let typed_i1_call_param_reps = if !resets_this + let typed_i1_call_param_reps = if !callee_reads_this && !has_rest && !ctx.func_synthetic_arguments.contains(fid) && ctx.typed_i1_functions.contains(fid) @@ -1463,18 +1453,6 @@ pub fn try_lower_func_ref_call( // because releasing on one side of it would leave the other side's call // reading dropped slots. // - // AFTER `implicit_this_restore`, and that order is load-bearing rather than - // stylistic. `implicit_this_save` runs BELOW the argument lowering, so its - // slot sits ABOVE this group, and `js_gc_temp_root_truncate` drops `base` - // and everything above it. Releasing first therefore drops the saved - // receiver, and `js_gc_temp_root_get` answers an out-of-range read with - // `0` — so the restore would rebind the enclosing method's `this` to the - // NUMBER 0. `implicit_this_restore` truncates at its own (higher) slot, and - // its doc calls out that a caller holding a lower group may release - // afterwards and drop the slot a second time harmlessly. - if let Some(prev) = prev_this { - crate::rooting::implicit_this_restore(ctx, prev); - } arg_group.release(ctx); if ctx.local_generator_funcs.contains(fid) { let wrap_ptr = format!("@__perry_wrap_{}", fname); diff --git a/crates/perry-codegen/src/lower_call/method_override.rs b/crates/perry-codegen/src/lower_call/method_override.rs index f926a8ceb0..f95043be38 100644 --- a/crates/perry-codegen/src/lower_call/method_override.rs +++ b/crates/perry-codegen/src/lower_call/method_override.rs @@ -782,9 +782,9 @@ pub(super) fn emit_own_method_override_check( // assigned via `this.method = fn` or `class X { method = fn; }` // (hono's RegExpRouter uses this exact shape — `match = match;` // assigns the imported standalone `match` function as an instance - // own-property; its body reads `this.buildAllMatchers()`). Bind - // `IMPLICIT_THIS` to the receiver around the call so non-arrow - // function bodies see the right `this` (issue #632 / #519 pattern). + // own-property; its body reads `this.buildAllMatchers()`). Pass the + // receiver as `this` so non-arrow function bodies see the right `this` + // (issue #632 / #519 pattern). ctx.current_block = override_idx; let user_arg_count = override_user_args.len(); let (args_ptr, args_len) = if user_arg_count == 0 { @@ -809,11 +809,7 @@ pub(super) fn emit_own_method_override_check( } else { this_box.to_string() }; - // #7211: rooted save/restore — the displaced implicit `this` is live - // across `js_native_call_value`, which runs arbitrary user code. - let prev_this = crate::rooting::implicit_this_save(ctx, &recv_for_this); - // Stage 1: the call runs with the implicit-`this` cell's value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); + let this_bits = ctx.block().bitcast_double_to_i64(&recv_for_this); let v_override = ctx.block().call( DOUBLE, "js_native_call_value", @@ -824,7 +820,6 @@ pub(super) fn emit_own_method_override_check( (I64, &args_len), ], ); - crate::rooting::implicit_this_restore(ctx, prev_this); let after_override = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); diff --git a/crates/perry-codegen/src/lower_call/mod.rs b/crates/perry-codegen/src/lower_call/mod.rs index 01c294a368..6a5b660a57 100644 --- a/crates/perry-codegen/src/lower_call/mod.rs +++ b/crates/perry-codegen/src/lower_call/mod.rs @@ -276,22 +276,23 @@ pub(crate) fn emit_rooted_call( pub(crate) const MAX_FIXED_CLOSURE_CALL_ARGS: usize = 16; /// Dispatch an unboxed closure handle over already-lowered arguments through -/// the closure-call ABI as a PLAIN call. Stage 1 of this-as-a-parameter: a -/// plain call keeps the implicit-`this` cell's current value, so the -/// receiver passed is the cell's ([`crate::expr::body_call::current_this_bits`]) -/// and the entry binds nothing. +/// the closure-call ABI as a PLAIN call (receiver `undefined`). pub(crate) fn emit_closure_handle_call( ctx: &mut FnCtx<'_>, closure_handle: &str, args: &[String], ) -> String { - let this_bits = crate::expr::body_call::current_this_bits(ctx); - emit_closure_handle_call_this(ctx, closure_handle, &this_bits, args) + emit_closure_handle_call_this( + ctx, + closure_handle, + crate::expr::body_call::JS_THIS_UNDEFINED, + args, + ) } /// Dispatch an unboxed closure handle with receiver `this_bits` (the -/// receiver's NaN-boxed bits, `i64`, valid here — nothing between here and -/// the dispatch collects). +/// receiver's NaN-boxed bits, `i64`, already re-read from its root by the +/// caller — nothing between here and the dispatch collects). /// /// Up to [`MAX_FIXED_CLOSURE_CALL_ARGS`] arguments use the per-arity /// `js_closure_call{N}(closure, this, ...)` register entry points. Wider diff --git a/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs b/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs index d1f74b291c..4a66e22e33 100644 --- a/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs +++ b/crates/perry-codegen/src/lower_call/pipeline_call_tests.rs @@ -86,17 +86,10 @@ fn immutable_function_alias_calls_the_known_symbol_directly() { } #[test] -fn mutable_function_alias_keeps_guarded_receiverless_dispatch() { +fn mutable_function_alias_keeps_guarded_plain_dispatch() { let ir = alias_call_ir(true); - // A plain call passes `undefined`; the entry owns binding it. assert!( - ir.lines() - .any(|l| l.contains("call double @js_closure_call1(") - && l.contains(&format!("i64 {}, double", crate::nanbox::TAG_UNDEFINED_I64))), + ir.contains("call double @js_closure_call1("), "a mutable alias has no stable target and must retain dynamic dispatch:\n{ir}" ); - assert!( - !ir.contains("call double @js_implicit_this_set"), - "the arrow-aware call entry owns receiverless this binding:\n{ir}" - ); } diff --git a/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs b/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs index 4e96c0cff6..997824042a 100644 --- a/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/property_get/dynamic_dispatch.rs @@ -506,30 +506,23 @@ pub(crate) fn try_lower_instance_method_call( )); (ptr_reg, user_arg_count_probe.to_string()) }; - // Issue #632: bind IMPLICIT_THIS to the receiver around - // the override call. The stored function may be a class - // field assigning a non-arrow function (`class X { match - // = match; }` — hono RegExpRouter — where the imported - // `match` body reads `this.buildAllMatchers()`). Without - // the bind, the body sees stale IMPLICIT_THIS and reads - // garbage. Mirrors `lower_call.rs:2607` for the closure- - // call fallthrough pattern (#519). - let recv_for_this_probe = recv_box.clone(); - // #7211: rooted save/restore across the user-code dispatch. - let prev_this_probe = crate::rooting::implicit_this_save(ctx, &recv_for_this_probe); - // Stage 1: the call runs with the implicit-`this` cell's value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); + // Issue #632: pass the receiver as `this` to the override + // call. The stored function may be a class field assigning a + // non-arrow function (`class X { match = match; }` — hono + // RegExpRouter — where the imported `match` body reads + // `this.buildAllMatchers()`). Mirrors the closure-call + // fallthrough pattern (#519). + let this_bits_probe = ctx.block().bitcast_double_to_i64(&recv_box); let v_override_probe = ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &own_method_probe), - (I64, &this_bits), + (I64, &this_bits_probe), (crate::types::PTR, &probe_args_ptr), (I64, &probe_args_len_str), ], ); - crate::rooting::implicit_this_restore(ctx, prev_this_probe); let after_override_probe = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&probe_outer_merge_label); @@ -1927,13 +1920,10 @@ fn emit_collapsed_instance_dispatch( ctx.block() .cond_br(&is_undef, &dispatch_label, &override_label); - // Override arm: bind IMPLICIT_THIS to the receiver and call the stored - // function value (#632 — a class-field non-arrow function reads `this`). + // Override arm: call the stored function value with the receiver as + // `this` (#632 — a class-field non-arrow function reads `this`). ctx.current_block = override_idx; - // #7211: rooted save/restore across the user-code dispatch. - let prev_this = crate::rooting::implicit_this_save(ctx, recv_box); - // Stage 1: the call runs with the implicit-`this` cell's value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); + let this_bits = ctx.block().bitcast_double_to_i64(recv_box); let v_override = ctx.block().call( DOUBLE, "js_native_call_value", @@ -1944,7 +1934,6 @@ fn emit_collapsed_instance_dispatch( (I64, &args_len), ], ); - crate::rooting::implicit_this_restore(ctx, prev_this); let after_override = ctx.block().label.clone(); if !ctx.block().is_terminated() { ctx.block().br(&merge_label); 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 4583b096e4..dc10cb8b13 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 @@ -242,9 +242,8 @@ pub(super) fn try_lower_imported_object_method_call( &closure_symbol, &fallback_label, ); - // This direct call does not bind the implicit-`this` cell, so the body - // is handed the receiver the cell already holds (stage 1). - let this_bits = crate::expr::body_call::current_this_bits(ctx); + // `recv.method(args)`: the method's receiver is `recv`. + let this_bits = recv_bits.clone(); let direct_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_symbol), @@ -398,8 +397,8 @@ pub(super) fn try_lower_dynamic_object_method_call( &closure_symbol, &miss_label, ); - // As above: the cell is not bound here, the body gets its value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); + // As above: the method's receiver is `recv`. + let this_bits = recv_bits.clone(); let direct_value = crate::expr::body_call::emit_js_body_call( ctx.block(), crate::expr::body_call::JsBody::Symbol(&closure_symbol), diff --git a/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs b/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs index ba478952f9..9d97f1bbe1 100644 --- a/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs +++ b/crates/perry-codegen/src/lower_call/property_get/static_dispatch.rs @@ -249,18 +249,15 @@ pub(crate) fn try_lower_static_dispatch( // register captured before it — see the rationale on this // closure's opening comment. let recv_box = group.reread(ctx, recv_idx)?; - // #7211: rooted save/restore — the displaced implicit `this` is - // live across the static method body below, which is user code. - let prev_this = crate::rooting::implicit_this_save(ctx, &recv_box); // Receiver-sensitive static `this`: arm the one-shot override with // the ACTUAL receiver box so the callee prologue's // `js_static_this_resolve` binds `this` to it (spec // OrdinaryCallBindThis). This must cover the dynamic-value receiver // shapes too (ClassExprFresh / factory `Call` / `LocalGet`), not // just plain class-refs: the prologue consumes the armed override - // or falls back to the LEXICAL class-ref — it never reads implicit - // `this` — so the previous implicit-this-only treatment of these - // shapes silently bound `this` to the shared template. A class + // or falls back to the LEXICAL class-ref, so binding the + // receiver any other way silently bound `this` to the shared + // template. A class // EXPRESSION's per-evaluation statics are OWN properties of the // fresh heap class object (never written to the template's // static-field globals), so `this.` inside the static body @@ -276,7 +273,6 @@ pub(crate) fn try_lower_static_dispatch( let arg_slices: Vec<(crate::types::LlvmType, &str)> = lowered.iter().map(|s| (DOUBLE, s.as_str())).collect(); let result = ctx.block().call(DOUBLE, &fn_name, &arg_slices); - crate::rooting::implicit_this_restore(ctx, prev_this); Ok(Some(result)) }); } @@ -338,14 +334,13 @@ pub(crate) fn try_lower_static_dispatch( let ptr_i64 = ctx.block().ptrtoint(&ptr_reg, I64); (ptr_i64, n.to_string()) }; - // Stage 1: the call runs with the implicit-`this` cell's value. - let this_bits = crate::expr::body_call::current_this_bits(ctx); return Ok(Some(ctx.block().call( DOUBLE, "js_native_call_value", &[ (DOUBLE, &callee_val), - (I64, &this_bits), + // A plain call, as it has always been lowered here. + (I64, crate::expr::body_call::JS_THIS_UNDEFINED), (I64, &args_ptr_i64), (I64, &args_len), ], diff --git a/crates/perry-codegen/src/rooting/group.rs b/crates/perry-codegen/src/rooting/group.rs index 9a656e0c71..8416f55a7c 100644 --- a/crates/perry-codegen/src/rooting/group.rs +++ b/crates/perry-codegen/src/rooting/group.rs @@ -9,7 +9,7 @@ use anyhow::Result; use perry_hir::Expr; use crate::expr::FnCtx; -use crate::types::{LlvmType, DOUBLE, I64}; +use crate::types::{LlvmType, DOUBLE}; use super::temp_root; use super::{call_void_with_roots, call_with_roots, read_slot, Arg, Repr, RootedSlot}; @@ -401,183 +401,8 @@ pub(crate) fn open_rooted_group<'a>(capacity: usize) -> RootedGroup<'a> { RootedGroup::new(capacity) } -/// A saved implicit `this`, held in a rooted slot for the duration of a -/// dispatch (#7211). -/// -/// **Moved here in slice 6 rather than re-exported.** It was already a paired -/// combinator with this file's contract — root before the window, re-read -/// after it, never hand out the register — but it lived in the raw API, so six -/// `lower_call/` modules had to name `expr::temp_root` while making no ordering -/// decision at all. Leaving a copy behind would have given the pair two -/// spellings, which is the drift that produced #7114; there is one. -/// -/// `js_implicit_this_set` swaps the `IMPLICIT_THIS` cell and returns what was -/// there, read straight out of a cell `scan_implicit_this_roots_mut` -/// (`object/this_binding.rs:176`) registers as a scanned MUTABLE root. The swap -/// has already overwritten the cell, so the returned value is now held ONLY in -/// an SSA register, across the whole call the bind exists to scope. -/// -/// Two ways that hurts, and the second is what makes it worse than an ordinary -/// stale read: -/// -/// * the enclosing frame still roots the same object, so an evacuating minor -/// inside the callee MOVES it and rewrites that root — leaving this register -/// naming from-space. The restore then publishes a pre-move address back -/// INTO a root the collector scans, so the corruption outlives the call that -/// caused it and surfaces in whatever reads `this` next; -/// * where no other root holds it, the object is simply collected. -/// -/// Seven lowerings emit this pair. They had seven copies of the same three -/// lines and therefore seven copies of the same bug, which is why it is a -/// combinator rather than seven edits. -pub(crate) struct ImplicitThisSave { - slot: RootedSlot, -} - -/// Bind `new_this` as the implicit `this` and root the value it displaced. -/// -/// Unconditional, unlike an operand group: the window is a user or native call, -/// so `operand_protection`'s "can this window collect?" test has exactly one -/// answer here and there is nothing to gate on. -pub(crate) fn implicit_this_save(ctx: &mut FnCtx<'_>, new_this: &str) -> ImplicitThisSave { - let prev = implicit_this_swap(ctx, new_this, "implicit_this.save"); - let idx = temp_root::temp_root_push_double(ctx, &prev); - ImplicitThisSave { - slot: RootedSlot { - idx, - repr: Repr::Boxed, - }, - } -} - -/// The implicit-`this` cell's address when this target reads it without a -/// call (ELF executables, `expr::agent_ptr`), for a save/restore pair that -/// wants to find the cell once: the address is per-thread and stable, so a -/// restore after the call may reuse it. -pub(crate) fn implicit_this_cell_ptr(ctx: &mut FnCtx<'_>) -> Option { - if crate::expr::agent_ptr::agent_ptr_access(ctx) - != crate::expr::agent_ptr::AgentPtrAccess::InitialExec - { - return None; - } - Some(crate::expr::agent_ptr::emit_agent_ptr( - ctx, - crate::runtime_abi::AGENT_PTR_IMPLICIT_THIS, - "perry_implicit_this_cell", - )) -} - -/// [`implicit_this_save`] through a cell address from -/// [`implicit_this_cell_ptr`]. -pub(crate) fn implicit_this_save_at( - ctx: &mut FnCtx<'_>, - cell: &str, - new_this: &str, -) -> ImplicitThisSave { - let prev = { - let blk = ctx.block(); - let prev_bits = blk.load(I64, cell); - let value_bits = blk.bitcast_double_to_i64(new_this); - // GC_STORE_AUDIT(ROOT_CELL): the implicit-`this` cell is a registered root. - blk.store(I64, &value_bits, cell); - blk.bitcast_i64_to_double(&prev_bits) - }; - let idx = temp_root::temp_root_push_double(ctx, &prev); - ImplicitThisSave { - slot: RootedSlot { - idx, - repr: Repr::Boxed, - }, - } -} - -/// [`implicit_this_restore`] through the same cell address. -pub(crate) fn implicit_this_restore_at(ctx: &mut FnCtx<'_>, cell: &str, save: ImplicitThisSave) { - let prev = read_slot(ctx, &save.slot); - save.slot.release(ctx); - let blk = ctx.block(); - let bits = blk.bitcast_double_to_i64(&prev); - // GC_STORE_AUDIT(ROOT_CELL): the implicit-`this` cell is a registered root. - blk.store(I64, &bits, cell); -} - -/// Restore the saved implicit `this`, re-read from its root. -/// -/// Reading the slot rather than the register is the fix, not a precaution: the -/// slot is a mutable root, so an evacuating cycle inside the dispatch rewrote -/// it and the register pushed beforehand names from-space. -/// -/// The release is emitted BEFORE the restore call so that nested saves — an -/// override arm inside an outer bind — release inner to outer. A release is a -/// stack cut, so a caller holding a LOWER group may release it afterwards and -/// drop this slot a second time harmlessly. -pub(crate) fn implicit_this_restore(ctx: &mut FnCtx<'_>, save: ImplicitThisSave) { - let prev = read_slot(ctx, &save.slot); - save.slot.release(ctx); - implicit_this_swap(ctx, &prev, "implicit_this.restore"); -} - -/// `js_implicit_this_set(value)`: bind `value` as the implicit `this` and -/// return the previous binding. On Apple aarch64 the cell is read and -/// written inline through the hot-cache lookup (`expr::hot_tls`), with the -/// runtime call as the fallback for every miss — the pair around a -/// dynamically-dispatched call was two runtime calls whose whole body was -/// that lookup plus a `replace`. -fn implicit_this_swap(ctx: &mut FnCtx<'_>, value: &str, stem: &str) -> String { - // ELF executables: the cell's address is a per-agent pointer read - // thread-pointer-relative (`expr::agent_ptr`), so the swap is a load and a - // store with no call. - if let Some(cell) = implicit_this_cell_ptr(ctx) { - let blk = ctx.block(); - let prev_bits = blk.load(I64, &cell); - let value_bits = blk.bitcast_double_to_i64(value); - // GC_STORE_AUDIT(ROOT_CELL): the implicit-`this` cell is a registered root. - blk.store(I64, &value_bits, &cell); - return blk.bitcast_i64_to_double(&prev_bits); - } - if !crate::expr::hot_tls::inline_hot_tls_enabled(ctx) { - return ctx - .block() - .call(DOUBLE, "js_implicit_this_set", &[(DOUBLE, value)]); - } - let lookup = crate::expr::hot_tls::emit_hot_tls_lookup(ctx, stem); - let merge_idx = ctx.new_block(&format!("{stem}.hot_tls.merge")); - let merge_label = ctx.block_label(merge_idx); - let cell = crate::expr::hot_tls::hot_tls_field( - ctx, - &lookup.hot, - crate::expr::hot_tls::HOT_TLS_IMPLICIT_THIS_OFFSET, - ); - let (fast_prev, fast_pred) = { - let blk = ctx.block(); - let prev_bits = blk.load(I64, &cell); - let value_bits = blk.bitcast_double_to_i64(value); - blk.store(I64, &value_bits, &cell); - let prev = blk.bitcast_i64_to_double(&prev_bits); - let pred = blk.label.clone(); - blk.br(&merge_label); - (prev, pred) - }; - ctx.current_block = lookup.slow_idx; - let (slow_prev, slow_pred) = { - let blk = ctx.block(); - let prev = blk.call(DOUBLE, "js_implicit_this_set", &[(DOUBLE, value)]); - let pred = blk.label.clone(); - blk.br(&merge_label); - (prev, pred) - }; - ctx.current_block = merge_idx; - ctx.block().phi( - DOUBLE, - &[(&fast_prev, &fast_pred), (&slow_prev, &slow_pred)], - ) -} - /// The `new.target` cell's saved previous value (#7664). /// -/// Structurally [`ImplicitThisSave`] for a different cell, and it is a separate -/// type rather than a parameter so the two cannot be crossed at a restore. -/// /// The cell is a registered mutable root — `scan_current_new_target_root_mut`, /// `gc/mod.rs` — so an evacuating cycle inside the constructor rewrites it and /// a register saved beforehand names from-space. The RUNTIME's own construct @@ -620,8 +445,8 @@ pub(crate) fn new_target_save_for_super(ctx: &mut FnCtx<'_>) -> Option, expr: &Expr) -> bool { mod group; mod ledger; -// `ImplicitThisSave` and `NewTargetSave` are deliberately absent: no caller -// names either type — both are only ever held as an inferred local between the -// `*_save` and `*_restore` pair — so re-exporting them would be an unused -// import. They stay `pub(crate)` in `group.rs`, where the functions that -// produce them live. +// `NewTargetSave` is deliberately absent: no caller names the type — it is +// only ever held as an inferred local between the `new_target_save` and +// `new_target_restore` pair — so re-exporting it would be an unused import. It +// stays `pub(crate)` in `group.rs`, where the functions that produce it live. pub(crate) use group::{ - implicit_this_cell_ptr, implicit_this_restore, implicit_this_restore_at, implicit_this_save, - implicit_this_save_at, new_target_restore, new_target_save, new_target_save_for_super, - open_rooted_group, with_rooted_accumulator, with_rooted_group, AccArray, EmittedValue, - RootedAcc, RootedGroup, + new_target_restore, new_target_save, new_target_save_for_super, open_rooted_group, + with_rooted_accumulator, with_rooted_group, AccArray, EmittedValue, RootedAcc, RootedGroup, }; // --------------------------------------------------------------------------- diff --git a/crates/perry-codegen/src/rooting/temp_root.rs b/crates/perry-codegen/src/rooting/temp_root.rs index d7bcf25ad3..061570b38d 100644 --- a/crates/perry-codegen/src/rooting/temp_root.rs +++ b/crates/perry-codegen/src/rooting/temp_root.rs @@ -224,10 +224,9 @@ pub(in crate::rooting) fn temp_root_truncate(ctx: &mut FnCtx<'_>, idx: &str) { // above it. Each released slot is zeroed (dropping its retention) and // its frame mirror cleared; the pool entry becomes reusable. // - // A repeated release of an already-released handle (the documented - // `implicit_this_restore` → outer-group interleaving) finds no - // watermark position and is the same harmless no-op the FFI's - // `base < len` guard made it. + // A repeated release of an already-released handle (an inner release + // interleaved with an outer group's) finds no watermark position and + // is the same harmless no-op the FFI's `base < len` guard made it. let Some(pos) = ctx.temp_roots.position(idx) else { return; }; diff --git a/crates/perry-codegen/src/runtime_decls/objects.rs b/crates/perry-codegen/src/runtime_decls/objects.rs index 1d0e82583a..c8ccfbebc7 100644 --- a/crates/perry-codegen/src/runtime_decls/objects.rs +++ b/crates/perry-codegen/src/runtime_decls/objects.rs @@ -628,7 +628,6 @@ pub fn declare_phase_b_objects(module: &mut LlModule) { &format!("[{} x ptr]", crate::expr::agent_ptr::AGENT_PTR_SLOTS), "initialexec", ); - module.declare_function("perry_implicit_this_cell", PTR, &[]); module.declare_function( "js_method_site_miss", DOUBLE, 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 720c0f2967..2d828ee635 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 @@ -417,9 +417,6 @@ pub(crate) fn declare_core(module: &mut LlModule) { // `js_native_call_method`'s field-scan dispatch when invoking a // closure-typed class field method-style. `Expr::This` codegen reads // this when the lexical this_stack is empty. - module.declare_function("js_implicit_this_get", DOUBLE, &[]); - module.declare_function("js_implicit_this_get_sloppy", DOUBLE, &[]); - module.declare_function("js_implicit_this_set", DOUBLE, &[DOUBLE]); // Static-method prologue `this`: takes the one-shot receiver override // armed by dynamic static dispatch / call/apply, else returns the // lexical class-ref argument. diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index 87551cf469..bb4f191632 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -286,7 +286,7 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { params.extend(std::iter::repeat_n(DOUBLE, argc)); module.declare_function(name, DOUBLE, ¶ms); } - module.declare_function("js_closure_resolve_arrow_direct_call", PTR, &[I64, I32]); + module.declare_function("js_closure_resolve_plain_direct_call", PTR, &[I64, I32]); // Phase B.16 / D follow-ups: more runtime functions discovered // by the test-files sweep histogram. diff --git a/crates/perry-codegen/src/runtime_decls/strings_part2.rs b/crates/perry-codegen/src/runtime_decls/strings_part2.rs index b540e7e5df..3a035ecbda 100644 --- a/crates/perry-codegen/src/runtime_decls/strings_part2.rs +++ b/crates/perry-codegen/src/runtime_decls/strings_part2.rs @@ -11,10 +11,6 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) { // by a `PERRY_RECV_ROUTE_COUNT=1` build; declared unconditionally so the // declaration set does not depend on a diagnostic knob. module.declare_function("js_recv_route_note", VOID, &[I32]); - // This-as-a-parameter stage-1 witness (`expr/body_call.rs`): emitted only - // by a `PERRY_THIS_WITNESS=1` build; declared unconditionally for the same - // reason. - module.declare_function("js_this_param_witness", VOID, &[I64, PTR, I64]); // OrdinaryCallBindThis for a sloppy body's receiver parameter // (`expr::body_call::emit_receiver_prologue`). Can allocate: a safepoint. module.declare_function("js_this_coerce_sloppy", DOUBLE, &[DOUBLE]); diff --git a/crates/perry-codegen/src/wasm32/runtime_abi.tsv b/crates/perry-codegen/src/wasm32/runtime_abi.tsv index c6e3e6a04a..3efc722d6c 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -618,6 +618,22 @@ js_closure_alloc_init ptr ptr,i32u,ptr js_closure_alloc_singleton ptr ptr js_closure_alloc_with_captures_singleton ptr ptr,i32u,ptr js_closure_call0 f64 ptr,i64 +js_closure_call1 f64 ptr,i64,f64 +js_closure_call10 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call11 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call12 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call13 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call14 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call15 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call16 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call2 f64 ptr,i64,f64,f64 +js_closure_call3 f64 ptr,i64,f64,f64,f64 +js_closure_call4 f64 ptr,i64,f64,f64,f64,f64 +js_closure_call5 f64 ptr,i64,f64,f64,f64,f64,f64 +js_closure_call6 f64 ptr,i64,f64,f64,f64,f64,f64,f64 +js_closure_call7 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call8 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64 +js_closure_call9 f64 ptr,i64,f64,f64,f64,f64,f64,f64,f64,f64,f64 js_closure_call_apply_with_spread f64 f64,i64,ptr,i64,i64 js_closure_call_array f64 i64,i64,ptr,i64 js_closure_exact_func_guard i64 f64,ptr @@ -625,7 +641,7 @@ js_closure_get_capture_bits i64 ptr,i32u js_closure_get_capture_f64 f64 ptr,i32u js_closure_get_capture_ptr i64 ptr,i32u js_closure_get_func ptr ptr -js_closure_resolve_arrow_direct_call ptr ptr,i32u +js_closure_resolve_plain_direct_call ptr ptr,i32u js_closure_resolve_versioned_loop_direct_call ptr ptr,i32u js_closure_set_box_capture_ptr void ptr,i32u,i64 js_closure_set_capture_bits void ptr,i32u,i64 @@ -1654,9 +1670,6 @@ js_i32_box_scope_release void ptr js_i32_box_set void ptr,i32s js_ic_diag_note_hit void i64,i32s js_immediate_has_pending i32s -js_implicit_this_get f64 -js_implicit_this_get_sloppy f64 -js_implicit_this_set f64 f64 js_import_meta_resolve f64 f64,f64,f64 js_import_meta_resolve_value f64 f64 js_in_operator f64 f64,f64 @@ -3684,7 +3697,6 @@ js_text_encoder_new i64 js_text_encoder_stream_new f64 js_text_encoding_stream_new f64 js_this_coerce_sloppy f64 f64 -js_this_param_witness void i64,ptr,i64 js_thread_has_pending i32s js_thread_parallel_filter f64 f64,f64 js_thread_parallel_map f64 f64,f64 @@ -4598,7 +4610,6 @@ perry_i18n_plural_category i32s i32s,f64 perry_i18n_set_currencies void ptr,i32u perry_i18n_set_locale_index void i32s perry_i18n_set_plural_locales void ptr,ptr,i32u -perry_implicit_this_cell ptr perry_init_guard_check_and_set i32s i64 perry_ios_classes_registered void perry_ios_get_connected_scene i64 diff --git a/crates/perry-runtime/src/agent_ptrs.rs b/crates/perry-runtime/src/agent_ptrs.rs index dea7597c77..b5a5b783a4 100644 --- a/crates/perry-runtime/src/agent_ptrs.rs +++ b/crates/perry-runtime/src/agent_ptrs.rs @@ -1,10 +1,10 @@ //! Per-agent runtime pointers that generated code reads WITHOUT a call. //! -//! Some runtime state is per agent (per thread) and read on hot emitted paths: -//! the first is the implicit-`this` cell a direct method call binds around -//! the call (`perry-codegen/src/expr/method_site.rs`), which otherwise costs -//! two runtime calls per method call. This block is the one place such -//! pointers live, at fixed slots, so emitted code can load one directly: +//! Some runtime state is per agent (per thread) and read on hot emitted paths. +//! This block is the one place such pointers live, at fixed slots, so emitted +//! code can load one directly. No slot is published today: slot 1 held the +//! implicit-`this` cell until this-as-a-parameter deleted it, and slot 0 is +//! reserved for the megamorphic follow-up's shape-record directory. //! //! * ELF executables (Linux, Android; x86-64 and aarch64): an `initialexec` //! thread-local access to [`PERRY_AGENT_PTRS`] — `mov %fs:off` (or @@ -24,8 +24,6 @@ use std::cell::Cell; -/// Slot 1: this agent's implicit-`this` cell. (Slot 0 is reserved.) -pub use crate::codegen_abi::AGENT_PTR_IMPLICIT_THIS; /// Slots in the block. **Must equal `AGENT_PTR_SLOTS` in /// `perry-codegen/src/expr/agent_ptr.rs`** (the emitted global's type). pub use crate::codegen_abi::AGENT_PTR_SLOTS; @@ -49,6 +47,7 @@ pub static PERRY_AGENT_PTRS: AgentPtrs = AgentPtrs([const { Cell::new(std::ptr::null()) }; AGENT_PTR_SLOTS]); /// Publish (or clear, with null) one of this agent's pointers. +#[allow(dead_code)] // no slot is published today (see the module doc) #[inline] pub(crate) fn publish(slot: usize, ptr: *const u8) { PERRY_AGENT_PTRS.0[slot].set(ptr); @@ -59,28 +58,3 @@ pub(crate) fn publish(slot: usize, ptr: *const u8) { pub(crate) fn hot_addr() -> *mut u8 { &PERRY_AGENT_PTRS as *const AgentPtrs as *mut u8 } - -/// This agent's implicit-`this` bits, read through the published slot — a -/// direct `#[thread_local]` access and one load, no `thread_local!` accessor — -/// publishing the slot on this agent's first read. -#[inline(always)] -pub(crate) fn implicit_this_bits() -> u64 { - let mut cell = PERRY_AGENT_PTRS.0[AGENT_PTR_IMPLICIT_THIS].get(); - if cell.is_null() { - cell = perry_implicit_this_cell(); - } - // SAFETY: a published slot points at this agent's live `HotTls` cell (a - // slot is cleared before the state it points into is freed); `Cell` - // has `u64`'s layout. - unsafe { *(cell as *const u64) } -} - -/// The address of this agent's implicit-`this` cell, published into slot -/// [`AGENT_PTR_IMPLICIT_THIS`] for emitted code that binds `this` around a -/// direct method call. A leaf: one TLS read and one store. -#[no_mangle] -pub extern "C" fn perry_implicit_this_cell() -> *const u8 { - let cell = &crate::tls_hot::hot().implicit_this as *const std::cell::Cell as *const u8; - publish(AGENT_PTR_IMPLICIT_THIS, cell); - cell -} diff --git a/crates/perry-runtime/src/array/generic.rs b/crates/perry-runtime/src/array/generic.rs index e9244c1528..1580b91f44 100644 --- a/crates/perry-runtime/src/array/generic.rs +++ b/crates/perry-runtime/src/array/generic.rs @@ -34,7 +34,7 @@ use super::generic_object::{ object_splice, object_unshift, }; use super::*; -use crate::closure::{js_closure_call3, ClosureHeader}; +use crate::closure::ClosureHeader; use crate::value::{JSValue, TAG_HOLE, TAG_NULL, TAG_TRUE, TAG_UNDEFINED}; use std::ptr; @@ -580,7 +580,7 @@ pub extern "C" fn js_arraylike_forEach(recv: f64, cb: f64, this_arg: f64) -> f64 return undef(); } let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -593,9 +593,9 @@ pub extern "C" fn js_arraylike_forEach(recv: f64, cb: f64, this_arg: f64) -> f64 continue; } let v = al_get(recv_h.get_nanbox_f64(), k); - js_closure_call3( + crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -607,7 +607,7 @@ pub extern "C" fn js_arraylike_forEach(recv: f64, cb: f64, this_arg: f64) -> f64 #[no_mangle] pub extern "C" fn js_arraylike_map(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -632,9 +632,9 @@ pub extern "C" fn js_arraylike_map(recv: f64, cb: f64, this_arg: f64) -> f64 { // element pointer was derived before it. The pre-call address is never // bound, so there is nothing stale to reach for. let (mapped, result) = result_h.across_mut::(|| { - js_closure_call3( + crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -656,7 +656,7 @@ pub extern "C" fn js_arraylike_map(recv: f64, cb: f64, this_arg: f64) -> f64 { #[no_mangle] pub extern "C" fn js_arraylike_filter(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -674,9 +674,9 @@ pub extern "C" fn js_arraylike_filter(recv: f64, cb: f64, this_arg: f64) -> f64 continue; } v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); - let keep = js_closure_call3( + let keep = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -699,7 +699,7 @@ pub extern "C" fn js_arraylike_filter(recv: f64, cb: f64, this_arg: f64) -> f64 #[no_mangle] pub extern "C" fn js_arraylike_some(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -712,9 +712,9 @@ pub extern "C" fn js_arraylike_some(recv: f64, cb: f64, this_arg: f64) -> f64 { continue; } let v = al_get(recv_h.get_nanbox_f64(), k); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -729,7 +729,7 @@ pub extern "C" fn js_arraylike_some(recv: f64, cb: f64, this_arg: f64) -> f64 { #[no_mangle] pub extern "C" fn js_arraylike_every(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -742,9 +742,9 @@ pub extern "C" fn js_arraylike_every(recv: f64, cb: f64, this_arg: f64) -> f64 { continue; } let v = al_get(recv_h.get_nanbox_f64(), k); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -762,7 +762,7 @@ pub extern "C" fn js_arraylike_every(recv: f64, cb: f64, this_arg: f64) -> f64 { #[no_mangle] pub extern "C" fn js_arraylike_find(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -774,9 +774,9 @@ pub extern "C" fn js_arraylike_find(recv: f64, cb: f64, this_arg: f64) -> f64 { let v_h = scope.root_nanbox_f64(undef()); for k in 0..len { v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -791,7 +791,7 @@ pub extern "C" fn js_arraylike_find(recv: f64, cb: f64, this_arg: f64) -> f64 { #[no_mangle] pub extern "C" fn js_arraylike_findIndex(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -801,9 +801,9 @@ pub extern "C" fn js_arraylike_findIndex(recv: f64, cb: f64, this_arg: f64) -> f callable(cb_h.get_nanbox_f64()); for k in 0..len { let v = al_get(recv_h.get_nanbox_f64(), k); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -818,7 +818,7 @@ pub extern "C" fn js_arraylike_findIndex(recv: f64, cb: f64, this_arg: f64) -> f #[no_mangle] pub extern "C" fn js_arraylike_findLast(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -831,9 +831,9 @@ pub extern "C" fn js_arraylike_findLast(recv: f64, cb: f64, this_arg: f64) -> f6 let mut k = len - 1; while k >= 0 { v_h.set_nanbox_f64(al_get(recv_h.get_nanbox_f64(), k)); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v_h.get_nanbox_f64(), k as f64, recv_h.get_nanbox_f64(), @@ -849,7 +849,7 @@ pub extern "C" fn js_arraylike_findLast(recv: f64, cb: f64, this_arg: f64) -> f6 #[no_mangle] pub extern "C" fn js_arraylike_findLastIndex(recv: f64, cb: f64, this_arg: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let _g = crate::object::ImplicitThisScope::bind(&scope, this_arg); + let this_h = scope.root_nanbox_f64(this_arg); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -860,9 +860,9 @@ pub extern "C" fn js_arraylike_findLastIndex(recv: f64, cb: f64, this_arg: f64) let mut k = len - 1; while k >= 0 { let v = al_get(recv_h.get_nanbox_f64(), k); - let hit = js_closure_call3( + let hit = crate::closure::js_closure_call3( callable(cb_h.get_nanbox_f64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), v, k as f64, recv_h.get_nanbox_f64(), @@ -882,8 +882,6 @@ pub extern "C" fn js_arraylike_findLastIndex(recv: f64, cb: f64, this_arg: f64) #[no_mangle] pub extern "C" fn js_arraylike_reduce(recv: f64, cb: f64, has_init: i32, init: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - // #11419: `this` is undefined in the callback (no thisArg parameter). - let _this = crate::object::ImplicitThisScope::bind_undefined(&scope); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -928,8 +926,6 @@ pub extern "C" fn js_arraylike_reduce(recv: f64, cb: f64, has_init: i32, init: f #[no_mangle] pub extern "C" fn js_arraylike_reduceRight(recv: f64, cb: f64, has_init: i32, init: f64) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - // #11419: `this` is undefined in the callback (no thisArg parameter). - let _this = crate::object::ImplicitThisScope::bind_undefined(&scope); let cb_h = scope.root_nanbox_f64(cb); let recv_h = scope.root_nanbox_f64(to_object(recv)); // Spec order: LengthOfArrayLike(O) is read *before* the IsCallable(cb) @@ -1398,12 +1394,11 @@ pub fn dispatch_arraylike_read_method( /// method thunks (`Array.prototype.pop`, etc.). A real-array receiver routes to /// the dense helpers; a plain array-like object routes to the spec-generic /// engine; any other receiver yields `undefined`. `recv` is the call-site -/// `this` (IMPLICIT_THIS) the thunk read. +/// `this` (the receiver parameter) the thunk read. pub fn array_proto_mutator(recv: f64, method: &str, args_ptr: *const f64, args_len: usize) -> f64 { // #6908: every mutator's step 1 is `ToObject(this value)`, which throws // for a nullish receiver. This is the receiver-less thunk invocation - // (`const f = arr.push; f(3)` — IMPLICIT_THIS holds its `undefined` - // default), which previously fell through every normalization below and + // (`const f = arr.push; f(3)` — a plain call passes `undefined`), which previously fell through every normalization below and // silently no-opped; node throws. let bits = recv.to_bits(); if bits == TAG_UNDEFINED || bits == TAG_NULL { diff --git a/crates/perry-runtime/src/array/generic_object.rs b/crates/perry-runtime/src/array/generic_object.rs index 534c676ddf..d8ebd5f943 100644 --- a/crates/perry-runtime/src/array/generic_object.rs +++ b/crates/perry-runtime/src/array/generic_object.rs @@ -382,8 +382,7 @@ pub(crate) fn object_sort(recv: f64, cmp_validated: *const ClosureHeader) -> f64 // Length and indexed getters may collect before comparison begins. let scope = crate::gc::RuntimeHandleScope::new(); let recv_handle = scope.root_nanbox_f64(recv); - // #11419: the comparator runs with `this` undefined. - let _this = crate::object::ImplicitThisScope::bind_undefined(&scope); + // #11419: the comparator runs with `this` undefined (a plain call). let cmp_handle = scope.root_raw_const_ptr(cmp_validated); let cmp = if cmp_validated.is_null() { None diff --git a/crates/perry-runtime/src/array/iter_methods.rs b/crates/perry-runtime/src/array/iter_methods.rs index 4a1e90d990..a5de77ff62 100644 --- a/crates/perry-runtime/src/array/iter_methods.rs +++ b/crates/perry-runtime/src/array/iter_methods.rs @@ -151,24 +151,6 @@ mod rooted_iter_array_tests { } } -/// Bind the callback's `this` to `undefined` for the duration of a dense -/// iteration (spec: absent `thisArg` means the callback's `this` is -/// `undefined` — NOT whatever ambient receiver the enclosing call left in -/// IMPLICIT_THIS; test262 some/15.4.4.17-5-25, filter/15.4.4.20-5-30). -/// Explicit-`thisArg` call sites route through the `js_arraylike_*` engine -/// instead of these helpers. Arrow callbacks capture `this` lexically and -/// are unaffected. -/// -/// The displaced receiver is the caller's `this`, held across every callback -/// in the loop, so it is restored from a root in the iteration's `scope` -/// (#10490 — this guard used to keep it in a plain field). -#[inline] -fn bind_undefined_this( - scope: &crate::gc::RuntimeHandleScope, -) -> crate::object::ImplicitThisScope<'_> { - crate::object::ImplicitThisScope::bind_undefined(scope) -} - /// #5989/#8117: `.forEach` on a receiver codegen could not prove is a /// collection is statically fused to the ARRAY entry point below, so a native /// `Set`/`Map` arrives there. Run the collection's own `forEach` and report @@ -282,7 +264,6 @@ pub extern "C" fn js_array_forEach(arr: *const ArrayHeader, callback: *const Clo Some(h) => h.get_nanbox_f64(), None => rooted.receiver(), }; - let _tg = bind_undefined_this(&scope); if crate::array::array_iteration_is_exotic(arr) { for i in 0..length as usize { let arr = rooted.arr(); @@ -370,7 +351,6 @@ pub extern "C" fn js_array_map( // conservative scan; that knob was deleted in #7611, so there is no // longer a configuration in which this rooting is optional. See gh #6206. let cb_handle = scope.root_raw_const_ptr(callback); - let _tg = bind_undefined_this(&scope); // ECMA-262 §23.1.3.20 step 5: ArraySpeciesCreate(O, len) runs BEFORE // the iteration — it reads `O.constructor` / `@@species` (firing any @@ -495,7 +475,6 @@ pub extern "C" fn js_array_map_discard(arr: *const ArrayHeader, callback: *const let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); if crate::array::array_iteration_is_exotic(arr) { for i in 0..length as usize { let arr = rooted.arr(); @@ -570,7 +549,6 @@ pub extern "C" fn js_array_filter( let cb_site = crate::closure::DirectCall3::resolve(callback); // Root the callback across the loop — see js_array_map / gh #6206. let cb_handle = scope.root_raw_const_ptr(callback); - let _tg = bind_undefined_this(&scope); // ECMA-262 §23.1.3.7 step 5: ArraySpeciesCreate(O, 0) runs before the // iteration (validates `O.constructor` / `@@species`, throwing on a @@ -672,7 +650,6 @@ pub extern "C" fn js_array_find(arr: *const ArrayHeader, callback: *const Closur let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in 0..length as usize { @@ -748,7 +725,6 @@ pub extern "C" fn js_array_findIndex( let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in 0..length as usize { @@ -809,7 +785,6 @@ pub extern "C" fn js_array_find_last( let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in (0..length).rev() { let element = if exotic { @@ -867,7 +842,6 @@ pub extern "C" fn js_array_find_last_index( let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in (0..length).rev() { let element = if exotic { @@ -982,7 +956,6 @@ pub extern "C" fn js_array_some(arr: *const ArrayHeader, callback: *const Closur let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in 0..length as usize { @@ -1080,7 +1053,7 @@ pub extern "C" fn js_array_some_captureless( }; let result = callback( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), element, i as f64, array_receiver_value(arr), @@ -1151,7 +1124,6 @@ pub extern "C" fn js_array_every(arr: *const ArrayHeader, callback: *const Closu let current_callback = || { crate::value::js_nanbox_get_pointer(cb_handle.get_nanbox_f64()) as *const ClosureHeader }; - let _tg = bind_undefined_this(&scope); let exotic = crate::array::array_iteration_is_exotic(arr); for i in 0..length as usize { @@ -1226,7 +1198,6 @@ pub extern "C" fn js_array_flatMap( crate::value::JSValue::pointer(result as *const u8).bits(), )); }; - let _tg = bind_undefined_this(&scope); for i in 0..length as usize { let Some(element) = rooted.present(i) else { @@ -1279,10 +1250,8 @@ pub extern "C" fn js_array_reduce( has_initial: i32, initial: f64, ) -> f64 { - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback runs with `this` undefined (a plain call), not the + // receiver of whatever method dispatch encloses this call. // #8137: a Buffer-backed `Uint8Array` receiver. Perry's // `new Uint8Array([…])` is a `BufferHeader`, absent from the typed-array // registry, so the `lookup_typed_array_kind` re-dispatch below never diff --git a/crates/perry-runtime/src/array/iterator.rs b/crates/perry-runtime/src/array/iterator.rs index 46354b050f..095dce8c81 100644 --- a/crates/perry-runtime/src/array/iterator.rs +++ b/crates/perry-runtime/src/array/iterator.rs @@ -490,11 +490,6 @@ fn async_from_sync_call_raw(iter: f64, method: &[u8], args: &[f64]) -> Result Result Result f64 { if !is_callable_value(method) { throw_iterator_method_not_callable(); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); let iterator = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value), std::ptr::null(), 0, ) }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // GetIterator step 5: the result must be an Object. if !is_async_iterator_object(iterator) { throw_iterator_result_not_object(); @@ -969,8 +955,8 @@ pub(crate) fn array_from_spread_value(value: f64) -> *mut ArrayHeader { // * `clone_closure_rebind_this(method, value)` would bind a from-space // `this`, so a user `[Symbol.iterator]()` factory reads a dead // receiver and yields nothing; - // * `js_implicit_this_set(value)` publishes the same dead receiver to - // the canonical bound-method path; + // * the `[Symbol.iterator]()` call's receiver would be the same dead + // object for the canonical bound-method path; // * the `js_array_is_array(value)` fallback reads a recycled GcHeader // and reports a live array as "not iterable". // @@ -1201,15 +1187,10 @@ pub(crate) fn array_from_spread_value(value: f64) -> *mut ArrayHeader { } // Spec `GetIterator(obj)` → `Call(method, obj)`: the // `[Symbol.iterator]()` factory runs with `this === obj`. A canonical - // bound class method (#5128's `@@iterator` wrapper) reads its receiver - // from IMPLICIT_THIS, so set it here too — mirroring `js_get_iterator`. - // Without this the wrapper saw a stale `this` and the generator + // bound class method (#5128's `@@iterator` wrapper) takes its + // receiver from the call, so pass it — mirroring `js_get_iterator`. + // Without it the wrapper saw a stale `this` and the generator // yielded nothing (empty spread). - let prev_this = crate::object::js_implicit_this_set(value()); - // The DISPLACED receiver rides through arbitrary user code before - // being republished, so it is rooted too — republishing a from-space - // `this` is the same defect one frame out. - let prev_this_h = scope.root_nanbox_f64(prev_this); let trap_buf = crate::exception::js_try_push(); // `js_try_push` captured the handle-stack depth AFTER these roots // were pushed, so the `longjmp` restore leaves them intact and @@ -1220,25 +1201,22 @@ pub(crate) fn array_from_spread_value(value: f64) -> *mut ArrayHeader { crate::closure::js_closure_call0( js_nanbox_get_pointer(rebound_h.get_nanbox_f64()) as *const crate::closure::ClosureHeader, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value()), ) }); let iter = match outcome { Some(iter) => iter, None => { - // Factory threw: restore the receiver and unwind the trap - // frame before re-propagating, so IMPLICIT_THIS can't leak - // into later calls (mirrors `async_from_sync_call_cached_raw` + // Factory threw: unwind the trap frame before + // re-propagating (mirrors `async_from_sync_call_cached_raw` // above). let exc = crate::exception::js_get_exception(); crate::exception::js_clear_exception(); - crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); crate::exception::js_try_end(); crate::exception::js_throw(exc) } }; let iter_h = scope.root_nanbox_f64(iter); - crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); crate::exception::js_try_end(); if crate::array::js_array_is_array(iter_h.get_nanbox_f64()).to_bits() == crate::value::TAG_TRUE @@ -1384,18 +1362,14 @@ fn throw_iterator_result_not_object() -> ! { pub extern "C" fn js_iterator_next_result(iter_f64: f64) -> f64 { let next = named_field(iter_f64, b"next"); let result = if is_callable_value(next) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( next, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(iter_f64), std::ptr::null(), 0, ) - }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + } } else if next.to_bits() == crate::value::TAG_UNDEFINED && is_builtin_iterator_class_id(crate::value::js_nanbox_get_pointer(iter_f64) as usize) { @@ -1454,17 +1428,14 @@ pub extern "C" fn js_iterator_close_if_not_done(iter_f64: f64, done_f64: f64) -> crate::closure::throw_not_callable(); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); let result = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( ret, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(iter_f64), std::ptr::null(), 0, ) }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if !is_object_like_value(result) { throw_iterator_result_not_object(); } @@ -1538,16 +1509,11 @@ pub(crate) fn sync_iterator_to_array_if_not_async(iter_f64: f64) -> Option<*mut } else { // Call(next, iterator) — bind `this` like `js_iterator_to_array` // does for its stored-closure path (#9019). - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let r = closure::js_closure_call1( + closure::js_closure_call1( next_ptr, - crate::closure::plain_call_receiver(), + closure::JsThis::from_f64(iter_f64), f64::from_bits(TAG_UNDEFINED), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - r + ) }; if crate::promise::js_value_is_promise(step) != 0 { return None; @@ -1582,17 +1548,14 @@ pub(crate) fn call_symbol_async_iterator(value: f64) -> Option { if !is_callable_value(method) { return None; } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); let iterator = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value), std::ptr::null(), 0, ) }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None } else { @@ -1760,15 +1723,11 @@ fn js_iterator_to_array_impl(iter_f64: f64) -> *mut ArrayHeader { // Call(next, iterator): bind `this` for the stored-closure path // exactly like `js_iterator_next_result` — a user-assigned // `it.next = function () { … }` may read `this` (#9019). - let prev_this = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_h.get_nanbox_f64())); - let r = closure::js_closure_call1( + closure::js_closure_call1( js_nanbox_get_pointer(next_h.get_nanbox_f64()) as *const closure::ClosureHeader, - crate::closure::plain_call_receiver(), + closure::JsThis::from_f64(iter_h.get_nanbox_f64()), f64::from_bits(TAG_UNDEFINED), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - r + ) }; // IteratorNext (ECMA-262 §7.4.2 step 3): if Type(result) is not // Object, throw a TypeError. `is_pointer()` is true only for @@ -1884,16 +1843,11 @@ fn js_async_iterator_to_array(iter_f64: f64) -> *mut ArrayHeader { } else { // Call(next, iterator) — bind `this` for the stored-closure path // (#9019), mirroring `js_iterator_to_array`. - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_f64)); - let r = closure::js_closure_call1( + closure::js_closure_call1( next_ptr, - crate::closure::plain_call_receiver(), + closure::JsThis::from_f64(iter_f64), f64::from_bits(TAG_UNDEFINED), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - r + ) }; let Some(step_result) = settled_promise_value(step) else { break; diff --git a/crates/perry-runtime/src/array/reduce_right.rs b/crates/perry-runtime/src/array/reduce_right.rs index 8ef1389e22..072344a604 100644 --- a/crates/perry-runtime/src/array/reduce_right.rs +++ b/crates/perry-runtime/src/array/reduce_right.rs @@ -22,10 +22,8 @@ pub extern "C" fn js_array_reduce_right( has_initial: i32, initial: f64, ) -> f64 { - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback runs with `this` undefined (a plain call), not + // the receiver of whatever method dispatch encloses this call. // #8137: a Buffer-backed `Uint8Array` receiver reads as an `ArrayHeader` // below — correct `length`, GARBAGE elements. `reduceRight` is the widest // case in the family: it is wrong even for a STATICALLY typed diff --git a/crates/perry-runtime/src/array/sort.rs b/crates/perry-runtime/src/array/sort.rs index bfbf9493aa..a217758088 100644 --- a/crates/perry-runtime/src/array/sort.rs +++ b/crates/perry-runtime/src/array/sort.rs @@ -769,10 +769,8 @@ pub extern "C" fn js_array_sort_with_comparator( if comparator.is_null() { return js_array_sort_default(arr); } - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback runs with `this` undefined (a plain call), not + // the receiver of whatever method dispatch encloses this call. unsafe { // Runtime plain-object receiver behind a statically-Array variable — // probe the RAW pointer before the array-plausibility clean. diff --git a/crates/perry-runtime/src/async_hooks.rs b/crates/perry-runtime/src/async_hooks.rs index 461a10b713..ea67e2cfc1 100644 --- a/crates/perry-runtime/src/async_hooks.rs +++ b/crates/perry-runtime/src/async_hooks.rs @@ -11,9 +11,9 @@ use std::sync::{LazyLock, Mutex}; use crate::array::{js_array_length, ArrayHeader}; use crate::closure::{ - js_closure_alloc, js_closure_call1, js_closure_call4, 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, + 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, }; use crate::object::{js_object_get_field_by_name, ObjectHeader}; use crate::string::{js_string_from_bytes, StringHeader}; @@ -1616,16 +1616,13 @@ pub extern "C" fn js_async_resource_run_in_async_scope( } let outcome = try_run_resource_scope(ids, || { let callback = crate::fs::extract_closure_ptr(rebound_handle.get_nanbox_f64()); - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_arg_handle.get_nanbox_f64(), - )); let callback_outcome = crate::exception::js_call_catching(|| { args_array_handle.with_const_ptr::(|arr| { if arr.is_null() { unsafe { - js_closure_call_array( + crate::closure::js_closure_call_array( callback as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), ptr::null(), 0, ) @@ -1636,9 +1633,9 @@ pub extern "C" fn js_async_resource_run_in_async_scope( crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 }; unsafe { - js_closure_call_array( + crate::closure::js_closure_call_array( callback as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), data, len, ) @@ -1646,7 +1643,6 @@ pub extern "C" fn js_async_resource_run_in_async_scope( } }) }); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); match callback_outcome { Ok(value) => value, Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/async_hooks/context_snapshots.rs b/crates/perry-runtime/src/async_hooks/context_snapshots.rs index 7c6a1400a3..aaf81ecf63 100644 --- a/crates/perry-runtime/src/async_hooks/context_snapshots.rs +++ b/crates/perry-runtime/src/async_hooks/context_snapshots.rs @@ -54,14 +54,11 @@ pub(super) fn call_callback_with_rest(callback_value: f64, this_arg: f64, rest: } let args_array = ptr_from_nanboxed(rest) as *const ArrayHeader; let args_array_handle = scope.root_raw_const_ptr(args_array); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_arg_handle.get_nanbox_f64(), - )); let result = if args_array.is_null() { unsafe { - js_closure_call_array( + crate::closure::js_closure_call_array( callback as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), ptr::null(), 0, ) @@ -75,15 +72,14 @@ pub(super) fn call_callback_with_rest(callback_value: f64, this_arg: f64, rest: unsafe { crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64 } }; unsafe { - js_closure_call_array( + crate::closure::js_closure_call_array( callback as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), data, len, ) } }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/async_hooks/provider_ffi.rs b/crates/perry-runtime/src/async_hooks/provider_ffi.rs index 967c0a027f..cf5f524a12 100644 --- a/crates/perry-runtime/src/async_hooks/provider_ffi.rs +++ b/crates/perry-runtime/src/async_hooks/provider_ffi.rs @@ -216,8 +216,10 @@ unsafe fn provider_run_catching( } /// Provider callback wrapper for external EventEmitter-style dispatch. It -/// additionally restores implicit `this` and can retire a one-shot provider -/// before propagating a JavaScript exception. +/// additionally roots the listener receiver for the duration of the callback +/// and can retire a one-shot provider before propagating a JavaScript +/// exception. `callback` makes its own JS calls and passes them the receiver +/// itself: nothing here binds `this` for it. #[no_mangle] pub unsafe extern "C" fn js_async_hooks_provider_run_catching_with_this( async_id: u64, @@ -227,7 +229,7 @@ pub unsafe extern "C" fn js_async_hooks_provider_run_catching_with_this( data: *mut std::ffi::c_void, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let this_value = scope.root_nanbox_f64(this_value); + let _receiver_root = scope.root_nanbox_f64(this_value); if let Err(error) = try_enter_resource_scope(provider_ids(async_id)) { let error = scope.root_nanbox_f64(error); if destroy_after != 0 { @@ -238,15 +240,11 @@ pub unsafe extern "C" fn js_async_hooks_provider_run_catching_with_this( } crate::exception::js_throw(error.get_nanbox_f64()); } - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_value.get_nanbox_f64(), - )); let outcome = crate::exception::js_call_catching(|| callback(data)); let (threw, result) = match outcome { Ok(value) => (false, scope.root_nanbox_f64(value)), Err(error) => (true, scope.root_nanbox_f64(error)), }; - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); let leave = try_leave_resource_scope(async_id); let (leave_threw, leave_result) = match leave { Ok(()) => ( diff --git a/crates/perry-runtime/src/bun_compat/plugin.rs b/crates/perry-runtime/src/bun_compat/plugin.rs index 80e43b405b..38cb2aaaf5 100644 --- a/crates/perry-runtime/src/bun_compat/plugin.rs +++ b/crates/perry-runtime/src/bun_compat/plugin.rs @@ -100,21 +100,20 @@ fn register(plugin: f64) -> f64 { b"config", js_nanbox_pointer(js_object_alloc(0, 0) as i64), ); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set(if is_function { - undefined() - } else { - plugin.get_nanbox_f64() - })); let result = crate::exception::catch_js_throw(|| unsafe { let args = [build.get_nanbox_f64()]; - crate::closure::js_native_call_value( + let receiver = if is_function { + undefined() + } else { + plugin.get_nanbox_f64() + }; + crate::closure::native_call_value_this( setup.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args.as_ptr(), 1, ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); let result = match result { Ok(value) => value, Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/bun_compat/self_global.rs b/crates/perry-runtime/src/bun_compat/self_global.rs index 2110e1cd96..8d77af1a48 100644 --- a/crates/perry-runtime/src/bun_compat/self_global.rs +++ b/crates/perry-runtime/src/bun_compat/self_global.rs @@ -100,11 +100,7 @@ mod tests { global_value.to_bits() ); - set_self( - std::ptr::null(), - crate::closure::body_call::current_this(), - 42.0, - ); + set_self(std::ptr::null(), crate::closure::JsThis::UNDEFINED, 42.0); assert!(crate::object::get_accessor_descriptor(global as usize, "self").is_none()); super::super::js_set_bun_platform(); assert_eq!( diff --git a/crates/perry-runtime/src/bun_compat/spawn.rs b/crates/perry-runtime/src/bun_compat/spawn.rs index 5f604b35cd..773490f969 100644 --- a/crates/perry-runtime/src/bun_compat/spawn.rs +++ b/crates/perry-runtime/src/bun_compat/spawn.rs @@ -90,20 +90,15 @@ fn call_method(receiver: f64, name: &[u8], args: &[f64]) -> f64 { } let method = scope.root_nanbox_f64(method); let args = scope.root_nanbox_f64_slice(args); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - result + } } fn closure_with_captures(func: *const u8, arity: u32, captures: &[f64]) -> f64 { diff --git a/crates/perry-runtime/src/child_process/emitter.rs b/crates/perry-runtime/src/child_process/emitter.rs index 8c3d58e170..43b111b50c 100644 --- a/crates/perry-runtime/src/child_process/emitter.rs +++ b/crates/perry-runtime/src/child_process/emitter.rs @@ -2,9 +2,8 @@ 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, ClosureHeader, + js_register_closure_arity, native_call_value_this, ClosureHeader, JsThis, }; -use crate::object::js_implicit_this_set; use crate::string::js_string_from_bytes; use crate::value::JSValue; @@ -54,9 +53,6 @@ pub(crate) fn cp_emit(target: f64, event: &str, args: &[f64]) -> bool { let key = cp_listener_key(event); let mut i: u32 = 0; let mut fired = false; - let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); loop { let arr = match cp_array_ptr(cp_get_field(target.get_nanbox_f64(), &key)) { Some(a) => a, @@ -66,17 +62,15 @@ pub(crate) fn cp_emit(target: f64, event: &str, args: &[f64]) -> bool { break; } let cb = crate::array::js_array_get_f64(arr, i); - js_implicit_this_set(target.get_nanbox_f64()); let current_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); unsafe { - let _ = js_native_call_value( + let _ = native_call_value_this( cb, - crate::closure::plain_call_receiver(), + JsThis::from_f64(target.get_nanbox_f64()), current_args.as_ptr(), current_args.len(), ); } - js_implicit_this_set(prev.get_nanbox_f64()); fired = true; i += 1; } @@ -351,18 +345,11 @@ pub(crate) extern "C" fn cp_pipe_data_thunk( let dest = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let write = cp_get_field(dest, b"write"); if !crate::fs::extract_closure_ptr(write).is_null() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(js_implicit_this_set(dest)); let args = [chunk]; unsafe { - let _ = js_native_call_value( - write, - crate::closure::plain_call_receiver(), - args.as_ptr(), - args.len(), - ); + let _ = + native_call_value_this(write, JsThis::from_f64(dest), args.as_ptr(), args.len()); } - js_implicit_this_set(prev.get_nanbox_f64()); } cp_undefined() } @@ -377,14 +364,10 @@ pub(crate) extern "C" fn cp_pipe_end_thunk( let dest = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); let end = cp_get_field(dest, b"end"); if !crate::fs::extract_closure_ptr(end).is_null() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(js_implicit_this_set(dest)); let args = [cp_undefined()]; unsafe { - let _ = - js_native_call_value(end, crate::closure::plain_call_receiver(), args.as_ptr(), 0); + let _ = native_call_value_this(end, JsThis::from_f64(dest), args.as_ptr(), 0); } - js_implicit_this_set(prev.get_nanbox_f64()); } 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 dcdeaedd4e..f87d5edf34 100644 --- a/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs +++ b/crates/perry-runtime/src/child_process/emitter/relocation_tests.rs @@ -22,10 +22,10 @@ extern "C" fn relocate( extern "C" fn observe( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - let target = crate::object::js_implicit_this_get(); + let target = this.as_f64(); cp_set_field(target, b"seen", arg); cp_undefined() } diff --git a/crates/perry-runtime/src/closure/body_call.rs b/crates/perry-runtime/src/closure/body_call.rs index aebe395591..61870b1aa1 100644 --- a/crates/perry-runtime/src/closure/body_call.rs +++ b/crates/perry-runtime/src/closure/body_call.rs @@ -32,44 +32,21 @@ //! declares N parameters reads only the first N (`wide_call.rs`). /// The receiver a JS body takes as its second native parameter -/// (`perry_abi::JS_BODY_THIS_PARAM`): the NaN-boxed `this` bits, passed in an -/// integer register. Defined once in perry-abi and shared with perry-ffi. -/// -/// Stage 1 of this-as-a-parameter: every caller passes the receiver the -/// implicit-`this` cell holds for the call (a runtime caller reads it with -/// [`current_this`]), and bodies still read the cell. A native body declares -/// the parameter and must not assume anything about it yet. +/// (`perry_abi::JS_BODY_THIS_PARAM`), defined once in perry-abi and shared +/// with perry-ffi. It is the ONLY way a body learns its receiver: a +/// method-style caller passes the receiver, a plain call +/// [`JsThis::UNDEFINED`]. There is no ambient `this` state. pub use crate::codegen_abi::JsThis; const _: () = assert!(crate::codegen_abi::TAG_UNDEFINED == crate::value::TAG_UNDEFINED); -/// The receiver the implicit-`this` cell holds right now: what a body called -/// at this point would read from the cell. Read through the per-agent pointer -/// block (`agent_ptrs`), the cheapest path to the cell on every target but -/// Apple aarch64, where `HotTls` is the fast path. +/// The receiver of a PLAIN call — a call with no receiver: `f(x)` through a +/// function value, a callback a builtin invokes without a `thisArg`: +/// `undefined` (OrdinaryCallBindThis; a sloppy body coerces it to +/// `globalThis` itself). #[inline(always)] -pub fn current_this() -> JsThis { - #[cfg(not(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" - )))] - return JsThis(crate::agent_ptrs::implicit_this_bits()); - #[cfg(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" - ))] - return JsThis(crate::object::implicit_this_bits()); -} - -/// The receiver a runtime caller passes for a PLAIN call (a call with no -/// receiver of its own). Stage 1 keeps the plain-call behavior of the cell: -/// a plain call runs with whatever the cell holds, so this is -/// [`current_this`], and the entry it is passed to binds nothing. -#[inline(always)] -pub fn plain_call_receiver() -> JsThis { - current_this() +pub const fn plain_call_receiver() -> JsThis { + JsThis::UNDEFINED } /// The native type of a JS body taking the receiver and one `f64` per @@ -174,3 +151,19 @@ pub(crate) use { js_bare_body_fn, js_body_call, js_body_call_unwind, js_body_fn, js_body_fn_ty, js_method_body_call, js_method_body_fn, }; + +/// Call function value `func` with receiver `this` and `args`: the Rust-side +/// method-style call (`thisArg` of a builtin, an emitter, a getter's holder). +/// A plain call passes [`plain_call_receiver`]. +/// +/// # Safety +/// As [`crate::closure::js_native_call_value`]. +#[inline] +pub unsafe fn call_value(func: f64, this: JsThis, args: &[f64]) -> f64 { + let ptr = if args.is_empty() { + std::ptr::null() + } else { + args.as_ptr() + }; + crate::closure::native_call_value_this(func, this, ptr, args.len()) +} diff --git a/crates/perry-runtime/src/closure/dispatch.rs b/crates/perry-runtime/src/closure/dispatch.rs index a4cb31bdb9..76e4390de4 100644 --- a/crates/perry-runtime/src/closure/dispatch.rs +++ b/crates/perry-runtime/src/closure/dispatch.rs @@ -40,6 +40,7 @@ pub use calln::{ }; pub use direct::{DirectCall1, DirectCall2, DirectCall3, DirectCall4}; +pub(crate) use value_call::native_call_value_this; pub use value_call::{ js_closure_call_apply_with_spread, js_closure_call_array, js_native_call_value, }; diff --git a/crates/perry-runtime/src/closure/dispatch/bound.rs b/crates/perry-runtime/src/closure/dispatch/bound.rs index 75439b60ed..6a44c86a40 100644 --- a/crates/perry-runtime/src/closure/dispatch/bound.rs +++ b/crates/perry-runtime/src/closure/dispatch/bound.rs @@ -58,7 +58,11 @@ pub(crate) unsafe fn bound_method_source_func_ptr(closure: *const ClosureHeader) /// Extracts the namespace object and method name from the closure captures, /// then calls js_native_call_method with the packed arguments. #[inline] -pub unsafe fn dispatch_bound_method(closure: *const ClosureHeader, args: &[f64]) -> f64 { +pub unsafe fn dispatch_bound_method( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + args: &[f64], +) -> f64 { let mut namespace_obj = js_closure_get_capture_f64(closure, 0); let method_name_ptr = js_closure_get_capture_ptr(closure, 1) as *const i8; let method_name_len = js_closure_get_capture_ptr(closure, 2) as usize; @@ -95,10 +99,10 @@ pub unsafe fn dispatch_bound_method(closure: *const ClosureHeader, args: &[f64]) if let Some((func_ptr, param_count, has_synth_args, has_rest)) = crate::object::lookup_class_method_in_chain(owner_id, name) { - // The call-time `this` (IMPLICIT_THIS) is the receiver the - // private method body runs against — for `f.call(o)` it is + // The call-time `this` is the receiver the private + // method body runs against — for `f.call(o)` it is // `o`, for a bare `f()` it is undefined. - let call_this = crate::object::js_implicit_this_get(); + let call_this = this.as_f64(); return if let Some(brand) = private_brand { crate::object::call_vtable_method_with_private_brand( func_ptr, @@ -127,7 +131,7 @@ pub unsafe fn dispatch_bound_method(closure: *const ClosureHeader, args: &[f64]) if let (Some(owner_id), Some(brand)) = (crate::object::class_ref_id(namespace_obj), private_brand) { - let call_this = crate::object::js_implicit_this_get(); + let call_this = this.as_f64(); if let Some(result) = crate::object::call_private_static_method_for_owner( owner_id, name, @@ -147,10 +151,10 @@ pub unsafe fn dispatch_bound_method(closure: *const ClosureHeader, args: &[f64]) // Canonical class method value (test262 method identity): a class method is // a single shared function object whose captured receiver is the OWNER // class's prototype-ref — a marker, not the real `this`. The actual receiver - // is the call-site `this` (IMPLICIT_THIS): for `const f = c.m; f()` that is - // the spec `this`, and for `this.m = this.m.bind(this)` the outer - // `dispatch_bound_function` has already set IMPLICIT_THIS to the instance so - // the rebind targets the right object. Ordinary `obj.method(args)` calls do + // is the call-site `this` (the `this` this dispatch was handed): for + // `const f = c.m; f()` that is the spec `this`, and for + // `this.m = this.m.bind(this)` the outer `dispatch_bound_function` passes + // the instance so the rebind targets the right object. Ordinary `obj.method(args)` calls do // NOT reach here (they lower straight to `js_native_call_method`), so this // only governs method-as-value invocations. // The captured slot-0 prototype-ref names the OWNER class. A method value is a @@ -168,7 +172,7 @@ pub unsafe fn dispatch_bound_method(closure: *const ClosureHeader, args: &[f64]) // exactly the failure the comment below describes for the self-shadowing `bind` // case. let owner_proto_ref = namespace_obj; - let call_receiver = crate::object::canonical_bound_method_receiver(owner_proto_ref); + let call_receiver = crate::object::canonical_bound_method_receiver(owner_proto_ref, this); if method_name_len > 0 && !method_name_ptr.is_null() { if let Some(owner_id) = crate::object::class_prototype_ref_id(owner_proto_ref) { if let Ok(name) = std::str::from_utf8(std::slice::from_raw_parts( @@ -249,12 +253,13 @@ unsafe fn dispatch_symbol_bound_method( let has_rest = (meta >> 32) & 1 == 1; let is_static = (meta >> 33) & 1 == 1; if is_static { - // Bind IMPLICIT_THIS to the class ref for the duration, exactly like + // The static method runs with the class ref as `this`, exactly like // the direct-call path. The one-shot static-`this` override (armed by // the Function.prototype call/apply arms for a static bound-method // value) still wins in the static-method prologue. - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); + // A static body binds `this` in its own prologue + // (`js_static_this_resolve`: this override, else its class ref), so + // it takes no receiver parameter. crate::object::static_private_owner_push(receiver); let result = crate::object::call_registered_static_method( func_ptr, @@ -264,7 +269,6 @@ unsafe fn dispatch_symbol_bound_method( has_rest, ); crate::object::static_private_owner_pop(); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } else { // Computed symbol methods never synthesize an `arguments` object but @@ -283,8 +287,8 @@ unsafe fn dispatch_symbol_bound_method( /// Dispatch a `Function.prototype.bind` result (BOUND_FUNCTION_FUNC_PTR /// sentinel). Reads the bound target/this/partial-args from the closure -/// captures, prepends the bound args to the call-time args, sets -/// `IMPLICIT_THIS` to the bound receiver, and invokes the target closure. +/// captures, prepends the bound args to the call-time args, and invokes the +/// target closure with the bound receiver as its `this`. /// Refs #2840. #[inline] pub unsafe fn dispatch_bound_function(closure: *const ClosureHeader, args: &[f64]) -> f64 { @@ -328,19 +332,16 @@ pub unsafe fn dispatch_bound_function(closure: *const ClosureHeader, args: &[f64 }; // A bound concise/object-literal method reads `this` from its baked capture - // slot, not IMPLICIT_THIS — rebind it to the bound receiver so the bound - // `this` is honored (arrows/non-captures_this targets are returned as-is). + // slot, not only the `this` parameter — rebind it to the bound receiver so + // the bound `this` is honored (arrows/non-captures_this targets are + // returned as-is). let target = rebind_explicit_this(target, bound_this); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(bound_this)); - let result = js_native_call_value( + super::value_call::native_call_value_this( target, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(bound_this), call_ptr, call_len, - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + ) } /// OrdinaryCallBindThis for the `call`/`apply`/`bind` entry points: box a @@ -405,7 +406,7 @@ pub(crate) fn coerce_call_this(target: f64, this_arg: f64) -> f64 { /// object-literal method (and object-literal accessor / symbol method) is /// lowered with `captures_this` and its reserved (last) capture slot baked to /// the *defining object* at construction time — so its body reads `this` from -/// that slot, NOT from `IMPLICIT_THIS`. Setting `IMPLICIT_THIS` therefore can't +/// that slot, NOT from its `this` parameter. Passing a receiver therefore can't /// redirect such a method to an explicit receiver: the baked slot wins, and the /// explicit `this` is silently ignored. /// @@ -420,7 +421,7 @@ pub(crate) fn coerce_call_this(target: f64, this_arg: f64) -> f64 { /// receiver is honored. Returns `target` UNCHANGED for: /// - arrow functions (lexical `this` — they must ignore an explicit receiver, /// yet still carry `CAPTURES_THIS_FLAG`, so they are excluded explicitly); -/// - non-`captures_this` functions (they already read `IMPLICIT_THIS`); +/// - non-`captures_this` functions (they already read the `this` parameter); /// - bound functions and non-closure values /// (`clone_closure_rebind_this` no-ops on these — it only rewrites a /// `CAPTURES_THIS` slot). diff --git a/crates/perry-runtime/src/closure/dispatch/calln.rs b/crates/perry-runtime/src/closure/dispatch/calln.rs index b15ad134f0..b0de9c1f25 100644 --- a/crates/perry-runtime/src/closure/dispatch/calln.rs +++ b/crates/perry-runtime/src/closure/dispatch/calln.rs @@ -1,982 +1,565 @@ -//! Per-arity `js_closure_callN(closure, this, a0..)` FFI entry points -//! (0..=16) and the shared `dispatch_registered_call` / -//! `dispatch_rest_or_declared_arity` routing helpers. +//! Per-arity closure-call FFI entry points (0..=16): +//! `js_closure_call{N}(closure, this, a0..)`, where `this` is the call's +//! receiver (`JsThis::UNDEFINED` for a plain call), and the shared routing +//! helpers. //! -//! Stage 1 of this-as-a-parameter: bodies still read the implicit-`this` -//! cell, and the routing below passes the cell's value as every body's -//! receiver parameter. An entry therefore makes the cell hold the `this` it -//! is handed for the duration of the call ([`with_receiver`]); a caller that -//! passes the cell's current value (every runtime plain call, -//! `plain_call_receiver()`) binds nothing. +//! Each funnels into one per-arity dispatcher that routes bound +//! methods/functions, rest bundling, arity padding and the direct body call, +//! threading the receiver to the body as its `this` parameter +//! (`perry_abi::JS_BODY_THIS_PARAM`). //! //! The hot-loop counterpart -- resolve a closure ONCE and call it directly for -//! the rest of the loop -- lives in the sibling `direct` module (#8180). It -//! subsumes the `resolve_call2_direct` helper that used to sit here and had -//! exactly one consumer. +//! the rest of the loop -- lives in the sibling `direct` module (#8180). use super::*; use crate::closure::JsThis; -/// Run `call` with the implicit-`this` cell holding `this`. When the cell -/// already holds it — the common case — nothing is bound. Otherwise the cell -/// is bound for the call and the displaced value restored after it, rooted -/// across the call (an `ImplicitThisScope`), except for an arrow callee, whose -/// `this` is lexical and reads neither the cell nor the parameter. -#[inline(always)] -pub(crate) fn with_receiver( - closure: *const ClosureHeader, - this: JsThis, - call: impl FnOnce() -> f64, -) -> f64 { - if this == crate::closure::body_call::current_this() - || crate::closure::closure_is_arrow(closure) - { - return call(); - } - let scope = crate::gc::RuntimeHandleScope::new(); - let _bound = crate::object::ImplicitThisScope::bind(&scope, this.as_f64()); - call() +macro_rules! closure_call_dispatch { + ($dispatch:ident, $n:literal; $($a:ident),*) => { + /// 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() { + return dispatch_proxy_callee_or_throw(closure, this, &[$($a),*]); + } + match resolve_strategy(func_ptr).kind() { + DispatchKind::BoundMethod => unsafe { + dispatch_bound_method(closure, this, &[$($a),*]) + }, + DispatchKind::BoundFunction => unsafe { + dispatch_bound_function(closure, &[$($a),*]) + }, + DispatchKind::Rest(fixed_arity, synth) => unsafe { + dispatch_rest_bundled(closure, func_ptr, this, &[$($a),*], fixed_arity, synth) + }, + DispatchKind::Arity(declared) if arity_needs_dispatch(declared, $n) => unsafe { + dispatch_with_arity(closure, func_ptr, this, &[$($a),*], declared) + }, + _ => unsafe { + crate::closure::body_call::js_body_call!(func_ptr, closure, this $(, $a)*) + }, + } + } + }; } +// `js_closure_call0` is `extern "C-unwind"` in unwinding (test) builds so an +// interpreted throw can reach a generated caller's catch landing pad; +// production keeps plain C (#8479). +closure_call_dispatch!(dispatch_call0, 0;); + /// Call a closure with receiver `this` and no arguments. #[cfg(panic = "abort")] #[no_mangle] pub extern "C" fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { - with_receiver(closure, this, || js_closure_call0_impl(closure)) + dispatch_call0(closure, this) } -/// Test/debug builds use Rust unwinding for JS exceptions. Keep this entry -/// point unwind-capable there so an interpreted throw can reach a generated -/// caller's catch landing pad. Production builds use the plain-C definition -/// above because their raw Itanium exceptions must cross it without Rust's -/// abort-on-unwind guard (#8479). +/// See the production definition above. #[cfg(not(panic = "abort"))] #[no_mangle] pub extern "C-unwind" fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { - with_receiver(closure, this, || js_closure_call0_impl(closure)) + dispatch_call0(closure, this) } // #8479: NOT `C-unwind` (below). The runtime is built `panic=abort` and JS // throws travel as a raw Itanium `_Unwind_Exception` that must step THROUGH -// these frames untouched (see `crate::eh`). -macro_rules! closure_call_entry { - ($entry:ident, $cell:ident, $n:literal; $($a:ident),+) => { - #[doc = concat!("Call a closure with receiver `this` and ", stringify!($n), " argument(s).")] - #[no_mangle] - pub extern "C" fn $entry(closure: *const ClosureHeader, this: JsThis, $($a: f64),+) -> f64 { - with_receiver(closure, this, || $cell(closure, $($a),+)) - } - }; -} - -closure_call_entry!(js_closure_call1, closure_call1_cell, 1; a0); -closure_call_entry!(js_closure_call2, closure_call2_cell, 2; a0, a1); -closure_call_entry!(js_closure_call3, closure_call3_cell, 3; a0, a1, a2); -closure_call_entry!(js_closure_call4, closure_call4_cell, 4; a0, a1, a2, a3); -closure_call_entry!(js_closure_call5, closure_call5_cell, 5; a0, a1, a2, a3, a4); -closure_call_entry!(js_closure_call6, closure_call6_cell, 6; a0, a1, a2, a3, a4, a5); -closure_call_entry!(js_closure_call7, closure_call7_cell, 7; a0, a1, a2, a3, a4, a5, a6); -closure_call_entry!(js_closure_call8, closure_call8_cell, 8; a0, a1, a2, a3, a4, a5, a6, a7); -closure_call_entry!(js_closure_call9, closure_call9_cell, 9; a0, a1, a2, a3, a4, a5, a6, a7, a8); -closure_call_entry!(js_closure_call10, closure_call10_cell, 10; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); -closure_call_entry!(js_closure_call11, closure_call11_cell, 11; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); -closure_call_entry!(js_closure_call12, closure_call12_cell, 12; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); -closure_call_entry!(js_closure_call13, closure_call13_cell, 13; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); -closure_call_entry!(js_closure_call14, closure_call14_cell, 14; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); -closure_call_entry!(js_closure_call15, closure_call15_cell, 15; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); -closure_call_entry!(js_closure_call16, closure_call16_cell, 16; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); - -#[inline(always)] -fn js_closure_call0_impl(closure: *const ClosureHeader) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[]); - } - match resolve_strategy(func_ptr).kind() { - DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, &[]) }, - DispatchKind::BoundFunction => unsafe { dispatch_bound_function(closure, &[]) }, - DispatchKind::Rest(fixed_arity, synth) => unsafe { - dispatch_rest_bundled(closure, func_ptr, &[], fixed_arity, synth) - }, - DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 0) => unsafe { - dispatch_with_arity(closure, func_ptr, &[], declared) - }, - _ => unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this() - ) - }, - } -} - -/// Call a closure with 1 argument, returning f64 -// The one-argument value-call path can run arbitrary generated code and must -// let a JS exception unwind to the generated caller's catch landing pad. -// #8479: NOT `C-unwind`. The runtime is built `panic=abort` and JS throws -// travel as a raw Itanium `_Unwind_Exception` that must step THROUGH these -// frames untouched (see `crate::eh` and the panic=abort rationale in the -// workspace Cargo.toml). Marking a frame `extern "C-unwind"` in a -// panic=abort crate does not enable that — it makes rustc wrap the call in -// an abort-on-unwind landing pad, which is exactly the RFC-2945 guard a JS -// throw trips ("panic in a function that cannot unwind"). #8416 introduced -// the first two such guards here; #8464 added ~40 more and measurably -// regressed main (+20 gap crashes, gc-stress) before being reverted. -#[inline(always)] -fn closure_call1_cell(closure: *const ClosureHeader, arg0: f64) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0]); - } - dispatch_call1_resolved( - closure, - func_ptr, - arg0, - resolve_strategy(func_ptr), - crate::closure::body_call::current_this(), - ) +// these frames untouched (see `crate::eh` and the panic=abort rationale in the +// workspace Cargo.toml). Marking a frame `extern "C-unwind"` in a panic=abort +// crate does not enable that — it makes rustc wrap the call in an +// abort-on-unwind landing pad, which is exactly the RFC-2945 guard a JS throw +// trips. +closure_call_dispatch!(dispatch_call1, 1; a0); +/// Call a closure with receiver `this` and 1 argument. +#[no_mangle] +pub extern "C" fn js_closure_call1(closure: *const ClosureHeader, this: JsThis, a0: f64) -> f64 { + dispatch_call1(closure, this, a0) } -/// `this` is the receiver the implicit-`this` cell holds for the call — the -/// caller knows it, so the direct arm does not re-read the cell. -#[inline(always)] -fn dispatch_call1_resolved( +closure_call_dispatch!(dispatch_call2, 2; a0, a1); +/// Call a closure with receiver `this` and 2 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call2( closure: *const ClosureHeader, - func_ptr: *const u8, - arg0: f64, - strategy: DispatchStrategy, - this: crate::closure::JsThis, + this: JsThis, + a0: f64, + a1: f64, ) -> f64 { - match strategy.kind() { - DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, &[arg0]) }, - DispatchKind::BoundFunction => unsafe { dispatch_bound_function(closure, &[arg0]) }, - DispatchKind::Rest(fixed_arity, synth) => unsafe { - dispatch_rest_bundled(closure, func_ptr, &[arg0], fixed_arity, synth) - }, - DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 1) => unsafe { - dispatch_with_arity(closure, func_ptr, &[arg0], declared) - }, - _ => unsafe { crate::closure::body_call::js_body_call!(func_ptr, closure, this, arg0) }, - } -} - -/// Call a closure with 2 arguments, returning f64 -// A dynamically-dispatched closure can throw into a generated caller's catch -// landing pad; this bridge is on Next's loadManifest/readFileSync path. -#[inline(always)] -fn closure_call2_cell(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1]); - } - match resolve_strategy(func_ptr).kind() { - DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, &[arg0, arg1]) }, - DispatchKind::BoundFunction => unsafe { dispatch_bound_function(closure, &[arg0, arg1]) }, - DispatchKind::Rest(fixed_arity, synth) => unsafe { - dispatch_rest_bundled(closure, func_ptr, &[arg0, arg1], fixed_arity, synth) - }, - DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 2) => unsafe { - dispatch_with_arity(closure, func_ptr, &[arg0, arg1], declared) - }, - _ => unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1 - ) - }, - } + dispatch_call2(closure, this, a0, a1) } -/// Call a closure with 3 arguments, returning f64 -#[inline(always)] -fn closure_call3_cell(closure: *const ClosureHeader, arg0: f64, arg1: f64, arg2: f64) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1, arg2]); - } - match resolve_strategy(func_ptr).kind() { - DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, &[arg0, arg1, arg2]) }, - DispatchKind::BoundFunction => unsafe { - dispatch_bound_function(closure, &[arg0, arg1, arg2]) - }, - DispatchKind::Rest(fixed_arity, synth) => unsafe { - dispatch_rest_bundled(closure, func_ptr, &[arg0, arg1, arg2], fixed_arity, synth) - }, - DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 3) => unsafe { - dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2], declared) - }, - _ => unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2 - ) - }, - } +closure_call_dispatch!(dispatch_call3, 3; a0, a1, a2); +/// Call a closure with receiver `this` and 3 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call3( + closure: *const ClosureHeader, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, +) -> f64 { + dispatch_call3(closure, this, a0, a1, a2) } -/// Call a closure with 4 arguments, returning f64 -#[inline(always)] -fn closure_call4_cell( +closure_call_dispatch!(dispatch_call4, 4; a0, a1, a2, a3); +/// Call a closure with receiver `this` and 4 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call4( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1, arg2, arg3]); - } - match resolve_strategy(func_ptr).kind() { - DispatchKind::BoundMethod => unsafe { - dispatch_bound_method(closure, &[arg0, arg1, arg2, arg3]) - }, - DispatchKind::BoundFunction => unsafe { - dispatch_bound_function(closure, &[arg0, arg1, arg2, arg3]) - }, - DispatchKind::Rest(fixed_arity, synth) => unsafe { - dispatch_rest_bundled( - closure, - func_ptr, - &[arg0, arg1, arg2, arg3], - fixed_arity, - synth, - ) - }, - DispatchKind::Arity(declared) if arity_needs_dispatch(declared, 4) => unsafe { - dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2, arg3], declared) - }, - _ => unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3 - ) - }, - } + dispatch_call4(closure, this, a0, a1, a2, a3) } -/// Call a closure with 5 arguments, returning f64 -#[inline(always)] -fn closure_call5_cell( +closure_call_dispatch!(dispatch_call5, 5; a0, a1, a2, a3, a4); +/// Call a closure with receiver `this` and 5 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call5( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1, arg2, arg3, arg4]); - } - if func_ptr == BOUND_METHOD_FUNC_PTR { - return unsafe { dispatch_bound_method(closure, &[arg0, arg1, arg2, arg3, arg4]) }; - } - if func_ptr == BOUND_FUNCTION_FUNC_PTR { - return unsafe { dispatch_bound_function(closure, &[arg0, arg1, arg2, arg3, arg4]) }; - } - if let Some((fixed_arity, synth)) = lookup_closure_rest_full(func_ptr) { - return unsafe { - dispatch_rest_bundled( - closure, - func_ptr, - &[arg0, arg1, arg2, arg3, arg4], - fixed_arity, - synth, - ) - }; - } - if let Some(declared) = dispatch_arity(func_ptr) { - if arity_needs_dispatch(declared, 5) { - return unsafe { - dispatch_with_arity(closure, func_ptr, &[arg0, arg1, arg2, arg3, arg4], declared) - }; - } - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4 - ) - } + dispatch_call5(closure, this, a0, a1, a2, a3, a4) } -/// Call a closure with 6 arguments, returning f64 -#[inline(always)] -fn closure_call6_cell( +closure_call_dispatch!(dispatch_call6, 6; a0, a1, a2, a3, a4, a5); +/// Call a closure with receiver `this` and 6 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call6( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw(closure, &[arg0, arg1, arg2, arg3, arg4, arg5]); - } - if func_ptr == BOUND_METHOD_FUNC_PTR { - return unsafe { dispatch_bound_method(closure, &[arg0, arg1, arg2, arg3, arg4, arg5]) }; - } - if func_ptr == BOUND_FUNCTION_FUNC_PTR { - return unsafe { dispatch_bound_function(closure, &[arg0, arg1, arg2, arg3, arg4, arg5]) }; - } - if let Some((fixed_arity, synth)) = lookup_closure_rest_full(func_ptr) { - return unsafe { - dispatch_rest_bundled( - closure, - func_ptr, - &[arg0, arg1, arg2, arg3, arg4, arg5], - fixed_arity, - synth, - ) - }; - } - if let Some(declared) = dispatch_arity(func_ptr) { - if arity_needs_dispatch(declared, 6) { - return unsafe { - dispatch_with_arity( - closure, - func_ptr, - &[arg0, arg1, arg2, arg3, arg4, arg5], - declared, - ) - }; - } - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5 - ) - } + dispatch_call6(closure, this, a0, a1, a2, a3, a4, a5) } -#[inline] -pub(crate) fn dispatch_registered_call( +closure_call_dispatch!(dispatch_call7, 7; a0, a1, a2, a3, a4, a5, a6); +/// Call a closure with receiver `this` and 7 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call7( closure: *const ClosureHeader, - func_ptr: *const u8, - args: &[f64], -) -> Option { - if func_ptr == BOUND_METHOD_FUNC_PTR { - return Some(unsafe { dispatch_bound_method(closure, args) }); - } - if func_ptr == BOUND_FUNCTION_FUNC_PTR { - return Some(unsafe { dispatch_bound_function(closure, args) }); - } - None + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, +) -> f64 { + dispatch_call7(closure, this, a0, a1, a2, a3, a4, a5, a6) } -#[inline] -pub(crate) fn dispatch_rest_or_declared_arity( +closure_call_dispatch!(dispatch_call8, 8; a0, a1, a2, a3, a4, a5, a6, a7); +/// Call a closure with receiver `this` and 8 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call8( closure: *const ClosureHeader, - func_ptr: *const u8, - args: &[f64], - provided: u32, -) -> Option { - if let Some((fixed_arity, synth)) = lookup_closure_rest_full(func_ptr) { - return Some(unsafe { dispatch_rest_bundled(closure, func_ptr, args, fixed_arity, synth) }); - } - if let Some(declared) = dispatch_arity(func_ptr) { - if arity_needs_dispatch(declared, provided) { - return Some(unsafe { dispatch_with_arity(closure, func_ptr, args, declared) }); - } - } - None + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, +) -> f64 { + dispatch_call8(closure, this, a0, a1, a2, a3, a4, a5, a6, a7) } -/// Call a closure with 7 arguments, returning f64 -#[inline(always)] -fn closure_call7_cell( +closure_call_dispatch!(dispatch_call9, 9; a0, a1, a2, a3, a4, a5, a6, a7, a8); +/// Call a closure with receiver `this` and 9 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call9( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[arg0, arg1, arg2, arg3, arg4, arg5, arg6], - ); - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 7) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6 - ) - } + dispatch_call9(closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8) } -/// Call a closure with 8 arguments, returning f64 -#[inline(always)] -fn closure_call8_cell( +closure_call_dispatch!(dispatch_call10, 10; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); +/// Call a closure with receiver `this` and 10 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call10( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7], - ); - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 8) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7 - ) - } + dispatch_call10(closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) } -/// Call a closure with 9 arguments, returning f64 -#[inline(always)] -fn closure_call9_cell( +closure_call_dispatch!(dispatch_call11, 11; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +/// Call a closure with receiver `this` and 11 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call11( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8], - ); - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 9) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8 - ) - } + dispatch_call11(closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) } -/// Call a closure with 10 arguments, returning f64 -#[inline(always)] -fn closure_call10_cell( +closure_call_dispatch!(dispatch_call12, 12; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +/// Call a closure with receiver `this` and 12 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call12( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9], - ); - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 10) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9 - ) - } + dispatch_call12( + closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, + ) } -/// Call a closure with 11 arguments, returning f64 -#[inline(always)] -fn closure_call11_cell( +closure_call_dispatch!(dispatch_call13, 13; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +/// Call a closure with receiver `this` and 13 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call13( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 11) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10 - ) - } + dispatch_call13( + closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, + ) } -/// Call a closure with 12 arguments, returning f64 -#[inline(always)] -fn closure_call12_cell( +closure_call_dispatch!(dispatch_call14, 14; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +/// Call a closure with receiver `this` and 14 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call14( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, - arg11: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, + a13: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 12) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11 - ) - } + dispatch_call14( + closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, + ) } -/// Call a closure with 13 arguments, returning f64 -#[inline(always)] -fn closure_call13_cell( +closure_call_dispatch!(dispatch_call15, 15; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); +/// Call a closure with receiver `this` and 15 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call15( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, - arg11: f64, - arg12: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, + a13: f64, + a14: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 13) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11, - arg12 - ) - } + dispatch_call15( + closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, + ) } -/// Call a closure with 14 arguments, returning f64 -#[inline(always)] -fn closure_call14_cell( +closure_call_dispatch!(dispatch_call16, 16; a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); +/// Call a closure with receiver `this` and 16 arguments. +#[no_mangle] +pub extern "C" fn js_closure_call16( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, - arg11: f64, - arg12: f64, - arg13: f64, + this: JsThis, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, + a13: f64, + a14: f64, + a15: f64, ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( - closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 14) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11, - arg12, - arg13 - ) - } + dispatch_call16( + closure, this, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, + ) } -/// Call a closure with 15 arguments, returning f64 -#[inline(always)] -fn closure_call15_cell( +/// Dispatch `args` (any count) to `closure` with receiver `this`: the +/// per-arity dispatchers up to 16 arguments, the padded wide ladder past +/// them. The one Rust-side way to call a closure pointer. +pub(crate) fn dispatch_call_slice( closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, - arg11: f64, - arg12: f64, - arg13: f64, - arg14: f64, + this: JsThis, + args: &[f64], ) -> f64 { - let func_ptr = get_valid_func_ptr(closure); - if func_ptr.is_null() { - return dispatch_proxy_callee_or_throw( + let a = |i: usize| args[i]; + match args.len() { + 0 => dispatch_call0(closure, this), + 1 => dispatch_call1(closure, this, a(0)), + 2 => dispatch_call2(closure, this, a(0), a(1)), + 3 => dispatch_call3(closure, this, a(0), a(1), a(2)), + 4 => dispatch_call4(closure, this, a(0), a(1), a(2), a(3)), + 5 => dispatch_call5(closure, this, a(0), a(1), a(2), a(3), a(4)), + 6 => dispatch_call6(closure, this, a(0), a(1), a(2), a(3), a(4), a(5)), + 7 => dispatch_call7(closure, this, a(0), a(1), a(2), a(3), a(4), a(5), a(6)), + 8 => dispatch_call8( closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, arg14, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - arg14, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - arg14, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 15) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + ), + 9 => dispatch_call9( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + ), + 10 => dispatch_call10( closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11, - arg12, - arg13, - arg14 - ) + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + ), + 11 => dispatch_call11( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + ), + 12 => dispatch_call12( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + a(11), + ), + 13 => dispatch_call13( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + a(11), + a(12), + ), + 14 => dispatch_call14( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + a(11), + a(12), + a(13), + ), + 15 => dispatch_call15( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + a(11), + a(12), + a(13), + a(14), + ), + 16 => dispatch_call16( + closure, + this, + a(0), + a(1), + a(2), + a(3), + a(4), + a(5), + a(6), + a(7), + a(8), + a(9), + a(10), + a(11), + a(12), + a(13), + a(14), + a(15), + ), + _ => dispatch_call_wide(closure, this, args), } } -/// Call a closure with 16 arguments, returning f64 -#[inline(always)] -fn closure_call16_cell( - closure: *const ClosureHeader, - arg0: f64, - arg1: f64, - arg2: f64, - arg3: f64, - arg4: f64, - arg5: f64, - arg6: f64, - arg7: f64, - arg8: f64, - arg9: f64, - arg10: f64, - arg11: f64, - arg12: f64, - arg13: f64, - arg14: f64, - arg15: f64, -) -> f64 { +/// 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() { - return dispatch_proxy_callee_or_throw( - closure, - &[ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, - arg13, arg14, arg15, - ], - ); - } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - arg14, arg15, - ]; - if let Some(result) = dispatch_registered_call(closure, func_ptr, &args) { - return result; + return dispatch_proxy_callee_or_throw(closure, this, args); } - let args = [ - arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, - arg14, arg15, - ]; - if let Some(result) = dispatch_rest_or_declared_arity(closure, func_ptr, &args, 16) { - return result; - } - unsafe { - crate::closure::body_call::js_body_call!( - func_ptr, - closure, - crate::closure::body_call::current_this(), - arg0, - arg1, - arg2, - arg3, - arg4, - arg5, - arg6, - arg7, - arg8, - arg9, - arg10, - arg11, - arg12, - arg13, - arg14, - arg15 - ) + match resolve_strategy(func_ptr).kind() { + DispatchKind::BoundMethod => unsafe { dispatch_bound_method(closure, this, args) }, + DispatchKind::BoundFunction => unsafe { dispatch_bound_function(closure, args) }, + DispatchKind::Rest(fixed_arity, synth) => unsafe { + dispatch_rest_bundled(closure, func_ptr, this, args, fixed_arity, synth) + }, + DispatchKind::Arity(declared) if arity_needs_dispatch(declared, args.len() as u32) => unsafe { + dispatch_with_arity(closure, func_ptr, this, args, declared) + }, + _ => unsafe { super::super::dispatch_wide_abi(closure, func_ptr, this, args, args.len()) }, } } #[cfg(test)] -mod plain_receiver_tests { +mod plain_call_tests { use super::*; extern "C" fn observe_dynamic_this( @@ -988,24 +571,17 @@ mod plain_receiver_tests { } #[test] - fn an_undefined_receiver_is_bound_for_non_arrows_only() { + 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 sentinel = 42.0; - let original = crate::object::js_implicit_this_set(sentinel); - let regular_result = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); + // A receiver passed to an enclosing call must not leak into a plain + // call: the body sees exactly the `undefined` this entry passes. + let explicit = js_closure_call1(closure, JsThis::from_f64(42.0), 0.0); + assert_eq!(explicit, 42.0); + let regular_result = js_closure_call1(closure, crate::closure::plain_call_receiver(), 0.0); assert_eq!(regular_result.to_bits(), crate::value::TAG_UNDEFINED); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - - crate::closure::js_register_closure_arrow_function(body); - // An arrow keeps its lexical `this`: the entry binds nothing, and the - // body is handed the cell as it stands. - let arrow_result = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); - assert_eq!(arrow_result, sentinel); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - crate::object::js_implicit_this_set(original); } } @@ -1066,36 +642,3 @@ mod abi_table_tests { } } } - -#[cfg(test)] -mod receiver_binding_tests { - use super::*; - - extern "C" fn observe(_: *const ClosureHeader, this: JsThis, _: f64) -> f64 { - // Stage 1: the body reads the cell; the parameter must agree with it. - let cell = crate::object::js_implicit_this_get(); - assert_eq!(cell.to_bits(), this.bits(), "parameter and cell disagree"); - cell - } - - #[test] - fn an_entry_binds_the_cell_to_the_receiver_it_is_handed_and_restores_it() { - let body = observe as *const u8; - let closure = crate::closure::js_closure_alloc(body, 0); - crate::closure::js_register_closure_arity(body, 1); - - let sentinel = 42.0; - let original = crate::object::js_implicit_this_set(sentinel); - // A plain runtime call passes the cell: the body sees it unchanged. - let plain = js_closure_call1(closure, crate::closure::plain_call_receiver(), 0.0); - assert_eq!(plain, sentinel); - // An explicit receiver is what the body sees, and the cell comes back. - let explicit = js_closure_call1(closure, JsThis::from_f64(7.0), 0.0); - assert_eq!(explicit, 7.0); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - let undefined = js_closure_call1(closure, JsThis::UNDEFINED, 0.0); - assert_eq!(undefined.to_bits(), crate::value::TAG_UNDEFINED); - assert_eq!(crate::object::js_implicit_this_get(), sentinel); - crate::object::js_implicit_this_set(original); - } -} diff --git a/crates/perry-runtime/src/closure/dispatch/direct.rs b/crates/perry-runtime/src/closure/dispatch/direct.rs index 7a7baf5057..cd766a818b 100644 --- a/crates/perry-runtime/src/closure/dispatch/direct.rs +++ b/crates/perry-runtime/src/closure/dispatch/direct.rs @@ -82,21 +82,19 @@ pub(crate) fn resolve_direct_func_ptr( Some(func_ptr) } -/// Resolve one receiverless arrow callback at a method boundary. Ordinary -/// functions stay on `js_closure_callN`, which is responsible for binding a -/// bare call's dynamic `this` to `undefined`; bound/rest/padded calls likewise -/// retain their full dispatcher semantics. +/// Resolve one callback for PLAIN calls (`f(x)`) at a method boundary: the +/// body the caller may call directly, passing `undefined` as its receiver — +/// exactly what `js_closure_callN` passes. An arrow ignores the receiver; an +/// ordinary function binds it (a sloppy body coerces it in its own +/// prologue). Bound/rest/padded calls retain their full dispatcher semantics. #[no_mangle] -pub extern "C" fn js_closure_resolve_arrow_direct_call( +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 { return std::ptr::null(); }; - if !resolve_strategy(func_ptr).is_arrow() { - return std::ptr::null(); - } let Some(trusted) = super::super::registry::lookup_closure_trusted_direct(func_ptr) else { return func_ptr; }; @@ -180,9 +178,8 @@ macro_rules! define_direct_call_site { /// Invoke with the CURRENT closure address (see the module docs on /// rooting) and receiver `this` (`plain_call_receiver()` for a - /// plain call). Falls back to the full dispatcher when the closure - /// did not resolve. Stage 1: the implicit-`this` cell holds `this` - /// for the call (`calln::with_receiver`), which the body reads. + /// plain call, a builtin's `thisArg` otherwise). Falls back to + /// the full dispatcher when the closure did not resolve. #[inline] pub fn call( &self, @@ -190,11 +187,9 @@ macro_rules! define_direct_call_site { this: crate::closure::JsThis, $($arg: f64),+ ) -> f64 { - super::calln::with_receiver(closure, this, || { - // SAFETY: `self.0` is the resolved body of `closure` at - // this arity, or the dispatcher fallback. - unsafe { (self.0)(closure, this, $($arg),+) } - }) + // SAFETY: `self.0` is the resolved body of `closure` at this + // arity, or the dispatcher fallback. + unsafe { (self.0)(closure, this, $($arg),+) } } /// Whether the direct target was resolved. Test-only: a "fast @@ -344,12 +339,12 @@ mod tests { } #[test] - fn exported_resolver_admits_only_directly_callable_arrows() { + 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); assert_eq!( - js_closure_resolve_arrow_direct_call(arrow, 3), + js_closure_resolve_plain_direct_call(arrow, 3), add3 as *const u8 ); crate::closure::js_register_closure_trusted_direct( @@ -359,10 +354,10 @@ mod tests { 0, ); assert_eq!( - js_closure_resolve_arrow_direct_call(arrow, 3), + js_closure_resolve_plain_direct_call(arrow, 3), trusted_add3 as *const u8 ); - assert!(js_closure_resolve_arrow_direct_call(arrow, 2).is_null()); + 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); @@ -372,16 +367,23 @@ mod tests { 0, 0, ); - assert!(js_closure_resolve_arrow_direct_call(ordinary, 3).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. + 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); - assert!(js_closure_resolve_arrow_direct_call(rest, 3).is_null()); + assert!(js_closure_resolve_plain_direct_call(rest, 3).is_null()); for sentinel in [BOUND_METHOD_FUNC_PTR, BOUND_FUNCTION_FUNC_PTR] { let bound = closure_for(sentinel); - assert!(js_closure_resolve_arrow_direct_call(bound, 3).is_null()); + assert!(js_closure_resolve_plain_direct_call(bound, 3).is_null()); } } @@ -398,7 +400,7 @@ mod tests { let wrong_count = closure_for(boxed1 as *const u8); assert_eq!( - js_closure_resolve_arrow_direct_call(wrong_count, 1), + js_closure_resolve_plain_direct_call(wrong_count, 1), boxed1 as *const u8, "a wrong capture count must retain the checked public body" ); @@ -406,7 +408,7 @@ mod tests { let non_box = crate::closure::js_closure_alloc(boxed1 as *const u8, 1); crate::closure::js_closure_set_capture_bits(non_box, 0, crate::value::TAG_UNDEFINED); assert_eq!( - js_closure_resolve_arrow_direct_call(non_box, 1), + js_closure_resolve_plain_direct_call(non_box, 1), boxed1 as *const u8, "a non-box payload must retain the checked public body" ); @@ -415,7 +417,7 @@ mod tests { 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!( - js_closure_resolve_arrow_direct_call(valid, 1), + js_closure_resolve_plain_direct_call(valid, 1), trusted_boxed1 as *const u8, "the exact compiler-installed layout must select the private body" ); diff --git a/crates/perry-runtime/src/closure/dispatch/validate.rs b/crates/perry-runtime/src/closure/dispatch/validate.rs index a81b67b436..376cab5f43 100644 --- a/crates/perry-runtime/src/closure/dispatch/validate.rs +++ b/crates/perry-runtime/src/closure/dispatch/validate.rs @@ -147,12 +147,15 @@ pub fn get_valid_func_ptr(closure: *const ClosureHeader) -> *const u8 { /// id (#5976/#6321) — which turned the SIGSEGV into a spurious TypeError. Node /// calls the proxy. Re-boxing the id and asking the proxy registry costs nothing /// on the hot path: this runs only where the code used to throw unconditionally. -pub fn dispatch_proxy_callee_or_throw(closure: *const ClosureHeader, args: &[f64]) -> f64 { +pub fn dispatch_proxy_callee_or_throw( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + args: &[f64], +) -> f64 { let boxed = f64::from_bits(crate::value::POINTER_TAG | (closure as u64 & crate::value::POINTER_MASK)); if crate::proxy::js_proxy_is_proxy(boxed) == 1 { - let this_arg = f64::from_bits(crate::value::TAG_UNDEFINED); - return crate::proxy::call_proxy_value_with_this(boxed, this_arg, args); + return crate::proxy::call_proxy_value_with_this(boxed, this.as_f64(), args); } throw_not_callable() } diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index 19b79d7e37..f873228815 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -2,25 +2,9 @@ //! function: `js_native_call_value` (the generic NaN-boxed callee //! dispatcher), `js_closure_call_array` (any argument count) and the //! spread-apply bridge `js_closure_call_apply_with_spread`; plus the V8 -//! trampoline adapter `js_closure_v8_callback`. Stage 1: each binds the -//! implicit-`this` cell to its receiver for the call when the cell holds -//! anything else (`calln::with_receiver`). +//! trampoline adapter `js_closure_v8_callback`. use super::*; -use crate::closure::JsThis; - -/// The closure a NaN-boxed callee names, for `calln::with_receiver`'s arrow -/// test: null for a non-pointer value (a proxy id, a class ref), which is -/// then bound like any ordinary callee. -#[inline(always)] -fn callee_closure(func_value: f64) -> *const ClosureHeader { - let bits = func_value.to_bits(); - if (bits >> 48) == 0x7FFD { - (bits & 0x0000_FFFF_FFFF_FFFF) as *const ClosureHeader - } else { - std::ptr::null() - } -} /// Call a JavaScript function value with variable arguments /// This is the native implementation for dynamic function dispatch. @@ -44,17 +28,18 @@ fn callee_closure(func_value: f64) -> *const ClosureHeader { // throw trips ("panic in a function that cannot unwind"). #8416 introduced // the first two such guards here; #8464 added ~40 more and measurably // regressed main (+20 gap crashes, gc-stress) before being reverted. +/// Call function value `func_value` with receiver `this` +/// (`JsThis::UNDEFINED` for a plain call) and `args_len` arguments at +/// `args_ptr`: what `func.call(this, ...args)` does. #[cfg(panic = "abort")] #[no_mangle] pub unsafe extern "C" fn js_native_call_value( func_value: f64, - this: JsThis, + this: crate::closure::JsThis, args_ptr: *const f64, args_len: usize, ) -> f64 { - super::calln::with_receiver(callee_closure(func_value), this, || unsafe { - js_native_call_value_impl(func_value, args_ptr, args_len) - }) + unsafe { native_call_value_this(func_value, this, args_ptr, args_len) } } // Debug/test static archives transport Perry exceptions with Rust unwinding, @@ -64,17 +49,22 @@ pub unsafe extern "C" fn js_native_call_value( #[no_mangle] pub unsafe extern "C-unwind" fn js_native_call_value( func_value: f64, - this: JsThis, + this: crate::closure::JsThis, args_ptr: *const f64, args_len: usize, ) -> f64 { - super::calln::with_receiver(callee_closure(func_value), this, || unsafe { - js_native_call_value_impl(func_value, args_ptr, args_len) - }) + unsafe { native_call_value_this(func_value, this, args_ptr, args_len) } } +/// Call function value `func_value` with receiver `this` and `args_len` +/// arguments at `args_ptr`: the one value-call dispatcher. #[inline(always)] -unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_len: usize) -> f64 { +pub(crate) unsafe fn native_call_value_this( + func_value: f64, + this: crate::closure::JsThis, + args_ptr: *const f64, + args_len: usize, +) -> f64 { use crate::value::JSValue; let jsval = JSValue::from_bits(func_value.to_bits()); @@ -102,8 +92,7 @@ unsafe fn js_native_call_value_impl(func_value: f64, args_ptr: *const f64, args_ } } let arr_box = f64::from_bits(0x7FFD_0000_0000_0000 | (a as u64 & 0x0000_FFFF_FFFF_FFFF)); - let this_arg = f64::from_bits(crate::value::TAG_UNDEFINED); - return crate::proxy::js_proxy_apply(func_value, this_arg, arr_box); + return crate::proxy::js_proxy_apply(func_value, this.as_f64(), arr_box); } // Dynamic `super()` for `class X extends = (0..args_len).map(arg_at).collect(); - return dispatch_rest_bundled(closure, func_ptr, &all, fixed_arity, synth); + return dispatch_rest_bundled(closure, func_ptr, this, &all, fixed_arity, synth); } } - // Call with the appropriate arity - match dispatch_args_len { - 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), - 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), arg_at(0)), - 2 => js_closure_call2( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - ), - 3 => js_closure_call3( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - ), - 4 => js_closure_call4( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - ), - 5 => js_closure_call5( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - ), - 6 => js_closure_call6( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - ), - 7 => js_closure_call7( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - ), - 8 => js_closure_call8( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - ), - // Arities 9..=16 must each dispatch through their own - // `js_closure_call{N}` so the func-ptr is transmuted to a signature - // with the matching number of `f64` params. Collapsing these into - // `js_closure_call8` (the pre-fix `_` arm) silently dropped args 9+ for - // any closure VALUE / method invoked with >8 args — the codegen-side - // wrapper now carries up to 16 params (see artifacts.rs), so the runtime - // dispatch must reach them. >16 args fall back to the array path. - 9 => js_closure_call9( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - ), - 10 => js_closure_call10( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - ), - 11 => js_closure_call11( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - ), - 12 => js_closure_call12( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - arg_at(11), - ), - 13 => js_closure_call13( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - arg_at(11), - arg_at(12), - ), - 14 => js_closure_call14( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - arg_at(11), - arg_at(12), - arg_at(13), - ), - 15 => js_closure_call15( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - arg_at(11), - arg_at(12), - arg_at(13), - arg_at(14), - ), - 16 => js_closure_call16( - closure, - crate::closure::plain_call_receiver(), - arg_at(0), - arg_at(1), - arg_at(2), - arg_at(3), - arg_at(4), - arg_at(5), - arg_at(6), - arg_at(7), - arg_at(8), - arg_at(9), - arg_at(10), - arg_at(11), - arg_at(12), - arg_at(13), - arg_at(14), - arg_at(15), - ), - // >16 args: marshal into a stack buffer and dispatch via the variadic - // array path (which itself fans back out to `js_closure_call{N}`). - _ => { - let mut buf: Vec = Vec::with_capacity(dispatch_args_len); - for i in 0..dispatch_args_len { - buf.push(arg_at(i)); - } - closure_call_array_cell(closure as i64, buf.as_ptr(), buf.len() as i64) + // Call with the appropriate arity (padded with `undefined` up to the + // body's declared arity). + if dispatch_args_len <= 16 { + let mut buf = [undef; 16]; + for (i, slot) in buf.iter_mut().enumerate().take(dispatch_args_len) { + *slot = arg_at(i); } + return super::calln::dispatch_call_slice(closure, this, &buf[..dispatch_args_len]); } + // >16 args: marshal into a buffer and dispatch via the variadic array + // path. + let buf: Vec = (0..dispatch_args_len).map(arg_at).collect(); + call_array_this(closure, this, buf.as_ptr(), buf.len() as i64) } -/// Call a closure with receiver `this` and `args_len` arguments at -/// `args_ptr` — any count, including more than the 16 `js_closure_callN` -/// covers. Takes the closure pointer already unboxed (an integer register), -/// unlike [`js_native_call_value`]. +/// Call a closure with receiver `this` (`JsThis::UNDEFINED` for a plain +/// call) and `args_len` arguments at `args_ptr` — any count, including more +/// than the 16 `js_closure_callN` covers. Takes the closure pointer already +/// unboxed (an integer register), unlike [`js_native_call_value`]. #[no_mangle] pub unsafe extern "C" fn js_closure_call_array( closure_env: i64, - this: JsThis, + this: crate::closure::JsThis, args_ptr: *const f64, args_len: i64, ) -> f64 { - super::calln::with_receiver(closure_env as *const ClosureHeader, this, || unsafe { - closure_call_array_cell(closure_env, args_ptr, args_len) - }) + call_array_this( + closure_env as *const ClosureHeader, + this, + args_ptr, + args_len, + ) } -/// V8's `native_callback_trampoline` (perry-jsruntime) contract: +/// Adapter for V8's `native_callback_trampoline` (perry-jsruntime). +/// /// `js_create_callback(func_ptr, closure_env, param_count)` registers a JS /// callable whose trampoline invokes `func_ptr(closure_env, args_ptr, -/// args_len)` — with no receiver. The codegen arm for `Expr::JsCreateCallback` -/// (issue #248 Phase 2B) passes THIS function as `func_ptr` and the raw -/// `*const ClosureHeader` as `closure_env`; the closure runs as a plain call -/// (stage 1: with whatever the implicit-`this` cell holds). +/// args_len)` — a contract with no receiver. The codegen arm for +/// `Expr::JsCreateCallback` (issue #248 Phase 2B) passes THIS function as +/// `func_ptr` and the raw `*const ClosureHeader` (NaN-boxing stripped) as +/// `closure_env`; it calls the closure as a plain call. #[no_mangle] pub unsafe extern "C" fn js_closure_v8_callback( closure_env: i64, args_ptr: *const f64, args_len: i64, ) -> f64 { - unsafe { closure_call_array_cell(closure_env, args_ptr, args_len) } + call_array_this( + closure_env as *const ClosureHeader, + crate::closure::JsThis::UNDEFINED, + args_ptr, + args_len, + ) } -/// [`js_closure_call_array`]'s dispatch, with the receiver already in the -/// implicit-`this` cell. -unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_len: i64) -> f64 { - let closure = closure_env as *const ClosureHeader; +unsafe fn call_array_this( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + args_ptr: *const f64, + args_len: i64, +) -> f64 { if closure.is_null() { throw_not_callable(); } @@ -550,7 +349,7 @@ unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_l tmp.push(unboxed); } } - return dispatch_rest_bundled(closure, fp_for_rest, &tmp, fixed_arity, synth); + return dispatch_rest_bundled(closure, fp_for_rest, this, &tmp, fixed_arity, synth); } // Perry's closure-body arithmetic uses plain `fadd`/`fmul`/etc on // f64 inputs and assumes its arguments arrive as plain doubles, not @@ -582,199 +381,14 @@ unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_l } raw }; + if n <= 16 { + let mut buf = [0.0f64; 16]; + for (i, slot) in buf.iter_mut().enumerate().take(n) { + *slot = a(i); + } + return super::calln::dispatch_call_slice(closure, this, &buf[..n]); + } match n { - 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), - 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), - 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), - 3 => js_closure_call3( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - ), - 4 => js_closure_call4( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - ), - 5 => js_closure_call5( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - ), - 6 => js_closure_call6( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - ), - 7 => js_closure_call7( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - ), - 8 => js_closure_call8( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - ), - 9 => js_closure_call9( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - ), - 10 => js_closure_call10( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - ), - 11 => js_closure_call11( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - ), - 12 => js_closure_call12( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - a(11), - ), - 13 => js_closure_call13( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - a(11), - a(12), - ), - 14 => js_closure_call14( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - a(11), - a(12), - a(13), - ), - 15 => js_closure_call15( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - a(11), - a(12), - a(13), - a(14), - ), - 16 => js_closure_call16( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - a(4), - a(5), - a(6), - a(7), - a(8), - a(9), - a(10), - a(11), - a(12), - a(13), - a(14), - a(15), - ), // #3527: arities above 16 can't go through a fixed per-arity // `js_closure_callN` (none exist past 16). Build the full unboxed // arg slice and dispatch through the strategy resolver so the @@ -800,17 +414,17 @@ unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_l throw_not_callable(); } match resolve_strategy(func_ptr).kind() { - DispatchKind::BoundMethod => dispatch_bound_method(closure, &full), + DispatchKind::BoundMethod => dispatch_bound_method(closure, this, &full), DispatchKind::BoundFunction => dispatch_bound_function(closure, &full), DispatchKind::Rest(fixed_arity, synth) => { - dispatch_rest_bundled(closure, func_ptr, &full, fixed_arity, synth) + dispatch_rest_bundled(closure, func_ptr, this, &full, fixed_arity, synth) } DispatchKind::Arity(declared) => { - dispatch_with_arity(closure, func_ptr, &full, 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, &full, width) + dispatch_with_arity(closure, func_ptr, this, &full, width) } } } @@ -832,7 +446,7 @@ unsafe fn closure_call_array_cell(closure_env: i64, args_ptr: *const f64, args_l #[no_mangle] pub unsafe extern "C" fn js_closure_call_apply_with_spread( closure_box: f64, - this: JsThis, + this: crate::closure::JsThis, regular_args: *const f64, regular_count: i64, spread_arr_handle: i64, diff --git a/crates/perry-runtime/src/closure/dynamic_props.rs b/crates/perry-runtime/src/closure/dynamic_props.rs index a5e0eb3c5f..a726d1339d 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -360,12 +360,10 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { return f64::from_bits(crate::value::TAG_UNDEFINED); } let receiver = crate::value::js_nanbox_pointer(ptr as i64); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; + return crate::closure::js_closure_call0( + closure, + crate::closure::JsThis::from_f64(receiver), + ); } if let Some(val) = closure_get_own_dynamic_prop(ptr, prop) { @@ -435,20 +433,19 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { if acc.get == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let receiver = crate::value::js_nanbox_pointer(ptr as i64); - let getter_bits = clone_closure_rebind_this(acc.get, receiver); + let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + let receiver = + this_scope.root_nanbox_f64(crate::value::js_nanbox_pointer(ptr as i64)); + let getter_bits = clone_closure_rebind_this(acc.get, receiver.get_nanbox_f64()); let getter = (getter_bits & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if getter.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; + return crate::closure::js_closure_call0( + getter, + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), + ); } if let Some(p) = closure_get_own_dynamic_prop(proto_ptr, prop) { return p; @@ -467,19 +464,18 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { if acc.get == 0 { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let receiver = crate::value::js_nanbox_pointer(ptr as i64); - let getter_bits = clone_closure_rebind_this(acc.get, receiver); + let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + let receiver = this_scope.root_nanbox_f64(crate::value::js_nanbox_pointer(ptr as i64)); + let getter_bits = clone_closure_rebind_this(acc.get, receiver.get_nanbox_f64()); let getter = (getter_bits & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if getter.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; + return crate::closure::js_closure_call0( + getter, + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), + ); } { let key_hdr = crate::string::js_string_from_bytes(prop.as_ptr(), prop.len() as u32); @@ -539,13 +535,10 @@ pub(crate) fn function_prototype_inherited_get( let getter = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !getter.is_null() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = - crate::closure::js_closure_call0(getter, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return Some(result); + return Some(crate::closure::js_closure_call0( + getter, + crate::closure::JsThis::from_f64(receiver), + )); } } return Some(f64::from_bits(crate::value::TAG_UNDEFINED)); diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index c087098b94..d58a809cef 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -35,7 +35,7 @@ pub use alloc::{ pub(crate) use alloc::{ closure_install_boxed_captures, gc_capture_slot_range, singleton_closure_if_cached, }; -pub use body_call::{current_this, plain_call_receiver, JsThis}; +pub use body_call::{call_value, plain_call_receiver, JsThis}; pub use shape::closure_kind_probe; pub(crate) use registry::closure_registry_census; @@ -67,6 +67,7 @@ pub use registry::{ BOUND_METHOD_FUNC_PTR, CAPTURES_THIS_FLAG, NO_THIS_REBIND_FLAG, }; +pub(crate) use dispatch::native_call_value_this; pub(crate) use dispatch::{ bound_function_lazy_name, bound_function_length, bound_method_source_func_ptr, coerce_call_this, rebind_explicit_this, rebind_explicit_this_allocates, diff --git a/crates/perry-runtime/src/closure/receiver_param_tests.rs b/crates/perry-runtime/src/closure/receiver_param_tests.rs index 6c19655924..1660da56dc 100644 --- a/crates/perry-runtime/src/closure/receiver_param_tests.rs +++ b/crates/perry-runtime/src/closure/receiver_param_tests.rs @@ -1,14 +1,14 @@ -//! This-as-a-parameter stage 1: a JS body is -//! `body(callee, this, a0, ...)` (`perry_abi::JS_BODY_*`), and every runtime -//! route into it passes, as `this`, the receiver the implicit-`this` cell -//! holds — with the JS arguments still in their own positions after it. +//! This-as-a-parameter: a JS body is `body(callee, this, a0, ...)` +//! (`perry_abi::JS_BODY_*`), and every runtime route into it passes, as `this`, +//! the receiver its caller supplied — with the JS arguments still in their own +//! positions after it. A plain call supplies `undefined`. //! -//! Each test binds a receiver in the cell, calls a probe body through one -//! dispatch route (exact arity, padded arity, rest bundling, the padded wide -//! ladder, a hoisted `DirectCallN`), and checks what the probe received. -//! Sabotage: the funnel passes anything but `current_this()` -> the -//! receiver assertions fail; the funnel drops the receiver argument (the -//! arguments shift one slot) -> the argument assertions fail. +//! Each test calls a probe body with an explicit receiver through one dispatch +//! route (exact arity, padded arity, rest bundling, the padded wide ladder, a +//! hoisted `DirectCallN`), and checks what the probe received. +//! Sabotage: a route passes anything but its `this` argument -> the receiver +//! assertions fail; a route drops the receiver argument (the arguments shift +//! one slot) -> the argument assertions fail. use super::*; use std::cell::{Cell, RefCell}; @@ -58,30 +58,19 @@ wide_probe!( const UNDEF: u64 = crate::value::TAG_UNDEFINED; -/// Bind `receiver` in the cell for the duration of `f`, as a method dispatch does. -fn with_receiver(receiver: f64, f: impl FnOnce() -> R) -> R { - let scope = crate::gc::RuntimeHandleScope::new(); - let _bind = crate::object::ImplicitThisScope::bind(&scope, receiver); - f() -} - fn receiver() -> f64 { f64::from_bits(crate::value::JSValue::int32(4242).bits()) } +fn this() -> JsThis { + JsThis::from_f64(receiver()) +} + #[test] -fn an_exact_arity_call_passes_the_cell_receiver_then_the_arguments() { +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 r = with_receiver(receiver(), || { - js_closure_call3( - closure, - crate::closure::plain_call_receiver(), - 1.0, - 2.0, - 3.0, - ) - }); + let r = js_closure_call3(closure, this(), 1.0, 2.0, 3.0); assert_eq!(r, 3.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits(), "the body's `this` parameter"); @@ -92,9 +81,7 @@ fn an_exact_arity_call_passes_the_cell_receiver_then_the_arguments() { 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); - with_receiver(receiver(), || { - js_closure_call1(closure, crate::closure::plain_call_receiver(), 9.0) - }); + js_closure_call1(closure, this(), 9.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!(args, vec![9f64.to_bits(), UNDEF, UNDEF]); @@ -104,16 +91,7 @@ fn a_padded_call_passes_the_receiver_and_pads_after_it() { 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); - with_receiver(receiver(), || { - js_closure_call4( - closure, - crate::closure::plain_call_receiver(), - 5.0, - 6.0, - 7.0, - 8.0, - ) - }); + js_closure_call4(closure, this(), 5.0, 6.0, 7.0, 8.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!( @@ -128,14 +106,8 @@ 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 args: Vec = (0..36).map(f64::from).collect(); - let r = with_receiver(receiver(), || unsafe { - js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - args.as_ptr(), - args.len() as i64, - ) - }); + let r = + unsafe { js_closure_call_array(closure as i64, this(), args.as_ptr(), args.len() as i64) }; assert_eq!(r, 0.0); let (this, seen_args) = seen(); assert_eq!(this, receiver().to_bits()); @@ -146,39 +118,33 @@ fn a_wide_call_passes_the_receiver_and_every_argument_slot() { } #[test] -fn a_hoisted_direct_call_passes_the_cell_receiver() { +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 site = DirectCall3::resolve(closure); assert!(site.is_direct(), "the probe resolves to a direct call"); - with_receiver(receiver(), || { - site.call( - closure, - crate::closure::plain_call_receiver(), - 1.0, - 2.0, - 3.0, - ) - }); + site.call(closure, this(), 1.0, 2.0, 3.0); let (this, args) = seen(); assert_eq!(this, receiver().to_bits()); assert_eq!(args, vec![1f64.to_bits(), 2f64.to_bits(), 3f64.to_bits()]); } #[test] -fn the_witness_counts_a_parameter_that_disagrees_with_the_cell() { - let (checks0, mismatches0) = crate::object::this_witness_counts(); - let receiver_bits = receiver().to_bits(); - with_receiver(receiver(), || { - crate::object::js_this_param_witness(receiver_bits, std::ptr::null(), 0); - crate::object::js_this_param_witness(UNDEF, std::ptr::null(), 0); - }); - let (checks1, mismatches1) = crate::object::this_witness_counts(); - // Counters are process-wide; other tests may witness concurrently, so - // only lower bounds are exact. - assert!(checks1 >= checks0 + 2); - assert!( - mismatches1 > mismatches0, - "a disagreeing `this` was not counted" +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); + js_closure_call3(closure, this(), 1.0, 2.0, 3.0); + js_closure_call3( + closure, + crate::closure::plain_call_receiver(), + 4.0, + 5.0, + 6.0, + ); + let (this, args) = seen(); + assert_eq!( + this, UNDEF, + "a plain call must not inherit an earlier receiver" ); + assert_eq!(args, vec![4f64.to_bits(), 5f64.to_bits(), 6f64.to_bits()]); } diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index 65603c8443..65f3bf47ce 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -1141,6 +1141,7 @@ pub unsafe fn build_rest_array_rooted( pub unsafe fn dispatch_rest_bundled( closure: *const ClosureHeader, func_ptr: *const u8, + this: crate::closure::JsThis, args: &[f64], fixed_arity: u32, kind: RestDispatchKind, @@ -1198,11 +1199,11 @@ pub unsafe fn dispatch_rest_bundled( ($($i:tt),* $(,)?) => {{ if let Some(arguments_double) = all_arguments_double { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*, rest_double, arguments_double + func_ptr, closure, this $(, a!($i))*, rest_double, arguments_double ) } else { crate::closure::body_call::js_body_call_unwind!( - func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*, rest_double + func_ptr, closure, this $(, a!($i))*, rest_double ) } }}; @@ -1237,7 +1238,7 @@ pub unsafe fn dispatch_rest_bundled( slots.push(arguments_double); } let width = slots.len(); - super::dispatch_wide_abi(closure, func_ptr, &slots, width) + super::dispatch_wide_abi(closure, func_ptr, this, &slots, width) } } } @@ -1261,6 +1262,7 @@ pub unsafe fn dispatch_rest_bundled( pub unsafe fn dispatch_with_arity( closure: *const ClosureHeader, func_ptr: *const u8, + this: crate::closure::JsThis, args: &[f64], declared_arity: u32, ) -> f64 { @@ -1285,7 +1287,7 @@ pub unsafe fn dispatch_with_arity( // `arm!` macro builds the (padded) call args from the arg-index token list. macro_rules! arm { ($($i:tt),* $(,)?) => { - crate::closure::body_call::js_body_call!(func_ptr, closure, crate::closure::body_call::current_this() $(, a!($i))*) + crate::closure::body_call::js_body_call!(func_ptr, closure, this $(, a!($i))*) }; } match k { @@ -1352,7 +1354,7 @@ pub unsafe fn dispatch_with_arity( ), // #10420: more than 32 declared params used to return `undefined` // without calling the body. - _ => super::dispatch_wide_abi(closure, func_ptr, args, k), + _ => super::dispatch_wide_abi(closure, func_ptr, this, args, k), } } @@ -1365,8 +1367,8 @@ pub const BOUND_METHOD_FUNC_PTR: *const u8 = 0xBADD_DEAD_u64 as *const u8; /// result: a bound function with a fixed `this`, prepended partial args, and an /// adjusted `.name` / `.length`. When `js_closure_callN` (or `js_native_call_value`) /// detects this sentinel it dispatches via `dispatch_bound_function`, which -/// prepends the bound args, sets `IMPLICIT_THIS` to the bound receiver, and calls -/// the target closure. +/// prepends the bound args and calls the target closure with the bound receiver +/// as its `this`. /// /// Captures layout (`js_function_bind`, `dispatch/bound.rs`): /// [0] = target closure value (f64, NaN-boxed) diff --git a/crates/perry-runtime/src/closure/unbox.rs b/crates/perry-runtime/src/closure/unbox.rs index 2646d79520..d86797dceb 100644 --- a/crates/perry-runtime/src/closure/unbox.rs +++ b/crates/perry-runtime/src/closure/unbox.rs @@ -39,7 +39,7 @@ pub extern "C-unwind" fn js_closure_unbox_callee_checked(callee: f64) -> i64 { /// the fused member-call lowering (`o.m(args)` compiled as property-get + /// `js_closure_callN`). An object-literal method carries its `this` baked /// into a reserved capture slot at construction time, and that slot WINS over -/// the `IMPLICIT_THIS` cell codegen sets around the call — so a method +/// the `this` parameter codegen passes to the call — so a method /// inherited through `Object.setPrototypeOf(obj, proto)` ran with `this` /// bound to the PROTO literal instead of the receiver. `js_native_call_method` /// (the by-name dispatcher) already rebinds via `clone_closure_rebind_this`; diff --git a/crates/perry-runtime/src/closure/wide_call.rs b/crates/perry-runtime/src/closure/wide_call.rs index e39f2b2b91..2665c886b5 100644 --- a/crates/perry-runtime/src/closure/wide_call.rs +++ b/crates/perry-runtime/src/closure/wide_call.rs @@ -38,6 +38,7 @@ pub(crate) const MAX_DYNAMIC_CALL_WIDTH: usize = 1024; pub(crate) unsafe fn dispatch_wide_abi( closure: *const ClosureHeader, func_ptr: *const u8, + this: crate::closure::JsThis, args: &[f64], width: usize, ) -> f64 { @@ -47,7 +48,7 @@ pub(crate) unsafe fn dispatch_wide_abi( // even a valid wasm component). Say so instead. #[cfg(target_os = "wasi")] { - let _ = (closure, func_ptr, args); + let _ = (closure, func_ptr, this, args); throw_too_wide_for_wasi(width) } #[cfg(not(target_os = "wasi"))] @@ -64,7 +65,7 @@ pub(crate) unsafe fn dispatch_wide_abi( padded!($slots, [$($i,)+ $($i + $step),+], $step * 2, $($more)*) }; ($slots:ident, [$($i:expr),+], $step:expr,) => {{ - crate::closure::body_call::js_body_call_unwind!(func_ptr, closure, crate::closure::body_call::current_this() $(, $slots[$i])+) + crate::closure::body_call::js_body_call_unwind!(func_ptr, closure, this $(, $slots[$i])+) }}; } macro_rules! fill { @@ -206,7 +207,15 @@ mod tests { let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); // Exactly the declared width, through each ladder rung. for width in [40usize, 64, 65, 256, 257, 1024] { - let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, width) }; + let result = unsafe { + dispatch_wide_abi( + std::ptr::null(), + body, + crate::closure::JsThis::UNDEFINED, + &args, + width, + ) + }; assert_eq!(result, expected(40), "width {width}"); } } @@ -215,7 +224,15 @@ mod tests { fn missing_slots_are_padded_with_undefined() { let body = body_40 as *const u8; let args: Vec = (0..23).map(|i| i as f64 + 100.0).collect(); - let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, 40) }; + let result = unsafe { + dispatch_wide_abi( + std::ptr::null(), + body, + crate::closure::JsThis::UNDEFINED, + &args, + 40, + ) + }; assert_eq!(result, expected(23)); } @@ -225,8 +242,15 @@ mod tests { fn declared_arity_past_the_exact_arms_reaches_the_body() { let body = body_40 as *const u8; let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); - let result = - unsafe { crate::closure::dispatch_with_arity(std::ptr::null(), body, &args, 40) }; + let result = unsafe { + crate::closure::dispatch_with_arity( + std::ptr::null(), + body, + crate::closure::JsThis::UNDEFINED, + &args, + 40, + ) + }; assert_eq!(result, expected(40)); } @@ -270,6 +294,7 @@ mod tests { crate::closure::dispatch_rest_bundled( std::ptr::null(), body, + crate::closure::JsThis::UNDEFINED, &args, 16, crate::closure::registry::RestDispatchKind::UserRest, @@ -284,7 +309,15 @@ mod tests { let body = body_40 as *const u8; let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); // A width of 30 means the body only owns 30 slots; 30..40 read as undefined. - let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, 30) }; + let result = unsafe { + dispatch_wide_abi( + std::ptr::null(), + body, + crate::closure::JsThis::UNDEFINED, + &args, + 30, + ) + }; assert_eq!(result, expected(30)); } } diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 23362863ab..921feb6563 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -18,8 +18,8 @@ use std::sync::{Once, OnceLock}; use crate::array::ArrayHeader; use crate::closure::{js_closure_get_capture_f64, ClosureHeader}; use crate::object::{ - js_implicit_this_set, js_object_alloc, js_object_delete_field, js_object_get_field_by_name_f64, - js_object_keys, js_object_set_field_by_name, ObjectHeader, + js_object_alloc, js_object_delete_field, js_object_get_field_by_name_f64, js_object_keys, + js_object_set_field_by_name, ObjectHeader, }; use crate::string::{js_string_from_bytes, StringHeader}; use crate::value::JSValue; @@ -343,21 +343,16 @@ pub(crate) fn cluster_emit_event(event: &str, args: &[f64]) -> bool { if listeners.is_empty() { return false; } - let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); for listener in listeners { let cb = f64::from_bits(listener.callback_bits); - js_implicit_this_set(cluster_default_value()); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( cb, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(cluster_default_value()), args.as_ptr(), args.len(), ); } - js_implicit_this_set(prev.get_nanbox_f64()); } true } @@ -1527,26 +1522,25 @@ fn emit(target: f64, event: &str, args: &[f64]) -> bool { let mut i = 0; let mut fired = false; let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + // A listener can collect, so the emitter is rooted ONCE here and re-read + // at each use. + let target_handle = this_scope.root_nanbox_f64(target); loop { - let Some(arr) = array_ptr(get_field(target, &key)) else { + let Some(arr) = array_ptr(get_field(target_handle.get_nanbox_f64(), &key)) else { break; }; if i >= crate::array::js_array_length(arr) { break; } let cb = crate::array::js_array_get_f64(arr, i); - js_implicit_this_set(target); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( cb, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(target_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); } - js_implicit_this_set(prev.get_nanbox_f64()); fired = true; i += 1; } diff --git a/crates/perry-runtime/src/collection_iter.rs b/crates/perry-runtime/src/collection_iter.rs index 4198323c28..5dac128736 100644 --- a/crates/perry-runtime/src/collection_iter.rs +++ b/crates/perry-runtime/src/collection_iter.rs @@ -283,18 +283,14 @@ pub(crate) fn call_with_this_capturing_throw( this_value: f64, args: &[f64], ) -> Result { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_value)); - let result = call_capturing_throw(|| unsafe { - crate::closure::js_native_call_value( + call_capturing_throw(|| unsafe { + crate::closure::native_call_value_this( callee, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_value), args.as_ptr(), args.len(), ) - }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + }) } pub(crate) fn iterator_close(iter: f64) { diff --git a/crates/perry-runtime/src/dgram.rs b/crates/perry-runtime/src/dgram.rs index d2135769cd..ed4488f2f2 100644 --- a/crates/perry-runtime/src/dgram.rs +++ b/crates/perry-runtime/src/dgram.rs @@ -493,14 +493,16 @@ pub(crate) fn collect_rest_args(rest: f64) -> Vec { collect_args(raw as *const ArrayHeader) } -pub(crate) fn this_value(closure: *const ClosureHeader) -> f64 { +/// The socket a method thunk acts on: the socket the method was bound to +/// (capture 0), else the call's receiver. +pub(crate) fn this_value(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { if !closure.is_null() { let bits = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; if bits != 0 { return f64::from_bits(bits); } } - crate::object::js_implicit_this_get() + this.as_f64() } pub(crate) fn socket_value_from_handle(handle: i64) -> f64 { @@ -576,9 +578,9 @@ pub(crate) fn socket_async_ids(socket: f64) -> crate::async_hooks::AsyncResource extern "C" fn dgram_async_dispose( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - close_impl(this_value(closure), &[]); + close_impl(this_value(closure, this), &[]); let promise = crate::promise::js_promise_resolved(undefined_value()); boxed_pointer(promise as *const u8) } @@ -815,18 +817,14 @@ pub(crate) fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { if !is_callable_value(callback) { return undefined_value(); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( callback, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + } } #[cfg(test)] diff --git a/crates/perry-runtime/src/dgram/thunks.rs b/crates/perry-runtime/src/dgram/thunks.rs index 18ad9b9ce7..34d6013eec 100644 --- a/crates/perry-runtime/src/dgram/thunks.rs +++ b/crates/perry-runtime/src/dgram/thunks.rs @@ -10,74 +10,74 @@ use crate::closure::ClosureHeader; pub(crate) extern "C" fn dgram_send_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - send_impl(this_value(closure), &collect_rest_args(rest)) + send_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_sendto_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - sendto_impl(this_value(closure), &collect_rest_args(rest)) + sendto_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_bind_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - bind_impl(this_value(closure), &collect_rest_args(rest)) + bind_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_close_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - close_impl(this_value(closure), &collect_rest_args(rest)) + close_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_address_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - address_impl(this_value(closure)) + address_impl(this_value(closure, this)) } pub(crate) extern "C" fn dgram_remote_address_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - remote_address_impl(this_value(closure)) + remote_address_impl(this_value(closure, this)) } pub(crate) extern "C" fn dgram_connect_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - connect_impl(this_value(closure), &collect_rest_args(rest)) + connect_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_disconnect_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - disconnect_impl(this_value(closure)) + disconnect_impl(this_value(closure, this)) } pub(crate) extern "C" fn dgram_on_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let socket = this_value(closure); + let socket = this_value(closure, this); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); let listener = args.get(1).copied().unwrap_or_else(undefined_value); @@ -87,10 +87,10 @@ pub(crate) extern "C" fn dgram_on_thunk( pub(crate) extern "C" fn dgram_once_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let socket = this_value(closure); + let socket = this_value(closure, this); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); let listener = args.get(1).copied().unwrap_or_else(undefined_value); @@ -100,10 +100,10 @@ pub(crate) extern "C" fn dgram_once_thunk( pub(crate) extern "C" fn dgram_remove_listener_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let socket = this_value(closure); + let socket = this_value(closure, this); let args = collect_rest_args(rest); if args.len() >= 2 { remove_listener(socket, args[0], args[1]); @@ -113,10 +113,10 @@ pub(crate) extern "C" fn dgram_remove_listener_thunk( pub(crate) extern "C" fn dgram_emit_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let socket = this_value(closure); + let socket = this_value(closure, this); let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); let emitted = emit_event_value(socket, event, args.get(1..).unwrap_or(&[])); @@ -125,29 +125,29 @@ pub(crate) extern "C" fn dgram_emit_thunk( pub(crate) extern "C" fn dgram_listener_count_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { let args = collect_rest_args(rest); let event = args.first().copied().unwrap_or_else(undefined_value); - listener_snapshot(this_value(closure), event).len() as f64 + listener_snapshot(this_value(closure, this), event).len() as f64 } pub(crate) extern "C" fn dgram_event_names_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - event_names_impl(this_value(closure)) + event_names_impl(this_value(closure, this)) } pub(crate) extern "C" fn dgram_add_membership_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { membership_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), "addMembership", ) @@ -155,11 +155,11 @@ pub(crate) extern "C" fn dgram_add_membership_thunk( pub(crate) extern "C" fn dgram_drop_membership_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { membership_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), "dropMembership", ) @@ -167,11 +167,11 @@ pub(crate) extern "C" fn dgram_drop_membership_thunk( pub(crate) extern "C" fn dgram_add_source_membership_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { source_membership_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), "addSourceSpecificMembership", ) @@ -179,11 +179,11 @@ pub(crate) extern "C" fn dgram_add_source_membership_thunk( pub(crate) extern "C" fn dgram_drop_source_membership_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { source_membership_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), "dropSourceSpecificMembership", ) @@ -191,51 +191,51 @@ pub(crate) extern "C" fn dgram_drop_source_membership_thunk( pub(crate) extern "C" fn dgram_set_broadcast_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - set_broadcast_impl(this_value(closure), &collect_rest_args(rest)) + set_broadcast_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_multicast_ttl_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - set_multicast_ttl_impl(this_value(closure), &collect_rest_args(rest)) + set_multicast_ttl_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_multicast_loopback_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - set_multicast_loopback_impl(this_value(closure), &collect_rest_args(rest)) + set_multicast_loopback_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_multicast_interface_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - set_multicast_interface_impl(this_value(closure), &collect_rest_args(rest)) + set_multicast_interface_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_ttl_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - set_ttl_impl(this_value(closure), &collect_rest_args(rest)) + set_ttl_impl(this_value(closure, this), &collect_rest_args(rest)) } pub(crate) extern "C" fn dgram_set_recv_buffer_size_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { set_buffer_size_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), KEY_RECV_BUFFER_SIZE, "uv_recv_buffer_size", @@ -244,11 +244,11 @@ pub(crate) extern "C" fn dgram_set_recv_buffer_size_thunk( pub(crate) extern "C" fn dgram_set_send_buffer_size_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { set_buffer_size_impl( - this_value(closure), + this_value(closure, this), &collect_rest_args(rest), KEY_SEND_BUFFER_SIZE, "uv_send_buffer_size", @@ -257,11 +257,11 @@ pub(crate) extern "C" fn dgram_set_send_buffer_size_thunk( pub(crate) extern "C" fn dgram_get_recv_buffer_size_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { get_buffer_size_impl( - this_value(closure), + this_value(closure, this), KEY_RECV_BUFFER_SIZE, "uv_recv_buffer_size", ) @@ -269,11 +269,11 @@ pub(crate) extern "C" fn dgram_get_recv_buffer_size_thunk( pub(crate) extern "C" fn dgram_get_send_buffer_size_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { get_buffer_size_impl( - this_value(closure), + this_value(closure, this), KEY_SEND_BUFFER_SIZE, "uv_send_buffer_size", ) @@ -281,18 +281,18 @@ pub(crate) extern "C" fn dgram_get_send_buffer_size_thunk( pub(crate) extern "C" fn dgram_ref_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - ref_impl(this_value(closure), true) + ref_impl(this_value(closure, this), true) } pub(crate) extern "C" fn dgram_unref_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _rest: f64, ) -> f64 { - ref_impl(this_value(closure), false) + ref_impl(this_value(closure, this), false) } pub(crate) extern "C" fn dgram_zero_thunk( diff --git a/crates/perry-runtime/src/disposable.rs b/crates/perry-runtime/src/disposable.rs index 472bb5a921..426bce4e2b 100644 --- a/crates/perry-runtime/src/disposable.rs +++ b/crates/perry-runtime/src/disposable.rs @@ -32,11 +32,12 @@ 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_native_call_value, js_register_closure_arity, ClosureHeader, + js_closure_set_capture_ptr, js_register_closure_arity, native_call_value_this, ClosureHeader, + JsThis, }; use crate::object::{ - js_implicit_this_set, js_object_alloc, js_object_get_field_f64, js_object_set_field_by_name, - js_object_set_field_f64, js_register_class_extends_error, + js_object_alloc, js_object_get_field_f64, js_object_set_field_by_name, js_object_set_field_f64, + js_register_class_extends_error, }; use crate::string::js_string_from_bytes; use crate::value::{ @@ -106,18 +107,7 @@ extern "C" fn bound_dispose_thunk( if !is_callable_value(method) { return undefined(); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(js_implicit_this_set(resource)); - let result = unsafe { - js_native_call_value( - method, - crate::closure::plain_call_receiver(), - std::ptr::null(), - 0, - ) - }; - js_implicit_this_set(prev.get_nanbox_f64()); - result + unsafe { native_call_value_this(method, JsThis::from_f64(resource), std::ptr::null(), 0) } } fn make_bound_dispose_thunk(method: f64, resource: f64) -> f64 { diff --git a/crates/perry-runtime/src/dyn_eval/bridge.rs b/crates/perry-runtime/src/dyn_eval/bridge.rs index f58b3ab24a..2a420db441 100644 --- a/crates/perry-runtime/src/dyn_eval/bridge.rs +++ b/crates/perry-runtime/src/dyn_eval/bridge.rs @@ -250,24 +250,17 @@ pub(crate) fn set_member(base: f64, name: &str, value: f64) { /// Plain call `f(args…)` with an explicit `this` (undefined for ordinary /// calls). Mirrors the runtime's own `Function.prototype.call` plumbing: -/// arm the implicit-`this` slot, rebind explicit-`this` closures, dispatch -/// through the arity-aware value-call tower, restore. +/// pass the receiver explicitly and dispatch through the arity-aware +/// value-call tower. pub(crate) fn call_function(callee: f64, this: f64, args: &[f64]) -> f64 { - let prev = crate::object::js_implicit_this_set(this); - let prev_idx = root_push(prev); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( callee, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this), args.as_ptr(), args.len(), ) - }; - let result_idx = root_push(result); - crate::object::js_implicit_this_set(root_get(prev_idx)); - let result = root_get(result_idx); - roots_truncate(prev_idx); - result + } } /// Method call `base.name(args…)` — the generic dispatch tower compiled code diff --git a/crates/perry-runtime/src/event_target.rs b/crates/perry-runtime/src/event_target.rs index eb2b44622a..7053c0f16e 100644 --- a/crates/perry-runtime/src/event_target.rs +++ b/crates/perry-runtime/src/event_target.rs @@ -1013,8 +1013,9 @@ pub unsafe extern "C" fn js_event_target_dispatch_event( let args = [event]; let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + // A listener can collect, so the receiver is rooted ONCE here and re-read + // at each call. + let target_value_handle = this_scope.root_nanbox_f64(target_value); for (callback, capture, once) in callbacks { let Some(callable) = closure_value_from_listener(callback) else { continue; @@ -1022,14 +1023,12 @@ pub unsafe extern "C" fn js_event_target_dispatch_event( if once { remove_event_listener_value_with_capture(target, event_name_ptr, callback, capture); } - crate::object::js_implicit_this_set(target_value); - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( callable, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(target_value_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if event_bool_field(event_ptr, b"_immediateStopped") { break; } @@ -1127,10 +1126,13 @@ pub unsafe extern "C" fn js_event_target_set_max_listeners( /// The receiver a bound EventTarget method closure captured in slot 0 (stored /// as NaN-boxed bits so the GC's closure scan keeps it alive and relocates it). -unsafe fn bound_event_target(closure: *const crate::closure::ClosureHeader) -> *mut ObjectHeader { +unsafe fn bound_event_target( + closure: *const crate::closure::ClosureHeader, + this: crate::closure::JsThis, +) -> *mut ObjectHeader { let bits = crate::closure::js_closure_get_capture_ptr(closure, 0) as u64; if bits == 0 { - let receiver = crate::object::js_implicit_this_get(); + let receiver = this.as_f64(); if let Some(target) = value_as_ptr::(receiver) { let valid = crate::value::addr_class::try_read_gc_header(target as usize) .is_some_and(|h| h.obj_type == crate::gc::GC_TYPE_OBJECT); @@ -1352,6 +1354,7 @@ pub(crate) fn install_web_event_proto_methods(name: &str, proto_obj: *mut Object /// arguments are rooted across it and re-read afterwards. unsafe fn bound_listener_call( closure: *const crate::closure::ClosureHeader, + this: crate::closure::JsThis, event_type: f64, listener: f64, options: f64, @@ -1365,7 +1368,7 @@ unsafe fn bound_listener_call( return undefined_value(); } let scope = crate::gc::RuntimeHandleScope::new(); - let target_handle = scope.root_raw_mut_ptr(bound_event_target(closure)); + let target_handle = scope.root_raw_mut_ptr(bound_event_target(closure, this)); let listener_handle = scope.root_nanbox_f64(listener); let options_handle = scope.root_nanbox_f64(options); @@ -1385,30 +1388,30 @@ unsafe fn bound_listener_call( extern "C" fn event_target_add_event_listener_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event_type: f64, listener: f64, options: f64, ) -> f64 { - unsafe { bound_listener_call(closure, event_type, listener, options, true) } + unsafe { bound_listener_call(closure, this, event_type, listener, options, true) } } extern "C" fn event_target_remove_event_listener_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event_type: f64, listener: f64, options: f64, ) -> f64 { - unsafe { bound_listener_call(closure, event_type, listener, options, false) } + unsafe { bound_listener_call(closure, this, event_type, listener, options, false) } } extern "C" fn event_target_dispatch_event_thunk( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - unsafe { js_event_target_dispatch_event(bound_event_target(closure), event) } + unsafe { js_event_target_dispatch_event(bound_event_target(closure, this), event) } } /// The EventTarget method names, in the order Node's `EventTarget.prototype` diff --git a/crates/perry-runtime/src/exception.rs b/crates/perry-runtime/src/exception.rs index 9a038cd6b0..259dfcec9b 100644 --- a/crates/perry-runtime/src/exception.rs +++ b/crates/perry-runtime/src/exception.rs @@ -842,7 +842,7 @@ mod tests { } /// PR #10564 review finding, reproduced without touching the new - /// `implicit_this`/`new_target` savepoints directly: a bare + /// `new_target` savepoint directly: a bare /// save/call/restore pair — no RAII guard — around a call that throws. /// This is exactly the shape at the 4 named sites (fetch_globals.rs's /// Temporal/Intl subclass bridges, handle_methods.rs's prototype-walk @@ -851,15 +851,13 @@ mod tests { /// `longjmp` nor a system unwind ever reaches it once the call throws. /// /// Deliberately uses only pre-existing public entry points - /// (`js_implicit_this_set`/`js_new_target_set`/`catch_js_throw`/ - /// `js_throw`) that this fix does not change, so the same test body - /// fails on the unfixed tree (nothing restores either cell across the - /// inner `js_throw`) and passes once `implicit_this`/`new_target` join - /// `catch_savepoints!` — the fail-before/pass-after proof for the - /// mechanism every one of the 4 sites shares. + /// (`js_new_target_set`/`catch_js_throw`/`js_throw`) that this fix does + /// not change, so the same test body fails on the unfixed tree (nothing + /// restores the cell across the inner `js_throw`) and passes once + /// `new_target` joins `catch_savepoints!` — the fail-before/pass-after + /// proof for the mechanism every one of the 4 sites shares. #[test] fn a_bare_save_call_restore_site_is_made_exception_safe_by_the_savepoint() { - let base_this = crate::object::js_implicit_this_get().to_bits(); let base_nt = crate::object::js_new_target_get().to_bits(); let outcome: Result<(), f64> = catch_js_throw(|| { @@ -868,16 +866,10 @@ mod tests { let inner: Result<(), f64> = catch_js_throw(|| { // The bare save/call/restore pair itself: set, call something // that throws, restore — except the restore is unreachable. - let _prev_this = crate::object::js_implicit_this_set(11.0); let _prev_nt = crate::object::js_new_target_set(22.0); js_throw(99.0) }); assert_eq!(inner, Err(99.0)); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - base_this, - "the try open around the bare site must have restored IMPLICIT_THIS" - ); assert_eq!( crate::object::js_new_target_get().to_bits(), base_nt, @@ -885,7 +877,6 @@ mod tests { ); }); assert_eq!(outcome, Ok(())); - assert_eq!(crate::object::js_implicit_this_get().to_bits(), base_this); assert_eq!(crate::object::js_new_target_get().to_bits(), base_nt); } diff --git a/crates/perry-runtime/src/exception/savepoints.rs b/crates/perry-runtime/src/exception/savepoints.rs index a4f6a9e7c4..3cbf510f26 100644 --- a/crates/perry-runtime/src/exception/savepoints.rs +++ b/crates/perry-runtime/src/exception/savepoints.rs @@ -189,20 +189,10 @@ catch_savepoints! { capture: crate::object::call_method_depth_savepoint, restore: crate::object::call_method_depth_restore, latch: catch_subsystem::ALWAYS, idle: 0; - // PR #10564 review finding: the runtime guards that displace IMPLICIT_THIS - // around a `super()`/accessor/listener call they don't own (Temporal/Intl - // subclass bridges, the handle-method prototype-walk accessor dispatch, - // the stdlib listener/getter dispatchers) are a bare save/call/restore - // pair, not `ImplicitThisScope` — neither transport runs the restore - // statement that follows the call. The captured value is a second root - // for the object the live cell's own scanner already protects; see - // `scan_pending_trap_roots_mut` below. - implicit_this: u64, - capture: crate::object::implicit_this_trap_savepoint, - restore: crate::object::implicit_this_trap_restore, - latch: catch_subsystem::ALWAYS, idle: crate::value::TAG_UNDEFINED; - // Same shape as `implicit_this`, for `new.target` (the Temporal/Intl - // subclass `super()` bridges save/restore both together). + // PR #10564 review finding: the Temporal/Intl subclass `super()` bridges + // save/restore `new.target` with a bare statement pair that neither + // transport runs; the captured value is a second root for the object the + // live cell's scanner protects (see `scan_pending_trap_roots_mut`). new_target: u64, capture: crate::object::new_target_trap_savepoint, restore: crate::object::new_target_trap_restore, @@ -261,15 +251,15 @@ catch_savepoints! { latch: catch_subsystem::DYN_EVAL, idle: 0; } -/// Root + rewrite `implicit_this`/`new_target` in every OPEN `try`'s captured -/// savepoint (PR #10564 review finding). +/// Root + rewrite `new_target` in every OPEN `try`'s captured savepoint +/// (PR #10564 review finding). /// -/// `capture()` copies the live cells' bits into this per-depth slab +/// `capture()` copies the live cell's bits into this per-depth slab /// precisely so `js_throw` can put them back after a bare save/call/restore -/// site (see `object::this_binding::implicit_this_trap_savepoint`) gets +/// site (see `object::this_binding::new_target_trap_savepoint`) gets /// longjmp'd or unwound past. That copy is a second root for the same value /// the live cell's own scanner (`object::this_binding:: -/// scan_implicit_this_roots_mut`) already protects, and it is invisible to +/// scan_dispatch_binding_roots_mut`) already protects, and it is invisible to /// that scanner. A moving minor that runs while a `try` is open — before any /// throw crosses it — must rewrite this copy too, or a later throw restores a /// from-space address. Bounded by `try_depth <= MAX_TRY_DEPTH`, same as every @@ -283,7 +273,6 @@ pub(super) fn scan_pending_trap_roots_mut( // SAFETY: every slot below `try_depth` was written by `capture()` in // `try_push_with_kind` before `try_depth` advanced past it. let entry = unsafe { entry.assume_init_mut() }; - visitor.visit_nanbox_u64_slot(&mut entry.implicit_this); visitor.visit_nanbox_u64_slot(&mut entry.new_target); } } diff --git a/crates/perry-runtime/src/exception/savepoints/tests.rs b/crates/perry-runtime/src/exception/savepoints/tests.rs index 81a39e4c0e..7c5858360c 100644 --- a/crates/perry-runtime/src/exception/savepoints/tests.rs +++ b/crates/perry-runtime/src/exception/savepoints/tests.rs @@ -93,10 +93,6 @@ pub(super) fn call_method(_: u32) { /// unchanged (no tag rewriting to worry about), so a distinct marker per call /// is enough to make `capture()` observe a change — mirrors how /// `static_private_owner`'s witness below reuses `marker as f64`. -pub(super) fn implicit_this(marker: u32) { - crate::object::js_implicit_this_set(marker as f64); -} - pub(super) fn new_target(marker: u32) { crate::object::js_new_target_set(marker as f64); } diff --git a/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs b/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs index ee2ce01b66..23a509df8b 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/glob.rs @@ -739,7 +739,6 @@ fn excluded_by_function(entry: &FsGlobMatch, options: &FsGlobOptions<'_>) -> Res return Ok(false); }; let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let result = crate::exception::catch_js_throw(|| { let arg = if options.with_file_types { unsafe { build_dirent_object(&entry.dirent_name, &entry.dirent_parent, entry.kind) } @@ -753,7 +752,6 @@ fn excluded_by_function(entry: &FsGlobMatch, options: &FsGlobOptions<'_>) -> Res arg.get_nanbox_f64(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); result.map(|value| crate::value::js_is_truthy(value) != 0) } 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 f7ea4396d7..950673ca4a 100644 --- a/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs +++ b/crates/perry-runtime/src/fs/dir_glob_watch/watch.rs @@ -412,13 +412,12 @@ fn emit_listener0(object_value: f64, callback: f64) { if cb.is_null() { return; } - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - object_handle.get_nanbox_f64(), - )); with_watcher_uncaught_trap(|| { - crate::closure::js_closure_call0(cb, crate::closure::plain_call_receiver()); + crate::closure::js_closure_call0( + cb, + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), + ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } fn emit_fs_watch_event( @@ -442,28 +441,23 @@ fn emit_fs_watch_event( let refreshed_callbacks = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&callback_handles); let refreshed_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); for callback in refreshed_callbacks { let cb = extract_closure_ptr(callback); if cb.is_null() { continue; } - crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { crate::closure::js_closure_call2( cb, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), refreshed_args[0], refreshed_args[1], ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } fn emit_watch_file_change( - this: crate::closure::JsThis, object_value: f64, callbacks: Vec, curr: &StatSnapshot, @@ -485,23 +479,19 @@ fn emit_watch_file_change( let refreshed_callbacks = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&callback_handles); let refreshed_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(this.as_f64()); for callback in refreshed_callbacks { let cb = extract_closure_ptr(callback); if cb.is_null() { continue; } - crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { crate::closure::js_closure_call2( cb, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), refreshed_args[0], refreshed_args[1], ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } @@ -911,21 +901,18 @@ fn emit_fs_watch_error( let err_handle = scope.root_nanbox_f64(err_value); let refreshed_callbacks = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&callback_handles); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); for callback in refreshed_callbacks { let cb = extract_closure_ptr(callback); if cb.is_null() { continue; } - crate::object::js_implicit_this_set(object_handle.get_nanbox_f64()); with_watcher_uncaught_trap(|| { crate::closure::js_closure_call1( cb, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), err_handle.get_nanbox_f64(), ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } @@ -940,7 +927,7 @@ fn start_promise_watcher(id: usize, state: &mut PromiseWatchState) { extern "C" fn watch_file_poll_impl( closure: *const ClosureHeader, - this: crate::closure::JsThis, + _this: crate::closure::JsThis, ) -> f64 { let id = js_closure_get_capture_f64(closure, 0) as usize; let delivery = WATCH_FILE_STATES.with(|states| { @@ -959,7 +946,7 @@ extern "C" fn watch_file_poll_impl( Some((state.object_value, callbacks, curr, prev, state.bigint)) }); if let Some((object_value, callbacks, curr, prev, bigint)) = delivery { - emit_watch_file_change(this, object_value, callbacks, &curr, &prev, bigint); + emit_watch_file_change(object_value, callbacks, &curr, &prev, bigint); } undefined_value() } diff --git a/crates/perry-runtime/src/fs/stream/write_file_input.rs b/crates/perry-runtime/src/fs/stream/write_file_input.rs index 18e15f2921..d19e6a0641 100644 --- a/crates/perry-runtime/src/fs/stream/write_file_input.rs +++ b/crates/perry-runtime/src/fs/stream/write_file_input.rs @@ -150,17 +150,14 @@ fn well_known_iterator_method(value: f64, name: &str) -> Option { fn call_well_known_iterator(value: f64, name: &str) -> Option { let method = well_known_iterator_method(value, name)?; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(value)); let iterator = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value), std::ptr::null(), 0, ) }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None } else { diff --git a/crates/perry-runtime/src/gc/mod.rs b/crates/perry-runtime/src/gc/mod.rs index 757fa71150..06e5053f60 100644 --- a/crates/perry-runtime/src/gc/mod.rs +++ b/crates/perry-runtime/src/gc/mod.rs @@ -1118,12 +1118,13 @@ pub fn gc_init() { crate::symbol::new_symbol_side_table_root_scan_state, MutableRootScannerSource::RuntimeMutableScanner, ); - // Issue #1813: the implicit-`this` cell holds the live receiver across a - // dynamically-dispatched method body. A moving GC triggered from inside - // that body (e.g. @perryts/mysql Pool.acquire → handshake → nativeScramble - // under concurrent load) must rewrite the cell, or the body's next - // `this`-derived dispatch derefs a relocated receiver → SIGSEGV. - reg_scanner!(crate::object::scan_implicit_this_roots_mut); + // Issue #1813: the dispatch-binding cells (`new.target`, the one-shot + // static-`this` override, the static private-owner stack) hold a live heap + // value across a call body. A moving GC triggered from inside that body + // (e.g. @perryts/mysql Pool.acquire → handshake → nativeScramble under + // concurrent load, when the implicit-`this` cell still existed) must + // rewrite them, or the body's next read derefs a relocated value. + reg_scanner!(crate::object::scan_dispatch_binding_roots_mut); // Fresh class evaluations are lexical environments, not merely template // class ids. Method dispatch keeps the active evaluation here so private // accesses remain exact across `.call`/`.apply`; root and rewrite those diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots.rs b/crates/perry-runtime/src/gc/tests/runtime_roots.rs index a0e53c6db4..d7dbf94794 100644 --- a/crates/perry-runtime/src/gc/tests/runtime_roots.rs +++ b/crates/perry-runtime/src/gc/tests/runtime_roots.rs @@ -9,7 +9,6 @@ mod fs_options_object; mod generator_attach_prototype; mod handle_stack; mod hook_dispatch_handles; -mod implicit_this_scope; mod interned_string_caches; mod iter_result_keys; mod json_construction; 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 ef33f84aed..5afdf33174 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 @@ -31,7 +31,7 @@ fn seen() -> Vec { extern "C" fn record_this_and_six_args( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, a0: f64, a1: f64, _a2: f64, @@ -39,12 +39,7 @@ extern "C" fn record_this_and_six_args( _a4: f64, a5: f64, ) -> f64 { - record(&[ - crate::object::js_implicit_this_get().to_bits(), - a0.to_bits(), - a1.to_bits(), - a5.to_bits(), - ]); + record(&[this.bits(), a0.to_bits(), a1.to_bits(), a5.to_bits()]); 0.0 } diff --git a/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs b/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs deleted file mode 100644 index 031f0287fb..0000000000 --- a/crates/perry-runtime/src/gc/tests/runtime_roots/implicit_this_scope.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Moving-GC regression for the runtime's implicit-`this` guards (#10490). -//! -//! A runtime entry point that binds `IMPLICIT_THIS` for a callback displaces -//! the CALLER's receiver and writes it back when the callback returns. The -//! callback is user code, so a copying minor inside it can move that receiver; -//! a displaced value kept in a plain Rust field is not a root and is restored -//! as a retired from-space address — the caller's next `this.x` reads -//! `undefined`. `js_native_call_method`'s prototype-override early path and -//! the dense/array-like `Array.prototype` callback engines each carried such a -//! guard. Every test here plants a callback that runs a copying minor, then -//! asserts the restored cell is the caller's RELOCATED receiver — and that the -//! receiver actually moved, so a green run cannot be vacuous. - -use super::super::super::*; -use super::super::support::*; - -extern "C" fn collect_arity0( - _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, -) -> f64 { - crate::gc::gc_collect_minor(); - f64::from_bits(crate::value::TAG_UNDEFINED) -} - -extern "C" fn collect_arity3( - _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, - _value: f64, - _index: f64, - _recv: f64, -) -> f64 { - crate::gc::gc_collect_minor(); - f64::from_bits(crate::value::TAG_UNDEFINED) -} - -fn boxed(ptr: *const u8) -> f64 { - f64::from_bits(crate::JSValue::pointer(ptr).bits()) -} - -/// Install a young object as the caller's `this`, run `call`, and assert the -/// cell afterwards holds that object's post-collection address. -fn assert_caller_this_survives(what: &str, call: impl FnOnce()) { - let _guard = CopyingNurseryTestGuard::new(0); - let _scan = ConservativeScanDisabledGuard::new(); - let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - let _force = ForcedEvacuationTestGuard::on(); - register_runtime_handle_root_scanner_for_tests(); - - let scope = crate::gc::RuntimeHandleScope::new(); - let caller = scope.root_nanbox_f64(boxed(crate::object::js_object_alloc(0, 0) as *const u8)); - let caller_before = caller.get_nanbox_u64(); - let outer = crate::object::js_implicit_this_set(caller.get_nanbox_f64()); - - let cycles_before = copying_minor_cycles(); - call(); - assert!( - copying_minor_cycles() > cycles_before, - "{what}: the callback must run a copying minor" - ); - assert_ne!( - caller.get_nanbox_u64(), - caller_before, - "{what}: the collection must actually move the caller's receiver" - ); - let restored = crate::object::js_implicit_this_set(outer); - assert_eq!( - restored.to_bits(), - caller.get_nanbox_u64(), - "{what}: the displaced `this` must be restored at its relocated address, \ - not the pre-collection one ({caller_before:#x})" - ); -} - -#[test] -fn implicit_this_scope_restores_a_relocated_receiver() { - assert_caller_this_survives("ImplicitThisScope", || { - let scope = crate::gc::RuntimeHandleScope::new(); - let _bound = crate::object::ImplicitThisScope::bind( - &scope, - f64::from_bits(crate::value::TAG_UNDEFINED), - ); - crate::gc::gc_collect_minor(); - }); -} - -#[test] -fn prototype_override_method_call_restores_a_relocated_caller_this() { - assert_caller_this_survives("js_native_call_method prototype override", || { - crate::closure::js_register_closure_arity(collect_arity0 as *const u8, 0); - let scope = crate::gc::RuntimeHandleScope::new(); - let method = scope - .root_nanbox_f64(boxed( - crate::closure::js_closure_alloc(collect_arity0 as *const u8, 0) as *const u8, - )); - let proto = scope.root_nanbox_f64(boxed(crate::object::js_object_alloc(0, 1) as *const u8)); - let key = crate::string::js_string_from_bytes(b"run".as_ptr(), 3); - crate::object::js_object_set_field_by_name( - crate::value::js_nanbox_get_pointer(proto.get_nanbox_f64()) as *mut crate::ObjectHeader, - key, - method.get_nanbox_f64(), - ); - let receiver = - scope.root_nanbox_f64(boxed(crate::object::js_object_alloc(0, 0) as *const u8)); - crate::object::object_ops::js_object_set_prototype_of( - receiver.get_nanbox_f64(), - proto.get_nanbox_f64(), - ); - assert!( - crate::object::prototype_chain::object_has_individual_class_prototype( - crate::value::js_nanbox_get_pointer(receiver.get_nanbox_f64()) as usize - ), - "the receiver must take the #9247 prototype-override early path" - ); - unsafe { - crate::object::js_native_call_method( - receiver.get_nanbox_f64(), - b"run".as_ptr().cast(), - 3, - std::ptr::null(), - 0, - ); - } - }); -} - -#[test] -fn dense_array_for_each_restores_a_relocated_caller_this() { - assert_caller_this_survives("js_array_forEach", || { - let scope = crate::gc::RuntimeHandleScope::new(); - let arr = crate::array::js_array_push_f64(crate::array::js_array_alloc(0), 1.0); - let arr = scope.root_raw_const_ptr(arr); - let cb = crate::closure::js_closure_alloc_singleton(collect_arity3 as *const u8); - // `js_array_forEach` roots the receiver itself, so a scoped argument is - // the right shape here (#7341). - arr.with_const_ptr(|ptr| crate::array::js_array_forEach(ptr, cb)); - }); -} - -#[test] -fn arraylike_for_each_restores_a_relocated_caller_this() { - assert_caller_this_survives("js_arraylike_forEach", || { - let scope = crate::gc::RuntimeHandleScope::new(); - let arr = crate::array::js_array_push_f64(crate::array::js_array_alloc(0), 1.0); - let arr = scope.root_nanbox_f64(boxed(arr as *const u8)); - let cb = crate::closure::js_closure_alloc_singleton(collect_arity3 as *const u8); - crate::array::js_arraylike_forEach( - arr.get_nanbox_f64(), - boxed(cb as *const u8), - f64::from_bits(crate::value::TAG_UNDEFINED), - ); - }); -} 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 35451e471a..88d89cd4be 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 @@ -69,10 +69,10 @@ fn test(receiver: &RuntimeHandle<'_>, input: &RuntimeHandle<'_>) -> bool { extern "C" fn return_this( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } extern "C" fn return_null( _: *const crate::closure::ClosureHeader, @@ -83,20 +83,20 @@ extern "C" fn return_null( } extern "C" fn read_answer( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); api::finish(dispatch::get(&receiver, b"answer")) } extern "C" fn collect_and_echo( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); let argument = scope.root_nanbox_f64(arg); gc_collect_minor(); put(&receiver, b"seen", &argument); @@ -231,25 +231,21 @@ fn perex_dispatch_generic_prototype_test_coerces_before_getting_exec() { )); assert!(crate::proxy::proxy_wraps_callable(method.get_nanbox_f64())); ORDER.with(|n| n.set(0)); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); let args = [argument.get_nanbox_f64()]; let result = crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), args.as_ptr(), 1, ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); assert_eq!(result.unwrap().to_bits(), crate::value::TAG_TRUE); assert_eq!(ORDER.with(Cell::get), 123); ORDER.with(|n| n.set(0)); assert!(matches!( dispatch::test_value( - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), 1.0, argument.get_nanbox_f64() ), @@ -275,10 +271,6 @@ fn perex_dispatch_getter_and_callback_reacquire_original_input_after_gc() { let before = handle_address::(&input); let method = function(&scope, collecting_getter as *const u8, 0); getter(&receiver, &method); - let displaced = object(&scope); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - displaced.get_nanbox_f64(), - )); let roots = RuntimeHandleScope::active_len_for_tests(); let result = api::finish(dispatch::execute( &receiver, @@ -298,11 +290,6 @@ fn perex_dispatch_getter_and_callback_reacquire_original_input_after_gc() { api::finish(dispatch::get(&receiver, b"seen")).to_bits(), handle_string_value(&input).to_bits() ); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - displaced.get_nanbox_f64().to_bits() - ); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } #[test] @@ -337,10 +324,6 @@ fn perex_dispatch_actual_throws_restore_this_roots_and_accounting() { } else { put(&receiver, b"exec", &method); } - let displaced = object(&scope); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - displaced.get_nanbox_f64(), - )); let roots = RuntimeHandleScope::active_len_for_tests(); let live = external_side_live_bytes(); let result = @@ -353,11 +336,6 @@ fn perex_dispatch_actual_throws_restore_this_roots_and_accounting() { assert_ne!(handle_address::(&input), before); assert_eq!(RuntimeHandleScope::active_len_for_tests(), roots); assert_eq!(external_side_live_bytes(), live); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - displaced.get_nanbox_f64().to_bits() - ); - crate::object::js_implicit_this_set(previous.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 ab02984833..3e041d91b7 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 @@ -162,10 +162,10 @@ extern "C" fn no_match( } extern "C" fn return_this( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } extern "C" fn factory( _: *const crate::closure::ClosureHeader, @@ -547,50 +547,45 @@ thread_local! { static ORDER: Cell = const { Cell::new(0) }; } fn ordered(event: u64) { ORDER.with(|n| n.set(n.get() * 10 + event)); } -fn ordered_field(event: u64, name: &[u8]) -> f64 { +fn ordered_field(this: crate::closure::JsThis, event: u64, name: &[u8]) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); ordered(event); gc_collect_minor(); get(&receiver, name) } extern "C" fn ordered_input( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - ordered_field(1, b"text") + ordered_field(this, 1, b"text") } extern "C" fn ordered_constructor( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - ordered_field(2, b"holder") + ordered_field(this, 2, b"holder") } extern "C" fn ordered_species( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - ordered_field(3, b"factory") + ordered_field(this, 3, b"factory") } extern "C" fn ordered_flags( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - ordered_field(4, b"flagText") + ordered_field(this, 4, b"flagText") } extern "C" fn ordered_factory( closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, receiver: f64, flags: f64, ) -> f64 { ordered(5); - factory( - closure, - crate::closure::body_call::current_this(), - receiver, - flags, - ) + factory(closure, this, receiver, flags) } extern "C" fn ordered_index( _: *const crate::closure::ClosureHeader, @@ -602,11 +597,11 @@ extern "C" fn ordered_index( } extern "C" fn ordered_set_index( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, index: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); ordered(7); gc_collect_minor(); put(&receiver, b"seenIndex", index); 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 94163f186e..7518c8fa5b 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 @@ -84,32 +84,32 @@ extern "C" fn throw_string( } extern "C" fn search_override( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); gc_collect_minor(); api::finish(dispatch::set_last_index(&receiver, 7.0)); receiver.get_nanbox_f64() } extern "C" fn search_throw( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); api::finish(dispatch::set_last_index(&receiver, 7.0)); crate::exception::js_throw(902.0) } extern "C" fn global_override( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); gc_collect_minor(); let count = api::finish(dispatch::get(&receiver, b"calls")); if count == 0.0 { 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 011a558f56..8ffc0b3854 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 @@ -33,10 +33,9 @@ pub(super) fn register_host_roots() { gc_register_mutable_root_scanner( crate::object::regex_proto_thunks::scan_canonical_test_site_roots_mut, ); - gc_register_mutable_root_scanner(crate::object::scan_implicit_this_roots_mut); - // PR #10564 review finding: implicit_this/new_target savepoints in - // exception.rs are a second root for whatever these tests displace - // IMPLICIT_THIS to across a throw. gc_init registers this in + gc_register_mutable_root_scanner(crate::object::scan_dispatch_binding_roots_mut); + // PR #10564 review finding: the new_target savepoint in exception.rs is + // a second root for whatever a throw displaces. gc_init registers this in // production; the isolation guard clears that registry too. gc_register_mutable_root_scanner(crate::exception::scan_exception_roots_mut); gc_register_mutable_root_scanner(crate::closure::scan_singleton_closure_roots_mut); 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 644c3362a1..6dd759f8ff 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 @@ -127,7 +127,7 @@ extern "C" fn hook( } extern "C" fn builtin_callback( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, matched: f64, first: f64, absent: f64, @@ -142,10 +142,7 @@ extern "C" fn builtin_callback( let input = scope.root_nanbox_f64(input); let groups = scope.root_nanbox_f64(groups); gc_collect_minor(); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - TAG_UNDEFINED - ); + assert_eq!(this.as_f64().to_bits(), TAG_UNDEFINED); assert_eq!( get(&state, b"lastIndex"), if position == 0.0 { 0.0 } else { 77.0 } @@ -174,11 +171,11 @@ extern "C" fn builtin_callback( } extern "C" fn alias_exec( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); - let state = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let state = scope.root_nanbox_f64(this.as_f64()); gc_collect_minor(); let n = get(&state, b"calls"); put(&state, b"calls", n + 1.0); @@ -423,7 +420,7 @@ fn perex_replace_hooks_preserve_receiver_and_arbitrary_result_before_coercion() } #[test] -fn perex_replace_collecting_throw_cleans_native_arguments_roots_and_this() { +fn perex_replace_collecting_throw_cleans_native_arguments_and_roots() { let _guard = CopyingNurseryTestGuard::new(0); let _scan = ConservativeScanDisabledGuard::new(); let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); @@ -433,9 +430,8 @@ fn perex_replace_collecting_throw_cleans_native_arguments_roots_and_this() { let input = text(&scope, b"abc"); let search = text(&scope, b"b"); let callback = function(&scope, collecting_throw as *const u8, 3); + // A young object whose move proves the throwing callback's collection ran. let previous = object(&scope); - let displaced = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); // Warm lazy String/Function prototype setup before checking native bytes. let empty = text(&scope, b"none"); let _ = crate::regex::js_string_replace_js( @@ -458,18 +454,13 @@ fn perex_replace_collecting_throw_cleans_native_arguments_roots_and_this() { assert_eq!(RuntimeHandleScope::active_len_for_tests(), roots); assert_eq!(external_side_live_bytes(), live); assert_ne!(previous.get_nanbox_f64().to_bits(), before); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - previous.get_nanbox_f64().to_bits() - ); - crate::object::js_implicit_this_set(displaced.get_nanbox_f64()); } extern "C" fn primitive_hook_getter( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - assert_eq!(crate::object::js_implicit_this_get(), 23.0); + 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) 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 6097b239ce..7a8b517d1b 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 @@ -261,9 +261,9 @@ extern "C" fn hook( } extern "C" fn primitive_hook( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - assert_eq!(crate::object::js_implicit_this_get(), 23.0); + 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) @@ -323,20 +323,20 @@ fn event(n: u64) { extern "C" fn matrix_third( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(2, b"value") + field_event(this, 2, b"value") } extern "C" fn matrix_hook( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, _: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); let input = scope.root_nanbox_f64(input); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); assert!(crate::proxy::reflect_value_is_object( receiver.get_nanbox_f64() )); @@ -349,11 +349,9 @@ extern "C" fn matrix_hook( extern "C" fn matrix_hook_getter( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - assert!(crate::proxy::reflect_value_is_object( - crate::object::js_implicit_this_get() - )); + 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); @@ -574,42 +572,42 @@ fn perex_string_methods_ignore_primitive_hooks_and_preserve_boxed_hooks_after_gc } } -fn field_event(n: u64, name: &[u8]) -> f64 { +fn field_event(this: crate::closure::JsThis, n: u64, name: &[u8]) -> f64 { let scope = RuntimeHandleScope::new(); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); event(n); gc_collect_minor(); get(&receiver, name) } extern "C" fn input_text( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(1, b"text") + field_event(this, 1, b"text") } extern "C" fn constructor_get( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(2, b"holder") + field_event(this, 2, b"holder") } extern "C" fn species_get( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(3, b"factory") + field_event(this, 3, b"factory") } extern "C" fn flags_get( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(4, b"flagText") + field_event(this, 4, b"flagText") } extern "C" fn limit_get( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(6, b"number") + field_event(this, 6, b"number") } extern "C" fn factory( _: *const crate::closure::ClosureHeader, @@ -709,13 +707,13 @@ fn perex_split_species_order_zero_limit_and_empty_input() { extern "C" fn custom_exec( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, input: f64, ) -> f64 { let scope = RuntimeHandleScope::new(); let state = scope.root_nanbox_f64(crate::closure::js_closure_get_capture_f64(c, 0)); let input = scope.root_nanbox_f64(input); - let receiver = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + let receiver = scope.root_nanbox_f64(this.as_f64()); gc_collect_minor(); assert_eq!( input.get_nanbox_f64().to_bits(), @@ -812,7 +810,7 @@ extern "C" fn throwing_hook( crate::exception::js_throw(984.0) } #[test] -fn perex_split_collecting_throw_releases_native_arguments_and_restores_this() { +fn perex_split_collecting_throw_releases_native_arguments() { let _guard = CopyingNurseryTestGuard::new(0); let _scan = ConservativeScanDisabledGuard::new(); let _triggers = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); @@ -823,9 +821,8 @@ fn perex_split_collecting_throw_releases_native_arguments_and_restores_this() { let sep = object(&scope); let method = function(&scope, throwing_hook as *const u8, 2); symbol(&sep, "split", method.get_nanbox_f64()); + // A young object whose move proves the throwing hook's collection ran. let previous = object(&scope); - let displaced = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); let roots = RuntimeHandleScope::active_len_for_tests(); let live = external_side_live_bytes(); let before = previous.get_nanbox_f64().to_bits(); @@ -837,11 +834,6 @@ fn perex_split_collecting_throw_releases_native_arguments_and_restores_this() { assert_eq!(RuntimeHandleScope::active_len_for_tests(), roots); assert_eq!(external_side_live_bytes(), live); assert_ne!(previous.get_nanbox_f64().to_bits(), before); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - previous.get_nanbox_f64().to_bits() - ); - crate::object::js_implicit_this_set(displaced.get_nanbox_f64()); } #[test] @@ -977,19 +969,19 @@ extern "C" fn primitive_result( } extern "C" fn primitive_number_hint( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, hint: f64, ) -> f64 { assert_eq!(bytes(hint), b"number"); - primitive_result(c, crate::closure::body_call::current_this()) + primitive_result(c, this) } extern "C" fn primitive_string_hint( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, hint: f64, ) -> f64 { assert_eq!(bytes(hint), b"string"); - primitive_result(c, crate::closure::body_call::current_this()) + primitive_result(c, this) } #[test] @@ -1002,9 +994,6 @@ fn perex_abstract_string_conversion_rejects_symbols_after_collecting_object_hook let scope = RuntimeHandleScope::new(); let symbol_value = scope.root_nanbox_f64(unsafe { crate::symbol::js_symbol_new_empty() }); let string_value = text(&scope, b"23"); - let previous = object(&scope); - let displaced = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); for value in [&symbol_value, &string_value] { for mode in 0..4 { let local = RuntimeHandleScope::new(); @@ -1048,13 +1037,8 @@ fn perex_abstract_string_conversion_rejects_symbols_after_collecting_object_hook let result = local.root_string_ptr(result.unwrap()); assert_eq!(bytes(handle_string_value(&result)), b"23"); } - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - previous.get_nanbox_f64().to_bits() - ); } } - crate::object::js_implicit_this_set(displaced.get_nanbox_f64()); } #[test] @@ -1119,9 +1103,9 @@ fn perex_construction_uses_strict_string_conversion_for_pattern_and_flags() { } extern "C" fn separator_text( _: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - field_event(8, b"text") + field_event(this, 8, b"text") } #[test] 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 4e14a61002..2bfc10cadd 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 @@ -1,66 +1,5 @@ use super::*; -#[test] -fn test_implicit_this_root_scanner_marks_and_rewrites() { - // Regression for #1813. The implicit-`this` cell holds the NaN-boxed - // receiver across a dynamically-dispatched non-arrow method body. Under a - // moving GC triggered from inside that body (the @perryts/mysql - // Pool.acquire → handshake → nativeScramble path under concurrent load) - // the receiver relocates. The scanner must (a) MARK it so it is not swept - // when the cell is its only root, and (b) REWRITE the cell to the moved - // copy so the body's next `this`-derived dispatch derefs live memory - // instead of the stale slot (the reported SIGSEGV in js_native_call_method). - // `nursery_user` is live across the `arena_alloc_gc_old` call below (used - // afterwards to build `nursery_hdr`); the block-full slow path in that - // allocation can reach `gc_check_trigger()`, so suppress automatic - // triggers for the setup below. - let _trigger_guard = GcTriggerThresholdTestGuard::suppress_automatic_triggers(); - clear_marks(); - clear_mark_seeds(); - let prev_this = crate::object::js_implicit_this_get(); - - let nursery_user = crate::arena::arena_alloc_gc(64, 8, GC_TYPE_OBJECT); - let valid_ptrs = build_valid_pointer_set(); - let old_user = crate::arena::arena_alloc_gc_old(64, 8, GC_TYPE_OBJECT); - let nursery_hdr = unsafe { header_from_user_ptr(nursery_user) as *mut GcHeader }; - - crate::object::js_implicit_this_set(f64::from_bits(ptr_bits(nursery_user as usize))); - - // Mark phase: the live receiver must be discovered as a root. - crate::object::scan_implicit_this_roots_mut(&mut RuntimeRootVisitor::for_mark(&valid_ptrs)); - unsafe { - assert_ne!( - (*nursery_hdr).gc_flags & GC_FLAG_MARKED, - 0, - "IMPLICIT_THIS scanner must mark the receiver so GC does not sweep `this`" - ); - } - - // Rewrite phase: the cell must follow the forwarding pointer. - unsafe { - set_forwarding_address(nursery_hdr, old_user); - } - crate::object::scan_implicit_this_roots_mut(&mut RuntimeRootVisitor::for_rewrite(&valid_ptrs)); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - ptr_bits(old_user as usize), - "IMPLICIT_THIS must be rewritten to the receiver's relocated copy (#1813)" - ); - - // Idle / undefined cell must be a no-op (the default state between calls). - crate::object::js_implicit_this_set(f64::from_bits(crate::value::TAG_UNDEFINED)); - crate::object::scan_implicit_this_roots_mut(&mut RuntimeRootVisitor::for_rewrite(&valid_ptrs)); - assert_eq!( - crate::object::js_implicit_this_get().to_bits(), - crate::value::TAG_UNDEFINED, - "scanning the idle implicit-`this` cell must leave TAG_UNDEFINED untouched" - ); - - crate::object::js_implicit_this_set(prev_this); - clear_marks(); - clear_mark_seeds(); -} - #[test] fn test_class_side_table_scanner_marks_values_but_not_function_keys() { let _guard = GcTestIsolationGuard::new(); diff --git a/crates/perry-runtime/src/intl.rs b/crates/perry-runtime/src/intl.rs index 7ec7fa62e8..74e3c70604 100644 --- a/crates/perry-runtime/src/intl.rs +++ b/crates/perry-runtime/src/intl.rs @@ -987,11 +987,31 @@ fn dt_component_option_from_handle( /// (`"MEZ"`, `"invalid"`, `"Europe/İstanbul"`, …) do not. Returns the (best /// effort, un-recased) canonical identifier, or `None` to signal `RangeError`. fn make_instance(closure: *const ClosureHeader, kind: &str, locales: f64, options: f64) -> f64 { + make_instance_this( + closure, + crate::closure::JsThis::UNDEFINED, + kind, + locales, + options, + ) +} + +/// [`make_instance`] for a constructor native called with receiver `this`: +/// the receiver the legacy ChainNumberFormat / ChainDateTimeFormat path +/// (`ctor_guard::chain_legacy_constructed`) installs the instance on. +fn make_instance_this( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + kind: &str, + locales: f64, + options: f64, +) -> f64 { // Locale/option access can invoke user Proxy traps. Keep both arguments and // the partially initialized result live across those calls; the handles are // refreshed explicitly in the long PluralRules read-order sequence below. let scope = crate::gc::RuntimeHandleScope::new(); let closure_handle = scope.root_raw_const_ptr(closure); + let this_handle = scope.root_nanbox_f64(this.as_f64()); let locales_handle = scope.root_nanbox_f64(locales); let options_handle = scope.root_nanbox_f64(options); let locale = locale_or_default(locales_handle.get_nanbox_f64()); @@ -1631,99 +1651,21 @@ fn make_instance(closure: *const ClosureHeader, kind: &str, locales: f64, option // ChainNumberFormat / ChainDateTimeFormat only (see `chain_legacy_constructed`): // Intl.Collator ignores its this-value, so it is deliberately excluded. if matches!(kind, KIND_NUMBER | KIND_DATE_TIME) { - if let Some(this_value) = closure_handle - .with_const_ptr(|closure| ctor_guard::chain_legacy_constructed(closure, instance)) - { + if let Some(this_value) = closure_handle.with_const_ptr(|closure| { + ctor_guard::chain_legacy_constructed(closure, this_handle.get_nanbox_f64(), instance) + }) { return this_value; } } instance } -pub(super) extern "C" fn number_format_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - make_instance(closure, KIND_NUMBER, rest_arg(rest, 0), rest_arg(rest, 1)) -} - -pub(super) extern "C" fn date_time_format_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - make_instance( - closure, - KIND_DATE_TIME, - rest_arg(rest, 0), - rest_arg(rest, 1), - ) -} - -pub(super) extern "C" fn collator_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - make_instance(closure, KIND_COLLATOR, rest_arg(rest, 0), rest_arg(rest, 1)) -} - -pub(super) extern "C" fn segmenter_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - require_new_target("Segmenter"); - make_instance( - closure, - KIND_SEGMENTER, - rest_arg(rest, 0), - rest_arg(rest, 1), - ) -} - -pub(super) extern "C" fn list_format_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - require_new_target("ListFormat"); - make_instance( - closure, - KIND_LIST_FORMAT, - rest_arg(rest, 0), - rest_arg(rest, 1), - ) -} - -pub(super) extern "C" fn relative_time_format_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - require_new_target("RelativeTimeFormat"); - make_instance( - closure, - KIND_RELATIVE_TIME, - rest_arg(rest, 0), - rest_arg(rest, 1), - ) -} - -pub(super) extern "C" fn plural_rules_constructor_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - rest: f64, -) -> f64 { - require_new_target("PluralRules"); - make_instance( - closure, - KIND_PLURAL_RULES, - rest_arg(rest, 0), - rest_arg(rest, 1), - ) -} +mod constructor_thunks; +pub(super) use constructor_thunks::{ + collator_constructor_thunk, date_time_format_constructor_thunk, list_format_constructor_thunk, + number_format_constructor_thunk, plural_rules_constructor_thunk, + relative_time_format_constructor_thunk, segmenter_constructor_thunk, +}; fn supported_locales_array(locales: f64, options: f64) -> f64 { // `supportedLocalesOf(locales, options)`: diff --git a/crates/perry-runtime/src/intl/constructor_thunks.rs b/crates/perry-runtime/src/intl/constructor_thunks.rs new file mode 100644 index 0000000000..70b3406679 --- /dev/null +++ b/crates/perry-runtime/src/intl/constructor_thunks.rs @@ -0,0 +1,96 @@ +//! The `Intl.*` constructor natives (split out of `intl.rs` for the +//! file-size gate; unchanged). + +use super::*; + +pub(crate) extern "C" fn number_format_constructor_thunk( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + rest: f64, +) -> f64 { + make_instance_this( + closure, + this, + KIND_NUMBER, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} + +pub(crate) extern "C" fn date_time_format_constructor_thunk( + closure: *const ClosureHeader, + this: crate::closure::JsThis, + rest: f64, +) -> f64 { + make_instance_this( + closure, + this, + KIND_DATE_TIME, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} + +pub(crate) extern "C" fn collator_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { + make_instance(closure, KIND_COLLATOR, rest_arg(rest, 0), rest_arg(rest, 1)) +} + +pub(crate) extern "C" fn segmenter_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { + require_new_target("Segmenter"); + make_instance( + closure, + KIND_SEGMENTER, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} + +pub(crate) extern "C" fn list_format_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { + require_new_target("ListFormat"); + make_instance( + closure, + KIND_LIST_FORMAT, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} + +pub(crate) extern "C" fn relative_time_format_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { + require_new_target("RelativeTimeFormat"); + make_instance( + closure, + KIND_RELATIVE_TIME, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} + +pub(crate) extern "C" fn plural_rules_constructor_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, + rest: f64, +) -> f64 { + require_new_target("PluralRules"); + make_instance( + closure, + KIND_PLURAL_RULES, + rest_arg(rest, 0), + rest_arg(rest, 1), + ) +} diff --git a/crates/perry-runtime/src/intl/ctor_guard.rs b/crates/perry-runtime/src/intl/ctor_guard.rs index 912149cbfb..9996c6d755 100644 --- a/crates/perry-runtime/src/intl/ctor_guard.rs +++ b/crates/perry-runtime/src/intl/ctor_guard.rs @@ -113,6 +113,7 @@ fn receiver_on_constructor_chain(receiver: f64, closure: *const ClosureHeader) - /// this for the Collator kind. pub(super) fn chain_legacy_constructed( closure: *const ClosureHeader, + this_value: f64, instance: f64, ) -> Option { // Only the legacy path: a `new`-invocation (new.target present) always @@ -120,7 +121,6 @@ pub(super) fn chain_legacy_constructed( if crate::object::js_new_target_get().to_bits() != crate::value::TAG_UNDEFINED { return None; } - let this_value = crate::object::js_implicit_this_get(); let this_bits = this_value.to_bits(); // `this` must be an Object (pointer-tagged, valid heap object). if (this_bits & !POINTER_MASK) != POINTER_TAG { diff --git a/crates/perry-runtime/src/intl/subclass.rs b/crates/perry-runtime/src/intl/subclass.rs index 1af40ded1a..f077ff7bac 100644 --- a/crates/perry-runtime/src/intl/subclass.rs +++ b/crates/perry-runtime/src/intl/subclass.rs @@ -147,21 +147,19 @@ pub(crate) unsafe fn intl_subclass_super( if !is_intl_constructor_value(parent_val) { return false; } - // #9445: the parent constructor is user-reachable code; root the displaced - // receiver AND `this_box`, which is consumed again after the call. + // #9445: the parent constructor is user-reachable code; root `this_box`, + // which is passed as the receiver and consumed again after the call. let this_scope = crate::gc::RuntimeHandleScope::new(); let this_h = this_scope.root_nanbox_f64(this_box); - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); // #10490: the displaced `new.target` crosses the same call. let prev_nt = this_scope.root_nanbox_f64(crate::object::js_new_target_set(parent_val)); - let instance = crate::closure::js_native_call_value( + let instance = crate::closure::native_call_value_this( parent_val, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), args_ptr, args_len, ); crate::object::js_new_target_set(prev_nt.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // Re-home the freshly-built instance's brand + bound methods onto `this`. let this_bits = this_h.get_nanbox_f64().to_bits(); if (this_bits >> 48) == 0x7FFD { diff --git a/crates/perry-runtime/src/iterator_helpers.rs b/crates/perry-runtime/src/iterator_helpers.rs index bb974fc735..81fe1e8762 100644 --- a/crates/perry-runtime/src/iterator_helpers.rs +++ b/crates/perry-runtime/src/iterator_helpers.rs @@ -99,12 +99,11 @@ pub fn is_iterator_helper_addr(addr: usize) -> bool { /// it with the INHERITING getter is what killed the entire helper surface. /// Every built-in iterator now inherits `.next` from its shared /// `%…IteratorPrototype%` singleton (`object/iterator_prototypes.rs`), and that -/// inherited `next` is a THUNK that resolves its receiver from -/// `js_implicit_this_get()` and dispatches by class id. So -/// `js_object_get_field_by_name` found a perfectly callable closure for an -/// array / Map / Set / String iterator, this function took the closure-call -/// branch, and the thunk ran with whatever `this` happened to be left in the -/// thread-local — reporting `done` on the first step for every source kind, or +/// inherited `next` is a THUNK that resolves its receiver from the call's +/// `this` and dispatches by class id. So `js_object_get_field_by_name` found a +/// perfectly callable closure for an array / Map / Set / String iterator, this +/// function took the closure-call branch, and the thunk ran with whatever +/// receiver that call supplied rather than the iterator — reporting `done` on the first step for every source kind, or /// throwing `Method %IteratorPrototype%.next called on incompatible receiver`. /// `js_iterator_to_array` already carries the own-field version of this fix /// (#321); this is the same fix for the helper chain. @@ -148,21 +147,17 @@ unsafe fn iterator_step(iter_f64: f64) -> (f64, bool) { }; let use_field = !next_ptr.is_null() && is_closure_ptr(next_ptr as usize); - // The user's `next` runs here and can collect anything, so the receiver and - // the saved implicit-`this` both come out of handles rather than out of - // bare locals that predate the call. + // The user's `next` runs here and can collect anything, so the receiver + // comes out of a handle rather than out of a bare local that predates the + // call. let result_f64 = if use_field { let next_h = scope.root_nanbox_f64(f64::from_bits(next_val.bits())); - let prev_this_h = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(iter_h.get_nanbox_f64())); - let r = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( next_h.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(iter_h.get_nanbox_f64()), std::ptr::null(), 0, - ); - crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); - r + ) } else { crate::object::js_native_call_method( iter_h.get_nanbox_f64(), diff --git a/crates/perry-runtime/src/iterator_helpers/tests.rs b/crates/perry-runtime/src/iterator_helpers/tests.rs index f23ac4ec76..317454bcf9 100644 --- a/crates/perry-runtime/src/iterator_helpers/tests.rs +++ b/crates/perry-runtime/src/iterator_helpers/tests.rs @@ -509,12 +509,12 @@ fn symbol_iterator_returns_the_helper_itself() { /// /// This pins the second half of the `iterator_step` fix independently of the /// own-vs-inherited lookup: an own `next` takes the closure branch either way, -/// so only the `js_implicit_this_set` around the call makes this pass. It is +/// so only the explicit receiver on the call makes this pass. It is /// the shape a hand-written `next() { return this.#impl.next(); }` takes, and /// pre-fix it saw `undefined`. /// -/// SABOTAGE CHECK: drop the `js_implicit_this_set` pair in `iterator_step` and -/// this reports a receiver of `undefined`. +/// SABOTAGE CHECK: pass `JsThis::UNDEFINED` instead of the iterator in +/// `iterator_step` and this reports a receiver of `undefined`. #[test] fn an_own_next_method_is_called_with_the_iterator_as_this() { use std::sync::atomic::{AtomicU64, Ordering}; @@ -523,10 +523,10 @@ fn an_own_next_method_is_called_with_the_iterator_as_this() { extern "C" fn next_recording_this( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); OBSERVED_THIS.store(this.to_bits(), Ordering::SeqCst); unsafe { crate::iter_result::make_iter_result(JSValue::undefined(), true) } } diff --git a/crates/perry-runtime/src/json/replacer.rs b/crates/perry-runtime/src/json/replacer.rs index 6e9ee2549d..b64d2d3a6f 100644 --- a/crates/perry-runtime/src/json/replacer.rs +++ b/crates/perry-runtime/src/json/replacer.rs @@ -57,8 +57,8 @@ fn tombstoned_key_slot(key: f64) -> bool { /// keys its already-serialized/dedup Maps by `this`) breaks without it — the /// Flight encoder's `referenceMap.get(this)` then never finds the parent path, /// so it re-serializes endlessly (Next.js standalone startup runaway). Mirror -/// the reviver path (`internalize_json_property`), which sets the implicit -/// `this` to the holder around the user-callback call. +/// the reviver path (`internalize_json_property`), which passes the holder as +/// the user callback's `this`. #[inline] pub(crate) unsafe fn call_replacer( replacer: *const crate::ClosureHeader, @@ -66,15 +66,12 @@ pub(crate) unsafe fn call_replacer( value_f64: f64, holder_f64: f64, ) -> f64 { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(holder_f64)); - let result = crate::js_closure_call2( + let result = crate::closure::js_closure_call2( replacer, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(holder_f64), key_f64, value_f64, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON` // (#6009 fast-probe cache). super::invalidate_object_proto_tojson_state(); diff --git a/crates/perry-runtime/src/json/reviver.rs b/crates/perry-runtime/src/json/reviver.rs index e68808afea..6b9659ab3c 100644 --- a/crates/perry-runtime/src/json/reviver.rs +++ b/crates/perry-runtime/src/json/reviver.rs @@ -715,15 +715,13 @@ unsafe fn call_reviver( let holder_arg = holder_handle.get_nanbox_f64(); let key_arg = key_handle.get_nanbox_f64(); let value_arg = value_handle.get_nanbox_f64(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(holder_arg)); - let result = crate::js_closure_call3( + let result = crate::closure::js_closure_call3( reviver, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(holder_arg), key_arg, value_arg, context_handle.get_nanbox_f64(), ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); let result_bits = result.to_bits(); let revived_bits = if result_bits == value_arg.to_bits() { value_handle.get_nanbox_u64() diff --git a/crates/perry-runtime/src/json/stringify.rs b/crates/perry-runtime/src/json/stringify.rs index 84d6e14485..12b0dc140f 100644 --- a/crates/perry-runtime/src/json/stringify.rs +++ b/crates/perry-runtime/src/json/stringify.rs @@ -313,15 +313,12 @@ pub(crate) unsafe fn object_get_to_json(ptr: *const u8) -> Option { // record it in `TO_JSON_KEY` before recursing here. let key_f64_arg = current_to_json_key_arg(); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(recv_handle.get_nanbox_f64()), &key_f64_arg, 1, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); Some(result) @@ -370,16 +367,12 @@ pub(crate) unsafe fn array_get_to_json(arr: *const crate::ArrayHeader) -> Option let recv_handle = scope.root_nanbox_f64(recv); // `toJSON(key)` receives the property key of this array value (#5909). let key_f64_arg = current_to_json_key_arg(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - recv_handle.get_nanbox_f64(), - )); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(method_bits), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(recv_handle.get_nanbox_f64()), &key_f64_arg, 1, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); Some(result) diff --git a/crates/perry-runtime/src/json/stringify_scalars.rs b/crates/perry-runtime/src/json/stringify_scalars.rs index 6b73315def..de3884b973 100644 --- a/crates/perry-runtime/src/json/stringify_scalars.rs +++ b/crates/perry-runtime/src/json/stringify_scalars.rs @@ -362,18 +362,14 @@ pub(crate) unsafe fn bigint_apply_to_json(value: f64) -> Option { if !crate::closure::is_closure_ptr(method_ptr) { return None; } - let recv = value_handle.get_nanbox_f64(); // `toJSON(key)` receives the property key of this BigInt value (#5909). let key_f64_arg = super::stringify_tojson_probe::current_to_json_key_arg(); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(method_bits), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), &key_f64_arg, 1, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); // The user callback may have installed/removed `Object.prototype.toJSON`. invalidate_object_proto_tojson_state(); Some(result) diff --git a/crates/perry-runtime/src/map.rs b/crates/perry-runtime/src/map.rs index 5b58020a1b..6b07189464 100644 --- a/crates/perry-runtime/src/map.rs +++ b/crates/perry-runtime/src/map.rs @@ -3052,8 +3052,6 @@ fn js_map_foreach_impl( // appends are visited too. Bounding the walk by `size` exposed holes // and truncated later entries (#9072). let mut i = 0usize; - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); loop { let map = map_handle.get_raw_const_ptr::(); if i >= (*map).used as usize { @@ -3076,18 +3074,10 @@ fn js_map_foreach_impl( } let args = [value, key, map_value]; let cb = callback_handle.get_nanbox_f64(); - let this_v = this_handle.get_nanbox_f64(); - // Bind `thisArg` for the duration of the call (matches the - // URLSearchParams.forEach pattern); `js_native_call_value` + let this_v = crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()); + // `thisArg` is the callback's receiver; the value-call path // dispatches the NaN-boxed callback with the full arg vector. - crate::object::js_implicit_this_set(this_v); - let _ = crate::closure::js_native_call_value( - cb, - crate::closure::plain_call_receiver(), - args.as_ptr(), - args.len(), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); + let _ = crate::closure::native_call_value_this(cb, this_v, args.as_ptr(), args.len()); } } let map = map_handle.get_raw_const_ptr::(); diff --git a/crates/perry-runtime/src/messaging.rs b/crates/perry-runtime/src/messaging.rs index 4e4aa42d77..47eed3dacb 100644 --- a/crates/perry-runtime/src/messaging.rs +++ b/crates/perry-runtime/src/messaging.rs @@ -321,17 +321,15 @@ fn invoke_message_handler(handler: f64, event: f64, port_box: f64) { let handler_h = scope.root_nanbox_f64(handler); let event_h = scope.root_nanbox_f64(event); let port_h = scope.root_nanbox_f64(port_box); - let prev_this = scope.root_nanbox_f64(object::js_implicit_this_set(port_h.get_nanbox_f64())); let args = [event_h.get_nanbox_f64()]; unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( handler_h.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(port_h.get_nanbox_f64()), args.as_ptr(), args.len(), ); } - object::js_implicit_this_set(prev_this.get_nanbox_f64()); } /// Macrotask body: deliver exactly one queued message to `port_box`'s port. diff --git a/crates/perry-runtime/src/module_require.rs b/crates/perry-runtime/src/module_require.rs index ee138338e9..28d7c275b9 100644 --- a/crates/perry-runtime/src/module_require.rs +++ b/crates/perry-runtime/src/module_require.rs @@ -1529,7 +1529,7 @@ static KEEP_JS_MODULE_AMBIENT_REQUIRE: extern "C" fn() -> f64 = js_module_ambien pub extern "C" fn js_module_ambient_require_apply(spec: f64) -> f64 { require_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), spec, ) } diff --git a/crates/perry-runtime/src/node_api_host/functions.rs b/crates/perry-runtime/src/node_api_host/functions.rs index e23a3964f1..670b85a15f 100644 --- a/crates/perry-runtime/src/node_api_host/functions.rs +++ b/crates/perry-runtime/src/node_api_host/functions.rs @@ -292,19 +292,14 @@ pub unsafe extern "C" fn napi_call_function( }; arguments.push(f64::from_bits(bits)); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let previous_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set( - f64::from_bits(receiver_bits), - )); let call_result = catch_value_call(env, || { crate::closure::js_native_call_value( f64::from_bits(function_bits), - crate::closure::plain_call_receiver(), + crate::closure::JsThis(receiver_bits), arguments.as_ptr(), arguments.len(), ) }); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); match call_result { Ok(value) => { if !result.is_null() { diff --git a/crates/perry-runtime/src/node_inspector.rs b/crates/perry-runtime/src/node_inspector.rs index 079f9682c1..0054c5a77c 100644 --- a/crates/perry-runtime/src/node_inspector.rs +++ b/crates/perry-runtime/src/node_inspector.rs @@ -255,18 +255,14 @@ fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { if !is_callable_value(callback) { return undefined(); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( callback, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + } } fn node_error_value(message: &str, code: &'static str) -> f64 { diff --git a/crates/perry-runtime/src/node_repl.rs b/crates/perry-runtime/src/node_repl.rs index c2868b294b..550da09b40 100644 --- a/crates/perry-runtime/src/node_repl.rs +++ b/crates/perry-runtime/src/node_repl.rs @@ -164,22 +164,21 @@ fn call_function(callback: f64, this: f64, args: &[f64]) -> f64 { if !is_callable_value(callback) { return undefined(); } + let this_scope = crate::gc::RuntimeHandleScope::new(); + // The rebind clone allocates, so the receiver is re-read from a root. + let this_h = this_scope.root_nanbox_f64(this); let rebound = f64::from_bits(crate::closure::clone_closure_rebind_this( callback.to_bits(), this, )); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( rebound, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + } } fn call_method(receiver: f64, name: &str, args: &[f64]) -> f64 { diff --git a/crates/perry-runtime/src/node_stream.rs b/crates/perry-runtime/src/node_stream.rs index 60c890aca4..b497f239f3 100644 --- a/crates/perry-runtime/src/node_stream.rs +++ b/crates/perry-runtime/src/node_stream.rs @@ -162,7 +162,7 @@ pub use destroy_state::{js_node_stream_method_destroy, js_node_stream_method_des // ───────────────────────────────────────────────────────────────── #[inline] -fn this_value(closure: *const ClosureHeader) -> f64 { +fn this_value(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { // Slot 0 was set by `build_object` to the NaN-boxed bits of the // host object value cast to i64; reverse the cast. if !closure.is_null() { @@ -170,8 +170,8 @@ fn this_value(closure: *const ClosureHeader) -> f64 { // A `TAG_UNDEFINED` slot-0 marks a *prototype-method value* (e.g. // `Stream.prototype.on`, installed by `attach_event_emitter_prototype_methods`) // rather than an instance-bound method: it has no fixed receiver and must - // read the call-site `this` (set by `Function.prototype.call`/`apply` into - // IMPLICIT_THIS), so `Stream.prototype.on.call(streamInstance, ev, fn)` + // read the call-site `this` (the method's receiver parameter, which + // `Function.prototype.call`/`apply` supply), so `Stream.prototype.on.call(streamInstance, ev, fn)` // — readable-stream's `Readable.prototype.on` wrapper — registers the // listener on the instance, not on the prototype. `build_object` always // captures a real object pointer, so existing instance-bound closures @@ -180,35 +180,35 @@ fn this_value(closure: *const ClosureHeader) -> f64 { return f64::from_bits(bits); } } - crate::object::js_implicit_this_get() + this.as_f64() } -extern "C" fn ns_chain0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - this_value(closure) +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, + this: crate::closure::JsThis, _a: f64, ) -> f64 { - this_value(closure) + this_value(closure, this) } extern "C" fn ns_chain2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, _b: f64, ) -> f64 { - this_value(closure) + this_value(closure, this) } extern "C" fn ns_chain3( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, _b: f64, _c: f64, ) -> f64 { - this_value(closure) + this_value(closure, this) } fn call_old_stream_on(old_stream: f64, event: &[u8], listener: *const ClosureHeader) { @@ -291,10 +291,10 @@ extern "C" fn ns_wrap_close(closure: *const ClosureHeader, _this: crate::closure extern "C" fn ns_wrap1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, old_stream: f64, ) -> f64 { - let stream = this_value(closure); + 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); @@ -555,11 +555,11 @@ extern "C" fn ns_writable_final_callback_done( extern "C" fn ns_emit2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, arg: f64, ) -> f64 { - let stream = this_value(closure); + 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); @@ -569,56 +569,49 @@ extern "C" fn ns_emit2( extern "C" fn ns_emit_rest( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, rest: f64, ) -> f64 { emit_stream_event_from_array( - this_value(closure), + this_value(closure, this), event, raw_ptr_from_value(rest) as *const _, ) } -extern "C" fn ns_resume0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - resume_readable_stream(this_value(closure)) +extern "C" fn ns_resume0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + resume_readable_stream(this_value(closure, this)) } -extern "C" fn ns_pause0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - pause_readable_stream(this_value(closure)) +extern "C" fn ns_pause0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + pause_readable_stream(this_value(closure, this)) } -extern "C" fn ns_is_paused0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - f64::from_bits(if readable_is_paused(this_value(closure)) { +extern "C" fn ns_is_paused0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + f64::from_bits(if readable_is_paused(this_value(closure, this)) { TAG_TRUE } else { TAG_FALSE }) } -extern "C" fn ns_async_dispose( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, -) -> f64 { - let stream = this_value(closure); +extern "C" fn ns_async_dispose(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + let stream = this_value(closure, this); destroy_stream(stream, abort_error()); resolved_promise(f64::from_bits(TAG_UNDEFINED)) } -extern "C" fn ns_read1( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, - n: f64, -) -> f64 { - let stream = this_value(closure); +extern "C" fn ns_read1(closure: *const ClosureHeader, this: crate::closure::JsThis, n: f64) -> f64 { + let stream = this_value(closure, this); read_stream_with_size_arg(stream, n) } extern "C" fn ns_set_encoding1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, encoding: f64, ) -> f64 { - let stream = this_value(closure); + let stream = this_value(closure, this); set_visible_readable_encoding(stream, normalize_readable_encoding(encoding)); stream } @@ -866,7 +859,7 @@ fn readable_push_receiver(this: crate::closure::JsThis, closure: *const ClosureH return implicit; } if closure.is_null() { - return this_value(closure); + return this_value(closure, this); } throw_readable_push_invalid_receiver() } @@ -924,10 +917,10 @@ fn readable_unshift_after_end_error() -> f64 { extern "C" fn ns_unshift1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, ) -> f64 { - unshift_chunk(this_value(closure), chunk) + unshift_chunk(this_value(closure, this), chunk) } /// `readable.compose(stream)` (#1539): the instance-method form of @@ -935,22 +928,22 @@ extern "C" fn ns_unshift1( /// data-flow and error handling paths cover module and prototype calls. extern "C" fn ns_compose1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, arg: f64, ) -> f64 { - build_node_stream_compose(vec![this_value(closure), arg]) + build_node_stream_compose(vec![this_value(closure, this), arg]) } extern "C" fn ns_pipe2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, dest: f64, options: f64, ) -> f64 { if pipe_destination_is_missing(dest) { throw_readable_pipe_missing_destination(); } - let stream = this_value(closure); + let stream = this_value(closure, this); pipe_stream_to_destination(stream, dest, pipe_options_end(options)) } extern "C" fn ns_writable_write_done( @@ -970,10 +963,10 @@ extern "C" fn ns_writable_write_done( extern "C" fn ns_unpipe1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, dest: f64, ) -> f64 { - let stream = this_value(closure); + let stream = this_value(closure, this); if dest.to_bits() == TAG_UNDEFINED { unpipe_all_destinations(stream); } else { @@ -1151,33 +1144,33 @@ extern "C" fn transform_flush_callback( extern "C" fn ns_write3( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, enc: f64, cb: f64, ) -> f64 { - let stream = this_value(closure); + let stream = this_value(closure, this); write_writable_chunk(stream, chunk, enc, cb) } extern "C" fn ns_end3( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, chunk: f64, encoding: f64, cb: f64, ) -> f64 { - let stream = this_value(closure); + let stream = this_value(closure, this); finish_stream_with_args(stream, chunk, encoding, cb); stream } -extern "C" fn ns_cork0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - cork_stream(this_value(closure)) +extern "C" fn ns_cork0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + cork_stream(this_value(closure, this)) } -extern "C" fn ns_uncork0(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - uncork_stream(this_value(closure)) +extern "C" fn ns_uncork0(closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + uncork_stream(this_value(closure, this)) } extern "C" fn writable_write_callback_noop( @@ -1195,17 +1188,14 @@ fn invoke_writable_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: js_closure_set_capture_f64(cb, 2, callback); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let args = [chunk, enc, cb_value]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( write, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } else { throw_missing_stream_method("The _write() method is not implemented"); } @@ -1216,17 +1206,14 @@ fn invoke_writable_writev(stream: f64, chunks: f64) { let cb = js_closure_alloc(writable_write_callback_noop as *const u8, 0); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let args = [chunks, cb_value]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( writev, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } @@ -1266,17 +1253,14 @@ fn invoke_transform_write(stream: f64, chunk: f64, enc: f64, len: f64, callback: js_closure_set_capture_f64(cb, 2, callback); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); let args = [chunk, enc, cb_value]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( transform, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; } throw_missing_stream_method("The _transform() method is not implemented"); diff --git a/crates/perry-runtime/src/node_stream/async_iterator.rs b/crates/perry-runtime/src/node_stream/async_iterator.rs index f49277c2b3..6bb38ff58f 100644 --- a/crates/perry-runtime/src/node_stream/async_iterator.rs +++ b/crates/perry-runtime/src/node_stream/async_iterator.rs @@ -589,10 +589,10 @@ pub(super) fn call_source_iterator_return(stream: f64) { extern "C" fn ns_readable_iterator_next( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let iterator = scope.root_nanbox_f64(this_value(closure)); + let iterator = scope.root_nanbox_f64(this_value(closure, this)); if iterator_is_done(iterator.get_nanbox_f64()) { return readable_iterator_done(); } @@ -695,9 +695,9 @@ extern "C" fn ns_readable_iterator_next( extern "C" fn ns_readable_iterator_return( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let iterator = this_value(closure); + let iterator = this_value(closure, this); let already_done = iterator_is_done(iterator); iterator_mark_done(iterator); iterator_remove_listeners(iterator); @@ -715,24 +715,27 @@ extern "C" fn ns_readable_iterator_return( extern "C" fn ns_readable_iterator_self( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - this_value(closure) + this_value(closure, this) } pub(super) extern "C" fn ns_async_iterator( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - build_readable_async_iterator(this_value(closure), true) + build_readable_async_iterator(this_value(closure, this), true) } pub(super) extern "C" fn ns_iterator1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, opts: f64, ) -> f64 { - build_readable_async_iterator(this_value(closure), destroy_on_return_from_options(opts)) + build_readable_async_iterator( + this_value(closure, this), + destroy_on_return_from_options(opts), + ) } fn install_async_iterator_symbol(target: f64, func: crate::closure::body_call::js_body_fn_ty!()) { @@ -841,9 +844,9 @@ pub extern "C" fn js_make_single_value_async_iterator(value: f64) -> f64 { extern "C" fn single_value_iterator_next( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let iterator = this_value(closure); + let iterator = this_value(closure, this); if iterator_is_done(iterator) { return readable_iterator_done(); } @@ -855,9 +858,9 @@ extern "C" fn single_value_iterator_next( extern "C" fn single_value_iterator_return( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - iterator_mark_done(this_value(closure)); + iterator_mark_done(this_value(closure, this)); readable_iterator_done() } @@ -969,9 +972,9 @@ mod fifo_pending_tests { js_closure_set_capture_f64(data_cb, 0, iterator); set_readable_flowing(stream, f64::from_bits(TAG_TRUE)); - ns_readable_iter_on_data(data_cb, crate::closure::body_call::current_this(), 1.0); + ns_readable_iter_on_data(data_cb, crate::closure::JsThis::UNDEFINED, 1.0); assert!(readable_is_flowing(stream)); - ns_readable_iter_on_data(data_cb, crate::closure::body_call::current_this(), 2.0); + ns_readable_iter_on_data(data_cb, crate::closure::JsThis::UNDEFINED, 2.0); assert!(readable_is_paused(stream)); assert_eq!( @@ -1043,11 +1046,8 @@ mod fifo_pending_tests { let rejected = js_closure_alloc(ns_readable_source_iterator_rejected as *const u8, 1); js_closure_set_capture_f64(rejected, 0, iterator); - let result = ns_readable_source_iterator_rejected( - rejected, - crate::closure::body_call::current_this(), - 7.0, - ); + let result = + ns_readable_source_iterator_rejected(rejected, crate::closure::JsThis::UNDEFINED, 7.0); let promise = crate::value::js_nanbox_get_pointer(result) as *mut crate::promise::Promise; assert!(iterator_is_done(iterator)); assert!(stream_destroyed(stream)); diff --git a/crates/perry-runtime/src/node_stream_constructors/builders.rs b/crates/perry-runtime/src/node_stream_constructors/builders.rs index bea5497fca..3b4acfa968 100644 --- a/crates/perry-runtime/src/node_stream_constructors/builders.rs +++ b/crates/perry-runtime/src/node_stream_constructors/builders.rs @@ -286,7 +286,7 @@ pub unsafe extern "C" fn js_array_subclass_init_args( /// Legacy shape-carried compatibility closure for `Array.prototype.fill`. pub(super) extern "C" fn ns_array_fill( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, start: f64, end: f64, @@ -297,7 +297,7 @@ pub(super) extern "C" fn ns_array_fill( // from index 1 (node: `7|8|8`, perry: `8|8|8`). let present = |v: f64| i32::from(!JSValue::from_bits(v.to_bits()).is_undefined()); crate::array::js_array_fill_generic( - super::this_value(closure), + super::this_value(closure, this), value, present(start), start, diff --git a/crates/perry-runtime/src/node_stream_destroy_state.rs b/crates/perry-runtime/src/node_stream_destroy_state.rs index 67e34b9f06..584b5c9d80 100644 --- a/crates/perry-runtime/src/node_stream_destroy_state.rs +++ b/crates/perry-runtime/src/node_stream_destroy_state.rs @@ -73,17 +73,14 @@ pub(super) fn destroy_stream(stream: f64, err: f64) { err }; let args = [destroy_arg, cb_value]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( destroy, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; } } @@ -92,10 +89,10 @@ pub(super) fn destroy_stream(stream: f64, err: f64) { pub(super) extern "C" fn ns_destroy1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, err: f64, ) -> f64 { - let stream = this_value(closure); + let stream = this_value(closure, this); destroy_stream(stream, err); stream } diff --git a/crates/perry-runtime/src/node_stream_dispatch.rs b/crates/perry-runtime/src/node_stream_dispatch.rs index 42aca14f32..da3ae6ba19 100644 --- a/crates/perry-runtime/src/node_stream_dispatch.rs +++ b/crates/perry-runtime/src/node_stream_dispatch.rs @@ -309,8 +309,9 @@ fn event_emitter_async_resource_backing(receiver: f64) -> Option EventEmitterAsyncResourceBacking { - if let Some(backing) = event_emitter_async_resource_backing(this_value(closure)) { + if let Some(backing) = event_emitter_async_resource_backing(this_value(closure, this)) { return backing; } crate::node_submodules::diagnostics::throw_type_error_no_code( @@ -324,7 +325,7 @@ extern "C" fn ns_ee_async_resource_emit_rest( event: f64, rest: f64, ) -> f64 { - let backing = require_event_emitter_async_resource_receiver(closure); + let backing = require_event_emitter_async_resource_receiver(closure, this); let runtime_async_id = match backing { EventEmitterAsyncResourceBacking::RuntimeResource(resource) => { crate::async_hooks::js_async_resource_async_id(resource) as u64 @@ -343,9 +344,9 @@ extern "C" fn ns_ee_async_resource_emit_rest( extern "C" fn ns_ee_async_resource_destroy( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match require_event_emitter_async_resource_receiver(closure) { + match require_event_emitter_async_resource_receiver(closure, this) { EventEmitterAsyncResourceBacking::ExternalEmitter(handle) => { crate::object::event_emitter_async_resource_dispatch() .map(|dispatch| unsafe { dispatch(handle, 3) }) @@ -359,10 +360,10 @@ extern "C" fn ns_ee_async_resource_destroy( extern "C" fn ns_ee_async_resource_getter( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let operation = crate::closure::js_closure_get_capture_ptr(closure, 1) as u32; - match require_event_emitter_async_resource_receiver(closure) { + match require_event_emitter_async_resource_receiver(closure, this) { EventEmitterAsyncResourceBacking::ExternalEmitter(handle) => { crate::object::event_emitter_async_resource_dispatch() .map(|dispatch| unsafe { dispatch(handle, operation) }) diff --git a/crates/perry-runtime/src/node_stream_event_emitter.rs b/crates/perry-runtime/src/node_stream_event_emitter.rs index fe80e1dc4a..7484c91fe4 100644 --- a/crates/perry-runtime/src/node_stream_event_emitter.rs +++ b/crates/perry-runtime/src/node_stream_event_emitter.rs @@ -9,10 +9,10 @@ const STREAM_ONCE_PREFIX: &[u8] = b"__perryStreamOnce:"; pub(super) extern "C" fn ns_set_max_listeners( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - set_stream_max_listeners(super::this_value(closure), value) + set_stream_max_listeners(super::this_value(closure, this), value) } #[no_mangle] @@ -79,9 +79,9 @@ pub(crate) fn validate_max_listeners(value: f64) -> f64 { pub(super) extern "C" fn ns_get_max_listeners( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - stream_max_listeners(super::this_value(closure)) + stream_max_listeners(super::this_value(closure, this)) } #[no_mangle] @@ -95,55 +95,55 @@ fn stream_max_listeners(stream: f64) -> f64 { pub(super) extern "C" fn ns_on2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); add_stream_listener_for_event(stream, event, cb); stream } pub(super) extern "C" fn ns_once2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); add_stream_listener_for_event_with_options(stream, event, cb, true, false); stream } pub(super) extern "C" fn ns_prepend_listener2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); add_stream_listener_for_event_with_options(stream, event, cb, false, true); stream } pub(super) extern "C" fn ns_prepend_once_listener2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); add_stream_listener_for_event_with_options(stream, event, cb, true, true); stream } pub(super) extern "C" fn ns_remove_listener2( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, cb: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); remove_stream_listener_for_event(stream, event, cb); stream } @@ -159,10 +159,10 @@ pub(super) extern "C" fn ns_off2( pub(super) extern "C" fn ns_remove_all_listeners1( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); remove_all_stream_listeners_for_event(stream, event); stream } @@ -243,10 +243,10 @@ pub extern "C" fn js_node_stream_method_remove_all_listeners( pub(super) extern "C" fn ns_listener_count( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - stream_listener_count_for_event(super::this_value(closure), event) as f64 + stream_listener_count_for_event(super::this_value(closure, this), event) as f64 } #[no_mangle] @@ -260,9 +260,9 @@ pub extern "C" fn js_node_stream_method_listener_count(stream_handle: i64, event pub(super) extern "C" fn ns_event_names( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); f64::from_bits(JSValue::pointer(stream_event_names_array(stream) as *const u8).bits()) } @@ -273,10 +273,10 @@ pub extern "C" fn js_node_stream_method_event_names(stream_handle: i64) -> i64 { pub(super) extern "C" fn ns_listeners( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); f64::from_bits( JSValue::pointer(stream_listeners_array_for_event(stream, event, false) as *const u8) .bits(), @@ -285,10 +285,10 @@ pub(super) extern "C" fn ns_listeners( pub(super) extern "C" fn ns_raw_listeners( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { - let stream = super::this_value(closure); + let stream = super::this_value(closure, this); f64::from_bits( JSValue::pointer(stream_listeners_array_for_event(stream, event, true) as *const u8).bits(), ) @@ -711,18 +711,14 @@ pub(super) fn call_listener_args(stream: f64, listener: f64, args: &[f64]) -> f6 if !is_callable_value(listener) { return f64::from_bits(super::TAG_UNDEFINED); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( listener, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + } } pub(super) extern "C" fn ns_capture_rejection( diff --git a/crates/perry-runtime/src/node_stream_iter_helpers.rs b/crates/perry-runtime/src/node_stream_iter_helpers.rs index 051ec5ee35..850740474c 100644 --- a/crates/perry-runtime/src/node_stream_iter_helpers.rs +++ b/crates/perry-runtime/src/node_stream_iter_helpers.rs @@ -282,10 +282,10 @@ pub(super) fn extend_with_array( pub(super) extern "C" fn ns_iter_to_array( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, opts: f64, ) -> f64 { - let this = this_value(closure); + let this = this_value(closure, this); // An already-errored stream rejects immediately (matches the error-first // check `settle_consuming` used). if let Some(err) = readable_hidden_error(this) { @@ -353,11 +353,11 @@ pub(super) extern "C" fn ns_to_array_abort( pub(super) extern "C" fn ns_iter_map( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, mapper: f64, opts: f64, ) -> f64 { - let this = this_value(closure); + let this = this_value(closure, this); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); let cb = callback_closure(mapper); @@ -386,11 +386,11 @@ pub(super) extern "C" fn ns_iter_map( pub(super) extern "C" fn ns_iter_filter( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { - let this = this_value(closure); + let this = this_value(closure, this); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); let cb = callback_closure(predicate); @@ -775,13 +775,13 @@ pub(super) extern "C" fn ns_consume_abort( pub(super) extern "C" fn ns_iter_reduce( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, reducer: f64, initial: f64, opts: f64, ) -> f64 { consume_stream( - this_value(closure), + this_value(closure, this), reducer, CONSUME_OP_REDUCE, initial, @@ -791,12 +791,12 @@ pub(super) extern "C" fn ns_iter_reduce( pub(super) extern "C" fn ns_iter_for_each( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, action: f64, opts: f64, ) -> f64 { consume_stream( - this_value(closure), + this_value(closure, this), action, CONSUME_OP_FOR_EACH, f64::from_bits(TAG_UNDEFINED), @@ -806,12 +806,12 @@ pub(super) extern "C" fn ns_iter_for_each( pub(super) extern "C" fn ns_iter_find( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { consume_stream( - this_value(closure), + this_value(closure, this), predicate, CONSUME_OP_FIND, f64::from_bits(TAG_UNDEFINED), @@ -821,12 +821,12 @@ pub(super) extern "C" fn ns_iter_find( pub(super) extern "C" fn ns_iter_some( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { consume_stream( - this_value(closure), + this_value(closure, this), predicate, CONSUME_OP_SOME, f64::from_bits(TAG_UNDEFINED), @@ -836,12 +836,12 @@ pub(super) extern "C" fn ns_iter_some( pub(super) extern "C" fn ns_iter_every( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, predicate: f64, opts: f64, ) -> f64 { consume_stream( - this_value(closure), + this_value(closure, this), predicate, CONSUME_OP_EVERY, f64::from_bits(TAG_UNDEFINED), @@ -851,11 +851,11 @@ pub(super) extern "C" fn ns_iter_every( pub(super) extern "C" fn ns_iter_flat_map( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, mapper: f64, opts: f64, ) -> f64 { - let this = this_value(closure); + let this = this_value(closure, this); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); let cb = callback_closure(mapper); @@ -1072,10 +1072,10 @@ pub(super) extern "C" fn ns_take_limit_rejected( pub(super) extern "C" fn ns_take_source_next( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let iterator = scope.root_nanbox_f64(this_value(closure)); + let iterator = scope.root_nanbox_f64(this_value(closure, this)); if has_truthy_hidden(iterator.get_nanbox_f64(), hidden_key(TAKE_SOURCE_DONE_KEY)) { return take_source_done_result(); } @@ -1168,9 +1168,9 @@ pub(super) extern "C" fn ns_take_source_next( pub(super) extern "C" fn ns_take_source_return( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - finish_take_source(this_value(closure), None, true); + finish_take_source(this_value(closure, this), None, true); take_source_done_result() } @@ -1203,11 +1203,11 @@ fn take_source_iterator(source: f64, result: f64, count: u32) -> f64 { pub(super) extern "C" fn ns_iter_take( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, count: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let this = scope.root_nanbox_f64(this_value(closure)); + let this = scope.root_nanbox_f64(this_value(closure, this)); if !readable_chunks_nonempty(this.get_nanbox_f64()) { if get_hidden_value( this.get_nanbox_f64(), @@ -1252,10 +1252,10 @@ pub(super) extern "C" fn ns_iter_take( pub(super) extern "C" fn ns_iter_drop( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, count: f64, ) -> f64 { - let this = this_value(closure); + let this = this_value(closure, this); prepare_readable_for_iteration(this); let arr = readable_chunks_array(this); let mut out = crate::array::js_array_alloc(0); @@ -1274,8 +1274,8 @@ pub(super) extern "C" fn ns_iter_drop( mod take_tests { use super::*; - extern "C" fn source_next(closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - let source = this_value(closure); + 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; set_hidden_value(source, hidden_key(b"count"), value); if value > 2.0 && has_truthy_hidden(source, hidden_key(b"failAfterLimit")) { @@ -1298,9 +1298,9 @@ mod take_tests { extern "C" fn source_return( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let source = this_value(closure); + let source = this_value(closure, this); set_hidden_value(source, hidden_key(b"returned"), f64::from_bits(TAG_TRUE)); f64::from_bits(TAG_UNDEFINED) } @@ -1331,7 +1331,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!has_truthy_hidden(source, hidden_key(b"returned"))); @@ -1359,7 +1359,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); let iterator = get_hidden_value(result, hidden_key(READABLE_SOURCE_ITERATOR_KEY)).unwrap(); assert!(!stream_destroyed(stream)); @@ -1387,7 +1387,7 @@ mod take_tests { let take = js_closure_alloc(ns_iter_take as *const u8, 1); js_closure_set_capture_ptr(take, 0, stream.to_bits() as i64); - let result = ns_iter_take(take, crate::closure::body_call::current_this(), 2.0); + let result = ns_iter_take(take, crate::closure::JsThis::UNDEFINED, 2.0); assert_eq!(readable_hidden_error(result), Some(7.0)); assert_eq!(get_hidden_value(result, hidden_signal_key()), Some(8.0)); diff --git a/crates/perry-runtime/src/node_stream_pipeline.rs b/crates/perry-runtime/src/node_stream_pipeline.rs index 2c1ae29b5a..2beaf7d020 100644 --- a/crates/perry-runtime/src/node_stream_pipeline.rs +++ b/crates/perry-runtime/src/node_stream_pipeline.rs @@ -1242,7 +1242,6 @@ fn new_composed_duplex(stages: &[f64], source: Option, writable: bool) -> f hidden_key(b"__perryStreamComposePriming"), f64::from_bits(TAG_TRUE), ); - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let primed = catch_pipeline_throw(|| { prime_composed_duplex_from_source( composite.get_nanbox_f64(), @@ -1251,7 +1250,6 @@ fn new_composed_duplex(stages: &[f64], source: Option, writable: bool) -> f ); f64::from_bits(TAG_UNDEFINED) }); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); if let Err(err) = primed { let err = scope.root_nanbox_f64(err); fail_composed_duplex( diff --git a/crates/perry-runtime/src/node_stream_readwrite.rs b/crates/perry-runtime/src/node_stream_readwrite.rs index 8a07c6d1ca..9964b85786 100644 --- a/crates/perry-runtime/src/node_stream_readwrite.rs +++ b/crates/perry-runtime/src/node_stream_readwrite.rs @@ -707,17 +707,14 @@ pub(super) fn schedule_writable_finish(stream: f64, callback: Option) { callback.unwrap_or_else(|| f64::from_bits(TAG_UNDEFINED)), ); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( final_callback, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), [cb_value].as_ptr(), 1, ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return; } } @@ -1038,17 +1035,14 @@ pub(super) fn finish_transform_stream(stream: f64, callback: Option) -> boo callback.unwrap_or_else(|| f64::from_bits(TAG_UNDEFINED)), ); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( flush, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), [cb_value].as_ptr(), 1, ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); true } @@ -1320,17 +1314,14 @@ pub(super) fn invoke_construct_callback(stream: f64, opts: f64) { let cb = js_closure_alloc(ns_construct_callback_done as *const u8, 1); js_closure_set_capture_f64(cb, 0, stream); let cb_value = f64::from_bits(JSValue::pointer(cb as *const u8).bits()); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( construct, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), [cb_value].as_ptr(), 1, ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } pub(super) fn invoke_read_once(stream: f64) { @@ -1360,17 +1351,14 @@ fn invoke_read_once_inner(stream: f64, emit_default_error: bool) { } set_hidden_value(stream, hidden_read_invoked_key(), f64::from_bits(TAG_TRUE)); let size = get_hidden_value(stream, hidden_hwm_key()).unwrap_or_else(|| default_hwm(false)); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( read, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), [size].as_ptr(), 1, ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } pub(super) fn maybe_emit_default_read_error(stream: f64) { diff --git a/crates/perry-runtime/src/node_stream_tests.rs b/crates/perry-runtime/src/node_stream_tests.rs index 4ef6f15c6c..88f18fb97d 100644 --- a/crates/perry-runtime/src/node_stream_tests.rs +++ b/crates/perry-runtime/src/node_stream_tests.rs @@ -356,11 +356,11 @@ pub(super) extern "C" fn noop_listener( pub(super) extern "C" fn capture_data_listener( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + receiver: crate::closure::JsThis, chunk: f64, ) -> f64 { let expected = crate::closure::js_closure_get_capture_f64(closure, 0); - let actual = crate::object::js_implicit_this_get(); + let actual = receiver.as_f64(); READABLE_THIS_MATCHES.with(|matches| { matches .borrow_mut() @@ -408,10 +408,10 @@ pub(super) extern "C" fn capture_readable_listener( pub(super) extern "C" fn capture_end_listener( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + receiver: crate::closure::JsThis, ) -> f64 { let expected = crate::closure::js_closure_get_capture_f64(closure, 0); - let actual = crate::object::js_implicit_this_get(); + let actual = receiver.as_f64(); READABLE_THIS_MATCHES.with(|matches| { matches .borrow_mut() @@ -566,12 +566,12 @@ extern "C" fn read_throws( extern "C" fn transform_upper_callback( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + receiver: crate::closure::JsThis, chunk: f64, _enc: f64, cb: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = receiver.as_f64(); TRANSFORM_THIS_HAS_STREAM_STATE.with(|matches| { matches.borrow_mut().push( get_hidden_value(this, hidden_readable_flag_key()).is_some() @@ -633,29 +633,21 @@ extern "C" fn transform_error_callback( extern "C" fn transform_push_pair_callback( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + receiver: crate::closure::JsThis, _chunk: f64, _enc: f64, cb: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = receiver.as_f64(); let push = js_object_get_field_by_name_f64( raw_ptr_from_value(this) as *const ObjectHeader, hidden_key(b"push"), ); unsafe { - let _ = crate::closure::js_native_call_value( - push, - crate::closure::plain_call_receiver(), - [string_value("a")].as_ptr(), - 1, - ); - let _ = crate::closure::js_native_call_value( - push, - crate::closure::plain_call_receiver(), - [string_value("b")].as_ptr(), - 1, - ); + let _ = + crate::closure::native_call_value_this(push, receiver, [string_value("a")].as_ptr(), 1); + let _ = + crate::closure::native_call_value_this(push, receiver, [string_value("b")].as_ptr(), 1); let _ = crate::closure::js_native_call_value( cb, crate::closure::plain_call_receiver(), @@ -668,10 +660,10 @@ extern "C" fn transform_push_pair_callback( extern "C" fn transform_flush_tail_callback( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + receiver: crate::closure::JsThis, cb: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = receiver.as_f64(); TRANSFORM_THIS_HAS_STREAM_STATE.with(|matches| { matches.borrow_mut().push( get_hidden_value(this, hidden_readable_flag_key()).is_some() @@ -1321,23 +1313,21 @@ fn stream_json_stringify_uses_node_state_shape() { } #[test] -fn stream_methods_use_implicit_this_without_closure_capture() { +fn stream_methods_use_their_receiver_without_closure_capture() { let stream = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); - let prev_this = crate::object::js_implicit_this_set(stream); let _ = ns_end3( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(stream), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), f64::from_bits(TAG_UNDEFINED), ); - crate::object::js_implicit_this_set(prev_this); assert!(js_node_stream_is_stub_ended_after_read(stream)); } #[test] -fn stream_method_closure_capture_wins_over_stale_implicit_this() { +fn stream_method_closure_capture_wins_over_a_foreign_receiver() { let stream = js_node_stream_passthrough_new(f64::from_bits(TAG_UNDEFINED)); let other = box_pointer(crate::object::js_object_alloc(0, 0) as *const u8); let end = js_object_get_field_by_name_f64( @@ -1345,16 +1335,14 @@ fn stream_method_closure_capture_wins_over_stale_implicit_this() { hidden_key(b"end"), ); - let prev_this = crate::object::js_implicit_this_set(other); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( end, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(other), std::ptr::null(), 0, ); } - crate::object::js_implicit_this_set(prev_this); assert!(js_node_stream_is_stub_ended_after_read(stream)); assert!(!stream_hidden_ended(other)); @@ -1409,7 +1397,7 @@ fn readable_pipe_stub_returns_destination_and_rejects_missing_destination() { assert_eq!( ns_pipe2( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, dest, f64::from_bits(TAG_UNDEFINED) ) diff --git a/crates/perry-runtime/src/node_submodules/consumers.rs b/crates/perry-runtime/src/node_submodules/consumers.rs index 790cacf8d9..661cf4d002 100644 --- a/crates/perry-runtime/src/node_submodules/consumers.rs +++ b/crates/perry-runtime/src/node_submodules/consumers.rs @@ -478,7 +478,7 @@ fn call_collector_method( if let Some(promise) = promise_ptr_from_value(result) { crate::promise::js_promise_then(promise, step, reject); } else { - consumer_collect_step(step, crate::closure::body_call::current_this(), result); + consumer_collect_step(step, crate::closure::plain_call_receiver(), result); } } @@ -508,17 +508,14 @@ fn call_symbol_async_iterator(stream: f64) -> Option { if !is_callable_value(method) { return None; } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(stream)); let iterator = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), std::ptr::null(), 0, ) }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if iterator.to_bits() == crate::value::TAG_UNDEFINED { None } else { diff --git a/crates/perry-runtime/src/node_submodules/diagnostics.rs b/crates/perry-runtime/src/node_submodules/diagnostics.rs index 6966f6537a..75d9a70c8e 100644 --- a/crates/perry-runtime/src/node_submodules/diagnostics.rs +++ b/crates/perry-runtime/src/node_submodules/diagnostics.rs @@ -773,14 +773,6 @@ pub(crate) fn suppress_uncaught_drain f64>(f: F) -> f64 { result } -pub(crate) fn with_implicit_this f64>(this_arg: f64, f: F) -> f64 { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_arg)); - let result = f(); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result -} - pub(crate) fn update_channel_active(id: i64) { DIAG_CHANNELS.with(|channels| { if let Some(ch) = channels.borrow_mut().get_mut(&id) { @@ -1117,16 +1109,19 @@ pub(crate) fn run_store_wrapped( if !valid_closure_value(fn_value) { crate::closure::throw_not_callable(); } + let scope = crate::gc::RuntimeHandleScope::new(); + // The rebind clone allocates, so the receiver is re-read from a root. + let this_arg_handle = scope.root_nanbox_f64(this_arg); let rebound = crate::closure::clone_closure_rebind_this(fn_value.to_bits(), this_arg); let cb = (rebound & crate::value::POINTER_MASK) as *const ClosureHeader; - with_implicit_this(this_arg, || unsafe { - js_closure_call_array( + unsafe { + crate::closure::js_closure_call_array( cb as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), args.as_ptr(), args.len() as i64, ) - }) + } } pub(crate) extern "C" fn diag_channel_subscribe( @@ -1562,16 +1557,19 @@ pub(crate) fn call_fn_value(fn_value: f64, this_arg: f64, args: &[f64]) -> f64 { if !valid_closure_value(fn_value) { crate::closure::throw_not_callable(); } + let scope = crate::gc::RuntimeHandleScope::new(); + // The rebind clone allocates, so the receiver is re-read from a root. + let this_arg_handle = scope.root_nanbox_f64(this_arg); let rebound = crate::closure::clone_closure_rebind_this(fn_value.to_bits(), this_arg); let cb = (rebound & crate::value::POINTER_MASK) as *const ClosureHeader; - with_implicit_this(this_arg, || unsafe { - js_closure_call_array( + unsafe { + crate::closure::js_closure_call_array( cb as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), args.as_ptr(), args.len() as i64, ) - }) + } } pub(crate) extern "C" fn diag_trace_sync( diff --git a/crates/perry-runtime/src/node_submodules/fs_promises.rs b/crates/perry-runtime/src/node_submodules/fs_promises.rs index f5b5ac801c..1f78e6cab6 100644 --- a/crates/perry-runtime/src/node_submodules/fs_promises.rs +++ b/crates/perry-runtime/src/node_submodules/fs_promises.rs @@ -78,7 +78,7 @@ fn promise_from_result_value(call: impl FnOnce() -> Result) -> f64 { pub extern "C" fn js_fs_promises_read_file(path: f64, options: f64) -> f64 { thunk_fs_promises_readFile( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), path, options, ) @@ -88,7 +88,7 @@ pub extern "C" fn js_fs_promises_read_file(path: f64, options: f64) -> f64 { pub extern "C" fn js_fs_promises_write_file(path: f64, data: f64, options: f64) -> f64 { thunk_fs_promises_writeFile( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), path, data, options, @@ -99,7 +99,7 @@ pub extern "C" fn js_fs_promises_write_file(path: f64, data: f64, options: f64) pub extern "C" fn js_fs_promises_append_file(path: f64, data: f64, options: f64) -> f64 { thunk_fs_promises_appendFile( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), path, data, options, @@ -110,7 +110,7 @@ pub extern "C" fn js_fs_promises_append_file(path: f64, data: f64, options: f64) pub extern "C" fn js_fs_promises_mkdir(path: f64, options: f64) -> f64 { thunk_fs_promises_mkdir( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), path, options, ) @@ -120,7 +120,7 @@ pub extern "C" fn js_fs_promises_mkdir(path: f64, options: f64) -> f64 { pub extern "C" fn js_fs_promises_rmdir(path: f64, options: f64) -> f64 { thunk_fs_promises_rmdir( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), path, options, ) diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 7e4e9bbfc5..f876c2739c 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -24,7 +24,7 @@ use std::cell::RefCell; use std::sync::atomic::{AtomicI64, Ordering}; use crate::closure::{ - js_closure_alloc, js_closure_call0, js_closure_call1, js_closure_call2, js_closure_call_array, + 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, }; diff --git a/crates/perry-runtime/src/node_submodules/stream_promises.rs b/crates/perry-runtime/src/node_submodules/stream_promises.rs index b2d51895a4..59d409d09f 100644 --- a/crates/perry-runtime/src/node_submodules/stream_promises.rs +++ b/crates/perry-runtime/src/node_submodules/stream_promises.rs @@ -452,18 +452,14 @@ fn invoke_destination_method(destination: f64, method: &[u8], args: &[f64]) -> f let Some(func) = get_object_property(destination, method) else { return undefined_value(); }; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(destination)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( func, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(destination), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + } } fn write_chunks_to_destination(destination: f64, chunks: &[f64]) { diff --git a/crates/perry-runtime/src/node_submodules/test.rs b/crates/perry-runtime/src/node_submodules/test.rs index a458797be6..a00a1c2b8d 100644 --- a/crates/perry-runtime/src/node_submodules/test.rs +++ b/crates/perry-runtime/src/node_submodules/test.rs @@ -840,16 +840,15 @@ extern "C" fn mock_function_invoke( let rest_handle = scope.root_nanbox_f64(rest); let arg_handles = scope.root_nanbox_f64_slice(&args); let call_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_value)); // #9445 + let this_handle = scope.root_nanbox_f64(this_value); let call_result = catch_js(|| unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( implementation_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), call_args.as_ptr(), call_args.len(), ) }); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); match call_result { Ok(result) => { @@ -1600,7 +1599,7 @@ pub extern "C" fn js_node_test_mock_fn( ) -> f64 { mock_fn_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), original, implementation_or_options, options, @@ -1616,7 +1615,7 @@ pub extern "C" fn js_node_test_mock_method( ) -> f64 { mock_method_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), target, property, implementation, @@ -1633,7 +1632,7 @@ pub extern "C" fn js_node_test_mock_getter( ) -> f64 { mock_getter_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), target, property, implementation, @@ -1650,7 +1649,7 @@ pub extern "C" fn js_node_test_mock_setter( ) -> f64 { mock_setter_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), target, property, implementation, @@ -1675,19 +1674,19 @@ pub extern "C" fn js_node_test_mock_property_with_presence( #[no_mangle] pub extern "C" fn js_node_test_mock_reset() -> f64 { - mock_reset_thunk(std::ptr::null(), crate::closure::body_call::current_this()) + mock_reset_thunk(std::ptr::null(), crate::closure::plain_call_receiver()) } #[no_mangle] pub extern "C" fn js_node_test_mock_restore_all() -> f64 { - mock_restore_all_thunk(std::ptr::null(), crate::closure::body_call::current_this()) + mock_restore_all_thunk(std::ptr::null(), crate::closure::plain_call_receiver()) } #[no_mangle] pub extern "C" fn js_node_test_snapshot_set_default_serializers(serializers: f64) -> f64 { snapshot_set_default_serializers( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), serializers, ) } @@ -1696,7 +1695,7 @@ pub extern "C" fn js_node_test_snapshot_set_default_serializers(serializers: f64 pub extern "C" fn js_node_test_snapshot_set_resolve_snapshot_path(resolver: f64) -> f64 { snapshot_set_resolve_snapshot_path( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), resolver, ) } @@ -1705,37 +1704,29 @@ pub extern "C" fn js_node_test_snapshot_set_resolve_snapshot_path(resolver: f64) pub extern "C" fn js_node_test_mock_timers_enable(options: f64) -> f64 { mock_timers_enable( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), options, ) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_tick(ms: f64) -> f64 { - mock_timers_tick( - std::ptr::null(), - crate::closure::body_call::current_this(), - ms, - ) + mock_timers_tick(std::ptr::null(), crate::closure::plain_call_receiver(), ms) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_run_all() -> f64 { - mock_timers_run_all(std::ptr::null(), crate::closure::body_call::current_this()) + mock_timers_run_all(std::ptr::null(), crate::closure::plain_call_receiver()) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_set_time(ms: f64) -> f64 { - mock_timers_set_time( - std::ptr::null(), - crate::closure::body_call::current_this(), - ms, - ) + mock_timers_set_time(std::ptr::null(), crate::closure::plain_call_receiver(), ms) } #[no_mangle] pub extern "C" fn js_node_test_mock_timers_reset() -> f64 { - mock_timers_reset(std::ptr::null(), crate::closure::body_call::current_this()) + mock_timers_reset(std::ptr::null(), crate::closure::plain_call_receiver()) } pub(crate) fn decorate_test_export( diff --git a/crates/perry-runtime/src/node_submodules/test_runner.rs b/crates/perry-runtime/src/node_submodules/test_runner.rs index f5778669d6..9d1c821f5c 100644 --- a/crates/perry-runtime/src/node_submodules/test_runner.rs +++ b/crates/perry-runtime/src/node_submodules/test_runner.rs @@ -910,7 +910,7 @@ pub(crate) extern "C" fn thunk_test_run( pub extern "C" fn js_node_test_register(name: f64, options: f64, callback: f64) -> f64 { thunk_test( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), name, options, callback, @@ -921,7 +921,7 @@ pub extern "C" fn js_node_test_register(name: f64, options: f64, callback: f64) pub extern "C" fn js_node_test_skip(name: f64, options: f64, callback: f64) -> f64 { thunk_test_skip( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), name, options, callback, @@ -932,7 +932,7 @@ pub extern "C" fn js_node_test_skip(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_todo(name: f64, options: f64, callback: f64) -> f64 { thunk_test_todo( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), name, options, callback, @@ -943,7 +943,7 @@ pub extern "C" fn js_node_test_todo(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_only(name: f64, options: f64, callback: f64) -> f64 { thunk_test_only( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), name, options, callback, @@ -954,7 +954,7 @@ pub extern "C" fn js_node_test_only(name: f64, options: f64, callback: f64) -> f pub extern "C" fn js_node_test_suite(name: f64, options: f64, callback: f64) -> f64 { thunk_test_suite( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), name, options, callback, @@ -965,7 +965,7 @@ pub extern "C" fn js_node_test_suite(name: f64, options: f64, callback: f64) -> pub extern "C" fn js_node_test_before(callback: f64) -> f64 { thunk_test_before( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), callback, ) } @@ -974,7 +974,7 @@ pub extern "C" fn js_node_test_before(callback: f64) -> f64 { pub extern "C" fn js_node_test_after(callback: f64) -> f64 { thunk_test_after( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), callback, ) } @@ -983,7 +983,7 @@ pub extern "C" fn js_node_test_after(callback: f64) -> f64 { pub extern "C" fn js_node_test_before_each(callback: f64) -> f64 { thunk_test_before_each( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), callback, ) } @@ -992,7 +992,7 @@ pub extern "C" fn js_node_test_before_each(callback: f64) -> f64 { pub extern "C" fn js_node_test_after_each(callback: f64) -> f64 { thunk_test_after_each( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), callback, ) } @@ -1001,7 +1001,7 @@ pub extern "C" fn js_node_test_after_each(callback: f64) -> f64 { pub extern "C" fn js_node_test_run(options: f64) -> f64 { thunk_test_run( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), options, ) } diff --git a/crates/perry-runtime/src/node_submodules/tests.rs b/crates/perry-runtime/src/node_submodules/tests.rs index 0dcfc77326..d5050afb2b 100644 --- a/crates/perry-runtime/src/node_submodules/tests.rs +++ b/crates/perry-runtime/src/node_submodules/tests.rs @@ -600,16 +600,14 @@ fn sys_namespace_types_member_reuses_util_types() { fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { let stream = crate::node_stream::js_node_stream_passthrough_new(undefined_value()); let end = get_object_property(stream, b"end").expect("stream.end should exist"); - let prev_this = crate::object::js_implicit_this_set(stream); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( end, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), std::ptr::null(), 0, ); } - crate::object::js_implicit_this_set(prev_this); let _ = crate::promise::js_promise_run_microtasks(); let opts = js_object_alloc(0, 1); @@ -621,7 +619,7 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, boxed_ptr(opts as *const u8), ); @@ -638,7 +636,7 @@ fn stream_promises_finished_resolves_for_finished_writable_side_stub_stream() { fn stream_promises_finished_rejects_invalid_inputs() { let number_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, 123.0, undefined_value(), ); @@ -650,7 +648,7 @@ fn stream_promises_finished_rejects_invalid_inputs() { let string_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, string_value("x"), undefined_value(), ); @@ -663,7 +661,7 @@ fn stream_promises_finished_rejects_invalid_inputs() { let object = js_object_alloc(0, 0); let object_promise = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, boxed_ptr(object as *const u8), undefined_value(), ); @@ -682,7 +680,7 @@ fn stream_promises_finished_rejects_hidden_stream_error() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, undefined_value(), ); @@ -701,7 +699,7 @@ fn stream_promises_finished_rejects_later_destroy_error() { crate::node_stream::test_install_manual_read(stream); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, undefined_value(), ); @@ -775,7 +773,7 @@ fn stream_promises_finished_resolves_for_resumed_readable_from() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, undefined_value(), ); @@ -792,7 +790,7 @@ fn stream_promises_finished_duplex_default_waits_after_writable_finish_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, undefined_value(), ); @@ -816,7 +814,7 @@ fn stream_promises_finished_duplex_default_waits_after_readable_end_only() { let stream = crate::node_stream::js_node_stream_duplex_new(undefined_value()); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, undefined_value(), ); @@ -841,7 +839,7 @@ fn stream_promises_finished_duplex_readable_false_resolves_after_writable_finish let options = finished_bool_option(b"readable", false); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, options, ); @@ -861,7 +859,7 @@ fn stream_promises_finished_duplex_writable_false_resolves_after_readable_end() let options = finished_bool_option(b"writable", false); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, options, ); @@ -928,7 +926,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { let promise_value = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, source, boxed_ptr(sink as *const u8), undefined_value(), @@ -945,7 +943,7 @@ fn stream_promises_pipeline_transfers_readable_from_chunks() { fn stream_promises_pipeline_rejects_missing_streams() { let direct_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined_value(), undefined_value(), undefined_value(), @@ -959,7 +957,7 @@ fn stream_promises_pipeline_rejects_missing_streams() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, 123.0, undefined_value(), boxed_ptr(empty_rest as *const u8), @@ -975,7 +973,7 @@ fn stream_promises_pipeline_rejects_missing_streams() { fn stream_promises_pipeline_rejects_invalid_source_body() { let direct_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, 123.0, 456.0, undefined_value(), @@ -989,7 +987,7 @@ fn stream_promises_pipeline_rejects_invalid_source_body() { let empty_rest = crate::array::js_array_alloc(0); let rest_promise = thunk_streamP_pipeline( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, 123.0, 456.0, boxed_ptr(empty_rest as *const u8), @@ -1015,7 +1013,7 @@ fn stream_promises_finished_rejects_when_signal_aborts() { let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, boxed_ptr(opts as *const u8), ); @@ -1044,21 +1042,19 @@ fn stream_promises_finished_with_signal_resolves_for_finished_writable_side_stub ); let stream = crate::node_stream::js_node_stream_passthrough_new(undefined_value()); let end = get_object_property(stream, b"end").expect("stream.end should exist"); - let prev_this = crate::object::js_implicit_this_set(stream); unsafe { - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( end, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(stream), std::ptr::null(), 0, ); } - crate::object::js_implicit_this_set(prev_this); let _ = crate::promise::js_promise_run_microtasks(); let promise_value = thunk_streamP_finished( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, stream, boxed_ptr(opts as *const u8), ); diff --git a/crates/perry-runtime/src/node_submodules/trace_events.rs b/crates/perry-runtime/src/node_submodules/trace_events.rs index 5af7a42a0d..d4626369a4 100644 --- a/crates/perry-runtime/src/node_submodules/trace_events.rs +++ b/crates/perry-runtime/src/node_submodules/trace_events.rs @@ -650,18 +650,14 @@ fn emit_enabled_trace_warning() { let process = scope.root_nanbox_f64(process); let callback = scope.root_nanbox_f64(callback); let warning = scope.root_nanbox_f64(warning); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - process.get_nanbox_f64(), - )); unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( callback.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(process.get_nanbox_f64()), [warning.get_nanbox_f64()].as_ptr(), 1, ); } - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } #[cfg(test)] diff --git a/crates/perry-runtime/src/node_vm/modules.rs b/crates/perry-runtime/src/node_vm/modules.rs index b3225ae8c6..68bde0b489 100644 --- a/crates/perry-runtime/src/node_vm/modules.rs +++ b/crates/perry-runtime/src/node_vm/modules.rs @@ -75,19 +75,14 @@ fn evaluate_synthetic_module(module: *mut ObjectHeader) -> f64 { })); let js = JSValue::from_bits(callback.get_nanbox_f64().to_bits()); if !js.is_undefined() && !js.is_null() { - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set(with_hmut( - &module, - object_value, - ))); let outcome = crate::exception::js_call_catching(|| unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( callback.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(with_hmut(&module, object_value)), std::ptr::null(), 0, ) }); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); if let Err(error) = outcome { with_hmut(&module, |module| set_field(module, FIELD_ERROR, error)); with_hmut(&module, |module| set_status(module, STATUS_ERRORED)); diff --git a/crates/perry-runtime/src/object/arguments.rs b/crates/perry-runtime/src/object/arguments.rs index 8e97856e47..7563128249 100644 --- a/crates/perry-runtime/src/object/arguments.rs +++ b/crates/perry-runtime/src/object/arguments.rs @@ -697,7 +697,7 @@ pub(crate) unsafe fn arguments_object_get_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null(), crate::closure::body_call::current_this()); + arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::UNDEFINED); } if let Some(box_ptr) = mapped_box { let value = crate::r#box::js_box_get(box_ptr); @@ -709,10 +709,11 @@ pub(crate) unsafe fn arguments_object_get_field( let closure = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - let value = crate::closure::js_closure_call0( - closure, - crate::closure::plain_call_receiver(), - ); + // An accessor on the arguments object runs with it as `this`. + let this = crate::closure::JsThis::from_f64(crate::value::js_nanbox_pointer( + obj as i64, + )); + let value = crate::closure::js_closure_call0(closure, this); return Some(JSValue::from_bits(value.to_bits())); } } @@ -743,7 +744,7 @@ pub(crate) unsafe fn arguments_object_set_field( }); if name == "callee" && state.restricted_callee() { - arguments_throw_type_error(std::ptr::null(), crate::closure::body_call::current_this()); + arguments_throw_type_error(std::ptr::null(), crate::closure::JsThis::UNDEFINED); } if !super::own_key_present(obj, key) { return false; @@ -753,11 +754,9 @@ pub(crate) unsafe fn arguments_object_set_field( let closure = (acc.set & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !closure.is_null() { - crate::closure::js_closure_call1( - closure, - crate::closure::plain_call_receiver(), - value, - ); + let this = + crate::closure::JsThis::from_f64(crate::value::js_nanbox_pointer(obj as i64)); + crate::closure::js_closure_call1(closure, this, value); } } return true; 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 99c36e1db1..280496797f 100644 --- a/crates/perry-runtime/src/object/bound_method_receiver_tests.rs +++ b/crates/perry-runtime/src/object/bound_method_receiver_tests.rs @@ -77,17 +77,14 @@ unsafe fn register_class() -> (f64, f64) { (method, proto) } -/// `method.call(receiver)`: IMPLICIT_THIS bound to `receiver` for the call. +/// `method.call(receiver)`: `receiver` passed as the call's `this`. unsafe fn call_with_this(method: f64, receiver: f64) -> f64 { - let prev = crate::object::js_implicit_this_set(receiver); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), std::ptr::null(), 0, - ); - crate::object::js_implicit_this_set(prev); - result + ) } /// `Object.setPrototypeOf(Object.create(null), proto)`. diff --git a/crates/perry-runtime/src/object/class_constructors.rs b/crates/perry-runtime/src/object/class_constructors.rs index af1a650137..2f542a2aca 100644 --- a/crates/perry-runtime/src/object/class_constructors.rs +++ b/crates/perry-runtime/src/object/class_constructors.rs @@ -865,9 +865,6 @@ pub unsafe extern "C" fn js_super_method_call_dynamic( if let Some((func_ptr, param_count, has_rest)) = super::class_registry::lookup_static_method_in_chain(parent_cid, name) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_value)); crate::object::static_this_arm_if_unarmed(this_value); let result = if has_rest { // Mirror `js_class_static_method_call`'s rest bundling: fixed @@ -899,7 +896,6 @@ pub unsafe extern "C" fn js_super_method_call_dynamic( super::class_registry::call_static_method(func_ptr, args_ptr, args_len, param_count) }; crate::object::static_this_disarm(); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } } @@ -931,20 +927,12 @@ pub unsafe extern "C" fn js_super_method_call_dynamic( // walks the parent chain and drops its read lock before returning, so the // invoked body may re-take the registry lock without deadlocking (wall-37). if let Some(method_value) = super::class_registry::lookup_prototype_method(parent_cid, name) { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(super::IMPLICIT_THIS.with(|c| c.replace(this_value.to_bits()))); - let result = crate::closure::js_native_call_value( + return crate::closure::native_call_value_this( method_value, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_value), args_ptr, args_len, ); - super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - return result; } // #6316: the parent chain is real (an intermediate user class) but bottoms // out in a NATIVE base whose surface perry stamps onto the instance — @@ -984,24 +972,16 @@ unsafe fn call_displaced_native_base_method( return crate::object::map_set_subclass::super_collection_method(this_value, name, args) .unwrap_or(undef); }; - // The stashed closure already captures the receiver in slot 0, but bind - // IMPLICIT_THIS too: the shared emitter/stream stubs read their receiver - // through `this_value(closure)`, which falls back to IMPLICIT_THIS when the - // capture is undefined (the prototype-installed form). - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(super::IMPLICIT_THIS.with(|c| c.replace(this_value.to_bits()))); - let result = crate::closure::js_native_call_value( + // The stashed closure already captures the receiver in slot 0, but pass + // it as `this` too: the shared emitter/stream stubs fall back to their + // `this` argument when the capture is undefined (the prototype-installed + // form). + crate::closure::native_call_value_this( method_value, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_value), args_ptr, args_len, - ); - super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - result + ) } /// Keepalive anchor (generated-code-only callee). diff --git a/crates/perry-runtime/src/object/class_gc_roots.rs b/crates/perry-runtime/src/object/class_gc_roots.rs index 221d4df315..495f926b43 100644 --- a/crates/perry-runtime/src/object/class_gc_roots.rs +++ b/crates/perry-runtime/src/object/class_gc_roots.rs @@ -27,8 +27,8 @@ use super::class_registry::{ /// stored address would point at the stale/forwarded location. /// /// This is the same rooting-hazard class the integer-hack design (#1772) was -/// rejected for, and a sibling of the `IMPLICIT_THIS` fix in #1813 -/// (`scan_implicit_this_roots_mut`). The keys are scalar `class_id`s (no GC +/// rejected for, and a sibling of the #1813 fix to the (since removed) +/// implicit-`this` cell. The keys are scalar `class_id`s (no GC /// concern); only the pointer *values* are visited. `CLASS_STATIC_METHODS` /// stores code `func_ptr`s, not heap pointers, so it is deliberately not /// scanned here. diff --git a/crates/perry-runtime/src/object/class_registry/construct.rs b/crates/perry-runtime/src/object/class_registry/construct.rs index f4e6aebcbc..a1aa1de1c1 100644 --- a/crates/perry-runtime/src/object/class_registry/construct.rs +++ b/crates/perry-runtime/src/object/class_registry/construct.rs @@ -7,7 +7,7 @@ crate::perry_thread_local! { /// **This is a GC root, and must stay one (#7231).** It holds a NaN-boxed /// closure/class value for the whole constructor body, and a constructor /// body runs arbitrary user code. `this_binding.rs`'s `NEW_TARGET` holds - /// the same value under `scan_implicit_this_roots_mut`; this is a second + /// the same value under `scan_dispatch_binding_roots_mut`; this is a second /// copy on a different path, and a second copy of a root that is not /// itself a root is exactly the shape #7226 found in `prev_this`. /// @@ -47,8 +47,8 @@ pub(crate) use rooted_arguments::construct_two_rooted; /// through `synthetic_class_id_for_function` so the instance's /// `class_id` matches the bucket prototype methods were registered /// against. Allocates a fresh object stamped with the synthetic id, -/// then invokes the function as the constructor with `IMPLICIT_THIS` -/// bound to the new object so any `this.foo = …` writes in the +/// then invokes the function as the constructor with the new object +/// as `this` so any `this.foo = …` writes in the /// function body land on the instance. Returns the NaN-boxed new /// instance pointer. /// @@ -601,7 +601,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_blob_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, parts, options, ); @@ -622,7 +622,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_file_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, parts, name, options, @@ -654,7 +654,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_headers_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, init, ); } @@ -670,7 +670,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_request_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, input, init, ); @@ -687,7 +687,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( .unwrap_or(f64::from_bits(crate::value::TAG_UNDEFINED)); return crate::object::global_this_response_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, body, init, ); @@ -871,7 +871,7 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( "Storage" => { return crate::web_storage::storage_constructor_illegal( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, ); } "BroadcastChannel" => { @@ -1201,13 +1201,13 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( // here — otherwise `new ()` would dereference an // arbitrary pointer as a `ClosureHeader` and crash. if is_callable_function_value(func_value) { - // Bind `this` to the new instance, dispatch the constructor, - // then restore the previous IMPLICIT_THIS. The dispatch + // Pass the new instance as `this` and dispatch the constructor. + // The dispatch // result is discarded — JS `new` semantics use the receiver, // not the returned value (object returns would override, but // dayjs and siblings rely on the receiver mutation pattern). - // #7280: `nan_boxed` (the implicit `this` this call is building) and - // the three DISPLACED cell values are held across a call that runs a + // #7280: `nan_boxed` (the `this` this call is building) and + // the two DISPLACED new.target values are held across a call that runs a // user constructor body — see the long note in // `construct_registered_class_ref`. Unrooted, the evacuating minor // moves the instance and this arm returns the pre-move address; @@ -1216,24 +1216,20 @@ pub unsafe extern "C-unwind" fn js_new_function_construct( // `PERRY_GC_MOVING_LOOP_POLLS=1 PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1`. let scope = crate::gc::RuntimeHandleScope::new(); let inst_handle = scope.root_nanbox_f64(nan_boxed); - let prev_this = crate::object::js_implicit_this_get(); - let prev_this_handle = scope.root_nanbox_f64(prev_this); let prev_new_target = crate::object::js_new_target_get(); let prev_new_target_handle = scope.root_nanbox_f64(prev_new_target); - crate::object::js_implicit_this_set(nan_boxed); crate::object::js_new_target_set(func_value); let prev_current_new_target = CURRENT_NEW_TARGET.with(|value| value.replace(func_value.to_bits())); let prev_current_new_target_handle = scope.root_nanbox_u64(prev_current_new_target); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( func_value, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(inst_handle.get_nanbox_f64()), args_ptr, args_len, ); CURRENT_NEW_TARGET.with(|value| value.set(prev_current_new_target_handle.get_nanbox_u64())); crate::object::js_new_target_set(prev_new_target_handle.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); if constructor_return_overrides_this(result) { return result; } @@ -1895,29 +1891,25 @@ pub unsafe extern "C" fn js_new_function_construct_with_new_target( } // #7280: same unrooted-receiver shape as the plain-`new` tail above — - // `nan_boxed` and the three displaced cell values cross a user + // `nan_boxed` and the two displaced new.target values cross a user // constructor body. Reproduced by // `Reflect.construct(plainFn, [x], otherFn)`, 200/200 iterations wrong // under `PERRY_GC_MOVING_LOOP_POLLS=1 PERRY_GC_SCHEDULE_SEED=1 PERRY_GC_SCHEDULE_RATE=1`. let scope = crate::gc::RuntimeHandleScope::new(); let inst_handle = scope.root_nanbox_f64(nan_boxed); - let prev_this = crate::object::js_implicit_this_get(); - let prev_this_handle = scope.root_nanbox_f64(prev_this); let prev_new_target = crate::object::js_new_target_get(); let prev_new_target_handle = scope.root_nanbox_f64(prev_new_target); - crate::object::js_implicit_this_set(nan_boxed); crate::object::js_new_target_set(nt); let prev_current_new_target = CURRENT_NEW_TARGET.with(|value| value.replace(nt.to_bits())); let prev_current_new_target_handle = scope.root_nanbox_u64(prev_current_new_target); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( func_value, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(inst_handle.get_nanbox_f64()), args_ptr, args_len, ); CURRENT_NEW_TARGET.with(|value| value.set(prev_current_new_target_handle.get_nanbox_u64())); crate::object::js_new_target_set(prev_new_target_handle.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); if constructor_return_overrides_this(result) { return result; } diff --git a/crates/perry-runtime/src/object/class_registry/construct/rooted_arguments.rs b/crates/perry-runtime/src/object/class_registry/construct/rooted_arguments.rs index 9729cf4ce1..4ad01fda8f 100644 --- a/crates/perry-runtime/src/object/class_registry/construct/rooted_arguments.rs +++ b/crates/perry-runtime/src/object/class_registry/construct/rooted_arguments.rs @@ -3,13 +3,12 @@ use super::*; /// Construct with argument slots the collector can rewrite even while native /// constructor setup allocates before entering the JavaScript body. The trap /// sits below both the buffer owner and shadow frame, so JS throws release -/// storage and restore both new.target cells as well as implicit this. +/// storage and restore both new.target cells. pub(crate) fn construct_rooted_arguments(function: f64, args: &[f64], new_target: f64) -> f64 { use std::cell::UnsafeCell; let scope = crate::gc::RuntimeHandleScope::new(); let function = scope.root_nanbox_f64(function); let new_target = scope.root_nanbox_f64(new_target); - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let previous_target = scope.root_nanbox_f64(crate::object::js_new_target_get()); let previous_current = scope.root_nanbox_f64(js_new_target_value()); let args: Vec> = args.iter().copied().map(UnsafeCell::new).collect(); @@ -35,7 +34,6 @@ pub(crate) fn construct_rooted_arguments(function: f64, args: &[f64], new_target }); CURRENT_NEW_TARGET.with(|cell| cell.set(previous_current.get_nanbox_f64().to_bits())); crate::object::js_new_target_set(previous_target.get_nanbox_f64()); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); drop(frame); drop(args); match result { 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 38cbfc4383..bfc72cc328 100644 --- a/crates/perry-runtime/src/object/class_registry/decl_accessors.rs +++ b/crates/perry-runtime/src/object/class_registry/decl_accessors.rs @@ -175,14 +175,10 @@ pub(crate) unsafe fn class_chain_getter_value( if acc.raw_get != 0 { let scope = crate::gc::RuntimeHandleScope::new(); let this = scope.root_nanbox_f64(this_of()); - // The compiled getter reads `this` from its parameter; nested code it - // calls may read the implicit `this`, so publish the receiver too. - let prev = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(this.get_nanbox_f64())); + // The compiled getter reads `this` from its parameter. let _boundary = crate::object::prototype_chain::UserCodeResolutionBoundary::enter(); let f = crate::closure::body_call::js_method_body_fn!(acc.raw_get as *const u8;); let v = f(this.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return Some((crate::JSValue::from_bits(v.to_bits()), acc.raw_get)); } if acc.get != 0 { @@ -196,8 +192,7 @@ pub(crate) unsafe fn class_chain_getter_value( /// declares an accessor for it: `None` when no accessor answers (the caller /// keeps resolving), `Some(true)` when a setter ran, `Some(false)` when the /// accessor has no setter — the write must not create a data property. -/// `this` is the receiver the setter sees, also published as the implicit -/// `this` for the duration of the call. +/// `this` is the receiver the setter sees. /// /// # Safety /// `this` and `value` are live values. @@ -215,11 +210,8 @@ pub(crate) unsafe fn class_chain_setter_apply( let value_h = scope.root_nanbox_f64(value); let (_holder, acc) = class_proto_accessor(class_id, name)?; if acc.raw_set != 0 { - let prev = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_h.get_nanbox_f64())); let f = crate::closure::body_call::js_method_body_fn!(acc.raw_set as *const u8; value); let _ = f(this_h.get_nanbox_f64(), value_h.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); return Some(true); } if acc.set != 0 { 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 bf75161281..6ae890b0cf 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static.rs @@ -1049,14 +1049,10 @@ pub(crate) unsafe fn class_symbol_getter_value( return Some(f64::from_bits(crate::value::TAG_UNDEFINED)); } let result = if is_static { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::object::static_private_owner_push(receiver); let f = crate::closure::body_call::js_bare_body_fn!(getter as *const u8;); let result = f(); crate::object::static_private_owner_pop(); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); result } else { let f = crate::closure::body_call::js_method_body_fn!(getter as *const u8;); @@ -1097,15 +1093,11 @@ pub(crate) unsafe fn class_symbol_setter_apply( if let Some(&(_, setter)) = map.get(&(cid, sym_key, is_static)) { if setter != 0 { if is_static { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope - .root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::object::static_private_owner_push(receiver); let f = crate::closure::body_call::js_bare_body_fn!(setter as *const u8; a0); let _ = f(value); crate::object::static_private_owner_pop(); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } else { let f = crate::closure::body_call::js_method_body_fn!(setter as *const u8; a0); @@ -1403,7 +1395,7 @@ pub(crate) fn class_method_bind_length(class_id: u32, name: &str) -> Option } /// Call a static method func_ptr with `args` (no `this` prepend — static -/// methods read `this` from the implicit-this slot, set by the caller). +/// methods resolve `this` through `js_static_this_resolve`). /// Mirrors the arity dispatch of `call_vtable_method` minus the receiver arg. pub(crate) unsafe fn call_static_method( func_ptr: usize, @@ -1576,8 +1568,8 @@ pub(crate) unsafe fn nm_static_buffer_proto_chain( /// #1788: dispatch a static method on a class value (`Sub.greet()` where /// `Sub extends make(...)`, or a class-object value) by walking the class_id /// parent chain in `CLASS_STATIC_METHODS`. Binds `this` to the receiver (so -/// `this.` resolves through the subclass's static-field chain), calls -/// the method, and restores the previous implicit-this. On miss returns the +/// `this.` resolves through the subclass's static-field chain) and calls +/// the method. On miss returns the /// receiver unchanged — preserving the prior "yield the class ref for a /// chained call during module init" behavior for genuinely-absent methods. #[no_mangle] @@ -1622,8 +1614,6 @@ pub unsafe extern "C" fn js_class_static_method_call( return receiver; } if let Some((func_ptr, param_count, has_rest)) = lookup_static_method_in_chain(class_id, name) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::object::static_private_owner_push(receiver); // Receiver-sensitive static `this`: arm the one-shot override so the // method prologue (`js_static_this_resolve`) sees the DYNAMIC receiver @@ -1661,7 +1651,6 @@ pub unsafe extern "C" fn js_class_static_method_call( }; crate::object::static_this_disarm(); crate::object::static_private_owner_pop(); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } // #10893: not a static METHOD — a static ACCESSOR on the class-id chain @@ -1727,16 +1716,12 @@ pub unsafe extern "C" fn js_class_static_method_call( let member = f64::from_bits(member.bits()); let mv = crate::value::JSValue::from_bits(member.to_bits()); if !mv.is_undefined() && !mv.is_null() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( member, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args_ptr, args_len, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } } @@ -1750,27 +1735,21 @@ pub unsafe extern "C" fn js_class_static_method_call( // `class X extends Promise` — inherited builtin static (`X.all(...)`, // `X.resolve(...)`, …). Dispatch the spec static with `this` = the subclass // receiver so `NewPromiseCapability(X)` constructs the subclass. Resolves the - // reified static value and calls it (its thunk reads `this` from the - // implicit-this slot, already bound to `receiver` by the caller above). + // reified static value and calls it with `receiver` as its `this`. if super::promise_parent_in_chain(class_id) && crate::object::promise_static_function_spec(name).is_some() { let static_val = crate::object::js_promise_static_function_value(name.as_ptr(), name.len()); if static_val.to_bits() != crate::value::TAG_UNDEFINED { - // The reified static thunk reads its `this` constructor from the - // implicit-this slot, so bind it to the subclass receiver for the - // duration of the call — `NewPromiseCapability(receiver)` then - // constructs the subclass. - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + // The reified static thunk reads its `this` constructor from its + // `this` argument, so pass the subclass receiver — + // `NewPromiseCapability(receiver)` then constructs the subclass. + let result = crate::closure::native_call_value_this( static_val, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args_ptr, args_len, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } } @@ -1845,16 +1824,12 @@ pub unsafe extern "C" fn js_class_static_method_call( // not a real inherited member. && member.to_bits() != closure_val.to_bits() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( member, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args_ptr, args_len, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } } @@ -1868,16 +1843,12 @@ pub unsafe extern "C" fn js_class_static_method_call( }; if let Some(member) = fn_proto_member { if crate::collection_iter::is_callable(member) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( member, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args_ptr, args_len, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return result; } } diff --git a/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs b/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs index d83c907494..c9047ffa30 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static/private_and_dynamic.rs @@ -301,9 +301,6 @@ pub(crate) unsafe fn call_private_static_method_for_owner( let scope = crate::gc::RuntimeHandleScope::new(); let this_value = scope.root_nanbox_f64(this_value); let private_brand = scope.root_nanbox_f64(private_brand); - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_value.get_nanbox_f64(), - )); crate::object::static_private_owner_push(private_brand.get_nanbox_f64()); crate::object::private_lexical_brand_push(private_brand.get_nanbox_f64()); crate::object::static_this_arm_if_unarmed(this_value.get_nanbox_f64()); @@ -311,6 +308,5 @@ pub(crate) unsafe fn call_private_static_method_for_owner( crate::object::static_this_disarm(); crate::object::private_lexical_brand_pop(); crate::object::static_private_owner_pop(); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); Some(result) } diff --git a/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs b/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs index 2bc75c26a8..e76f7edaa3 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static/static_accessor_call.rs @@ -98,13 +98,10 @@ pub(crate) unsafe fn try_static_accessor_value_call( } let callee_handle = scope.root_nanbox_f64(callee); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - let receiver = receiver_handle.get_nanbox_f64(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( - callee_handle.get_nanbox_f64(), crate::closure::plain_call_receiver(), + Some(crate::closure::native_call_value_this( + callee_handle.get_nanbox_f64(), + crate::closure::JsThis::from_f64(receiver_handle.get_nanbox_f64()), args.as_ptr(), args.len(), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - Some(result) + )) } diff --git a/crates/perry-runtime/src/object/class_registry/prototype_objects.rs b/crates/perry-runtime/src/object/class_registry/prototype_objects.rs index 47a93f9ae0..d20d211d0b 100644 --- a/crates/perry-runtime/src/object/class_registry/prototype_objects.rs +++ b/crates/perry-runtime/src/object/class_registry/prototype_objects.rs @@ -109,7 +109,7 @@ pub(crate) fn ensure_function_prototype_object( // synthetic prototype must carry the EventEmitter methods (`emit`/`on`/`once`/ // …) so the `Object.setPrototypeOf(x, EventEmitter.prototype)` mixin pattern // (pino's logger prototype) gives `x` a working `emit`/`on`. The installed - // closures read IMPLICIT_THIS, so a plain object that merely inherits this + // closures read their `this` argument, so a plain object that merely inherits this // prototype dispatches against ITSELF (listener state is keyed by the receiver // object, not a captured instance). Mirrors what `Stream.prototype` already // does. This proto is cached (`class_prototype_object_root_store` above), so @@ -565,11 +565,11 @@ unsafe fn inherited_proto_accessor_value( if acc.get == 0 { return Some(JSValue::undefined()); } - // Route through `invoke_accessor_getter` rather than a bare - // `js_implicit_this_set` + `js_closure_call0`. A getter installed via + // Route through `invoke_accessor_getter` rather than a bare closure call + // with the receiver as `this`. A getter installed via // `Object.defineProperty(Class.prototype, name, { get })` is an ORDINARY // method closure whose body reads `this` from its captured receiver slot — - // not from IMPLICIT_THIS — so merely setting IMPLICIT_THIS left the getter + // not from its `this` argument — so merely passing `this` left the getter // observing the prototype it lives on instead of the instance (winston's // `get transports()` saw the prototype, whose `this._readableState` is // undefined → "Cannot convert undefined or null to object"). @@ -851,8 +851,6 @@ unsafe fn resolve_proto_chain_field_inner( } } let field_val = if let Some(receiver) = receiver { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let previous_this = this_scope.root_nanbox_f64(js_implicit_this_set(receiver)); // The recursive `get_field(proto_obj, key)` re-derives a class // getter's `this` from `proto_obj`; stash the real instance so an // inherited getter (object-literal `get x()` on an @@ -861,7 +859,6 @@ unsafe fn resolve_proto_chain_field_inner( super::super::field_get_set::accessor_receiver_override_begin(receiver); let value = js_object_get_field_by_name(proto_obj as *const _, key); super::super::field_get_set::accessor_receiver_override_end(prev_override); - js_implicit_this_set(previous_this.get_nanbox_f64()); value } else { js_object_get_field_by_name(proto_obj as *const _, key) diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index 070271317b..60bebc629b 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -204,7 +204,7 @@ pub(crate) fn class_own_static_accessor_ptrs(class_id: u32, name: &str) -> Optio } /// Trampoline giving a raw vtable getter func_ptr (`fn(this) -> f64`) the -/// closure calling convention. The receiver comes from `IMPLICIT_THIS`, set +/// closure calling convention. The receiver is the `this` argument passed /// by the method-call dispatch the closure value travels through. extern "C" fn class_accessor_getter_thunk( closure: *const crate::closure::ClosureHeader, diff --git a/crates/perry-runtime/src/object/class_registry/state.rs b/crates/perry-runtime/src/object/class_registry/state.rs index c9a7a07b54..e1002680d3 100644 --- a/crates/perry-runtime/src/object/class_registry/state.rs +++ b/crates/perry-runtime/src/object/class_registry/state.rs @@ -401,7 +401,7 @@ pub static CLASS_VTABLE_REGISTRY: ImageTable< /// #1788: per-class STATIC-method registry: class_id -> { name -> (func_ptr, /// param_count, has_rest) }. Static methods are emitted as `perry_static_*` -/// (no `this` param — they read `this` from the implicit-this slot) and are +/// (no `this` param — they resolve `this` via `js_static_this_resolve`) and are /// NOT in the instance vtable above, so a subclass whose parent is a /// class-expression value (`class Sub extends make(...) {}`) can't resolve an /// inherited static method (`Sub.greet()`) at compile time. This table is diff --git a/crates/perry-runtime/src/object/collection_proto_thunks.rs b/crates/perry-runtime/src/object/collection_proto_thunks.rs index b57bbf6ee3..84ed851edc 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks.rs @@ -7,7 +7,7 @@ //! which previously resolved to `global_this_builtin_noop_thunk` and silently //! did nothing. Per spec these methods must perform a `this` brand check and //! throw a `TypeError` when called on an incompatible receiver. The thunks -//! below read the `IMPLICIT_THIS` receiver (set by the `.call`/`.apply` +//! below take the receiver as `this` (set by the `.call`/`.apply` //! dispatch), brand-check it, throw on mismatch, and otherwise dispatch to the //! real runtime helper — so reflective collection calls now also *work*. //! @@ -444,7 +444,7 @@ fn map_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> *mut cra } /// Read the original receiver after its brand has been checked. Some native -/// callers publish a raw pointer in IMPLICIT_THIS; normalize it before rooting +/// callers pass a raw pointer as `this`; normalize it before rooting /// or exposing it to JavaScript. The backing collection is not the receiver. fn collection_this_value(this: crate::closure::JsThis) -> f64 { let bits = this.bits(); diff --git a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs index 9fa7ae0825..68b288b4b1 100644 --- a/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs +++ b/crates/perry-runtime/src/object/collection_proto_thunks/subclass_tests.rs @@ -16,13 +16,10 @@ fn reflective_map_mutators_keep_subclass_identity() { let _lock = crate::gc::global_side_table_test_lock(); let scope = crate::gc::RuntimeHandleScope::new(); let receiver = subclass(&scope, 0); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); assert_eq!( map_proto_set_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0, 10.0 ) @@ -32,19 +29,22 @@ fn reflective_map_mutators_keep_subclass_identity() { assert_eq!( map_proto_get_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ), 10.0 ); assert_eq!( - map_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), + map_proto_size_getter_thunk( + std::ptr::null(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()) + ), 1.0 ); assert_eq!( map_proto_delete_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), @@ -53,7 +53,7 @@ fn reflective_map_mutators_keep_subclass_identity() { assert_eq!( map_proto_has_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), @@ -61,20 +61,22 @@ fn reflective_map_mutators_keep_subclass_identity() { ); map_proto_set_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 2.0, 20.0, ); map_proto_clear_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 0.0, ); assert_eq!( - map_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), + map_proto_size_getter_thunk( + std::ptr::null(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()) + ), 0.0 ); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } #[test] @@ -82,13 +84,10 @@ fn reflective_set_mutators_keep_subclass_identity() { let _lock = crate::gc::global_side_table_test_lock(); let scope = crate::gc::RuntimeHandleScope::new(); let receiver = subclass(&scope, 1); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); assert_eq!( set_proto_add_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), @@ -97,20 +96,23 @@ fn reflective_set_mutators_keep_subclass_identity() { assert_eq!( set_proto_has_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), crate::value::TAG_TRUE ); assert_eq!( - set_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), + set_proto_size_getter_thunk( + std::ptr::null(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()) + ), 1.0 ); assert_eq!( set_proto_delete_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), @@ -119,7 +121,7 @@ fn reflective_set_mutators_keep_subclass_identity() { assert_eq!( set_proto_has_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 1.0 ) .to_bits(), @@ -127,19 +129,21 @@ fn reflective_set_mutators_keep_subclass_identity() { ); set_proto_add_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 2.0, ); set_proto_clear_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), 0.0, ); assert_eq!( - set_proto_size_getter_thunk(std::ptr::null(), crate::closure::body_call::current_this()), + set_proto_size_getter_thunk( + std::ptr::null(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()) + ), 0.0 ); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); } #[test] diff --git a/crates/perry-runtime/src/object/dataview_proto_thunks.rs b/crates/perry-runtime/src/object/dataview_proto_thunks.rs index 8ce6bbaf0b..797faa9949 100644 --- a/crates/perry-runtime/src/object/dataview_proto_thunks.rs +++ b/crates/perry-runtime/src/object/dataview_proto_thunks.rs @@ -13,8 +13,8 @@ //! DataView *instances* already worked: `dv.getInt32(0)`, `dv.byteLength`, etc. //! are routed through codegen / `buffer_dispatch::dispatch_buffer_method` on a //! `BufferHeader` marked as a DataView. The thunks here only add the -//! *reflectable* own properties on the prototype; they read the receiver from -//! `IMPLICIT_THIS` (set by the `.call`/`.apply` dispatch), brand-check that it +//! *reflectable* own properties on the prototype; they take the receiver as +//! their `this` argument (supplied by the `.call`/`.apply` dispatch), brand-check that it //! is a DataView (throwing `TypeError` otherwise, per spec — covering test262's //! `this-has-no-*` / `this-is-not-object` cases), then dispatch to the SAME //! runtime helpers the instance path uses. @@ -24,12 +24,12 @@ use super::*; -/// Resolve the `IMPLICIT_THIS` receiver to a DataView `BufferHeader` address, +/// Resolve the `this` receiver to a DataView `BufferHeader` address, /// or `None` if the receiver is not a DataView. Mirrors `typed_array_receiver` /// / `array_buffer_receiver_addr` in `global_this.rs` (NaN-boxed pointer or /// raw-i64 form), then brand-checks `is_data_view`. -fn dataview_receiver_addr() -> Option { - let this_bits = IMPLICIT_THIS.with(|c| c.get()); +fn dataview_receiver_addr(this: crate::closure::JsThis) -> Option { + let this_bits = this.bits(); let this_jsv = crate::value::JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -47,8 +47,8 @@ fn dataview_receiver_addr() -> Option { /// Brand-check helper: returns the DataView address or throws a `TypeError` /// for an incompatible receiver. Mirrors `typed_array_brand_error`. -fn require_dataview_receiver() -> usize { - match dataview_receiver_addr() { +fn require_dataview_receiver(this: crate::closure::JsThis) -> usize { + match dataview_receiver_addr(this) { Some(addr) => addr, None => super::object_ops::throw_object_type_error( b"Method DataView.prototype called on incompatible receiver", @@ -67,9 +67,9 @@ fn undef() -> f64 { extern "C" fn dataview_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let addr = require_dataview_receiver(); + let addr = require_dataview_receiver(this); let buf = addr as *const crate::buffer::BufferHeader; f64::from_bits( crate::value::JSValue::number(crate::buffer::js_buffer_length(buf) as f64).bits(), @@ -78,25 +78,25 @@ extern "C" fn dataview_byte_length_getter_thunk( extern "C" fn dataview_byte_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let addr = require_dataview_receiver(); + let addr = require_dataview_receiver(this); let offset = crate::buffer::buffer_byte_offset(addr); f64::from_bits(crate::value::JSValue::number(offset as f64).bits()) } extern "C" fn dataview_buffer_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let addr = require_dataview_receiver(); + let addr = require_dataview_receiver(this); let backing = crate::buffer::buffer_backing_array_buffer(addr); f64::from_bits(crate::value::js_nanbox_pointer(backing as i64).to_bits()) } // --------------------------------------------------------------------------- // get* methods (spec length 1): getInt8 … getFloat64. Signature -// `(closure, byteOffset, rest)` where `rest` bundles the optional +// `(closure, this, byteOffset, rest)` where `rest` bundles the optional // `littleEndian` flag (big-endian default). // --------------------------------------------------------------------------- @@ -112,8 +112,8 @@ fn rest_first_arg(rest: f64) -> f64 { crate::array::js_array_get_f64(arr, 0) } -fn dataview_get(suffix: &str, offset: f64, rest: f64) -> f64 { - let addr = require_dataview_receiver(); +fn dataview_get(this: crate::closure::JsThis, suffix: &str, offset: f64, rest: f64) -> f64 { + let addr = require_dataview_receiver(this); let kind = match crate::buffer::DataViewKind::from_method_suffix(suffix) { Some(k) => k, None => return undef(), @@ -123,8 +123,14 @@ fn dataview_get(suffix: &str, offset: f64, rest: f64) -> f64 { crate::buffer::js_data_view_get(buf_f64, offset, kind, little) } -fn dataview_set(suffix: &str, offset: f64, value: f64, rest: f64) -> f64 { - let addr = require_dataview_receiver(); +fn dataview_set( + this: crate::closure::JsThis, + suffix: &str, + offset: f64, + value: f64, + rest: f64, +) -> f64 { + let addr = require_dataview_receiver(this); let kind = match crate::buffer::DataViewKind::from_method_suffix(suffix) { Some(k) => k, None => return undef(), @@ -138,11 +144,11 @@ macro_rules! dataview_get_thunk { ($name:ident, $suffix:literal) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, offset: f64, rest: f64, ) -> f64 { - dataview_get($suffix, offset, rest) + dataview_get(this, $suffix, offset, rest) } }; } @@ -151,12 +157,12 @@ macro_rules! dataview_set_thunk { ($name:ident, $suffix:literal) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, offset: f64, value: f64, rest: f64, ) -> f64 { - dataview_set($suffix, offset, value, rest) + dataview_set(this, $suffix, offset, value, rest) } }; } diff --git a/crates/perry-runtime/src/object/date_proto_thunks.rs b/crates/perry-runtime/src/object/date_proto_thunks.rs index d334d65c77..f746764445 100644 --- a/crates/perry-runtime/src/object/date_proto_thunks.rs +++ b/crates/perry-runtime/src/object/date_proto_thunks.rs @@ -10,7 +10,7 @@ //! //! Per spec each `Date.prototype` getter performs `thisTimeValue(this)`, which //! throws a `TypeError` when `this` is not an Object with a `[[DateValue]]` -//! slot. These thunks read the `IMPLICIT_THIS` receiver (set by the +//! slot. These thunks take the receiver as their `this` argument (set by the //! `.call`/`.apply` dispatch), brand-check it via `is_date_value`, throw on //! mismatch, and otherwise dispatch to the SAME `js_date_get_*` helper the //! instance path uses — so reflective Date getter calls now also *work*. @@ -22,10 +22,10 @@ use super::*; -/// Resolve the `IMPLICIT_THIS` receiver to a Date time value, or throw a +/// Resolve the `this` receiver to a Date time value, or throw a /// `TypeError` (`thisTimeValue` brand check) when it is not a Date. -fn require_date_timestamp() -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn require_date_timestamp(this: crate::closure::JsThis) -> f64 { + let this = this.as_f64(); if crate::date::is_date_value(this) { crate::date::date_cell_timestamp(this) } else { @@ -37,9 +37,9 @@ macro_rules! date_getter_thunk { ($name:ident, $rt:path) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - $rt(require_date_timestamp()) + $rt(require_date_timestamp(this)) } }; } @@ -108,9 +108,9 @@ extern "C" fn date_to_utc_string( #[cfg(feature = "temporal")] extern "C" fn date_to_temporal_instant( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let timestamp = require_date_timestamp(); + let timestamp = require_date_timestamp(this); crate::temporal::instant::from_epoch_milliseconds_static(&[timestamp]) } @@ -139,7 +139,7 @@ extern "C" fn date_to_json( /// Apply the `Date.prototype.toJSON` algorithm to an explicit receiver. /// -/// The closure thunk above uses the implicit-this slot for normal JavaScript +/// The closure thunk above takes its receiver as the `this` argument for normal JavaScript /// calls. JSON.stringify already has the Date value in hand, so it calls this /// shared implementation directly instead of bypassing the observable /// `Invoke(O, "toISOString")` step with `date::js_date_to_json`. @@ -218,21 +218,19 @@ pub(crate) fn date_to_json_value(this: f64) -> f64 { { // `Call(func, O, «»)` — toJSON's `key` argument is intentionally not // forwarded (Invoke passes an empty argument list). - let prev = invoke_scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); let closure = crate::value::js_nanbox_get_pointer(func.get_nanbox_f64()) as *const crate::closure::ClosureHeader; - let r = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return r; + return crate::closure::js_closure_call0( + closure, + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), + ); } super::object_ops::throw_object_type_error(b"toISOString is not a function") } #[cfg(test)] -pub(crate) fn test_date_to_json_current_this() -> f64 { - date_to_json(std::ptr::null(), crate::closure::body_call::current_this()) +pub(crate) fn test_date_to_json_with_this(this: f64) -> f64 { + date_to_json(std::ptr::null(), crate::closure::JsThis::from_f64(this)) } /// True iff `value` is an ECMAScript Object (`Type(O) is Object`). Objects are @@ -463,9 +461,9 @@ pub(crate) fn install_date_constructor_statics(ctor: *mut crate::closure::Closur /// Legacy `Date.prototype.getYear` — `getFullYear() - 1900` (NaN-preserving). extern "C" fn date_get_year( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let fy = crate::date::js_date_get_full_year(require_date_timestamp()); + let fy = crate::date::js_date_get_full_year(require_date_timestamp(this)); if fy.is_nan() { fy } else { @@ -544,18 +542,18 @@ pub(crate) fn install_date_proto_getters(proto_obj: *mut ObjectHeader) { // `setDate.call(nonDate, 1)` silently produced garbage instead of throwing. // // Each setter performs `thisTimeValue(this)` (TypeError if `this` is not a -// Date) and is variadic, so these thunks read the `IMPLICIT_THIS` receiver, +// Date) and is variadic, so these thunks take the `this` receiver, // brand-check it, collect the `rest` arguments, and dispatch to the same // `js_date_apply_setter` the instance path uses. `js_date_apply_setter` reads // `[[DateValue]]` BEFORE coercing the arguments, so the read-before-ToNumber // ordering holds on the reflective path too. -/// Resolve the `IMPLICIT_THIS` receiver to a Date value, or throw a `TypeError` +/// Resolve the `this` receiver to a Date value, or throw a `TypeError` /// (`thisTimeValue` brand check) when it is not a Date. Returns the NaN-boxed /// `DateCell` value (the setter dispatch needs the receiver itself, not just /// its timestamp, so it can mutate the cell in place). -fn require_date_this() -> f64 { - let this = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn require_date_this(this: crate::closure::JsThis) -> f64 { + let this = this.as_f64(); if crate::date::is_date_value(this) { this } else { @@ -571,10 +569,10 @@ macro_rules! date_setter_thunk { ($name:ident, $is_utc:expr, $field:expr) => { extern "C" fn $name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = require_date_this(); + let this = require_date_this(this); let args = super::global_this::global_this_rest_array_values(rest); crate::date::js_date_apply_setter( this, @@ -625,15 +623,23 @@ date_setter_thunk!(date_set_utc_milliseconds, 1, 6); /// fallback simply defers to the non-locale formatter instead of statically /// pinning the Intl formatting web from the always-installed Date prototype. #[cfg(not(feature = "intl-namespace"))] -fn date_to_locale_opts_impl(_rest: f64, _ctx: crate::intl::TemporalLocaleCtx) -> f64 { - let this = require_date_this(); +fn date_to_locale_opts_impl( + this: crate::closure::JsThis, + _rest: f64, + _ctx: crate::intl::TemporalLocaleCtx, +) -> f64 { + let this = require_date_this(this); let s = crate::date::js_date_to_locale_string(this); crate::value::js_nanbox_string(s as i64) } #[cfg(feature = "intl-namespace")] -fn date_to_locale_opts_impl(rest: f64, ctx: crate::intl::TemporalLocaleCtx) -> f64 { - let this = require_date_this(); +fn date_to_locale_opts_impl( + this: crate::closure::JsThis, + rest: f64, + ctx: crate::intl::TemporalLocaleCtx, +) -> f64 { + let this = require_date_this(this); let epoch_ms = crate::date::date_cell_timestamp(this); if epoch_ms.is_nan() { // Invalid Date → "Invalid Date" for all three methods. @@ -653,26 +659,26 @@ fn date_to_locale_opts_impl(rest: f64, ctx: crate::intl::TemporalLocaleCtx) -> f extern "C" fn date_to_locale_string_opts( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainDateTime) + date_to_locale_opts_impl(this, rest, crate::intl::TemporalLocaleCtx::PlainDateTime) } extern "C" fn date_to_locale_date_string_opts( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainDate) + date_to_locale_opts_impl(this, rest, crate::intl::TemporalLocaleCtx::PlainDate) } extern "C" fn date_to_locale_time_string_opts( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - date_to_locale_opts_impl(rest, crate::intl::TemporalLocaleCtx::PlainTime) + date_to_locale_opts_impl(this, rest, crate::intl::TemporalLocaleCtx::PlainTime) } /// Install the brand-checked `Date.prototype` setter thunks. Called from diff --git a/crates/perry-runtime/src/object/descriptor_state.rs b/crates/perry-runtime/src/object/descriptor_state.rs index adb1d26fbb..b58124d957 100644 --- a/crates/perry-runtime/src/object/descriptor_state.rs +++ b/crates/perry-runtime/src/object/descriptor_state.rs @@ -1493,11 +1493,10 @@ pub(crate) unsafe fn json_object_getter_value( return Some(f64::from_bits(TAG_UNDEFINED)); } let receiver = crate::value::js_nanbox_pointer(obj as i64); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); - js_implicit_this_set(prev.get_nanbox_f64()); - Some(result) + Some(crate::closure::js_closure_call0( + closure, + crate::closure::JsThis::from_f64(receiver), + )) } /// Monotonic (#6386): has an accessor descriptor keyed `"constructor"` ever diff --git a/crates/perry-runtime/src/object/disposable_proto_thunks.rs b/crates/perry-runtime/src/object/disposable_proto_thunks.rs index 2c6f7f6005..91b533c2c9 100644 --- a/crates/perry-runtime/src/object/disposable_proto_thunks.rs +++ b/crates/perry-runtime/src/object/disposable_proto_thunks.rs @@ -5,7 +5,7 @@ //! runtime helpers and never touch these thunks. The thunks exist for the //! *reflective* path — `DisposableStack.prototype.use`, method extraction //! (`const use = stack.use`), `.call`/`.apply`, and Test262's `verifyProperty` -//! descriptor checks. Each reads the `IMPLICIT_THIS` receiver, brand-checks it +//! descriptor checks. Each takes the receiver as `this`, brand-checks it //! against the stack class id, throws a `TypeError` on an incompatible //! receiver, and otherwise dispatches to the shared runtime helper. //! @@ -29,7 +29,7 @@ fn throw_incompatible(proto: &str, method: &str) -> ! { )) } -/// Resolve `IMPLICIT_THIS` to a stack `ObjectHeader` of the expected class id, +/// Resolve the `this` receiver to a stack `ObjectHeader` of the expected class id, /// or throw a `TypeError`. fn stack_receiver_or_throw( this: crate::closure::JsThis, diff --git a/crates/perry-runtime/src/object/field_get_set/accessors.rs b/crates/perry-runtime/src/object/field_get_set/accessors.rs index 57672e3906..e3fec79a26 100644 --- a/crates/perry-runtime/src/object/field_get_set/accessors.rs +++ b/crates/perry-runtime/src/object/field_get_set/accessors.rs @@ -285,18 +285,14 @@ unsafe fn prototype_property_value_with_guard( return None; } let receiver = crate::value::js_nanbox_pointer(receiver_addr() as i64); - let previous_this = super::super::js_implicit_this_set(receiver); - // The DISPLACED `this` and the displaced accessor receiver both ride - // through `js_object_get_field_by_name` — which can run a getter — before - // being republished. Rooting the ACCESSOR_RECEIVER_OVERRIDE cell (#7231) - // protects the armed value, not these saved ones; that residual is what - // these two handles close. - let previous_this_h = scope.root_nanbox_f64(previous_this); + // The displaced accessor receiver rides through `js_object_get_field_by_name` + // — which can run a getter — before being republished. Rooting the + // ACCESSOR_RECEIVER_OVERRIDE cell (#7231) protects the armed value, not the + // saved one; that residual is what this handle closes. let prev_override = accessor_receiver_override_begin(receiver); let prev_override_h = prev_override.map(|v| scope.root_nanbox_f64(v)); let property = js_object_get_field_by_name(proto_ptr(), key()); accessor_receiver_override_end(prev_override_h.map(|h| h.get_nanbox_f64())); - super::super::js_implicit_this_set(previous_this_h.get_nanbox_f64()); if property.is_undefined() { None } else { @@ -486,14 +482,12 @@ pub(crate) unsafe fn invoke_accessor_getter(get_bits: u64, receiver: f64) -> JSV // // #9417: every value below is GC-managed and lives across an allocation. // `coerce_call_this` boxes a primitive receiver, `clone_closure_rebind_this` - // allocates a fresh `ClosureHeader`, and `js_closure_call0` runs USER CODE — + // allocates a fresh `ClosureHeader`, and the getter call runs USER CODE — // any of the three can drive an evacuating young-gen minor. A bare Rust // local is exactly the slot the collector cannot see or rewrite (#7249, // #7498), and the receiver here is not merely read afterwards: it is - // PUBLISHED into the GC-rooted `IMPLICIT_THIS` cell, and `prev` is written - // back into that same cell after the getter body has had a full turn to - // allocate. Either store installs a pre-collection address as some frame's - // `this`, after which every ordinary property read off it answers + // passed to the getter as its `this`. A stale copy would install a + // pre-collection address as the getter's `this`, after which every ordinary property read off it answers // `undefined` (`js_object_get_own_field_or_undef` fails its // `obj_type == GC_TYPE_OBJECT` check on the retired cell and returns // TAG_UNDEFINED rather than faulting) — the silent-wrong-answer shape @@ -515,14 +509,15 @@ pub(crate) unsafe fn invoke_accessor_getter(get_bits: u64, receiver: f64) -> JSV if (call_h.get_nanbox_u64() & crate::value::POINTER_MASK) == 0 { return JSValue::undefined(); } - let prev_h = scope.root_nanbox_f64(super::super::js_implicit_this_set(recv_h.get_nanbox_f64())); let closure = (call_h.get_nanbox_u64() & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; let result_f64 = { let _boundary = crate::object::prototype_chain::UserCodeResolutionBoundary::enter(); - crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()) + crate::closure::js_closure_call0( + closure, + crate::closure::JsThis::from_f64(recv_h.get_nanbox_f64()), + ) }; - super::super::js_implicit_this_set(prev_h.get_nanbox_f64()); JSValue::from_bits(result_f64.to_bits()) } @@ -552,15 +547,13 @@ pub(crate) unsafe fn invoke_accessor_setter(set_bits: u64, receiver: f64, value: if (call_h.get_nanbox_u64() & crate::value::POINTER_MASK) == 0 { return; } - let prev_h = scope.root_nanbox_f64(super::super::js_implicit_this_set(recv_h.get_nanbox_f64())); let closure = (call_h.get_nanbox_u64() & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; let _ = crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(recv_h.get_nanbox_f64()), value_h.get_nanbox_f64(), ); - super::super::js_implicit_this_set(prev_h.get_nanbox_f64()); } /// Invoke an accessor owned by a descriptor-marked object before its empty diff --git a/crates/perry-runtime/src/object/field_get_set/class_object_props.rs b/crates/perry-runtime/src/object/field_get_set/class_object_props.rs index 9cf0a88e46..2cb6d84ed2 100644 --- a/crates/perry-runtime/src/object/field_get_set/class_object_props.rs +++ b/crates/perry-runtime/src/object/field_get_set/class_object_props.rs @@ -363,7 +363,7 @@ pub(super) unsafe fn instance_constructor_value( // The prototype's OWN `constructor` data field answers the common // shapes directly — `Object.prototype`, a declared `C.prototype`, a // materialized `F.prototype`, a `{ constructor: F }` literal — so take - // it without the general chain walk, whose implicit-`this` juggling, + // it without the general chain walk, whose receiver juggling, // accessor-receiver override and registry probes cost ~3000 // instructions per read. Skipped when an accessor owns the key, which // must run through the walk to fire with the right receiver. diff --git a/crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs b/crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs index 8609d202ae..f0f8977563 100644 --- a/crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs +++ b/crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs @@ -566,8 +566,8 @@ pub(crate) fn get_field_by_name_past_inherited_cache( // which reads the property first) must resolve the reified Promise prototype // method. The generic prototype walk does not surface these builtin // `Promise.prototype` methods for a subclass instance, so hook them here when - // no own key shadows them. The method thunks unwrap the backing cell from the - // implicit-this receiver (see `promise_prototype_receiver`). + // no own key shadows them. The method thunks unwrap the backing cell from + // their `this` receiver (see `promise_prototype_receiver`). if !key.is_null() && ((obj as u64) >> 48) == 0 && crate::value::addr_class::is_above_handle_band(obj as usize) diff --git a/crates/perry-runtime/src/object/field_set_by_name/tail.rs b/crates/perry-runtime/src/object/field_set_by_name/tail.rs index de898a23fc..07fecb493b 100644 --- a/crates/perry-runtime/src/object/field_set_by_name/tail.rs +++ b/crates/perry-runtime/src/object/field_set_by_name/tail.rs @@ -125,15 +125,11 @@ pub(crate) fn set_field_by_name_object_tail( as *const crate::closure::ClosureHeader; if !closure.is_null() { let receiver = crate::value::js_nanbox_pointer(obj as i64); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let previous_this = - this_scope.root_nanbox_f64(super::js_implicit_this_set(receiver)); crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), value, ); - super::js_implicit_this_set(previous_this.get_nanbox_f64()); } } else { crate::error::throw_immutable_write(0, name); @@ -771,15 +767,11 @@ pub(crate) fn set_field_by_name_object_tail( as *const crate::closure::ClosureHeader; if !closure.is_null() { let receiver = crate::value::js_nanbox_pointer(obj as i64); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let previous_this = - this_scope.root_nanbox_f64(super::js_implicit_this_set(receiver)); crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), value, ); - super::js_implicit_this_set(previous_this.get_nanbox_f64()); } } else { crate::error::throw_immutable_write(0, k); diff --git a/crates/perry-runtime/src/object/global_this/array_error.rs b/crates/perry-runtime/src/object/global_this/array_error.rs index a5ba24ead0..d91927fd30 100644 --- a/crates/perry-runtime/src/object/global_this/array_error.rs +++ b/crates/perry-runtime/src/object/global_this/array_error.rs @@ -38,28 +38,20 @@ pub(crate) extern "C" fn function_prototype_call_thunk( }; let this_arg = crate::closure::coerce_call_this(target, this_arg); // Concise/object-literal methods read `this` from a baked capture slot, not - // IMPLICIT_THIS; rebind so the explicit `.call(thisArg)` receiver is honored. + // the `this` argument; rebind so the explicit `.call(thisArg)` receiver is honored. let target = crate::closure::rebind_explicit_this(target, this_arg); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = - prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( target, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg), args_ptr, args_len, ) - }; - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - result + } } /// `Function.prototype.bind` as a real callable thunk. Reads the target -/// function from `IMPLICIT_THIS` (set by `.call`/`.apply`/`Reflect.apply`), +/// function from its `this` argument (set by `.call`/`.apply`/`Reflect.apply`), /// flattens `(thisArg, ...boundArgs)` into one argument list, and delegates to /// `js_function_bind` (which builds the BOUND_FUNCTION closure). /// @@ -184,7 +176,7 @@ pub(crate) extern "C" fn global_this_queue_microtask_thunk( /// Thunk for `Object.prototype.toString` exposed as a callable closure /// value. Mirrors `Object.prototype.toString.call(x)` — returns the -/// `"[object Tag]"` string for the receiver in IMPLICIT_THIS. +/// `"[object Tag]"` string for the `this` receiver. /// /// Tag detection uses the same coarse NaN-box / GC-type discrimination /// the rest of the runtime relies on: arrays → `"[object Array]"`, @@ -612,25 +604,17 @@ pub(crate) extern "C" fn function_prototype_apply_thunk( // Rebind a concise/object-literal method's baked `this` slot to the // explicit `.apply(thisArg)` receiver (no-op for arrows / plain fns). let target = crate::closure::rebind_explicit_this(target, this_arg); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = - prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( target, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg), args.as_ptr(), args.len(), - ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - result + ) } } -/// #4101: `Function.prototype.toString` as a real callable thunk. Reads the -/// receiver from `IMPLICIT_THIS` (set by `.call`/`.apply`'s runtime arm), then: +/// #4101: `Function.prototype.toString` as a real callable thunk. Takes the +/// receiver as its `this` argument (set by `.call`/`.apply`'s runtime arm), then: /// • throws a `TypeError` when `this` is not callable (the spec brand check /// deferred from #4098 — `Function.prototype.toString.call({})`), and /// • otherwise returns the function's reconstructed source text. @@ -679,7 +663,7 @@ pub(crate) extern "C" fn function_prototype_to_string_thunk( } /// Thunk for `Array.prototype.slice` exposed as a real callable closure -/// value. Reads the array receiver from `IMPLICIT_THIS` (set by +/// value. Takes the array receiver as its `this` argument (set by /// `Function.prototype.call`/`.apply`'s runtime arm in /// `js_native_call_method`) and forwards ordinary array-like objects to the /// generic engine or real arrays to the shared dense slice-value helper. @@ -708,7 +692,7 @@ pub(crate) extern "C" fn array_prototype_slice_thunk( this_jsv.as_pointer::() } else { // Tolerate raw-i64-encoded array receivers (some module-init - // call sites stash array pointers in IMPLICIT_THIS without + // call sites pass array pointers as `this` without // NaN-boxing). The clean_arr_ptr check inside js_array_slice // re-validates. let raw = this_bits as *const crate::array::ArrayHeader; @@ -745,7 +729,7 @@ pub(crate) extern "C" fn array_prototype_slice_thunk( /// Real callable thunks for the generic `Array.prototype` mutators /// (`pop`/`shift`/`reverse` — no positional args; `push`/`unshift`/`splice` — -/// variadic). Each reads the call-site receiver from `IMPLICIT_THIS` (set by +/// variadic). Each takes the call-site receiver as its `this` argument (set by /// the own-field dispatch and `Function.prototype.call`/`.apply`) and forwards /// to the shared engine, which mutates a real array via the dense helpers or a /// plain array-like object via live `Get`/`Set`/`Delete`. Without these, the @@ -894,7 +878,7 @@ pub(crate) extern "C" fn array_prototype_copy_within_thunk( } /// Real thunks for the generic `Array.prototype` iteration / search methods, -/// each routing the call-site receiver (IMPLICIT_THIS) through the +/// each routing the call-site receiver (`this`) through the /// `js_arraylike_*` engine. These replace the previous noop thunks so a /// reflective resolution — `Array.prototype.map.call(x, …)` through a stored /// reference, or a method reached through an object whose [[Prototype]] chain @@ -907,10 +891,10 @@ macro_rules! array_proto_arraylike_cb_thunk { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn $name( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); let a = |i: usize| { args.get(i) @@ -948,10 +932,10 @@ macro_rules! array_proto_arraylike_optarg_thunk { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn $name( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); let a = |i: usize| { args.get(i) @@ -978,10 +962,10 @@ macro_rules! array_proto_arraylike_search_thunk { #[allow(non_snake_case)] // thunk name mirrors JS API surface pub(crate) extern "C" fn $name( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); let args = global_this_rest_array_values(rest); let a = |i: usize| { args.get(i) 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 baeb01b590..120b4d59b2 100644 --- a/crates/perry-runtime/src/object/global_this/bigint_promise.rs +++ b/crates/perry-runtime/src/object/global_this/bigint_promise.rs @@ -230,8 +230,7 @@ pub(crate) extern "C" fn array_from_thunk( value: f64, ) -> f64 { // Reflective `Array.from.call(C, items)` / `Array.from.apply(C, [items])` - // binds `C` as the implicit `this`. Read it FIRST (before any nested call - // can overwrite it) and run the spec algorithm — when `C IsConstructor`, + // passes `C` as `this`. Run the spec algorithm — when `C IsConstructor`, // the result is built via `Construct(C)`. A plain reflective call (no // explicit receiver) leaves `this` as undefined / a non-constructor, so // the default `%Array%` path is taken. @@ -245,8 +244,7 @@ pub(crate) extern "C" fn array_of_thunk( this: crate::closure::JsThis, rest: f64, ) -> f64 { - // Reflective `Array.of.call(C, ...items)` binds `C` as the implicit `this`. - // Read it FIRST (before any nested call can overwrite it); when `C + // Reflective `Array.of.call(C, ...items)` passes `C` as `this`; when `C // IsConstructor` the result is built via `Construct(C, «len»)`, otherwise the // default `%Array%` path is taken. See `array_of_full` (ECMA-262 §23.1.2.3). let c = this.as_f64(); @@ -760,21 +758,19 @@ pub(crate) extern "C" fn typed_array_from_thunk( // coercion at element k means the map callback never ran for k+1 // (test262 from/set-value-abrupt-completion). let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev = this_scope.root_nanbox_f64(this.as_f64()); + // #9445: `thisArg` is rooted ONCE here, not once per callback, and re-read + // at each call because every callback can move it. + let this_arg_h = this_scope.root_nanbox_f64(this_arg); let map_at = |k: usize, v: f64| -> f64 { if map_closure.is_null() { return v; } - crate::object::js_implicit_this_set(this_arg); - let r = crate::closure::js_closure_call2( + crate::closure::js_closure_call2( map_closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_h.get_nanbox_f64()), v, k as f64, - ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - r + ) }; if let Some(kind) = kind_opt { let out = crate::typedarray::typed_array_alloc(kind, raw.len() as u32); 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 3cbb8ce8ea..37e4448def 100644 --- a/crates/perry-runtime/src/object/global_this/builtin_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/builtin_thunks.rs @@ -754,7 +754,7 @@ mod tests { ]); let f = global_this_function_call_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, rest, ); let args = [ 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 7405bdcd5f..68701e1531 100644 --- a/crates/perry-runtime/src/object/global_this/ctor_thunks.rs +++ b/crates/perry-runtime/src/object/global_this/ctor_thunks.rs @@ -387,8 +387,8 @@ pub(crate) extern "C" fn webcrypto_subtle_getter_thunk( super::super::native_module::subtle_crypto_namespace() } -fn cryptokey_receiver_addr() -> Option { - let this_bits = IMPLICIT_THIS.with(|c| c.get()); +fn cryptokey_receiver_addr(this: crate::closure::JsThis) -> Option { + let this_bits = this.bits(); let this_jsv = crate::value::JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & crate::value::POINTER_MASK) as usize @@ -406,8 +406,8 @@ fn cryptokey_brand_error() -> ! { ) } -fn cryptokey_property_getter(key: &[u8]) -> f64 { - let addr = cryptokey_receiver_addr().unwrap_or_else(|| cryptokey_brand_error()); +fn cryptokey_property_getter(this: crate::closure::JsThis, key: &[u8]) -> f64 { + let addr = cryptokey_receiver_addr(this).unwrap_or_else(|| cryptokey_brand_error()); unsafe { super::super::crypto_key_property_value(addr, key) .map(|value| f64::from_bits(value.bits())) @@ -417,30 +417,30 @@ fn cryptokey_property_getter(key: &[u8]) -> f64 { pub(crate) extern "C" fn cryptokey_algorithm_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - cryptokey_property_getter(b"algorithm") + cryptokey_property_getter(this, b"algorithm") } pub(crate) extern "C" fn cryptokey_extractable_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - cryptokey_property_getter(b"extractable") + cryptokey_property_getter(this, b"extractable") } pub(crate) extern "C" fn cryptokey_type_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - cryptokey_property_getter(b"type") + cryptokey_property_getter(this, b"type") } pub(crate) extern "C" fn cryptokey_usages_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - cryptokey_property_getter(b"usages") + cryptokey_property_getter(this, b"usages") } /// #10427: `globalThis.crypto.` is a property READ, resolved fresh 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 adfd1f96a9..a95db4e392 100644 --- a/crates/perry-runtime/src/object/global_this/fetch_globals.rs +++ b/crates/perry-runtime/src/object/global_this/fetch_globals.rs @@ -413,7 +413,7 @@ unsafe fn attach_temporal_cell_to_this(this_box: f64, cell_box: f64) { /// (`js_fetch_or_value_super`, the non-spread `super(a, b)` path) and the /// args-array `js_super_construct_apply` (the `super(...spread)` path). When /// `parent_val` is a Temporal constructor, run it (Temporal ctors return a -/// fresh cell and never mutate the implicit `this`) and stash the returned cell +/// fresh cell and never mutate their `this`) and stash the returned cell /// on `this_box` so method / getter / instanceof dispatch can recover the /// Temporal brand. Returns `true` when handled. (#5587) #[cfg(feature = "temporal")] @@ -432,21 +432,19 @@ pub(crate) unsafe fn temporal_subclass_super( // to the parent ctor for the duration of the call (the cell it returns is // re-homed onto the subclass `this`; the exact new.target identity is not // observable to these native ctors beyond being defined). Restore after. - // #9445: root the displaced receiver and `this_box` (consumed again below) - // across the parent constructor call. + // #9445: root `this_box` (consumed again below) across the parent + // constructor call. let this_scope = crate::gc::RuntimeHandleScope::new(); let this_h = this_scope.root_nanbox_f64(this_box); - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); // #10490: the displaced `new.target` crosses the same call. let prev_nt = this_scope.root_nanbox_f64(crate::object::js_new_target_set(parent_val)); - let cell = crate::closure::js_native_call_value( + let cell = crate::closure::native_call_value_this( parent_val, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), args_ptr, args_len, ); crate::object::js_new_target_set(prev_nt.get_nanbox_f64()); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if crate::temporal::is_temporal_value(cell) { attach_temporal_cell_to_this(this_h.get_nanbox_f64(), cell); } @@ -478,14 +476,14 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( let handle = if kind == 1 { global_this_request_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, arg0, arg1, ) } else { global_this_response_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, arg0, arg1, ) @@ -502,7 +500,7 @@ pub(crate) unsafe fn attach_fetch_handle_for_construction( pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) -> f64 { let handle = global_this_request_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, input, init, ); @@ -516,7 +514,7 @@ pub extern "C" fn js_request_subclass_init(this_box: f64, input: f64, init: f64) pub extern "C" fn js_response_subclass_init(this_box: f64, body: f64, init: f64) -> f64 { let handle = global_this_response_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, body, init, ); @@ -637,8 +635,8 @@ fn is_uncallable_builtin_super_parent_class_id(class_id: u32) -> bool { /// instead every runtime-value `super()` dispatches through here. When /// `parent_val` resolves to the Request/Response constructor we allocate the /// native handle and stash it on `this` (so inherited body methods work); -/// otherwise we fall back to the ordinary implicit-`this`-bound -/// `js_native_call_value`, preserving the prior behavior for every other +/// otherwise we fall back to an ordinary value call with `this` passed, +/// preserving the prior behavior for every other /// runtime-value parent (Effect's `Data.Class`, etc.). #[no_mangle] pub unsafe extern "C" fn js_fetch_or_value_super( @@ -831,8 +829,8 @@ pub unsafe extern "C" fn js_fetch_or_value_super( } } // `class X extends Temporal.` (non-spread `super(a, b)`): a Temporal - // constructor returns a fresh NaN-boxed cell and does NOT mutate the - // implicit `this`, so the ordinary dispatch below would drop that cell and + // constructor returns a fresh NaN-boxed cell and does NOT mutate its + // `this`, so the ordinary dispatch below would drop that cell and // leave the subclass instance an empty object with no Temporal brand. Stash // the cell on `this` instead. The native ctor never calls the subclass // constructor, so `called`-counter invariants hold. (#5587) @@ -1036,14 +1034,14 @@ pub unsafe extern "C" fn js_fetch_or_value_super( let handle = if kind == Some("Request") { global_this_request_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, arg0, arg1, ) } else { global_this_response_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, arg0, arg1, ) @@ -1161,16 +1159,12 @@ pub unsafe extern "C" fn js_fetch_or_value_super( } } } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_box)); - let r = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( callee, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_box), args_ptr, args_len, - ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - r + ) } } } diff --git a/crates/perry-runtime/src/object/global_this/generator.rs b/crates/perry-runtime/src/object/global_this/generator.rs index d1ad04f48f..c5996a0b93 100644 --- a/crates/perry-runtime/src/object/global_this/generator.rs +++ b/crates/perry-runtime/src/object/global_this/generator.rs @@ -845,8 +845,8 @@ fn build_generator_tower( // Without the async one, `for await (x of gen())` over an async-generator // *method instance* can't resolve the async iterator and hangs/yields nothing // (the instance carries no own iterator symbol). The async-iterator- - // acquisition path (`js_get_async_iterator`) sets the implicit-this before - // invoking this thunk, so it returns the generator instance. + // acquisition path (`js_get_async_iterator`) passes the receiver as `this` + // when invoking this thunk, so it returns the generator instance. // // The SYNC `%Generator.prototype%` carries `[Symbol.iterator]` for the same // reason (#6696): a *computed* read `gen[Symbol.iterator]` walks the @@ -856,8 +856,8 @@ fn build_generator_tower( // `value[Symbol.iterator]()` on the delegate generator. `for (x of gen())` // is unaffected: it drives the generator's own `.next()` directly (the // builtin-iterator recognizers), and where the sync iterator-acquisition - // path (`js_get_iterator`) does read `[Symbol.iterator]`, it binds - // implicit-this before invoking the method, so the thunk returns the + // path (`js_get_iterator`) does read `[Symbol.iterator]`, it passes the + // receiver as `this` when invoking the method, so the thunk returns the // generator instance. let (symbol_name, display_name, thunk) = if is_async { ( 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 697dced395..305f132f5b 100644 --- a/crates/perry-runtime/src/object/global_this/math_temporal.rs +++ b/crates/perry-runtime/src/object/global_this/math_temporal.rs @@ -409,7 +409,7 @@ fn build_temporal_now_namespace() -> f64 { /// path and use the returned cell. /// Generic accessor-getter thunk shared by every `Temporal..prototype` /// getter. The property name and expected brand kind are stored on the closure -/// instance (`__tname` / `__tkind`); the receiver comes from `IMPLICIT_THIS`. +/// instance (`__tname` / `__tkind`); the receiver is the `this` argument. /// Throws `TypeError` on a non-Temporal or wrong-brand receiver (the getter /// `branding.js` tests: `blank.call(undefined)`, `years.call({})`, …). #[cfg(feature = "temporal")] @@ -435,7 +435,7 @@ extern "C" fn temporal_proto_getter_thunk( /// Generic method thunk shared by every `Temporal..prototype` method. /// Rest-ABI (fixed arity 0): all args arrive in `rest`. Brand-checks the -/// `IMPLICIT_THIS` receiver, then forwards to the per-type dispatch router — +/// `this` receiver, then forwards to the per-type dispatch router — /// used when a prototype method is invoked through indirection /// (`Temporal.Duration.prototype.add.call(d, x)`); the normal `d.add(x)` path /// is the brand arm in `js_native_call_method`. @@ -636,7 +636,7 @@ fn temporal_brand_type_error(type_name: &str, member: &str) -> ! { } /// Shared body for a `Temporal.ZonedDateTime.prototype` accessor getter invoked -/// reflectively. Resolves `this` from `IMPLICIT_THIS`, brand-checks it is a +/// reflectively. Takes `this` as an argument, brand-checks it is a /// `ZonedDateTime`, and returns the getter's value. #[cfg(feature = "temporal")] extern "C" fn temporal_zdt_proto_getter_thunk( diff --git a/crates/perry-runtime/src/object/global_this/populate.rs b/crates/perry-runtime/src/object/global_this/populate.rs index c64501e709..e73228e17a 100644 --- a/crates/perry-runtime/src/object/global_this/populate.rs +++ b/crates/perry-runtime/src/object/global_this/populate.rs @@ -401,10 +401,10 @@ pub(crate) fn populate_global_this_builtins(singleton_at_entry: *mut ObjectHeade } // Populate well-known method properties on the prototype // (currently just `Array.prototype.slice`). Methods are - // ClosureHeader-backed thunks that read their receiver from - // `IMPLICIT_THIS` and dispatch to the corresponding native - // entry point — works in tandem with `.call`/`.apply` since - // those arms (#970) rebind IMPLICIT_THIS before forwarding. + // ClosureHeader-backed thunks that take their receiver as + // `this` and dispatch to the corresponding native entry + // point — works in tandem with `.call`/`.apply` since those + // arms (#970) pass the explicit receiver when forwarding. populate_builtin_prototype_methods(name, proto_obj); if name == "Function" { // SAFETY: `proto_obj` is the live, just-populated prototype. 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 d85b98a8da..975420c954 100644 --- a/crates/perry-runtime/src/object/global_this/proto_methods.rs +++ b/crates/perry-runtime/src/object/global_this/proto_methods.rs @@ -257,7 +257,7 @@ extern "C" fn object_prototype_dunder_proto_setter_thunk( } /// Install a native accessor (getter only) on a builtin prototype. The getter -/// is an ordinary `ClosureHeader` that reads its receiver from `IMPLICIT_THIS` +/// is an ordinary `ClosureHeader` that takes its receiver as `this` /// and brand-checks it, so `Object.getOwnPropertyDescriptor(P, k).get.call({})` /// throws like node's. /// @@ -395,7 +395,7 @@ pub(crate) fn populate_builtin_prototype_methods(builtin_name: &str, proto_obj: ); // Generic mutators get REAL thunks (vs the noop above) so a borrowed // reference works: `obj.pop = Array.prototype.pop; obj.pop()` and - // `Array.prototype.splice.call(obj, …)`. Each reads IMPLICIT_THIS and + // `Array.prototype.splice.call(obj, …)`. Each reads its `this` and // runs the array algorithm on a real array or array-like object. // #7760: `values` was a NOOP in the list above, and // `Array.prototype[Symbol.iterator]` was not an own property at all 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 b00dd7cf43..284c5d5b06 100644 --- a/crates/perry-runtime/src/object/global_this/typed_array.rs +++ b/crates/perry-runtime/src/object/global_this/typed_array.rs @@ -1,7 +1,7 @@ use super::*; -fn array_buffer_receiver_addr() -> Option { - let this_bits = IMPLICIT_THIS.with(|c| c.get()); +fn array_buffer_receiver_addr(this: crate::closure::JsThis) -> Option { + let this_bits = this.bits(); let this_jsv = JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -25,9 +25,9 @@ fn array_buffer_brand_error() -> ! { pub(crate) extern "C" fn array_buffer_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match array_buffer_receiver_addr() { + match array_buffer_receiver_addr(this) { Some(addr) => { let buf = addr as *const crate::buffer::BufferHeader; f64::from_bits( @@ -100,11 +100,11 @@ pub(crate) extern "C" fn shared_array_buffer_slice_thunk( pub(crate) extern "C" fn array_buffer_slice_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, start: f64, end: f64, ) -> f64 { - match array_buffer_receiver_addr() { + match array_buffer_receiver_addr(this) { Some(addr) => unsafe { let args = [start, end]; super::super::buffer_dispatch::dispatch_buffer_method(addr, "slice", args.as_ptr(), 2) @@ -119,8 +119,13 @@ pub(crate) extern "C" fn array_buffer_slice_thunk( /// reflectively (`ArrayBuffer.prototype.resize.call(ab, n)`). Instances /// dispatch through `buffer_dispatch` directly; these only brand-check the /// receiver and forward to the same arm, so the two cannot disagree (#10873). -fn array_buffer_method_via_dispatch(method: &'static str, arg: f64, brand_error: &[u8]) -> f64 { - match array_buffer_receiver_addr() { +fn array_buffer_method_via_dispatch( + this: crate::closure::JsThis, + method: &'static str, + arg: f64, + brand_error: &[u8], +) -> f64 { + match array_buffer_receiver_addr(this) { Some(addr) if !crate::buffer::is_shared_array_buffer(addr) && !crate::buffer::is_data_view(addr) => @@ -134,10 +139,11 @@ fn array_buffer_method_via_dispatch(method: &'static str, arg: f64, brand_error: pub(crate) extern "C" fn array_buffer_resize_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( + this, "resize", new_length, b"Method ArrayBuffer.prototype.resize called on incompatible receiver", @@ -146,10 +152,11 @@ pub(crate) extern "C" fn array_buffer_resize_thunk( pub(crate) extern "C" fn array_buffer_transfer_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( + this, "transfer", new_length, b"Method ArrayBuffer.prototype.transfer called on incompatible receiver", @@ -158,10 +165,11 @@ pub(crate) extern "C" fn array_buffer_transfer_thunk( pub(crate) extern "C" fn array_buffer_transfer_to_fixed_length_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, new_length: f64, ) -> f64 { array_buffer_method_via_dispatch( + this, "transferToFixedLength", new_length, b"Method ArrayBuffer.prototype.transferToFixedLength called on incompatible receiver", @@ -172,8 +180,8 @@ pub(crate) extern "C" fn array_buffer_transfer_to_fixed_length_thunk( /// accessors on `ArrayBuffer.prototype`. Instance reads are answered by /// `get_field_by_name_tail`; this is the `Object.getOwnPropertyDescriptor( /// ArrayBuffer.prototype, "resizable").get.call(ab)` route. -fn array_buffer_flag_getter(key: &[u8]) -> f64 { - let Some(addr) = array_buffer_receiver_addr() else { +fn array_buffer_flag_getter(this: crate::closure::JsThis, key: &[u8]) -> f64 { + let Some(addr) = array_buffer_receiver_addr(this) else { super::super::object_ops::throw_object_type_error( b"Method ArrayBuffer.prototype getter called on incompatible receiver", ) @@ -198,23 +206,23 @@ fn array_buffer_flag_getter(key: &[u8]) -> f64 { pub(crate) extern "C" fn array_buffer_resizable_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - array_buffer_flag_getter(b"resizable") + array_buffer_flag_getter(this, b"resizable") } pub(crate) extern "C" fn array_buffer_max_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - array_buffer_flag_getter(b"maxByteLength") + array_buffer_flag_getter(this, b"maxByteLength") } pub(crate) extern "C" fn array_buffer_detached_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - array_buffer_flag_getter(b"detached") + array_buffer_flag_getter(this, b"detached") } pub(crate) unsafe fn validate_array_buffer_species_constructor(addr: usize) { @@ -355,14 +363,14 @@ impl TypedArrayAccessorReceiver { } } -/// Resolve the `IMPLICIT_THIS` receiver for the reflected +/// Resolve the `this` receiver for the reflected /// `%TypedArray%.prototype` accessors installed by #2060. A Perry /// `Uint8Array` (and Node's `Buffer` subclass) uses `BufferHeader`, so a miss /// in `lookup_typed_array_kind` must continue through the strict buffer brand /// check instead of becoming a `TypeError` (#9347). -fn typed_array_receiver() -> Option { +fn typed_array_receiver(this: crate::closure::JsThis) -> Option { use crate::value::JSValue; - let this_bits = IMPLICIT_THIS.with(|c| c.get()); + let this_bits = this.bits(); let this_jsv = JSValue::from_bits(this_bits); let raw = if this_jsv.is_pointer() { (this_bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -427,9 +435,9 @@ fn typed_array_buffer_value(receiver: TypedArrayAccessorReceiver) -> f64 { /// `%TypedArray%.prototype.length` getter — element count of the receiver. extern "C" fn typed_array_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match typed_array_receiver() { + match typed_array_receiver(this) { Some(receiver) => { f64::from_bits(crate::value::JSValue::number(receiver.length() as f64).bits()) } @@ -440,9 +448,9 @@ extern "C" fn typed_array_length_getter_thunk( /// `%TypedArray%.prototype.byteLength` getter — `length * BYTES_PER_ELEMENT`. extern "C" fn typed_array_byte_length_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match typed_array_receiver() { + match typed_array_receiver(this) { Some(receiver) => { f64::from_bits(crate::value::JSValue::number(receiver.byte_length() as f64).bits()) } @@ -453,9 +461,9 @@ extern "C" fn typed_array_byte_length_getter_thunk( /// `%TypedArray%.prototype.byteOffset` getter. extern "C" fn typed_array_byte_offset_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match typed_array_receiver() { + match typed_array_receiver(this) { Some(receiver) => { f64::from_bits(crate::value::JSValue::number(receiver.byte_offset() as f64).bits()) } @@ -467,9 +475,9 @@ extern "C" fn typed_array_byte_offset_getter_thunk( /// identity as the direct property path. extern "C" fn typed_array_buffer_getter_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match typed_array_receiver() { + match typed_array_receiver(this) { Some(receiver) => typed_array_buffer_value(receiver), None => typed_array_brand_error(), } diff --git a/crates/perry-runtime/src/object/global_this_tables.rs b/crates/perry-runtime/src/object/global_this_tables.rs index e666063ecb..b8398e49de 100644 --- a/crates/perry-runtime/src/object/global_this_tables.rs +++ b/crates/perry-runtime/src/object/global_this_tables.rs @@ -105,8 +105,8 @@ pub(crate) const GLOBAL_THIS_BUILTIN_CONSTRUCTORS: &[&str] = &[ /// /// #7518: `try_dispatch_value_called_proto_method` needs this to tell a built-in /// *prototype method* invoked as a value — the #3716 uncurry-this idiom, which it -/// must re-dispatch by name on `IMPLICIT_THIS` — from a global *constructor* -/// invoked as a value, which it must not: `IMPLICIT_THIS.EventTarget(…)` resolves +/// must re-dispatch by name on the call's `this` — from a global *constructor* +/// invoked as a value, which it must not: `this.EventTarget(…)` resolves /// to nothing and the by-name tower's catch-all throws /// `TypeError: EventTarget is not a function`. /// diff --git a/crates/perry-runtime/src/object/global_this_webassembly.rs b/crates/perry-runtime/src/object/global_this_webassembly.rs index 62d1aa4df2..c25c0b1ad3 100644 --- a/crates/perry-runtime/src/object/global_this_webassembly.rs +++ b/crates/perry-runtime/src/object/global_this_webassembly.rs @@ -1488,17 +1488,13 @@ mod tests { fn async_members_reject_with_compile_error() { let closure = std::ptr::null(); assert_rejected_with_compile_error( - webassembly_compile_thunk( - closure, - crate::closure::body_call::current_this(), - undefined(), - ), + webassembly_compile_thunk(closure, crate::closure::JsThis::UNDEFINED, undefined()), "WebAssembly.compile", ); assert_rejected_with_compile_error( webassembly_instantiate_thunk( closure, - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined(), undefined(), ), @@ -1507,7 +1503,7 @@ mod tests { assert_rejected_with_compile_error( webassembly_compile_streaming_thunk( closure, - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined(), ), "WebAssembly.compileStreaming", @@ -1515,7 +1511,7 @@ mod tests { assert_rejected_with_compile_error( webassembly_instantiate_streaming_thunk( closure, - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined(), ), "WebAssembly.instantiateStreaming", @@ -1527,7 +1523,7 @@ mod tests { fn validate_reports_false() { let result = webassembly_validate_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined(), ); assert_eq!( @@ -1542,7 +1538,7 @@ mod tests { let message = string_value("boom"); let compile_err = webassembly_compile_error_ctor_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, message, ); assert_eq!(error_name_bytes(compile_err), b"CompileError".to_vec()); @@ -1552,7 +1548,7 @@ mod tests { // Message coercion mirrors `new Error(v)`. let numbered = webassembly_link_error_ctor_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, 42.0, ); assert_eq!(error_name_bytes(numbered), b"LinkError".to_vec()); @@ -1560,7 +1556,7 @@ mod tests { let bare = webassembly_runtime_error_ctor_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, undefined(), ); assert_eq!(error_name_bytes(bare), b"RuntimeError".to_vec()); @@ -1576,12 +1572,12 @@ mod tests { let compile_err = webassembly_compile_error_ctor_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, string_value("x"), ); let link_err = webassembly_link_error_ctor_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, string_value("x"), ); diff --git a/crates/perry-runtime/src/object/instanceof.rs b/crates/perry-runtime/src/object/instanceof.rs index 0d0aa2b168..583bf62036 100644 --- a/crates/perry-runtime/src/object/instanceof.rs +++ b/crates/perry-runtime/src/object/instanceof.rs @@ -459,7 +459,7 @@ fn throw_invalid_instanceof_rhs(type_ref: f64) -> ! { /// returns `false` when `this` is not callable, so `Function.prototype[Symbol /// .hasInstance].call(undefined, {})` is `false` (not a throw). Installed on /// `Function.prototype` under the `@@hasInstance` key; the receiver flows in -/// through `IMPLICIT_THIS` set by the `.call`/member dispatch. +/// as the `this` argument supplied by the `.call`/member dispatch. pub(crate) extern "C" fn function_prototype_has_instance_thunk( _closure: *const crate::closure::ClosureHeader, this: crate::closure::JsThis, diff --git a/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs b/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs index d53b45246a..629551586a 100644 --- a/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs +++ b/crates/perry-runtime/src/object/instanceof/proxy_rhs.rs @@ -21,8 +21,6 @@ pub(super) fn proxy_instanceof(value: f64, constructor: f64) -> f64 { if !crate::proxy::is_callable_function(method.get_nanbox_f64()) { throw_type_error(b"Symbol(Symbol.hasInstance) is not a function"); } - let previous_this = - scope.root_nanbox_f64(js_implicit_this_set(constructor.get_nanbox_f64())); let called = crate::exception::catch_js_throw(|| unsafe { let args = [value.get_nanbox_f64()]; if crate::proxy::js_proxy_is_proxy(method.get_nanbox_f64()) != 0 { @@ -32,15 +30,14 @@ pub(super) fn proxy_instanceof(value: f64, constructor: f64) -> f64 { &args, ) } else { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(constructor.get_nanbox_f64()), args.as_ptr(), 1, ) } }); - js_implicit_this_set(previous_this.get_nanbox_f64()); match called { Ok(result) => crate::value::js_is_truthy(result) != 0, Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/object/iterator_prototypes.rs b/crates/perry-runtime/src/object/iterator_prototypes.rs index 420751d115..0cfc095299 100644 --- a/crates/perry-runtime/src/object/iterator_prototypes.rs +++ b/crates/perry-runtime/src/object/iterator_prototypes.rs @@ -25,7 +25,7 @@ //! - `getOwnPropertyDescriptor(proto, "next")` reads the recorded builtin //! attrs off the prototype object (it's a regular `GC_TYPE_OBJECT` field). //! -//! The `next` closures are thin thunks: they read `js_implicit_this_get()` and +//! The `next` closures are thin thunks: they take the receiver as `this` and //! route by the receiver's class id to the existing //! `dispatch_{array,map,set,string}_iterator_method`. This is the ONLY behaviour //! addition for `proto.next.call(it)` / value-read `it.next()`; the class-id CALL @@ -164,11 +164,11 @@ pub(crate) fn note_iterator_prototype_exposed(value: f64) { } } -/// Resolve and validate the implicit-`this` object shared by the family +/// Resolve and validate the `this` object shared by the family /// prototype thunks. Keeping the raw-address probe here gives both the generic /// family dispatcher and the helper-specific brand check one audited path. -unsafe fn implicit_this_iterator_object() -> Option<*mut ObjectHeader> { - let this = super::js_implicit_this_get(); +unsafe fn this_iterator_object(this: crate::closure::JsThis) -> Option<*mut ObjectHeader> { + let this = this.as_f64(); let jv = JSValue::from_bits(this.to_bits()); if !jv.is_pointer() { return None; @@ -180,14 +180,14 @@ unsafe fn implicit_this_iterator_object() -> Option<*mut ObjectHeader> { Some(obj) } -/// Dispatch `method` on the implicit-`this` iterator instance, routing by class +/// Dispatch `method` on the `this` iterator instance, routing by class /// id to the matching existing iterator dispatcher. Shared by the per-family /// `next` thunks (read as a value or invoked via `.call`) and the parent /// `[Symbol.iterator]` thunk. Returns a `{ value:undefined, done:true }`-ish /// throw when `this` is not a recognized iterator (test262 `this-not-object` / /// `does-not-have-...-internal-slots` brand checks). -unsafe fn dispatch_on_implicit_this(method: &str) -> f64 { - let Some(obj) = implicit_this_iterator_object() else { +unsafe fn dispatch_on_this(this: crate::closure::JsThis, method: &str) -> f64 { + let Some(obj) = this_iterator_object(this) else { return brand_type_error(method); }; let class_id = (*obj).class_id; @@ -228,49 +228,49 @@ fn brand_type_error(method: &str) -> f64 { crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64)) } -// --- `next` thunks, one per family (all read implicit-this, dispatch by id) --- +// --- `next` thunks, one per family (all take `this`, dispatch by id) --- extern "C" fn array_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - unsafe { dispatch_on_implicit_this("next") } + unsafe { dispatch_on_this(this, "next") } } extern "C" fn map_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - unsafe { dispatch_on_implicit_this("next") } + unsafe { dispatch_on_this(this, "next") } } extern "C" fn set_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - unsafe { dispatch_on_implicit_this("next") } + unsafe { dispatch_on_this(this, "next") } } extern "C" fn string_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - unsafe { dispatch_on_implicit_this("next") } + unsafe { dispatch_on_this(this, "next") } } extern "C" fn regexp_string_iterator_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { - let Some(obj) = implicit_this_iterator_object() else { + let Some(obj) = this_iterator_object(this) else { return brand_type_error("next"); }; if (*obj).class_id != crate::regex::REGEXP_STRING_ITERATOR_CLASS_ID { return brand_type_error("next"); } - dispatch_on_implicit_this("next") + dispatch_on_this(this, "next") } } @@ -279,11 +279,11 @@ extern "C" fn regexp_string_iterator_next_thunk( /// a saved/bound canonical method must not re-enter a later `next` override. extern "C" fn iterator_helper_next_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { unsafe { - let Some(obj) = implicit_this_iterator_object() else { + let Some(obj) = this_iterator_object(this) else { return brand_type_error("next"); }; if (*obj).class_id != crate::iterator_helpers::ITERATOR_HELPER_CLASS_ID { @@ -330,7 +330,7 @@ fn set_to_string_tag(obj: *mut ObjectHeader, tag: &str) { /// appeared user-reparented. A caller treating that as "the per-instance chain /// is authoritative, resolve methods by ordinary inheriting lookup" then /// reached the `%…IteratorPrototype%` `next` THUNK, -/// which resolves its receiver from `js_implicit_this_get()` rather than the +/// which resolves its receiver from the call-site `this` rather than the /// bound `this` (#7576) — producing `Method %IteratorPrototype%.next called on /// incompatible receiver`. The prototype itself is still recorded either way; /// the class-default variant also avoids the divergence bit and cache flushes. @@ -530,7 +530,6 @@ pub(crate) unsafe fn call_overridden_iterator_next( ) -> Option { let scope = crate::gc::RuntimeHandleScope::new(); let iter = scope.root_nanbox_f64(crate::value::js_nanbox_pointer(iter_obj as i64)); - let previous = scope.root_nanbox_f64(super::js_implicit_this_get()); // #9019: an OWN `next` (`it.next = fn`, stored past the reserved floor // by `object/reserved_floor.rs`) shadows the prototype thunk and exists // independently of the tower, so probe it BEFORE the tower-null @@ -564,16 +563,14 @@ pub(crate) unsafe fn call_overridden_iterator_next( crate::closure::throw_not_callable(); } let method = scope.root_nanbox_f64(own); - super::js_implicit_this_set(iter.get_nanbox_f64()); let result = crate::exception::js_call_catching(|| { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(iter.get_nanbox_f64()), std::ptr::null(), 0, ) }); - super::js_implicit_this_set(previous.get_nanbox_f64()); return match result { Ok(value) => Some(value), Err(error) => crate::exception::js_throw(error), @@ -652,16 +649,14 @@ pub(crate) unsafe fn call_overridden_iterator_next( } let method = scope.root_nanbox_f64(method); - super::js_implicit_this_set(iter.get_nanbox_f64()); let result = crate::exception::js_call_catching(|| { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(iter.get_nanbox_f64()), std::ptr::null(), 0, ) }); - super::js_implicit_this_set(previous.get_nanbox_f64()); match result { Ok(value) => Some(value), Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index fc83014ed5..8a2085e3a5 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -358,29 +358,19 @@ pub(crate) use field_get_set::{ private_member_access_hints_restore, private_member_access_hints_savepoint, scan_private_lexical_brand_roots_mut, }; -#[cfg(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" -))] -pub(crate) use this_binding::implicit_this_bits; #[cfg(test)] pub(crate) use this_binding::js_derived_super_scope_push; -#[cfg(test)] -pub(crate) use this_binding::this_witness_counts; pub(crate) use this_binding::SuperNewTargetScope; pub(crate) use this_binding::{ derived_super_binding_stack_restore, derived_super_binding_stack_savepoint, - implicit_this_trap_restore, implicit_this_trap_savepoint, new_target_trap_restore, - new_target_trap_savepoint, scan_implicit_this_roots_mut, static_private_owner_current, - static_private_owner_pop, static_private_owner_push, static_private_owner_stack_restore, - static_private_owner_stack_savepoint, static_this_arm, static_this_arm_if_unarmed, - static_this_disarm, IMPLICIT_THIS, + new_target_trap_restore, new_target_trap_savepoint, scan_dispatch_binding_roots_mut, + static_private_owner_current, static_private_owner_pop, static_private_owner_push, + static_private_owner_stack_restore, static_private_owner_stack_savepoint, static_this_arm, + static_this_arm_if_unarmed, static_this_disarm, }; pub use this_binding::{ - js_implicit_this_get, js_implicit_this_get_sloppy, js_implicit_this_set, js_new_target_get, - js_new_target_set, js_static_this_arm_classref, js_static_this_arm_value, - js_static_this_resolve, js_this_coerce_sloppy, js_this_param_witness, ImplicitThisScope, + js_new_target_get, js_new_target_set, js_static_this_arm_classref, js_static_this_arm_value, + js_static_this_resolve, js_this_coerce_sloppy, }; pub use to_string_tag::js_object_to_string; pub(crate) use to_string_tag::typed_array_to_string_tag_name; diff --git a/crates/perry-runtime/src/object/native_call_method.rs b/crates/perry-runtime/src/object/native_call_method.rs index c209cba7f2..8ce240e8cb 100644 --- a/crates/perry-runtime/src/object/native_call_method.rs +++ b/crates/perry-runtime/src/object/native_call_method.rs @@ -361,15 +361,12 @@ unsafe fn call_primitive_closure_value( this_h.get_nanbox_f64(), ); let bound_h = scope.root_nanbox_u64(bound); - let prev_this = crate::object::js_implicit_this_set(this_h.get_nanbox_f64()); - let prev_this_h = scope.root_nanbox_f64(prev_this); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( bound_h.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), args_ptr, args_len, ); - crate::object::js_implicit_this_set(prev_this_h.get_nanbox_f64()); Some(result) } @@ -743,18 +740,13 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( if let Some((func_ptr, param_count, has_rest)) = lookup_class_symbol_method_in_chain(class_id, sym_key, true) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object)); - let result = call_registered_static_method( + return call_registered_static_method( func_ptr, args_ptr, args_len, param_count, has_rest, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - return result; } } } else if is_class_object_value(object) { @@ -763,18 +755,13 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( if let Some((func_ptr, param_count, has_rest)) = lookup_class_symbol_method_in_chain(class_id, sym_key, true) { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object)); - let result = call_registered_static_method( + return call_registered_static_method( func_ptr, args_ptr, args_len, param_count, has_rest, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - return result; } } else if key_jsval.is_pointer() || JSValue::from_bits(bits).is_pointer() { let obj_val = JSValue::from_bits(bits); @@ -831,20 +818,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( crate::object::js_object_get_index_polymorphic(object.to_bits() as i64, index); let fv = JSValue::from_bits(field.to_bits()); if !fv.is_undefined() && !fv.is_null() { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value( + return crate::closure::native_call_value_this( field, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - return result; } } } @@ -925,9 +904,8 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( } } - // Non-string key: read the property value, then invoke it with `this` - // bound to the receiver (the codegen `Expr::This` fallback reads - // `IMPLICIT_THIS` when there's no lexical `this`). + // Non-string key: read the property value, then invoke it with the + // receiver as its `this`. let field = if is_symbol_key { crate::symbol::js_object_get_symbol_property(object, key) } else { @@ -945,7 +923,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( // `symbol.rs::js_object_set_symbol_method` / // `dynamic_props.rs::clone_closure_rebind_this`). So when `o = Object.create(P)` // resolves `o[SYM]()`, the closure we get back carries `this === P`, not - // `this === o`, and `IMPLICIT_THIS` alone can't override the baked-in slot. + // `this === o`, and the receiver argument alone can't override the baked-in slot. // When the symbol method is NOT an OWN property of the receiver (i.e. it was // inherited through the prototype chain), rebind its `this` slot to the // receiver before invoking. `clone_closure_rebind_this` is a no-op for @@ -962,21 +940,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method_value( field }; - // #8495: the displaced receiver must be ROOTED across the call. The - // `replace` has already overwritten the cell, so this is the only copy the - // frame holds; an evacuating collection inside the callee moves it and the - // restore would publish a pre-move address back INTO the scanned cell. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = - prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( field, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object), args_ptr, args_len, - ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - result + ) } /// every regular + spread arg already concatenated by codegen), materialises @@ -1426,21 +1395,17 @@ pub unsafe extern "C-unwind" fn js_native_call_method( // `clone_closure_rebind_this` only rewrites a closure that // carries CAPTURES_THIS_FLAG; it returns a native builtin // (and an arrow, and a generator step) UNCHANGED. Native - // method bodies read their receiver from - // `js_implicit_this_get()` — the same #7576 property that - // made the `%IteratorPrototype%.next` THUNK throw — so - // without binding it here `Object.prototype.isPrototypeOf` - // saw no `this` ("called on null or undefined") and + // method bodies take their receiver as the `this` + // parameter — the same #7576 property that made the + // `%IteratorPrototype%.next` THUNK throw — so without + // passing it here `Object.prototype.isPrototypeOf` saw no + // `this` ("called on null or undefined") and // `Object(true).valueOf()` saw the wrong one ("called on - // incompatible receiver"). #9244. Restored on the way out, - // including when the callee throws — from a ROOT: the - // displaced `this` is the caller's receiver and the callee - // is user code that can move it (#10490). The receiver is - // re-read here, after the clone above allocated. - let _this_scope = crate::object::ImplicitThisScope::bind(&root_scope, object()); - return crate::closure::js_native_call_value( + // incompatible receiver"). #9244. The receiver is re-read + // from its root here, after the clone above allocated. + return crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args.as_ptr(), args.len(), ); @@ -1790,17 +1755,11 @@ pub unsafe extern "C-unwind" fn js_native_call_method( // `closure_get_dynamic_prop` may return a method read off the // closure's `Object.setPrototypeOf` proto, whose bound `this` // (an object-literal method binds the literal) would otherwise - // win over IMPLICIT_THIS and leave `this` as the PROTO. + // win over the receiver argument and leave `this` as the PROTO. let bound = crate::closure::clone_closure_rebind_this( dyn_val.get_nanbox_u64(), f64::from_bits(object().to_bits()), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object().to_bits()))); // #7803: `clone_closure_rebind_this` above ALLOCATES, so the // caller's raw `args_ptr` buffer holds pre-move addresses from // here on. `arg_handles` is what the collector rewrites; the @@ -1815,14 +1774,13 @@ pub unsafe extern "C-unwind" fn js_native_call_method( &call_args, ) } else { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), call_args.as_ptr(), call_args.len(), ) }; - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } // #10423: `fn.constructor(p, body)` with no own `constructor` calls @@ -1879,32 +1837,25 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let getter = (acc.get & crate::value::POINTER_MASK) as *const crate::closure::ClosureHeader; if !getter.is_null() { - // #8495: root the displaced receiver across the call below. - let prev_getter_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_getter_this_h = prev_getter_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object().to_bits()))); let method_fn = crate::closure::js_closure_call0( getter, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), ); let bound = crate::closure::clone_closure_rebind_this( method_fn.to_bits(), object(), ); - IMPLICIT_THIS.with(|c| c.set(object().to_bits())); // #7803: two collection points above this line — the - // getter is USER CODE (`js_closure_call0`) and the - // rebind allocates — so the caller's raw buffer is - // stale. Re-read the rooted arguments. + // getter is USER CODE and the rebind allocates — so + // the caller's raw buffer is stale. Re-read the rooted + // arguments. let call_args = refreshed_args(); - let result = crate::closure::js_native_call_value( + return crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), call_args.as_ptr(), call_args.len(), ); - IMPLICIT_THIS.with(|c| c.set(prev_getter_this_h.get_nanbox_u64())); - return result; } } } @@ -1979,18 +1930,10 @@ pub unsafe extern "C-unwind" fn js_native_call_method( crate::proxy::js_proxy_get(object_handle.get_nanbox_f64(), key_handle.get_nanbox_f64()); let method_handle = root_scope.root_nanbox_f64(method_value); let args = refreshed_args(); - // Bind `this` to the proxy for the duration of the call, matching the - // receiver semantics of a normal `obj.method(args)` invocation. A - // canonical class-method value reads its receiver from IMPLICIT_THIS via - // `canonical_bound_method_receiver` (#6699 routes a proxy receiver - // through there). - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope.root_nanbox_u64( - IMPLICIT_THIS.with(|c| c.replace(object_handle.get_nanbox_f64().to_bits())), - ); + // The proxy is the call's receiver, matching the receiver semantics + // of a normal `obj.method(args)` invocation. A canonical class-method + // value takes it via `canonical_bound_method_receiver` (#6699 routes a + // proxy receiver through there). let proxy_class_id = crate::proxy::proxy_target_class_id(object_handle.get_nanbox_f64()); let is_static_class_method = proxy_class_id.is_some_and(|class_id| { crate::object::class_registry::lookup_static_method_in_chain(class_id, method_name) @@ -1999,16 +1942,15 @@ pub unsafe extern "C-unwind" fn js_native_call_method( if is_static_class_method { crate::object::static_this_arm(object_handle.get_nanbox_f64()); } - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( method_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); if is_static_class_method { crate::object::static_this_disarm(); } - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } @@ -2169,10 +2111,10 @@ pub unsafe extern "C-unwind" fn js_native_call_method( // `Object.setPrototypeOf` chain, so `dyn_val` may be a method // read off the PROTO object — and an object-literal method // carries a bound `this` (the literal). A bound `this` wins over - // IMPLICIT_THIS, so setting IMPLICIT_THIS alone left `this` as - // the PROTO instead of the receiver. Rebind to the receiver, - // exactly as the ObjectHeader arm does for its inherited-field - // dispatch (`clone_closure_rebind_this` + IMPLICIT_THIS) — that + // the receiver argument, so passing the receiver alone left + // `this` as the PROTO. Rebind to the receiver, exactly as the + // ObjectHeader arm does for its inherited-field dispatch + // (`clone_closure_rebind_this` + the receiver argument) — that // asymmetry is why a plain-object receiver worked and a FUNCTION // receiver did not. // @@ -2193,20 +2135,13 @@ pub unsafe extern "C-unwind" fn js_native_call_method( dyn_val.to_bits(), f64::from_bits(recv_bits), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = - prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = crate::closure::js_native_call_value( + // The clone above allocated: re-read the receiver from its root. + return crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - return result; } // Nothing on the function's own properties or its prototype chain // answers `method_name` (the Function.prototype and Object.prototype @@ -2370,19 +2305,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( field_val.bits(), f64::from_bits(jsval().bits()), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval().bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } } @@ -2429,19 +2357,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( field_val.bits(), f64::from_bits(jsval().bits()), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval().bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } } @@ -2687,7 +2608,7 @@ pub unsafe extern "C-unwind" fn js_native_call_method( // `class X extends Promise`: inherited `then`/`catch`/`finally` dispatch // against the hidden backing Promise cell. A subclass override (own field / // vtable / prototype method) has already been consulted above, so only a - // genuinely inherited builtin reaches here. Bind `this` to the instance so + // genuinely inherited builtin reaches here. Pass the instance as `this` so // the reified thunk unwraps the backing cell and species-chains via // `receiver.constructor`. (Covers the `X.resolve().finally().then()` chains // that codegen dispatches straight through `js_native_call_method`.) @@ -2695,16 +2616,12 @@ pub unsafe extern "C-unwind" fn js_native_call_method( if crate::promise::subclass_backing_promise(object()).is_some() { if let Some(m) = crate::promise::promise_proto_method(method_name) { let args = refreshed_args(); - let prev_this = - root_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object())); - let result = crate::closure::js_native_call_value( + return crate::closure::native_call_value_this( m, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args.as_ptr(), args.len(), ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - return result; } } } @@ -2758,26 +2675,19 @@ pub unsafe extern "C-unwind" fn js_native_call_method( // objects store dynamic props in the exotic-expando side table, not the // ObjectHeader field map, so the field/vtable/prototype dispatch above // never sees them. Look the name up there; if it is a callable, invoke it - // with the receiver bound as `this` (via IMPLICIT_THIS, matching the - // closure-field dispatch path above). + // with the receiver as `this` (matching the closure-field dispatch path + // above). if jsval().is_pointer() { if let Some((addr, kind)) = super::exotic_expando::exotic_expando_kind_of_value(object()) { if let Some(bits) = super::exotic_expando::value_lookup(kind, addr, method_name) { let candidate = f64::from_bits(bits); if crate::collection_iter::is_callable(candidate) { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object().to_bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( candidate, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } } @@ -2856,20 +2766,15 @@ pub unsafe extern "C-unwind" fn js_native_call_method( let got = super::field_get_set::js_object_get_field_by_name(recv, accessor_key); let candidate = f64::from_bits(got.bits()); if crate::collection_iter::is_callable(candidate) { - // Root the displaced receiver across the call: the replace has - // already overwritten the cell, so this is the frame's only copy - // (#8495, same as the exotic-expando arm above). - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(receiver.to_bits()))); let args = refreshed_args(); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( candidate, - crate::closure::plain_call_receiver(), + // Re-read: the key allocation and the getter can + // move the receiver. + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return result; } } 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 03f6b74b42..85b26c8cc2 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 @@ -11,10 +11,10 @@ extern "C" fn original( extern "C" fn own_method( _closure: *const closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _arg: f64, ) -> f64 { - crate::object::js_implicit_this_get() + this.as_f64() } fn check_own_method_dispatch(proxy: bool) { diff --git a/crates/perry-runtime/src/object/native_call_method/common_methods.rs b/crates/perry-runtime/src/object/native_call_method/common_methods.rs index cb8d593883..d179d2982b 100644 --- a/crates/perry-runtime/src/object/native_call_method/common_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/common_methods.rs @@ -754,16 +754,14 @@ pub(crate) unsafe fn dispatch_function_proto_method( std::ptr::null() }; let rest_len = args_len.saturating_sub(1); - // The callee, the explicit `this`, and the saved previous - // implicit-`this` all cross the invocation — a moving + // The callee and the explicit `this` both cross the + // invocation — a moving // collection inside the callee relocates them (#8082: the // forced gate faulted reading the stale callee closure in // `maybe_alias_explicit_this_construction` after the call). let scope = crate::gc::RuntimeHandleScope::new(); let callee_h = scope.root_nanbox_f64(object); let this_h = scope.root_nanbox_f64(this_arg); - let prev_this_h = - scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); // Static bound-method value (`C.m.call(x)`): arm the one-shot // static-`this` override so the method body sees `x` instead // of the lexical class-ref (static private brand checks). @@ -772,22 +770,21 @@ pub(crate) unsafe fn dispatch_function_proto_method( super::static_this_arm(this_arg); } // A concise/object-literal method reads `this` from a baked - // capture slot, not IMPLICIT_THIS; rebind to the explicit + // capture slot, not the `this` argument; rebind to the explicit // `.call(thisArg)` receiver (no-op for arrows / plain fns). let call_target = crate::closure::rebind_explicit_this( callee_h.get_nanbox_f64(), this_h.get_nanbox_f64(), ); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( call_target, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), rest_ptr, rest_len, ); if static_target { super::static_this_disarm(); } - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); // #4973: `http.Server.call(this, handler)` — the inherits // pattern. Alias the explicit `this` object to the handle the // native class export constructed. @@ -903,14 +900,12 @@ pub(crate) unsafe fn dispatch_function_proto_method( } else { (buf.as_ptr(), buf.len()) }; - // Same rooting discipline as the `call` arm (#8082): callee, - // explicit `this`, and the saved implicit-`this` cross the - // invocation and must survive a moving collection inside it. + // Same rooting discipline as the `call` arm (#8082): callee + // and explicit `this` cross the invocation and must survive a + // moving collection inside it. let scope = crate::gc::RuntimeHandleScope::new(); let callee_h = scope.root_nanbox_f64(object); let this_h = scope.root_nanbox_f64(this_arg); - let prev_this_h = - scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(this_arg.to_bits()))); // Static bound-method value — see the matching `call` arm. let static_target = super::native_module::is_static_bound_method_value(object); if static_target { @@ -923,16 +918,15 @@ pub(crate) unsafe fn dispatch_function_proto_method( callee_h.get_nanbox_f64(), this_h.get_nanbox_f64(), ); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( apply_target, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_h.get_nanbox_f64()), call_args_ptr, call_args_len, ); if static_target { super::static_this_disarm(); } - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); // #4973: `http.Server.apply(this, args)` — same inherits // pattern as the `call` arm above. super::native_this_alias::maybe_alias_explicit_this_construction( diff --git a/crates/perry-runtime/src/object/native_call_method/disposal.rs b/crates/perry-runtime/src/object/native_call_method/disposal.rs index 16a5cca723..29649803da 100644 --- a/crates/perry-runtime/src/object/native_call_method/disposal.rs +++ b/crates/perry-runtime/src/object/native_call_method/disposal.rs @@ -14,6 +14,9 @@ pub(super) unsafe fn try_symbol_dispose_dispatch( // Only real heap objects store symbol-keyed methods. Native handles and // primitives return None here and fall through to the existing dispatch. let _obj = object_ptr_from_value(object)?; + // The symbol read can run a getter; the receiver is re-read from this root. + let scope = crate::gc::RuntimeHandleScope::new(); + let object_handle = scope.root_nanbox_f64(object); let want_async = method_name == "__perry_async_dispose__"; let shorts: &[&str] = if want_async { &["asyncDispose", "dispose"] @@ -26,23 +29,16 @@ pub(super) unsafe fn try_symbol_dispose_dispatch( continue; } let sym_f64 = f64::from_bits(JSValue::pointer(sym as *const u8).bits()); - let method = crate::symbol::js_object_get_symbol_property(object, sym_f64); + let method = + crate::symbol::js_object_get_symbol_property(object_handle.get_nanbox_f64(), sym_f64); let mjsv = JSValue::from_bits(method.to_bits()); if method.to_bits() != crate::value::TAG_UNDEFINED && !mjsv.is_null() && mjsv.is_pointer() { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_scope = crate::gc::RuntimeHandleScope::new(); - let prev_h = - prev_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value( + return Some(crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, - ); - IMPLICIT_THIS.with(|c| c.set(prev_h.get_nanbox_u64())); - return Some(result); + )); } } None 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 5aadd6ac94..62d72dcbb8 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 @@ -130,19 +130,14 @@ unsafe fn dictionary_function_proto_method_call( } // A user callable inherited from the recorded prototype: an ordinary // method call with the function as `this`. - let prev_this_h = scope.root_nanbox_u64( - super::IMPLICIT_THIS.with(|c| c.replace(receiver_h.get_nanbox_f64().to_bits())), - ); let callee = crate::closure::rebind_explicit_this(value_h.get_nanbox_f64(), receiver_h.get_nanbox_f64()); - let result = crate::closure::js_native_call_value( + Some(crate::closure::native_call_value_this( callee, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver_h.get_nanbox_f64()), args_ptr, args_len, - ); - super::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - Some(result) + )) } #[cfg(test)] diff --git a/crates/perry-runtime/src/object/native_call_method/handle_methods.rs b/crates/perry-runtime/src/object/native_call_method/handle_methods.rs index 0fff2a5c85..11479c3988 100644 --- a/crates/perry-runtime/src/object/native_call_method/handle_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/handle_methods.rs @@ -39,22 +39,18 @@ unsafe fn dispatch_handle_proto_method( } // An inherited prototype method may be a closure that BAKED `this` into a // capture slot at definition time (object-literal methods are lowered with - // `captures_this`). Setting IMPLICIT_THIS alone can't override that slot, so + // `captures_this`). Passing `this` alone can't override that slot, so // rebind the closure's `this` to the handle receiver first — // `clone_closure_rebind_this` is a no-op for closures that don't capture // `this` and for non-closure values. Mirrors the class-prototype fallback. let _ = closure_ptr; let bound = crate::closure::clone_closure_rebind_this(resolved_bits, object); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(object)); - let result = crate::closure::js_native_call_value( + Some(crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object), args_ptr, args_len, - ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - Some(result) + )) } pub(super) unsafe fn dispatch_handle( @@ -220,20 +216,12 @@ pub(super) unsafe fn dispatch_handle( { let stored_ptr = crate::value::js_nanbox_get_pointer(stored) as usize; if crate::closure::is_closure_ptr(stored_ptr) { - let recv_bits = jsval.bits(); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( stored, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -981,31 +969,24 @@ pub(super) unsafe fn dispatch_handle( // (issue #87). // // Issue #519: bind `this` to the receiver - // for the duration of the call. Non-arrow - // function bodies read `this` from - // IMPLICIT_THIS (codegen Expr::This - // fallback when this_stack is empty); - // without this save/set/restore, the - // body sees `this = undefined` and any + // for the call. Non-arrow function bodies + // read `this` from their `this` argument + // (codegen Expr::This fallback when + // this_stack is empty); without passing + // the receiver, the body sees `this = undefined` and any // `this.foo()` call falls through to the // issue #510 catch-all "(undefined).foo // is not a function" TypeError. Hono's // RegExpRouter.match (imported function // assigned as a class field) hit this. - let recv_bits = jsval.bits(); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(field_val.bits()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64( + object_handle.get_nanbox_f64(), + ), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -1162,18 +1143,14 @@ pub(super) unsafe fn dispatch_handle( // Mirrors `resolve_proto_chain_field_with_receiver` // (the winston `get transports()` fix). let receiver_f64 = f64::from_bits(jsval.bits()); - // #8495: root the displaced receiver across the call below. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope.root_nanbox_u64( - IMPLICIT_THIS.with(|c| c.replace(receiver_f64.to_bits())), - ); // #10490: the displaced accessor receiver rides - // through the same getter call — root it too. + // through the getter call — root it. + let override_scope = crate::gc::RuntimeHandleScope::new(); let prev_override = super::super::field_get_set::accessor_receiver_override_begin( receiver_f64, ) - .map(|value| prev_this_scope.root_nanbox_f64(value)); + .map(|value| override_scope.root_nanbox_f64(value)); let field_val = js_object_get_field_by_name( proto_obj as *const _, method_key as *const crate::StringHeader, @@ -1181,7 +1158,6 @@ pub(super) unsafe fn dispatch_handle( super::super::field_get_set::accessor_receiver_override_end( prev_override.map(|handle| handle.get_nanbox_f64()), ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); if !field_val.is_undefined() && !field_val.is_null() { resolved_method = Some(ResolvedMethod::ProtoClosure { field_bits: field_val.bits(), @@ -1228,19 +1204,12 @@ pub(super) unsafe fn dispatch_handle( field_bits, f64::from_bits(jsval.bits()), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } None => {} @@ -1284,7 +1253,7 @@ pub(super) unsafe fn dispatch_handle( // `symbol.rs::js_object_set_symbol_method`). So when // `o = Object.create(P)` resolves `o.method()`, the // closure carries `this === P`, not `this === o`, and - // setting `IMPLICIT_THIS = o` can't override the + // passing `this = o` can't override the // baked-in slot that the body reads. Rebind the slot // to the receiver before invoking. This mirrors the // symbol-keyed fix (#1969) for the string-keyed @@ -1296,19 +1265,12 @@ pub(super) unsafe fn dispatch_handle( field_val.bits(), f64::from_bits(jsval.bits()), ); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -1320,19 +1282,12 @@ pub(super) unsafe fn dispatch_handle( // only exists in `CLASS_PROTOTYPE_METHODS`. Bind `this` // to the receiver and call the stored closure. if let Some(method_value) = lookup_prototype_method(class_id, method_name) { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(jsval.bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( method_value, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } diff --git a/crates/perry-runtime/src/object/native_call_method/namespace_override.rs b/crates/perry-runtime/src/object/native_call_method/namespace_override.rs index 4d5e8a2087..dba91b5b72 100644 --- a/crates/perry-runtime/src/object/native_call_method/namespace_override.rs +++ b/crates/perry-runtime/src/object/native_call_method/namespace_override.rs @@ -80,12 +80,10 @@ pub(super) unsafe fn call_native_namespace_override( } RUNNING.with(|r| r.borrow_mut().push((module, method_name.to_string()))); let _running = Running; - let _this_scope = - crate::object::ImplicitThisScope::bind(root_scope, namespace_handle.get_nanbox_f64()); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args_handles); - Some(crate::closure::js_native_call_value( + Some(crate::closure::native_call_value_this( bound_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(namespace_handle.get_nanbox_f64()), args.as_ptr(), args.len(), )) diff --git a/crates/perry-runtime/src/object/native_call_method/object_proto.rs b/crates/perry-runtime/src/object/native_call_method/object_proto.rs index 716e08c823..6da2cfdbe7 100644 --- a/crates/perry-runtime/src/object/native_call_method/object_proto.rs +++ b/crates/perry-runtime/src/object/native_call_method/object_proto.rs @@ -32,17 +32,15 @@ pub(super) unsafe fn call_object_to_string_method(object: f64) -> Option { if !crate::closure::is_closure_ptr(method_ptr) { throw_object_to_string_not_function(); } + // Re-read: the key allocation and the method read can move the receiver. + let receiver = object_handle.get_nanbox_f64(); let bound = crate::closure::clone_closure_rebind_this(method_bits, receiver); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + Some(crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), std::ptr::null(), 0, - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - Some(result) + )) } pub(crate) unsafe fn js_object_default_value_of(receiver: f64) -> f64 { diff --git a/crates/perry-runtime/src/object/native_call_method/patched_proto.rs b/crates/perry-runtime/src/object/native_call_method/patched_proto.rs index 180cec634f..5dafc7745a 100644 --- a/crates/perry-runtime/src/object/native_call_method/patched_proto.rs +++ b/crates/perry-runtime/src/object/native_call_method/patched_proto.rs @@ -114,13 +114,11 @@ pub unsafe extern "C-unwind" fn js_native_call_method_patched_proto( ); let bound = scope.root_nanbox_u64(bound); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - // A non-arrow function body reads its receiver from `IMPLICIT_THIS`; + // A non-arrow function body reads its receiver from its `this` argument; // `clone_closure_rebind_this` only covers a closure that captured `this`. - // Restored on the way out, including when the callee throws (#10490). - let _this_scope = crate::object::ImplicitThisScope::bind(&scope, receiver.get_nanbox_f64()); - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( bound.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), args.as_ptr(), args.len(), ) diff --git a/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs b/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs index 86de6f6433..aedafe6d5e 100644 --- a/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs +++ b/crates/perry-runtime/src/object/native_call_method/primitive_methods.rs @@ -108,7 +108,7 @@ pub(super) unsafe fn dispatch_primitive( // Rebind the closure's reserved `this` slot to the class ref, as // the prototype/field method-dispatch arms above do. A static // data property holding an object-literal method (`captures_this`) - // bakes `this` into a capture slot that `IMPLICIT_THIS` alone + // bakes `this` into a capture slot that the `this` argument alone // can't override; `clone_closure_rebind_this` is a no-op for // closures that don't capture `this`, so plain functions and // arrows are unaffected. @@ -118,18 +118,12 @@ pub(super) unsafe fn dispatch_primitive( ); let prop_handle = root_scope.root_nanbox_f64(f64::from_bits(bound)); let args = refreshed_args(); - // #8495: root the displaced receiver across the call below. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope.root_nanbox_u64( - IMPLICIT_THIS.with(|c| c.replace(object_handle.get_nanbox_f64().to_bits())), - ); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( prop_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -308,7 +302,7 @@ pub(super) unsafe fn dispatch_primitive( // `.annotations` receiver lowers to a generic Call result), or any // `(expr-returning-a-class-object).staticMethod()`. The compile-time // static-dispatch tower (property_get.rs) binds `this` via - // IMPLICIT_THIS; the generic field-scan path below does NOT, so + // the `this` argument; the generic field-scan path below does NOT, so // `this.` (effect's `annotations() { make(this.ast, ...) }`) // read `undefined`. Route to `js_class_static_method_call`, which binds // `this` to the receiver and walks the class_id parent chain — but only @@ -351,14 +345,10 @@ pub(super) unsafe fn dispatch_primitive( let receiver = object_handle.get_nanbox_f64(); let bound = crate::closure::clone_closure_rebind_this(method.get_nanbox_u64(), receiver); - let _this = crate::object::ImplicitThisScope::bind( - root_scope, - object_handle.get_nanbox_f64(), - ); let args = refreshed_args(); - return Some(crate::closure::js_native_call_value( + return Some(crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), )); @@ -395,14 +385,10 @@ pub(super) unsafe fn dispatch_primitive( let bound = crate::closure::clone_closure_rebind_this(method.get_nanbox_u64(), receiver); // Re-read: the clone above allocated (#10490). - let _this = crate::object::ImplicitThisScope::bind( - root_scope, - object_handle.get_nanbox_f64(), - ); let args = refreshed_args(); - return Some(crate::closure::js_native_call_value( + return Some(crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), )); @@ -440,19 +426,13 @@ pub(super) unsafe fn dispatch_primitive( if (v.to_bits() & crate::value::TAG_MASK) == crate::value::POINTER_TAG && crate::closure::is_closure_ptr(cand) { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv.to_bits()))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( v, - crate::closure::plain_call_receiver(), + // Re-read: the own-property read above can run a getter. + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -484,8 +464,8 @@ pub(super) unsafe fn dispatch_primitive( let has_own_ctor = crate::promise::promise_has_own_constructor(promise_addr); if has_own_then || has_own_ctor { - // Spec path: look up the prototype method and call it with - // IMPLICIT_THIS set to the promise value so the thunk can read it. + // Spec path: look up the prototype method and call it with the + // promise value as `this` so the thunk can read it. if has_own_then && method_name == "then" { // For "then" with own "then": Invoke(promise, "then", args) directly. // exotic_get_own_property invokes accessor getters (propagating throws) @@ -509,43 +489,30 @@ pub(super) unsafe fn dispatch_primitive( )); } let args = refreshed_args(); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(promise_val.to_bits()))); let result = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( own_then, - crate::closure::plain_call_receiver(), + // Re-read: the own `then` read above can run a getter. + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ) }; - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } // For "catch"/"finally" with own "then", or "then" with own "constructor": - // call the prototype thunk with IMPLICIT_THIS = promise_val so it can + // call the prototype thunk with `this` = the promise so it can // read the receiver and invoke call_receiver_then / SpeciesConstructor. if let Some(proto_method) = crate::promise::promise_proto_method(method_name) { let args = refreshed_args(); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(promise_val.to_bits()))); let result = unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( proto_method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ) }; - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } // Fallthrough to fast path if prototype method lookup fails. @@ -660,18 +627,12 @@ pub(super) unsafe fn dispatch_primitive( ); let prop_handle = root_scope.root_nanbox_f64(f64::from_bits(bound)); let args = refreshed_args(); - // #8495: root the displaced receiver across the call below. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope.root_nanbox_u64( - IMPLICIT_THIS.with(|c| c.replace(object_handle.get_nanbox_f64().to_bits())), - ); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( prop_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } // Own key present but NOT callable (`re.exec = 5; re.exec()`). @@ -722,7 +683,7 @@ pub(super) unsafe fn dispatch_primitive( // reinterprets the cell's bytes as an object and returns garbage. Every // `Date.prototype` method (getters, setters, `toISOString`, `toJSON`, // `toString`, …) is installed on `Date.prototype` and reads the - // `IMPLICIT_THIS` receiver, so resolve the method there and dispatch with + // `this` receiver, so resolve the method there and dispatch with // `this` bound to the cell. Previously only `toString` was routed this way; // every other dynamic/computed call (`date[m](...)`, `Reflect.apply`) fell // through and silently dropped setter mutations — e.g. dayjs's @@ -747,19 +708,13 @@ pub(super) unsafe fn dispatch_primitive( if (v.to_bits() & crate::value::TAG_MASK) == crate::value::POINTER_TAG && crate::closure::is_closure_ptr((v.to_bits() & 0x0000_FFFF_FFFF_FFFF) as usize) { - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = - prev_this_scope.root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(recv_bits))); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( v, - crate::closure::plain_call_receiver(), + // Re-read: the own-property read above can run a getter. + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } @@ -775,19 +730,14 @@ pub(super) unsafe fn dispatch_primitive( let value = crate::object::js_object_get_field_by_name(proto_ptr, key); if !value.is_undefined() { let value_f64 = f64::from_bits(value.bits()); - // #8495: root the displaced receiver across the call below — the - // replace has already overwritten the cell, so this is the frame's only - // copy and the restore would otherwise publish a pre-move address. - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope - .root_nanbox_u64(IMPLICIT_THIS.with(|c| c.replace(object.to_bits()))); - let result = crate::closure::js_native_call_value( + // Re-read: the key allocation and the prototype read can + // both move the cell. + let result = crate::closure::native_call_value_this( value_f64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(object_handle.get_nanbox_f64()), args_ptr, args_len, ); - IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); return Some(result); } } 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 2345646d70..29da6b5d27 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 @@ -5,12 +5,12 @@ use super::*; /// `js_native_call_value`, which would invoke the shared no-op thunk /// (`global_this_builtin_noop_thunk`) and return `undefined`. This is the final /// link in the "uncurry-this" idiom `Function.prototype.call.bind(method)`: the -/// `Function.prototype.call` thunk stashes the intended receiver in -/// `IMPLICIT_THIS`, then calls the bound `method` value — which until now no-op'd. +/// `Function.prototype.call` thunk passes the intended receiver as `this` +/// to the bound `method` value — which until now no-op'd. /// /// When the invoked closure is a no-op-backed built-in proto method, recover its /// recorded method name and re-dispatch through the real `js_native_call_method` -/// tower using the current `IMPLICIT_THIS` as the receiver. Returns `None` for +/// tower using the call's `this` as the receiver. Returns `None` for /// any other closure so normal dispatch proceeds untouched. /// /// Gated on a recorded built-in `.length` (a proto method always has one) AND on @@ -20,6 +20,7 @@ use super::*; /// invariant, and it stopped holding (#7518). See the exclusion below. pub(crate) unsafe fn try_dispatch_value_called_proto_method( closure: *const crate::closure::ClosureHeader, + this: crate::closure::JsThis, args_ptr: *const f64, args_len: usize, ) -> Option { @@ -40,7 +41,7 @@ pub(crate) unsafe fn try_dispatch_value_called_proto_method( let name_hdr = crate::builtins::js_string_coerce(name_val); let name = super::has_own_helpers::str_from_string_header(name_hdr)?; // #7518: a global CONSTRUCTOR reached as a VALUE is never a prototype-method - // uncurry, so re-dispatching it as `IMPLICIT_THIS.(…)` is always wrong: + // uncurry, so re-dispatching it as `this.(…)` is always wrong: // the by-name tower has no such method and its catch-all throws // `TypeError: is not a function`. Constructors share the no-op thunk // with the proto methods this helper serves, and the `.length` gate above @@ -50,7 +51,7 @@ pub(crate) unsafe fn try_dispatch_value_called_proto_method( // a recorded length and opened the gate — re-breaking #6301: a // `class Bus extends EventTarget {}` has no static parent class id, so its // `super()` runs the parent VALUE through `js_fetch_or_value_super`, which - // binds `IMPLICIT_THIS` to the new instance before the value call. That then + // passes the new instance as `this` to the value call. That then // re-dispatched `bus.EventTarget()` and threw. Make the exclusion explicit // and table-driven so growing the spec-length table cannot re-open it. // @@ -69,7 +70,7 @@ pub(crate) unsafe fn try_dispatch_value_called_proto_method( { return None; } - let receiver = f64::from_bits(IMPLICIT_THIS.with(|c| c.get())); + let receiver = this.as_f64(); Some(js_native_call_method( receiver, name.as_ptr() as *const i8, diff --git a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs index f7f1be1f5a..e817378b67 100644 --- a/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs +++ b/crates/perry-runtime/src/object/native_call_method/to_locale_string_tests.rs @@ -162,12 +162,10 @@ fn object_prototype_builtin_invokes_to_string_without_self_redispatch() { // the source-level `toLocaleString` dispatcher, which found the same own // thunk again and overflowed the native stack before reaching `toString`. let receiver = crate::object::builtin_prototype_value("Object"); - let previous = crate::object::js_implicit_this_set(receiver); let result = crate::object::global_this::object_prototype_to_locale_string_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(receiver), ); - crate::object::js_implicit_this_set(previous); let ptr = crate::value::js_get_string_pointer_unified(result) as *const crate::StringHeader; assert!(!ptr.is_null()); diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index c01e1ec0cd..43856f5f96 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -512,6 +512,7 @@ pub(crate) fn install_nm_ee_ops() { /// `closure::dispatch::value_call`; see `NmEeOps::ee_dynamic_super`). unsafe fn nm_ee_dynamic_super( func_value: f64, + this: crate::closure::JsThis, args_ptr: *const f64, args_len: usize, ) -> Option { @@ -521,7 +522,7 @@ unsafe fn nm_ee_dynamic_super( if (module == "events" && (method == "EventEmitter" || method == "EventEmitterAsyncResource")) || (module == "stream" && method == "Stream") { - let this_val = super::js_implicit_this_get(); + let this_val = this.as_f64(); if crate::value::JSValue::from_bits(this_val.to_bits()).is_pointer() { if method == "EventEmitterAsyncResource" { let options = if !args_ptr.is_null() && args_len > 0 { @@ -1318,7 +1319,7 @@ pub extern "C" fn js_class_method_bind( // `class_prototype_method_value_for_name` instead of minting a fresh // per-receiver closure here. The canonical captures the OWNER class's // prototype-ref (capture 0); `dispatch_bound_method` recognises that marker - // and supplies the call-site `this` (IMPLICIT_THIS) so invocations still see + // and supplies the call-site `this` argument so invocations still see // the right receiver — e.g. the `this.m = this.m.bind(this)` idiom rebinds // correctly, and a bare `const f = c.m; f()` runs with the spec `this`. // @@ -1655,7 +1656,7 @@ pub(crate) fn build_symbol_bound_method_closure( /// Resolve the effective receiver for a BOUND_METHOD dispatch. When the /// captured receiver is a canonical class-method marker (a class prototype-ref, /// produced by `class_prototype_method_value_for_name`), substitute the -/// call-site `this` (IMPLICIT_THIS) provided it is itself a dispatchable class +/// call-site `this` argument provided it is itself a dispatchable class /// receiver (an instance or class ref). Otherwise the captured value is the real /// receiver and is returned unchanged. See `dispatch_bound_method`. /// Is `value` a bound STATIC-method value — a BOUND_METHOD closure whose @@ -1686,13 +1687,16 @@ pub(crate) fn is_static_bound_method_value(value: f64) -> bool { || class_registry::is_class_object_value(captured) } -pub(crate) fn canonical_bound_method_receiver(captured: f64) -> f64 { +pub(crate) fn canonical_bound_method_receiver( + captured: f64, + call_this: crate::closure::JsThis, +) -> f64 { if class_prototype_ref_id(captured).is_some() { // The captured prototype identifies the method's owner, not its receiver. // Class methods are strict: every call-site value, including null, // undefined, primitives and arrays, must reach the body unchanged. // Dispatch resolves the body from the captured owner independently. - super::js_implicit_this_get() + call_this.as_f64() } else { captured } @@ -1718,7 +1722,7 @@ pub(super) fn class_id_from_method_receiver(instance: f64) -> Option { // this guard, a free call to a `C.prototype.method` bound-method // value made from inside a function-object method body (e.g. // test262's `assert.throws(…, function(){ m(...) })`, where - // `IMPLICIT_THIS` is the `assert` function) would mis-substitute the + // the call-site `this` was the `assert` function) would mis-substitute the // function object as the receiver and dispatch `assert.method(...)` // instead of `C.prototype.method`, bypassing the generator wrapper's // param prologue. See `canonical_bound_method_receiver`. @@ -1979,19 +1983,4 @@ pub(crate) use cjs_default::{ }; #[cfg(test)] -mod buffer_pool_size_tests { - // #11471: `Buffer.poolSize` is per realm, so a worker's write (possibly of - // a heap value) never reaches another thread. - #[test] - fn buffer_pool_size_writes_stay_on_their_thread() { - let written = std::thread::spawn(|| { - super::set_buffer_pool_size(1.0); - super::buffer_pool_size() - }) - .join() - .unwrap(); - assert_eq!(written, 1.0); - let elsewhere = std::thread::spawn(super::buffer_pool_size).join().unwrap(); - assert_eq!(elsewhere, 65536.0); - } -} +mod buffer_pool_size_tests; 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 8d207a36ee..ab4d0fd093 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 @@ -191,7 +191,7 @@ fn attach_prototype(constructor_value: f64, methods: &[(&str, u32)]) -> f64 { /// Materialize an unbound `AsyncResource.prototype` method for native-handle /// property reads whose static type was erased. Invocation observes the -/// call-site receiver through `IMPLICIT_THIS`, just like the real prototype. +/// 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); diff --git a/crates/perry-runtime/src/object/native_module/buffer_pool_size_tests.rs b/crates/perry-runtime/src/object/native_module/buffer_pool_size_tests.rs new file mode 100644 index 0000000000..237669bfb6 --- /dev/null +++ b/crates/perry-runtime/src/object/native_module/buffer_pool_size_tests.rs @@ -0,0 +1,14 @@ +// #11471: `Buffer.poolSize` is per realm, so a worker's write (possibly of +// a heap value) never reaches another thread. +#[test] +fn buffer_pool_size_writes_stay_on_their_thread() { + let written = std::thread::spawn(|| { + super::set_buffer_pool_size(1.0); + super::buffer_pool_size() + }) + .join() + .unwrap(); + assert_eq!(written, 1.0); + let elsewhere = std::thread::spawn(super::buffer_pool_size).join().unwrap(); + assert_eq!(elsewhere, 65536.0); +} 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 a378f6acaf..f65d5ec813 100644 --- a/crates/perry-runtime/src/object/native_module/callable_exports.rs +++ b/crates/perry-runtime/src/object/native_module/callable_exports.rs @@ -429,7 +429,7 @@ extern "C" fn buffer_prototype_method_thunk( } /// `this` for a `Buffer.prototype.offset`/`.parent` accessor read, resolved -/// through `IMPLICIT_THIS` (the same mechanism `require_webcrypto_this` in +/// from the getter's `this` argument (the same mechanism `require_webcrypto_this` in /// `ctor_thunks.rs` uses for Web Crypto getters). `None` for a non-buffer /// receiver — Node's real getters answer `undefined` rather than throwing /// (`isInstance(this, Buffer) ? … : undefined`), and ordinary Buffer/typed- 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 fbe7154a36..94dd89a28c 100644 --- a/crates/perry-runtime/src/object/native_module/web_locks.rs +++ b/crates/perry-runtime/src/object/native_module/web_locks.rs @@ -660,7 +660,7 @@ pub extern "C" fn js_worker_threads_locks_request( ) -> f64 { worker_threads_locks_request( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, name_value, options_or_callback, maybe_callback, @@ -677,5 +677,5 @@ extern "C" fn worker_threads_locks_query( #[no_mangle] pub extern "C" fn js_worker_threads_locks_query() -> f64 { - worker_threads_locks_query(std::ptr::null(), crate::closure::body_call::current_this()) + worker_threads_locks_query(std::ptr::null(), crate::closure::JsThis::UNDEFINED) } diff --git a/crates/perry-runtime/src/object/nm_namespace_hooks.rs b/crates/perry-runtime/src/object/nm_namespace_hooks.rs index 78289b9355..0da6bf0a15 100644 --- a/crates/perry-runtime/src/object/nm_namespace_hooks.rs +++ b/crates/perry-runtime/src/object/nm_namespace_hooks.rs @@ -75,7 +75,8 @@ pub(crate) struct NmEeOps { /// Dynamic `super()` for `class X extends `: /// installs the EE methods on the fresh instance. `None` when the callee /// is not the bound events export (fall through to normal call dispatch). - pub ee_dynamic_super: unsafe fn(f64, *const f64, usize) -> Option, + /// The second argument is the call's `this` (the fresh instance). + pub ee_dynamic_super: unsafe fn(f64, crate::closure::JsThis, *const f64, usize) -> Option, } static NM_EE_OPS: AtomicPtr = AtomicPtr::new(std::ptr::null_mut()); diff --git a/crates/perry-runtime/src/object/own_override.rs b/crates/perry-runtime/src/object/own_override.rs index f42a91bbc9..e4d63b9c6b 100644 --- a/crates/perry-runtime/src/object/own_override.rs +++ b/crates/perry-runtime/src/object/own_override.rs @@ -354,8 +354,7 @@ unsafe fn resolve_own_user_method(recv: f64, name: &str) -> Option { /// would be a wrong `this` or an unrooted method value, neither of which a /// fixture reliably catches. /// -/// `IMPLICIT_THIS` is bound across the call and restored after it, because the -/// callee reads its receiver from there when it has no lexical `this`. +/// The receiver is passed to the callee as its `this` argument. /// /// `recv` and `args` are READERS, not values: resolving the method can run an /// own accessor's getter and allocate, so the receiver and arguments handed to @@ -391,19 +390,12 @@ unsafe fn invoke_own_user_method(own: f64, recv: f64, args: &[f64]) -> f64 { let arg_handles = root_scope.root_nanbox_f64_slice(args); // Re-read AFTER rooting: resolving the method can move the heap. let refreshed = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - let prev_this_scope = crate::gc::RuntimeHandleScope::new(); - let prev_this_h = prev_this_scope.root_nanbox_u64( - crate::object::this_binding::IMPLICIT_THIS - .with(|c| c.replace(recv_handle.get_nanbox_f64().to_bits())), - ); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(recv_handle.get_nanbox_f64()), refreshed.as_ptr(), refreshed.len(), - ); - crate::object::this_binding::IMPLICIT_THIS.with(|c| c.set(prev_this_h.get_nanbox_u64())); - result + ) } /// Does the array behind `arr` OWN a property named `push`? Returns the LIVE diff --git a/crates/perry-runtime/src/object/primitive_proto_thunks.rs b/crates/perry-runtime/src/object/primitive_proto_thunks.rs index 04c2174657..76535331fb 100644 --- a/crates/perry-runtime/src/object/primitive_proto_thunks.rs +++ b/crates/perry-runtime/src/object/primitive_proto_thunks.rs @@ -186,8 +186,8 @@ fn primitive_proto_method_closure_value(method_name: &str, func_ptr: *const u8, crate::value::js_nanbox_pointer(closure as i64) } -fn receiver_value() -> f64 { - f64::from_bits(IMPLICIT_THIS.with(|c| c.get())) +fn receiver_value(this: crate::closure::JsThis) -> f64 { + f64::from_bits(this.bits()) } fn throw_incompatible_receiver(proto: &str, method: &str) -> ! { @@ -202,8 +202,8 @@ fn boxed_payload(receiver: f64, expected_class_id: u32) -> Option { (class_id == expected_class_id).then_some(payload) } -fn number_receiver_or_throw(method: &str) -> f64 { - let receiver = receiver_value(); +fn number_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> f64 { + let receiver = receiver_value(this); if let Some(payload) = boxed_payload(receiver, CLASS_ID_BOXED_NUMBER) { return payload; } @@ -226,8 +226,8 @@ fn number_receiver_or_throw(method: &str) -> f64 { throw_incompatible_receiver("Number.prototype", method) } -fn boolean_receiver_or_throw(method: &str) -> f64 { - let receiver = receiver_value(); +fn boolean_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> f64 { + let receiver = receiver_value(this); if let Some(payload) = boxed_payload(receiver, CLASS_ID_BOXED_BOOLEAN) { return payload; } @@ -245,8 +245,8 @@ fn boolean_receiver_or_throw(method: &str) -> f64 { throw_incompatible_receiver("Boolean.prototype", method) } -fn symbol_receiver_or_throw(method: &str) -> f64 { - let receiver = receiver_value(); +fn symbol_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> f64 { + let receiver = receiver_value(this); if let Some(payload) = boxed_payload(receiver, CLASS_ID_BOXED_SYMBOL) { return payload; } @@ -256,8 +256,8 @@ fn symbol_receiver_or_throw(method: &str) -> f64 { throw_incompatible_receiver("Symbol.prototype", method) } -fn bigint_receiver_or_throw(method: &str) -> f64 { - let receiver = receiver_value(); +fn bigint_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> f64 { + let receiver = receiver_value(this); if let Some(payload) = boxed_payload(receiver, CLASS_ID_BOXED_BIGINT) { return payload; } @@ -270,8 +270,8 @@ fn bigint_receiver_or_throw(method: &str) -> f64 { /// ECMA-262 `thisStringValue(value)`: accept a String primitive or a boxed /// `String` object; throw `TypeError` for anything else, including `null`, /// `undefined`, numbers, booleans, symbols, and plain objects. -fn string_receiver_or_throw(method: &str) -> f64 { - let receiver = receiver_value(); +fn string_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> f64 { + let receiver = receiver_value(this); let jv = crate::value::JSValue::from_bits(receiver.to_bits()); // String primitive (heap or SSO inline). if jv.is_string() || jv.is_short_string() { @@ -295,51 +295,51 @@ fn string_value(ptr: *mut crate::string::StringHeader) -> f64 { pub(super) extern "C" fn number_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - number_receiver_or_throw("valueOf") + number_receiver_or_throw(this, "valueOf") } pub(super) extern "C" fn number_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, radix: f64, ) -> f64 { string_value(crate::value::js_jsvalue_to_string_radix( - number_receiver_or_throw("toString"), + number_receiver_or_throw(this, "toString"), radix, )) } pub(super) extern "C" fn number_proto_to_fixed_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, decimals: f64, ) -> f64 { string_value(crate::string::js_number_to_fixed( - number_receiver_or_throw("toFixed"), + number_receiver_or_throw(this, "toFixed"), decimals, )) } pub(super) extern "C" fn number_proto_to_precision_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, precision: f64, ) -> f64 { string_value(crate::string::js_number_to_precision( - number_receiver_or_throw("toPrecision"), + number_receiver_or_throw(this, "toPrecision"), precision, )) } pub(super) extern "C" fn number_proto_to_exponential_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, decimals: f64, ) -> f64 { string_value(crate::string::js_number_to_exponential( - number_receiver_or_throw("toExponential"), + number_receiver_or_throw(this, "toExponential"), decimals, )) } @@ -362,10 +362,10 @@ pub(super) extern "C" fn number_proto_to_exponential_thunk( /// (Intl builds a fresh instance per call; there is no formatter cache). pub(super) extern "C" fn number_proto_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let n = number_receiver_or_throw("toLocaleString"); + let n = number_receiver_or_throw(this, "toLocaleString"); #[cfg(feature = "intl-namespace")] { let args = super::global_this::global_this_rest_array_values(rest); @@ -385,16 +385,16 @@ pub(super) extern "C" fn number_proto_to_locale_string_thunk( pub(super) extern "C" fn boolean_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - boolean_receiver_or_throw("valueOf") + boolean_receiver_or_throw(this, "valueOf") } pub(super) extern "C" fn boolean_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let value = boolean_receiver_or_throw("toString"); + let value = boolean_receiver_or_throw(this, "toString"); let bytes = if crate::value::JSValue::from_bits(value.to_bits()).as_bool() { b"true".as_slice() } else { @@ -406,16 +406,16 @@ pub(super) extern "C" fn boolean_proto_to_string_thunk( pub(super) extern "C" fn symbol_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - symbol_receiver_or_throw("valueOf") + symbol_receiver_or_throw(this, "valueOf") } pub(super) extern "C" fn symbol_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let symbol = symbol_receiver_or_throw("toString"); + let symbol = symbol_receiver_or_throw(this, "toString"); let s = unsafe { crate::symbol::js_symbol_to_string(symbol) } as *mut crate::string::StringHeader; string_value(s) @@ -423,28 +423,28 @@ pub(super) extern "C" fn symbol_proto_to_string_thunk( pub(super) extern "C" fn bigint_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - bigint_receiver_or_throw("valueOf") + bigint_receiver_or_throw(this, "valueOf") } pub(super) extern "C" fn bigint_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, radix: f64, ) -> f64 { string_value(crate::value::js_jsvalue_to_string_radix( - bigint_receiver_or_throw("toString"), + bigint_receiver_or_throw(this, "toString"), radix, )) } pub(super) extern "C" fn bigint_proto_to_locale_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest: f64, ) -> f64 { - let value = bigint_receiver_or_throw("toLocaleString"); + let value = bigint_receiver_or_throw(this, "toLocaleString"); let args = super::global_this::global_this_rest_array_values(rest); let undef = f64::from_bits(crate::value::TAG_UNDEFINED); let _locales = args.first().copied().unwrap_or(undef); @@ -477,16 +477,16 @@ pub(super) extern "C" fn bigint_proto_to_locale_string_thunk( /// `TypeError` for any other receiver. pub(super) extern "C" fn string_proto_to_string_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - string_receiver_or_throw("toString") + string_receiver_or_throw(this, "toString") } /// `String.prototype.valueOf()` — brand-checked, identical semantics to /// `toString()`: returns the [[StringData]] of the receiver. pub(super) extern "C" fn string_proto_value_of_thunk( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - string_receiver_or_throw("valueOf") + string_receiver_or_throw(this, "valueOf") } diff --git a/crates/perry-runtime/src/object/property_key.rs b/crates/perry-runtime/src/object/property_key.rs index 0619395fdd..7af752df9f 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -129,14 +129,14 @@ unsafe fn ordinary_to_primitive_string_key(value: f64) -> Option { continue; } let bound = crate::closure::clone_closure_rebind_this(method_bits, receiver); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + // Re-read: the key allocation, the method read and the clone can all + // move the receiver. + let result = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if js_value_is_not_object(result) { return Some(result); } @@ -379,12 +379,7 @@ pub unsafe extern "C" fn js_super_accessor_get( // A static getter is a BARE body declaring no parameters; the // receiver passed below is ignored by it (over-application is safe). let f = crate::closure::body_call::js_bare_body_fn!(getter_ptr as *const u8; a0); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope - .root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let r = f(receiver); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return r; + return f(receiver); } } match crate::object::get_parent_class_id(cid) { @@ -551,15 +546,12 @@ pub unsafe extern "C" fn js_object_super_call( let bound = crate::closure::clone_closure_rebind_this(callee_handle.get_nanbox_u64(), receiver); let bound_handle = scope.root_nanbox_u64(bound); let receiver = f64::from_bits(receiver_handle.get_heap_word_u64()); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( f64::from_bits(bound_handle.get_nanbox_u64()), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args_ptr, args_len, - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + ) } #[cfg(test)] diff --git a/crates/perry-runtime/src/object/prototype_chain.rs b/crates/perry-runtime/src/object/prototype_chain.rs index 4ffc11b31f..f457330246 100644 --- a/crates/perry-runtime/src/object/prototype_chain.rs +++ b/crates/perry-runtime/src/object/prototype_chain.rs @@ -1165,14 +1165,9 @@ pub(crate) fn resolve_inherited_field_from_prototype( return None; } // `js_object_get_field_by_name` handles its own further prototype hops - // (recorded protos on the proto object), so this is the full walk. Bind - // accessor getters to the original receiver while walking inherited - // properties; otherwise prototype accessors would observe the prototype - // object instead of the instance. + // (recorded protos on the proto object), so this is the full walk. let receiver = f64::from_bits(crate::value::js_nanbox_pointer(obj_ptr as i64).to_bits()); let scope = crate::gc::RuntimeHandleScope::new(); - let previous_this = super::js_implicit_this_set(receiver); - let previous_this_handle = scope.root_nanbox_f64(previous_this); // The recursive `get_field(proto, key)` re-derives the accessor receiver // from `proto`; stash the real instance so an inherited getter binds `this` // to it, not to the prototype. @@ -1182,7 +1177,6 @@ pub(crate) fn resolve_inherited_field_from_prototype( super::field_get_set::accessor_receiver_override_end( prev_override_handle.map(|handle| handle.get_nanbox_f64()), ); - super::js_implicit_this_set(previous_this_handle.get_nanbox_f64()); if v.bits() == 0x7FFC_0000_0000_0001 { // undefined — treat as "not present" so callers fall back cleanly. None diff --git a/crates/perry-runtime/src/object/regex_proto_thunks.rs b/crates/perry-runtime/src/object/regex_proto_thunks.rs index 44006fa4d3..d710fa33ab 100644 --- a/crates/perry-runtime/src/object/regex_proto_thunks.rs +++ b/crates/perry-runtime/src/object/regex_proto_thunks.rs @@ -33,11 +33,11 @@ enum RegexReceiver { Prototype, } -/// Resolve `IMPLICIT_THIS` to a RegExp instance or `RegExp.prototype`, throwing +/// Resolve the `this` receiver to a RegExp instance or `RegExp.prototype`, throwing /// `TypeError` otherwise (matching the spec brand check shared by every /// flag/`source` getter). -fn regex_receiver_or_throw(getter: &str) -> RegexReceiver { - let receiver = crate::value::JSValue::from_bits(IMPLICIT_THIS.with(|c| c.get())); +fn regex_receiver_or_throw(this: crate::closure::JsThis, getter: &str) -> RegexReceiver { + let receiver = crate::value::JSValue::from_bits(this.bits()); if receiver.is_pointer() { let ptr = receiver.as_pointer::() as usize; if crate::regex::is_registered_regex(ptr) { @@ -78,8 +78,8 @@ fn regex_has_flag(re: *const crate::regex::RegExpHeader, flag: char) -> bool { /// Shared body for the boolean flag getters: regex → boolean, prototype → /// undefined, else TypeError. -fn flag_getter(getter: &str, flag: char) -> f64 { - match regex_receiver_or_throw(getter) { +fn flag_getter(this: crate::closure::JsThis, getter: &str, flag: char) -> f64 { + match regex_receiver_or_throw(this, getter) { RegexReceiver::Regex(re) => { f64::from_bits(crate::value::JSValue::bool(regex_has_flag(re, flag)).bits()) } @@ -89,58 +89,58 @@ fn flag_getter(getter: &str, flag: char) -> f64 { pub(super) extern "C" fn regex_proto_global_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("global", 'g') + flag_getter(this, "global", 'g') } pub(super) extern "C" fn regex_proto_ignore_case_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("ignoreCase", 'i') + flag_getter(this, "ignoreCase", 'i') } pub(super) extern "C" fn regex_proto_multiline_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("multiline", 'm') + flag_getter(this, "multiline", 'm') } pub(super) extern "C" fn regex_proto_dot_all_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("dotAll", 's') + flag_getter(this, "dotAll", 's') } pub(super) extern "C" fn regex_proto_sticky_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("sticky", 'y') + flag_getter(this, "sticky", 'y') } pub(super) extern "C" fn regex_proto_unicode_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("unicode", 'u') + flag_getter(this, "unicode", 'u') } pub(super) extern "C" fn regex_proto_unicode_sets_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("unicodeSets", 'v') + flag_getter(this, "unicodeSets", 'v') } pub(super) extern "C" fn regex_proto_has_indices_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - flag_getter("hasIndices", 'd') + flag_getter(this, "hasIndices", 'd') } pub(super) extern "C" fn regex_proto_source_getter( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - match regex_receiver_or_throw("source") { + match regex_receiver_or_throw(this, "source") { RegexReceiver::Regex(re) => { // `js_regexp_get_source` returns the *escaped* source string. let s = crate::regex::js_regexp_get_source(re); @@ -339,7 +339,7 @@ pub(super) extern "C" fn regex_proto_to_string_thunk( f64::from_bits(crate::js_nanbox_string(s as i64).to_bits()) } -/// Resolve `IMPLICIT_THIS` to a live RegExp instance (with `[[RegExpMatcher]]`), +/// Resolve the `this` receiver to a live RegExp instance (with `[[RegExpMatcher]]`), /// throwing `TypeError` otherwise. Unlike the flag/`source` getters, this does /// NOT treat `RegExp.prototype` specially — builtin exec requires a matcher. #[cfg(feature = "regex-engine")] @@ -883,7 +883,6 @@ pub(crate) fn regexp_get_property( as *const crate::regex::RegExpHeader; return crate::JSValue::from_bits(crate::regex::js_regexp_get_last_index(re).to_bits()); } - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let result = crate::exception::catch_js_throw(|| { if let Some(name) = &name { let addr = crate::value::js_nanbox_get_pointer(receiver.get_nanbox_f64()) as usize; @@ -911,7 +910,6 @@ pub(crate) fn regexp_get_property( }); crate::proxy::js_reflect_get(proto.get_nanbox_f64(), key, receiver.get_nanbox_f64()) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); drop(name); match result { Ok(value) => crate::JSValue::from_bits(value.to_bits()), diff --git a/crates/perry-runtime/src/object/string_proto_thunks.rs b/crates/perry-runtime/src/object/string_proto_thunks.rs index a931fffb58..7a46e0754d 100644 --- a/crates/perry-runtime/src/object/string_proto_thunks.rs +++ b/crates/perry-runtime/src/object/string_proto_thunks.rs @@ -248,7 +248,7 @@ fn throw_string_proto_nullish(method: &str) -> ! { } /// `RequireObjectCoercible(this)` + `ToString(this)` for the generic-`this` -/// String.prototype methods. `this` is the IMPLICIT_THIS receiver bound by +/// String.prototype methods. `this` is the receiver argument passed by /// `.call`/`.apply`/property dispatch. `null`/`undefined` throw `TypeError`; /// everything else (a primitive string, a boxed `String`/`Boolean`/`Number` /// object, a `{ toString }` object) coerces via the shared `js_string_coerce`, diff --git a/crates/perry-runtime/src/object/temporal_proto.rs b/crates/perry-runtime/src/object/temporal_proto.rs index 42b2604c98..c3fc98909f 100644 --- a/crates/perry-runtime/src/object/temporal_proto.rs +++ b/crates/perry-runtime/src/object/temporal_proto.rs @@ -44,7 +44,7 @@ fn throw_brand(member: &str) -> ! { } /// Shared accessor getter for every `Temporal..prototype` field. Reads -/// `IMPLICIT_THIS`, derives the property name from the closure (`"get hour"` → +/// its `this` argument, derives the property name from the closure (`"get hour"` → /// `"hour"`), and routes to the brand router; a non-Temporal `this` throws. pub(super) extern "C" fn temporal_proto_getter_thunk( c: *const crate::closure::ClosureHeader, @@ -60,7 +60,7 @@ pub(super) extern "C" fn temporal_proto_getter_thunk( } /// Shared method thunk for every `Temporal..prototype` method. Reads -/// `IMPLICIT_THIS`, derives the method name from the closure, brand-checks the +/// its `this` argument, derives the method name from the closure, brand-checks the /// receiver, and routes to the brand router with the rest-array args. pub(super) extern "C" fn temporal_proto_method_thunk( c: *const crate::closure::ClosureHeader, diff --git a/crates/perry-runtime/src/object/tests.rs b/crates/perry-runtime/src/object/tests.rs index ee67b06886..c4ca9130fc 100644 --- a/crates/perry-runtime/src/object/tests.rs +++ b/crates/perry-runtime/src/object/tests.rs @@ -112,12 +112,12 @@ extern "C" fn value_of_finite( extern "C" fn symbol_to_primitive_this_object( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, hint: f64, ) -> f64 { let hint_value = JSValue::from_bits(hint.to_bits()); assert_eq!(js_string_to_rust(hint_value), "number"); - crate::object::js_implicit_this_get() + this.as_f64() } extern "C" fn to_iso_string_sentinel( @@ -159,9 +159,9 @@ fn date_to_json_number_hint_honors_symbol_to_primitive() { crate::value::js_nanbox_pointer(value_of as i64), ); - let prev_this = js_implicit_this_set(receiver_value); - let result = catch_js(crate::object::date_proto_thunks::test_date_to_json_current_this); - js_implicit_this_set(prev_this); + let result = catch_js(|| { + crate::object::date_proto_thunks::test_date_to_json_with_this(receiver_value) + }); let result = result.expect("Date.prototype.toJSON should not throw"); assert!( @@ -200,9 +200,9 @@ fn date_to_json_symbol_to_primitive_object_result_throws() { crate::value::js_nanbox_pointer(to_iso as i64), ); - let prev_this = js_implicit_this_set(receiver_value); - let result = catch_js(crate::object::date_proto_thunks::test_date_to_json_current_this); - js_implicit_this_set(prev_this); + let result = catch_js(|| { + crate::object::date_proto_thunks::test_date_to_json_with_this(receiver_value) + }); assert!( result.is_err(), @@ -1320,12 +1320,12 @@ fn map_size_by_name_does_not_oob_read_keys_array() { } /// #7518: a `globalThis` built-in CONSTRUCTOR reached as a VALUE must never be -/// re-dispatched as a method name on `IMPLICIT_THIS`. +/// re-dispatched as a method name on the call's `this`. /// /// `try_dispatch_value_called_proto_method` exists for the #3716 uncurry-this /// idiom: a built-in *prototype method* invoked as a value arrives backed by the /// shared `global_this_builtin_noop_thunk`, so the helper recovers its recorded -/// `name` and re-dispatches `IMPLICIT_THIS.(…)` through the real by-name +/// `name` and re-dispatches `this.(…)` through the real by-name /// tower. Global constructors share that same no-op thunk, and the only thing /// keeping them out was incidental — they recorded no builtin `.length`. /// @@ -1333,8 +1333,8 @@ fn map_size_by_name_does_not_oob_read_keys_array() { /// `EventTarget.length` reads `0` like Node. That gave the EventTarget global a /// recorded length, opened the gate, and re-broke #6301: `class Bus extends /// EventTarget {}` has no static parent class id, so its `super()` runs the -/// parent VALUE through `js_fetch_or_value_super` — which binds `IMPLICIT_THIS` -/// to the new instance before the value call — and the helper turned that into +/// parent VALUE through `js_fetch_or_value_super` — which passes the new +/// instance as `this` to the value call — and the helper turned that into /// `bus.EventTarget()`, whose miss throws `TypeError: EventTarget is not a /// function`. `parity` is tag-gated, so the gap test that covers this sat red on /// `main` for a week unnoticed; this assertion lives in the per-PR `cargo-test` @@ -1353,10 +1353,11 @@ fn global_builtin_constructor_values_are_not_redispatched_by_name() { // PROCESS-global CLOSURE_PROPS table (#6965). let _global = crate::gc::global_side_table_test_lock(); // Give the pre-fix failure mode a real receiver to miss on, so a regression - // surfaces as a clean catchable throw rather than a dispatch on whatever - // `IMPLICIT_THIS` happened to hold. - let receiver = crate::value::js_nanbox_pointer(js_object_alloc(0, 0) as i64); - let prev_this = crate::object::js_implicit_this_set(receiver); + // surfaces as a clean catchable throw rather than a dispatch on an + // arbitrary receiver. + let root_scope = crate::gc::RuntimeHandleScope::new(); + let receiver = + root_scope.root_nanbox_f64(crate::value::js_nanbox_pointer(js_object_alloc(0, 0) as i64)); let mut with_recorded_length = 0usize; let mut offenders: Vec = Vec::new(); @@ -1373,7 +1374,12 @@ fn global_builtin_constructor_values_are_not_redispatched_by_name() { } let verdict = catch_js(|| { match unsafe { - crate::object::try_dispatch_value_called_proto_method(closure, std::ptr::null(), 0) + crate::object::try_dispatch_value_called_proto_method( + closure, + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), + std::ptr::null(), + 0, + ) } { None => 1.0, Some(_) => 0.0, @@ -1386,12 +1392,10 @@ fn global_builtin_constructor_values_are_not_redispatched_by_name() { } } - crate::object::js_implicit_this_set(prev_this); - assert!( offenders.is_empty(), "globalThis built-in constructors must not be value-dispatched as \ - `IMPLICIT_THIS.(…)`; offenders: {offenders:?}" + `this.(…)`; offenders: {offenders:?}" ); // Non-vacuity: the bug needs the no-op thunk PLUS a recorded spec `.length`. // If nothing in the table carries a length, every entry above declined at the diff --git a/crates/perry-runtime/src/object/this_binding.rs b/crates/perry-runtime/src/object/this_binding.rs index c41fce4c9c..1bb451d32a 100644 --- a/crates/perry-runtime/src/object/this_binding.rs +++ b/crates/perry-runtime/src/object/this_binding.rs @@ -5,52 +5,6 @@ use super::*; use std::cell::{Cell, RefCell}; -// Implicit `this` for closure-typed class fields invoked method-style. -// -// Issue #519: when `obj.fn(args)` calls a closure stored as a class field, -// the field-scan dispatch in `js_native_call_method` could not bind `this` -// through the closure ABI, which then had no `this` slot (it has one since -// this-as-a-parameter stage 1, `perry_abi::JS_BODY_THIS_PARAM`, but bodies -// still read this cell). Hono's RegExpRouter does this with `match = match` (the -// imported function from matcher.js), and the function body's -// `this.buildAllMatchers()` reads `this = 0` and TypeErrors out. -// -// Codegen for `Expr::This` (perry-codegen/src/expr.rs) reads from this -// thread-local when the lexical `this_stack` is empty (i.e. inside a -// non-arrow function body or top-level closure body). The field-scan -// dispatch saves the previous value, sets it to the receiver, calls the -// closure, then restores. Direct function calls (`fn(args)`) don't touch -// this slot, so non-method invocations don't pollute it across calls. -// -// Defaults to `TAG_UNDEFINED`. JS spec says top-level `this` is undefined -// in strict mode, which matches. -// The implicit-`this` cell itself lives INLINE in this thread's -// `tls_hot::HotTls` (`implicit_this`), so the save/restore pair around every -// dynamically-dispatched call — two calls per call — can be performed by -// generated code on Apple aarch64 without entering the runtime at all; the -// accessors below are the portable path and the fallback. -#[inline(always)] -fn implicit_this_cell() -> &'static Cell { - &crate::tls_hot::hot().implicit_this -} - -/// The `IMPLICIT_THIS.with(|cell| …)` shape the runtime's dispatch paths and -/// prototype thunks already use, re-backed by the inline `HotTls` field above -/// instead of a `perry_thread_local!` slot. Preserving the shape keeps the -/// move invisible at those ~120 call sites while making every one of them one -/// hop shorter — and it is the same cell generated code now reads and writes -/// directly on Apple aarch64. -pub(crate) struct ImplicitThisSlot; - -impl ImplicitThisSlot { - #[inline(always)] - pub(crate) fn with(&self, f: impl FnOnce(&Cell) -> R) -> R { - f(implicit_this_cell()) - } -} - -pub(crate) static IMPLICIT_THIS: ImplicitThisSlot = ImplicitThisSlot; - crate::perry_thread_local! { pub(crate) static NEW_TARGET: Cell = const { Cell::new(crate::value::TAG_UNDEFINED) }; // One-shot receiver override for STATIC method bodies. A compiled static @@ -65,7 +19,7 @@ crate::perry_thread_local! { static STATIC_THIS_OVERRIDE: Cell<(bool, u64)> = const { Cell::new((false, crate::value::TAG_UNDEFINED)) }; /// Lexical ClassDefinitionEvaluation owner for static method/accessor - /// dispatch. Unlike IMPLICIT_THIS, this is not replaced by `.call`'s + /// dispatch. Unlike the `this` argument, this is not replaced by `.call`'s /// visible receiver. A stack makes nested dispatch frame-local. static STATIC_PRIVATE_OWNER_STACK: RefCell> = const { RefCell::new(crate::exception::CatchStack::new( @@ -173,107 +127,12 @@ pub extern "C" fn js_static_this_resolve(default_this: f64) -> f64 { }) } -/// The implicit-`this` cell's raw bits: the receiver a JS body called right -/// now would read. `closure::JsThis::current` is the body-call funnel's -/// spelling of it (through `agent_ptrs` everywhere but Apple aarch64, where -/// this `HotTls` read is the fast path). -#[cfg(all( - target_vendor = "apple", - target_arch = "aarch64", - target_pointer_width = "64" -))] -#[inline(always)] -pub(crate) fn implicit_this_bits() -> u64 { - implicit_this_cell().get() -} - -static THIS_WITNESS_CHECKS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); -static THIS_WITNESS_MISMATCHES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); -static THIS_WITNESS_REPORT: std::sync::Once = std::sync::Once::new(); - -/// How many mismatches [`js_this_param_witness`] names individually before it -/// only counts. -const THIS_WITNESS_NAMED: u64 = 64; - -/// The this-as-a-parameter stage-1 witness: the prologue of every compiled -/// body that reads the implicit-`this` cell calls this with its `this` -/// PARAMETER (`perry_abi::JS_BODY_THIS_PARAM`) and its own name, and it -/// compares the two. Stage 1 requires that every caller passes exactly the -/// receiver the cell holds, so a mismatch names a caller that does not. -/// -/// Emitted ONLY by a build compiled with `PERRY_THIS_WITNESS=1` — a -/// COMPILE-time knob, so a product build contains no call and pays nothing. -/// Reported once, on stderr, at exit, as one `PERRY_THIS_WITNESS checks=N -/// mismatches=M` line; the first mismatches are also named as they happen. -/// A leaf: it never allocates on the JS heap and never collects. -#[no_mangle] -pub extern "C" fn js_this_param_witness(param_bits: u64, site: *const u8, site_len: u64) { - use std::sync::atomic::Ordering; - THIS_WITNESS_REPORT.call_once(|| unsafe { - libc::atexit(this_witness_report); - }); - THIS_WITNESS_CHECKS.fetch_add(1, Ordering::Relaxed); - let cell_bits = implicit_this_cell().get(); - if cell_bits == param_bits { - return; - } - let n = THIS_WITNESS_MISMATCHES.fetch_add(1, Ordering::Relaxed); - if n < THIS_WITNESS_NAMED { - let name = if site.is_null() { - "?".into() - } else { - String::from_utf8_lossy(unsafe { std::slice::from_raw_parts(site, site_len as usize) }) - }; - eprintln!( - "PERRY_THIS_WITNESS mismatch in {name}: this param={param_bits:#018x} cell={cell_bits:#018x}" - ); - } -} - -/// `(checks, mismatches)` so far (tests). -#[cfg(test)] -pub(crate) fn this_witness_counts() -> (u64, u64) { - use std::sync::atomic::Ordering; - ( - THIS_WITNESS_CHECKS.load(Ordering::Relaxed), - THIS_WITNESS_MISMATCHES.load(Ordering::Relaxed), - ) -} - -extern "C" fn this_witness_report() { - use std::sync::atomic::Ordering; - eprintln!( - "PERRY_THIS_WITNESS checks={} mismatches={}", - THIS_WITNESS_CHECKS.load(Ordering::Relaxed), - THIS_WITNESS_MISMATCHES.load(Ordering::Relaxed) - ); -} - -/// Keepalive anchor — `js_this_param_witness` is called only from generated -/// code of a witness build, so the auto-optimize whole-program build would -/// otherwise dead-strip it. -#[cfg(feature = "keepalive-anchors")] -#[used(compiler)] -static KEEP_JS_THIS_PARAM_WITNESS: extern "C" fn(u64, *const u8, u64) = js_this_param_witness; - /// Keepalive anchor — `js_this_coerce_sloppy` is called only from generated /// code (a sloppy body's receiver prologue). #[cfg(feature = "keepalive-anchors")] #[used(compiler)] static KEEP_JS_THIS_COERCE_SLOPPY: extern "C" fn(f64) -> f64 = js_this_coerce_sloppy; -/// Read the current implicit `this` (issue #519). -#[no_mangle] -pub extern "C" fn js_implicit_this_get() -> f64 { - f64::from_bits(implicit_this_cell().get()) -} - -/// Read implicit `this` using ordinary (non-strict) function binding rules. -#[no_mangle] -pub extern "C" fn js_implicit_this_get_sloppy() -> f64 { - js_this_coerce_sloppy(js_implicit_this_get()) -} - /// OrdinaryCallBindThis for a non-strict body: `undefined`/`null` become /// `globalThis`, a boolean/string/number its wrapper object; objects and /// class refs are unchanged. Emitted at the entry of a sloppy body that reads @@ -309,94 +168,6 @@ pub extern "C" fn js_this_coerce_sloppy(value: f64) -> f64 { value } -/// Set the implicit `this` and return the previous value. -/// Callers must restore the previous value to scope the binding to the -/// duration of a single method-style call. -/// -/// **The returned previous value is a GC-managed pointer held in a bare Rust -/// local** (#9417, #9445). It is the caller's receiver, and the call this -/// save/restore brackets is user code, so an evacuating young-gen minor inside -/// it moves that object and rewrites every slot the collector can see — a -/// Rust local is not one. Restoring the stale bits then installs a retired -/// from-space address as the caller's `this`, and the failure is silent: -/// `js_object_get_own_field_or_undef` fails its type check on the recycled cell -/// and answers `undefined`, so the caller's next `this.` throws a -/// TypeError naming a property nowhere near the defect. Every runtime site -/// therefore roots the saved value in a `RuntimeHandleScope` and re-reads it at -/// the restore: -/// -/// ```ignore -/// let this_scope = crate::gc::RuntimeHandleScope::new(); -/// let prev = this_scope.root_nanbox_f64(js_implicit_this_set(receiver)); -/// … user code … -/// js_implicit_this_set(prev.get_nanbox_f64()); -/// ``` -/// -/// or, when the restore must also run as the callee unwinds, the -/// [`ImplicitThisScope`] guard, which is that idiom with the restore in `Drop`. -/// -/// This is longjmp-safe: `exception.rs` saves and restores the handle stack at -/// trap boundaries, so a throw through the window truncates the scope exactly -/// as a normal drop would. Inside a loop, open the scope PER ITERATION (or -/// reuse an existing per-iteration scope) so the handle stack does not grow by -/// one slot per callback. -/// -/// This is part of every dynamically-dispatched call's save/restore path. -/// Keep the shipped path to one TLS replacement; default-off diagnostics here -/// still impose their mode checks millions of times on closure-heavy programs. -#[no_mangle] -pub extern "C" fn js_implicit_this_set(value: f64) -> f64 { - f64::from_bits(implicit_this_cell().replace(value.to_bits())) -} - -/// Bind `IMPLICIT_THIS` for the lifetime of the guard and restore the -/// displaced value on the way out — including when the callee unwinds, which -/// the runtime's `extern "C-unwind"` dispatch surfaces make an ordinary -/// outcome: a plain set/restore pair leaks the receiver into every later -/// implicit-`this` read once a callback throws (#9244). -/// -/// The displaced value is the CALLER's receiver, held across the user code the -/// guard brackets, so it lives in a slot of the borrowed `RuntimeHandleScope` -/// — marked, and rewritten when an evacuating minor moves it — and `Drop` -/// re-reads that slot. A guard that keeps it in a plain field is the #9445 -/// shape with the restore moved into `Drop`, where a sweep for -/// `let prev = js_implicit_this_set(..)` cannot see it: the private guards this -/// replaced reinstalled a retired from-space address as the caller's `this` -/// after `Object.setPrototypeOf(o, proto); o.run()` ran an allocating method -/// (#10490), and after every `Array.prototype` callback method. -/// -/// The borrow is what makes the order safe: the scope must be declared before -/// the guard, so the guard's `Drop` runs while its slot is still on the handle -/// stack. -pub struct ImplicitThisScope<'scope> { - previous: crate::gc::RuntimeHandle<'scope>, -} - -impl<'scope> ImplicitThisScope<'scope> { - #[inline] - pub fn bind(scope: &'scope crate::gc::RuntimeHandleScope, receiver: f64) -> Self { - Self { - previous: scope.root_nanbox_f64(js_implicit_this_set(receiver)), - } - } - - /// Bind `this` to `undefined`: OrdinaryCallBindThis for a callback the - /// runtime invokes with no receiver (`sort` comparators, `reduce` - /// callbacks, an absent `thisArg`). Without it the callee reads whatever - /// the enclosing method dispatch left in the cell (#11419). - #[inline] - pub fn bind_undefined(scope: &'scope crate::gc::RuntimeHandleScope) -> Self { - Self::bind(scope, f64::from_bits(crate::value::TAG_UNDEFINED)) - } -} - -impl Drop for ImplicitThisScope<'_> { - #[inline] - fn drop(&mut self) { - js_implicit_this_set(self.previous.get_nanbox_f64()); - } -} - /// Read the current `new.target` value for ordinary function bodies. #[no_mangle] pub extern "C" fn js_new_target_get() -> f64 { @@ -447,32 +218,11 @@ impl Drop for SuperNewTargetScope<'_> { } } -/// `catch_savepoints!` capture/restore for `IMPLICIT_THIS` (PR #10564 review -/// finding). Several runtime guards displace `IMPLICIT_THIS` around a call -/// they don't control — a `super()` bridge, a prototype-walk accessor -/// dispatch, a stdlib listener/getter dispatcher — with a bare -/// save/call/restore statement sequence, not `ImplicitThisScope`. Neither a -/// `longjmp` nor a system unwind runs the restore statement that follows the -/// call, so a throw crossing one of those sites leaves the callee's receiver -/// installed for every later implicit-`this` read. This closes that gap the -/// same way `runtime_handles`/`call_method` already do: captured at every `try`, -/// replayed by `js_throw` before the exception transports, regardless of -/// transport. It is an unconditional `set`, so it composes safely with a -/// `ImplicitThisScope::drop` that also fires on the unwind path: whichever -/// runs last for a given frame reproduces the same locally-correct value. -#[inline] -pub(crate) fn implicit_this_trap_savepoint() -> u64 { - implicit_this_cell().get() -} - -pub(crate) fn implicit_this_trap_restore(bits: u64) { - implicit_this_cell().set(bits); -} - -/// `catch_savepoints!` capture/restore for `NEW_TARGET`. Same rationale as -/// [`implicit_this_trap_savepoint`]: the Temporal/Intl subclass `super()` -/// bridges (`fetch_globals.rs`, `intl/subclass.rs`) save/restore `new.target` -/// with a bare statement pair around the parent constructor call. +/// `catch_savepoints!` capture/restore for `NEW_TARGET`: the Temporal/Intl +/// subclass `super()` bridges (`fetch_globals.rs`, `intl/subclass.rs`) +/// save/restore `new.target` with a bare statement pair around the parent +/// constructor call, which neither a `longjmp` nor a system unwind runs, so +/// every `try` captures it and `js_throw` replays it. #[inline] pub(crate) fn new_target_trap_savepoint() -> u64 { NEW_TARGET.with(|c| c.get()) @@ -482,37 +232,15 @@ pub(crate) fn new_target_trap_restore(bits: u64) { NEW_TARGET.with(|c| c.set(bits)); } -/// GC mutable-root scanner for the implicit-`this` cell (issue #1813). -/// -/// `IMPLICIT_THIS` holds the NaN-boxed receiver for the duration of a -/// dynamically-dispatched non-arrow method body — set then restored by -/// `js_native_call_method` and by the codegen `js_implicit_this_set` -/// save/restore around `js_native_call_value`. That receiver is a live -/// heap object for the whole call, but the cell is plain thread-local -/// storage, so before this scanner it was invisible to GC: not a root. -/// -/// When a moving GC runs *during* the method body — e.g. a nested stdlib -/// pump draining network IO for `@perryts/mysql`'s `Pool.acquire` → -/// handshake → `nativeScramble` under concurrent load — the receiver is -/// evacuated/copied. Without a root slot to rewrite, the cell kept the -/// stale pre-move pointer and the body's next `this`-derived dispatch -/// dereferenced freed/relocated memory: the concurrent-load SIGSEGV in -/// `js_native_call_method` reported in #1813. (It only surfaced under -/// memory pressure because nursery copying / old-gen evacuation only move -/// objects then — hence the load-dependent heisenbug.) -/// -/// Marking also keeps `this` reachable when the cell is its only root. -/// Non-pointer tags (the `TAG_UNDEFINED` default, plus null/int/bool) -/// flow through `visit_nanbox_bits` as no-ops, so scanning the idle cell -/// is safe. -pub fn scan_implicit_this_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { - { - let c = implicit_this_cell(); - let mut bits = c.get(); - if visitor.visit_nanbox_u64_slot(&mut bits) { - c.set(bits); - } - } +/// GC mutable-root scanner for the dispatch-binding cells: `new.target`, the +/// one-shot static-`this` override and the static private-owner stack. Each +/// holds a NaN-boxed heap value (a constructor, a receiver) for the duration +/// of a call, in plain thread-local storage the collector cannot see, so it +/// marks them and rewrites them when a moving collection relocates the value +/// (the #1813 class: a stale pre-move pointer read after a nested collection). +/// Non-pointer tags flow through `visit_nanbox_u64_slot` as no-ops, so an +/// idle cell is safe to scan. +pub fn scan_dispatch_binding_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { NEW_TARGET.with(|c| { let mut bits = c.get(); if visitor.visit_nanbox_u64_slot(&mut bits) { 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 5324d54c83..026f9ec0ad 100644 --- a/crates/perry-runtime/src/object/typed_array_proto_thunks.rs +++ b/crates/perry-runtime/src/object/typed_array_proto_thunks.rs @@ -17,7 +17,7 @@ //! regular Array helper, which (after the array-like-receiver change) silently //! accepted a plain array and returned a wrong-but-non-throwing result. //! -//! These thunks instead read the `IMPLICIT_THIS` receiver (set by the +//! These thunks instead take the receiver as their `this` argument (set by the //! `.call`/`.apply` dispatch), brand-check it via `lookup_typed_array_kind`, //! throw a `TypeError` on a non-TypedArray receiver, and otherwise delegate to //! the existing `dispatch_typed_array_method` tower (the same code the fast @@ -155,11 +155,14 @@ fn throw_not_typed_array(method: &str) -> ! { )) } -/// Read the `IMPLICIT_THIS` receiver and brand-check it as a real TypedArray. +/// Brand-check the `this` receiver as a real TypedArray. /// Returns the cleaned receiver, or throws a `TypeError`. #[inline] -unsafe fn ta_receiver_or_throw(method: &str) -> TypedArrayProtoReceiver { - let bits = IMPLICIT_THIS.with(|c| c.get()); +unsafe fn ta_receiver_or_throw( + this: crate::closure::JsThis, + method: &str, +) -> TypedArrayProtoReceiver { + let bits = this.bits(); // A TypedArray receiver reaches here in either of two boxings: a NaN-boxed // `POINTER_TAG` value (top16 >= 0x7FF8) or a *raw* heap pointer whose top16 // is 0 (the receiver-typed fast path threads the bare pointer — see the @@ -967,8 +970,8 @@ pub(crate) unsafe fn dispatch_uint8_buffer_method( /// `TYPED_ARRAY_PROTO_METHODS`; the `unwrap_or(undefined)` guard never fires in /// practice (the name set is kept in sync) but avoids a panic on drift. #[inline] -unsafe fn brand_then_dispatch(method: &str, args: &[f64]) -> f64 { - let receiver = ta_receiver_or_throw(method); +unsafe fn brand_then_dispatch(this: crate::closure::JsThis, method: &str, args: &[f64]) -> f64 { + let receiver = ta_receiver_or_throw(this, method); let args_ptr = if args.is_empty() { std::ptr::null() } else { @@ -1012,13 +1015,13 @@ macro_rules! ta_thunk { ($name:ident, $method:literal, $argc:literal) => { pub(super) extern "C" fn $name( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, a: f64, b: f64, d: f64, ) -> f64 { let all = [a, b, d]; - unsafe { brand_then_dispatch($method, &all[..$argc]) } + unsafe { brand_then_dispatch(this, $method, &all[..$argc]) } } }; } @@ -1053,37 +1056,37 @@ ta_thunk!(ta_with_thunk, "with", 2); pub(super) extern "C" fn ta_last_index_of_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, search_element: f64, rest: f64, ) -> f64 { unsafe { let args = first_arg_with_rest(search_element, rest); - brand_then_dispatch("lastIndexOf", &args) + brand_then_dispatch(this, "lastIndexOf", &args) } } pub(super) extern "C" fn ta_reduce_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { unsafe { let args = first_arg_with_rest(callback, rest); - brand_then_dispatch("reduce", &args) + brand_then_dispatch(this, "reduce", &args) } } pub(super) extern "C" fn ta_reduce_right_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, callback: f64, rest: f64, ) -> f64 { unsafe { let args = first_arg_with_rest(callback, rest); - brand_then_dispatch("reduceRight", &args) + brand_then_dispatch(this, "reduceRight", &args) } } @@ -1093,7 +1096,7 @@ pub(super) extern "C" fn ta_reduce_right_thunk( /// but keeps the install path total. pub(super) extern "C" fn ta_generic_thunk( c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, a: f64, b: f64, d: f64, @@ -1104,7 +1107,7 @@ pub(super) extern "C" fn ta_generic_thunk( let name = super::has_own_helpers::str_from_string_header(name_hdr).unwrap_or("TypedArray method"); let all = [a, b, d]; - brand_then_dispatch(name, &all) + brand_then_dispatch(this, name, &all) } } @@ -1139,13 +1142,13 @@ fn throw_not_uint8_array(method: &str) -> ! { )) } -/// Resolve the `IMPLICIT_THIS` receiver to a Uint8Array-backed buffer address. +/// Resolve the `this` receiver to a Uint8Array-backed buffer address. /// The base64/hex methods only exist on Uint8Array, so a multi-byte typed-array /// receiver (`Uint8Array.prototype.toBase64.call(new Int8Array())`) is rejected /// with a `TypeError` just like Node's `ValidateUint8Array`. #[inline] -unsafe fn uint8_receiver_or_throw(method: &str) -> usize { - match ta_receiver_or_throw(method) { +unsafe fn uint8_receiver_or_throw(this: crate::closure::JsThis, method: &str) -> usize { + match ta_receiver_or_throw(this, method) { TypedArrayProtoReceiver::Uint8Buffer(addr) => addr, TypedArrayProtoReceiver::TypedArray(_) => throw_not_uint8_array(method), } @@ -1153,13 +1156,13 @@ unsafe fn uint8_receiver_or_throw(method: &str) -> usize { pub(super) extern "C" fn u8_to_base64_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, opts: f64, _b: f64, _d: f64, ) -> f64 { unsafe { - let addr = uint8_receiver_or_throw("toBase64"); + let addr = uint8_receiver_or_throw(this, "toBase64"); let args = [opts]; super::dispatch_buffer_method(addr, "toBase64", args.as_ptr(), args.len()) } @@ -1167,26 +1170,26 @@ pub(super) extern "C" fn u8_to_base64_thunk( pub(super) extern "C" fn u8_to_hex_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _a: f64, _b: f64, _d: f64, ) -> f64 { unsafe { - let addr = uint8_receiver_or_throw("toHex"); + let addr = uint8_receiver_or_throw(this, "toHex"); super::dispatch_buffer_method(addr, "toHex", std::ptr::null(), 0) } } pub(super) extern "C" fn u8_set_from_base64_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, str_arg: f64, opts: f64, _d: f64, ) -> f64 { unsafe { - let addr = uint8_receiver_or_throw("setFromBase64"); + let addr = uint8_receiver_or_throw(this, "setFromBase64"); let args = [str_arg, opts]; super::dispatch_buffer_method(addr, "setFromBase64", args.as_ptr(), args.len()) } @@ -1194,13 +1197,13 @@ pub(super) extern "C" fn u8_set_from_base64_thunk( pub(super) extern "C" fn u8_set_from_hex_thunk( _c: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, str_arg: f64, _b: f64, _d: f64, ) -> f64 { unsafe { - let addr = uint8_receiver_or_throw("setFromHex"); + let addr = uint8_receiver_or_throw(this, "setFromHex"); let args = [str_arg]; super::dispatch_buffer_method(addr, "setFromHex", args.as_ptr(), args.len()) } diff --git a/crates/perry-runtime/src/object/util_types.rs b/crates/perry-runtime/src/object/util_types.rs index 4102eecaf8..14f64b05e3 100644 --- a/crates/perry-runtime/src/object/util_types.rs +++ b/crates/perry-runtime/src/object/util_types.rs @@ -435,7 +435,7 @@ mod buffer_view_tests { assert_eq!( super::super::global_this::array_buffer_is_view_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, value ) .to_bits(), diff --git a/crates/perry-runtime/src/object/view_brand_tests.rs b/crates/perry-runtime/src/object/view_brand_tests.rs index dd8c155532..79e28d8d01 100644 --- a/crates/perry-runtime/src/object/view_brand_tests.rs +++ b/crates/perry-runtime/src/object/view_brand_tests.rs @@ -34,7 +34,7 @@ fn row(value: f64) -> Row { is_view_direct: is_true(crate::object::js_util_types_is_array_buffer_view(value)), is_view_value: is_true(array_buffer_is_view_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::UNDEFINED, value, )), typed: is_true(crate::object::js_util_types_is_typed_array(value)), diff --git a/crates/perry-runtime/src/object/weakref_proto_thunks.rs b/crates/perry-runtime/src/object/weakref_proto_thunks.rs index 6a027983c9..6b3f213803 100644 --- a/crates/perry-runtime/src/object/weakref_proto_thunks.rs +++ b/crates/perry-runtime/src/object/weakref_proto_thunks.rs @@ -242,7 +242,7 @@ fn throw_incompatible(proto: &str, method: &str) -> ! { )) } -/// Resolve `IMPLICIT_THIS` to a receiver of the expected weak-wrapper class id, +/// Resolve the `this` receiver to a receiver of the expected weak-wrapper class id, /// or throw a `TypeError`. Mirrors `collection_proto_thunks`' /// `weak_receiver_or_throw` for the WeakRef/FinalizationRegistry pair. fn wrapper_receiver_or_throw( diff --git a/crates/perry-runtime/src/os_process_streams.rs b/crates/perry-runtime/src/os_process_streams.rs index 1f3c4e5595..9bb3717ed5 100644 --- a/crates/perry-runtime/src/os_process_streams.rs +++ b/crates/perry-runtime/src/os_process_streams.rs @@ -1447,18 +1447,16 @@ fn pump_stdin_data_chunks() { let scope = crate::gc::RuntimeHandleScope::new(); let cb_handle = scope.root_raw_const_ptr(cb as *const crate::closure::ClosureHeader); // Node calls stream listeners with `this === stream`. Re-read the - // singleton per listener and root the displaced receiver: the previous - // listener was user code, so either may have moved (#9445). + // singleton per listener: the previous listener was user code, so it + // may have moved (#9445). let this = stdin_this_value(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this), arg_handle.get_nanbox_f64(), ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } return; } @@ -1474,13 +1472,11 @@ fn pump_stdin_data_chunks() { for cb in readable_listeners { let scope = crate::gc::RuntimeHandleScope::new(); let cb_handle = scope.root_raw_const_ptr(cb as *const crate::closure::ClosureHeader); - // Per-listener re-read + rooted save/restore (#9445), as for `data`. + // Per-listener re-read of the receiver (#9445), as for `data`. let this = stdin_this_value(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); + crate::closure::js_closure_call0(closure, crate::closure::JsThis::from_f64(this)); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } @@ -1522,17 +1518,15 @@ fn maybe_fire_stdin_end() { let scope = crate::gc::RuntimeHandleScope::new(); let cb_handle = scope.root_raw_const_ptr(cb as *const crate::closure::ClosureHeader); - // Per-listener re-read + rooted save/restore (#9445), as for `data`. + // Per-listener re-read of the receiver (#9445), as for `data`. let this = stdin_this_value(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this), flush_handle.get_nanbox_f64(), ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } } @@ -1563,13 +1557,11 @@ fn maybe_fire_stdin_end() { for cb in end_listeners { let scope = crate::gc::RuntimeHandleScope::new(); let cb_handle = scope.root_raw_const_ptr(cb as *const crate::closure::ClosureHeader); - // Per-listener re-read + rooted save/restore (#9445), as for `data`. + // Per-listener re-read of the receiver (#9445), as for `data`. let this = stdin_this_value(); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(this)); cb_handle.with_const_ptr::(|closure| { - crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); + crate::closure::js_closure_call0(closure, crate::closure::JsThis::from_f64(this)); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } diff --git a/crates/perry-runtime/src/promise/assimilate.rs b/crates/perry-runtime/src/promise/assimilate.rs index 3d06d67085..91438909e1 100644 --- a/crates/perry-runtime/src/promise/assimilate.rs +++ b/crates/perry-runtime/src/promise/assimilate.rs @@ -390,17 +390,14 @@ extern "C" fn promise_resolve_thenable_job( let reject_value = crate::value::js_nanbox_pointer(reject_closure as i64); let args = [resolve_value, reject_value]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(thenable)); let result = combinator_catch_js(|| unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( then_action, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(thenable), args.as_ptr(), args.len(), ) }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if let Err(reason) = result { if thenable_job_take_guard(guard_arr) { js_promise_reject(promise, reason); @@ -492,20 +489,16 @@ pub(super) fn assimilate_via_then_property(value: f64) -> f64 { .with_mut_ptr::(|reject| crate::value::js_nanbox_pointer(reject as i64)); let args = [resolve_f64, reject_f64]; - // Bind `this` to the thenable so a non-arrow `then` body reads the right - // receiver, then call `Get(value, "then")` as a value (own data property). - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - value_handle.get_nanbox_f64(), - )); // #9445 + // Call `Get(value, "then")` as a value (own data property) with the + // thenable as its receiver, so a non-arrow `then` body reads the right `this`. unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( then_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); // Re-read the wrapper through its handle: the user `then` just ran and may // have relocated it (#9539). Returning `new_promise`'s pre-call address is diff --git a/crates/perry-runtime/src/promise/async_step.rs b/crates/perry-runtime/src/promise/async_step.rs index 7ec8e4d487..90e890fd11 100644 --- a/crates/perry-runtime/src/promise/async_step.rs +++ b/crates/perry-runtime/src/promise/async_step.rs @@ -1381,7 +1381,7 @@ fn array_from_async_call_next( // result so the iteration loop continues without going through .then. array_from_async_step( chain_closure as *const _, - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), next_result, ); } @@ -1515,10 +1515,11 @@ fn array_from_async_map_or_push(closure: *const crate::closure::ClosureHeader, v let this_arg = crate::closure::js_closure_get_capture_f64(closure, AFA_THIS_ARG); let args = [value, index]; let args_arr = crate::array::js_array_from_f64(args.as_ptr(), args.len() as u32); - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_arg)); - let mapped_promise = js_promise_try(map_fn, args_arr); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); + let mapped_promise = super::combinators::promise_try_this( + map_fn, + crate::closure::JsThis::from_f64(this_arg), + args_arr, + ); let mapped_closure = crate::closure::js_closure_get_capture_ptr(closure, AFA_MAPPED_CLOSURE) as *const crate::closure::ClosureHeader; @@ -1718,7 +1719,7 @@ mod tests { ]) }); - async_step_fulfill_thunk(thunk, crate::closure::body_call::current_this(), 41.0); + async_step_fulfill_thunk(thunk, crate::closure::plain_call_receiver(), 41.0); assert_eq!( (*captured_to).state, diff --git a/crates/perry-runtime/src/promise/checked_dispatch.rs b/crates/perry-runtime/src/promise/checked_dispatch.rs index 268487d88f..ee5e7576bf 100644 --- a/crates/perry-runtime/src/promise/checked_dispatch.rs +++ b/crates/perry-runtime/src/promise/checked_dispatch.rs @@ -50,18 +50,14 @@ pub extern "C" fn js_promise_then_checked( )); } let args = [on_fulfilled, on_rejected]; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = unsafe { - crate::closure::js_native_call_value( + return unsafe { + crate::closure::native_call_value_this( own_then, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(promise_val), args.as_ptr(), args.len(), ) }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; } if promise_has_own_constructor(promise_addr) || subclass::subclass_backing_promise(promise_val).is_some() @@ -69,16 +65,12 @@ pub extern "C" fn js_promise_then_checked( // Own `constructor` override OR a `class X extends Promise` instance: // route through the SpeciesConstructor-aware thunk, which unwraps the // backing cell and reads `this.constructor` for species chaining. - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_then_thunk( + return promise_prototype_then_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(promise_val), on_fulfilled, on_rejected, ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; } let promise = promise_addr as *mut Promise; box_promise_ptr(js_promise_then( @@ -95,15 +87,11 @@ pub extern "C" fn js_promise_catch_checked(promise_val: f64, on_rejected: f64) - || promise_has_own_constructor(promise_addr) || subclass::subclass_backing_promise(promise_val).is_some() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_catch_thunk( + return promise_prototype_catch_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(promise_val), on_rejected, ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; } let promise = promise_addr as *mut Promise; box_promise_ptr(js_promise_catch(promise, arg_to_closure(on_rejected))) @@ -116,15 +104,11 @@ pub extern "C" fn js_promise_finally_checked(promise_val: f64, on_finally: f64) || promise_has_own_constructor(promise_addr) || subclass::subclass_backing_promise(promise_val).is_some() { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(promise_val)); - let result = promise_prototype_finally_thunk( + return promise_prototype_finally_thunk( std::ptr::null(), - crate::closure::body_call::current_this(), + crate::closure::JsThis::from_f64(promise_val), on_finally, ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; } let promise = promise_addr as *mut Promise; box_promise_ptr(js_promise_finally(promise, arg_to_closure(on_finally))) diff --git a/crates/perry-runtime/src/promise/combinators.rs b/crates/perry-runtime/src/promise/combinators.rs index d57831632d..cf2a9d8c49 100644 --- a/crates/perry-runtime/src/promise/combinators.rs +++ b/crates/perry-runtime/src/promise/combinators.rs @@ -217,18 +217,18 @@ fn promise_try_closure_ptr(callback: f64) -> Option<*const crate::closure::Closu crate::closure::is_closure_ptr(ptr).then_some(ptr as *const crate::closure::ClosureHeader) } -fn promise_try_call(callback: f64, args_ptr: *const f64, args_len: usize) -> Result { +fn promise_try_call( + callback: f64, + this: crate::closure::JsThis, + args_ptr: *const f64, + args_len: usize, +) -> Result { let Some(closure) = promise_try_closure_ptr(callback) else { return Err(promise_try_type_error_value(callback)); }; crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - args_ptr, - args_len as i64, - ) + crate::closure::js_closure_call_array(closure as i64, this, args_ptr, args_len as i64) }) } @@ -238,6 +238,16 @@ fn promise_try_call(callback: f64, args_ptr: *const f64, args_len: usize) -> Res pub extern "C" fn js_promise_try( callback: f64, args: *const crate::array::ArrayHeader, +) -> *mut Promise { + promise_try_this(callback, crate::closure::plain_call_receiver(), args) +} + +/// `js_promise_try` calling `callback` with `this` as its receiver +/// (`Array.fromAsync`'s `thisArg` for the map function). +pub(crate) fn promise_try_this( + callback: f64, + this: crate::closure::JsThis, + args: *const crate::array::ArrayHeader, ) -> *mut Promise { let (args_ptr, args_len) = if args.is_null() { (std::ptr::null(), 0) @@ -249,7 +259,7 @@ pub extern "C" fn js_promise_try( (data, len) }; - match promise_try_call(callback, args_ptr, args_len) { + match promise_try_call(callback, this, args_ptr, args_len) { Ok(value) => js_promise_resolved(value), Err(reason) => js_promise_rejected(reason), } diff --git a/crates/perry-runtime/src/promise/microtasks.rs b/crates/perry-runtime/src/promise/microtasks.rs index 48a522ee9c..ad6dc7291e 100644 --- a/crates/perry-runtime/src/promise/microtasks.rs +++ b/crates/perry-runtime/src/promise/microtasks.rs @@ -1199,7 +1199,7 @@ fn call_async_step_direct( crate::closure::body_call::js_body_call!( func_ptr, step_closure, - crate::closure::body_call::current_this(), + crate::closure::plain_call_receiver(), value, is_error_bits ) diff --git a/crates/perry-runtime/src/promise/spec_combinators.rs b/crates/perry-runtime/src/promise/spec_combinators.rs index c8301afec5..a1712ae1c7 100644 --- a/crates/perry-runtime/src/promise/spec_combinators.rs +++ b/crates/perry-runtime/src/promise/spec_combinators.rs @@ -300,13 +300,8 @@ fn call_with_this(func: f64, this_arg: f64, args: &[f64]) -> Result { } else { (args.as_ptr(), args.len()) }; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_arg)); - let result = combinator_catch_js(|| unsafe { - crate::closure::js_native_call_value(func, crate::closure::plain_call_receiver(), ptr, len) - }); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + let this = crate::closure::JsThis::from_f64(this_arg); + combinator_catch_js(|| unsafe { crate::closure::native_call_value_this(func, this, ptr, len) }) } /// `Invoke(obj, "then", args)` catching exceptions into `Err`. diff --git a/crates/perry-runtime/src/promise/then.rs b/crates/perry-runtime/src/promise/then.rs index c6411c50a2..dbbc284cbb 100644 --- a/crates/perry-runtime/src/promise/then.rs +++ b/crates/perry-runtime/src/promise/then.rs @@ -1028,18 +1028,14 @@ fn call_receiver_then(receiver: f64, args: &[f64]) -> f64 { let err_val = crate::value::JSValue::pointer(err_ptr as *const u8).bits(); crate::exception::js_throw(f64::from_bits(err_val)); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( then_fn, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), args.as_ptr(), args.len(), ) - }; - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + } } fn throw_promise_finally_non_object() -> ! { @@ -1595,7 +1591,7 @@ pub(crate) extern "C" fn promise_prototype_finally_thunk( /// `then`/`catch`/`finally` value-read, or `None` for any other property. /// Returns the shared prototype method so `p.then === Promise.prototype.then` /// (spec-required identity) and `.call(receiver)` properly receives the caller's -/// `this` via IMPLICIT_THIS rather than a captured pointer. +/// `this` as its receiver parameter rather than a captured pointer. pub unsafe fn js_promise_bound_method(_promise: *mut Promise, property: &str) -> Option { if !matches!(property, "then" | "catch" | "finally") { return None; diff --git a/crates/perry-runtime/src/proxy.rs b/crates/perry-runtime/src/proxy.rs index ce5afe4863..44c9d2d543 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -20,8 +20,6 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; -use crate::closure::{js_closure_call0, js_closure_call1, js_closure_call2, js_closure_call3}; - mod apply_construct; pub use apply_construct::{call_proxy_value_with_this, js_proxy_apply, js_proxy_construct}; pub(crate) use apply_construct::{is_callable_function, is_constructor_function}; @@ -701,44 +699,31 @@ fn coerce_trap_bool(value: f64) -> f64 { /// Invoke a present (already-confirmed-callable) handler trap with the handler /// bound as the trap's `this` (ECMA-262: traps are called as /// `Call(trap, handler, args)`). Object-literal/method traps read `this` from a -/// reserved closure slot, while free-function traps fall back to -/// `IMPLICIT_THIS`; we set both so either style observes the handler. Mirrors +/// reserved closure slot, while free-function traps read the `this` +/// parameter; we supply both so either style observes the handler. Mirrors /// the apply/construct/getOwnPropertyDescriptor trap-call dance, which the /// per-trap paths (get/set/has/deleteProperty/defineProperty/…) previously /// skipped — they called the trap with the wrong `this` and, for get/set, /// dropped the trailing `receiver` argument. fn call_trap(handler: f64, trap: f64, args: &[f64]) -> f64 { - let rebound = crate::closure::clone_closure_rebind_this(trap.to_bits(), handler); + let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + let handler = this_scope.root_nanbox_f64(handler); + let rebound = + crate::closure::clone_closure_rebind_this(trap.to_bits(), handler.get_nanbox_f64()); let closure = closure_from(f64::from_bits(rebound)); if closure.is_null() { return throw_type_error("proxy trap is not a function"); } let undef = f64::from_bits(TAG_UNDEFINED); let a = |i: usize| -> f64 { args.get(i).copied().unwrap_or(undef) }; - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(handler)); - let result = match args.len() { - 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), - 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), - 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), - 3 => js_closure_call3( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - ), - _ => crate::closure::js_closure_call4( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - ), - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + let this = crate::closure::JsThis::from_f64(handler.get_nanbox_f64()); + match args.len() { + 0 => crate::closure::js_closure_call0(closure, this), + 1 => crate::closure::js_closure_call1(closure, this, a(0)), + 2 => crate::closure::js_closure_call2(closure, this, a(0), a(1)), + 3 => crate::closure::js_closure_call3(closure, this, a(0), a(1), a(2)), + _ => crate::closure::js_closure_call4(closure, this, a(0), a(1), a(2), a(3)), + } } /// Throw `TypeError: Reflect. called on non-object`. Does not return. @@ -1005,11 +990,11 @@ enum MovedElement<'a> { /// Invoke a callable `f64` value with the supplied positional args and an /// explicit `thisArg` binding, throwing `TypeError` if `f` is not callable. -/// Used by `Reflect.apply`. `thisArg` flows through `IMPLICIT_THIS` so free +/// Used by `Reflect.apply`. `thisArg` is passed as the `this` parameter so free /// functions reading `this` observe it. fn call_with_this_and_args(f: f64, this_arg: f64, args: &[f64]) -> f64 { // A concise/object-literal method reads `this` from a baked capture slot, - // not IMPLICIT_THIS; rebind to the explicit `Reflect.apply` receiver so it + // not only the `this` parameter; rebind to the explicit `Reflect.apply` receiver so it // is honored (no-op for arrows / plain fns / bound fns). // // That rebind is also the one thing on this path that ALLOCATES, and the @@ -1049,47 +1034,26 @@ fn dispatch_with_explicit_this(f: f64, this_arg: f64, args: &[f64]) -> f64 { if closure.is_null() { return throw_type_error("Reflect.apply target is not a function"); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(this_arg)); + let this = crate::closure::JsThis::from_f64(this_arg); let a = |i: usize| -> f64 { args.get(i) .copied() .unwrap_or(f64::from_bits(TAG_UNDEFINED)) }; - let result = match args.len() { - 0 => js_closure_call0(closure, crate::closure::plain_call_receiver()), - 1 => js_closure_call1(closure, crate::closure::plain_call_receiver(), a(0)), - 2 => js_closure_call2(closure, crate::closure::plain_call_receiver(), a(0), a(1)), - 3 => js_closure_call3( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - ), - 4 => crate::closure::js_closure_call4( - closure, - crate::closure::plain_call_receiver(), - a(0), - a(1), - a(2), - a(3), - ), + match args.len() { + 0 => crate::closure::js_closure_call0(closure, this), + 1 => crate::closure::js_closure_call1(closure, this, a(0)), + 2 => crate::closure::js_closure_call2(closure, this, a(0), a(1)), + 3 => crate::closure::js_closure_call3(closure, this, a(0), a(1), a(2)), + 4 => crate::closure::js_closure_call4(closure, this, a(0), a(1), a(2), a(3)), // #10425: this arm was `_ => js_closure_call4(…)`, so every argument // after the fourth was dropped. The variadic entry point owns // arbitrary-arity dispatch, rest bundling included. (`call_trap` // above keeps its catch-all: a proxy trap receives at most four.) n => unsafe { - crate::closure::js_closure_call_array( - closure as i64, - crate::closure::plain_call_receiver(), - args.as_ptr(), - n as i64, - ) + crate::closure::js_closure_call_array(closure as i64, this, args.as_ptr(), n as i64) }, - }; - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + } } /// Resolve the ultimate target when a Proxy wraps a class constructor. Used @@ -1728,15 +1692,19 @@ fn call_setter_with_receiver(setter_bits: u64, receiver: f64, value: f64) -> boo if setter_bits == 0 { return false; } - let rebound = crate::closure::clone_closure_rebind_this(setter_bits, receiver); + let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + let receiver = this_scope.root_nanbox_f64(receiver); + let value = this_scope.root_nanbox_f64(value); + let rebound = crate::closure::clone_closure_rebind_this(setter_bits, receiver.get_nanbox_f64()); let closure = closure_from(f64::from_bits(rebound)); if closure.is_null() { return false; } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let _ = js_closure_call1(closure, crate::closure::plain_call_receiver(), value); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); + let _ = crate::closure::js_closure_call1( + closure, + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), + value.get_nanbox_f64(), + ); true } diff --git a/crates/perry-runtime/src/proxy/apply_construct.rs b/crates/perry-runtime/src/proxy/apply_construct.rs index d2c8695c61..ae058c6286 100644 --- a/crates/perry-runtime/src/proxy/apply_construct.rs +++ b/crates/perry-runtime/src/proxy/apply_construct.rs @@ -92,10 +92,10 @@ pub(crate) fn is_constructor_function(value: f64) -> bool { /// Forward a `[[Call]]` to `target` (the default behavior when a proxy has no /// `apply` trap). If `target` is itself a proxy, recurse so its own trap chain /// runs; otherwise invoke the target through the canonical value-call path with -/// `this_arg` bound via `IMPLICIT_THIS`. Routing through `js_native_call_value` -/// (rather than calling the closure directly) also recovers built-in prototype -/// methods invoked as values — e.g. forwarding to `Object.prototype.hasOwnProperty` -/// re-dispatches by name with the receiver taken from `IMPLICIT_THIS`. +/// `this_arg` as its receiver. Routing through the value-call path (rather than +/// calling the closure directly) also recovers built-in prototype methods +/// invoked as values — e.g. forwarding to `Object.prototype.hasOwnProperty` +/// re-dispatches by name with `this_arg` as the receiver. fn forward_apply(target: f64, this_arg: f64, args_array: f64) -> f64 { if lookup(target).is_some() { return js_proxy_apply(target, this_arg, args_array); @@ -121,20 +121,16 @@ fn forward_apply(target: f64, this_arg: f64, args_array: f64) -> f64 { } else { (buf.as_ptr(), buf.len()) }; - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - this_arg.get_nanbox_f64(), - )); // Keep the native argument owner above the trap. A JS longjmp would // otherwise bypass Vec::drop on every throwing forwarded proxy call. let result = crate::exception::catch_js_throw(|| unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( target.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg.get_nanbox_f64()), ptr, n, ) }); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); drop(buf); match result { Ok(value) => value, @@ -155,7 +151,6 @@ pub fn call_proxy_value_with_this(proxy: f64, this_arg: f64, args: &[f64]) -> f6 let proxy_h = scope.root_nanbox_f64(proxy); let this_h = scope.root_nanbox_f64(this_arg); let arg_handles: Vec<_> = args.iter().map(|a| scope.root_nanbox_f64(*a)).collect(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let result = crate::exception::catch_js_throw(|| { let arr_h = scope .root_nanbox_u64(POINTER_TAG | (crate::array::js_array_alloc(0) as u64 & POINTER_MASK)); @@ -170,7 +165,6 @@ pub fn call_proxy_value_with_this(proxy: f64, this_arg: f64, args: &[f64]) -> f6 f64::from_bits(arr_h.get_nanbox_u64()), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); drop(arg_handles); match result { Ok(value) => value, @@ -243,26 +237,22 @@ pub extern "C" fn js_proxy_apply(proxy_boxed: f64, this_arg: f64, args_array: f6 } // Invoke the trap with the handler bound as `this` and the spec argument // list (target, thisArgument, argArray). Object-literal/free-function traps - // read `this` from a closure slot and/or the IMPLICIT_THIS fallback, so we - // set both — mirroring the `Reflect.get` accessor path. + // read `this` from a closure slot and/or the `this` parameter, so we + // supply both — mirroring the `Reflect.get` accessor path. let rebound = crate::closure::clone_closure_rebind_this(trap_bits, handler.get_nanbox_f64()); let closure = closure_from(f64::from_bits(rebound)); if closure.is_null() { return throw_type_error("proxy apply trap is not a function"); } - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - handler.get_nanbox_f64(), - )); let result = crate::exception::catch_js_throw(|| { js_closure_call3( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(handler.get_nanbox_f64()), target.get_nanbox_f64(), this_arg.get_nanbox_f64(), args_array.get_nanbox_f64(), ) }); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); match result { Ok(value) => value, Err(error) => crate::exception::js_throw(error), @@ -372,19 +362,15 @@ pub extern "C" fn js_proxy_construct(proxy_boxed: f64, args_array: f64, new_targ if closure.is_null() { return throw_type_error("proxy construct trap is not a function"); } - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - handler.get_nanbox_f64(), - )); let result = crate::exception::catch_js_throw(|| { js_closure_call3( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(handler.get_nanbox_f64()), target.get_nanbox_f64(), args_array.get_nanbox_f64(), nt.get_nanbox_f64(), ) }); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); match result { Ok(value) => value, Err(error) => crate::exception::js_throw(error), diff --git a/crates/perry-runtime/src/proxy/get.rs b/crates/perry-runtime/src/proxy/get.rs index 08c1ab2e3c..05f565455a 100644 --- a/crates/perry-runtime/src/proxy/get.rs +++ b/crates/perry-runtime/src/proxy/get.rs @@ -111,9 +111,6 @@ pub(crate) fn proxy_get_with_receiver(proxy_boxed: f64, key: f64, receiver: f64) // Ordinary target getters and further prototype hops must keep Receiver. // Clear/restore the override around the operation; getters and Proxy hops // consume it before entering user code so nested reads bind independently. - let previous_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - receiver.get_nanbox_f64(), - )); let previous_override = crate::object::accessor_receiver_override_begin(receiver.get_nanbox_f64()) .map(|value| scope.root_nanbox_f64(value)); @@ -121,6 +118,5 @@ pub(crate) fn proxy_get_with_receiver(proxy_boxed: f64, key: f64, receiver: f64) crate::object::accessor_receiver_override_end( previous_override.map(|value| value.get_nanbox_f64()), ); - crate::object::js_implicit_this_set(previous_this.get_nanbox_f64()); result } diff --git a/crates/perry-runtime/src/proxy/reflect.rs b/crates/perry-runtime/src/proxy/reflect.rs index 4dbce4e54e..efc8f866ee 100644 --- a/crates/perry-runtime/src/proxy/reflect.rs +++ b/crates/perry-runtime/src/proxy/reflect.rs @@ -1,9 +1,9 @@ use super::{ closure_from, coerce_trap_bool, extract_pointer, handler_trap, is_callable_function, - js_closure_call0, js_closure_call2, js_proxy_delete, js_proxy_get, js_proxy_has, lookup, - nanbox_bool, reflect_non_object_typeerror, reflect_ordinary_delete_property_key, - reflect_ordinary_set_with_receiver, reflect_value_is_object, revoked_return, - target_get_property_key, throw_type_error, PROXIES, TAG_NULL, TAG_TRUE, TAG_UNDEFINED, + js_proxy_delete, js_proxy_get, js_proxy_has, lookup, nanbox_bool, reflect_non_object_typeerror, + reflect_ordinary_delete_property_key, reflect_ordinary_set_with_receiver, + reflect_value_is_object, revoked_return, target_get_property_key, throw_type_error, PROXIES, + TAG_NULL, TAG_TRUE, TAG_UNDEFINED, }; /// `Reflect.get(target, key, receiver)` (#2766). @@ -51,7 +51,7 @@ pub extern "C" fn js_reflect_get(target: f64, key: f64, receiver: f64) -> f64 { } // #2766: if `key` resolves to an accessor *getter* on `target`, rebind its // `this` to the receiver and invoke it - object-literal getters capture - // `this` in a reserved closure slot (not `IMPLICIT_THIS`), so plain + // `this` in a reserved closure slot (not only the `this` parameter), so plain // forwarding would read the target's fields, not the receiver's. When the // receiver equals the target we can skip the clone and use the ordinary // read. @@ -76,22 +76,20 @@ pub extern "C" fn js_reflect_get(target: f64, key: f64, receiver: f64) -> f64 { crate::closure::clone_closure_rebind_this(getter.get_nanbox_f64().to_bits(), recv); let closure = closure_from(f64::from_bits(rebound)); if !closure.is_null() { - // Also set IMPLICIT_THIS for free-function getters that read - // `this` from the implicit-this fallback rather than a slot. - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + // Also pass the receiver as the `this` parameter for + // free-function getters that read it rather than a slot. let recv = if receiver_handle.get_nanbox_f64().to_bits() == TAG_UNDEFINED { target_handle.get_nanbox_f64() } else { receiver_handle.get_nanbox_f64() }; - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = js_closure_call0(closure, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - return result; + return crate::closure::js_closure_call0( + closure, + crate::closure::JsThis::from_f64(recv), + ); } } } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 let recv = if receiver_handle.get_nanbox_f64().to_bits() == TAG_UNDEFINED { target_handle.get_nanbox_f64() } else { @@ -109,13 +107,10 @@ pub extern "C" fn js_reflect_get(target: f64, key: f64, receiver: f64) -> f64 { ) }; } - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let result = target_get_property_key( + target_get_property_key( target_handle.get_nanbox_f64(), property_key_handle.get_nanbox_f64(), - ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + ) } /// `Reflect.set(target, key, value, receiver?)` - returns the boolean result @@ -444,17 +439,12 @@ pub extern "C" fn js_reflect_get_own_property_descriptor(target: f64, key: f64) if closure.is_null() { return throw_type_error("proxy getOwnPropertyDescriptor trap is not a function"); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set( - handler_handle.get_nanbox_f64(), - )); - let result = js_closure_call2( + let result = crate::closure::js_closure_call2( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(handler_handle.get_nanbox_f64()), inner_handle.get_nanbox_f64(), property_key_handle.get_nanbox_f64(), ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); let result_handle = scope.root_nanbox_f64(result); let target_desc = crate::object::js_object_get_own_property_descriptor( diff --git a/crates/perry-runtime/src/pty/mod.rs b/crates/perry-runtime/src/pty/mod.rs index 6334267c5c..84aeb2b3b5 100644 --- a/crates/perry-runtime/src/pty/mod.rs +++ b/crates/perry-runtime/src/pty/mod.rs @@ -51,8 +51,7 @@ mod platform_impl { 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::{js_native_call_value, ClosureHeader}; - use crate::object::js_implicit_this_set; + use crate::closure::{native_call_value_this, ClosureHeader, JsThis}; use crate::value::JSValue; // Shape-id band kept clear of cluster (0x7FFF_FC80) and child_process @@ -83,7 +82,6 @@ mod platform_impl { let scope = crate::gc::RuntimeHandleScope::new(); let target = scope.root_nanbox_f64(target); let args = scope.root_nanbox_f64_slice(args); - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let key = pty_listener_key(event); let Some(arr) = cp_array_ptr(cp_get_field(target.get_nanbox_f64(), &key)) else { return; @@ -95,17 +93,15 @@ mod platform_impl { .collect(); let callbacks = scope.root_nanbox_f64_slice(&callbacks); for cb in callbacks { - js_implicit_this_set(target.get_nanbox_f64()); let current_args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&args); unsafe { - let _ = js_native_call_value( + let _ = native_call_value_this( cb.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + JsThis::from_f64(target.get_nanbox_f64()), current_args.as_ptr(), current_args.len(), ); } - js_implicit_this_set(prev.get_nanbox_f64()); } } diff --git a/crates/perry-runtime/src/regex/perex_dispatch.rs b/crates/perry-runtime/src/regex/perex_dispatch.rs index 7f0027d372..f3435f0159 100644 --- a/crates/perry-runtime/src/regex/perex_dispatch.rs +++ b/crates/perry-runtime/src/regex/perex_dispatch.rs @@ -50,16 +50,11 @@ pub(super) fn is_regexp(value: &RuntimeHandle<'_>) -> Result } pub(crate) fn get(owner: &RuntimeHandle<'_>, name: &[u8]) -> Result { - let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - let result = api::caught(|| { + api::caught(|| { let key = crate::string::canonical_key(name); let value = owner.get_nanbox_f64(); crate::proxy::js_reflect_get(value, js_nanbox_string(key as i64), value) - }); - // Getter dispatch can throw through its own normal this-restoration path. - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - result + }) } pub(crate) fn call_one( @@ -68,9 +63,7 @@ pub(crate) fn call_one( argument: &RuntimeHandle<'_>, ) -> Result { let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - let result = api::caught(|| { - crate::object::js_implicit_this_set(receiver.get_nanbox_f64()); + api::caught(|| { if crate::proxy::js_proxy_is_proxy(method.get_nanbox_f64()) == 1 { // The generic value-call bridge drops this for proxies. Supply // the actual receiver and an exact one-element GC argument array. @@ -86,17 +79,15 @@ pub(crate) fn call_one( } else { let args = [argument.get_nanbox_f64()]; unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), args.as_ptr(), 1, ) } } - }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - result + }) } #[cfg(test)] @@ -208,25 +199,18 @@ pub(crate) fn to_string(value: &RuntimeHandle<'_>) -> Result<*mut StringHeader, "Cannot convert a Symbol value to a string", )); } - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - let result = - api::caught(|| crate::value::js_jsvalue_to_string_coerce(primitive.get_nanbox_f64())); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - let string = result?; + let string = + api::caught(|| crate::value::js_jsvalue_to_string_coerce(primitive.get_nanbox_f64()))?; crate::string::js_string_addref(string); Ok(string) } pub(crate) fn get_symbol(owner: &RuntimeHandle<'_>, name: &str) -> Result { - let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - let result = api::caught(|| { + api::caught(|| { let key = crate::symbol::well_known_symbol(name); let value = owner.get_nanbox_f64(); crate::proxy::js_reflect_get(value, js_nanbox_pointer(key as i64), value) - }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - result + }) } pub(crate) fn set_last_index(owner: &RuntimeHandle<'_>, value: f64) -> Result<(), EngineError> { @@ -244,7 +228,6 @@ pub(crate) fn set_last_index(owner: &RuntimeHandle<'_>, value: f64) -> Result<() super::js_regexp_set_last_index(re, value.get_nanbox_f64()); return Ok(()); } - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let result = api::caught(|| { let key = crate::string::canonical_key(b"lastIndex"); crate::proxy::js_reflect_set( @@ -254,7 +237,6 @@ pub(crate) fn set_last_index(owner: &RuntimeHandle<'_>, value: f64) -> Result<() owner.get_nanbox_f64(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); if crate::value::js_is_truthy(result?) != 0 { Ok(()) } else { @@ -277,10 +259,7 @@ pub(crate) fn to_number(value: &RuntimeHandle<'_>) -> Result { "Cannot convert a BigInt value to a number", )); } - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); - let result = api::caught(|| crate::builtins::js_number_coerce(primitive.get_nanbox_f64())); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - result + api::caught(|| crate::builtins::js_number_coerce(primitive.get_nanbox_f64())) } fn to_primitive(value: &RuntimeHandle<'_>, string_hint: bool) -> Result { @@ -376,7 +355,7 @@ pub(crate) fn test_string(receiver: f64, input: *const StringHeader) -> Result Result { @@ -384,9 +363,7 @@ pub(crate) fn test_value( let scope = RuntimeHandleScope::new(); let receiver = scope.root_nanbox_f64(receiver); let argument = scope.root_nanbox_f64(argument); - let previous = scope.root_nanbox_f64(this.as_f64()); let input = api::caught(|| crate::value::js_jsvalue_to_string_coerce(argument.get_nanbox_f64())); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); test_string(receiver.get_nanbox_f64(), input?) } diff --git a/crates/perry-runtime/src/regex/perex_replace_storage.rs b/crates/perry-runtime/src/regex/perex_replace_storage.rs index af8c222ff4..c377550db7 100644 --- a/crates/perry-runtime/src/regex/perex_replace_storage.rs +++ b/crates/perry-runtime/src/regex/perex_replace_storage.rs @@ -111,18 +111,14 @@ pub(super) fn call( args: &List<'_>, memory: &MemoryBudget, ) -> Result { - let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); if crate::proxy::js_proxy_is_proxy(method.get_nanbox_f64()) == 1 { - let result = api::caught(|| { + return api::caught(|| { crate::proxy::js_proxy_apply( method.get_nanbox_f64(), receiver.get_nanbox_f64(), args.value(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); - return result; } let mut slots = Vec::>::new(); slots @@ -146,15 +142,13 @@ pub(super) fn call( slots.capacity().checked_mul(8).ok_or(StorageError::Limit)?, )?; let result = api::caught(|| unsafe { - crate::object::js_implicit_this_set(receiver.get_nanbox_f64()); - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), slots.as_ptr().cast(), slots.len(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); drop(reservation); drop(frame); drop(slots); @@ -212,8 +206,6 @@ pub(super) fn call_native( crate::gc::js_shadow_frame_pop(self.0); } } - let scope = RuntimeHandleScope::new(); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let slots = &args.slots; for slot in slots { // SAFETY: nothing else holds a reference to these cells, and the @@ -232,15 +224,13 @@ pub(super) fn call_native( slots.len().checked_mul(8).ok_or(StorageError::Limit)?, )?; let result = api::caught(|| unsafe { - crate::object::js_implicit_this_set(receiver.get_nanbox_f64()); - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), slots.as_ptr().cast(), slots.len(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); drop(reservation); drop(frame); result diff --git a/crates/perry-runtime/src/regex/perex_substitution.rs b/crates/perry-runtime/src/regex/perex_substitution.rs index 062a0119fb..3a2b976a72 100644 --- a/crates/perry-runtime/src/regex/perex_substitution.rs +++ b/crates/perry-runtime/src/regex/perex_substitution.rs @@ -96,7 +96,6 @@ impl Substitution<'_, '_> { ) })??; let key = scope.root_string_ptr(key); - let previous = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); let groups = self.groups.unwrap(); let value = api::caught(|| { let key = key @@ -107,7 +106,6 @@ impl Substitution<'_, '_> { groups.get_nanbox_f64(), ) }); - crate::object::js_implicit_this_set(previous.get_nanbox_f64()); let value = scope.root_nanbox_f64(value?); if value.get_nanbox_f64().to_bits() != TAG_UNDEFINED { selected = Some(text(&scope, &value)?); diff --git a/crates/perry-runtime/src/regex/replace_fn.rs b/crates/perry-runtime/src/regex/replace_fn.rs index a4700e0cd2..d25e50d2c4 100644 --- a/crates/perry-runtime/src/regex/replace_fn.rs +++ b/crates/perry-runtime/src/regex/replace_fn.rs @@ -5,17 +5,12 @@ use super::*; use crate::value::js_nanbox_string; pub(super) unsafe fn call_replace_callback(callback: f64, args: &[f64]) -> String { - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(f64::from_bits( - crate::value::TAG_UNDEFINED, - ))); - let ret = crate::closure::js_native_call_value( + let ret = crate::closure::native_call_value_this( callback, crate::closure::plain_call_receiver(), args.as_ptr(), args.len(), ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); // §22.1.3.19 step "Let replacement be ? ToString(? Call(replaceValue, …))": // the callback result is ToString-coerced — `undefined` renders as // "undefined", a number stringifies, an object runs its `toString`, and a diff --git a/crates/perry-runtime/src/regex/site_test.rs b/crates/perry-runtime/src/regex/site_test.rs index e50b6af7fd..3e379b4238 100644 --- a/crates/perry-runtime/src/regex/site_test.rs +++ b/crates/perry-runtime/src/regex/site_test.rs @@ -318,17 +318,14 @@ fn call_value_at_site(site_key: usize, callee: f64, this_value: Option) -> 0 }; let this_value = this_value.map(|value| scope.root_nanbox_f64(value)); - let this_guard = this_value - .as_ref() - .map(|value| crate::object::ImplicitThisScope::bind(&scope, value.get_nanbox_f64())); let active = ActiveFactoryGuard::push(site_key, expected_identity); + let this = this_value + .as_ref() + .map_or(crate::closure::plain_call_receiver(), |value| { + crate::closure::JsThis::from_f64(value.get_nanbox_f64()) + }); let result = unsafe { - crate::closure::js_native_call_value( - callee.get_nanbox_f64(), - crate::closure::plain_call_receiver(), - std::ptr::null(), - 0, - ) + crate::closure::native_call_value_this(callee.get_nanbox_f64(), this, std::ptr::null(), 0) }; if !active.handled() { let reason = if lookup(site_key).is_some() { @@ -339,7 +336,6 @@ fn call_value_at_site(site_key: usize, callee: f64, this_value: Option) -> note_declined(reason); } drop(active); - drop(this_guard); result } @@ -479,12 +475,11 @@ fn site_test_dispatch_impl(receiver: f64, method: f64, argument: f64) -> f64 { } let method = scope.root_nanbox_f64(method); - let _this_guard = crate::object::ImplicitThisScope::bind(&scope, receiver.get_nanbox_f64()); let args = [argument.get_nanbox_f64()]; unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), args.as_ptr(), 1, ) diff --git a/crates/perry-runtime/src/set.rs b/crates/perry-runtime/src/set.rs index e5c931052a..fdef4a877a 100644 --- a/crates/perry-runtime/src/set.rs +++ b/crates/perry-runtime/src/set.rs @@ -272,7 +272,7 @@ pub fn is_registered_set(addr: usize) -> bool { /// (`TypeError` on a non-`Set` receiver) before dispatching. Mirrors the /// receiver extraction used by the `Array.prototype.slice` thunk: a /// NaN-boxed pointer, or a bare raw-i64 pointer some module-init call sites -/// stash in `IMPLICIT_THIS`. Primitives (undefined/null/number/string/bool) +/// pass as the receiver. Primitives (undefined/null/number/string/bool) /// carry a non-zero tag in the top 16 bits and resolve to `None`. pub fn set_ptr_from_receiver_bits(bits: u64) -> Option<*mut SetHeader> { let jsv = crate::value::JSValue::from_bits(bits); @@ -2116,8 +2116,6 @@ fn js_set_foreach_impl( // during the callback are visited too. Bounding this by the live count // surfaced holes and stopped before later live values (#9072). let mut i = 0usize; - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); loop { let set = set_handle.get_raw_const_ptr::(); if i >= (*set).used as usize { @@ -2139,15 +2137,8 @@ fn js_set_foreach_impl( } let args = [value, value, set_value]; let cb = callback_handle.get_nanbox_f64(); - let this_v = this_handle.get_nanbox_f64(); - crate::object::js_implicit_this_set(this_v); - let _ = crate::closure::js_native_call_value( - cb, - crate::closure::plain_call_receiver(), - args.as_ptr(), - args.len(), - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); + let this_v = crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()); + let _ = crate::closure::native_call_value_this(cb, this_v, args.as_ptr(), args.len()); } } let set = set_handle.get_raw_const_ptr::(); diff --git a/crates/perry-runtime/src/symbol/accessors.rs b/crates/perry-runtime/src/symbol/accessors.rs index 4d8ace6d90..831a538eea 100644 --- a/crates/perry-runtime/src/symbol/accessors.rs +++ b/crates/perry-runtime/src/symbol/accessors.rs @@ -281,13 +281,11 @@ pub(super) unsafe fn invoke_symbol_accessor_setter( } let scope = crate::gc::RuntimeHandleScope::new(); let value_h = scope.root_nanbox_f64(value); - let prev = scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); crate::closure::js_closure_call1( closure, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), value_h.get_nanbox_f64(), ); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); value_h.get_nanbox_f64() } @@ -299,11 +297,7 @@ pub(super) unsafe fn invoke_symbol_accessor_getter(get_bits: u64, receiver: f64) if closure.is_null() { return f64::from_bits(TAG_UNDEFINED); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + crate::closure::js_closure_call0(closure, crate::closure::JsThis::from_f64(receiver)) } pub(super) fn scan_symbol_accessor_roots_mut(visitor: &mut crate::gc::RuntimeRootVisitor<'_>) { diff --git a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs index 768e3e9398..72f709d2b8 100644 --- a/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs +++ b/crates/perry-runtime/src/symbol/inherited_accessor_tests.rs @@ -10,17 +10,17 @@ thread_local! { } /// Getter body: answers the `this` it was invoked with. -extern "C" fn this_getter(_closure: *const ClosureHeader, _this: crate::closure::JsThis) -> f64 { - crate::object::js_implicit_this_get() +extern "C" fn this_getter(_closure: *const ClosureHeader, this: crate::closure::JsThis) -> f64 { + this.as_f64() } /// Setter body: records `(this, value)`. extern "C" fn recording_setter( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let this = crate::object::js_implicit_this_get(); + let this = this.as_f64(); SETTER_CALL.with(|c| c.set(Some((this.to_bits(), value.to_bits())))); f64::from_bits(TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/symbol/iterator.rs b/crates/perry-runtime/src/symbol/iterator.rs index 6babfa587f..7dbb2399b6 100644 --- a/crates/perry-runtime/src/symbol/iterator.rs +++ b/crates/perry-runtime/src/symbol/iterator.rs @@ -262,16 +262,14 @@ pub extern "C" fn js_get_iterator(val_f64: f64) -> f64 { throw_value_not_iterable(val_f64); } let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(val_f64)); + let val_h = this_scope.root_nanbox_f64(val_f64); let rebound = crate::closure::clone_closure_rebind_this(iter_fn.to_bits(), val_f64); let rebound_ptr = crate::value::js_nanbox_get_pointer(f64::from_bits(rebound)) as *const crate::closure::ClosureHeader; let iter = crate::closure::js_closure_call0( rebound_ptr, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(val_h.get_nanbox_f64()), ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if !is_object_value(iter) { throw_iterator_result_not_object(); } @@ -446,6 +444,8 @@ pub extern "C" fn js_get_iterator(val_f64: f64) -> f64 { // and wraps the wrong value if `this` stays the prototype. Rebind // `this` to the original value; a no-op for closures that don't // capture `this`. + let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 + let val_h = this_scope.root_nanbox_f64(val_f64); let rebound = crate::closure::clone_closure_rebind_this(iter_fn.to_bits(), val_f64); let call_target = f64::from_bits(rebound); let fn_ptr = crate::value::js_nanbox_get_pointer(call_target) @@ -455,15 +455,13 @@ pub extern "C" fn js_get_iterator(val_f64: f64) -> f64 { // `[Symbol.iterator]()` factory runs with `this === obj`. The // `clone_closure_rebind_this` above covers a closure that // *captures* `this` (effect's prototype method); a plain - // `function(){ …this… }` factory reads `this` dynamically off - // IMPLICIT_THIS, so set it here too (test262 yield-star-sync-* - // asserts the `[Symbol.iterator]` call's thisValue === obj). - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = - this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(val_f64)); - let iter = - crate::closure::js_closure_call0(fn_ptr, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); + // `function(){ …this… }` factory reads its `this` parameter, + // so pass `obj` there too (test262 yield-star-sync-* asserts + // the `[Symbol.iterator]` call's thisValue === obj). + let iter = crate::closure::js_closure_call0( + fn_ptr, + crate::closure::JsThis::from_f64(val_h.get_nanbox_f64()), + ); // Several Perry host-backed collections expose iterator // helpers as eager arrays for direct `.entries()` parity. When // the same function is reached through `Symbol.iterator`, wrap @@ -654,23 +652,18 @@ pub unsafe extern "C" fn js_to_primitive(value: f64, hint: i32) -> f64 { // Bind `this` to the object for the call. A class-instance // `[Symbol.toPrimitive]` lives on the prototype and reads `this.field` // (`class Temperature { [Symbol.toPrimitive](h){ return this.celsius } }`), - // reading `this` dynamically off IMPLICIT_THIS; without an explicit receiver + // reading its `this` parameter; without an explicit receiver // `this.celsius` resolved to `undefined`, so `+t` was `NaN` and `` `${t}` `` // was `undefined°C` (test_gap_symbols). The proxy arm above already binds // `this`; mirror it for the closure method. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - value_handle.get_nanbox_f64(), - )); // Spec says the return value must be a primitive; if it's still an // object pointer, that's a TypeError in JS, but we just return it // as-is and let the caller fall back. - let result = crate::closure::js_closure_call1( + crate::closure::js_closure_call1( closure_ptr, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), hint_f64, - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + ) } #[cfg(test)] diff --git a/crates/perry-runtime/src/text.rs b/crates/perry-runtime/src/text.rs index b8127ff02f..19cb635e5f 100644 --- a/crates/perry-runtime/src/text.rs +++ b/crates/perry-runtime/src/text.rs @@ -787,8 +787,8 @@ static KEEP_TEXT_DECODER_IGNORE_BOM: extern "C" fn(f64) -> f64 = js_text_decoder // Prototype thunks. // // A native method or accessor installed on a shared prototype is a -// `ClosureHeader` whose ABI carries no receiver; it reads one from -// `js_implicit_this_get()`. `temporal_proto_getter_thunk` is the template. +// `ClosureHeader` body; it reads its receiver from its `this` parameter. +// `temporal_proto_getter_thunk` is the template. // Each thunk brand-checks its receiver against the family class id, so // `TextDecoder.prototype.decode.call({})` throws a TypeError exactly as node // does, instead of silently decoding as utf-8. diff --git a/crates/perry-runtime/src/thread.rs b/crates/perry-runtime/src/thread.rs index 65a848beae..cd613ef998 100644 --- a/crates/perry-runtime/src/thread.rs +++ b/crates/perry-runtime/src/thread.rs @@ -1210,11 +1210,7 @@ unsafe fn parallel_map_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn( - local_closure, - crate::closure::body_call::current_this(), - arg, - ); + let result = call_fn(local_closure, crate::closure::plain_call_receiver(), arg); results.push(serialize_nanbox_for_thread(result.to_bits())); } @@ -1299,7 +1295,7 @@ unsafe fn single_thread_map( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, crate::closure::body_call::current_this(), arg); + let result = call_fn(closure, crate::closure::plain_call_receiver(), arg); let result_arr = result_handle.get_raw_mut_ptr::(); // GC_STORE_AUDIT(BARRIERED): single-thread map result slot uses the shared array slot-store helper. store_thread_array_slot(result_arr, i, result.to_bits()); @@ -1467,11 +1463,7 @@ unsafe fn parallel_filter_impl(array_val: f64, closure_val: f64) -> i64 { .as_ref() .map(|h| h.get_raw_mut_ptr::() as *const ClosureHeader) .unwrap_or(ptr::null()); - let result = call_fn( - local_closure, - crate::closure::body_call::current_this(), - arg, - ); + let result = call_fn(local_closure, crate::closure::plain_call_receiver(), arg); let keep = is_truthy_bits(result.to_bits()); if keep { kept.push(serialize_nanbox_for_thread(arg.to_bits())); @@ -1551,7 +1543,7 @@ unsafe fn single_thread_filter( let closure = closure_handle .as_ref() .map_or(ptr::null(), |h| h.get_raw_const_ptr::()); - let result = call_fn(closure, crate::closure::body_call::current_this(), arg); + let result = call_fn(closure, crate::closure::plain_call_receiver(), arg); let keep = is_truthy_bits(result.to_bits()); if keep { let result_arr = result_handle.get_raw_mut_ptr::(); @@ -1692,7 +1684,7 @@ unsafe fn spawn_impl(closure_val: f64) -> *mut crate::promise::Promise { unsafe { crate::closure::body_call::js_body_fn!(func_usize as *const u8;) }; let local_closure = closure_handle.get_raw_mut_ptr::() as *const ClosureHeader; - call_fn(local_closure, crate::closure::body_call::current_this()) + call_fn(local_closure, crate::closure::plain_call_receiver()) })); match call_result { diff --git a/crates/perry-runtime/src/timer.rs b/crates/perry-runtime/src/timer.rs index d501d83e07..0bb1a686ed 100644 --- a/crates/perry-runtime/src/timer.rs +++ b/crates/perry-runtime/src/timer.rs @@ -465,19 +465,17 @@ fn call_timer_callback( let previous = crate::async_context::enter_context(context); let mut previous = previous; let previous_roots = crate::async_context::root_snapshot(&scope, &previous); + let this_handle = scope.root_nanbox_f64(timer_handle_value(id)); let a = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); let cb = callback_handle.get_raw_const_ptr::(); - let prev_this = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(timer_handle_value(id))); with_timer_uncaught_trap(|| unsafe { crate::closure::js_closure_call_array( cb as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), a.as_ptr(), a.len() as i64, ); }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); crate::async_context::refresh_snapshot_from_roots(&mut previous, &previous_roots); crate::async_context::restore_context(previous); } @@ -506,21 +504,19 @@ fn call_timer_callback_entry( let callback_handle = scope.root_raw_const_ptr(callback as *const crate::closure::ClosureHeader); let arg_handles = scope.root_nanbox_f64_slice(args); - let prev_this = - scope.root_nanbox_f64(crate::object::js_implicit_this_set(timer_handle_value(id))); + let this_handle = scope.root_nanbox_f64(timer_handle_value(id)); with_timer_uncaught_trap(|| { let a = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); let cb = callback_handle.get_raw_const_ptr::(); unsafe { crate::closure::js_closure_call_array( cb as i64, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), a.as_ptr(), a.len() as i64, ); } }); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } fn timer_delay_text(delay_ms: f64) -> String { diff --git a/crates/perry-runtime/src/tls_hot.rs b/crates/perry-runtime/src/tls_hot.rs index 39cb68b3e3..4013d12745 100644 --- a/crates/perry-runtime/src/tls_hot.rs +++ b/crates/perry-runtime/src/tls_hot.rs @@ -170,17 +170,11 @@ pub(crate) struct HotTls { // measurable part. Only small `Copy` values with a `const` initial state // belong here; anything needing `Drop` stays a slot. // ------------------------------------------------------------------ - /// `object::this_binding` — the implicit `this` of the current - /// dynamically-dispatched method call, NaN-boxed (`TAG_UNDEFINED` when - /// none). FIRST inline value on purpose: generated code on Apple - /// aarch64 reads and writes it at the fixed byte offset - /// [`HOT_TLS_IMPLICIT_THIS_OFFSET`] (see `hot_tls_layout_is_what_codegen_assumes`). - pub(crate) implicit_this: Cell, /// `agent_ptrs::PERRY_AGENT_PTRS` — this thread's per-agent pointer block. /// Generated code on Apple aarch64 reads it at the fixed byte offset /// [`HOT_TLS_AGENT_PTRS_OFFSET`] (`perry-codegen/src/expr/agent_ptr.rs`), - /// so it sits right after `implicit_this`, where only fixed-size fields - /// precede it: an array sized by a tunable constant (the prototype rows, + /// so it is the FIRST inline value, where only fixed-size fields precede + /// it: an array sized by a tunable constant (the prototype rows, /// the box caches, the generic slots) must never come before a field /// whose offset generated code bakes in. pub(crate) agent_ptrs: Cell<*mut u8>, @@ -222,14 +216,9 @@ pub(crate) struct HotTls { /// not emit the inline path there (ILP32 codegen is refused until #11378). #[cfg(target_pointer_width = "64")] pub const HOT_TLS_INLINE_STATE_OFFSET: usize = 8; -#[cfg(target_pointer_width = "64")] -pub const HOT_TLS_IMPLICIT_THIS_OFFSET: usize = 128; #[cfg(target_pointer_width = "32")] pub const HOT_TLS_INLINE_STATE_OFFSET: usize = 4; -#[cfg(target_pointer_width = "32")] -pub const HOT_TLS_IMPLICIT_THIS_OFFSET: usize = 64; const _: () = assert!(std::mem::offset_of!(HotTls, inline_state) == HOT_TLS_INLINE_STATE_OFFSET); -const _: () = assert!(std::mem::offset_of!(HotTls, implicit_this) == HOT_TLS_IMPLICIT_THIS_OFFSET); /// `HotTls::agent_ptrs` (`perry-abi`), read by generated code on Apple aarch64. pub use crate::codegen_abi::HOT_TLS_AGENT_PTRS_OFFSET; #[cfg(target_pointer_width = "64")] @@ -278,7 +267,6 @@ impl HotTls { shape_install_memo: std::ptr::null_mut(), learned_inline_fields: std::ptr::null_mut(), temp_roots: std::ptr::null_mut(), - implicit_this: Cell::new(crate::value::TAG_UNDEFINED), dirty_old_pages: [const { Cell::new(usize::MAX) }; 16], last_external_dirty_page: Cell::new(usize::MAX), last_external_dirty_header: Cell::new(usize::MAX), diff --git a/crates/perry-runtime/src/tty.rs b/crates/perry-runtime/src/tty.rs index 1464800312..95ce31c1ea 100644 --- a/crates/perry-runtime/src/tty.rs +++ b/crates/perry-runtime/src/tty.rs @@ -221,12 +221,12 @@ fn heap_object_ptr(value: f64) -> Option<*mut ObjectHeader> { } } -fn this_value() -> f64 { - crate::object::js_implicit_this_get() +fn this_value(this: crate::closure::JsThis) -> f64 { + this.as_f64() } -fn this_object() -> Option<*mut ObjectHeader> { - heap_object_ptr(this_value()) +fn this_object(this: crate::closure::JsThis) -> Option<*mut ObjectHeader> { + heap_object_ptr(this_value(this)) } fn fd_from_object(obj: *mut ObjectHeader) -> Option { @@ -241,12 +241,14 @@ fn fd_from_object(obj: *mut ObjectHeader) -> Option { None } -fn current_fd(default_fd: i32) -> i32 { - this_object().and_then(fd_from_object).unwrap_or(default_fd) +fn current_fd(default_fd: i32, this: crate::closure::JsThis) -> i32 { + this_object(this) + .and_then(fd_from_object) + .unwrap_or(default_fd) } -fn this_has_fd() -> bool { - this_object().and_then(fd_from_object).is_some() +fn this_has_fd(this: crate::closure::JsThis) -> bool { + this_object(this).and_then(fd_from_object).is_some() } fn value_to_string(value: f64) -> Option { @@ -910,16 +912,16 @@ pub(crate) fn is_tty_stream_instance(value: f64, class_name: &str) -> bool { #[no_mangle] pub extern "C" fn js_tty_read_stream_set_raw_mode( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, mode: f64, ) -> f64 { let enabled = crate::value::js_is_truthy(mode) != 0; - let fd = current_fd(0); + let fd = current_fd(0, this); let _ = set_fd_raw_mode(fd, enabled); - if let Some(obj) = this_object() { + if let Some(obj) = this_object(this) { crate::object::js_object_set_field_by_name(obj, named_key(b"isRaw"), js_bool(enabled)); } - this_value() + this_value(this) } fn write_all_fd(fd: i32, bytes: &[u8]) -> bool { @@ -965,14 +967,14 @@ fn numeric_arg(value: f64, default: i32) -> i32 { default } -fn write_control_sequence(sequence: String) -> f64 { - js_bool(write_all_fd(current_fd(1), sequence.as_bytes())) +fn write_control_sequence(sequence: String, this: crate::closure::JsThis) -> f64 { + js_bool(write_all_fd(current_fd(1, this), sequence.as_bytes())) } #[no_mangle] pub extern "C" fn js_tty_write_stream_cursor_to( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, x: f64, y: f64, _callback: f64, @@ -985,13 +987,13 @@ pub extern "C" fn js_tty_write_stream_cursor_to( let y = numeric_arg(y, 0).max(0); format!("\x1b[{};{}H", y + 1, x + 1) }; - write_control_sequence(sequence) + write_control_sequence(sequence, this) } #[no_mangle] pub extern "C" fn js_tty_write_stream_move_cursor( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, dx: f64, dy: f64, _callback: f64, @@ -1009,13 +1011,13 @@ pub extern "C" fn js_tty_write_stream_move_cursor( } else if dy > 0 { sequence.push_str(&format!("\x1b[{}B", dy)); } - write_control_sequence(sequence) + write_control_sequence(sequence, this) } #[no_mangle] pub extern "C" fn js_tty_write_stream_clear_line( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, dir: f64, _callback: f64, ) -> f64 { @@ -1024,16 +1026,16 @@ pub extern "C" fn js_tty_write_stream_clear_line( 1 => 0, _ => 2, }; - write_control_sequence(format!("\x1b[{}K", mode)) + write_control_sequence(format!("\x1b[{}K", mode), this) } #[no_mangle] pub extern "C" fn js_tty_write_stream_clear_screen_down( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, _callback: f64, ) -> f64 { - write_control_sequence("\x1b[0J".to_string()) + write_control_sequence("\x1b[0J".to_string(), this) } fn winsize_value(fd: i32) -> Option<(i32, i32)> { @@ -1055,9 +1057,9 @@ pub(crate) fn tty_write_stream_dimension(property: &str) -> Option { #[no_mangle] pub extern "C" fn js_tty_write_stream_get_window_size( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let fd = current_fd(1); + let fd = current_fd(1, this); let arr = crate::array::js_array_alloc(2); let (cols, rows) = winsize_value(fd).unwrap_or((0, 0)); let col_value = if cols > 0 { @@ -1078,9 +1080,9 @@ pub extern "C" fn js_tty_write_stream_get_window_size( #[no_mangle] pub extern "C" fn js_tty_write_stream_refresh_size( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let _ = winsize_value(current_fd(1)); + let _ = winsize_value(current_fd(1, this)); TAG_UNDEFINED_F64 } @@ -1096,45 +1098,45 @@ fn register_resize_callback(callback: i64) { #[no_mangle] pub extern "C" fn js_tty_write_stream_on( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, callback: f64, ) -> f64 { - if event_name_from_value(event).as_deref() == Some("resize") && this_has_fd() { + if event_name_from_value(event).as_deref() == Some("resize") && this_has_fd(this) { let callback = callback_ptr_from_value(callback); if callback != 0 { register_resize_callback(callback); } } - this_value() + this_value(this) } #[no_mangle] pub extern "C" fn js_tty_write_stream_remove_listener( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, _callback: f64, ) -> f64 { - if event_name_from_value(event).as_deref() == Some("resize") && this_has_fd() { + if event_name_from_value(event).as_deref() == Some("resize") && this_has_fd(this) { RESIZE_CALLBACK.with(|cb| *cb.borrow_mut() = None); } - this_value() + this_value(this) } #[no_mangle] pub extern "C" fn js_tty_write_stream_remove_all_listeners( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, event: f64, ) -> f64 { let event_js = JSValue::from_bits(event.to_bits()); if (event_js.is_undefined() || event_name_from_value(event).as_deref() == Some("resize")) - && this_has_fd() + && this_has_fd(this) { RESIZE_CALLBACK.with(|cb| *cb.borrow_mut() = None); } - this_value() + this_value(this) } pub fn throw_invalid_fd(fd: f64) -> ! { diff --git a/crates/perry-runtime/src/tui/handle_object.rs b/crates/perry-runtime/src/tui/handle_object.rs index 30fde83188..35cd7bbbf9 100644 --- a/crates/perry-runtime/src/tui/handle_object.rs +++ b/crates/perry-runtime/src/tui/handle_object.rs @@ -209,8 +209,7 @@ pub(crate) fn tui_widget_id_from_bits(bits: u64) -> i64 { } /// `(kind, id)` for a NaN-boxed JS value. The prototype thunks resolve their -/// receiver through this, since `js_implicit_this_get` hands back a boxed -/// value. +/// receiver (their `this` parameter, a boxed value) through this. fn tui_handle_parts_value(value: f64) -> Option<(TuiKind, i64)> { let bits = value.to_bits(); if (bits & crate::value::TAG_MASK) != crate::value::POINTER_TAG { diff --git a/crates/perry-runtime/src/typedarray/iterate.rs b/crates/perry-runtime/src/typedarray/iterate.rs index 39e97fb2b6..6157f1d002 100644 --- a/crates/perry-runtime/src/typedarray/iterate.rs +++ b/crates/perry-runtime/src/typedarray/iterate.rs @@ -282,10 +282,8 @@ pub extern "C" fn js_typed_array_reduce( has_initial: i32, initial: f64, ) -> f64 { - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback is a plain call, so it runs with `this` undefined, + // not the receiver of whatever method dispatch encloses this call. let ta = clean_ta_ptr(ta); if ta.is_null() { if has_initial != 0 { @@ -349,10 +347,8 @@ pub extern "C" fn js_typed_array_reduce_right( has_initial: i32, initial: f64, ) -> f64 { - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback is a plain call, so it runs with `this` undefined, + // not the receiver of whatever method dispatch encloses this call. let ta = clean_ta_ptr(ta); if ta.is_null() { if has_initial != 0 { diff --git a/crates/perry-runtime/src/typedarray/transform.rs b/crates/perry-runtime/src/typedarray/transform.rs index 39580a5257..4a2757034e 100644 --- a/crates/perry-runtime/src/typedarray/transform.rs +++ b/crates/perry-runtime/src/typedarray/transform.rs @@ -188,10 +188,8 @@ pub extern "C" fn js_typed_array_sort_with_comparator( if comparator.is_null() { return js_typed_array_sort_default(ta); } - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback is a plain call, so it runs with `this` undefined, + // not the receiver of whatever method dispatch encloses this call. let ta_clean = clean_ta_ptr(ta as *const TypedArrayHeader) as *mut TypedArrayHeader; if ta_clean.is_null() { return ta_clean; @@ -284,10 +282,8 @@ pub extern "C" fn js_typed_array_to_sorted_with_comparator( if comparator.is_null() { return js_typed_array_to_sorted_default(ta); } - // #11419: the callback runs with `this` undefined, not the receiver of - // whatever method dispatch encloses this call. - let this_scope = crate::gc::RuntimeHandleScope::new(); - let _this = crate::object::ImplicitThisScope::bind_undefined(&this_scope); + // #11419: the callback is a plain call, so it runs with `this` undefined, + // not the receiver of whatever method dispatch encloses this call. let ta = clean_ta_ptr(ta); if ta.is_null() { return typed_array_alloc(KIND_FLOAT64, 0); diff --git a/crates/perry-runtime/src/typedarray_props.rs b/crates/perry-runtime/src/typedarray_props.rs index 18e3ef7615..3cc8d16d71 100644 --- a/crates/perry-runtime/src/typedarray_props.rs +++ b/crates/perry-runtime/src/typedarray_props.rs @@ -236,11 +236,7 @@ fn invoke_typed_array_accessor_getter(get_bits: u64, receiver: f64) -> f64 { if closure.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_closure_call0(closure, crate::closure::plain_call_receiver()); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); - result + crate::closure::js_closure_call0(closure, crate::closure::JsThis::from_f64(receiver)) } fn invoke_typed_array_accessor_setter(set_bits: u64, receiver: f64, value: f64) { @@ -248,10 +244,7 @@ fn invoke_typed_array_accessor_setter(set_bits: u64, receiver: f64, value: f64) if closure.is_null() { return; } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - crate::closure::js_closure_call1(closure, crate::closure::plain_call_receiver(), value); - crate::object::js_implicit_this_set(prev.get_nanbox_f64()); + crate::closure::js_closure_call1(closure, crate::closure::JsThis::from_f64(receiver), value); } fn barrier_typed_array_own_props(owner: usize, props: &mut [TypedArrayOwnProp]) { diff --git a/crates/perry-runtime/src/url/prototype.rs b/crates/perry-runtime/src/url/prototype.rs index fdaf2a435f..f633578ad9 100644 --- a/crates/perry-runtime/src/url/prototype.rs +++ b/crates/perry-runtime/src/url/prototype.rs @@ -9,8 +9,8 @@ use super::parse::*; use super::url_class::*; use super::*; -fn require_url_receiver(name: &str) -> *mut ObjectHeader { - let this = crate::object::js_implicit_this_get(); +fn require_url_receiver(name: &str, this: crate::closure::JsThis) -> *mut ObjectHeader { + let this = this.as_f64(); if let Some(obj) = object_from_f64(this) { if is_url_object_shape(obj) { return obj; @@ -26,9 +26,9 @@ macro_rules! url_getter { ($fn_name:ident, $name:literal, $slot:expr) => { extern "C" fn $fn_name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, ) -> f64 { - let obj = require_url_receiver($name); + let obj = require_url_receiver($name, this); crate::object::js_object_get_field_f64(obj, $slot) } }; @@ -38,10 +38,10 @@ macro_rules! url_setter { ($fn_name:ident, $name:literal, $setter:path) => { extern "C" fn $fn_name( _closure: *const crate::closure::ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, value: f64, ) -> f64 { - let obj = require_url_receiver($name); + let obj = require_url_receiver($name, this); $setter(obj, value); f64::from_bits(crate::value::TAG_UNDEFINED) } diff --git a/crates/perry-runtime/src/url/search_params.rs b/crates/perry-runtime/src/url/search_params.rs index b5ddcc62ff..a34aa602d9 100644 --- a/crates/perry-runtime/src/url/search_params.rs +++ b/crates/perry-runtime/src/url/search_params.rs @@ -929,8 +929,9 @@ pub extern "C" fn js_url_search_params_for_each( let entries = get_url_search_params_entries(params); let this_value = crate::value::js_nanbox_pointer(params as i64); let this_scope = crate::gc::RuntimeHandleScope::new(); - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_get()); + // A callback can collect, so `thisArg` is rooted ONCE here and re-read at + // each call. + let this_arg_handle = this_scope.root_nanbox_f64(this_arg); for (key, value) in entries { let args = [ create_string_f64(&value), @@ -938,14 +939,12 @@ pub extern "C" fn js_url_search_params_for_each( this_value, ]; unsafe { - crate::object::js_implicit_this_set(this_arg); - let _ = crate::closure::js_native_call_value( + let _ = crate::closure::native_call_value_this( callback, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_arg_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } } } diff --git a/crates/perry-runtime/src/util_promisify.rs b/crates/perry-runtime/src/util_promisify.rs index d5df3ad0b6..e0d1dc6745 100644 --- a/crates/perry-runtime/src/util_promisify.rs +++ b/crates/perry-runtime/src/util_promisify.rs @@ -321,7 +321,7 @@ pub extern "C" fn js_util_callbackify(fn_value: f64) -> f64 { /// original under a setjmp trap so a sync `throw` rejects the promise. extern "C" fn outer_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest_value: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -332,6 +332,9 @@ extern "C" fn outer_thunk( js_closure_get_capture_f64(closure, 0) }; let fn_handle = scope.root_nanbox_f64(fn_value); + // Node calls `original` with the wrapper's own receiver + // (`ReflectApply(original, this, args)`). + let this_handle = scope.root_nanbox_f64(this.as_f64()); let promise_ptr = js_promise_new(); if promise_ptr.is_null() { @@ -406,7 +409,7 @@ extern "C" fn outer_thunk( unsafe { crate::closure::js_native_call_value( fn_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), data, n, ); @@ -462,7 +465,7 @@ extern "C" fn inner_callback_thunk( /// `customPromisifyArgs`) rather than just the public key. extern "C" fn gkp_outer_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest_value: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -473,6 +476,9 @@ extern "C" fn gkp_outer_thunk( js_closure_get_capture_f64(closure, 0) }; let fn_handle = scope.root_nanbox_f64(fn_value); + // Node calls `original` with the wrapper's own receiver + // (`ReflectApply(original, this, args)`). + let this_handle = scope.root_nanbox_f64(this.as_f64()); let promise_ptr = js_promise_new(); if promise_ptr.is_null() { @@ -540,7 +546,7 @@ extern "C" fn gkp_outer_thunk( unsafe { crate::closure::js_native_call_value( fn_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), data, n, ); @@ -628,7 +634,7 @@ extern "C" fn gkp_inner_callback_thunk( /// `rest_value`, emits one warning per wrapper, then forwards the call. extern "C" fn deprecate_outer_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest_value: f64, ) -> f64 { if closure.is_null() { @@ -640,6 +646,9 @@ extern "C" fn deprecate_outer_thunk( let msg = js_closure_get_capture_f64(closure, 1); let code = js_closure_get_capture_f64(closure, 2); let fn_handle = scope.root_nanbox_f64(fn_value); + // Node calls `original` with the wrapper's own receiver + // (`ReflectApply(original, this, args)`). + let this_handle = scope.root_nanbox_f64(this.as_f64()); let msg_handle = scope.root_nanbox_f64(msg); let code_handle = scope.root_nanbox_f64(code); let rest_handle = scope.root_nanbox_f64(rest_value); @@ -678,7 +687,7 @@ extern "C" fn deprecate_outer_thunk( unsafe { crate::closure::js_native_call_value( fn_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), rest_data, rest_len, ) @@ -691,7 +700,7 @@ extern "C" fn deprecate_outer_thunk( /// argument is forwarded to the original promise-returning function. extern "C" fn callbackify_outer_thunk( closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, rest_value: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); @@ -702,6 +711,9 @@ extern "C" fn callbackify_outer_thunk( js_closure_get_capture_f64(closure, 0) }; let fn_handle = scope.root_nanbox_f64(fn_value); + // Node calls `original` with the wrapper's own receiver + // (`ReflectApply(original, this, args)`). + let this_handle = scope.root_nanbox_f64(this.as_f64()); let rest_bits = rest_value.to_bits(); let rest_arr_ptr = if (rest_bits & TAG_MASK) == POINTER_TAG { @@ -743,7 +755,7 @@ extern "C" fn callbackify_outer_thunk( let data = crate::array::array_elements_ptr(arr as *const ArrayHeader) as *const f64; crate::closure::js_native_call_value( fn_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(this_handle.get_nanbox_f64()), data, original_arg_len, ) @@ -816,18 +828,14 @@ extern "C" fn callbackify_outer_thunk( let on_rejected = nanbox_pointer(rejected_handle.get_raw_const_ptr::() as *const u8); let args = [on_fulfilled, on_rejected]; - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set( - returned_handle.get_nanbox_f64(), - )); unsafe { - crate::closure::js_native_call_value( + crate::closure::native_call_value_this( then_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(returned_handle.get_nanbox_f64()), args.as_ptr(), args.len(), ); } - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); return TAG_UNDEFINED_F64; } diff --git a/crates/perry-runtime/src/value/to_string_array.rs b/crates/perry-runtime/src/value/to_string_array.rs index 5345fd4867..2031c95e14 100644 --- a/crates/perry-runtime/src/value/to_string_array.rs +++ b/crates/perry-runtime/src/value/to_string_array.rs @@ -83,15 +83,12 @@ pub(crate) unsafe fn array_prototype_to_string_override(value: f64) -> ArrayToSt let receiver = value_handle.get_nanbox_f64(); let bound = crate::closure::clone_closure_rebind_this(method_bits, receiver); let bound_handle = scope.root_nanbox_u64(bound); - let prev_this = crate::object::js_implicit_this_set(receiver); - let prev_this_handle = scope.root_nanbox_f64(prev_this); - let ret = crate::closure::js_native_call_value( + let ret = crate::closure::native_call_value_this( bound_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this_handle.get_nanbox_f64()); if is_primitive_value(ret) { ArrayToStringOutcome::Primitive(ret) } else { @@ -150,17 +147,13 @@ pub(crate) fn call_array_prototype_to_string_method( let rebound = crate::closure::rebind_explicit_this(method_handle.get_nanbox_f64(), receiver); let rebound_handle = scope.root_nanbox_f64(rebound); - let previous = crate::object::js_implicit_this_set(receiver); - let previous_handle = scope.root_nanbox_f64(previous); let args = crate::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(arg_handles); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( rebound_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver_handle.get_nanbox_f64()), args.as_ptr(), args.len(), - ); - crate::object::js_implicit_this_set(previous_handle.get_nanbox_f64()); - result + ) } } @@ -191,16 +184,12 @@ pub(crate) fn array_prototype_to_string(value: f64) -> f64 { let receiver = receiver_handle.get_nanbox_f64(); let rebound = crate::closure::rebind_explicit_this(join_handle.get_nanbox_f64(), receiver); let rebound_handle = scope.root_nanbox_f64(rebound); - let previous = crate::object::js_implicit_this_set(receiver); - let previous_handle = scope.root_nanbox_f64(previous); - let result = unsafe { - crate::closure::js_native_call_value( + unsafe { + crate::closure::native_call_value_this( rebound_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver_handle.get_nanbox_f64()), std::ptr::null(), 0, ) - }; - crate::object::js_implicit_this_set(previous_handle.get_nanbox_f64()); - result + } } diff --git a/crates/perry-runtime/src/value/to_string_class_ref.rs b/crates/perry-runtime/src/value/to_string_class_ref.rs index 6d7a896f12..de92fb21dc 100644 --- a/crates/perry-runtime/src/value/to_string_class_ref.rs +++ b/crates/perry-runtime/src/value/to_string_class_ref.rs @@ -63,16 +63,12 @@ pub(crate) unsafe fn custom_to_primitive(value: f64, hint: &[u8]) -> CustomToPri if !crate::closure::is_closure_ptr(method_ptr) { return CustomToPrimitiveOutcome::TypeError; } - let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let result = crate::closure::js_native_call_value( + crate::closure::native_call_value_this( method, - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver), &hint, 1, - ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); - result + ) }; if is_primitive_value(result) { diff --git a/crates/perry-runtime/src/value/to_string_primitive.rs b/crates/perry-runtime/src/value/to_string_primitive.rs index 6e6d3d738d..88d96b5702 100644 --- a/crates/perry-runtime/src/value/to_string_primitive.rs +++ b/crates/perry-runtime/src/value/to_string_primitive.rs @@ -175,8 +175,6 @@ pub(crate) unsafe fn ordinary_to_primitive_for_toprimitive( } else { [b"valueOf", b"toString"] }; - // #9445: the displaced receiver is rooted ONCE here, not once per callback. - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_get()); for name in order { let recv = value_handle.get_nanbox_f64(); // #10510: a runtime-spelled literal — reuse the interned key instead @@ -199,15 +197,12 @@ pub(crate) unsafe fn ordinary_to_primitive_for_toprimitive( return time; } let method_handle = scope.root_nanbox_f64(method); - let recv = value_handle.get_nanbox_f64(); - crate::object::js_implicit_this_set(recv); - let result = crate::closure::js_native_call_value( + let result = crate::closure::native_call_value_this( method_handle.get_nanbox_f64(), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); if is_primitive_value(result) { return result; } @@ -459,16 +454,15 @@ pub(crate) unsafe fn call_own_method(method: f64, receiver: f64) -> Option // Rebind `this` to the receiver — an assigned closure may have baked a // different value into its reserved `this` slot (an inherited or bound // method), exactly as the method-dispatch tower does (#1982). - let bound = crate::closure::clone_closure_rebind_this(bits, receiver); let this_scope = crate::gc::RuntimeHandleScope::new(); // #9445 - let prev_this = this_scope.root_nanbox_f64(crate::object::js_implicit_this_set(receiver)); - let ret = crate::closure::js_native_call_value( + let receiver = this_scope.root_nanbox_f64(receiver); + let bound = crate::closure::clone_closure_rebind_this(bits, receiver.get_nanbox_f64()); + let ret = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(receiver.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); Some(ret) } @@ -639,14 +633,12 @@ unsafe fn call_method_for_primitive( // receiver, so rebinding is a correct no-op. Mirrors #1982. let recv = value_handle.get_nanbox_f64(); let bound = crate::closure::clone_closure_rebind_this(method_bits, recv); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let ret = crate::closure::js_native_call_value( + let ret = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); let ret_jsv = JSValue::from_bits(ret.to_bits()); let is_primitive = ret_jsv.is_any_string() || ret_jsv.is_number() @@ -699,14 +691,12 @@ unsafe fn call_function_method( let method_handle = scope.root_nanbox_f64(method); let bound = crate::closure::clone_closure_rebind_this(method_handle.get_nanbox_u64(), recv); - let prev_this = scope.root_nanbox_f64(crate::object::js_implicit_this_set(recv)); - let ret = crate::closure::js_native_call_value( + let ret = crate::closure::native_call_value_this( f64::from_bits(bound), - crate::closure::plain_call_receiver(), + crate::closure::JsThis::from_f64(value_handle.get_nanbox_f64()), std::ptr::null(), 0, ); - crate::object::js_implicit_this_set(prev_this.get_nanbox_f64()); FunctionMethodOutcome::Value(ret) } diff --git a/crates/perry-runtime/src/wasi.rs b/crates/perry-runtime/src/wasi.rs index 5332041eb7..8827ffc69a 100644 --- a/crates/perry-runtime/src/wasi.rs +++ b/crates/perry-runtime/src/wasi.rs @@ -782,12 +782,12 @@ pub extern "C" fn js_wasi_get_import_object( #[no_mangle] pub extern "C" fn js_wasi_start( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, instance: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_nanbox_f64(instance); - let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw()); + let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw(this)); ensure_wasi_not_started(wasi.get_nanbox_f64()); let import = scope.root_nanbox_f64(wasi_import_or_throw(wasi.get_nanbox_f64())); let memory = scope.root_nanbox_f64(instance_export(instance.get_nanbox_f64(), b"memory")); @@ -833,12 +833,12 @@ pub extern "C" fn js_wasi_start( #[no_mangle] pub extern "C" fn js_wasi_initialize( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, instance: f64, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); let instance = scope.root_nanbox_f64(instance); - let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw()); + let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw(this)); ensure_wasi_not_started(wasi.get_nanbox_f64()); let import = scope.root_nanbox_f64(wasi_import_or_throw(wasi.get_nanbox_f64())); let memory = scope.root_nanbox_f64(instance_export(instance.get_nanbox_f64(), b"memory")); @@ -875,7 +875,7 @@ pub extern "C" fn js_wasi_initialize( #[no_mangle] pub extern "C" fn js_wasi_finalize_bindings( _closure: *const ClosureHeader, - _this: crate::closure::JsThis, + this: crate::closure::JsThis, instance: f64, rest: f64, ) -> f64 { @@ -886,7 +886,10 @@ pub extern "C" fn js_wasi_finalize_bindings( // accepting the optional second argument. let options = rest_argument(rest, 0); let override_memory = scope.root_nanbox_f64(finalize_memory_option(options)); - let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw_with_code("ERR_INVALID_ARG_TYPE")); + let wasi = scope.root_nanbox_f64(wasi_receiver_or_throw_with_code( + "ERR_INVALID_ARG_TYPE", + this, + )); ensure_wasi_not_started(wasi.get_nanbox_f64()); let import = scope.root_nanbox_f64(wasi_import_or_throw(wasi.get_nanbox_f64())); let exported_memory = @@ -903,12 +906,12 @@ pub extern "C" fn js_wasi_finalize_bindings( undefined() } -fn wasi_receiver_or_throw() -> f64 { - wasi_receiver_or_throw_with_code("") +fn wasi_receiver_or_throw(this: crate::closure::JsThis) -> f64 { + wasi_receiver_or_throw_with_code("", this) } -fn wasi_receiver_or_throw_with_code(code: &'static str) -> f64 { - let this = crate::object::js_implicit_this_get(); +fn wasi_receiver_or_throw_with_code(code: &'static str, this: crate::closure::JsThis) -> f64 { + let this = this.as_f64(); if heap_object_ptr(this).is_none() { type_error_with_code("Value of \"this\" must be of type WASI", code); } diff --git a/crates/perry-stdlib/src/domain.rs b/crates/perry-stdlib/src/domain.rs index 5511d51945..89f5474af7 100644 --- a/crates/perry-stdlib/src/domain.rs +++ b/crates/perry-stdlib/src/domain.rs @@ -295,27 +295,19 @@ unsafe fn emit_domain_event(handle: Handle, event: &str, args: &[f64]) -> bool { // moving minor collection; an unrooted copy then holds a retired // from-space address for the NEXT listener in this same loop. Root both // through one handle scope and re-read the current bits before every - // call. - // - // #10490: the displaced `this` is the caller's receiver and every listener - // is user code that can move it too. Root it ONCE, in that same scope, - // before the first `js_implicit_this_set`, and restore it from that root - // rather than from a plain per-iteration local. + // call. Each listener runs with the domain as `this`. let scope = perry_runtime::gc::RuntimeHandleScope::new(); - let previous_this = scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_get()); let listener_handles = scope.root_nanbox_f64_slice(&listeners); let arg_handles = scope.root_nanbox_f64_slice(args); for listener_handle in &listener_handles { let live_args = perry_runtime::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); - perry_runtime::object::js_implicit_this_set(receiver); let _ = perry_runtime::closure::js_native_call_value( listener_handle.get_nanbox_f64(), - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(receiver), live_args.as_ptr(), live_args.len(), ); - perry_runtime::object::js_implicit_this_set(previous_this.get_nanbox_f64()); } true } diff --git a/crates/perry-stdlib/src/events.rs b/crates/perry-stdlib/src/events.rs index c93d8f93d3..8d5653af33 100644 --- a/crates/perry-stdlib/src/events.rs +++ b/crates/perry-stdlib/src/events.rs @@ -1048,18 +1048,13 @@ unsafe fn call_emitter_listener( arr_handle.get_raw_mut_ptr::() as i64, ); } - // #10490: root the displaced `this` across the listener (user code). - let this_scope = perry_runtime::gc::RuntimeHandleScope::new(); - let previous_this = - this_scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_set(receiver)); - let result = perry_runtime::closure::js_native_call_value( + // A listener runs with the emitter as `this`. + perry_runtime::closure::js_native_call_value( callback_value, - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(receiver), args.as_ptr(), args.len(), - ); - perry_runtime::object::js_implicit_this_set(previous_this.get_nanbox_f64()); - result + ) } const TAG_UNDEFINED_F64_BITS: u64 = 0x7FFC_0000_0000_0001; diff --git a/crates/perry-stdlib/src/events/warnings.rs b/crates/perry-stdlib/src/events/warnings.rs index 3b77d20ffc..7df3e3229f 100644 --- a/crates/perry-stdlib/src/events/warnings.rs +++ b/crates/perry-stdlib/src/events/warnings.rs @@ -53,26 +53,19 @@ unsafe fn emit_warning(warning: f64) { // allocate in its own dispatch preamble before it reads the // callee/args, which would otherwise leave these pointing at a // retired from-space address by the time they're dereferenced. - // - // #10490: the displaced `this` crosses the (user-replaceable) - // `process.emitWarning`, so root it in that same scope and - // restore it from the root, not from a plain local. + // `process.emitWarning` is called as a method of `process`. let scope = perry_runtime::gc::RuntimeHandleScope::new(); let callback_h = scope.root_nanbox_f64(emit_warning); let process_h = scope.root_nanbox_f64(process); let arg_handles = scope.root_nanbox_f64_slice(&[warning]); - let previous_this = scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_set( - process_h.get_nanbox_f64(), - )); let live_args = perry_runtime::gc::RuntimeHandleScope::refreshed_nanbox_f64_slice(&arg_handles); perry_runtime::closure::js_native_call_value( callback_h.get_nanbox_f64(), - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(process_h.get_nanbox_f64()), live_args.as_ptr(), live_args.len(), ); - perry_runtime::object::js_implicit_this_set(previous_this.get_nanbox_f64()); return; } } diff --git a/crates/perry-stdlib/src/streams.rs b/crates/perry-stdlib/src/streams.rs index fc9adb3959..0e4479a9e5 100644 --- a/crates/perry-stdlib/src/streams.rs +++ b/crates/perry-stdlib/src/streams.rs @@ -96,10 +96,6 @@ extern "C" { arguments: *const f64, argument_count: usize, ) -> f64; - #[link_name = "js_implicit_this_set"] - fn provider_js_implicit_this_set(value: f64) -> f64; - #[link_name = "js_implicit_this_get"] - fn provider_js_implicit_this_get() -> f64; #[link_name = "js_ffi_root_scope_enter"] fn provider_js_ffi_root_scope_enter() -> usize; #[link_name = "js_ffi_root_push_nanbox"] @@ -182,27 +178,16 @@ fn js_closure_alloc(function: *const u8, capture_count: u32) -> *mut ClosureHead provider_call!(provider_js_closure_alloc(function, capture_count)) } -// Stage 1 of this-as-a-parameter: these plain calls run with whatever the -// implicit-`this` cell holds, which is the receiver they pass. -fn js_closure_call0(closure: *const ClosureHeader) -> f64 { - provider_call!(provider_js_closure_call0(closure, plain_call_receiver())) +fn js_closure_call0(closure: *const ClosureHeader, this: JsThis) -> f64 { + provider_call!(provider_js_closure_call0(closure, this)) } -fn js_closure_call1(closure: *const ClosureHeader, arg0: f64) -> f64 { - provider_call!(provider_js_closure_call1( - closure, - plain_call_receiver(), - arg0 - )) +fn js_closure_call1(closure: *const ClosureHeader, this: JsThis, arg0: f64) -> f64 { + provider_call!(provider_js_closure_call1(closure, this, arg0)) } -fn js_closure_call2(closure: *const ClosureHeader, arg0: f64, arg1: f64) -> f64 { - provider_call!(provider_js_closure_call2( - closure, - plain_call_receiver(), - arg0, - arg1 - )) +fn js_closure_call2(closure: *const ClosureHeader, this: JsThis, arg0: f64, arg1: f64) -> f64 { + provider_call!(provider_js_closure_call2(closure, this, arg0, arg1)) } fn js_closure_get_capture_ptr(closure: *const ClosureHeader, index: u32) -> i64 { @@ -241,43 +226,22 @@ fn js_promise_mark_internally_handled(promise: *mut Promise) { provider_call!(provider_js_promise_mark_internally_handled(promise)) } -fn js_native_call_value(function: f64, arguments: *const f64, argument_count: usize) -> f64 { +/// Call `function` with `this` bound to `receiver` (a method call of a +/// function value, `function.call(receiver)`). +fn js_native_call_value( + function: f64, + receiver: f64, + arguments: *const f64, + argument_count: usize, +) -> f64 { provider_call!(provider_js_native_call_value( function, - plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(receiver), arguments, argument_count )) } -/// The receiver of a plain call in stage 1 of this-as-a-parameter: the -/// implicit-`this` cell's current value, read through the provider's C ABI -/// like every other runtime access in this file. -fn plain_call_receiver() -> JsThis { - JsThis(provider_call!(provider_js_implicit_this_get()).to_bits()) -} - -fn js_implicit_this_set(value: f64) -> f64 { - provider_call!(provider_js_implicit_this_set(value)) -} - -/// Call `f` with `IMPLICIT_THIS` bound to `receiver`, restoring the displaced -/// value from a transient ROOT afterwards (#10490): it is the caller's -/// receiver, and `f` runs user code that an evacuating minor can move it -/// across. Uses the provider's root stack like every other runtime-owned -/// operation in this file. -fn with_implicit_this(receiver: f64, f: impl FnOnce() -> f64) -> f64 { - let base = provider_call!(provider_js_ffi_root_scope_enter()); - let previous = js_implicit_this_set(receiver); - let slot = provider_call!(provider_js_ffi_root_push_nanbox(previous.to_bits())); - let result = f(); - js_implicit_this_set(f64::from_bits(provider_call!( - provider_js_ffi_root_get_nanbox(slot) - ))); - provider_call!(provider_js_ffi_root_scope_exit(base)); - result -} - fn js_promise_new() -> *mut Promise { provider_call!(provider_js_promise_new()) } @@ -346,7 +310,11 @@ pub(crate) fn internal_promise() -> *mut Promise { unsafe fn try_call_stream_action(callback: i64, reason: f64) -> Result { perry_runtime::exception::catch_js_throw(|| { - js_closure_call1(callback as *const ClosureHeader, reason) + js_closure_call1( + callback as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + reason, + ) }) .map_err(f64::to_bits) } @@ -1110,7 +1078,11 @@ unsafe fn invoke_start(stream_id: usize) { } }; if cb != 0 { - js_closure_call1(cb as *const ClosureHeader, controller); + js_closure_call1( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + controller, + ); } } @@ -1148,7 +1120,11 @@ extern "C" fn readable_pull_microtask( pull_deferred_byte_chunk(stream_id, cb); f64::from_bits(TAG_UNDEFINED) } else { - js_closure_call1(cb as *const ClosureHeader, stream_id as f64) + js_closure_call1( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + stream_id as f64, + ) } }); match pull_outcome { @@ -1296,7 +1272,10 @@ unsafe fn maybe_pull_inner(stream_id: usize, force: bool) { } unsafe fn pull_deferred_byte_chunk(stream_id: usize, cb: i64) { - let chunk = js_closure_call0(cb as *const ClosureHeader); + let chunk = js_closure_call0( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ); if chunk.to_bits() == TAG_UNDEFINED { let _ = js_readable_stream_controller_close(stream_id as f64); } else { @@ -1772,7 +1751,7 @@ pub(crate) unsafe fn has_async_iterator(value: f64) -> bool { unsafe fn call_symbol_async_iterator(value: f64) -> Option { let method = async_iterator_method(value)?; - let iterator = with_implicit_this(value, || js_native_call_value(method, std::ptr::null(), 0)); + let iterator = js_native_call_value(method, value, std::ptr::null(), 0); if iterator.to_bits() == TAG_UNDEFINED { None } else { @@ -1817,18 +1796,21 @@ unsafe fn await_maybe_promise(value: f64) -> SettledValue { } unsafe fn call_iterator_next(iterator: f64) -> Option { - let iter_ptr = js_nanbox_get_pointer(iterator); - if iter_ptr == 0 { + if js_nanbox_get_pointer(iterator) == 0 { return None; } - let iter_obj = iter_ptr as *const ObjectHeader; + // The key allocation can collect: the iterator is the receiver of the + // call below, so it is rooted across it and re-read. + let base = provider_call!(provider_js_ffi_root_scope_enter()); + let slot = provider_call!(provider_js_ffi_root_push_nanbox(iterator.to_bits())); let next_key = js_string_from_bytes(b"next".as_ptr(), 4); + let iterator = f64::from_bits(provider_call!(provider_js_ffi_root_get_nanbox(slot))); + provider_call!(provider_js_ffi_root_scope_exit(base)); + let iter_obj = js_nanbox_get_pointer(iterator) as *const ObjectHeader; let next_val = js_object_get_field_by_name(iter_obj, next_key); let next = f64::from_bits(next_val.bits()); if is_callable_value(next) { - let result = - with_implicit_this(iterator, || js_native_call_value(next, std::ptr::null(), 0)); - Some(result) + Some(js_native_call_value(next, iterator, std::ptr::null(), 0)) } else { Some(perry_runtime::object::js_native_call_method( iterator, @@ -2099,6 +2081,7 @@ pub unsafe extern "C" fn js_readable_stream_controller_enqueue( let size = if strategy_size_cb != 0 { let size = readable_strategy_size_to_number(js_closure_call1( strategy_size_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), chunk, )); if size.is_nan() || size < 0.0 || size.is_infinite() { diff --git a/crates/perry-stdlib/src/streams/pipe.rs b/crates/perry-stdlib/src/streams/pipe.rs index e9ffb61453..1217e9addf 100644 --- a/crates/perry-stdlib/src/streams/pipe.rs +++ b/crates/perry-stdlib/src/streams/pipe.rs @@ -882,7 +882,7 @@ pub unsafe extern "C" fn js_readable_stream_pipe_to( state.abort_listener, ); if perry_runtime::url::js_abort_signal_is_aborted(signal_ptr) != 0 { - readable_stream_pipe_to_aborted(listener, perry_runtime::closure::current_this()); + readable_stream_pipe_to_aborted(listener, perry_runtime::closure::JsThis::UNDEFINED); return promise; } } diff --git a/crates/perry-stdlib/src/streams/tests.rs b/crates/perry-stdlib/src/streams/tests.rs index 3a2889320d..a75a121d35 100644 --- a/crates/perry-stdlib/src/streams/tests.rs +++ b/crates/perry-stdlib/src/streams/tests.rs @@ -260,7 +260,6 @@ fn stream_runtime_owned_calls_cannot_bypass_provider_abi() { for forbidden in [ "perry_runtime::array::js_", "perry_runtime::closure::js_", - "perry_runtime::object::js_implicit_this_set", "perry_runtime::promise::js_", ] { assert!( @@ -423,3 +422,62 @@ fn buffered_tee_demand_skips_only_the_cold_source_hop() { streams.remove(&buffered); streams.remove(&empty); } + +/// Not a NaN-boxed value any call passes: "`next()` never ran". +const NEXT_NOT_CALLED: f64 = 0.5; + +/// Records the receiver it was called with in its own capture slot 0 (a slot +/// the collector sees), so no static holds a heap value. +extern "C" fn next_recording_this( + closure: *const ClosureHeader, + this: perry_runtime::closure::JsThis, +) -> f64 { + perry_runtime::closure::js_closure_set_capture_f64( + closure as *mut ClosureHeader, + 0, + this.as_f64(), + ); + f64::from_bits(TAG_UNDEFINED) +} + +/// An async-iterable source's `next()` is a METHOD call: the iterator is its +/// receiver. The body reads `this` only from its receiver parameter, so this +/// fails if `call_iterator_next` makes a plain call (the body would see +/// `undefined`). +#[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)); + perry_runtime::closure::js_closure_set_capture_f64( + next.get_raw_mut_ptr::(), + 0, + NEXT_NOT_CALLED, + ); + let iterator_obj = scope.root_raw_mut_ptr(js_object_alloc(0, 1)); + let key = js_string_from_bytes(b"next".as_ptr(), 4); + let next_value = f64::from_bits( + JSValue::pointer(next.get_raw_mut_ptr::() as *const u8).bits(), + ); + perry_runtime::object::js_object_set_field_by_name( + iterator_obj.get_raw_mut_ptr::(), + key, + next_value, + ); + let iterator = scope.root_nanbox_f64(f64::from_bits( + JSValue::object_ptr(iterator_obj.get_raw_mut_ptr::() as *mut u8).bits(), + )); + + let result = unsafe { call_iterator_next(iterator.get_nanbox_f64()) }; + assert!(result.is_some()); + let seen = perry_runtime::closure::js_closure_get_capture_f64( + next.get_raw_mut_ptr::(), + 0, + ); + assert_eq!( + seen.to_bits(), + iterator.get_nanbox_f64().to_bits(), + "next() must see the iterator as `this`" + ); +} diff --git a/crates/perry-stdlib/src/streams/transform.rs b/crates/perry-stdlib/src/streams/transform.rs index 17f7770112..dace897148 100644 --- a/crates/perry-stdlib/src/streams/transform.rs +++ b/crates/perry-stdlib/src/streams/transform.rs @@ -131,7 +131,11 @@ unsafe fn alloc_transform_stream_with_strategies( // transform/flush callbacks receive), so `controller.enqueue(c)` / // `controller.terminate()` / `controller.error(e)` act on the readable. if start_cb != 0 { - js_closure_call1(start_cb as *const ClosureHeader, readable_id as f64); + js_closure_call1( + start_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + readable_id as f64, + ); } id as f64 } @@ -316,6 +320,7 @@ extern "C" fn transform_write_job2( if transform_cb != 0 && readable_id != 0 { let ret = js_closure_call2( transform_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), chunk, readable_id as f64, ); @@ -831,6 +836,7 @@ unsafe fn perform_transform_close(writable_id: usize, promise: *mut Promise) { let flush_result = if !handled_native && flush_cb != 0 && readable_id != 0 { Some(js_closure_call1( flush_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), readable_id as f64, )) } else { diff --git a/crates/perry-stdlib/src/streams/writable.rs b/crates/perry-stdlib/src/streams/writable.rs index a1f3f48ce7..459e54c894 100644 --- a/crates/perry-stdlib/src/streams/writable.rs +++ b/crates/perry-stdlib/src/streams/writable.rs @@ -53,7 +53,11 @@ pub unsafe extern "C" fn js_writable_stream_new_with_sink_type( // start → write → close. The controller arg is the stream handle. let start_cb = closure_from_bits(start_bits.to_bits()); if start_cb != 0 { - js_closure_call1(start_cb as *const ClosureHeader, id as f64); + js_closure_call1( + start_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + id as f64, + ); } id as f64 } @@ -79,7 +83,11 @@ pub unsafe extern "C" fn js_writable_stream_new_from_sink_object(sink: f64, hwm: ); let start_cb = stream_object_closure(sink, b"start"); if start_cb != 0 { - js_closure_call1(start_cb as *const ClosureHeader, id as f64); + js_closure_call1( + start_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + id as f64, + ); } id as f64 } @@ -362,13 +370,24 @@ unsafe fn attach_writable_write_handlers( } unsafe fn try_call_writable_write(cb: i64, chunk: f64) -> Result { - perry_runtime::exception::catch_js_throw(|| js_closure_call1(cb as *const ClosureHeader, chunk)) - .map_err(f64::to_bits) + perry_runtime::exception::catch_js_throw(|| { + js_closure_call1( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + chunk, + ) + }) + .map_err(f64::to_bits) } unsafe fn try_call_writable_close(cb: i64) -> Result { - perry_runtime::exception::catch_js_throw(|| js_closure_call0(cb as *const ClosureHeader)) - .map_err(f64::to_bits) + perry_runtime::exception::catch_js_throw(|| { + js_closure_call0( + cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + ) + }) + .map_err(f64::to_bits) } extern "C" fn writable_close_fulfilled( @@ -559,9 +578,15 @@ pub(super) unsafe fn writable_stream_write( .map(|s| s.strategy_size_cb) .unwrap_or(0); let chunk_size = if size_cb != 0 { - let size = - JSValue::from_bits(js_closure_call1(size_cb as *const ClosureHeader, chunk).to_bits()) - .to_number(); + let size = JSValue::from_bits( + js_closure_call1( + size_cb as *const ClosureHeader, + perry_runtime::closure::plain_call_receiver(), + chunk, + ) + .to_bits(), + ) + .to_number(); if size.is_nan() || size < 0.0 || size.is_infinite() { let message = invalid_size_message(size); throw_range_error_with_code(&message, "ERR_INVALID_ARG_VALUE"); diff --git a/crates/perry-stdlib/src/tls.rs b/crates/perry-stdlib/src/tls.rs index 093e951de5..681b8c0592 100644 --- a/crates/perry-stdlib/src/tls.rs +++ b/crates/perry-stdlib/src/tls.rs @@ -1292,22 +1292,22 @@ pub unsafe extern "C" fn js_tls_client_preflight( js_nanbox_pointer(string_array(&protocols) as i64), ); let callback_socket = nanbox_handle(socket_id); + // The rebind below can allocate a replacement closure: root the + // argument object across it and re-read it. + let scope = perry_runtime::gc::RuntimeHandleScope::new(); + let argument_h = scope.root_nanbox_f64(js_nanbox_pointer(argument as i64)); // Node invokes an object-literal ALPNCallback with the pending - // TLSSocket, not the closure's original options-object receiver. + // TLSSocket as `this`, not the closure's original options-object + // receiver. let rebound_callback = perry_runtime::closure::js_closure_unbox_callee_checked_rebind( nanbox_handle(alpn_callback), callback_socket, ); - // #10490: root the displaced `this` across the ALPNCallback (user code). - let this_scope = perry_runtime::gc::RuntimeHandleScope::new(); - let previous_this = this_scope - .root_nanbox_f64(perry_runtime::object::js_implicit_this_set(callback_socket)); - let selected = js_closure_call1( + let selected = perry_runtime::closure::js_closure_call1( rebound_callback as *const ClosureHeader, - perry_runtime::closure::plain_call_receiver(), - js_nanbox_pointer(argument as i64), + perry_runtime::closure::JsThis::from_f64(callback_socket), + argument_h.get_nanbox_f64(), ); - perry_runtime::object::js_implicit_this_set(previous_this.get_nanbox_f64()); let selected = value_to_string(selected).map(String::into_bytes); if let Some(socket) = sockets().lock().unwrap().remove(&socket_id) { liveness::step(liveness::socket_keeps_alive(&socket), false); diff --git a/crates/perry-stdlib/src/worker_threads.rs b/crates/perry-stdlib/src/worker_threads.rs index 305d5fb853..57d67544fb 100644 --- a/crates/perry-stdlib/src/worker_threads.rs +++ b/crates/perry-stdlib/src/worker_threads.rs @@ -931,17 +931,11 @@ fn call_callback1(callback_bits: u64, this_bits: u64, arg: f64) { if closure.is_null() { return; } - // #10490: root the displaced `this` across the callback (user code). - let this_scope = perry_runtime::gc::RuntimeHandleScope::new(); - let prev_this = this_scope.root_nanbox_f64(perry_runtime::object::js_implicit_this_set( - f64::from_bits(this_bits), - )); perry_runtime::closure::js_closure_call1( closure, - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis(this_bits), arg, ); - perry_runtime::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } fn object_event_handler(target_bits: u64, name: &str) -> Option { diff --git a/crates/perry-stdlib/src/worker_threads/worker_surface.rs b/crates/perry-stdlib/src/worker_threads/worker_surface.rs index c3aa68696d..1bdc334a72 100644 --- a/crates/perry-stdlib/src/worker_threads/worker_surface.rs +++ b/crates/perry-stdlib/src/worker_threads/worker_surface.rs @@ -361,19 +361,21 @@ fn stream_listener_key(event: &str) -> String { format!("__perryWorkerStreamListener:{event}") } -fn stream_this() -> f64 { - perry_runtime::object::js_implicit_this_get() -} - -fn stream_register(event: f64, callback: f64) -> f64 { - let this = stream_this(); +fn stream_register(receiver: perry_runtime::closure::JsThis, event: f64, callback: f64) -> f64 { + let this = receiver.as_f64(); let Some(event) = string_value_to_string(event) else { return this; }; let key = stream_listener_key(&event); + // The listener array allocations can collect: root the receiver (and the + // callback being stored) across them and re-read both. + let scope = perry_runtime::gc::RuntimeHandleScope::new(); + let this_h = scope.root_nanbox_f64(this); + let callback_h = scope.root_nanbox_f64(callback); let arr = array_ptr_from_value(get_object_field_from_value(this, &key)) .unwrap_or_else(|| perry_runtime::array::js_array_alloc(0)); - let arr = perry_runtime::array::js_array_push_f64(arr, callback); + let arr = perry_runtime::array::js_array_push_f64(arr, callback_h.get_nanbox_f64()); + let this = this_h.get_nanbox_f64(); if let Some(obj) = object_ptr_from_value(this) { set_object_field( obj, @@ -385,7 +387,7 @@ fn stream_register(event: f64, callback: f64) -> f64 { } fn stream_emit_event(receiver: perry_runtime::closure::JsThis, event: f64, arg: f64) -> f64 { - let this = stream_this(); + let this = receiver.as_f64(); let Some(event) = string_value_to_string(event) else { return js_bool(false); }; @@ -398,37 +400,33 @@ fn stream_emit_event(receiver: perry_runtime::closure::JsThis, event: f64, arg: // listener allocates enough to trigger a moving minor collection // corrupts every read for the rest of this loop, not just one listener. // Root all three through one handle scope and re-read the current bits - // before every dispatch. - // - // #10490: the displaced `this` crosses every listener (user code) too. - // Root it ONCE in that same scope, before the first - // `js_implicit_this_set`, and restore it from that root rather than from - // a plain per-iteration local. + // before every dispatch. Each listener runs with the stream as `this`. let scope = perry_runtime::gc::RuntimeHandleScope::new(); let this_h = scope.root_nanbox_f64(this); let arr_h = scope.root_raw_mut_ptr(arr); let arg_h = scope.root_nanbox_f64(arg); - let prev_this = scope.root_nanbox_f64(receiver.as_f64()); let len = perry_runtime::array::js_array_length(arr_h.get_raw_mut_ptr()); for i in 0..len { let callback = perry_runtime::array::js_array_get_f64(arr_h.get_raw_mut_ptr(), i); - perry_runtime::object::js_implicit_this_set(this_h.get_nanbox_f64()); unsafe { let args = [arg_h.get_nanbox_f64()]; let _ = perry_runtime::closure::js_native_call_value( callback, - perry_runtime::closure::plain_call_receiver(), + perry_runtime::closure::JsThis::from_f64(this_h.get_nanbox_f64()), args.as_ptr(), args.len(), ); } - perry_runtime::object::js_implicit_this_set(prev_this.get_nanbox_f64()); } js_bool(len > 0) } -fn stream_remove_listener(event: f64, callback: f64) -> f64 { - let this = stream_this(); +fn stream_remove_listener( + receiver: perry_runtime::closure::JsThis, + event: f64, + callback: f64, +) -> f64 { + let this = receiver.as_f64(); let Some(event) = string_value_to_string(event) else { return this; }; @@ -436,14 +434,21 @@ fn stream_remove_listener(event: f64, callback: f64) -> f64 { let Some(arr) = array_ptr_from_value(get_object_field_from_value(this, &key)) else { return this; }; - let len = perry_runtime::array::js_array_length(arr); + // The filtered copy allocates: root the receiver, the old array and the + // callback compared against across it and re-read them. + let scope = perry_runtime::gc::RuntimeHandleScope::new(); + let this_h = scope.root_nanbox_f64(this); + let arr_h = scope.root_raw_mut_ptr(arr); + let callback_h = scope.root_nanbox_f64(callback); + let len = perry_runtime::array::js_array_length(arr_h.get_raw_mut_ptr()); let mut next = perry_runtime::array::js_array_alloc(len); for i in 0..len { - let value = perry_runtime::array::js_array_get_f64(arr, i); - if value.to_bits() != callback.to_bits() { + let value = perry_runtime::array::js_array_get_f64(arr_h.get_raw_mut_ptr(), i); + if value.to_bits() != callback_h.get_nanbox_f64().to_bits() { next = perry_runtime::array::js_array_push_f64(next, value); } } + let this = this_h.get_nanbox_f64(); if let Some(obj) = object_ptr_from_value(this) { set_object_field( obj, @@ -456,11 +461,11 @@ fn stream_remove_listener(event: f64, callback: f64) -> f64 { extern "C" fn stream_on( _closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { - stream_register(event, callback) + stream_register(this, event, callback) } extern "C" fn stream_emit( @@ -474,26 +479,26 @@ extern "C" fn stream_emit( extern "C" fn stream_off( _closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, event: f64, callback: f64, ) -> f64 { - stream_remove_listener(event, callback) + stream_remove_listener(this, event, callback) } extern "C" fn stream_this0( _closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, ) -> f64 { - stream_this() + this.as_f64() } extern "C" fn stream_this1( _closure: *const ClosureHeader, - _this: perry_runtime::closure::JsThis, + this: perry_runtime::closure::JsThis, _arg: f64, ) -> f64 { - stream_this() + this.as_f64() } extern "C" fn stream_write( diff --git a/crates/perry/src/commands/compile/object_cache.rs b/crates/perry/src/commands/compile/object_cache.rs index 88a6988dbb..b515b1a32c 100644 --- a/crates/perry/src/commands/compile/object_cache.rs +++ b/crates/perry/src/commands/compile/object_cache.rs @@ -1194,12 +1194,6 @@ fn compute_object_cache_key_with_env( "env_recv_route_count", env_var("PERRY_RECV_ROUTE_COUNT").as_deref().unwrap_or(""), ); - // This-as-a-parameter stage 1: a `=1` build emits a witness call at the - // entry of every body that reads the implicit-`this` cell. - h.field( - "env_this_witness", - env_var("PERRY_THIS_WITNESS").as_deref().unwrap_or(""), - ); h.field( "env_full_outline_ic", env_var("PERRY_FULL_OUTLINE_IC").as_deref().unwrap_or(""), diff --git a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs index 57c09e2259..79c534307e 100644 --- a/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs +++ b/crates/perry/src/commands/compile/object_cache/object_cache_tests.rs @@ -762,7 +762,6 @@ fn key_changes_with_codegen_env_vars() { "PERRY_TYPED_FEEDBACK", "PERRY_TYPED_FEEDBACK_TRACE", "PERRY_CONCAT_SITE_CACHE", - "PERRY_THIS_WITNESS", "PERRY_FULL_OUTLINE_IC", "PERRY_FULL_OUTLINE_IC_MIN_FUNCS", "PERRY_OUTLINE_METHOD_DISPATCH", diff --git a/crates/perry/tests/fixtures/this_param_routes.ts b/crates/perry/tests/fixtures/this_param_routes.ts index eff74d26ec..a42e0df9f3 100644 --- a/crates/perry/tests/fixtures/this_param_routes.ts +++ b/crates/perry/tests/fixtures/this_param_routes.ts @@ -1,6 +1,7 @@ -// Every route by which a JS body that reads `this` from the implicit-`this` -// cell is entered. Stage 1 of this-as-a-parameter: each caller must hand the -// body, as its `this` PARAMETER, exactly the receiver the cell holds. +// Every route by which a JS body that reads its dynamic `this` is entered. +// Each caller hands the body its receiver as the `this` PARAMETER, the only +// way a body learns it; a route that passes the wrong receiver changes the +// printed sums. const N = process.argv.length > 99 ? 1 : 300; const out: string[] = []; let s = 0; @@ -35,14 +36,32 @@ class D extends C { const d = new D(); const bound = fexpr.bind(owner); +// A plain call made while a method is running binds `this` to undefined +// (this module is strict): the method's receiver must not leak into a +// callback the runtime calls without one. +let leaks = 0; const sorter: any = { k: 3, run(this: any) { const a = [3, 1, 2]; - a.sort(function (x: number, y: number) { return x - y; }); - return a.join("") + this.k; + a.sort(function (this: any, x: number, y: number) { + if (this !== undefined) leaks++; + return x - y; + }); + const r = a.reduce(function (this: any, acc: number, v: number) { + if (this !== undefined) leaks++; + return acc + v; + }, 0); + return a.join("") + this.k + r; }, }; +// Async and generator bodies bind their receiver at entry too. +const og: any = { + k: 9, + async am(this: any, x: number) { await 0; return this.k + x; }, + async an(this: any, x: number) { return this.k - x; }, // no await: not state-machine lowered + *gm(this: any, x: number) { yield this.k + x; yield this.k * x; }, +}; for (let i = 0; i < N; i++) { const o = i & 1 ? p1 : p2; @@ -59,10 +78,18 @@ for (let i = 0; i < N; i++) { s += [4].map(function (this: any, v: number) { return this.k + v; }, owner)[0]; const q: any = new (fexpr as any)(1); // `new F()` s += q instanceof (fexpr as any) ? 1 : 0; + const it = og.gm(i); // generator method + s += it.next().value + it.next().value; if (i % 97 === 0) out.push(String(s)); } out.push(sorter.run()); -console.log(s, out.join(",")); +console.log(s, out.join(","), "leaks=" + leaks); +// Function EXPRESSIONS (not literal methods) installed as methods. +og.af = async function (this: any, x: number) { return this.k * x; }; +og.gf = function* (this: any) { yield this.k; }; +og.am(1).then((v: number) => + og.an(v).then((w: number) => + og.af(2).then((z: number) => console.log("async", v, w, z, og.gf().next().value)))); // Value wrappers of every function kind (installed as function objects). export function exportedValue(this: any) { return typeof this; } diff --git a/crates/perry/tests/source_graph_export_regressions.rs b/crates/perry/tests/source_graph_export_regressions.rs index aa4fdad25f..49f6e12062 100644 --- a/crates/perry/tests/source_graph_export_regressions.rs +++ b/crates/perry/tests/source_graph_export_regressions.rs @@ -314,11 +314,11 @@ fn cross_module_arrow_callback_dispatch_is_resolved_once_and_fails_closed() { let sink_run = llvm_function_body_containing( &all_ir, "@perry_method_sink_ts__Sink__run", - "@js_closure_resolve_arrow_direct_call(", + "@js_closure_resolve_plain_direct_call(", ); assert_eq!( sink_run - .matches("@js_closure_resolve_arrow_direct_call(") + .matches("@js_closure_resolve_plain_direct_call(") .count(), 1, "the callback target must be resolved once at method entry:\n{sink_run}" diff --git a/crates/perry/tests/this_param_abi.rs b/crates/perry/tests/this_param_abi.rs index 5666f951cd..e3969e084a 100644 --- a/crates/perry/tests/this_param_abi.rs +++ b/crates/perry/tests/this_param_abi.rs @@ -1,13 +1,8 @@ -//! This-as-a-parameter, stage 1: every JS body — compiled closure, value -//! wrapper, native builtin — is `double body(i64 callee, i64 this, double -//! a0, ...)` (`perry_abi::JS_BODY_*`), and every caller hands the body, as its -//! `this` parameter, exactly the receiver the implicit-`this` cell holds for -//! the call. Bodies still read the cell, so stage 1 changes no behavior; what -//! it must not do is let the parameter and the cell disagree. -//! -//! A `PERRY_THIS_WITNESS=1` build makes the prologue of every compiled body -//! that reads the cell compare the two (`js_this_param_witness`), and reports -//! `PERRY_THIS_WITNESS checks=N mismatches=M` at exit. +//! This-as-a-parameter: every JS body — compiled closure, value wrapper, +//! native builtin — is `double body(i64 callee, i64 this, double a0, ...)` +//! (`perry_abi::JS_BODY_*`), and the `this` parameter is the ONLY way a body +//! learns its receiver. There is no thread-local receiver cell: a caller that +//! binds a receiver passes it, and a plain call passes `undefined`. use std::path::{Path, PathBuf}; use std::process::Command; @@ -18,8 +13,9 @@ fn perry_bin() -> PathBuf { const ROUTES: &str = include_str!("fixtures/this_param_routes.ts"); /// node v26.5.1's output for `fixtures/this_param_routes.ts`. -const ROUTES_NODE: &str = - "346350 -933,-25431,81808,320762,1233\nfunction,function,function,function"; +const ROUTES_NODE: &str = "797550 -924,22981,272713,748250,12336 leaks=0\n\ + function,function,function,function\n\ + async 10 -1 18 9"; struct Run { stdout: String, @@ -28,8 +24,8 @@ struct Run { _dir: tempfile::TempDir, } -/// Compile `source` with the witness on and the constructed IR saved, run it. -fn compile_and_run_witnessed(source: &str) -> Run { +/// Compile `source` with the constructed IR saved, run it. +fn compile_and_run(source: &str) -> Run { let dir = tempfile::tempdir().expect("tempdir"); let ll_dir = tempfile::tempdir().expect("ll tempdir"); let entry = dir.path().join("main.ts"); @@ -42,7 +38,6 @@ fn compile_and_run_witnessed(source: &str) -> Run { .arg("-o") .arg(&output) .env("PERRY_NO_CACHE", "1") - .env("PERRY_THIS_WITNESS", "1") .env("PERRY_SAVE_LL", ll_dir.path()) .output() .expect("run perry compile"); @@ -70,41 +65,18 @@ fn compile_and_run_witnessed(source: &str) -> Run { } } -/// `(checks, mismatches)` from the witness's exit line. -fn witness_counts(stderr: &str) -> (u64, u64) { - let line = stderr - .lines() - .find(|l| l.starts_with("PERRY_THIS_WITNESS checks=")) - .unwrap_or_else(|| panic!("no witness report on stderr:\n{stderr}")); - let field = |name: &str| -> u64 { - line.split_whitespace() - .find_map(|w| w.strip_prefix(name)?.strip_prefix('=')?.parse().ok()) - .unwrap_or_else(|| panic!("no {name} in {line:?}")) - }; - (field("checks"), field("mismatches")) -} - -/// Sabotage: the method-site hit (or any caller) passes `undefined` — or the -/// runtime funnel passes anything but the cell's receiver — as `this` -> the -/// witness names the body and `mismatches` is non-zero. Sabotage: the witness -/// build emits no check -> `checks` is 0 and the lower bound fails. +/// Every route into a body that reads `this` hands it the receiver node +/// binds: the sums fold `this.k` from every route, so one route passing the +/// wrong receiver (or `undefined`) changes them. Sabotage: the method-site +/// hit passes `undefined` -> the first sum differs. Sabotage: the runtime's +/// sort/reduce callback call passes the running method's receiver instead of +/// the plain-call `undefined` (what a thread-local cell did) -> `leaks=` is +/// non-zero. Sabotage: an async or generator body skips its entry bind -> +/// the sum or the `async` line differs. #[test] -fn every_call_route_passes_the_cell_receiver_as_the_this_parameter() { - let run = compile_and_run_witnessed(ROUTES); +fn every_call_route_hands_the_body_its_receiver() { + let run = compile_and_run(ROUTES); assert_eq!(run.stdout, ROUTES_NODE, "stderr:\n{}", run.stderr); - let (checks, mismatches) = witness_counts(&run.stderr); - // 300 iterations of ~13 routes into cell-reading bodies: a witness that - // did not run cannot pass for one that found nothing. - assert!( - checks >= 3000, - "the witness checked only {checks} body entries\nstderr:\n{}", - run.stderr - ); - assert_eq!( - mismatches, 0, - "a caller passed a receiver other than the cell's\nstderr:\n{}", - run.stderr - ); } /// Every symbol the module hands to a closure allocator or registers as a @@ -112,7 +84,7 @@ fn every_call_route_passes_the_cell_receiver_as_the_this_parameter() { /// (e.g. `__perry_wrap_`) defined without `%js_this` -> named here. #[test] fn every_installed_body_is_defined_with_the_js_body_abi() { - let run = compile_and_run_witnessed(ROUTES); + let run = compile_and_run(ROUTES); assert_eq!(run.stdout, ROUTES_NODE, "stderr:\n{}", run.stderr); let ir = read_ir(run.ll_dir.path()); let installed = installed_bodies(&ir); diff --git a/scripts/check_runtime_symbols.sh b/scripts/check_runtime_symbols.sh index caad53e384..35d9bb81c6 100755 --- a/scripts/check_runtime_symbols.sh +++ b/scripts/check_runtime_symbols.sh @@ -54,7 +54,7 @@ SENTINELS=( js_closure_get_capture_bits js_closure_set_capture_bits js_closure_set_box_capture_ptr - js_closure_resolve_arrow_direct_call + 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 diff --git a/scripts/gc_root_dominance_check.py b/scripts/gc_root_dominance_check.py index c8dbd1b43f..8c5613d501 100755 --- a/scripts/gc_root_dominance_check.py +++ b/scripts/gc_root_dominance_check.py @@ -567,11 +567,6 @@ def build_cfg(f): # (closure/dynamic_props.rs:1040) when the callee captures `this`. That one # IS a collection point, and #7154's fix re-reads the arguments below it. "js_closure_unbox_callee_checked", - # object/this_binding.rs:160 -- a thread-local cell swap - "js_implicit_this_set", "js_implicit_this_get", - # object/this_binding.rs -- the stage-1 this-parameter witness (witness - # builds only): compares its argument with that same cell, two atomics. - "js_this_param_witness", # `js_gc_note_slot_layout` (gc/layout.rs:814) and its `_aware` sibling # (:833). `_aware` is the same body behind an early return taken when # neither the new nor the old bits are pointer-bearing, so it does strictly @@ -2636,17 +2631,14 @@ def rewritten_load_kind(text): "js_closure_get_capture_ptr", "js_gc_temp_root_get", # a MUTABLE root: rewritten on evacuation "js_box_get_bits", # box.rs mutable-capture cell read - "js_implicit_this_get", # object/this_binding.rs:160 thread-local "js_new_target_get", - # object/this_binding.rs:159 -- `js_implicit_this_SET` is a swap, so its - # RETURN value is a read of the same scanned mutable cell - # (`scan_implicit_this_roots_mut`, this_binding.rs:176) and the swap has - # already overwritten the only other copy. Listing the setter as a reader - # looks odd, which is exactly why #7214 left `prev_this` unrooted for a - # whole PR: the checker saw a call it knew could not collect, never - # classified the result as a heap value, and reported nothing at either - # end. Being non-collecting is what makes a call a root READ. - "js_implicit_this_set", + # A non-collecting SETTER of a scanned mutable cell that returns the + # previous value is a root READ too (#7214 left `prev_this` unrooted for a + # whole PR because the implicit-`this` swap was not listed here): being + # non-collecting is what makes a call a root read. `js_new_target_set` + # returns its previous value too; it must be listed here if generated + # code ever uses that result (`new_target_save` reads the cell with + # `js_new_target_get` first instead). # `js_get_string_pointer_unified` is a candidate and is deliberately left # out for now: its result IS a raw heap address in a bare register, but it # is not in NONCOLLECTING (its SSO branch allocates), so classifying it as @@ -2932,7 +2924,7 @@ def run_stale(parsed, poll_reaching, verbose, moving_only, fatal_only, # hands back an object the collector can move). HEAP_SOURCE_CALLS = frozenset({ "js_gc_temp_root_get", "js_shadow_slot_get", "js_closure_get_capture_bits", - "js_box_get_bits", "js_implicit_this_get", "js_new_target_get", + "js_box_get_bits", "js_new_target_get", "js_static_this_resolve", "js_get_exception", }) diff --git a/scripts/gc_runtime_root_holders.json b/scripts/gc_runtime_root_holders.json index 9c29432a14..af997485eb 100644 --- a/scripts/gc_runtime_root_holders.json +++ b/scripts/gc_runtime_root_holders.json @@ -386,7 +386,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.", + "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 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.", "window": { "start": { "file": "crates/perry-runtime/src/gc/census.rs", @@ -403,7 +403,7 @@ "sources": { "crates/perry-runtime/src/gc/census.rs": "25601f25ac70aa998f8cb5c1939d11e7c43a96235d4edf39a78e261b68709471", "crates/perry-runtime/src/gc/cycle.rs": "4744196ba5e9c5ac40912154cf5b45b4a618d81ddc776ab1095fbc585f27c878", - "crates/perry-runtime/src/gc/mod.rs": "bb38a949495846fd30598064bb04d8e8d384721c25a9a21d1029da1e0c5caa3c", + "crates/perry-runtime/src/gc/mod.rs": "2198c69b341ef4e95d5540aded59f5c9b1c7f36a37ab8ef895d1962f75792829", "crates/perry-runtime/src/gc/policy.rs": "7e2304ec822d26877df4eaf7aefb55c485c66425b8946b56a9b271a0abcc635b", "crates/perry-runtime/src/gc/progress.rs": "a5ad3971bbe4047229ca57325234780daa85921dbc778e1c08dff4ad07ccfb96" } diff --git a/scripts/local_binding_type_allowlist.json b/scripts/local_binding_type_allowlist.json index 1f2672e0f7..9d473dd05c 100644 --- a/scripts/local_binding_type_allowlist.json +++ b/scripts/local_binding_type_allowlist.json @@ -23,7 +23,7 @@ "access": "local_type_hint", "count": 1, "classification": "runtime-validated", - "reason": "The declared function type only narrows which loop-called bindings are ATTEMPTED for entry resolution; it licenses no call. The resolution itself goes through js_closure_resolve_arrow_direct_call(handle, arity), which validates the value is an arrow closure of that arity at runtime, so a wrong or stale declared type yields a failed resolution and the ordinary dynamic call, never a wrong callee. Async and generator function types are excluded at this read." + "reason": "The declared function type only narrows which loop-called bindings are ATTEMPTED for entry resolution; it licenses no call. The resolution itself goes through js_closure_resolve_plain_direct_call(handle, arity), which validates the value is a directly callable closure of that arity at runtime, so a wrong or stale declared type yields a failed resolution and the ordinary dynamic call, never a wrong callee. Async and generator function types are excluded at this read." }, { "path": "crates/perry-codegen/src/codegen/mod.rs", diff --git a/scripts/unrooted_local_shape_baseline.json b/scripts/unrooted_local_shape_baseline.json index 282981087e..33e5dd77ad 100644 --- a/scripts/unrooted_local_shape_baseline.json +++ b/scripts/unrooted_local_shape_baseline.json @@ -56,9 +56,9 @@ "crates/perry-stdlib/src/webcrypto/util.rs": 1, "crates/perry-stdlib/src/worker_threads.rs": 12, "crates/perry-stdlib/src/worker_threads/direct_message.rs": 2, - "crates/perry-stdlib/src/worker_threads/worker_surface.rs": 7, + "crates/perry-stdlib/src/worker_threads/worker_surface.rs": 6, "crates/perry-stdlib/src/zlib.rs": 3 }, "schema_version": 3, - "total": 369 + "total": 368 } diff --git a/test-files/test_gap_this_param_receiver_routes.ts b/test-files/test_gap_this_param_receiver_routes.ts new file mode 100644 index 0000000000..2abde40b12 --- /dev/null +++ b/test-files/test_gap_this_param_receiver_routes.ts @@ -0,0 +1,30 @@ +import * as util from "node:util"; +const out: string[] = []; +const obj: any = { + k: 7, + cb(this: any, x: number, done: (e: any, v?: any) => void) { done(null, this && this.k + x); }, + async am(this: any, x: number) { return this.k * x; }, + old(this: any) { return this && this.k; }, +}; +obj.p = util.promisify(obj.cb); +obj.c = util.callbackify(obj.am); +obj.d = util.deprecate(obj.old, "old is deprecated", "DEP_TEST"); +function argsAccessor(this: any, a: number) { + const args: any = arguments; + Object.defineProperty(args, "g", { get(this: any) { return this === args; }, configurable: true }); + return args.g; +} +out.push("args-accessor-this=" + argsAccessor(1)); +const holder: any = Object.create((Intl as any).NumberFormat.prototype); +const r = (Intl as any).NumberFormat.call(holder, "en-US"); +out.push("intl-legacy-chain=" + (r === holder)); +out.push("deprecate=" + obj.d()); +const sorted = [3, 1, 2].sort(function (this: any, a: number, b: number) { return (this === undefined ? 0 : 100) + a - b; }); +out.push("sort=" + sorted.join("")); +obj.p(3).then((v: any) => { + out.push("promisify=" + v); + obj.c(5, (e: any, v: any) => { + out.push("callbackify=" + v); + console.log(out.join(" ")); + }); +}); From e3700e07dc2311571bed5fef9be90a8531da6670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 14:23:31 +0000 Subject: [PATCH 06/17] refactor(runtime): split child_process reactor exec/execFile path into its own module reactor.rs was 2010 lines, over the 2000-line cap enforced by scripts/check_file_size.sh. Move the async exec/execFile section (cp_exec_async, cp_exec_accumulate, cp_exec_fire_close, cp_defer_exec_callback, cp_exec_cb_thunk) into a sibling reactor/exec_async.rs, the same way stdin.rs and streams.rs were already split out of this file. Pure code move, no behavior change. --- .../src/child_process/reactor.rs | 314 +--------------- .../src/child_process/reactor/exec_async.rs | 338 ++++++++++++++++++ 2 files changed, 341 insertions(+), 311 deletions(-) create mode 100644 crates/perry-runtime/src/child_process/reactor/exec_async.rs diff --git a/crates/perry-runtime/src/child_process/reactor.rs b/crates/perry-runtime/src/child_process/reactor.rs index e843c88520..c8bdd961e9 100644 --- a/crates/perry-runtime/src/child_process/reactor.rs +++ b/crates/perry-runtime/src/child_process/reactor.rs @@ -63,6 +63,9 @@ use streams::{ cp_pipe_from_child_stderr, cp_pipe_from_child_stdout, cp_pipe_from_file, cp_spawn_reader, CpPipe, }; +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}; /// Monotonic registry key for live children. static CP_NEXT_LIVE_ID: AtomicU64 = AtomicU64::new(1); @@ -1072,317 +1075,6 @@ pub(super) fn cp_register_reactor_arities() { crate::closure::js_register_closure_arity(cp_exec_cb_thunk as *const u8, 0); } -// ============================================================================ -// Async `exec` / `execFile` — run off the main thread, call back on a later -// tick (#4912). -// ============================================================================ - -/// Launch `command` (already shaped as `sh -c ` for `exec` or `file -/// args…` for `execFile`, with `cwd`/`env` applied) without blocking the main -/// thread, capturing stdout/stderr on background reader threads. When the child -/// exits, the pump fires `cb_val(err, stdout, stderr)` on a later event-loop -/// tick. A spawn failure (e.g. `ENOENT`) is reported the same way, deferred to -/// the next tick. Always returns `undefined` (the callback form's return). -pub(super) fn cp_exec_async( - mut command: Command, - cmd_str: String, - public_spawnfile: Option, - cb_val: f64, - 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. - let file = { - let program = command.get_program().to_string_lossy(); - #[cfg(unix)] - if program == "sh" { - "/bin/sh".to_string() - } else { - program.into_owned() - } - #[cfg(not(unix))] - program.into_owned() - }; - - // exec/execFile capture stdout+stderr and never feed stdin. - command.stdin(Stdio::null()); - command.stdout(Stdio::piped()); - command.stderr(Stdio::piped()); - - let timeout = run_options.timeout(); - let kill_signal = run_options.kill_signal(); - 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)), - ( - "removeAllListeners", - cp_cast1(cp_method_remove_all_listeners), - ), - ("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); - cp_set_field(cp, b"stderr", stderr_obj); - cp_set_field(cp, b"stdin", TAG_NULL_F64); - let mut stdio = crate::array::js_array_alloc(3); - stdio = crate::array::js_array_push_f64(stdio, TAG_NULL_F64); - stdio = crate::array::js_array_push_f64(stdio, stdout_obj); - stdio = crate::array::js_array_push_f64(stdio, stderr_obj); - cp_set_field(cp, b"stdio", cp_box_ptr(stdio as *const u8)); - cp_set_field(cp, b"exitCode", TAG_NULL_F64); - cp_set_field(cp, b"signalCode", TAG_NULL_F64); - cp_set_field(cp, b"killed", TAG_FALSE_F64); - cp_set_field(cp, b"connected", TAG_FALSE_F64); - // `cp_command_for_program` may resolve a bare executable against PATH to - // keep macOS on `posix_spawn`, but Node exposes the caller's original - // `execFile` spelling through `ChildProcess.spawnfile`. Keep the resolved - // path above for launch-error metadata and publish the explicit spelling - // when that API supplied one. `exec` continues to expose its real shell. - cp_set_field( - cp, - b"spawnfile", - cp_box_string(public_spawnfile.as_deref().unwrap_or(&file)), - ); - - match command.spawn() { - Ok(mut child) => { - let pid = child.id(); - // Duplicate the process handle BEFORE `child` moves to the waiter - // thread — the kill paths act on it instead of the recyclable pid. - #[cfg(windows)] - let win_proc_handle = cp_win_dup_proc_handle(&child); - let stdout_pipe = child.stdout.take(); - let stderr_pipe = child.stderr.take(); - let stdout_open = stdout_pipe.is_some(); - let stderr_open = stderr_pipe.is_some(); - let handle = CP_NEXT_LIVE_ID.fetch_add(1, Ordering::SeqCst); - cp_set_field(cp, b"pid", pid as f64); - cp_set_field(cp, b"__cpHandle", handle as f64); - cp_set_field(stdout_obj, b"__cpHandle", handle as f64); - cp_set_field(stderr_obj, b"__cpHandle", handle as f64); - - let exec = Box::new(CpExecPending { - cb_bits: cb_val.to_bits(), - stdout: Vec::new(), - stderr: Vec::new(), - run_options, - mode, - cmd: cmd_str, - file, - exceeded: false, - timed_out: false, - }); - let (process_ids, pipe_ids) = cp_init_async_resources(cp, None, stdout_obj, stderr_obj); - - { - register_thread_exit_release(); - let mut guard = cp_live_lock(); - let map = guard.get_or_insert_with(HashMap::new); - map.insert( - handle, - LiveChild { - cp_bits: cp.to_bits(), - process_ids, - pipe_ids, - pipe_bits: [ - TAG_NULL_F64.to_bits(), - stdout_obj.to_bits(), - stderr_obj.to_bits(), - ], - pid: pid as i32, - stdin: None, - stdout_open, - stderr_open, - stdout_eof_pending: false, - extra_open: Vec::new(), - loop_streams: Vec::new(), - spawned: false, - exited: None, - exit_emitted: false, - closed: false, - refed: true, - ipc_send: None, - ipc_advanced: false, - abort_signal_bits: 0, - abort_listener_bits: 0, - abort_kill_signal: libc_sigterm(), - #[cfg(windows)] - win_kill_signal: None, - #[cfg(windows)] - win_proc_handle, - exec: Some(exec), - }, - ); - } - crate::stdlib_pump::register_runtime_pump(0, cp_reactor_pump_extern); - CP_LIVE_COUNT.fetch_add(1, Ordering::SeqCst); - CP_REFED_COUNT.fetch_add(1, Ordering::SeqCst); - - if let Some(o) = stdout_pipe { - cp_spawn_reader(handle, cp_pipe_from_child_stdout(o), 1); - } - if let Some(e) = stderr_pipe { - cp_spawn_reader(handle, cp_pipe_from_child_stderr(e), 2); - } - let waiter: CpWaiter = Box::new(move || match child.wait() { - Ok(status) => { - #[cfg(unix)] - let signal = { - use std::os::unix::process::ExitStatusExt; - status.signal() - }; - #[cfg(not(unix))] - let signal: Option = None; - (status.code(), signal) - } - Err(_) => (Some(-1), None), - }); - cp_spawn_waiter( - handle, - waiter, - timeout.map(|timeout| (timeout, kill_signal)), - ); - crate::event_pump::js_notify_main_thread(); - cp - } - Err(e) => { - // Could not spawn at all (ENOENT, EACCES…). Build the same callback - // error the sync path produced and fire it on a later tick. - let run = CpRun { - stdout: Vec::new(), - stderr: Vec::new(), - stdout_piped: true, - stderr_piped: true, - code: None, - signal: None, - pid: None, - spawn_error: Some((super::cp_io_error_code(&e), e.to_string())), - run_error: None, - }; - let (err, out, errout) = - super::cp_exec_callback_args(&run, &run_options, &cmd_str, &file, &mode); - cp_defer_exec_callback(cb_val, err, out, errout); - cp - } - } -} - -/// Append a chunk to an exec child's captured stdout/stderr. Returns -/// `Some(kill_signal)` the first time the buffer overruns `maxBuffer`, so the -/// caller terminates the child (Node kills on a `maxBuffer` breach). Once -/// overrun, further chunks are dropped to bound memory — the one chunk that -/// crossed the limit keeps `len > maxBuffer`, which the output/error shapers -/// use to truncate and to name the offending stream. -fn cp_exec_accumulate(exec: &mut CpExecPending, stderr: bool, bytes: &[u8]) -> Option { - if exec.exceeded { - return None; - } - let max = exec.run_options.max_buffer; - let buf = if stderr { - &mut exec.stderr - } else { - &mut exec.stdout - }; - buf.extend_from_slice(bytes); - if buf.len() > max { - exec.exceeded = true; - return Some(exec.run_options.kill_signal()); - } - None -} - -/// Build the `CpRun` for a finished exec child and fire its callback. -fn cp_exec_fire_close(exec: Box, code: Option, signal: Option, pid: i32) { - let exec = *exec; - let run_error = if exec.exceeded { - Some(CpRunError::MaxBuffer) - } else if exec.timed_out { - Some(CpRunError::Timeout) - } else { - None - }; - // A timeout maps to Node's `(code: null, signal: killSignal)` shape. - let (code, signal) = if exec.timed_out { - (None, Some(exec.run_options.kill_signal())) - } else { - (code, signal) - }; - let run = CpRun { - stdout: exec.stdout, - stderr: exec.stderr, - stdout_piped: true, - stderr_piped: true, - code, - signal, - pid: Some(pid as u32), - spawn_error: None, - run_error, - }; - let (err, out, errout) = - super::cp_exec_callback_args(&run, &exec.run_options, &exec.cmd, &exec.file, &exec.mode); - let cb = crate::fs::extract_closure_ptr(f64::from_bits(exec.cb_bits)); - if !cb.is_null() { - crate::closure::js_closure_call3( - cb, - crate::closure::plain_call_receiver(), - err, - out, - errout, - ); - } -} - -/// Schedule a deferred `cb(err, stdout, stderr)` on the next `setImmediate` -/// macrotask — used for the empty-command, already-aborted, and spawn-failure -/// exec paths, which have no live child to ride the reactor close. The boxed -/// values are kept reachable by the immediate closure's captures. -pub(super) fn cp_defer_exec_callback(cb_val: f64, err: f64, stdout: f64, stderr: f64) { - cp_register_reactor_arities(); - let deferred = js_closure_alloc(cp_exec_cb_thunk as *const u8, 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); - js_closure_set_capture_ptr(deferred, 3, stderr.to_bits() as i64); - crate::timer::js_set_immediate_callback(deferred as i64); -} - -/// The deferred-callback thunk: slots 0..4 capture `cb`, `err`, `stdout`, -/// `stderr`. Takes no JS args. -extern "C" fn cp_exec_cb_thunk( - closure: *const ClosureHeader, - _this: crate::closure::JsThis, -) -> f64 { - let cb = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); - let err = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); - let out = f64::from_bits(js_closure_get_capture_ptr(closure, 2) as u64); - let errout = f64::from_bits(js_closure_get_capture_ptr(closure, 3) as u64); - let cbptr = crate::fs::extract_closure_ptr(cb); - if !cbptr.is_null() { - crate::closure::js_closure_call3( - cbptr, - crate::closure::plain_call_receiver(), - err, - out, - errout, - ); - } - cp_undefined() -} - // ============================================================================ // 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 new file mode 100644 index 0000000000..27bc4b2ef7 --- /dev/null +++ b/crates/perry-runtime/src/child_process/reactor/exec_async.rs @@ -0,0 +1,338 @@ +//! Async `exec`/`execFile` — run off the main thread, call back on a later +//! tick (#4912). +//! +//! Split out of `reactor.rs` only to keep that file under the repository's +//! 2000-line cap (`scripts/check_file_size.sh`); same subject as before, just +//! its own file (mirrors how `stdin.rs` / `streams.rs` were split out). + +use super::*; + +// ============================================================================ +// Async `exec` / `execFile` — run off the main thread, call back on a later +// tick (#4912). +// ============================================================================ + +/// Launch `command` (already shaped as `sh -c ` for `exec` or `file +/// args…` for `execFile`, with `cwd`/`env` applied) without blocking the main +/// thread, capturing stdout/stderr on background reader threads. When the child +/// exits, the pump fires `cb_val(err, stdout, stderr)` on a later event-loop +/// tick. A spawn failure (e.g. `ENOENT`) is reported the same way, deferred to +/// the next tick. Always returns `undefined` (the callback form's return). +pub(in crate::child_process) fn cp_exec_async( + mut command: Command, + cmd_str: String, + public_spawnfile: Option, + cb_val: f64, + 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. + let file = { + let program = command.get_program().to_string_lossy(); + #[cfg(unix)] + if program == "sh" { + "/bin/sh".to_string() + } else { + program.into_owned() + } + #[cfg(not(unix))] + program.into_owned() + }; + + // exec/execFile capture stdout+stderr and never feed stdin. + command.stdin(Stdio::null()); + command.stdout(Stdio::piped()); + command.stderr(Stdio::piped()); + + let timeout = run_options.timeout(); + let kill_signal = run_options.kill_signal(); + 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)), + ( + "removeAllListeners", + cp_cast1(cp_method_remove_all_listeners), + ), + ("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); + cp_set_field(cp, b"stderr", stderr_obj); + cp_set_field(cp, b"stdin", TAG_NULL_F64); + let mut stdio = crate::array::js_array_alloc(3); + stdio = crate::array::js_array_push_f64(stdio, TAG_NULL_F64); + stdio = crate::array::js_array_push_f64(stdio, stdout_obj); + stdio = crate::array::js_array_push_f64(stdio, stderr_obj); + cp_set_field(cp, b"stdio", cp_box_ptr(stdio as *const u8)); + cp_set_field(cp, b"exitCode", TAG_NULL_F64); + cp_set_field(cp, b"signalCode", TAG_NULL_F64); + cp_set_field(cp, b"killed", TAG_FALSE_F64); + cp_set_field(cp, b"connected", TAG_FALSE_F64); + // `cp_command_for_program` may resolve a bare executable against PATH to + // keep macOS on `posix_spawn`, but Node exposes the caller's original + // `execFile` spelling through `ChildProcess.spawnfile`. Keep the resolved + // path above for launch-error metadata and publish the explicit spelling + // when that API supplied one. `exec` continues to expose its real shell. + cp_set_field( + cp, + b"spawnfile", + cp_box_string(public_spawnfile.as_deref().unwrap_or(&file)), + ); + + match command.spawn() { + Ok(mut child) => { + let pid = child.id(); + // Duplicate the process handle BEFORE `child` moves to the waiter + // thread — the kill paths act on it instead of the recyclable pid. + #[cfg(windows)] + let win_proc_handle = cp_win_dup_proc_handle(&child); + let stdout_pipe = child.stdout.take(); + let stderr_pipe = child.stderr.take(); + let stdout_open = stdout_pipe.is_some(); + let stderr_open = stderr_pipe.is_some(); + let handle = CP_NEXT_LIVE_ID.fetch_add(1, Ordering::SeqCst); + cp_set_field(cp, b"pid", pid as f64); + cp_set_field(cp, b"__cpHandle", handle as f64); + cp_set_field(stdout_obj, b"__cpHandle", handle as f64); + cp_set_field(stderr_obj, b"__cpHandle", handle as f64); + + let exec = Box::new(CpExecPending { + cb_bits: cb_val.to_bits(), + stdout: Vec::new(), + stderr: Vec::new(), + run_options, + mode, + cmd: cmd_str, + file, + exceeded: false, + timed_out: false, + }); + let (process_ids, pipe_ids) = cp_init_async_resources(cp, None, stdout_obj, stderr_obj); + + { + register_thread_exit_release(); + let mut guard = cp_live_lock(); + let map = guard.get_or_insert_with(HashMap::new); + map.insert( + handle, + LiveChild { + cp_bits: cp.to_bits(), + process_ids, + pipe_ids, + pipe_bits: [ + TAG_NULL_F64.to_bits(), + stdout_obj.to_bits(), + stderr_obj.to_bits(), + ], + pid: pid as i32, + stdin: None, + stdout_open, + stderr_open, + stdout_eof_pending: false, + extra_open: Vec::new(), + loop_streams: Vec::new(), + spawned: false, + exited: None, + exit_emitted: false, + closed: false, + refed: true, + ipc_send: None, + ipc_advanced: false, + abort_signal_bits: 0, + abort_listener_bits: 0, + abort_kill_signal: libc_sigterm(), + #[cfg(windows)] + win_kill_signal: None, + #[cfg(windows)] + win_proc_handle, + exec: Some(exec), + }, + ); + } + crate::stdlib_pump::register_runtime_pump(0, cp_reactor_pump_extern); + CP_LIVE_COUNT.fetch_add(1, Ordering::SeqCst); + CP_REFED_COUNT.fetch_add(1, Ordering::SeqCst); + + if let Some(o) = stdout_pipe { + cp_spawn_reader(handle, cp_pipe_from_child_stdout(o), 1); + } + if let Some(e) = stderr_pipe { + cp_spawn_reader(handle, cp_pipe_from_child_stderr(e), 2); + } + let waiter: CpWaiter = Box::new(move || match child.wait() { + Ok(status) => { + #[cfg(unix)] + let signal = { + use std::os::unix::process::ExitStatusExt; + status.signal() + }; + #[cfg(not(unix))] + let signal: Option = None; + (status.code(), signal) + } + Err(_) => (Some(-1), None), + }); + cp_spawn_waiter( + handle, + waiter, + timeout.map(|timeout| (timeout, kill_signal)), + ); + crate::event_pump::js_notify_main_thread(); + cp + } + Err(e) => { + // Could not spawn at all (ENOENT, EACCES…). Build the same callback + // error the sync path produced and fire it on a later tick. + let run = CpRun { + stdout: Vec::new(), + stderr: Vec::new(), + stdout_piped: true, + stderr_piped: true, + code: None, + signal: None, + pid: None, + spawn_error: Some((super::super::cp_io_error_code(&e), e.to_string())), + run_error: None, + }; + let (err, out, errout) = + super::super::cp_exec_callback_args(&run, &run_options, &cmd_str, &file, &mode); + cp_defer_exec_callback(cb_val, err, out, errout); + cp + } + } +} + +/// Append a chunk to an exec child's captured stdout/stderr. Returns +/// `Some(kill_signal)` the first time the buffer overruns `maxBuffer`, so the +/// caller terminates the child (Node kills on a `maxBuffer` breach). Once +/// overrun, further chunks are dropped to bound memory — the one chunk that +/// crossed the limit keeps `len > maxBuffer`, which the output/error shapers +/// use to truncate and to name the offending stream. +pub(super) fn cp_exec_accumulate( + exec: &mut CpExecPending, + stderr: bool, + bytes: &[u8], +) -> Option { + if exec.exceeded { + return None; + } + let max = exec.run_options.max_buffer; + let buf = if stderr { + &mut exec.stderr + } else { + &mut exec.stdout + }; + buf.extend_from_slice(bytes); + if buf.len() > max { + exec.exceeded = true; + return Some(exec.run_options.kill_signal()); + } + None +} + +/// Build the `CpRun` for a finished exec child and fire its callback. +pub(super) fn cp_exec_fire_close( + exec: Box, + code: Option, + signal: Option, + pid: i32, +) { + let exec = *exec; + let run_error = if exec.exceeded { + Some(CpRunError::MaxBuffer) + } else if exec.timed_out { + Some(CpRunError::Timeout) + } else { + None + }; + // A timeout maps to Node's `(code: null, signal: killSignal)` shape. + let (code, signal) = if exec.timed_out { + (None, Some(exec.run_options.kill_signal())) + } else { + (code, signal) + }; + let run = CpRun { + stdout: exec.stdout, + stderr: exec.stderr, + stdout_piped: true, + stderr_piped: true, + code, + signal, + pid: Some(pid as u32), + spawn_error: None, + run_error, + }; + let (err, out, errout) = super::super::cp_exec_callback_args( + &run, + &exec.run_options, + &exec.cmd, + &exec.file, + &exec.mode, + ); + let cb = crate::fs::extract_closure_ptr(f64::from_bits(exec.cb_bits)); + if !cb.is_null() { + crate::closure::js_closure_call3( + cb, + crate::closure::plain_call_receiver(), + err, + out, + errout, + ); + } +} + +/// Schedule a deferred `cb(err, stdout, stderr)` on the next `setImmediate` +/// macrotask — used for the empty-command, already-aborted, and spawn-failure +/// exec paths, which have no live child to ride the reactor close. The boxed +/// values are kept reachable by the immediate closure's captures. +pub(in crate::child_process) fn cp_defer_exec_callback( + cb_val: f64, + err: f64, + stdout: f64, + stderr: f64, +) { + cp_register_reactor_arities(); + let deferred = js_closure_alloc(cp_exec_cb_thunk as *const u8, 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); + js_closure_set_capture_ptr(deferred, 3, stderr.to_bits() as i64); + crate::timer::js_set_immediate_callback(deferred as i64); +} + +/// The deferred-callback thunk: slots 0..4 capture `cb`, `err`, `stdout`, +/// `stderr`. Takes no JS args. +pub(super) extern "C" fn cp_exec_cb_thunk( + closure: *const ClosureHeader, + _this: crate::closure::JsThis, +) -> f64 { + let cb = f64::from_bits(js_closure_get_capture_ptr(closure, 0) as u64); + let err = f64::from_bits(js_closure_get_capture_ptr(closure, 1) as u64); + let out = f64::from_bits(js_closure_get_capture_ptr(closure, 2) as u64); + let errout = f64::from_bits(js_closure_get_capture_ptr(closure, 3) as u64); + let cbptr = crate::fs::extract_closure_ptr(cb); + if !cbptr.is_null() { + crate::closure::js_closure_call3( + cbptr, + crate::closure::plain_call_receiver(), + err, + out, + errout, + ); + } + cp_undefined() +} From ecf409bc141bc04fcb0296f69e88bc1956f95575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 14:23:31 +0000 Subject: [PATCH 07/17] test(codegen): update export alias identity test for the this-as-a-parameter wrapper ABI The this-as-a-parameter ABI now passes the JS receiver as a second i64 parameter (i64 %this_closure, i64 %js_this) into every installed body, so the wrapper's forwarding call must include %js_this alongside %this_closure. Update the expected call shape; the test still asserts what it always asserted -- function identity through the $-export alias resolves by ID, not by the exported spelling. --- crates/perry-codegen/tests/export_function_alias_identity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/perry-codegen/tests/export_function_alias_identity.rs b/crates/perry-codegen/tests/export_function_alias_identity.rs index 81eb5caa19..043a82604a 100644 --- a/crates/perry-codegen/tests/export_function_alias_identity.rs +++ b/crates/perry-codegen/tests/export_function_alias_identity.rs @@ -79,7 +79,7 @@ fn dollar_export_aliases_resolve_function_identity_not_sanitized_spelling() { .next() .unwrap(); let mut args = if wrapper { - vec!["i64 %this_closure".to_string()] + vec!["i64 %this_closure".to_string(), "i64 %js_this".to_string()] } else { Vec::new() }; From e493c5cfa97ff5518e3428e5e5788d0516f7e581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 16:25:32 +0200 Subject: [PATCH 08/17] changelog: name the fragments after PR #11637 --- ...-typed-js-bodies.md => 11637-perry-ffi-0.6-typed-js-bodies.md} | 0 ...-parameter-stage-1.md => 11637-this-as-a-parameter-stage-1.md} | 0 ...-parameter-stage-2.md => 11637-this-as-a-parameter-stage-2.md} | 0 ...-parameter-stage-3.md => 11637-this-as-a-parameter-stage-3.md} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{perry-ffi-0.6-typed-js-bodies.md => 11637-perry-ffi-0.6-typed-js-bodies.md} (100%) rename changelog.d/{this-as-a-parameter-stage-1.md => 11637-this-as-a-parameter-stage-1.md} (100%) rename changelog.d/{this-as-a-parameter-stage-2.md => 11637-this-as-a-parameter-stage-2.md} (100%) rename changelog.d/{this-as-a-parameter-stage-3.md => 11637-this-as-a-parameter-stage-3.md} (100%) diff --git a/changelog.d/perry-ffi-0.6-typed-js-bodies.md b/changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md similarity index 100% rename from changelog.d/perry-ffi-0.6-typed-js-bodies.md rename to changelog.d/11637-perry-ffi-0.6-typed-js-bodies.md diff --git a/changelog.d/this-as-a-parameter-stage-1.md b/changelog.d/11637-this-as-a-parameter-stage-1.md similarity index 100% rename from changelog.d/this-as-a-parameter-stage-1.md rename to changelog.d/11637-this-as-a-parameter-stage-1.md diff --git a/changelog.d/this-as-a-parameter-stage-2.md b/changelog.d/11637-this-as-a-parameter-stage-2.md similarity index 100% rename from changelog.d/this-as-a-parameter-stage-2.md rename to changelog.d/11637-this-as-a-parameter-stage-2.md diff --git a/changelog.d/this-as-a-parameter-stage-3.md b/changelog.d/11637-this-as-a-parameter-stage-3.md similarity index 100% rename from changelog.d/this-as-a-parameter-stage-3.md rename to changelog.d/11637-this-as-a-parameter-stage-3.md From 4f62c3f5b1b81c1d8debfb758adb6b62341bc54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 15:15:09 +0000 Subject: [PATCH 09/17] fix(runtime): the WASI closure-arity signature probe is missing the receiver parameter perry_wasi_sig.c ref.test candidate types (double (closure, f64 x k)) predate this-as-a-parameter: every real JS body now takes (closure, this: u64, f64 x k) on wasm32. None of the candidates ever match any more, so wasi_body_params always returns None and dispatch falls back to the caller arity instead of the body real one, under-supplying a body that declares more parameters than its call site -- an indirect-call type mismatch that traps under wasmtime (repro: scripts/wasi_smoke iterators.ts, gen().map(fn) through the shared IteratorPrototype[Symbol.iterator] thunk). Add the missing unsigned long long receiver parameter to every perry_body_N candidate typedef and update the doc comment. --- crates/perry-runtime/src/closure/registry.rs | 6 +- crates/perry-runtime/src/ffi/perry_wasi_sig.c | 78 ++++++++++--------- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index 65f3bf47ce..a08ead8d05 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -436,9 +436,9 @@ fn wasi_exact_arity(func_ptr: *const u8, strategy: DispatchStrategy) -> Dispatch } /// WASI: the number of `f64` parameters `func_ptr` really takes after the -/// closure pointer, asked of the engine with `ref.test` -/// (`ffi/perry_wasi_sig.c`). `None` for a type outside `(closure, f64 x -/// 0..=32) -> f64`. +/// 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" { diff --git a/crates/perry-runtime/src/ffi/perry_wasi_sig.c b/crates/perry-runtime/src/ffi/perry_wasi_sig.c index ec1e05014d..e685919f53 100644 --- a/crates/perry-runtime/src/ffi/perry_wasi_sig.c +++ b/crates/perry-runtime/src/ffi/perry_wasi_sig.c @@ -12,46 +12,54 @@ * (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 *); -typedef double (*perry_body_1)(const ClosureHeader *, double); -typedef double (*perry_body_2)(const ClosureHeader *, double, double); -typedef double (*perry_body_3)(const ClosureHeader *, double, double, double); -typedef double (*perry_body_4)(const ClosureHeader *, double, double, double, double); -typedef double (*perry_body_5)(const ClosureHeader *, double, double, double, double, double); -typedef double (*perry_body_6)(const ClosureHeader *, double, double, double, double, double, double); -typedef double (*perry_body_7)(const ClosureHeader *, double, double, double, double, double, double, double); -typedef double (*perry_body_8)(const ClosureHeader *, double, double, double, double, double, double, double, double); -typedef double (*perry_body_9)(const ClosureHeader *, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_10)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_11)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_12)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_13)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_14)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_15)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_16)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_17)(const ClosureHeader *, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double, double); -typedef double (*perry_body_18)(const ClosureHeader *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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 *, 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); +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, or -1 if its - * type is none of `double (closure, double x k)` for k in 0..=32. */ +/* 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; From ae91cb6aa02917854d4e580b49eae9524a98a30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 20:34:31 +0200 Subject: [PATCH 10/17] gc_effects: regenerate tables for the this-as-a-parameter body ABI --- crates/perry-codegen/src/gc_effects/macos-aarch64.tsv | 10 +++------- crates/perry-codegen/src/gc_effects/windows-x86_64.tsv | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv index 405bf6af51..ce180fd9ad 100644 --- a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv +++ b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv @@ -618,7 +618,6 @@ js_closure_call13 Reenters js_closure_call14 Reenters js_closure_call15 Reenters js_closure_call16 Reenters -js_closure_call1_receiverless Reenters js_closure_call2 Reenters js_closure_call3 Reenters js_closure_call4 Reenters @@ -634,15 +633,15 @@ js_closure_get_capture_bits Leaf js_closure_get_capture_f64 Leaf js_closure_get_capture_ptr Leaf js_closure_get_func Leaf -js_closure_resolve_arrow_direct_call Leaf +js_closure_resolve_plain_direct_call Leaf js_closure_resolve_versioned_loop_direct_call Leaf js_closure_set_box_capture_ptr Leaf js_closure_set_capture_bits Leaf js_closure_set_capture_f64 Leaf js_closure_set_capture_ptr Leaf -js_closure_unbind_this Reenters js_closure_unbox_callee_checked ThrowOnly js_closure_unbox_callee_checked_rebind Reenters +js_closure_v8_callback Reenters js_cluster_disconnect Reenters js_cluster_emit Reenters js_cluster_event_names Reenters @@ -1450,9 +1449,6 @@ js_i32_box_scope_release Leaf js_i32_box_set Leaf js_ic_diag_note_hit Leaf js_immediate_has_pending Leaf -js_implicit_this_get Leaf -js_implicit_this_get_sloppy Reenters -js_implicit_this_set Leaf js_import_meta_resolve Reenters js_import_meta_resolve_value Reenters js_in_operator Reenters @@ -3228,6 +3224,7 @@ js_text_encoder_encode_llvm Reenters js_text_encoder_new Reenters js_text_encoder_stream_new Reenters js_text_encoding_stream_new Reenters +js_this_coerce_sloppy Reenters js_thread_has_pending Leaf js_thread_parallel_filter Reenters js_thread_parallel_map Reenters @@ -3920,7 +3917,6 @@ perry_i18n_plural_category Reenters perry_i18n_set_currencies Leaf perry_i18n_set_locale_index Leaf perry_i18n_set_plural_locales Leaf -perry_implicit_this_cell Leaf perry_init_guard_check_and_set Leaf perry_key_content_hash Leaf perry_macos_bundle_chdir 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 8334ff53d5..2f10f8f3de 100644 --- a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv @@ -618,7 +618,6 @@ js_closure_call13 Reenters js_closure_call14 Reenters js_closure_call15 Reenters js_closure_call16 Reenters -js_closure_call1_receiverless Reenters js_closure_call2 Reenters js_closure_call3 Reenters js_closure_call4 Reenters @@ -634,15 +633,15 @@ js_closure_get_capture_bits Leaf js_closure_get_capture_f64 Leaf js_closure_get_capture_ptr Leaf js_closure_get_func Leaf -js_closure_resolve_arrow_direct_call Leaf +js_closure_resolve_plain_direct_call Leaf js_closure_resolve_versioned_loop_direct_call Leaf js_closure_set_box_capture_ptr Leaf js_closure_set_capture_bits Leaf js_closure_set_capture_f64 Leaf js_closure_set_capture_ptr Leaf -js_closure_unbind_this Reenters js_closure_unbox_callee_checked Reenters js_closure_unbox_callee_checked_rebind Reenters +js_closure_v8_callback Reenters js_cluster_disconnect Reenters js_cluster_emit Reenters js_cluster_event_names Reenters @@ -1450,9 +1449,6 @@ js_i32_box_scope_release Leaf js_i32_box_set Leaf js_ic_diag_note_hit Leaf js_immediate_has_pending Leaf -js_implicit_this_get Leaf -js_implicit_this_get_sloppy Reenters -js_implicit_this_set Leaf js_import_meta_resolve Reenters js_import_meta_resolve_value Reenters js_in_operator Reenters @@ -3228,6 +3224,7 @@ js_text_encoder_encode_llvm Reenters js_text_encoder_new Reenters js_text_encoder_stream_new Reenters js_text_encoding_stream_new Reenters +js_this_coerce_sloppy Reenters js_thread_has_pending Leaf js_thread_parallel_filter Reenters js_thread_parallel_map Reenters @@ -3914,7 +3911,6 @@ perry_i18n_plural_category Reenters perry_i18n_set_currencies Leaf perry_i18n_set_locale_index Leaf perry_i18n_set_plural_locales Leaf -perry_implicit_this_cell Leaf perry_init_guard_check_and_set Leaf perry_key_content_hash Leaf perry_macos_bundle_chdir Leaf From d1959d4faa092ee7982342dd910685e9e2e74b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 28 Sep 2026 18:57:15 +0000 Subject: [PATCH 11/17] perf: a function object points to its body's static JsFunctionInfo; delete the closure-body registry Every JS function body has ONE static, immutable `JsFunctionInfo` (perry-abi): 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. The function object's header word at +8 points to it, so every per-body fact is one load from the object and nothing is looked up by code address. - Codegen emits `@$info` as a constant next to each body, and every allocation names the info. Another module's info is declared `external`, never copied, 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!` in the runtime and stdlib, `perry_ffi::js_function_info!` in addons): the parameter count comes from the body's type, and any other signature does not compile. - Deleted: CLOSURE_BODY_REGISTRY, TRUSTED_TARGETS, DISPATCH_RECENT, the twelve `js_register_closure_*` entries with their call sites, and every module-init registration call. A class constructor's rest position travels with its class constructor flags. - perry-ffi 0.6: `alloc_closure` takes `&'static JsFunctionInfo`; `register_closure_arity` / `register_closure_rest` are gone. - `ClosureHeader::code()` proves the cell is a live function object before it loads through the info: comparing an arbitrary receiver's code with a known body stays a compare. - WASI: the `ref.test` body-signature probe (ffi/perry_wasi_sig.c and its build step) is deleted. The info's parameter count is the body's real one, so dispatch no longer infers it from the table entry's wasm type. - check_js_body_call_funnel.py refuses any registrar or deleted code-keyed table name in the crates. The GC holder / registry-lifetime inventories drop the entries for the deleted registration flags and tables, and the PASS1_MARKED census pin is re-audited for the removed census row. - test-files/test_gap_fn_info_facts.ts reads each info field through generic calls, against node. Layout: the info pointer replaces the code pointer at +8 (one dependent load on a generic indirect call through the runtime; the method-site hit caches code and is unchanged). --- .../11637-perry-ffi-0.6-typed-js-bodies.md | 20 +- changelog.d/fn-info-closure-body-registry.md | 37 + crates/perry-abi/src/lib.rs | 279 +++- crates/perry-codegen/src/block.rs | 48 + crates/perry-codegen/src/codegen/arguments.rs | 11 +- crates/perry-codegen/src/codegen/artifacts.rs | 14 +- .../src/codegen/closure_collect.rs | 12 +- crates/perry-codegen/src/codegen/entry.rs | 5 +- crates/perry-codegen/src/codegen/helpers.rs | 28 +- crates/perry-codegen/src/codegen/mod.rs | 4 + .../perry-codegen/src/codegen/string_pool.rs | 458 +++--- .../src/codegen/trusted_box_callback_tests.rs | 35 +- crates/perry-codegen/src/expr/arrays_finds.rs | 6 +- crates/perry-codegen/src/expr/closure.rs | 36 +- .../perry-codegen/src/expr/dyn_extern_i18n.rs | 19 +- .../src/expr/logical_collections.rs | 6 +- crates/perry-codegen/src/expr/method_site.rs | 30 +- crates/perry-codegen/src/expr/property_get.rs | 4 +- crates/perry-codegen/src/expr/super_method.rs | 8 +- crates/perry-codegen/src/fn_info.rs | 306 ++++ crates/perry-codegen/src/function.rs | 5 + .../src/gc_effects/linux-x86_64.tsv | 12 - .../src/gc_effects/macos-aarch64.tsv | 12 - .../src/gc_effects/windows-x86_64.tsv | 12 - crates/perry-codegen/src/lib.rs | 1 + .../src/lower_call/early_branches.rs | 38 +- .../perry-codegen/src/lower_call/func_ref.rs | 4 +- .../lower_call/native_table/thread_lodash.rs | 2 +- .../property_get/imported_object.rs | 9 +- crates/perry-codegen/src/module.rs | 54 + .../runtime_decls/stdlib_ffi/language_core.rs | 13 +- .../src/runtime_decls/strings.rs | 43 +- crates/perry-codegen/src/target_layout.rs | 11 +- .../perry-codegen/src/wasm32/runtime_abi.tsv | 18 +- crates/perry-ext-events/src/dispatch_ext.rs | 2 +- crates/perry-ext-events/src/lib.rs | 74 +- .../perry-ext-events/src/module_iterators.rs | 24 +- crates/perry-ext-events/src/module_on.rs | 21 +- crates/perry-ext-events/src/tests.rs | 2 +- crates/perry-ext-http/src/agent.rs | 6 +- crates/perry-ext-http/src/client_abort.rs | 3 +- .../src/client_request_surface.rs | 10 +- crates/perry-ext-http/src/lib.rs | 16 +- .../src/server/server/upgrade_tests.rs | 6 +- crates/perry-ext-http/src/server/types.rs | 4 +- crates/perry-ext-net/src/pipe.rs | 23 +- crates/perry-ext-net/src/socket_events.rs | 7 +- crates/perry-ext-typescript/src/bun.rs | 33 +- crates/perry-ext-ws/src/tests.rs | 20 +- crates/perry-ext-zlib/src/lib.rs | 16 +- crates/perry-ffi/src/closure.rs | 103 +- crates/perry-ffi/src/lib.rs | 8 +- crates/perry-ffi/src/types.rs | 9 +- crates/perry-runtime/build.rs | 23 - .../src/array/collection_tag_tests.rs | 2 +- .../src/array/generic/class_mutator_tests.rs | 1 + .../perry-runtime/src/array/iter_methods.rs | 11 +- crates/perry-runtime/src/array/iterator.rs | 39 +- crates/perry-runtime/src/array/tests.rs | 4 +- .../src/array/typed_array_receiver_tests.rs | 45 +- crates/perry-runtime/src/async_hooks.rs | 34 +- .../src/async_hooks/context_snapshots.rs | 6 +- .../src/async_hooks/provider_ffi.rs | 3 +- .../src/async_hooks/test_support.rs | 2 +- crates/perry-runtime/src/builtins/console.rs | 19 +- .../perry-runtime/src/builtins/formatting.rs | 4 +- .../src/builtins/formatting/util_format.rs | 4 +- .../perry-runtime/src/bun_compat/cli_utils.rs | 52 +- crates/perry-runtime/src/bun_compat/glob.rs | 15 +- crates/perry-runtime/src/bun_compat/mod.rs | 81 +- crates/perry-runtime/src/bun_compat/plugin.rs | 22 +- .../src/bun_compat/self_global.rs | 8 +- crates/perry-runtime/src/bun_compat/spawn.rs | 118 +- crates/perry-runtime/src/bun_ffi/callback.rs | 8 +- crates/perry-runtime/src/bun_ffi/dlopen.rs | 70 +- crates/perry-runtime/src/bun_ffi/read.rs | 31 +- .../src/child_process/builder.rs | 197 +-- .../src/child_process/emitter.rs | 34 +- .../child_process/emitter/relocation_tests.rs | 10 +- .../perry-runtime/src/child_process/exec.rs | 18 +- .../src/child_process/failed_spawn.rs | 4 +- .../perry-runtime/src/child_process/fork.rs | 71 +- crates/perry-runtime/src/child_process/mod.rs | 6 +- .../src/child_process/reactor.rs | 57 +- .../src/child_process/reactor/exec_async.rs | 43 +- .../child_process/reactor/lifecycle_tests.rs | 6 +- crates/perry-runtime/src/closure/alloc.rs | 125 +- crates/perry-runtime/src/closure/body_call.rs | 89 ++ crates/perry-runtime/src/closure/dispatch.rs | 15 +- .../src/closure/dispatch/bound.rs | 46 +- .../bound/function_method_value_tests.rs | 2 +- .../src/closure/dispatch/calln.rs | 20 +- .../src/closure/dispatch/direct.rs | 160 +- .../src/closure/dispatch/validate.rs | 96 +- .../src/closure/dispatch/value_call.rs | 54 +- .../src/closure/dynamic_props.rs | 7 +- crates/perry-runtime/src/closure/mod.rs | 41 +- .../src/closure/receiver_param_tests.rs | 27 +- crates/perry-runtime/src/closure/registry.rs | 1375 ++--------------- crates/perry-runtime/src/closure/shape.rs | 86 +- crates/perry-runtime/src/closure/tests.rs | 9 +- crates/perry-runtime/src/cluster.rs | 124 +- crates/perry-runtime/src/dgram.rs | 113 +- crates/perry-runtime/src/dgram/net.rs | 6 +- crates/perry-runtime/src/dgram/ops.rs | 16 +- crates/perry-runtime/src/disposable.rs | 14 +- crates/perry-runtime/src/dns.rs | 15 +- crates/perry-runtime/src/dyn_eval/interp.rs | 6 +- crates/perry-runtime/src/dyn_eval/tests.rs | 3 +- crates/perry-runtime/src/error.rs | 30 +- .../perry-runtime/src/error_subclass_stack.rs | 14 +- crates/perry-runtime/src/event_target.rs | 69 +- crates/perry-runtime/src/ffi/perry_wasi_sig.c | 98 -- crates/perry-runtime/src/fs/deferred.rs | 2 +- .../src/fs/dir_glob_watch/watch.rs | 195 ++- crates/perry-runtime/src/fs/fd_ops.rs | 75 +- crates/perry-runtime/src/fs/filehandle.rs | 212 ++- .../src/fs/mkdtemp_disposable.rs | 12 +- crates/perry-runtime/src/fs/stats.rs | 4 +- crates/perry-runtime/src/fs/stream.rs | 321 ++-- .../src/fs/stream/options_init.rs | 40 +- .../perry-runtime/src/fs/stream/read_turn.rs | 5 +- .../src/fs/stream/utf8_stream.rs | 22 +- .../perry-runtime/src/fs/stream/write_turn.rs | 5 +- crates/perry-runtime/src/gc/census.rs | 1 - crates/perry-runtime/src/gc/tests/alloc.rs | 5 +- .../tests/array_pointer_slot_enumeration.rs | 4 +- crates/perry-runtime/src/gc/tests/barrier.rs | 13 +- crates/perry-runtime/src/gc/tests/copying.rs | 27 +- .../gc/tests/copying/pointer_publish_7154.rs | 4 +- .../gc/tests/copying/weak_holder_registry.rs | 7 +- .../src/gc/tests/copying/weak_semantics.rs | 2 +- .../perry-runtime/src/gc/tests/cycle_state.rs | 4 +- .../layout_trace/object_closure_slots.rs | 4 +- .../src/gc/tests/rooted_container_values.rs | 2 +- .../src/gc/tests/rooted_define_property.rs | 5 +- .../src/gc/tests/rooted_for_in.rs | 12 +- crates/perry-runtime/src/gc/tests/roots.rs | 2 +- .../runtime_roots/arraylike_callbacks.rs | 2 +- .../runtime_roots/call_argument_lists.rs | 22 +- .../tests/runtime_roots/callback_scanners.rs | 55 +- .../generator_attach_prototype.rs | 13 +- .../runtime_roots/hook_dispatch_handles.rs | 52 +- .../tests/runtime_roots/json_record_output.rs | 6 +- .../tests/runtime_roots/perex_construction.rs | 12 +- .../gc/tests/runtime_roots/perex_dispatch.rs | 56 +- .../src/gc/tests/runtime_roots/perex_glob.rs | 14 +- .../gc/tests/runtime_roots/perex_match_all.rs | 80 +- .../tests/runtime_roots/perex_match_search.rs | 33 +- .../gc/tests/runtime_roots/perex_public.rs | 8 +- .../gc/tests/runtime_roots/perex_replace.rs | 53 +- .../runtime_roots/perex_replace_direct.rs | 14 +- .../src/gc/tests/runtime_roots/perex_split.rs | 154 +- .../tests/runtime_roots/regexp_last_index.rs | 3 +- .../runtime_roots/side_table_scanners.rs | 6 + .../gc/tests/runtime_roots/sort_collection.rs | 8 +- .../runtime_roots/string_normalize_form.rs | 3 +- .../runtime_roots/thenable_assimilation.rs | 7 +- .../tests/runtime_roots/transient_handles.rs | 11 +- .../perry-runtime/src/gc/tests/step_bounds.rs | 2 +- crates/perry-runtime/src/gc/tests/support.rs | 4 +- crates/perry-runtime/src/intl.rs | 175 ++- .../perry-runtime/src/intl/display_names.rs | 9 +- .../perry-runtime/src/intl/duration_format.rs | 12 +- crates/perry-runtime/src/intl/install.rs | 18 +- .../src/intl/list_relative_plural.rs | 8 +- crates/perry-runtime/src/intl/locale.rs | 149 +- .../perry-runtime/src/intl/method_install.rs | 27 +- crates/perry-runtime/src/intl/segmenter.rs | 8 +- .../perry-runtime/src/intl/segments_view.rs | 12 +- crates/perry-runtime/src/intl/subclass.rs | 3 +- .../src/iterator_helpers/tests.rs | 107 +- .../src/json/replacer_hidden_fields_tests.rs | 6 +- .../src/json_tape/cached_read.rs | 2 +- crates/perry-runtime/src/jsx.rs | 2 +- .../perry-runtime/src/map_tombstone_tests.rs | 10 +- crates/perry-runtime/src/messaging.rs | 141 +- crates/perry-runtime/src/module_require.rs | 29 +- .../src/module_require/import_meta_resolve.rs | 8 +- .../src/node_api_host/functions.rs | 15 +- .../src/node_api_host/v10_tests.rs | 8 +- crates/perry-runtime/src/node_inspector.rs | 86 +- crates/perry-runtime/src/node_repl.rs | 52 +- crates/perry-runtime/src/node_stream.rs | 89 +- .../src/node_stream/async_iterator.rs | 91 +- .../async_iterator/foreign_eof_tests.rs | 2 - .../src/node_stream/readable_from_promises.rs | 4 +- .../src/node_stream_constructors.rs | 37 - .../src/node_stream_constructors/builders.rs | 10 +- .../node_stream_constructors/introspection.rs | 5 +- .../src/node_stream_constructors/pipeline.rs | 29 +- .../node_stream_constructors/web_adapter.rs | 168 +- .../src/node_stream_destroy_state.rs | 8 +- .../perry-runtime/src/node_stream_dispatch.rs | 182 +-- .../src/node_stream_duplex_methods.rs | 139 +- .../src/node_stream_event_emitter.rs | 5 +- .../src/node_stream_iter_helpers.rs | 66 +- .../perry-runtime/src/node_stream_pipeline.rs | 51 +- .../src/node_stream_readwrite.rs | 232 ++- .../src/node_stream_state_tests.rs | 7 +- .../src/node_stream_state_view.rs | 12 +- crates/perry-runtime/src/node_stream_tests.rs | 173 +-- .../src/node_stream_tests_extra.rs | 218 +-- .../perry-runtime/src/node_submodules/blob.rs | 118 +- .../src/node_submodules/consumers.rs | 4 +- .../src/node_submodules/diagnostics.rs | 109 +- .../src/node_submodules/diagnostics_tail.rs | 36 +- .../src/node_submodules/fs_promises.rs | 81 +- .../perry-runtime/src/node_submodules/mod.rs | 405 +++-- .../src/node_submodules/stream_promises.rs | 44 +- .../perry-runtime/src/node_submodules/test.rs | 218 ++- .../test_metadata_unit_tests.rs | 2 +- .../node_submodules/test_once_unit_tests.rs | 6 +- .../src/node_submodules/test_property.rs | 34 +- .../src/node_submodules/test_reporters.rs | 2 +- .../src/node_submodules/test_runner.rs | 4 +- .../src/node_submodules/test_snapshot.rs | 4 +- .../src/node_submodules/test_unit_tests.rs | 2 +- .../src/node_submodules/tests.rs | 12 +- .../src/node_submodules/timers.rs | 6 +- .../src/node_submodules/trace_events.rs | 32 +- crates/perry-runtime/src/node_test.rs | 36 +- crates/perry-runtime/src/node_v8.rs | 2 +- crates/perry-runtime/src/node_vm.rs | 20 +- crates/perry-runtime/src/node_vm/modules.rs | 29 +- .../src/object/accessor_pair_tests.rs | 2 +- crates/perry-runtime/src/object/arguments.rs | 10 +- crates/perry-runtime/src/object/assert.rs | 10 +- .../src/object/async_generator_queue.rs | 65 +- .../src/object/bound_method_receiver_tests.rs | 1 + .../src/object/class_constructors.rs | 100 +- .../perry-runtime/src/object/class_image.rs | 11 +- .../src/object/class_registry.rs | 2 +- .../src/object/class_registry/class_meta.rs | 2 +- .../src/object/class_registry/construct.rs | 13 +- .../object/class_registry/decl_accessors.rs | 7 +- .../class_registry/function_prototype.rs | 8 +- .../object/class_registry/parent_static.rs | 10 +- .../src/object/class_registry/registration.rs | 90 +- .../src/object/class_registry/state.rs | 19 +- .../class_registry/static_accessor_attrs.rs | 10 +- .../src/object/collection_proto_thunks.rs | 151 +- .../src/object/dataview_proto_thunks.rs | 117 +- .../src/object/date_proto_thunks.rs | 249 ++- .../perry-runtime/src/object/descriptors.rs | 3 +- .../src/object/descriptors/builders.rs | 14 +- .../src/object/disposable_proto_thunks.rs | 83 +- .../src/object/field_get_set/enumeration.rs | 3 +- .../field_get_set/get_field_by_name_tail.rs | 5 +- .../has_property/evaluation_accessor_tests.rs | 1 + .../has_property/function_presence_tests.rs | 8 +- .../perry-runtime/src/object/global_this.rs | 7 +- .../array_error/apply_args_tests.rs | 3 +- .../src/object/global_this/bigint_promise.rs | 22 + .../src/object/global_this/builtin_thunks.rs | 5 +- .../src/object/global_this/ctor_thunks.rs | 66 +- .../src/object/global_this/fetch_globals.rs | 4 +- .../src/object/global_this/generator.rs | 84 +- .../src/object/global_this/install_static.rs | 521 ++++--- .../src/object/global_this/math_temporal.rs | 371 +++-- .../src/object/global_this/populate.rs | 288 ++-- .../src/object/global_this/proto_methods.rs | 359 +++-- .../src/object/global_this/typed_array.rs | 37 +- .../src/object/global_this_webassembly.rs | 133 +- .../src/object/inherited_read_cache_tests.rs | 4 +- .../src/object/iterator_prototypes.rs | 56 +- .../src/object/literal_constructor.rs | 2 +- .../perry-runtime/src/object/method_site.rs | 71 +- .../src/object/native_call_method.rs | 8 +- .../closure_override_tests.rs | 7 +- .../code_point_at_dispatch_tests.rs | 4 +- .../dispatch_arg_coercion_tests.rs | 7 +- .../native_call_method/function_shape.rs | 22 +- .../native_call_method/proto_dispatch.rs | 2 +- .../src/object/native_get/tests.rs | 4 +- .../perry-runtime/src/object/native_module.rs | 6 +- .../native_module/async_hooks_exports.rs | 6 +- .../object/native_module/callable_exports.rs | 68 +- .../callable_exports/buffer_species.rs | 14 +- .../callable_exports/module_cjs.rs | 56 +- .../native_module/namespace_builders.rs | 27 +- .../src/object/native_module/web_locks.rs | 26 +- .../src/object/native_module_stream.rs | 7 +- .../src/object/native_this_alias.rs | 5 +- .../object_ops/define_class_accessor.rs | 11 +- .../src/object/object_ops/define_property.rs | 2 +- .../object/object_ops/descriptor_helpers.rs | 5 +- .../src/object/object_ops/keys_array.rs | 2 +- .../src/object/own_override_push_tests.rs | 7 +- .../src/object/primitive_proto_thunks.rs | 107 +- .../perry-runtime/src/object/property_key.rs | 4 +- .../src/object/regex_proto_thunks.rs | 82 +- .../src/object/reserved_floor.rs | 6 +- .../object/restricted_function_store_tests.rs | 14 +- .../src/object/string_proto_thunks.rs | 43 +- .../src/object/temporal_proto.rs | 10 +- crates/perry-runtime/src/object/tests.rs | 45 +- .../src/object/typed_array_proto_thunks.rs | 186 ++- crates/perry-runtime/src/object/util_types.rs | 9 +- .../src/object/weakref_proto_thunks.rs | 35 +- .../src/object/websocket_global.rs | 4 +- .../src/os/os_process_emitter.rs | 6 +- .../perry-runtime/src/os_process_streams.rs | 115 +- crates/perry-runtime/src/perf_hooks.rs | 6 +- .../src/perf_hooks/prototypes.rs | 113 +- .../perry-runtime/src/perf_hooks/timerify.rs | 17 +- crates/perry-runtime/src/process.rs | 29 +- crates/perry-runtime/src/process/env_misc.rs | 2 +- .../perry-runtime/src/process/finalization.rs | 18 +- crates/perry-runtime/src/process/ipc.rs | 57 +- .../perry-runtime/src/process/node_module.rs | 25 +- .../src/process/node_module/source_map.rs | 30 +- .../perry-runtime/src/process/permission.rs | 12 +- crates/perry-runtime/src/process/report.rs | 12 +- .../perry-runtime/src/promise/assimilate.rs | 45 +- .../perry-runtime/src/promise/async_step.rs | 22 +- .../perry-runtime/src/promise/combinators.rs | 99 +- .../src/promise/combinators_tests.rs | 12 +- .../perry-runtime/src/promise/microtasks.rs | 4 +- .../src/promise/microtasks/empty.rs | 12 +- .../src/promise/rejection_loop_tests.rs | 2 +- .../promise/resolving_function_kind_tests.rs | 2 +- crates/perry-runtime/src/promise/scanners.rs | 4 +- .../src/promise/spec_combinators.rs | 115 +- crates/perry-runtime/src/promise/then.rs | 65 +- crates/perry-runtime/src/proxy.rs | 10 +- .../perry-runtime/src/proxy/reflect_misc.rs | 2 +- crates/perry-runtime/src/pty/mod.rs | 89 +- crates/perry-runtime/src/pty/tests.rs | 23 +- crates/perry-runtime/src/readline_helpers.rs | 2 +- crates/perry-runtime/src/regex/site_test.rs | 23 +- .../perry-runtime/src/set_tombstone_tests.rs | 8 +- .../src/symbol/inherited_accessor_tests.rs | 18 +- crates/perry-runtime/src/symbol/properties.rs | 6 +- crates/perry-runtime/src/thread.rs | 57 +- .../src/thread_transfer_guard_tests.rs | 2 +- .../perry-runtime/src/timer/handle_object.rs | 28 +- .../perry-runtime/src/timer/tests_inline.rs | 3 +- crates/perry-runtime/src/tty.rs | 50 +- crates/perry-runtime/src/tui/handle_object.rs | 44 +- crates/perry-runtime/src/tui/hooks.rs | 3 +- .../src/typed_feedback/guards.rs | 46 +- .../perry-runtime/src/typed_feedback/tests.rs | 46 +- crates/perry-runtime/src/url/abort.rs | 32 +- crates/perry-runtime/src/url/prototype.rs | 65 +- crates/perry-runtime/src/url/search_params.rs | 15 +- crates/perry-runtime/src/util_abort.rs | 6 +- crates/perry-runtime/src/util_debuglog.rs | 5 +- crates/perry-runtime/src/util_mime.rs | 1 + crates/perry-runtime/src/util_promisify.rs | 45 +- crates/perry-runtime/src/v8.rs | 8 +- .../src/value/to_string_array.rs | 2 +- crates/perry-runtime/src/wasi.rs | 25 +- .../perry-runtime/src/weakref/test_support.rs | 4 +- crates/perry-runtime/src/web_storage.rs | 74 +- crates/perry-runtime/src/webassembly.rs | 21 +- crates/perry-runtime/src/webassembly_calls.rs | 96 +- crates/perry-runtime/src/webassembly_table.rs | 33 +- crates/perry-stdlib/src/bun_sql.rs | 48 +- .../src/common/dispatch/emitter_als.rs | 6 +- crates/perry-stdlib/src/crypto/random.rs | 6 +- crates/perry-stdlib/src/domain.rs | 16 +- crates/perry-stdlib/src/events.rs | 18 +- crates/perry-stdlib/src/events/events_on.rs | 32 +- .../perry-stdlib/src/events/module_helpers.rs | 3 +- .../perry-stdlib/src/events/once_helpers.rs | 42 +- crates/perry-stdlib/src/fetch/dispatch.rs | 2 +- .../src/fetch/headers_method_value.rs | 4 +- crates/perry-stdlib/src/readline/mod.rs | 33 +- .../perry-stdlib/src/readline/test_support.rs | 6 +- .../src/runtime_thread_exit_tests.rs | 5 +- .../listeners_tests.rs | 6 +- .../stdlib_misc_tests.rs | 2 +- .../streams_tests.rs | 3 +- .../symbols_tests.rs | 2 +- .../src/runtime_thread_exit_tests/ui_tests.rs | 12 +- crates/perry-stdlib/src/sqlite/better.rs | 2 +- crates/perry-stdlib/src/sqlite/bun.rs | 20 +- crates/perry-stdlib/src/sqlite/dispatch.rs | 8 +- crates/perry-stdlib/src/streams.rs | 68 +- crates/perry-stdlib/src/streams/byob.rs | 14 +- crates/perry-stdlib/src/streams/pipe.rs | 85 +- crates/perry-stdlib/src/streams/strategy.rs | 20 +- crates/perry-stdlib/src/streams/tee.rs | 28 +- crates/perry-stdlib/src/streams/tests.rs | 7 +- crates/perry-stdlib/src/streams/transform.rs | 44 +- crates/perry-stdlib/src/streams/writable.rs | 37 +- crates/perry-stdlib/src/string_decoder.rs | 29 +- crates/perry-stdlib/src/tls.rs | 7 +- crates/perry-stdlib/src/webcrypto/digest.rs | 10 +- crates/perry-stdlib/src/worker_threads.rs | 24 +- .../src/worker_threads/broadcast_channel.rs | 30 +- .../src/worker_threads/message_port.rs | 70 +- .../src/worker_threads/parent_port.rs | 26 +- .../src/worker_threads/worker_surface.rs | 230 ++- crates/perry-ui-ios/src/notifications.rs | 4 +- .../tests/source_graph_export_regressions.rs | 7 +- crates/perry/tests/this_param_abi.rs | 41 +- scripts/check_js_body_call_funnel.py | 122 +- scripts/check_runtime_symbols.sh | 1 - scripts/gc_root_dominance_check.py | 3 + scripts/gc_runtime_root_holders.json | 72 +- scripts/registry_lifetime_allowlist.json | 13 - scripts/wasi_toolchain.sh | 4 +- test-files/test_gap_fn_info_facts.ts | 127 ++ 405 files changed, 10009 insertions(+), 7893 deletions(-) create mode 100644 changelog.d/fn-info-closure-body-registry.md create mode 100644 crates/perry-codegen/src/fn_info.rs delete mode 100644 crates/perry-runtime/src/ffi/perry_wasi_sig.c create mode 100644 test-files/test_gap_fn_info_facts.ts 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/fn-info-closure-body-registry.md b/changelog.d/fn-info-closure-body-registry.md new file mode 100644 index 0000000000..12020f2077 --- /dev/null +++ b/changelog.d/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 3907646ad7..5206975167 100644 --- a/crates/perry-abi/src/lib.rs +++ b/crates/perry-abi/src/lib.rs @@ -19,12 +19,12 @@ pub const AGENT_PTR_SLOTS: usize = 4; 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; @@ -116,7 +116,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. /// @@ -130,6 +130,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.")] @@ -145,6 +160,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]; @@ -163,6 +213,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 @@ -223,12 +482,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 5819ae5312..3d96520147 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 824d0e17cc..0fd08065e4 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 00d34f724d..dfc9269b2a 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 } => { @@ -1578,21 +1572,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 15723d9932..733d5bfb80 100644 --- a/crates/perry-codegen/src/codegen/mod.rs +++ b/crates/perry-codegen/src/codegen/mod.rs @@ -3745,6 +3745,10 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result> 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 079c4ce0ec..8ae264d85a 100644 --- a/crates/perry-codegen/src/codegen/string_pool.rs +++ b/crates/perry-codegen/src/codegen/string_pool.rs @@ -150,17 +150,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, @@ -173,30 +173,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 @@ -881,20 +879,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 @@ -1022,13 +1018,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 @@ -1049,8 +1043,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. @@ -1208,24 +1202,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( @@ -1234,6 +1214,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()), ], ); } @@ -1536,15 +1517,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 { @@ -1557,6 +1532,7 @@ pub(super) fn emit_string_pool( (I64, &bytes_i64), (I64, &len_str), (I64, &func_i64), + (I32, &spec_length.to_string()), ], ); blk.call_void( @@ -1571,247 +1547,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 1259cb2ee1..83b7a4bc7c 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 36c6d7d799..4218a1e09f 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 @@ -1001,9 +1002,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 8de29180c1..a60eb0926e 100644 --- a/crates/perry-codegen/src/expr/property_get.rs +++ b/crates/perry-codegen/src/expr/property_get.rs @@ -1247,9 +1247,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 10bcbd1775..5a51c50d46 100644 --- a/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/linux-x86_64.tsv @@ -2742,18 +2742,6 @@ js_register_class_static_method_bind_length Leaf js_register_class_static_setter Leaf 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 ce180fd9ad..7e667d4fb8 100644 --- a/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv +++ b/crates/perry-codegen/src/gc_effects/macos-aarch64.tsv @@ -2742,18 +2742,6 @@ js_register_class_static_method_bind_length Reenters js_register_class_static_setter Leaf 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 2f10f8f3de..41a069cfb0 100644 --- a/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv +++ b/crates/perry-codegen/src/gc_effects/windows-x86_64.tsv @@ -2742,18 +2742,6 @@ js_register_class_static_method_bind_length Leaf js_register_class_static_setter Leaf 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 9cd4cc29c7..e2764e1b26 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 26cc673d26..a8aed3384a 100644 --- a/crates/perry-codegen/src/lower_call/func_ref.rs +++ b/crates/perry-codegen/src/lower_call/func_ref.rs @@ -1455,10 +1455,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 2d828ee635..e4d346cea6 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 bb4f191632..377f2e8b7f 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,12 +214,12 @@ 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]); // 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", @@ -230,41 +231,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 31d9840571..ae6bef2c0f 100644 --- a/crates/perry-codegen/src/wasm32/runtime_abi.tsv +++ b/crates/perry-codegen/src/wasm32/runtime_abi.tsv @@ -3196,7 +3196,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 @@ -3210,27 +3210,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 9b5600f39f..4bf068971b 100644 --- a/crates/perry-runtime/src/builtins/formatting.rs +++ b/crates/perry-runtime/src/builtins/formatting.rs @@ -283,7 +283,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 { @@ -307,7 +307,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 6a44c86a40..3dc67d7397 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; } @@ -493,7 +494,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(); @@ -590,7 +591,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; @@ -770,7 +771,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(); @@ -849,7 +853,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); } @@ -880,10 +884,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, @@ -898,7 +901,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()) } @@ -911,9 +922,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 f873228815..08da792a22 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -162,9 +162,9 @@ pub(crate) unsafe fn native_call_value_this( // 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 @@ -190,13 +190,11 @@ pub(crate) unsafe fn native_call_value_this( } 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); @@ -228,11 +226,9 @@ pub(crate) unsafe fn native_call_value_this( // 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 @@ -323,8 +319,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 { @@ -396,24 +393,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) => { @@ -422,10 +416,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 10f0915a7f..b9f87ae48c 100644 --- a/crates/perry-runtime/src/closure/dynamic_props.rs +++ b/crates/perry-runtime/src/closure/dynamic_props.rs @@ -398,7 +398,7 @@ pub fn closure_get_dynamic_prop(ptr: usize, prop: &str) -> f64 { // 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) }; } @@ -874,12 +874,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 d58a809cef..d10d0d5d5c 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 0bf997f13e..279b90335c 100644 --- a/crates/perry-runtime/src/closure/shape.rs +++ b/crates/perry-runtime/src/closure/shape.rs @@ -53,14 +53,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 @@ -96,8 +96,6 @@ crate::perry_thread_local! { /// This agent's base Function ShapeIds, indexed by `FunctionProtoKind`, /// then the FunctionDictionary id (0 = not minted yet). static BASE_SHAPES: std::cell::Cell<[u32; 5]> = const { std::cell::Cell::new([0; 5]) }; - /// 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 @@ -178,27 +176,17 @@ pub(crate) fn function_dictionary_shape() -> u32 { ) } -/// 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 @@ -259,7 +247,7 @@ pub(crate) fn refresh_closure_shape(ptr: usize) { 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 @@ -433,15 +421,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}"); @@ -462,15 +458,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 @@ -480,8 +476,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); @@ -515,7 +511,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, @@ -523,11 +519,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(), @@ -540,7 +536,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( @@ -568,7 +564,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( @@ -589,8 +585,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( @@ -629,7 +625,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(), @@ -637,7 +633,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 fb128d0e99..c01153c9ee 100644 --- a/crates/perry-runtime/src/gc/census.rs +++ b/crates/perry-runtime/src/gc/census.rs @@ -585,7 +585,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 5a292815b5..0a3acb247e 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/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 546122552e..f17d027c58 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); @@ -1360,11 +1364,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, ); @@ -1536,15 +1540,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], @@ -1703,7 +1705,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); @@ -1766,7 +1768,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); @@ -1902,7 +1904,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); @@ -1961,8 +1963,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 2bfc10cadd..885120fed9 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 @@ -551,6 +551,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 ffa4967cf5..af174bb48a 100644 --- a/crates/perry-runtime/src/node_vm.rs +++ b/crates/perry-runtime/src/node_vm.rs @@ -248,7 +248,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) }) @@ -1307,15 +1307,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) @@ -1398,33 +1396,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 b7f902334e..66ad77854f 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -202,7 +202,7 @@ 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_own_static_accessor_ptrs, invalidate_class_string_member_order, + class_own_setter_length, class_own_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 76b9fcfe94..66539f101a 100644 --- a/crates/perry-runtime/src/object/class_registry/class_meta.rs +++ b/crates/perry-runtime/src/object/class_registry/class_meta.rs @@ -329,7 +329,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 a1aa1de1c1..57f3311bc0 100644 --- a/crates/perry-runtime/src/object/class_registry/construct.rs +++ b/crates/perry-runtime/src/object/class_registry/construct.rs @@ -1382,11 +1382,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 6ae890b0cf..caddf95249 100644 --- a/crates/perry-runtime/src/object/class_registry/parent_static.rs +++ b/crates/perry-runtime/src/object/class_registry/parent_static.rs @@ -858,12 +858,12 @@ pub unsafe extern "C" fn js_register_class_computed_accessor( .entry(class_id) .or_default() .entry(name) - .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; } } } @@ -1134,7 +1134,7 @@ pub(crate) unsafe fn class_static_accessor_getter_value( return Some(result); } if let Some(accessors) = map.and_then(|map| map.get(&cid)) { - if let Some(&(getter, _)) = accessors.get(name) { + if let Some(&AccessorDecl { get: getter, .. }) = accessors.get(name) { if getter == 0 { return Some(f64::from_bits(crate::value::TAG_UNDEFINED)); } @@ -1203,7 +1203,7 @@ pub(crate) unsafe fn class_static_accessor_setter_apply( return applied; } if let Some(accessors) = map.and_then(|map| map.get(&cid)) { - if let Some(&(_, setter)) = accessors.get(name) { + if let Some(&AccessorDecl { set: setter, .. }) = accessors.get(name) { if setter != 0 { // Mirror the getter path: the compiled static-accessor // prologue consumes this override and binds `this` to the diff --git a/crates/perry-runtime/src/object/class_registry/registration.rs b/crates/perry-runtime/src/object/class_registry/registration.rs index 60bebc629b..b4847a17f7 100644 --- a/crates/perry-runtime/src/object/class_registry/registration.rs +++ b/crates/perry-runtime/src/object/class_registry/registration.rs @@ -195,14 +195,37 @@ pub(crate) fn class_own_accessor_ptrs(class_id: u32, name: &str) -> Option<(usiz pub(crate) fn class_own_static_accessor_ptrs(class_id: u32, name: &str) -> 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. @@ -243,7 +266,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 { @@ -260,18 +283,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() { @@ -280,13 +307,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 @@ -350,11 +379,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. @@ -372,7 +405,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); @@ -392,7 +430,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`. @@ -402,8 +440,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 @@ -415,7 +461,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 @@ -492,6 +538,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. @@ -513,11 +560,12 @@ unsafe fn register_class_static_accessor_half( .entry(class_id as u32) .or_default() .entry(name) - .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 e1002680d3..311b6590be 100644 --- a/crates/perry-runtime/src/object/class_registry/state.rs +++ b/crates/perry-runtime/src/object/class_registry/state.rs @@ -328,10 +328,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 @@ -360,6 +363,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; } @@ -371,6 +385,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; } @@ -412,8 +427,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_registry/static_accessor_attrs.rs b/crates/perry-runtime/src/object/class_registry/static_accessor_attrs.rs index d32428edc5..603a8fdca5 100644 --- a/crates/perry-runtime/src/object/class_registry/static_accessor_attrs.rs +++ b/crates/perry-runtime/src/object/class_registry/static_accessor_attrs.rs @@ -84,8 +84,13 @@ pub(crate) unsafe fn static_accessor_descriptor( setter: usize, ) -> f64 { let scope = crate::gc::RuntimeHandleScope::new(); - let get = scope.root_nanbox_f64(class_accessor_function_value(getter, false, name)); - let set = class_accessor_function_value(setter, true, name); + let get = scope.root_nanbox_f64(class_accessor_function_value(getter, false, name, None)); + let set = class_accessor_function_value( + setter, + true, + name, + class_own_setter_length(class_id, name, true), + ); let (enumerable, configurable) = static_accessor_attrs(class_id, name); crate::object::descriptors::build_accessor_descriptor( get.get_nanbox_f64(), @@ -145,6 +150,7 @@ mod tests { name.as_ptr(), name.len() as i64, setter as *const () as usize as i64, + 1, ); } js_register_class_string_member_order( 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 1c7a0409ed..e14cc2473c 100644 --- a/crates/perry-runtime/src/object/descriptors.rs +++ b/crates/perry-runtime/src/object/descriptors.rs @@ -713,8 +713,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 dd3f783c53..e76cb495fd 100644 --- a/crates/perry-runtime/src/object/field_get_set/enumeration.rs +++ b/crates/perry-runtime/src/object/field_get_set/enumeration.rs @@ -656,7 +656,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 b78abaeaf2..2fc79e098e 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 @@ -207,8 +207,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); @@ -650,7 +649,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 120b4d59b2..cddb233a59 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 c5b749b1e6..b8e464b3f0 100644 --- a/crates/perry-runtime/src/object/global_this/fetch_globals.rs +++ b/crates/perry-runtime/src/object/global_this/fetch_globals.rs @@ -1286,7 +1286,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( @@ -1314,7 +1314,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. @@ -589,84 +565,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/ @@ -793,9 +825,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 { @@ -1024,12 +1057,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); @@ -1046,16 +1080,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 @@ -1077,14 +1109,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 2170bc2b21..2d1a25da40 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); @@ -909,7 +925,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; }; @@ -920,7 +936,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 7dff1e0edb..e3799cd152 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 62d72dcbb8..c1fc369565 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 @@ -160,11 +160,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"), @@ -185,7 +188,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(); @@ -193,10 +196,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 @@ -212,8 +212,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 306bcc6ada..5af0c4834a 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -1508,7 +1508,7 @@ 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 @@ -1612,7 +1612,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()) { @@ -1681,7 +1681,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/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 b1c10ab8b6..12cb0ab78d 100644 --- a/crates/perry-runtime/src/object/native_this_alias.rs +++ b/crates/perry-runtime/src/object/native_this_alias.rs @@ -578,10 +578,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_class_accessor.rs b/crates/perry-runtime/src/object/object_ops/define_class_accessor.rs index 2f4c73251f..0a5a2e4d8d 100644 --- a/crates/perry-runtime/src/object/object_ops/define_class_accessor.rs +++ b/crates/perry-runtime/src/object/object_ops/define_class_accessor.rs @@ -56,13 +56,18 @@ pub(super) unsafe fn define_declared_class_accessor( let scope = crate::gc::RuntimeHandleScope::new(); let desc = scope.root_nanbox_f64(descriptor_value); if !configurable { - // The validator compares accessor halves by closure `func_ptr`, which a + // The validator compares accessor halves by closure code address, which a // reflected class accessor value carries. Root the getter value across // the setter value's allocation; the validator roots both on entry. let get = scope.root_nanbox_f64( - super::super::class_registry::class_accessor_function_value(getter, false, name), + super::super::class_registry::class_accessor_function_value(getter, false, name, None), + ); + let set = super::super::class_registry::class_accessor_function_value( + setter, + true, + name, + super::super::class_registry::class_own_setter_length(class_id, name, true), ); - let set = super::super::class_registry::class_accessor_function_value(setter, true, name); validate_nonconfigurable_redefine( name, PropertyAttrs::new(false, enumerable, false), 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 8a2c2e24e3..9feb14559e 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 7af752df9f..20440c2106 100644 --- a/crates/perry-runtime/src/object/property_key.rs +++ b/crates/perry-runtime/src/object/property_key.rs @@ -373,7 +373,7 @@ pub unsafe extern "C" fn js_super_accessor_get( let mut depth = 0usize; while cid != 0 && depth < 32 { if let Some(getter_ptr) = - reg.get(&cid).and_then(|m| m.get(key_name)).map(|&(g, _)| g) + reg.get(&cid).and_then(|m| m.get(key_name)).map(|d| d.get) { if getter_ptr != 0 { // A static getter is a BARE body declaring no parameters; the @@ -623,7 +623,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 6bbaeb7939..569a111384 100644 --- a/crates/perry-runtime/src/object/reserved_floor.rs +++ b/crates/perry-runtime/src/object/reserved_floor.rs @@ -247,9 +247,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 6e56d0516a..69a52c4d65 100644 --- a/crates/perry-runtime/src/process/node_module.rs +++ b/crates/perry-runtime/src/process/node_module.rs @@ -168,7 +168,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, @@ -891,9 +895,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"); @@ -992,17 +994,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); @@ -1058,9 +1057,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()); @@ -1127,7 +1124,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, @@ -1152,7 +1149,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 44c9d2d543..a1d2a44607 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -2507,11 +2507,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)); @@ -2923,7 +2921,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 8b323a1b5e..d35e07a954 100644 --- a/crates/perry-runtime/src/symbol/properties.rs +++ b/crates/perry-runtime/src/symbol/properties.rs @@ -271,7 +271,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 a0937f3c54..cf388b2413 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, } @@ -909,11 +911,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)); @@ -1032,7 +1037,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] @@ -1043,7 +1048,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 @@ -1054,7 +1059,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); @@ -1115,7 +1120,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 = @@ -1191,7 +1196,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); @@ -1337,7 +1343,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); @@ -1384,7 +1390,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; @@ -1444,7 +1450,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); @@ -1597,11 +1604,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(); @@ -1665,7 +1672,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 { @@ -1680,13 +1688,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 280a9e2fbe..f18214a72b 100644 --- a/crates/perry-runtime/src/typed_feedback/guards.rs +++ b/crates/perry-runtime/src/typed_feedback/guards.rs @@ -1355,7 +1355,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 { @@ -1368,19 +1368,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, @@ -1412,7 +1410,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 @@ -1432,14 +1430,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 @@ -1481,7 +1479,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 { { @@ -1490,7 +1488,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 { @@ -1544,7 +1542,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; } @@ -1585,11 +1583,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 d84f98cd23..13d459e5c6 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 4833831c39..2e7ea7d437 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 e1dec12aa3..3e1f2d6bcc 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 aad7a8cec4..506270509c 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", @@ -256,12 +250,6 @@ "verdict": "not_a_gc_pointer", "why": "Atomic count of spawned children that have not reached close. It is only a lock-free emptiness gate for the reactor and root scanner; the ChildProcess values themselves live in CP_LIVE and are visited by cp_reactor_scan_roots_mut." }, - { - "file": "crates/perry-runtime/src/child_process/reactor.rs", - "name": "CP_NEXT_LIVE_ID", - "verdict": "not_a_gc_pointer", - "why": "Monotonic id counter for CP_LIVE keys. CP_LIVE itself is covered by cp_reactor_scan_roots_mut." - }, { "file": "crates/perry-runtime/src/child_process/reactor.rs", "name": "CP_REFED_COUNT", @@ -286,30 +274,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 +356,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 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 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 +371,7 @@ "function": "run_to_completion" }, "sources": { - "crates/perry-runtime/src/gc/census.rs": "25601f25ac70aa998f8cb5c1939d11e7c43a96235d4edf39a78e261b68709471", + "crates/perry-runtime/src/gc/census.rs": "4f5334caa2aba2562ec43245de238a5a53be0a79f4d98e9bc6beadc018583384", "crates/perry-runtime/src/gc/cycle.rs": "4744196ba5e9c5ac40912154cf5b45b4a618d81ddc776ab1095fbc585f27c878", "crates/perry-runtime/src/gc/mod.rs": "2198c69b341ef4e95d5540aded59f5c9b1c7f36a37ab8ef895d1962f75792829", "crates/perry-runtime/src/gc/policy.rs": "7e2304ec822d26877df4eaf7aefb55c485c66425b8946b56a9b271a0abcc635b", @@ -2882,10 +2846,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" @@ -3026,14 +2986,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" @@ -3623,10 +3575,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" @@ -3707,10 +3655,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" @@ -4030,10 +3974,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" @@ -4094,14 +4034,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 6579dca063..4fc5c1e28f 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", @@ -590,13 +584,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); From 36645b2d6ac1c8ac991cfee270b52da3afd90a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 01:13:27 +0200 Subject: [PATCH 12/17] changelog: name the fragment after #11654 --- ...re-body-registry.md => 11654-fn-info-closure-body-registry.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog.d/{fn-info-closure-body-registry.md => 11654-fn-info-closure-body-registry.md} (100%) diff --git a/changelog.d/fn-info-closure-body-registry.md b/changelog.d/11654-fn-info-closure-body-registry.md similarity index 100% rename from changelog.d/fn-info-closure-body-registry.md rename to changelog.d/11654-fn-info-closure-body-registry.md From 44f5544473f667470b98743bd73b3cda86e6370e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 00:08:45 +0000 Subject: [PATCH 13/17] gc_runtime_root_holders: drop the CP_NEXT_LIVE_ID entry the exec split made stale Moving cp_exec_async into reactor/exec_async.rs took the only allocating function that names CP_NEXT_LIVE_ID out of reactor.rs, so rule B no longer makes the counter a candidate in its declaring file and the inventory entry matches no holder. The static is still a scalar AtomicU64 id counter; the ChildProcess values it keys live in CP_LIVE, which cp_reactor_scan_roots_mut visits. --- scripts/gc_runtime_root_holders.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/scripts/gc_runtime_root_holders.json b/scripts/gc_runtime_root_holders.json index aad7a8cec4..938ca06c05 100644 --- a/scripts/gc_runtime_root_holders.json +++ b/scripts/gc_runtime_root_holders.json @@ -256,12 +256,6 @@ "verdict": "not_a_gc_pointer", "why": "Atomic count of spawned children that have not reached close. It is only a lock-free emptiness gate for the reactor and root scanner; the ChildProcess values themselves live in CP_LIVE and are visited by cp_reactor_scan_roots_mut." }, - { - "file": "crates/perry-runtime/src/child_process/reactor.rs", - "name": "CP_NEXT_LIVE_ID", - "verdict": "not_a_gc_pointer", - "why": "Monotonic id counter for CP_LIVE keys. CP_LIVE itself is covered by cp_reactor_scan_roots_mut." - }, { "file": "crates/perry-runtime/src/child_process/reactor.rs", "name": "CP_REFED_COUNT", From b0de0f1ae7ad67636dfec8d96c200b5111b4c148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 01:11:19 +0000 Subject: [PATCH 14/17] perf(runtime): runtime value calls go through js_native_call_value, not an inlined dispatcher native_call_value_this was #[inline(always)] over the whole value-call dispatcher, and the this-as-a-parameter funnel routes ~130 runtime callers through it, so every one of them carried its own copy (Function.prototype call/apply thunks, forEach/replace callbacks, each arm_trap_and_run closure). It grew every linked program by ~190 KB on linux-x64 (loop fixture 7,563,048 -> 7,755,560 stripped). The dispatcher body now lives only in the two js_native_call_value definitions; native_call_value_this is a thin call to the export. loop is 7,497,512 with this change, below main. --- .../src/closure/dispatch/value_call.rs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index f873228815..5264ceeb73 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -39,7 +39,7 @@ pub unsafe extern "C" fn js_native_call_value( args_ptr: *const f64, args_len: usize, ) -> f64 { - unsafe { native_call_value_this(func_value, this, args_ptr, args_len) } + unsafe { native_call_value_this_impl(func_value, this, args_ptr, args_len) } } // Debug/test static archives transport Perry exceptions with Rust unwinding, @@ -53,17 +53,33 @@ pub unsafe extern "C-unwind" fn js_native_call_value( args_ptr: *const f64, args_len: usize, ) -> f64 { - unsafe { native_call_value_this(func_value, this, args_ptr, args_len) } + unsafe { native_call_value_this_impl(func_value, this, args_ptr, args_len) } } /// Call function value `func_value` with receiver `this` and `args_len` -/// arguments at `args_ptr`: the one value-call dispatcher. +/// arguments at `args_ptr`: the one value-call dispatcher, for runtime callers. +/// +/// This calls the exported `js_native_call_value` instead of inlining the +/// dispatcher: the runtime has over a hundred callers, and inlining the whole +/// dispatch into each of them grew every linked program by ~190 KB. #[inline(always)] pub(crate) unsafe fn native_call_value_this( func_value: f64, this: crate::closure::JsThis, args_ptr: *const f64, args_len: usize, +) -> f64 { + unsafe { js_native_call_value(func_value, this, args_ptr, args_len) } +} + +/// The dispatcher body, inlined only into the two `js_native_call_value` +/// definitions (one per panic strategy). +#[inline(always)] +unsafe fn native_call_value_this_impl( + func_value: f64, + this: crate::closure::JsThis, + args_ptr: *const f64, + args_len: usize, ) -> f64 { use crate::value::JSValue; From 3492ff3370dfe14a5685bc87e24ecbfc5ba6adf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 01:11:19 +0000 Subject: [PATCH 15/17] fix(runtime): reach the stream subclass-init shim through the stream install 736782e1b made Function.prototype.call/.apply run the node:stream subclass-init shim for Readable.call(this, opts) by calling js_node_stream_*_subclass_init directly. That path is linked into every program, so it pinned all of node_stream (and the promise, timer and process-stream code behind it) into programs that never import stream: +554 KB stripped on the linux-x64 loop fixture (7,009,384 -> 7,563,048), and the darwin-arm64 small-program budget over by 6-10%. The shim is now registered by js_nm_install_stream, which codegen emits only when a program imports stream; a stream base callee cannot exist otherwise. The store is black_boxed like js_nm_enable_install_all, since the slot has a single writer and whole-program optimization otherwise devirtualizes the load back into a direct call. The dynamic super() path shares the same entry. loop fixture: 7,497,512 -> 6,952,040 stripped. The #10448/#10449/#10454/ #10544/#10798 and stdio-writable gap tests still match node. --- .../src/object/native_module_registry.rs | 34 +++++++++++++++++++ .../src/object/native_this_alias.rs | 11 ++++++ 2 files changed, 45 insertions(+) diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index 318855c689..df4449b99c 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -508,6 +508,14 @@ pub extern "C" fn js_nm_install_stream() { Ordering::Relaxed, ); nm_register_ctor(NmBucket::Stream, nm_ctor_stream); + // `black_box` for the reason `js_nm_enable_install_all` gives: this is the + // only value ever stored to the single-pointer slot, and without it + // whole-program optimization devirtualizes the load in + // `nm_stream_subclass_init` into a direct call, re-pinning node_stream. + let init = std::hint::black_box( + super::native_this_alias::node_stream_subclass_init as NmStreamSubclassInitFn, + ); + NM_STREAM_SUBCLASS_INIT.store(init as *mut (), Ordering::Relaxed); } #[no_mangle] pub extern "C" fn js_nm_install_timers() { @@ -722,6 +730,32 @@ fn nm_register_ctor(b: NmBucket, f: NmCtorFn) { NM_CTOR_REGISTRY[b as usize].store(f as *mut (), Ordering::Relaxed); } +type NmStreamSubclassInitFn = fn(&str, f64, f64) -> bool; + +/// The stream bucket's subclass-init shim (`Readable.call(this, opts)`, a +/// dynamic `super(opts)` onto a stream base). Registered by +/// `js_nm_install_stream` so the always-linked call paths that reach it do not +/// pin node_stream into programs that never import `stream`. +static NM_STREAM_SUBCLASS_INIT: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut()); + +/// Run the installed stream subclass-init shim for stream base `method` on +/// `this`. False when `method` is no stream base or `stream` was never +/// installed (then no stream base callee can exist either). +pub(crate) fn nm_stream_subclass_init(method: &str, this: f64, opts: f64) -> bool { + let mut p = NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed); + // See nm_dispatch_lookup: unit tests run without the codegen install. + #[cfg(test)] + if p.is_null() && nm_lazy_install_enabled() { + js_nm_install_all(); + p = NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed); + } + if p.is_null() { + return false; + } + let f = unsafe { std::mem::transmute::<*mut (), NmStreamSubclassInitFn>(p) }; + f(method, this, opts) +} + /// Per-module callable-export "attach" handlers: prototype/statics decoration /// for bound module exports (`stream.Readable`'s prototype tower, /// `sqlite.DatabaseSync`'s methods, `AsyncLocalStorage.bind`, …). Formerly a diff --git a/crates/perry-runtime/src/object/native_this_alias.rs b/crates/perry-runtime/src/object/native_this_alias.rs index b1c10ab8b6..6daee7f016 100644 --- a/crates/perry-runtime/src/object/native_this_alias.rs +++ b/crates/perry-runtime/src/object/native_this_alias.rs @@ -252,7 +252,18 @@ pub(crate) unsafe fn maybe_construct_http_class_with_this( /// (`js_fetch_or_value_super`, #10448/#10798) and the explicit-`this` /// `Base.call(this, opts)` path below (#10454), so every construction shape /// installs the identical surface. False when `method` is no stream base. +/// +/// The node_stream shims are reached through the stream bucket's install +/// (`js_nm_install_stream`), never directly: this runs on the always-linked +/// `Function.prototype.call`/`.apply` path, and a direct call would link all of +/// node_stream into every program. A stream base callee only exists in a +/// program that imports `stream`, which emits that install. pub(crate) fn run_node_stream_subclass_init(method: &str, this: f64, opts: f64) -> bool { + super::native_module_registry::nm_stream_subclass_init(method, this, opts) +} + +/// The stream bucket's subclass-init entry, installed by `js_nm_install_stream`. +pub(crate) fn node_stream_subclass_init(method: &str, this: f64, opts: f64) -> bool { use crate::node_stream as ns; match method { "Readable" => ns::js_node_stream_readable_subclass_init(this, opts), From 1ca9c790fcb3d6795445c9e62c1d520676986ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 08:36:50 +0000 Subject: [PATCH 16/17] fix(runtime): stream subclass-init slot: no unused mut, thread-exit verdict nm_stream_subclass_init reassigned its load only under cfg(test), so a non-test build saw an unused `mut` and failed the -D warnings job. Shadow the binding in the test-only branch instead. NM_STREAM_SUBCLASS_INIT holds only the node_stream_subclass_init fn pointer (its one store is in js_nm_install_stream): a code address, never an arena address. Record that no_heap_address verdict for the #11471 thread-exit custody gate, like its NM_CTOR/NM_ATTACH sibling registries. --- .../perry-runtime/src/object/native_module_registry.rs | 10 ++++++---- scripts/thread_exit_address_globals.json | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index df4449b99c..82a6062830 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -742,13 +742,15 @@ static NM_STREAM_SUBCLASS_INIT: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mu /// `this`. False when `method` is no stream base or `stream` was never /// installed (then no stream base callee can exist either). pub(crate) fn nm_stream_subclass_init(method: &str, this: f64, opts: f64) -> bool { - let mut p = NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed); + let p = NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed); // See nm_dispatch_lookup: unit tests run without the codegen install. #[cfg(test)] - if p.is_null() && nm_lazy_install_enabled() { + let p = if p.is_null() && nm_lazy_install_enabled() { js_nm_install_all(); - p = NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed); - } + NM_STREAM_SUBCLASS_INIT.load(Ordering::Relaxed) + } else { + p + }; if p.is_null() { return false; } diff --git a/scripts/thread_exit_address_globals.json b/scripts/thread_exit_address_globals.json index 9170d11a7b..4ed5c5ddec 100644 --- a/scripts/thread_exit_address_globals.json +++ b/scripts/thread_exit_address_globals.json @@ -2676,6 +2676,14 @@ "verdict": "no_heap_address", "why": "Per-bucket NmAttachFn fn pointers stored at native_module_registry.rs:759; code addresses." }, + { + "file": "crates/perry-runtime/src/object/native_module_registry.rs", + "names": [ + "NM_STREAM_SUBCLASS_INIT" + ], + "verdict": "no_heap_address", + "why": "The node_stream_subclass_init NmStreamSubclassInitFn fn pointer, its only store at native_module_registry.rs:518 (js_nm_install_stream); a code address." + }, { "file": "crates/perry-runtime/src/object/native_module_registry.rs", "names": [ From ee5b988e77cc8f43382d3fe26b42d19fa2e6d89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 29 Sep 2026 12:42:58 +0000 Subject: [PATCH 17/17] gc_runtime_root_holders: keep the stage-3 re-audit note the merge dropped The PASS1_MARKED entry's audit trail took main's side in the merge and lost this branch's note for the implicit-this scanner registration (`scan_dispatch_binding_roots_mut`). Restore it after main's notes. --- scripts/gc_runtime_root_holders.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gc_runtime_root_holders.json b/scripts/gc_runtime_root_holders.json index 01d2871bdf..1bffb21ca4 100644 --- a/scripts/gc_runtime_root_holders.json +++ b/scripts/gc_runtime_root_holders.json @@ -392,7 +392,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. 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.", + "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. 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.", "window": { "start": { "file": "crates/perry-runtime/src/gc/census.rs",