From 17fcc74515ca693825ffef2dfa1a05fe51ac1b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 07:59:16 +0200 Subject: [PATCH 01/13] wip(runtime): per-module native-module method-dispatch registry (devirt phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the monolithic dispatch_native_module_method (592 arms) into 37 per-module nm_dispatch_ bucket fns reached through a per-module dispatch registry, so the linker can dead-strip handlers a program never imports. NmCtx + nm_general_closures! macro carry the old prologue marshalling. Thin router does name extract+normalize then registry lookup — names no bucket fn. Each js_nm_install_() is the sole static ref to its bucket; codegen will emit one per static import (next commit). Runtime compiles green. Codegen install emission + correctness/measurement pending. See NM_DEVIRT_PLAN.md. --- NM_DEVIRT_PLAN.md | 64 + crates/perry-runtime/src/object/mod.rs | 1 + .../src/object/native_module_dispatch.rs | 2790 ++++++++++------- .../src/object/native_module_registry.rs | 192 ++ 4 files changed, 1838 insertions(+), 1209 deletions(-) create mode 100644 NM_DEVIRT_PLAN.md create mode 100644 crates/perry-runtime/src/object/native_module_registry.rs diff --git a/NM_DEVIRT_PLAN.md b/NM_DEVIRT_PLAN.md new file mode 100644 index 000000000..09061fc5b --- /dev/null +++ b/NM_DEVIRT_PLAN.md @@ -0,0 +1,64 @@ +# Native-module method-dispatch devirtualization (feat/nm-method-devirt) + +Goal: let `-dead_strip` remove native-module handler code (cluster/child_process/ +dns/tls/vm/repl/inspector/perf_hooks/sqlite/dgram/…) from binaries that don't +import those modules. Today one monolithic `dispatch_native_module_method` +(437→592 arms, `native_module_dispatch.rs`) statically names every handler, so any +program creating one native namespace pins all of them. Measured ceiling for +hello-world: ~213KB (method dispatch only; constructor dispatcher in +class_registry.rs is a SEPARATE later phase). + +## Validated partition +592 arms → 37 buckets, 0 unmapped, 0 unbalanced (brace-balanced boundaries). +Buckets: assert async_hooks bigint buffer child_process cluster console crypto +dgram dns domain events fs http https inspector module net os path perf process +punycode querystring readline repl sea sqlite stream timers tls tty url util v8 +vm wasi zlib. Sub-namespace tags map to bucket: crypto.subtle/webcrypto/Certificate→crypto, +path.posix/win32→path, util.types/util/types→util, dns/promises→dns, +assert/strict & assert.instance→assert, inspector/promises & inspector.Network→inspector, +punycode.default→punycode, perf_hooks/perf_observer*/perf_histogram→perf, +v8.Serializer/Deserializer/GCProfiler/promiseHooks/startupSnapshot→v8. + +## Design (minimal disruption — vtable struct + hook UNCHANGED) +- `NmCtx { obj, args_ptr, args_len, assert_skip_prototype }` + `nm_general_closures!` + macro (general closures only: arg,i32_arg,str_to_f64,bool_to_f64,bool_tag,ptr_addr, + optional_ptr_addr,arg_bits,pack_args,pack_args_from,ptr_to_f64,typed_kind,_arg_event_ptr, + _arg_closure_ptr). Path closures (require_path_str_ptr,optional_path_str_ptr, + path_join/resolve/basename_value) inline ONLY in nm_dispatch_path. +- `dispatch_native_module_method(obj,method,args,len)` becomes a THIN ROUTER: extract + field0 name + existing normalization (current lines 128-165) → build NmCtx → + `nm_dispatch_registry_lookup(canonical) -> Optionf64>` → + call, else undefined. Still pointed at by the shared vtable.dispatch (955) and + native_arena.rs:474 — both unchanged. +- 37 `nm_dispatch_(ctx,module,method)->f64`: `let NmCtx{obj,args_ptr,args_len, + assert_skip_prototype}=*ctx; nm_general_closures!(); match (module,method){ _=>undefined }`. +- Registry (native_module_registry.rs): bucket id enum + `NM_DISPATCH_REGISTRY: + [AtomicPtr; 37]` (null init) + `nm_module_index(name)->Option` (string match, + NO fn refs) + `#[no_mangle] js_nm_install_()` storing `nm_dispatch_ as ptr` + (SOLE static ref to each bucket fn) + `js_nm_install_all()` (dynamic-require fallback). +- Codegen: at each `js_create_native_module_namespace` site (8 sites, main + static_field_meta.rs:572) ALSO emit `js_nm_install_()` for the static name, or + `js_nm_install_all()` if the module name is dynamic/unanalyzable. Runtime-internal + creators (node_v8, perf_hooks) call their `js_nm_install_v8/perf()`. +- Completeness invariant: every namespace-create site (compile-time name) emits the + matching install BEFORE any method dispatch on it → registry never misses → never + silently returns undefined. Dynamic name → install_all (correct, larger). + +## Why correct (vs cfg-gating): precise linker reachability through real edges, +sound graceful degradation (install_all), semantics never change with a build flag. + +## Status +[x] worktree off origin/main (5258a6073) +[x] partition validated (592 arms → 37 active buckets, https dropped = 0-arm) +[x] generated: NmCtx + nm_general_closures! macro + thin router + 37 nm_dispatch_ fns (native_module_dispatch.rs) +[x] registry: NmBucket + NM_DISPATCH_REGISTRY + nm_module_index + 37 js_nm_install_() + js_nm_install_all() (native_module_registry.rs) +[x] **perry-runtime compiles GREEN** (cargo build -p perry-runtime, 0 errors) +[ ] CODEGEN: emit js_nm_install_() at each js_create_native_module_namespace site (8 sites, main static_field_meta.rs:572); install_all on dynamic; declare externs in runtime_decls. REQUIRED for correctness — until wired, native-module method dispatch returns undefined (registry empty). +[ ] internal creators node_v8/perf_hooks call js_nm_install_v8()/perf() +[ ] full perry build + correctness test (import os/process) + measure hello-world delta +[ ] constructor-dispatcher (js_new_function_construct) — phase 2 + +## Generators (in /tmp, re-runnable from git HEAD) +/tmp/nm_generate.py (dispatch file), /tmp/nm_gen_registry.py (registry). Both read +pristine source via `git show HEAD:...` so re-running is idempotent. diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index f82fadf01..d225bf2d7 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -54,6 +54,7 @@ mod native_module_crypto_key_object; mod native_module_crypto_random; mod native_module_dispatch; mod native_module_dispatch_crypto; +mod native_module_registry; mod native_module_stream; mod native_this_alias; mod object_literal_ops; diff --git a/crates/perry-runtime/src/object/native_module_dispatch.rs b/crates/perry-runtime/src/object/native_module_dispatch.rs index fa98f1956..6f42ae870 100644 --- a/crates/perry-runtime/src/object/native_module_dispatch.rs +++ b/crates/perry-runtime/src/object/native_module_dispatch.rs @@ -119,60 +119,27 @@ pub extern "C" fn js_http_connection_listener_noop(_socket: f64) -> f64 { /// Dispatch a method call on a native module namespace object. /// Extracts the module name from the object and dispatches to the appropriate /// runtime function based on (module_name, method_name). -pub(crate) unsafe fn dispatch_native_module_method( - obj: *const ObjectHeader, - method_name: &str, - args_ptr: *const f64, - args_len: usize, -) -> f64 { - // Extract the module name from field 0 of the namespace object - let module_field = js_object_get_field(obj as *mut _, 0); - let module_name = if module_field.is_string() { - let str_ptr = module_field.as_string_ptr(); - let len = (*str_ptr).byte_len as usize; - let data = (str_ptr as *const u8).add(std::mem::size_of::()); - std::str::from_utf8(std::slice::from_raw_parts(data, len)).unwrap_or("") - } else { - "" - }; - let (module_name, assert_skip_prototype) = match module_name { - "assert.instance" => ("assert", false), - "assert.instance.skip" => ("assert", true), - "assert/strict.instance" => ("assert/strict", false), - "assert/strict.instance.skip" => ("assert/strict", true), - "path/posix" => ("path.posix", false), - "path/win32" => ("path.win32", false), - "async_hooks.default" => ("async_hooks", false), - // #3687: cluster default-import method calls (`cluster.fork()`, - // `cluster.emit(...)`) dispatch against the base `cluster` arms. - "cluster.default" => ("cluster", false), - "os.default" => ("os", false), - "path.default" => ("path", false), - "path.posix.default" => ("path.posix", false), - "path.win32.default" => ("path.win32", false), - "process.default" => ("process", false), - "querystring.default" => ("querystring", false), - "url.default" => ("url", false), - "util.default" => ("util", false), - "vm.default" => ("vm", false), - // #3987-adjacent: `process.getBuiltinModule("punycode")` returns the - // CJS-default namespace (`punycode.default`); without this alias its - // method calls dispatched as `("punycode.default", "decode")` — which - // has no arm — and returned `undefined`. The base `("punycode", …)` - // arms below already implement decode/encode/toASCII/toUnicode. - "punycode.default" => ("punycode", false), - _ => (module_name, false), - }; - // Helper: get arg N as f64 - let arg = |n: usize| -> f64 { - if n < args_len && !args_ptr.is_null() { - *args_ptr.add(n) + +/// Per-call marshalling context shared by every `nm_dispatch_` fn. +pub(crate) struct NmCtx { + pub obj: *const ObjectHeader, + pub args_ptr: *const f64, + pub args_len: usize, + pub assert_skip_prototype: bool, +} + +/// General (non-path) marshalling closures from the old prologue. Identifiers are +/// passed in so the `let` bindings carry call-site hygiene (visible to the arms). +macro_rules! nm_general_closures { ($obj:ident, $args_ptr:ident, $args_len:ident, $arg:ident, $i32_arg:ident, $bool_to_f64:ident, $str_to_f64:ident, $pack_args:ident, $pack_args_from:ident, $bool_tag:ident, $ptr_addr:ident, $optional_ptr_addr:ident, $_arg_event_ptr:ident, $arg_bits:ident, $_arg_closure_ptr:ident, $ptr_to_f64:ident, $typed_kind:ident) => { + let $arg = |n: usize| -> f64 { + if n < $args_len && !$args_ptr.is_null() { + *$args_ptr.add(n) } else { f64::from_bits(JSValue::undefined().bits()) } }; - let i32_arg = |n: usize| -> i32 { - let v = arg(n); + let $i32_arg = |n: usize| -> i32 { + let v = $arg(n); let bits = v.to_bits(); if (bits >> 48) == 0x7FFE { return (bits & 0xFFFF_FFFF) as u32 as i32; @@ -183,132 +150,38 @@ pub(crate) unsafe fn dispatch_native_module_method( v as i32 } }; - - let require_path_str_ptr = |n: usize| -> *const crate::StringHeader { - if n < args_len { - let v = arg(n); - let ptr = crate::string::js_string_materialize_to_heap(v); - if !ptr.is_null() { - return ptr; - } - } - crate::path::throw_invalid_path_arg_type() - }; - let optional_path_str_ptr = |n: usize| -> *const crate::StringHeader { - if n >= args_len { - return std::ptr::null(); - } - let v = arg(n); - let jsv = JSValue::from_bits(v.to_bits()); - if jsv.is_undefined() { - return std::ptr::null(); - } - let ptr = crate::string::js_string_materialize_to_heap(v); - if !ptr.is_null() { - return ptr; - } - crate::path::throw_invalid_path_arg_type() - }; - - // Helper: convert i32 boolean to NaN-boxed TAG_TRUE / TAG_FALSE - let bool_to_f64 = |v: i32| -> f64 { + let $bool_to_f64 = |v: i32| -> f64 { if v != 0 { f64::from_bits(0x7FFC_0000_0000_0004) // TAG_TRUE } else { f64::from_bits(0x7FFC_0000_0000_0003) // TAG_FALSE } }; - - // Helper: convert *mut StringHeader to NaN-boxed string f64 - let str_to_f64 = + let $str_to_f64 = |ptr: *mut crate::StringHeader| -> f64 { f64::from_bits(JSValue::string_ptr(ptr).bits()) }; - let path_join_value = |win32: bool| -> f64 { - if args_len == 0 { - let result = if win32 { - crate::path::js_path_win32_join_unchecked(std::ptr::null(), std::ptr::null()) - } else { - crate::path::js_path_join_unchecked(std::ptr::null(), std::ptr::null()) - }; - return str_to_f64(result); - } - let first = require_path_str_ptr(0); - let mut result = if win32 { - crate::path::js_path_win32_join_unchecked(first, std::ptr::null()) - } else { - crate::path::js_path_join_unchecked(first, std::ptr::null()) - }; - for i in 1..args_len { - let segment = require_path_str_ptr(i); - result = if win32 { - crate::path::js_path_win32_join_unchecked(result, segment) - } else { - crate::path::js_path_join_unchecked(result, segment) - }; - } - str_to_f64(result) - }; - let path_resolve_value = |win32: bool| -> f64 { - let mut result = if args_len == 0 { - if win32 { - crate::path::js_path_win32_join_unchecked(std::ptr::null(), std::ptr::null()) - } else { - crate::path::js_path_join_unchecked(std::ptr::null(), std::ptr::null()) - } - } else { - require_path_str_ptr(0) as *mut crate::StringHeader - }; - for i in 1..args_len { - let segment = require_path_str_ptr(i); - result = if win32 { - crate::path::js_path_win32_resolve_join(result, segment) - } else { - crate::path::js_path_resolve_join(result, segment) - }; - } - if win32 { - str_to_f64(crate::path::js_path_win32_resolve(result)) - } else { - str_to_f64(crate::path::js_path_resolve(result)) - } - }; - let path_basename_value = |win32: bool| -> f64 { - let path = require_path_str_ptr(0); - let ext = optional_path_str_ptr(1); - if win32 { - if ext.is_null() { - str_to_f64(crate::path::js_path_win32_basename(path)) - } else { - str_to_f64(crate::path::js_path_win32_basename_ext(path, ext)) - } - } else if ext.is_null() { - str_to_f64(crate::path::js_path_basename(path)) - } else { - str_to_f64(crate::path::js_path_basename_ext(path, ext)) - } - }; - let pack_args = || -> *mut crate::array::ArrayHeader { - let mut arr = crate::array::js_array_alloc(args_len as u32); - for i in 0..args_len { - arr = crate::array::js_array_push_f64(arr, arg(i)); + let $pack_args = || -> *mut crate::array::ArrayHeader { + let mut arr = crate::array::js_array_alloc($args_len as u32); + for i in 0..$args_len { + arr = crate::array::js_array_push_f64(arr, $arg(i)); } arr }; - let pack_args_from = |start: usize| -> *mut crate::array::ArrayHeader { - let len = args_len.saturating_sub(start); + let $pack_args_from = |start: usize| -> *mut crate::array::ArrayHeader { + let len = $args_len.saturating_sub(start); let mut arr = crate::array::js_array_alloc(len as u32); - for i in start..args_len { - arr = crate::array::js_array_push_f64(arr, arg(i)); + for i in start..$args_len { + arr = crate::array::js_array_push_f64(arr, $arg(i)); } arr }; - let bool_tag = |v: bool| -> f64 { + let $bool_tag = |v: bool| -> f64 { if v { f64::from_bits(0x7FFC_0000_0000_0004) } else { f64::from_bits(0x7FFC_0000_0000_0003) } }; - let ptr_addr = |v: f64| -> usize { + let $ptr_addr = |v: f64| -> usize { let bits = v.to_bits(); if (bits >> 48) >= 0x7FF8 { (bits & 0x0000_FFFF_FFFF_FFFF) as usize @@ -316,87 +189,213 @@ pub(crate) unsafe fn dispatch_native_module_method( bits as usize } }; - let optional_ptr_addr = |v: f64| -> usize { + let $optional_ptr_addr = |v: f64| -> usize { let value = JSValue::from_bits(v.to_bits()); if value.is_undefined() || value.is_null() { 0 } else { - ptr_addr(v) + $ptr_addr(v) } }; - let _arg_event_ptr = |n: usize| -> *const crate::StringHeader { - crate::value::js_get_string_pointer_unified(arg(n)) as *const crate::StringHeader + let $_arg_event_ptr = |n: usize| -> *const crate::StringHeader { + crate::value::js_get_string_pointer_unified($arg(n)) as *const crate::StringHeader }; - // Raw NaN-box bits of arg `n` (undefined when missing). Used by the - // process EventEmitter arms so the runtime can coerce event names and - // validate listeners against the full JS value (#3047/#3046). - let arg_bits = |n: usize| -> i64 { arg(n).to_bits() as i64 }; - let _arg_closure_ptr = |n: usize| -> *const crate::closure::ClosureHeader { - if n >= args_len { + let $arg_bits = |n: usize| -> i64 { $arg(n).to_bits() as i64 }; + let $_arg_closure_ptr = |n: usize| -> *const crate::closure::ClosureHeader { + if n >= $args_len { return std::ptr::null(); } - let v = arg(n); + let v = $arg(n); let jsv = JSValue::from_bits(v.to_bits()); if jsv.is_undefined() || jsv.is_null() { std::ptr::null() } else { - ptr_addr(v) as *const crate::closure::ClosureHeader + $ptr_addr(v) as *const crate::closure::ClosureHeader } }; - let ptr_to_f64 = |ptr: *const u8| -> f64 { f64::from_bits(JSValue::pointer(ptr).bits()) }; - let typed_kind = |v: f64| -> Option { - let addr = ptr_addr(v); + let $ptr_to_f64 = |ptr: *const u8| -> f64 { f64::from_bits(JSValue::pointer(ptr).bits()) }; + let $typed_kind = |v: f64| -> Option { + let addr = $ptr_addr(v); if crate::buffer::is_uint8array_buffer(addr) { Some(crate::typedarray::KIND_UINT8) } else { crate::typedarray::lookup_typed_array_kind(addr) } }; + let _ = (&$arg, &$i32_arg, &$bool_to_f64, &$str_to_f64, &$pack_args, &$pack_args_from, &$bool_tag, &$ptr_addr, &$optional_ptr_addr, &$_arg_event_ptr, &$arg_bits, &$_arg_closure_ptr, &$ptr_to_f64, &$typed_kind,); +} } + +/// Thin router — extract+normalize the module name, dispatch via the per-module +/// registry. Names no bucket fn, so unused modules dead-strip. +pub(crate) unsafe fn dispatch_native_module_method( + obj: *const ObjectHeader, + method_name: &str, + args_ptr: *const f64, + args_len: usize, +) -> f64 { + // Extract the module name from field 0 of the namespace object + let module_field = js_object_get_field(obj as *mut _, 0); + let module_name = if module_field.is_string() { + let str_ptr = module_field.as_string_ptr(); + let len = (*str_ptr).byte_len as usize; + let data = (str_ptr as *const u8).add(std::mem::size_of::()); + std::str::from_utf8(std::slice::from_raw_parts(data, len)).unwrap_or("") + } else { + "" + }; + let (module_name, assert_skip_prototype) = match module_name { + "assert.instance" => ("assert", false), + "assert.instance.skip" => ("assert", true), + "assert/strict.instance" => ("assert/strict", false), + "assert/strict.instance.skip" => ("assert/strict", true), + "path/posix" => ("path.posix", false), + "path/win32" => ("path.win32", false), + "async_hooks.default" => ("async_hooks", false), + // #3687: cluster default-import method calls (`cluster.fork()`, + // `cluster.emit(...)`) dispatch against the base `cluster` arms. + "cluster.default" => ("cluster", false), + "os.default" => ("os", false), + "path.default" => ("path", false), + "path.posix.default" => ("path.posix", false), + "path.win32.default" => ("path.win32", false), + "process.default" => ("process", false), + "querystring.default" => ("querystring", false), + "url.default" => ("url", false), + "util.default" => ("util", false), + "vm.default" => ("vm", false), + // #3987-adjacent: `process.getBuiltinModule("punycode")` returns the + // CJS-default namespace (`punycode.default`); without this alias its + // method calls dispatched as `("punycode.default", "decode")` — which + // has no arm — and returned `undefined`. The base `("punycode", …)` + // arms below already implement decode/encode/toASCII/toUnicode. + "punycode.default" => ("punycode", false), + _ => (module_name, false), + }; + let ctx = NmCtx { obj, args_ptr, args_len, assert_skip_prototype }; + match super::native_module_registry::nm_dispatch_lookup(module_name) { + Some(f) => f(&ctx, module_name, method_name), + None => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_assert(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); match (module_name, method_name) { - ("async_hooks", "createHook") => { - ptr_to_f64(crate::async_hooks::js_async_hooks_create_hook(arg(0)) as *const u8) + ("assert", "default") | ("assert/strict", "default") => js_assert_ok(arg(0), arg(1)), + ("assert", "strict") | ("assert/strict", "strict") => js_assert_ok(arg(0), arg(1)), + ("assert", "ok") | ("assert/strict", "ok") => js_assert_ok(arg(0), arg(1)), + ("assert", "fail") | ("assert/strict", "fail") => js_assert_fail(arg(0)), + ("assert", "equal") => js_assert_equal(arg(0), arg(1), arg(2)), + ("assert", "notEqual") => js_assert_not_equal(arg(0), arg(1), arg(2)), + ("assert", "strictEqual") + | ("assert/strict", "strictEqual") + | ("assert/strict", "equal") => js_assert_strict_equal(arg(0), arg(1), arg(2)), + ("assert", "notStrictEqual") + | ("assert/strict", "notStrictEqual") + | ("assert/strict", "notEqual") => js_assert_not_strict_equal(arg(0), arg(1), arg(2)), + ("assert", "deepEqual") if assert_skip_prototype => { + js_assert_deep_equal_skip_prototype(arg(0), arg(1), arg(2)) } - ("async_hooks", "executionAsyncId") => { - crate::async_hooks::js_async_hooks_execution_async_id() + ("assert", "notDeepEqual") if assert_skip_prototype => { + js_assert_not_deep_equal_skip_prototype(arg(0), arg(1), arg(2)) } - ("async_hooks", "triggerAsyncId") => crate::async_hooks::js_async_hooks_trigger_async_id(), - ("async_hooks", "executionAsyncResource") => { - crate::async_hooks::js_async_hooks_execution_async_resource() + ("assert", "deepStrictEqual") + | ("assert/strict", "deepStrictEqual") + | ("assert/strict", "deepEqual") + if assert_skip_prototype => + { + js_assert_deep_strict_equal_skip_prototype(arg(0), arg(1), arg(2)) } - - ("inspector", "open") => { - crate::node_inspector::js_node_inspector_open(arg(0), arg(1), arg(2)) + ("assert", "notDeepStrictEqual") + | ("assert/strict", "notDeepStrictEqual") + | ("assert/strict", "notDeepEqual") + if assert_skip_prototype => + { + js_assert_not_deep_strict_equal_skip_prototype(arg(0), arg(1), arg(2)) } - ("inspector", "close") => crate::node_inspector::js_node_inspector_close(), - ("inspector", "url") => crate::node_inspector::js_node_inspector_url(), - ("inspector", "waitForDebugger") => { - crate::node_inspector::js_node_inspector_wait_for_debugger() + ("assert", "deepEqual") => js_assert_deep_equal(arg(0), arg(1), arg(2)), + ("assert", "notDeepEqual") => js_assert_not_deep_equal(arg(0), arg(1), arg(2)), + ("assert", "deepStrictEqual") + | ("assert/strict", "deepStrictEqual") + | ("assert/strict", "deepEqual") => js_assert_deep_strict_equal(arg(0), arg(1), arg(2)), + ("assert", "partialDeepStrictEqual") | ("assert/strict", "partialDeepStrictEqual") => { + js_assert_partial_deep_strict_equal(arg(0), arg(1), arg(2)) } - ("inspector", "Session") => crate::node_inspector::js_node_inspector_session_new(), - ("inspector/promises", "Session") => { - crate::node_inspector::js_node_inspector_promises_session_new() + ("assert", "notDeepStrictEqual") + | ("assert/strict", "notDeepStrictEqual") + | ("assert/strict", "notDeepEqual") => { + js_assert_not_deep_strict_equal(arg(0), arg(1), arg(2)) } - ("inspector.Network", "requestWillBeSent") - | ("inspector.Network", "responseReceived") - | ("inspector.Network", "loadingFinished") - | ("inspector.Network", "loadingFailed") - | ("inspector.Network", "dataSent") - | ("inspector.Network", "dataReceived") - | ("inspector.Network", "webSocketCreated") - | ("inspector.Network", "webSocketClosed") - | ("inspector.Network", "webSocketHandshakeResponseReceived") => { - crate::node_inspector::js_node_inspector_network_notify(arg(0)) + ("assert", "match") | ("assert/strict", "match") => js_assert_match(arg(0), arg(1), arg(2)), + ("assert", "doesNotMatch") | ("assert/strict", "doesNotMatch") => { + js_assert_does_not_match(arg(0), arg(1), arg(2)) } - ("sea", "isSea") => crate::node_sea::js_sea_is_sea(), - ("sea", "getAsset") => crate::node_sea::js_sea_get_asset(arg(0), arg(1)), - ("sea", "getAssetAsBlob") => crate::node_sea::js_sea_get_asset_as_blob(arg(0), arg(1)), - ("sea", "getRawAsset") => crate::node_sea::js_sea_get_raw_asset(arg(0)), - ("sea", "getAssetKeys") => crate::node_sea::js_sea_get_asset_keys(), - // ── Buffer constructor static API ── - // `class MyBuffer extends Buffer {}; MyBuffer.from(...)` reaches this - // path through js_class_static_method_call's native-superclass - // fallback. Return plain Buffer instances, matching Node's internal - // FastBuffer behavior rather than species/subclass construction. + ("assert", "throws") | ("assert/strict", "throws") => { + js_assert_throws(arg(0), arg(1), arg(2)) + } + ("assert", "doesNotThrow") | ("assert/strict", "doesNotThrow") => { + js_assert_does_not_throw(arg(0), arg(1), arg(2)) + } + ("assert", "rejects") | ("assert/strict", "rejects") => { + js_assert_rejects(arg(0), arg(1), arg(2)) + } + ("assert", "doesNotReject") | ("assert/strict", "doesNotReject") => { + js_assert_does_not_reject(arg(0), arg(1), arg(2)) + } + ("assert", "ifError") | ("assert/strict", "ifError") => js_assert_if_error(arg(0)), + ("assert", "Assert") | ("assert/strict", "Assert") => { + crate::fs::validate::throw_type_error_with_code( + "Class constructor Assert cannot be invoked without 'new'", + "ERR_CONSTRUCT_CALL_REQUIRED", + ) + } + + // ── fs module (args are NaN-boxed f64, booleans return as i32→f64) ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_async_hooks(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("async_hooks", "createHook") => { + ptr_to_f64(crate::async_hooks::js_async_hooks_create_hook(arg(0)) as *const u8) + } + ("async_hooks", "executionAsyncId") => { + crate::async_hooks::js_async_hooks_execution_async_id() + } + ("async_hooks", "triggerAsyncId") => crate::async_hooks::js_async_hooks_trigger_async_id(), + ("async_hooks", "executionAsyncResource") => { + crate::async_hooks::js_async_hooks_execution_async_resource() + } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_bigint(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("bigint", "asIntN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), true), + ("bigint", "asUintN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), false), + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_buffer(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { ("buffer.Buffer", "from") => { let data = arg(0); let second = JSValue::from_bits(arg(1).to_bits()); @@ -481,209 +480,193 @@ pub(crate) unsafe fn dispatch_native_module_method( ("buffer", "isUtf8") => crate::buffer::js_buffer_is_utf8(arg(0)), // ── process EventEmitter API ── - ("process", "on") => crate::os::js_process_on(arg_bits(0), arg_bits(1)), - ("process", "addListener") => crate::os::js_process_add_listener(arg_bits(0), arg_bits(1)), - ("process", "once") => crate::os::js_process_once(arg_bits(0), arg_bits(1)), - ("process", "prependListener") => { - crate::os::js_process_prepend_listener(arg_bits(0), arg_bits(1)) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_child_process(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("child_process", "spawn") => { + let cmd = crate::string::js_string_materialize_to_heap(arg(0)) as i64; + let args_p = optional_ptr_addr(arg(1)) as i64; + let opts_p = optional_ptr_addr(arg(2)) as i64; + crate::child_process::reactor::js_child_process_spawn_streams(cmd, args_p, opts_p) } - ("process", "prependOnceListener") => { - crate::os::js_process_prepend_once_listener(arg_bits(0), arg_bits(1)) + ("child_process", "spawnSync") => { + let cmd = crate::string::js_string_materialize_to_heap(arg(0)); + let args_p = optional_ptr_addr(arg(1)) as *const crate::array::ArrayHeader; + let opts_p = optional_ptr_addr(arg(2)) as *const ObjectHeader; + let result = crate::child_process::js_child_process_spawn_sync(cmd, args_p, opts_p); + ptr_to_f64(result as *const u8) } - ("process", "emit") => crate::os::js_process_emit(arg_bits(0), pack_args_from(1)), - ("process", "removeListener") => { - crate::os::js_process_remove_listener(arg_bits(0), arg_bits(1)) + ("child_process", "execSync") => { + let cmd = crate::string::js_string_materialize_to_heap(arg(0)); + let opts_p = optional_ptr_addr(arg(1)) as *const ObjectHeader; + crate::child_process::js_child_process_exec_sync(cmd, opts_p) } - ("process", "off") => crate::os::js_process_off(arg_bits(0), arg_bits(1)), - ("process", "removeAllListeners") => { - crate::os::js_process_remove_all_listeners(arg_bits(0)) + ("child_process", "exec") => { + let cmd = crate::string::js_string_materialize_to_heap(arg(0)); + crate::child_process::js_child_process_exec(cmd, arg(1), arg(2)) } - ("process", "listenerCount") => { - crate::os::js_process_listener_count(arg_bits(0), arg_bits(1)) + ("child_process", "execFile") => { + let file = crate::string::js_string_materialize_to_heap(arg(0)) as i64; + crate::child_process::js_child_process_exec_file(file, arg(1), arg(2), arg(3)) } - ("process", "listeners") => { - ptr_to_f64(crate::os::js_process_listeners(arg_bits(0)) as *const u8) + ("child_process", "execFileSync") => { + let file = crate::string::js_string_materialize_to_heap(arg(0)) as i64; + crate::child_process::js_child_process_exec_file_sync(file, arg(1), arg(2)) } - ("process", "rawListeners") => { - ptr_to_f64(crate::os::js_process_raw_listeners(arg_bits(0)) as *const u8) + ("child_process", "_forkChild") => crate::child_process::js_fork_child(args_len), + ("child_process", "fork") => { + let module = crate::string::js_string_materialize_to_heap(arg(0)) as i64; + let args_p = optional_ptr_addr(arg(1)) as i64; + let opts_p = optional_ptr_addr(arg(2)) as i64; + crate::child_process::fork::js_child_process_fork(module, args_p, opts_p) } - ("process", "eventNames") => ptr_to_f64(crate::os::js_process_event_names() as *const u8), - ("process", "setMaxListeners") => crate::os::js_process_set_max_listeners(arg(0)), - ("process", "getMaxListeners") => crate::os::js_process_get_max_listeners(), - ("process", "send") => { - crate::process::process_ipc_send_call(arg(0), arg(1), arg(2), arg(3)) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_cluster(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("cluster", "setupPrimary") | ("cluster", "setupMaster") => { + crate::cluster::js_cluster_setup_primary(arg(0)) } - ("process", "disconnect") => crate::process::process_ipc_disconnect_call(), - ("process", "emitWarning") => { - crate::process::js_process_emit_warning(arg(0), arg(1), arg(2)); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("cluster", "fork") => crate::cluster::js_cluster_fork(arg(0)), + ("cluster", "disconnect") => crate::cluster::js_cluster_disconnect(arg(0)), + ("cluster", "Worker") => f64::from_bits(JSValue::undefined().bits()), + // #3687: node:cluster default-import EventEmitter surface. + ("cluster", "on") | ("cluster", "addListener") => { + crate::cluster::js_cluster_on(arg(0), arg(1)) } - ("process", "getBuiltinModule") => crate::process::js_process_get_builtin_module(arg(0)), - ("process", "execve") => crate::process::js_process_execve(arg(0), arg(1), arg(2)), - ("module", "createRequire") => crate::module_require::js_module_create_require(arg(0)), - ("module", "enableCompileCache") => crate::process::js_module_enable_compile_cache(arg(0)), - ("module", "flushCompileCache") => crate::process::js_module_flush_compile_cache(), - ("module", "getCompileCacheDir") => crate::process::js_module_get_compile_cache_dir(), - ("module", "getSourceMapsSupport") => crate::process::js_module_get_source_maps_support(), - ("module", "isBuiltin") => crate::process::js_module_is_builtin(arg(0)), - ("module", "Module") => crate::process::js_module_module_new(arg(0)), - ("module", "_findPath") => crate::process::js_module_find_path(arg(0), arg(1), arg(2)), - ("module", "_initPaths") => crate::process::js_module_init_paths(), - ("module", "_load") => crate::process::js_module_load(arg(0), arg(1), arg(2)), - ("module", "_nodeModulePaths") => crate::process::js_module_node_module_paths(arg(0)), - ("module", "_preloadModules") => crate::process::js_module_preload_modules(arg(0)), - ("module", "_resolveFilename") => { - crate::process::js_module_resolve_filename(arg(0), arg(1), arg(2), arg(3)) + ("cluster", "once") => crate::cluster::js_cluster_once(arg(0), arg(1)), + ("cluster", "prependListener") => { + crate::cluster::js_cluster_prepend_listener(arg(0), arg(1)) } - ("module", "_resolveLookupPaths") => { - crate::process::js_module_resolve_lookup_paths(arg(0), arg(1)) + ("cluster", "prependOnceListener") => { + crate::cluster::js_cluster_prepend_once_listener(arg(0), arg(1)) } - ("module", "register") => crate::process::js_module_register(arg(0), arg(1), arg(2)), - ("module", "registerHooks") => crate::process::js_module_register_hooks(arg(0)), - ("module", "setSourceMapsSupport") => { - crate::process::js_module_set_source_maps_support(arg(0), arg(1)) + ("cluster", "emit") => crate::cluster::js_cluster_emit(arg(0), pack_args_from(1)), + ("cluster", "eventNames") => crate::cluster::js_cluster_event_names(), + ("cluster", "listenerCount") => crate::cluster::js_cluster_listener_count(arg(0)), + ("cluster", "removeListener") | ("cluster", "off") => { + crate::cluster::js_cluster_remove_listener(arg(0), arg(1)) } - ("module", "stripTypeScriptTypes") => { - crate::process::js_module_strip_typescript_types(arg(0), arg(1)) + ("cluster", "removeAllListeners") => { + crate::cluster::js_cluster_remove_all_listeners(arg(0)) } - ("process", "cwd") => str_to_f64(crate::os::js_process_cwd()), - ("process", "uptime") => crate::os::js_process_uptime(), - ("process", "memoryUsage") => crate::process::js_process_memory_usage(), - ("process", "threadCpuUsage") => crate::process::js_process_thread_cpu_usage(arg(0)), - ("process", "availableMemory") => crate::process::js_process_available_memory(), - ("process", "constrainedMemory") => crate::process::js_process_constrained_memory(), - ("process", "resourceUsage") => crate::process::js_process_resource_usage(), - ("process", "getActiveResourcesInfo") => crate::process::js_process_active_resources_info(), - ("process", "binding") => crate::process::js_process_binding(arg(0)), - ("process", "_linkedBinding") => crate::process::js_process_linked_binding(arg(0)), - ("process", "dlopen") => crate::process::js_process_dlopen(), - ("process", "_rawDebug") => crate::process::js_process_raw_debug(), - ("process", "_debugProcess") => crate::process::js_process_debug_process(), - ("process", "_debugEnd") => crate::process::js_process_debug_end(), - ("process", "_startProfilerIdleNotifier") => { - crate::process::js_process_start_profiler_idle_notifier() + + // #1577: captured-then-called crypto methods (`const f = + // crypto.createHash; f(...)`). The impls live in perry-stdlib (which + // depends on this crate), so route through the dispatcher stdlib + // registers at startup via `js_set_native_crypto_dispatch`. Null when + // stdlib isn't linked (e.g. runtime-only tests) → undefined. The + // `randomFillSync` arm above is handled inline and never reaches here. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_console(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("console", "Console") => crate::builtins::js_console_new2(arg(0), arg(1)), + ("console", "log") | ("console", "info") | ("console", "debug") | ("console", "dirxml") => { + crate::builtins::js_console_log_spread(pack_args()); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "_stopProfilerIdleNotifier") => { - crate::process::js_process_stop_profiler_idle_notifier() + ("console", "error") => { + crate::builtins::js_console_error_spread(pack_args()); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "reallyExit") => crate::process::js_process_really_exit(), - ("process", "_fatalException") => { - crate::process::js_process_fatal_exception(arg(0), arg(1)) + ("console", "warn") => { + crate::builtins::js_console_warn_spread(pack_args()); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "_tickCallback") => crate::process::js_process_tick_callback(), - ("process", "_getActiveHandles") => crate::process::js_process_get_active_handles(), - ("process", "_getActiveRequests") => crate::process::js_process_get_active_requests(), - ("process", "openStdin") => crate::process::js_process_open_stdin(), - ("process", "_kill") => crate::process::js_process_internal_kill(), - ("process", "getuid") => crate::process::js_process_getuid(), - ("process", "geteuid") => crate::process::js_process_geteuid(), - ("process", "getgid") => crate::process::js_process_getgid(), - ("process", "getegid") => crate::process::js_process_getegid(), - ("process", "sourceMapsEnabled") => crate::process::js_process_source_maps_enabled(), - ("process", "setSourceMapsEnabled") => { - crate::process::js_process_set_source_maps_enabled(arg(0)) + ("console", "assert") => { + crate::builtins::js_console_assert_spread(arg(0), pack_args_from(1) as i64); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "ref") => crate::process::js_process_ref(arg(0)), - ("process", "unref") => crate::process::js_process_unref(arg(0)), - ("process", "hasUncaughtExceptionCaptureCallback") => { - crate::process::js_process_has_uncaught_exception_capture_callback() + ("console", "dir") => { + crate::builtins::js_console_log_dynamic(arg(0)); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "setUncaughtExceptionCaptureCallback") => { - crate::process::js_process_set_uncaught_exception_capture_callback(arg(0)) + ("console", "trace") => { + crate::builtins::js_console_trace_spread(pack_args()); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "addUncaughtExceptionCaptureCallback") => { - crate::process::js_process_add_uncaught_exception_capture_callback(arg(0)) + ("console", "table") => { + if args_len > 1 { + crate::builtins::js_console_table_with_properties(arg(0), arg(1)); + } else { + crate::builtins::js_console_table(arg(0)); + } + f64::from_bits(JSValue::undefined().bits()) } - ("process", "nextTick") => { - // Validate the callback and forward trailing args (#3046). - unsafe { crate::os::js_process_next_tick(arg_bits(0), pack_args_from(1)) }; - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "clear") => { + crate::builtins::js_console_clear(); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "chdir") => { - // #3043 — route dynamic/method-value chdir calls through the - // full-value validator (matching the static codegen path) so a - // non-string argument throws TypeError [ERR_INVALID_ARG_TYPE] - // instead of silently no-oping on a null string pointer. - unsafe { - crate::process::js_process_chdir_jsv(arg(0)); - } - f64::from_bits(crate::value::TAG_UNDEFINED) - } - ("process", "loadEnvFile") => { - crate::process::js_process_load_env_file(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) - } - // #3712: node:http module-level header validation helpers. These mirror - // Node's `validateHeaderName` / `validateHeaderValue` (lib/_http_common - // + lib/_http_outgoing): on invalid input they throw the matching error - // codes, otherwise they return undefined. - ("http", "validateHeaderName") => js_http_validate_header_name(arg(0), arg(1)), - ("http", "validateHeaderValue") => js_http_validate_header_value(arg(0), arg(1)), - // #3712: parser/proxy setters are deterministic no-ops in Perry's - // runtime (no shared parser pool / env-driven proxy state), matching - // Node's `undefined` return for valid inputs. - ("http", "setMaxIdleHTTPParsers") | ("http", "setGlobalProxyFromEnv") => { - js_http_setter_noop(arg(0)) - } - ("http", "_connectionListener") => js_http_connection_listener_noop(arg(0)), - ("events", "init") => f64::from_bits(crate::value::TAG_UNDEFINED), - ("events", "EventEmitterAsyncResource") => { - let message = - b"Class constructor EventEmitterAsyncResource cannot be invoked without 'new'"; - let msg = crate::string::js_string_from_bytes(message.as_ptr(), message.len() as u32); - let err = crate::error::js_typeerror_new(msg); - crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64)) - } - ("process", "getgroups") => crate::process::js_process_getgroups(), - ("process", "setuid") => { - crate::process::js_process_setuid(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) - } - ("process", "seteuid") => { - crate::process::js_process_seteuid(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) - } - ("process", "setgid") => { - crate::process::js_process_setgid(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) - } - ("process", "setegid") => { - crate::process::js_process_setegid(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) - } - ("process", "setgroups") => { - crate::process::js_process_setgroups(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "count") => { + crate::builtins::js_console_count_value(arg(0)); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "initgroups") => { - crate::process::js_process_initgroups(arg(0), arg(1)); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "countReset") => { + crate::builtins::js_console_count_reset_value(arg(0)); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "kill") => crate::os::js_process_kill(arg(0), arg(1)), - ("process", "exit") => { - crate::process::js_process_exit(arg(0)); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "time") => { + crate::builtins::js_console_time_value(arg(0)); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "abort") => { - crate::process::js_process_abort(); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "timeEnd") => { + crate::builtins::js_console_time_end_value(arg(0)); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "umask") => { - let mask = arg(0); - let mask_value = JSValue::from_bits(mask.to_bits()); - if mask_value.is_undefined() { - crate::process::js_process_umask() + ("console", "timeLog") => { + if args_len > 1 { + crate::builtins::js_console_time_log_spread(arg(0), pack_args_from(1)); } else { - crate::process::js_process_umask_set(mask) + crate::builtins::js_console_time_log_value(arg(0)); } + f64::from_bits(JSValue::undefined().bits()) } - ("process", "emitWarning") => { - crate::process::js_process_emit_warning(arg(0), arg(1), arg(2)); - f64::from_bits(crate::value::TAG_UNDEFINED) + ("console", "group") | ("console", "groupCollapsed") => { + if args_len > 0 { + crate::builtins::js_console_log_dynamic(arg(0)); + } + crate::builtins::js_console_group_begin(); + f64::from_bits(JSValue::undefined().bits()) } - ("process", "hrtime") => crate::os::js_process_hrtime(arg(0)), - ("process", "cpuUsage") => crate::process::js_process_cpu_usage(arg(0)), - // ── crypto module ── + ("console", "groupEnd") => { + crate::builtins::js_console_group_end(); + f64::from_bits(JSValue::undefined().bits()) + } + ("console", "profile") | ("console", "profileEnd") | ("console", "timeStamp") => { + f64::from_bits(JSValue::undefined().bits()) + } + ("console", "context") => crate::builtins::js_console_context(arg(0)), + ("console", "createTask") => crate::builtins::js_console_create_task(arg(0)), + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_crypto(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { ("crypto", "randomFillSync") if args_len >= 1 => { super::native_module_crypto_random::random_fill_sync(arg(0), arg(1), arg(2)) } @@ -703,275 +686,151 @@ pub(crate) unsafe fn dispatch_native_module_method( super::native_module_crypto_random::random_fill_sync(arg(0), undefined, undefined) } // node:vm (createContext via #4050; rest #4079/#4087) - ("vm", m) => crate::node_vm::dispatch_vm_method(m, arg(0), arg(1), arg(2)), - // ── tty module ── - ("tty", "isatty") => crate::tty::js_tty_isatty(arg(0)), - ("tty", "ReadStream") => crate::tty::js_tty_read_stream_new(arg(0)), - ("tty", "WriteStream") => crate::tty::js_tty_write_stream_new(arg(0)), - - // ── tls module helpers ── - ("tls", "getCiphers") => crate::tls::js_tls_get_ciphers(), - ("tls", "getCACertificates") => crate::tls::js_tls_get_ca_certificates(arg(0)), - ("tls", "setDefaultCACertificates") => { - crate::tls::js_tls_set_default_ca_certificates(arg(0)) - } - ("tls", "checkServerIdentity") => crate::tls::js_tls_check_server_identity(arg(0), arg(1)), - ("tls", "createSecureContext") => crate::tls::js_tls_create_secure_context(arg(0)), - ("tls", "SecureContext") => crate::tls::js_tls_secure_context_new(arg(0)), - - // ── wasi module ── - ("wasi", "WASI") => crate::wasi::js_wasi_constructor_call(arg(0)), - - // ── net module legacy/internal helpers ── - ("net", "_normalizeArgs") => crate::net_validate::js_net_normalize_args(arg(0)), - ("net", "_createServerHandle") => crate::net_validate::js_net_create_server_handle_stub( - arg(0), - arg(1), - arg(2), - arg(3), - arg(4), - ), - - // ── perf_hooks module (performance.*) ── - // Statically lowered at call sites (module_static.rs); these arms - // also serve the generic namespace-object method-dispatch path. - ("perf_hooks", "now") => crate::date::js_performance_now(), - ("perf_hooks", "mark") => crate::perf_hooks::js_perf_mark(arg(0), arg(1)), - ("perf_hooks", "measure") => crate::perf_hooks::js_perf_measure(arg(0), arg(1), arg(2)), - ("perf_hooks", "getEntries") => crate::perf_hooks::js_perf_get_entries(), - ("perf_hooks", "getEntriesByType") => { - crate::perf_hooks::js_perf_get_entries_by_type(arg(0)) - } - ("perf_hooks", "getEntriesByName") => { - crate::perf_hooks::js_perf_get_entries_by_name(arg(0), arg(1)) - } - ("perf_hooks", "clearMarks") => crate::perf_hooks::js_perf_clear_marks(arg(0)), - ("perf_hooks", "clearMeasures") => crate::perf_hooks::js_perf_clear_measures(arg(0)), - ("perf_hooks", "eventLoopUtilization") => { - crate::perf_hooks::js_perf_event_loop_utilization(arg(0), arg(1)) + ("crypto" | "crypto.webcrypto", _) => { + let ptr = + crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) + } } - ("perf_hooks", "toJSON") => crate::perf_hooks::js_perf_to_json(), - ("perf_hooks", "clearResourceTimings") => { - crate::perf_hooks::js_perf_clear_resource_timings() + ("crypto.subtle", _) => { + let ptr = crate::value::JS_NATIVE_WEBCRYPTO_DISPATCH + .load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) + } } - ("perf_hooks", "setResourceTimingBufferSize") => { - crate::perf_hooks::js_perf_set_resource_timing_buffer_size(arg(0)) + // Captured-then-called zlib methods (`const f = zlib.gzip; await f(buf)`, + // `util.promisify(zlib.gzip)`). Mirrors the crypto arm above — the + // impls live in perry-stdlib which depends on this crate, so route + // through the dispatcher stdlib registers at startup via + // `js_set_native_zlib_dispatch`. Null when stdlib isn't linked. + ("crypto.Certificate", _) => { + let qualified: &[u8] = match method_name { + "verifySpkac" => b"Certificate.verifySpkac", + "exportPublicKey" => b"Certificate.exportPublicKey", + "exportChallenge" => b"Certificate.exportChallenge", + _ => return f64::from_bits(JSValue::undefined().bits()), + }; + let ptr = + crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(qualified.as_ptr(), qualified.len(), args_ptr, args_len) + } } - ("perf_hooks", "markResourceTiming") => crate::perf_hooks::js_perf_mark_resource_timing( - arg(0), - arg(1), - arg(2), - arg(3), - arg(4), - arg(5), - arg(6), - arg(7), - ), - ("perf_hooks", "timerify") => crate::perf_hooks::js_perf_timerify(arg(0), arg(1)), - // ── PerformanceObserver instance (perf_observer) ── - // The registry index lives in field[1] of the namespace object; the - // runtime fns re-derive it from the object value. - ("perf_observer", "observe") => { - let obs_val = crate::value::js_nanbox_pointer(obj as i64); - crate::perf_hooks::js_perf_observer_observe(obs_val, arg(0)) + // #3906: top-level v8 helpers invoked through a bound callable + // (`const s = v8.serialize; s(x)`). The method-call form + // (`v8.serialize(x)`) already lowers through the codegen + // NATIVE_MODULE_TABLE; these arms keep the value-read/bound-call form + // coherent with the same FFI impls. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_dgram(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("dgram", "createSocket") | ("dgram", "Socket") => { + crate::dgram::js_dgram_create_socket(pack_args()) } - ("perf_observer", "disconnect") => { - let obs_val = crate::value::js_nanbox_pointer(obj as i64); - crate::perf_hooks::js_perf_observer_disconnect(obs_val) + + // ── console module namespace (`node:console` / `console`) ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_dns(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("dns", "getServers") => crate::dns::dns_get_servers_value(), + ("dns", "setServers") => crate::dns::dns_set_servers_value(arg(0)), + ("dns/promises", "getServers") => crate::dns::dns_promises_get_servers_value(), + ("dns/promises", "setServers") => crate::dns::dns_promises_set_servers_value(arg(0)), + ("dns" | "dns/promises", "getDefaultResultOrder") => { + crate::dns::dns_get_default_result_order_value() } - ("perf_observer", "takeRecords") => { - let obs_val = crate::value::js_nanbox_pointer(obj as i64); - crate::perf_hooks::js_perf_observer_take_records(obs_val) + ("dns" | "dns/promises", "setDefaultResultOrder") => { + crate::dns::dns_set_default_result_order_value(arg(0)) } - // ── PerformanceObserverEntryList (the callback `list` arg) ── - ("perf_observer_list", "getEntries") => crate::perf_hooks::current_list_get_entries(), - ("perf_observer_list", "getEntriesByType") => { - crate::perf_hooks::current_list_get_by_type(arg(0)) - } - ("perf_observer_list", "getEntriesByName") => { - crate::perf_hooks::current_list_get_by_name(arg(0)) - } - - // ── Histogram instance methods (#1336) ── - // Every method is a no-op on the stub — `enable`/`disable`/`reset` - // don't sample anything, `record`/`recordDelta`/`add` discard input. - // `percentile(p)` returns 0 (no samples => no rank). - ("perf_histogram", "enable") - | ("perf_histogram", "disable") - | ("perf_histogram", "reset") - | ("perf_histogram", "record") - | ("perf_histogram", "recordDelta") - | ("perf_histogram", "add") => crate::perf_hooks::js_perf_histogram_noop(), - ("perf_histogram", "percentile") | ("perf_histogram", "percentileBigInt") => { - crate::perf_hooks::js_perf_histogram_percentile(arg(0)) - } + // #2130: captured-then-called child_process methods (`const spawn = + // require('child_process').spawn; spawn(...)`, Node's canonical test + // idiom). The bound-method closure produced by `cp.spawn` (and the + // other entries allowlisted in `is_native_module_callable_export`) + // funnels back here when invoked. The method-call form + // (`cp.spawn(...)`) is lowered to the same FFIs through dedicated + // codegen arms (`expr/child_proc.rs`); this arm mirrors them for the + // value-call form. `cmd` / `file` / `module` strings come in NaN-boxed + // (SSO-safe via `js_string_materialize_to_heap`); `args` is the array + // pointer (or null); `opts` is the options-object pointer (or 0). + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // ── timers module ── - ("timers", "setTimeout") if args_len >= 2 => { - let cb = arg(0); - let delay = arg(1); - let cb_handle = { - let bits = cb.to_bits(); - if (bits >> 48) >= 0x7FF8 { - (bits & 0x0000_FFFF_FFFF_FFFF) as i64 - } else { - bits as i64 - } - }; - if args_len > 2 { - let extra_ptr = unsafe { args_ptr.add(2) }; - return f64::from_bits( - JSValue::pointer(crate::timer::js_set_timeout_callback_args( - cb_handle, - delay, - extra_ptr, - (args_len - 2) as i32, - ) as *mut u8) - .bits(), - ); - } - return f64::from_bits(JSValue::pointer( - crate::timer::js_set_timeout_callback(cb_handle, delay) as *mut u8, - ).bits()); - } - ("timers", "setImmediate") if args_len >= 1 => { - let cb = arg(0); - let cb_handle = { - let bits = cb.to_bits(); - if (bits >> 48) >= 0x7FF8 { - (bits & 0x0000_FFFF_FFFF_FFFF) as i64 - } else { - bits as i64 - } - }; - if args_len > 1 { - let extra_ptr = unsafe { args_ptr.add(1) }; - return f64::from_bits( - JSValue::pointer(crate::timer::js_set_immediate_callback_args( - cb_handle, - extra_ptr, - (args_len - 1) as i32, - ) as *mut u8) - .bits(), - ); - } - return f64::from_bits( - JSValue::pointer(crate::timer::js_set_immediate_callback(cb_handle) as *mut u8) - .bits(), - ); - } - ("timers", "setInterval") if args_len >= 2 => { - let cb = arg(0); - let delay = arg(1); - let bits = cb.to_bits(); - let cb_handle = if (bits >> 48) >= 0x7FF8 { - (bits & 0x0000_FFFF_FFFF_FFFF) as i64 +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_domain(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("domain", "Domain" | "createDomain" | "create") => { + let ptr = + crate::value::JS_NATIVE_DOMAIN_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) } else { - bits as i64 - }; - if args_len > 2 { - let extra_ptr = unsafe { args_ptr.add(2) }; - return f64::from_bits( - JSValue::pointer(crate::timer::js_set_interval_callback_args( - cb_handle, - delay, - extra_ptr, - (args_len - 2) as i32, - ) as *mut u8) - .bits(), - ); + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) } - return f64::from_bits( - JSValue::pointer(crate::timer::setInterval(cb_handle, delay) as *mut u8).bits(), - ); - } - ("timers", "clearTimeout") if args_len >= 1 => { - crate::timer::js_clear_timeout_value(arg(0)); - return f64::from_bits(JSValue::undefined().bits()); - } - ("timers", "clearImmediate") if args_len >= 1 => { - crate::timer::js_clear_immediate_value(arg(0)); - return f64::from_bits(JSValue::undefined().bits()); - } - ("timers", "clearInterval") if args_len >= 1 => { - crate::timer::js_clear_interval_value(arg(0)); - return f64::from_bits(JSValue::undefined().bits()); - } - // ── assert module ── - // Root-callable `assert(x, msg)` / `assert.strict(x, msg)` — - // HIR lowers these to method "default". - ("assert", "default") | ("assert/strict", "default") => js_assert_ok(arg(0), arg(1)), - ("assert", "strict") | ("assert/strict", "strict") => js_assert_ok(arg(0), arg(1)), - ("assert", "ok") | ("assert/strict", "ok") => js_assert_ok(arg(0), arg(1)), - ("assert", "fail") | ("assert/strict", "fail") => js_assert_fail(arg(0)), - ("assert", "equal") => js_assert_equal(arg(0), arg(1), arg(2)), - ("assert", "notEqual") => js_assert_not_equal(arg(0), arg(1), arg(2)), - ("assert", "strictEqual") - | ("assert/strict", "strictEqual") - | ("assert/strict", "equal") => js_assert_strict_equal(arg(0), arg(1), arg(2)), - ("assert", "notStrictEqual") - | ("assert/strict", "notStrictEqual") - | ("assert/strict", "notEqual") => js_assert_not_strict_equal(arg(0), arg(1), arg(2)), - ("assert", "deepEqual") if assert_skip_prototype => { - js_assert_deep_equal_skip_prototype(arg(0), arg(1), arg(2)) - } - ("assert", "notDeepEqual") if assert_skip_prototype => { - js_assert_not_deep_equal_skip_prototype(arg(0), arg(1), arg(2)) - } - ("assert", "deepStrictEqual") - | ("assert/strict", "deepStrictEqual") - | ("assert/strict", "deepEqual") - if assert_skip_prototype => - { - js_assert_deep_strict_equal_skip_prototype(arg(0), arg(1), arg(2)) - } - ("assert", "notDeepStrictEqual") - | ("assert/strict", "notDeepStrictEqual") - | ("assert/strict", "notDeepEqual") - if assert_skip_prototype => - { - js_assert_not_deep_strict_equal_skip_prototype(arg(0), arg(1), arg(2)) } - ("assert", "deepEqual") => js_assert_deep_equal(arg(0), arg(1), arg(2)), - ("assert", "notDeepEqual") => js_assert_not_deep_equal(arg(0), arg(1), arg(2)), - ("assert", "deepStrictEqual") - | ("assert/strict", "deepStrictEqual") - | ("assert/strict", "deepEqual") => js_assert_deep_strict_equal(arg(0), arg(1), arg(2)), - ("assert", "partialDeepStrictEqual") | ("assert/strict", "partialDeepStrictEqual") => { - js_assert_partial_deep_strict_equal(arg(0), arg(1), arg(2)) - } - ("assert", "notDeepStrictEqual") - | ("assert/strict", "notDeepStrictEqual") - | ("assert/strict", "notDeepEqual") => { - js_assert_not_deep_strict_equal(arg(0), arg(1), arg(2)) - } - ("assert", "match") | ("assert/strict", "match") => js_assert_match(arg(0), arg(1), arg(2)), - ("assert", "doesNotMatch") | ("assert/strict", "doesNotMatch") => { - js_assert_does_not_match(arg(0), arg(1), arg(2)) - } - ("assert", "throws") | ("assert/strict", "throws") => { - js_assert_throws(arg(0), arg(1), arg(2)) - } - ("assert", "doesNotThrow") | ("assert/strict", "doesNotThrow") => { - js_assert_does_not_throw(arg(0), arg(1), arg(2)) - } - ("assert", "rejects") | ("assert/strict", "rejects") => { - js_assert_rejects(arg(0), arg(1), arg(2)) - } - ("assert", "doesNotReject") | ("assert/strict", "doesNotReject") => { - js_assert_does_not_reject(arg(0), arg(1), arg(2)) - } - ("assert", "ifError") | ("assert/strict", "ifError") => js_assert_if_error(arg(0)), - ("assert", "Assert") | ("assert/strict", "Assert") => { - crate::fs::validate::throw_type_error_with_code( - "Class constructor Assert cannot be invoked without 'new'", - "ERR_CONSTRUCT_CALL_REQUIRED", - ) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_events(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("events", "init") => f64::from_bits(crate::value::TAG_UNDEFINED), + ("events", "EventEmitterAsyncResource") => { + let message = + b"Class constructor EventEmitterAsyncResource cannot be invoked without 'new'"; + let msg = crate::string::js_string_from_bytes(message.as_ptr(), message.len() as u32); + let err = crate::error::js_typeerror_new(msg); + crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64)) } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // ── fs module (args are NaN-boxed f64, booleans return as i32→f64) ── +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_fs(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { ("fs", "_toUnixTimestamp") => crate::fs::js_fs_to_unix_timestamp(arg(0)), ("fs", "existsSync") => bool_to_f64(crate::fs::js_fs_exists_sync(arg(0))), ("fs", "readFileSync") => crate::fs::js_fs_read_file_dispatch(arg(0), arg(1)), @@ -1117,51 +976,308 @@ pub(crate) unsafe fn dispatch_native_module_method( ("fs", "isDirectory") => bool_to_f64(crate::fs::js_fs_is_directory(arg(0))), // ── os module (no args, return string or f64) ── - ("os", "tmpdir") => str_to_f64(crate::os::js_os_tmpdir()), - ("os", "homedir") => str_to_f64(crate::os::js_os_homedir()), - ("os", "platform") => str_to_f64(crate::os::js_os_platform()), - ("os", "arch") => str_to_f64(crate::os::js_os_arch()), - ("os", "hostname") => str_to_f64(crate::os::js_os_hostname()), - ("os", "type") => str_to_f64(crate::os::js_os_type()), - ("os", "release") => str_to_f64(crate::os::js_os_release()), - ("os", "eol") => str_to_f64(crate::os::js_os_eol()), - ("os", "devNull") => str_to_f64(crate::os::js_os_dev_null()), - ("os", "totalmem") => crate::os::js_os_totalmem(), - ("os", "freemem") => crate::os::js_os_freemem(), - ("os", "uptime") => crate::os::js_os_uptime(), - ("os", "availableParallelism") => crate::os::js_os_available_parallelism(), - ("os", "endianness") => str_to_f64(crate::os::js_os_endianness()), - ("os", "machine") => str_to_f64(crate::os::js_os_machine()), - ("os", "loadavg") => { - f64::from_bits(JSValue::pointer(crate::os::js_os_loadavg() as *const u8).bits()) - } - ("os", "version") => str_to_f64(crate::os::js_os_version()), - ("os", "cpus") => { - f64::from_bits(JSValue::pointer(crate::os::js_os_cpus() as *const u8).bits()) - } - ("os", "networkInterfaces") => f64::from_bits( - JSValue::pointer(crate::os::js_os_network_interfaces() as *const u8).bits(), - ), - ("os", "userInfo") => { - // #3004 — honor a runtime `options.encoding === "buffer"` value - // (variable / function-return / computed-key options object). - let opts_bits = arg(0).to_bits() as i64; - f64::from_bits( - JSValue::pointer(crate::os::js_os_user_info_options(opts_bits) as *const u8).bits(), - ) - } - ("os", "getPriority") => crate::os::js_os_get_priority(arg(0)), - ("os", "setPriority") => crate::os::js_os_set_priority(arg(0), arg(1)), + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // ── path module (args are NaN-boxed strings → extract raw StringHeader ptr) ── - ("path", "dirname") => str_to_f64(crate::path::js_path_dirname(require_path_str_ptr(0))), - ("path", "basename") => path_basename_value(false), - ("path", "extname") => str_to_f64(crate::path::js_path_extname(require_path_str_ptr(0))), - ("path", "normalize") => { - str_to_f64(crate::path::js_path_normalize(require_path_str_ptr(0))) - } - ("path", "resolve") => path_resolve_value(false), - ("path", "join") => path_join_value(false), +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_http(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("http", "validateHeaderName") => js_http_validate_header_name(arg(0), arg(1)), + ("http", "validateHeaderValue") => js_http_validate_header_value(arg(0), arg(1)), + // #3712: parser/proxy setters are deterministic no-ops in Perry's + // runtime (no shared parser pool / env-driven proxy state), matching + // Node's `undefined` return for valid inputs. + ("http", "setMaxIdleHTTPParsers") | ("http", "setGlobalProxyFromEnv") => { + js_http_setter_noop(arg(0)) + } + ("http", "_connectionListener") => js_http_connection_listener_noop(arg(0)), + ("http", "createServer") + | ("http", "Server") + // #4904: captured / aliased client entry points (`const { get } = + // require('http'); get(opts, cb)`) — same bound-value mechanism as + // the server factories; the stdlib dispatcher routes them to + // `js_http_get` / `js_http_request` (and https twins). + | ("http", "request") + | ("http", "get") + | ("https", "request") + | ("https", "get") + | ("https", "createServer") + | ("https", "Server") + | ("http2", "createServer") + | ("http2", "createSecureServer") + | ("http2", "Server") => { + let ptr = + crate::value::JS_NATIVE_HTTP_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn( + *const u8, + usize, + *const u8, + usize, + *const f64, + usize, + ) -> f64 = std::mem::transmute(ptr); + dispatch( + module_name.as_ptr(), + module_name.len(), + method_name.as_ptr(), + method_name.len(), + args_ptr, + args_len, + ) + } + } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_inspector(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("inspector", "open") => { + crate::node_inspector::js_node_inspector_open(arg(0), arg(1), arg(2)) + } + ("inspector", "close") => crate::node_inspector::js_node_inspector_close(), + ("inspector", "url") => crate::node_inspector::js_node_inspector_url(), + ("inspector", "waitForDebugger") => { + crate::node_inspector::js_node_inspector_wait_for_debugger() + } + ("inspector", "Session") => crate::node_inspector::js_node_inspector_session_new(), + ("inspector/promises", "Session") => { + crate::node_inspector::js_node_inspector_promises_session_new() + } + ("inspector.Network", "requestWillBeSent") + | ("inspector.Network", "responseReceived") + | ("inspector.Network", "loadingFinished") + | ("inspector.Network", "loadingFailed") + | ("inspector.Network", "dataSent") + | ("inspector.Network", "dataReceived") + | ("inspector.Network", "webSocketCreated") + | ("inspector.Network", "webSocketClosed") + | ("inspector.Network", "webSocketHandshakeResponseReceived") => { + crate::node_inspector::js_node_inspector_network_notify(arg(0)) + } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_module(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("module", "createRequire") => crate::module_require::js_module_create_require(arg(0)), + ("module", "enableCompileCache") => crate::process::js_module_enable_compile_cache(arg(0)), + ("module", "flushCompileCache") => crate::process::js_module_flush_compile_cache(), + ("module", "getCompileCacheDir") => crate::process::js_module_get_compile_cache_dir(), + ("module", "getSourceMapsSupport") => crate::process::js_module_get_source_maps_support(), + ("module", "isBuiltin") => crate::process::js_module_is_builtin(arg(0)), + ("module", "Module") => crate::process::js_module_module_new(arg(0)), + ("module", "_findPath") => crate::process::js_module_find_path(arg(0), arg(1), arg(2)), + ("module", "_initPaths") => crate::process::js_module_init_paths(), + ("module", "_load") => crate::process::js_module_load(arg(0), arg(1), arg(2)), + ("module", "_nodeModulePaths") => crate::process::js_module_node_module_paths(arg(0)), + ("module", "_preloadModules") => crate::process::js_module_preload_modules(arg(0)), + ("module", "_resolveFilename") => { + crate::process::js_module_resolve_filename(arg(0), arg(1), arg(2), arg(3)) + } + ("module", "_resolveLookupPaths") => { + crate::process::js_module_resolve_lookup_paths(arg(0), arg(1)) + } + ("module", "register") => crate::process::js_module_register(arg(0), arg(1), arg(2)), + ("module", "registerHooks") => crate::process::js_module_register_hooks(arg(0)), + ("module", "setSourceMapsSupport") => { + crate::process::js_module_set_source_maps_support(arg(0), arg(1)) + } + ("module", "stripTypeScriptTypes") => { + crate::process::js_module_strip_typescript_types(arg(0), arg(1)) + } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_net(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("net", "_normalizeArgs") => crate::net_validate::js_net_normalize_args(arg(0)), + ("net", "_createServerHandle") => crate::net_validate::js_net_create_server_handle_stub( + arg(0), + arg(1), + arg(2), + arg(3), + arg(4), + ), + + // ── perf_hooks module (performance.*) ── + // Statically lowered at call sites (module_static.rs); these arms + // also serve the generic namespace-object method-dispatch path. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_os(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("os", "tmpdir") => str_to_f64(crate::os::js_os_tmpdir()), + ("os", "homedir") => str_to_f64(crate::os::js_os_homedir()), + ("os", "platform") => str_to_f64(crate::os::js_os_platform()), + ("os", "arch") => str_to_f64(crate::os::js_os_arch()), + ("os", "hostname") => str_to_f64(crate::os::js_os_hostname()), + ("os", "type") => str_to_f64(crate::os::js_os_type()), + ("os", "release") => str_to_f64(crate::os::js_os_release()), + ("os", "eol") => str_to_f64(crate::os::js_os_eol()), + ("os", "devNull") => str_to_f64(crate::os::js_os_dev_null()), + ("os", "totalmem") => crate::os::js_os_totalmem(), + ("os", "freemem") => crate::os::js_os_freemem(), + ("os", "uptime") => crate::os::js_os_uptime(), + ("os", "availableParallelism") => crate::os::js_os_available_parallelism(), + ("os", "endianness") => str_to_f64(crate::os::js_os_endianness()), + ("os", "machine") => str_to_f64(crate::os::js_os_machine()), + ("os", "loadavg") => { + f64::from_bits(JSValue::pointer(crate::os::js_os_loadavg() as *const u8).bits()) + } + ("os", "version") => str_to_f64(crate::os::js_os_version()), + ("os", "cpus") => { + f64::from_bits(JSValue::pointer(crate::os::js_os_cpus() as *const u8).bits()) + } + ("os", "networkInterfaces") => f64::from_bits( + JSValue::pointer(crate::os::js_os_network_interfaces() as *const u8).bits(), + ), + ("os", "userInfo") => { + // #3004 — honor a runtime `options.encoding === "buffer"` value + // (variable / function-return / computed-key options object). + let opts_bits = arg(0).to_bits() as i64; + f64::from_bits( + JSValue::pointer(crate::os::js_os_user_info_options(opts_bits) as *const u8).bits(), + ) + } + ("os", "getPriority") => crate::os::js_os_get_priority(arg(0)), + ("os", "setPriority") => crate::os::js_os_set_priority(arg(0), arg(1)), + + // ── path module (args are NaN-boxed strings → extract raw StringHeader ptr) ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_path(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + let require_path_str_ptr = |n: usize| -> *const crate::StringHeader { + if n < args_len { + let v = arg(n); + let ptr = crate::string::js_string_materialize_to_heap(v); + if !ptr.is_null() { + return ptr; + } + } + crate::path::throw_invalid_path_arg_type() + }; + let optional_path_str_ptr = |n: usize| -> *const crate::StringHeader { + if n >= args_len { + return std::ptr::null(); + } + let v = arg(n); + let jsv = JSValue::from_bits(v.to_bits()); + if jsv.is_undefined() { + return std::ptr::null(); + } + let ptr = crate::string::js_string_materialize_to_heap(v); + if !ptr.is_null() { + return ptr; + } + crate::path::throw_invalid_path_arg_type() + }; + let path_join_value = |win32: bool| -> f64 { + if args_len == 0 { + let result = if win32 { + crate::path::js_path_win32_join_unchecked(std::ptr::null(), std::ptr::null()) + } else { + crate::path::js_path_join_unchecked(std::ptr::null(), std::ptr::null()) + }; + return str_to_f64(result); + } + let first = require_path_str_ptr(0); + let mut result = if win32 { + crate::path::js_path_win32_join_unchecked(first, std::ptr::null()) + } else { + crate::path::js_path_join_unchecked(first, std::ptr::null()) + }; + for i in 1..args_len { + let segment = require_path_str_ptr(i); + result = if win32 { + crate::path::js_path_win32_join_unchecked(result, segment) + } else { + crate::path::js_path_join_unchecked(result, segment) + }; + } + str_to_f64(result) + }; + let path_resolve_value = |win32: bool| -> f64 { + let mut result = if args_len == 0 { + if win32 { + crate::path::js_path_win32_join_unchecked(std::ptr::null(), std::ptr::null()) + } else { + crate::path::js_path_join_unchecked(std::ptr::null(), std::ptr::null()) + } + } else { + require_path_str_ptr(0) as *mut crate::StringHeader + }; + for i in 1..args_len { + let segment = require_path_str_ptr(i); + result = if win32 { + crate::path::js_path_win32_resolve_join(result, segment) + } else { + crate::path::js_path_resolve_join(result, segment) + }; + } + if win32 { + str_to_f64(crate::path::js_path_win32_resolve(result)) + } else { + str_to_f64(crate::path::js_path_resolve(result)) + } + }; + let path_basename_value = |win32: bool| -> f64 { + let path = require_path_str_ptr(0); + let ext = optional_path_str_ptr(1); + if win32 { + if ext.is_null() { + str_to_f64(crate::path::js_path_win32_basename(path)) + } else { + str_to_f64(crate::path::js_path_win32_basename_ext(path, ext)) + } + } else if ext.is_null() { + str_to_f64(crate::path::js_path_basename(path)) + } else { + str_to_f64(crate::path::js_path_basename_ext(path, ext)) + } + }; + match (module_name, method_name) { + ("path", "dirname") => str_to_f64(crate::path::js_path_dirname(require_path_str_ptr(0))), + ("path", "basename") => path_basename_value(false), + ("path", "extname") => str_to_f64(crate::path::js_path_extname(require_path_str_ptr(0))), + ("path", "normalize") => { + str_to_f64(crate::path::js_path_normalize(require_path_str_ptr(0))) + } + ("path", "resolve") => path_resolve_value(false), + ("path", "join") => path_join_value(false), ("path", "relative") => str_to_f64(crate::path::js_path_relative( require_path_str_ptr(0), require_path_str_ptr(1), @@ -1220,7 +1336,6 @@ pub(crate) unsafe fn dispatch_native_module_method( ptr_to_f64(crate::path::js_path_win32_parse(require_path_str_ptr(0)) as *const u8) } ("path.win32", "format") => str_to_f64(crate::path::js_path_win32_format(arg(0))), - ("path.posix", "dirname") => { str_to_f64(crate::path::js_path_dirname(require_path_str_ptr(0))) } @@ -1252,253 +1367,582 @@ pub(crate) unsafe fn dispatch_native_module_method( ("path.posix", "format") => str_to_f64(crate::path::js_path_format(arg(0))), // ── util module ── - ("util", "format") => crate::builtins::js_util_format(pack_args()), - ("util", "formatWithOptions") => { - let effective = args_len.saturating_sub(1); - let mut arr = crate::array::js_array_alloc(effective as u32); - for i in 1..args_len { - arr = crate::array::js_array_push_f64(arr, arg(i)); - } - crate::builtins::js_util_format_with_options(arg(0), arr) - } - ("util", "inspect") => crate::builtins::js_util_inspect(arg(0), arg(1)), - ("util", "convertProcessSignalToExitCode") => { - crate::os::js_util_convert_process_signal_to_exit_code(arg(0)) - } - // #2514: libuv-style errno → name/message/map helpers. - ("util", "getSystemErrorName") => crate::util_syserr::js_util_get_system_error_name(arg(0)), - ("util", "getSystemErrorMessage") => { - crate::util_syserr::js_util_get_system_error_message(arg(0)) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_perf(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("perf_hooks", "now") => crate::date::js_performance_now(), + ("perf_hooks", "mark") => crate::perf_hooks::js_perf_mark(arg(0), arg(1)), + ("perf_hooks", "measure") => crate::perf_hooks::js_perf_measure(arg(0), arg(1), arg(2)), + ("perf_hooks", "getEntries") => crate::perf_hooks::js_perf_get_entries(), + ("perf_hooks", "getEntriesByType") => { + crate::perf_hooks::js_perf_get_entries_by_type(arg(0)) } - ("util", "getSystemErrorMap") => crate::util_syserr::js_util_get_system_error_map(), - ("util", "aborted") => crate::util_abort::js_util_aborted(arg(0), arg(1)), - ("util", "transferableAbortController") => { - crate::util_abort::js_util_transferable_abort_controller() + ("perf_hooks", "getEntriesByName") => { + crate::perf_hooks::js_perf_get_entries_by_name(arg(0), arg(1)) } - ("util", "transferableAbortSignal") => { - crate::util_abort::js_util_transferable_abort_signal(arg(0)) + ("perf_hooks", "clearMarks") => crate::perf_hooks::js_perf_clear_marks(arg(0)), + ("perf_hooks", "clearMeasures") => crate::perf_hooks::js_perf_clear_measures(arg(0)), + ("perf_hooks", "eventLoopUtilization") => { + crate::perf_hooks::js_perf_event_loop_utilization(arg(0), arg(1)) } - ("util", "getCallSites") => crate::util_call_sites::js_util_get_call_sites(arg(0), arg(1)), - // #2514: util.parseEnv(content) → object. - ("util", "parseEnv") => crate::util_parse_env::js_util_parse_env(arg(0)), - ("util", "debuglog") | ("util", "debug") => { - crate::util_debuglog::js_util_debuglog(arg(0), arg(1)) + ("perf_hooks", "toJSON") => crate::perf_hooks::js_perf_to_json(), + ("perf_hooks", "clearResourceTimings") => { + crate::perf_hooks::js_perf_clear_resource_timings() } - ("util", "inherits") => crate::util_inherits::js_util_inherits(arg(0), arg(1)), - ("util", "_extend") => crate::util_mime::js_util_extend(arg(0), arg(1)), - ("util", "_errnoException") => { - crate::util_mime::js_util_errno_exception(arg(0), arg(1), arg(2)) + ("perf_hooks", "setResourceTimingBufferSize") => { + crate::perf_hooks::js_perf_set_resource_timing_buffer_size(arg(0)) } - ("util", "_exceptionWithHostPort") => crate::util_mime::js_util_exception_with_host_port( + ("perf_hooks", "markResourceTiming") => crate::perf_hooks::js_perf_mark_resource_timing( arg(0), arg(1), arg(2), arg(3), arg(4), + arg(5), + arg(6), + arg(7), ), - ("util", "MIMEType") => crate::util_mime::js_util_mime_type_new(arg(0)), - ("util", "MIMEParams") => crate::util_mime::js_util_mime_params_new(), - ("util", "diff") => crate::util_diff::js_util_diff(arg(0), arg(1)), - ("util", "isArray") => crate::array::js_array_is_array(arg(0)), - ("util", "isDeepStrictEqual") => { - crate::builtins::js_util_is_deep_strict_equal(arg(0), arg(1)) + ("perf_hooks", "timerify") => crate::perf_hooks::js_perf_timerify(arg(0), arg(1)), + + // ── PerformanceObserver instance (perf_observer) ── + // The registry index lives in field[1] of the namespace object; the + // runtime fns re-derive it from the object value. + ("perf_observer", "observe") => { + let obs_val = crate::value::js_nanbox_pointer(obj as i64); + crate::perf_hooks::js_perf_observer_observe(obs_val, arg(0)) } - ("util", "stripVTControlCharacters") => { - crate::builtins::js_util_strip_vt_control_characters(arg(0)) + ("perf_observer", "disconnect") => { + let obs_val = crate::value::js_nanbox_pointer(obj as i64); + crate::perf_hooks::js_perf_observer_disconnect(obs_val) + } + ("perf_observer", "takeRecords") => { + let obs_val = crate::value::js_nanbox_pointer(obj as i64); + crate::perf_hooks::js_perf_observer_take_records(obs_val) } - ("util", "styleText") => crate::util_style_text::js_util_style_text(arg(0), arg(1), arg(2)), - // #2514: util.toUSVString(value) → string with lone surrogates → U+FFFD. - ("util", "toUSVString") => crate::util_usv::js_util_to_usv_string(arg(0)), - ("util", "setTraceSigInt") => crate::util_settracesigint::js_util_set_trace_sig_int(arg(0)), - ("util", "promisify") => crate::util_promisify::js_util_promisify(arg(0)), - ("util", "callbackify") => crate::util_promisify::js_util_callbackify(arg(0)), - ("util", "deprecate") => crate::util_promisify::js_util_deprecate(arg(0), arg(1), arg(2)), - ("util", "parseArgs") => crate::util_parse_args::js_util_parse_args(arg(0)), - ("util", "isPromise") => { - let v = JSValue::from_bits(arg(0).to_bits()); - bool_tag( - v.is_pointer() - && crate::promise::js_is_promise( - v.as_pointer::() as *mut crate::promise::Promise - ) != 0, - ) + // ── PerformanceObserverEntryList (the callback `list` arg) ── + ("perf_observer_list", "getEntries") => crate::perf_hooks::current_list_get_entries(), + ("perf_observer_list", "getEntriesByType") => { + crate::perf_hooks::current_list_get_by_type(arg(0)) + } + ("perf_observer_list", "getEntriesByName") => { + crate::perf_hooks::current_list_get_by_name(arg(0)) + } + + // ── Histogram instance methods (#1336) ── + // Every method is a no-op on the stub — `enable`/`disable`/`reset` + // don't sample anything, `record`/`recordDelta`/`add` discard input. + // `percentile(p)` returns 0 (no samples => no rank). + ("perf_histogram", "enable") + | ("perf_histogram", "disable") + | ("perf_histogram", "reset") + | ("perf_histogram", "record") + | ("perf_histogram", "recordDelta") + | ("perf_histogram", "add") => crate::perf_hooks::js_perf_histogram_noop(), + ("perf_histogram", "percentile") | ("perf_histogram", "percentileBigInt") => { + crate::perf_hooks::js_perf_histogram_percentile(arg(0)) + } + + // ── timers module ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_process(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("process", "on") => crate::os::js_process_on(arg_bits(0), arg_bits(1)), + ("process", "addListener") => crate::os::js_process_add_listener(arg_bits(0), arg_bits(1)), + ("process", "once") => crate::os::js_process_once(arg_bits(0), arg_bits(1)), + ("process", "prependListener") => { + crate::os::js_process_prepend_listener(arg_bits(0), arg_bits(1)) + } + ("process", "prependOnceListener") => { + crate::os::js_process_prepend_once_listener(arg_bits(0), arg_bits(1)) + } + ("process", "emit") => crate::os::js_process_emit(arg_bits(0), pack_args_from(1)), + ("process", "removeListener") => { + crate::os::js_process_remove_listener(arg_bits(0), arg_bits(1)) + } + ("process", "off") => crate::os::js_process_off(arg_bits(0), arg_bits(1)), + ("process", "removeAllListeners") => { + crate::os::js_process_remove_all_listeners(arg_bits(0)) + } + ("process", "listenerCount") => { + crate::os::js_process_listener_count(arg_bits(0), arg_bits(1)) + } + ("process", "listeners") => { + ptr_to_f64(crate::os::js_process_listeners(arg_bits(0)) as *const u8) + } + ("process", "rawListeners") => { + ptr_to_f64(crate::os::js_process_raw_listeners(arg_bits(0)) as *const u8) + } + ("process", "eventNames") => ptr_to_f64(crate::os::js_process_event_names() as *const u8), + ("process", "setMaxListeners") => crate::os::js_process_set_max_listeners(arg(0)), + ("process", "getMaxListeners") => crate::os::js_process_get_max_listeners(), + ("process", "send") => { + crate::process::process_ipc_send_call(arg(0), arg(1), arg(2), arg(3)) + } + ("process", "disconnect") => crate::process::process_ipc_disconnect_call(), + ("process", "emitWarning") => { + crate::process::js_process_emit_warning(arg(0), arg(1), arg(2)); + f64::from_bits(crate::value::TAG_UNDEFINED) + } + ("process", "getBuiltinModule") => crate::process::js_process_get_builtin_module(arg(0)), + ("process", "execve") => crate::process::js_process_execve(arg(0), arg(1), arg(2)), + ("process", "cwd") => str_to_f64(crate::os::js_process_cwd()), + ("process", "uptime") => crate::os::js_process_uptime(), + ("process", "memoryUsage") => crate::process::js_process_memory_usage(), + ("process", "threadCpuUsage") => crate::process::js_process_thread_cpu_usage(arg(0)), + ("process", "availableMemory") => crate::process::js_process_available_memory(), + ("process", "constrainedMemory") => crate::process::js_process_constrained_memory(), + ("process", "resourceUsage") => crate::process::js_process_resource_usage(), + ("process", "getActiveResourcesInfo") => crate::process::js_process_active_resources_info(), + ("process", "binding") => crate::process::js_process_binding(arg(0)), + ("process", "_linkedBinding") => crate::process::js_process_linked_binding(arg(0)), + ("process", "dlopen") => crate::process::js_process_dlopen(), + ("process", "_rawDebug") => crate::process::js_process_raw_debug(), + ("process", "_debugProcess") => crate::process::js_process_debug_process(), + ("process", "_debugEnd") => crate::process::js_process_debug_end(), + ("process", "_startProfilerIdleNotifier") => { + crate::process::js_process_start_profiler_idle_notifier() } - ("util", "isArrayBuffer") => bool_tag(crate::buffer::is_array_buffer(ptr_addr(arg(0)))), - ("util", "isSharedArrayBuffer") => { - bool_tag(crate::buffer::is_shared_array_buffer(ptr_addr(arg(0)))) + ("process", "_stopProfilerIdleNotifier") => { + crate::process::js_process_stop_profiler_idle_notifier() } - ("util", "isAnyArrayBuffer") => { - bool_tag(crate::buffer::is_any_array_buffer(ptr_addr(arg(0)))) + ("process", "reallyExit") => crate::process::js_process_really_exit(), + ("process", "_fatalException") => { + crate::process::js_process_fatal_exception(arg(0), arg(1)) } - ("bigint", "asIntN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), true), - ("bigint", "asUintN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), false), - ("util", "isArrayBufferView") => crate::object::js_util_types_is_array_buffer_view(arg(0)), - ("util", "isTypedArray") => bool_tag(typed_kind(arg(0)).is_some()), - ("util", "isUint8Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT8)) + ("process", "_tickCallback") => crate::process::js_process_tick_callback(), + ("process", "_getActiveHandles") => crate::process::js_process_get_active_handles(), + ("process", "_getActiveRequests") => crate::process::js_process_get_active_requests(), + ("process", "openStdin") => crate::process::js_process_open_stdin(), + ("process", "_kill") => crate::process::js_process_internal_kill(), + ("process", "getuid") => crate::process::js_process_getuid(), + ("process", "geteuid") => crate::process::js_process_geteuid(), + ("process", "getgid") => crate::process::js_process_getgid(), + ("process", "getegid") => crate::process::js_process_getegid(), + ("process", "sourceMapsEnabled") => crate::process::js_process_source_maps_enabled(), + ("process", "setSourceMapsEnabled") => { + crate::process::js_process_set_source_maps_enabled(arg(0)) } - ("util", "isInt8Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT8)) + ("process", "ref") => crate::process::js_process_ref(arg(0)), + ("process", "unref") => crate::process::js_process_unref(arg(0)), + ("process", "hasUncaughtExceptionCaptureCallback") => { + crate::process::js_process_has_uncaught_exception_capture_callback() } - ("util", "isInt16Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT16)) + ("process", "setUncaughtExceptionCaptureCallback") => { + crate::process::js_process_set_uncaught_exception_capture_callback(arg(0)) } - ("util", "isUint16Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT16)) + ("process", "addUncaughtExceptionCaptureCallback") => { + crate::process::js_process_add_uncaught_exception_capture_callback(arg(0)) } - ("util", "isInt32Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT32)) + ("process", "nextTick") => { + // Validate the callback and forward trailing args (#3046). + unsafe { crate::os::js_process_next_tick(arg_bits(0), pack_args_from(1)) }; + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util", "isUint32Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT32)) + ("process", "chdir") => { + // #3043 — route dynamic/method-value chdir calls through the + // full-value validator (matching the static codegen path) so a + // non-string argument throws TypeError [ERR_INVALID_ARG_TYPE] + // instead of silently no-oping on a null string pointer. + unsafe { + crate::process::js_process_chdir_jsv(arg(0)); + } + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util", "isFloat32Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_FLOAT32)) + ("process", "loadEnvFile") => { + crate::process::js_process_load_env_file(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util", "isFloat64Array") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_FLOAT64)) + // #3712: node:http module-level header validation helpers. These mirror + // Node's `validateHeaderName` / `validateHeaderValue` (lib/_http_common + // + lib/_http_outgoing): on invalid input they throw the matching error + // codes, otherwise they return undefined. + ("process", "getgroups") => crate::process::js_process_getgroups(), + ("process", "setuid") => { + crate::process::js_process_setuid(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util", "isUint8ClampedArray") => { - bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT8_CLAMPED)) + ("process", "seteuid") => { + crate::process::js_process_seteuid(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util", "isMap") => bool_tag(crate::map::is_registered_map(ptr_addr(arg(0)))), - ("util", "isSet") => bool_tag(crate::set::is_registered_set(ptr_addr(arg(0)))), - - // ── util.types namespace ── - ("util.types", "isArgumentsObject") => { - crate::object::js_util_types_is_arguments_object(arg(0)) + ("process", "setgid") => { + crate::process::js_process_setgid(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isPromise") => crate::object::js_util_types_is_promise(arg(0)), - ("util.types", "isBigIntObject") => crate::object::js_util_types_is_big_int_object(arg(0)), - ("util.types", "isArrayBuffer") => crate::object::js_util_types_is_array_buffer(arg(0)), - ("util.types", "isSharedArrayBuffer") => { - crate::object::js_util_types_is_shared_array_buffer(arg(0)) + ("process", "setegid") => { + crate::process::js_process_setegid(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isAnyArrayBuffer") => { - crate::object::js_util_types_is_any_array_buffer(arg(0)) + ("process", "setgroups") => { + crate::process::js_process_setgroups(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isArrayBufferView") => { - crate::object::js_util_types_is_array_buffer_view(arg(0)) + ("process", "initgroups") => { + crate::process::js_process_initgroups(arg(0), arg(1)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isDataView") => crate::object::js_util_types_is_data_view(arg(0)), - ("util.types", "isTypedArray") => crate::object::js_util_types_is_typed_array(arg(0)), - ("util.types", "isUint8Array") => crate::object::js_util_types_is_uint8_array(arg(0)), - ("util.types", "isInt8Array") => crate::object::js_util_types_is_int8_array(arg(0)), - ("util.types", "isInt16Array") => crate::object::js_util_types_is_int16_array(arg(0)), - ("util.types", "isUint16Array") => crate::object::js_util_types_is_uint16_array(arg(0)), - ("util.types", "isInt32Array") => crate::object::js_util_types_is_int32_array(arg(0)), - ("util.types", "isUint32Array") => crate::object::js_util_types_is_uint32_array(arg(0)), - ("util.types", "isFloat16Array") => crate::object::js_util_types_is_float16_array(arg(0)), - ("util.types", "isFloat32Array") => crate::object::js_util_types_is_float32_array(arg(0)), - ("util.types", "isFloat64Array") => crate::object::js_util_types_is_float64_array(arg(0)), - ("util.types", "isUint8ClampedArray") => { - crate::object::js_util_types_is_uint8_clamped_array(arg(0)) + ("process", "kill") => crate::os::js_process_kill(arg(0), arg(1)), + ("process", "exit") => { + crate::process::js_process_exit(arg(0)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isBigInt64Array") => { - crate::object::js_util_types_is_big_int64_array(arg(0)) + ("process", "abort") => { + crate::process::js_process_abort(); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isBigUint64Array") => { - crate::object::js_util_types_is_big_uint64_array(arg(0)) + ("process", "umask") => { + let mask = arg(0); + let mask_value = JSValue::from_bits(mask.to_bits()); + if mask_value.is_undefined() { + crate::process::js_process_umask() + } else { + crate::process::js_process_umask_set(mask) + } } - ("util.types", "isMap") => crate::object::js_util_types_is_map(arg(0)), - ("util.types", "isMapIterator") => crate::object::js_util_types_is_map_iterator(arg(0)), - ("util.types", "isProxy") => crate::object::js_util_types_is_proxy(arg(0)), - ("util.types", "isExternal") => crate::object::js_util_types_is_external(arg(0)), - ("util.types", "isModuleNamespaceObject") => { - crate::object::js_util_types_is_module_namespace_object(arg(0)) + ("process", "emitWarning") => { + crate::process::js_process_emit_warning(arg(0), arg(1), arg(2)); + f64::from_bits(crate::value::TAG_UNDEFINED) } - ("util.types", "isSet") => crate::object::js_util_types_is_set(arg(0)), - ("util.types", "isSetIterator") => crate::object::js_util_types_is_set_iterator(arg(0)), - ("util.types", "isWeakMap") => crate::object::js_util_types_is_weak_map(arg(0)), - ("util.types", "isWeakSet") => crate::object::js_util_types_is_weak_set(arg(0)), - ("util.types", "isDate") => crate::object::js_util_types_is_date(arg(0)), - ("util.types", "isRegExp") => crate::object::js_util_types_is_reg_exp(arg(0)), - ("util.types", "isAsyncFunction") => crate::object::js_util_types_is_async_function(arg(0)), - ("util.types", "isGeneratorFunction") => { - crate::object::js_util_types_is_generator_function(arg(0)) + ("process", "hrtime") => crate::os::js_process_hrtime(arg(0)), + ("process", "cpuUsage") => crate::process::js_process_cpu_usage(arg(0)), + // ── crypto module ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_punycode(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("punycode", "decode") => crate::punycode::js_punycode_decode(arg(0)), + ("punycode", "encode") => crate::punycode::js_punycode_encode(arg(0)), + ("punycode", "toASCII") => crate::punycode::js_punycode_to_ascii(arg(0)), + ("punycode", "toUnicode") => crate::punycode::js_punycode_to_unicode(arg(0)), + // ── punycode.ucs2 sub-namespace (#2607) ── + ("punycode.ucs2", "decode") => crate::punycode::js_punycode_ucs2_decode(arg(0)), + ("punycode.ucs2", "encode") => crate::punycode::js_punycode_ucs2_encode(arg(0)), + + // ── dgram namespace (`node:dgram` / `dgram`) ── + // Gated behind `mod-dgram`: `crate::dgram` is only compiled when the + // program imports `dgram` (the compiler enables the feature on + // `module: "dgram"` usage), so this arm — and the `js_dgram_*` externs + // it calls — are absent otherwise. Unreachable when off (a dgram + // namespace can't exist without the import that enables the feature). + #[cfg(feature = "mod-dgram")] + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_querystring(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ( + "querystring", + "unescapeBuffer" | "unescape" | "escape" | "stringify" | "encode" | "parse" | "decode", + ) => { + let ptr = crate::value::JS_NATIVE_QUERYSTRING_DISPATCH + .load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) + } } - ("util.types", "isGeneratorObject") => { - crate::object::js_util_types_is_generator_object(arg(0)) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_readline(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("readline", "clearLine") => { + crate::readline_helpers::js_readline_clear_line_args(pack_args()) } - ("util.types", "isNativeError") => crate::object::js_util_types_is_native_error(arg(0)), - ("util.types", "isKeyObject") => crate::object::js_util_types_is_key_object(arg(0)), - ("util.types", "isCryptoKey") => crate::object::js_util_types_is_crypto_key(arg(0)), - ("util.types", "isNumberObject") => crate::object::js_util_types_is_number_object(arg(0)), - ("util.types", "isStringObject") => crate::object::js_util_types_is_string_object(arg(0)), - ("util.types", "isBooleanObject") => crate::object::js_util_types_is_boolean_object(arg(0)), - ("util.types", "isSymbolObject") => crate::object::js_util_types_is_symbol_object(arg(0)), - ("util.types", "isBoxedPrimitive") => { - crate::object::js_util_types_is_boxed_primitive(arg(0)) + ("readline", "clearScreenDown") => { + crate::readline_helpers::js_readline_clear_screen_down_args(pack_args()) + } + ("readline", "cursorTo") => { + crate::readline_helpers::js_readline_cursor_to_args(pack_args()) + } + ("readline", "moveCursor") => { + crate::readline_helpers::js_readline_move_cursor_args(pack_args()) } + ("readline", "emitKeypressEvents") => { + crate::readline_helpers::js_readline_emit_keypress_events_args(pack_args()) + } + + // ── node:dns / node:dns/promises configuration ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_repl(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("repl", "start") => crate::node_repl::js_repl_start(arg(0)), + ("repl", "REPLServer") => crate::node_repl::js_repl_repl_server_new(arg(0)), + ("repl", "Recoverable") => crate::node_repl::js_repl_recoverable_new(arg(0)), + + // #3680: `v8.Serializer` / `v8.DefaultSerializer` instance methods. + // The registry id lives in field[1] of the namespace object; the + // runtime re-derives it from the receiver value. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_sea(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("sea", "isSea") => crate::node_sea::js_sea_is_sea(), + ("sea", "getAsset") => crate::node_sea::js_sea_get_asset(arg(0), arg(1)), + ("sea", "getAssetAsBlob") => crate::node_sea::js_sea_get_asset_as_blob(arg(0), arg(1)), + ("sea", "getRawAsset") => crate::node_sea::js_sea_get_raw_asset(arg(0)), + ("sea", "getAssetKeys") => crate::node_sea::js_sea_get_asset_keys(), + // ── Buffer constructor static API ── + // `class MyBuffer extends Buffer {}; MyBuffer.from(...)` reaches this + // path through js_class_static_method_call's native-superclass + // fallback. Return plain Buffer instances, matching Node's internal + // FastBuffer behavior rather than species/subclass construction. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // ── node:util/types direct module ── - ("util/types", "isArgumentsObject") => { - crate::object::js_util_types_is_arguments_object(arg(0)) - } - ("util/types", "isPromise") => crate::object::js_util_types_is_promise(arg(0)), - ("util/types", "isBigIntObject") => crate::object::js_util_types_is_big_int_object(arg(0)), - ("util/types", "isArrayBuffer") => crate::object::js_util_types_is_array_buffer(arg(0)), - ("util/types", "isSharedArrayBuffer") => { - crate::object::js_util_types_is_shared_array_buffer(arg(0)) - } - ("util/types", "isAnyArrayBuffer") => { - crate::object::js_util_types_is_any_array_buffer(arg(0)) +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_sqlite(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("sqlite", _) => { + let ptr = + crate::value::JS_NATIVE_SQLITE_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: crate::value::JsNativeSqliteDispatchFn = std::mem::transmute(ptr); + dispatch( + method_name.as_ptr(), + method_name.len(), + args_ptr, + args_len, + 0, + ) + } } - ("util/types", "isArrayBufferView") => { - crate::object::js_util_types_is_array_buffer_view(arg(0)) + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_stream(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("stream", _) => dispatch_stream_native_module_method(method_name, args_ptr, args_len) + .unwrap_or_else(|| f64::from_bits(JSValue::undefined().bits())), + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_timers(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("timers", "setTimeout") if args_len >= 2 => { + let cb = arg(0); + let delay = arg(1); + let cb_handle = { + let bits = cb.to_bits(); + if (bits >> 48) >= 0x7FF8 { + (bits & 0x0000_FFFF_FFFF_FFFF) as i64 + } else { + bits as i64 + } + }; + if args_len > 2 { + let extra_ptr = unsafe { args_ptr.add(2) }; + return f64::from_bits( + JSValue::pointer(crate::timer::js_set_timeout_callback_args( + cb_handle, + delay, + extra_ptr, + (args_len - 2) as i32, + ) as *mut u8) + .bits(), + ); + } + return f64::from_bits(JSValue::pointer( + crate::timer::js_set_timeout_callback(cb_handle, delay) as *mut u8, + ).bits()); } - ("util/types", "isDataView") => crate::object::js_util_types_is_data_view(arg(0)), - ("util/types", "isTypedArray") => crate::object::js_util_types_is_typed_array(arg(0)), - ("util/types", "isUint8Array") => crate::object::js_util_types_is_uint8_array(arg(0)), - ("util/types", "isInt8Array") => crate::object::js_util_types_is_int8_array(arg(0)), - ("util/types", "isInt16Array") => crate::object::js_util_types_is_int16_array(arg(0)), - ("util/types", "isUint16Array") => crate::object::js_util_types_is_uint16_array(arg(0)), - ("util/types", "isInt32Array") => crate::object::js_util_types_is_int32_array(arg(0)), - ("util/types", "isUint32Array") => crate::object::js_util_types_is_uint32_array(arg(0)), - ("util/types", "isFloat16Array") => crate::object::js_util_types_is_float16_array(arg(0)), - ("util/types", "isFloat32Array") => crate::object::js_util_types_is_float32_array(arg(0)), - ("util/types", "isFloat64Array") => crate::object::js_util_types_is_float64_array(arg(0)), - ("util/types", "isUint8ClampedArray") => { - crate::object::js_util_types_is_uint8_clamped_array(arg(0)) + ("timers", "setImmediate") if args_len >= 1 => { + let cb = arg(0); + let cb_handle = { + let bits = cb.to_bits(); + if (bits >> 48) >= 0x7FF8 { + (bits & 0x0000_FFFF_FFFF_FFFF) as i64 + } else { + bits as i64 + } + }; + if args_len > 1 { + let extra_ptr = unsafe { args_ptr.add(1) }; + return f64::from_bits( + JSValue::pointer(crate::timer::js_set_immediate_callback_args( + cb_handle, + extra_ptr, + (args_len - 1) as i32, + ) as *mut u8) + .bits(), + ); + } + return f64::from_bits( + JSValue::pointer(crate::timer::js_set_immediate_callback(cb_handle) as *mut u8) + .bits(), + ); } - ("util/types", "isBigInt64Array") => { - crate::object::js_util_types_is_big_int64_array(arg(0)) + ("timers", "setInterval") if args_len >= 2 => { + let cb = arg(0); + let delay = arg(1); + let bits = cb.to_bits(); + let cb_handle = if (bits >> 48) >= 0x7FF8 { + (bits & 0x0000_FFFF_FFFF_FFFF) as i64 + } else { + bits as i64 + }; + if args_len > 2 { + let extra_ptr = unsafe { args_ptr.add(2) }; + return f64::from_bits( + JSValue::pointer(crate::timer::js_set_interval_callback_args( + cb_handle, + delay, + extra_ptr, + (args_len - 2) as i32, + ) as *mut u8) + .bits(), + ); + } + return f64::from_bits( + JSValue::pointer(crate::timer::setInterval(cb_handle, delay) as *mut u8).bits(), + ); } - ("util/types", "isBigUint64Array") => { - crate::object::js_util_types_is_big_uint64_array(arg(0)) + ("timers", "clearTimeout") if args_len >= 1 => { + crate::timer::js_clear_timeout_value(arg(0)); + return f64::from_bits(JSValue::undefined().bits()); } - ("util/types", "isMap") => crate::object::js_util_types_is_map(arg(0)), - ("util/types", "isMapIterator") => crate::object::js_util_types_is_map_iterator(arg(0)), - ("util/types", "isProxy") => crate::object::js_util_types_is_proxy(arg(0)), - ("util/types", "isExternal") => crate::object::js_util_types_is_external(arg(0)), - ("util/types", "isModuleNamespaceObject") => { - crate::object::js_util_types_is_module_namespace_object(arg(0)) + ("timers", "clearImmediate") if args_len >= 1 => { + crate::timer::js_clear_immediate_value(arg(0)); + return f64::from_bits(JSValue::undefined().bits()); } - ("util/types", "isSet") => crate::object::js_util_types_is_set(arg(0)), - ("util/types", "isSetIterator") => crate::object::js_util_types_is_set_iterator(arg(0)), - ("util/types", "isWeakMap") => crate::object::js_util_types_is_weak_map(arg(0)), - ("util/types", "isWeakSet") => crate::object::js_util_types_is_weak_set(arg(0)), - ("util/types", "isDate") => crate::object::js_util_types_is_date(arg(0)), - ("util/types", "isRegExp") => crate::object::js_util_types_is_reg_exp(arg(0)), - ("util/types", "isAsyncFunction") => crate::object::js_util_types_is_async_function(arg(0)), - ("util/types", "isGeneratorFunction") => { - crate::object::js_util_types_is_generator_function(arg(0)) + ("timers", "clearInterval") if args_len >= 1 => { + crate::timer::js_clear_interval_value(arg(0)); + return f64::from_bits(JSValue::undefined().bits()); } - ("util/types", "isGeneratorObject") => { - crate::object::js_util_types_is_generator_object(arg(0)) + // ── assert module ── + // Root-callable `assert(x, msg)` / `assert.strict(x, msg)` — + // HIR lowers these to method "default". + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_tls(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("tls", "getCiphers") => crate::tls::js_tls_get_ciphers(), + ("tls", "getCACertificates") => crate::tls::js_tls_get_ca_certificates(arg(0)), + ("tls", "setDefaultCACertificates") => { + crate::tls::js_tls_set_default_ca_certificates(arg(0)) } - ("util/types", "isNativeError") => crate::object::js_util_types_is_native_error(arg(0)), - ("util/types", "isKeyObject") => crate::object::js_util_types_is_key_object(arg(0)), - ("util/types", "isCryptoKey") => crate::object::js_util_types_is_crypto_key(arg(0)), - ("util/types", "isNumberObject") => crate::object::js_util_types_is_number_object(arg(0)), - ("util/types", "isStringObject") => crate::object::js_util_types_is_string_object(arg(0)), - ("util/types", "isBooleanObject") => crate::object::js_util_types_is_boolean_object(arg(0)), - ("util/types", "isSymbolObject") => crate::object::js_util_types_is_symbol_object(arg(0)), - ("util/types", "isBoxedPrimitive") => { - crate::object::js_util_types_is_boxed_primitive(arg(0)) + ("tls", "checkServerIdentity") => crate::tls::js_tls_check_server_identity(arg(0), arg(1)), + ("tls", "createSecureContext") => crate::tls::js_tls_create_secure_context(arg(0)), + ("tls", "SecureContext") => crate::tls::js_tls_secure_context_new(arg(0)), + + // ── wasi module ── + ("tls", _) => { + let ptr = + crate::value::JS_NATIVE_TLS_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + if ptr.is_null() { + f64::from_bits(JSValue::undefined().bits()) + } else { + let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = + std::mem::transmute(ptr); + dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) + } } - // ── url module (module-level functions return NaN-boxed JS values) ── + + // #2533: captured / aliased server factories + // (`const createServer = options.createServer || createServerHTTP; + // createServer(opts, handler)` — `@hono/node-server`'s `serve()`). The + // method-call form (`http.createServer(...)`) already lowers through a + // dedicated codegen NATIVE_MODULE_TABLE path; the value-read form yields + // a bound-method closure (see `is_native_module_callable_export`) that + // lands here when invoked. The impls live in perry-ext-http-server, so + // route through the dispatcher perry-stdlib registers at startup under + // `external-http-server-pump` (enabled whenever http/https/http2 is + // imported). Null when the http ext crate isn't linked → undefined. The + // dispatcher takes the module name so one callback serves all three. + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_tty(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("tty", "isatty") => crate::tty::js_tty_isatty(arg(0)), + ("tty", "ReadStream") => crate::tty::js_tty_read_stream_new(arg(0)), + ("tty", "WriteStream") => crate::tty::js_tty_write_stream_new(arg(0)), + + // ── tls module helpers ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_url(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { ("url", "fileURLToPath") => crate::url::js_url_file_url_to_path(arg(0), arg(1)), ("url", "fileURLToPathBuffer") => { crate::url::js_url_file_url_to_path_buffer(arg(0), arg(1)) @@ -1515,315 +1959,270 @@ pub(crate) unsafe fn dispatch_native_module_method( ("url", "resolveObject") => crate::url::js_url_legacy_resolve_object(arg(0), arg(1)), // ── punycode module (deprecated, #2513) ── - ("punycode", "decode") => crate::punycode::js_punycode_decode(arg(0)), - ("punycode", "encode") => crate::punycode::js_punycode_encode(arg(0)), - ("punycode", "toASCII") => crate::punycode::js_punycode_to_ascii(arg(0)), - ("punycode", "toUnicode") => crate::punycode::js_punycode_to_unicode(arg(0)), - // ── punycode.ucs2 sub-namespace (#2607) ── - ("punycode.ucs2", "decode") => crate::punycode::js_punycode_ucs2_decode(arg(0)), - ("punycode.ucs2", "encode") => crate::punycode::js_punycode_ucs2_encode(arg(0)), - - // ── dgram namespace (`node:dgram` / `dgram`) ── - // Gated behind `mod-dgram`: `crate::dgram` is only compiled when the - // program imports `dgram` (the compiler enables the feature on - // `module: "dgram"` usage), so this arm — and the `js_dgram_*` externs - // it calls — are absent otherwise. Unreachable when off (a dgram - // namespace can't exist without the import that enables the feature). - #[cfg(feature = "mod-dgram")] - ("dgram", "createSocket") | ("dgram", "Socket") => { - crate::dgram::js_dgram_create_socket(pack_args()) - } + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // ── console module namespace (`node:console` / `console`) ── - ("console", "Console") => crate::builtins::js_console_new2(arg(0), arg(1)), - ("console", "log") | ("console", "info") | ("console", "debug") | ("console", "dirxml") => { - crate::builtins::js_console_log_spread(pack_args()); - f64::from_bits(JSValue::undefined().bits()) +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_util(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("util", "format") => crate::builtins::js_util_format(pack_args()), + ("util", "formatWithOptions") => { + let effective = args_len.saturating_sub(1); + let mut arr = crate::array::js_array_alloc(effective as u32); + for i in 1..args_len { + arr = crate::array::js_array_push_f64(arr, arg(i)); + } + crate::builtins::js_util_format_with_options(arg(0), arr) } - ("console", "error") => { - crate::builtins::js_console_error_spread(pack_args()); - f64::from_bits(JSValue::undefined().bits()) + ("util", "inspect") => crate::builtins::js_util_inspect(arg(0), arg(1)), + ("util", "convertProcessSignalToExitCode") => { + crate::os::js_util_convert_process_signal_to_exit_code(arg(0)) } - ("console", "warn") => { - crate::builtins::js_console_warn_spread(pack_args()); - f64::from_bits(JSValue::undefined().bits()) + // #2514: libuv-style errno → name/message/map helpers. + ("util", "getSystemErrorName") => crate::util_syserr::js_util_get_system_error_name(arg(0)), + ("util", "getSystemErrorMessage") => { + crate::util_syserr::js_util_get_system_error_message(arg(0)) } - ("console", "assert") => { - crate::builtins::js_console_assert_spread(arg(0), pack_args_from(1) as i64); - f64::from_bits(JSValue::undefined().bits()) + ("util", "getSystemErrorMap") => crate::util_syserr::js_util_get_system_error_map(), + ("util", "aborted") => crate::util_abort::js_util_aborted(arg(0), arg(1)), + ("util", "transferableAbortController") => { + crate::util_abort::js_util_transferable_abort_controller() } - ("console", "dir") => { - crate::builtins::js_console_log_dynamic(arg(0)); - f64::from_bits(JSValue::undefined().bits()) + ("util", "transferableAbortSignal") => { + crate::util_abort::js_util_transferable_abort_signal(arg(0)) } - ("console", "trace") => { - crate::builtins::js_console_trace_spread(pack_args()); - f64::from_bits(JSValue::undefined().bits()) + ("util", "getCallSites") => crate::util_call_sites::js_util_get_call_sites(arg(0), arg(1)), + // #2514: util.parseEnv(content) → object. + ("util", "parseEnv") => crate::util_parse_env::js_util_parse_env(arg(0)), + ("util", "debuglog") | ("util", "debug") => { + crate::util_debuglog::js_util_debuglog(arg(0), arg(1)) } - ("console", "table") => { - if args_len > 1 { - crate::builtins::js_console_table_with_properties(arg(0), arg(1)); - } else { - crate::builtins::js_console_table(arg(0)); - } - f64::from_bits(JSValue::undefined().bits()) + ("util", "inherits") => crate::util_inherits::js_util_inherits(arg(0), arg(1)), + ("util", "_extend") => crate::util_mime::js_util_extend(arg(0), arg(1)), + ("util", "_errnoException") => { + crate::util_mime::js_util_errno_exception(arg(0), arg(1), arg(2)) } - ("console", "clear") => { - crate::builtins::js_console_clear(); - f64::from_bits(JSValue::undefined().bits()) + ("util", "_exceptionWithHostPort") => crate::util_mime::js_util_exception_with_host_port( + arg(0), + arg(1), + arg(2), + arg(3), + arg(4), + ), + ("util", "MIMEType") => crate::util_mime::js_util_mime_type_new(arg(0)), + ("util", "MIMEParams") => crate::util_mime::js_util_mime_params_new(), + ("util", "diff") => crate::util_diff::js_util_diff(arg(0), arg(1)), + ("util", "isArray") => crate::array::js_array_is_array(arg(0)), + ("util", "isDeepStrictEqual") => { + crate::builtins::js_util_is_deep_strict_equal(arg(0), arg(1)) } - ("console", "count") => { - crate::builtins::js_console_count_value(arg(0)); - f64::from_bits(JSValue::undefined().bits()) + ("util", "stripVTControlCharacters") => { + crate::builtins::js_util_strip_vt_control_characters(arg(0)) } - ("console", "countReset") => { - crate::builtins::js_console_count_reset_value(arg(0)); - f64::from_bits(JSValue::undefined().bits()) + ("util", "styleText") => crate::util_style_text::js_util_style_text(arg(0), arg(1), arg(2)), + // #2514: util.toUSVString(value) → string with lone surrogates → U+FFFD. + ("util", "toUSVString") => crate::util_usv::js_util_to_usv_string(arg(0)), + ("util", "setTraceSigInt") => crate::util_settracesigint::js_util_set_trace_sig_int(arg(0)), + ("util", "promisify") => crate::util_promisify::js_util_promisify(arg(0)), + ("util", "callbackify") => crate::util_promisify::js_util_callbackify(arg(0)), + ("util", "deprecate") => crate::util_promisify::js_util_deprecate(arg(0), arg(1), arg(2)), + ("util", "parseArgs") => crate::util_parse_args::js_util_parse_args(arg(0)), + ("util", "isPromise") => { + let v = JSValue::from_bits(arg(0).to_bits()); + bool_tag( + v.is_pointer() + && crate::promise::js_is_promise( + v.as_pointer::() as *mut crate::promise::Promise + ) != 0, + ) } - ("console", "time") => { - crate::builtins::js_console_time_value(arg(0)); - f64::from_bits(JSValue::undefined().bits()) + ("util", "isArrayBuffer") => bool_tag(crate::buffer::is_array_buffer(ptr_addr(arg(0)))), + ("util", "isSharedArrayBuffer") => { + bool_tag(crate::buffer::is_shared_array_buffer(ptr_addr(arg(0)))) } - ("console", "timeEnd") => { - crate::builtins::js_console_time_end_value(arg(0)); - f64::from_bits(JSValue::undefined().bits()) + ("util", "isAnyArrayBuffer") => { + bool_tag(crate::buffer::is_any_array_buffer(ptr_addr(arg(0)))) } - ("console", "timeLog") => { - if args_len > 1 { - crate::builtins::js_console_time_log_spread(arg(0), pack_args_from(1)); - } else { - crate::builtins::js_console_time_log_value(arg(0)); - } - f64::from_bits(JSValue::undefined().bits()) + ("util", "isArrayBufferView") => crate::object::js_util_types_is_array_buffer_view(arg(0)), + ("util", "isTypedArray") => bool_tag(typed_kind(arg(0)).is_some()), + ("util", "isUint8Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT8)) } - ("console", "group") | ("console", "groupCollapsed") => { - if args_len > 0 { - crate::builtins::js_console_log_dynamic(arg(0)); - } - crate::builtins::js_console_group_begin(); - f64::from_bits(JSValue::undefined().bits()) + ("util", "isInt8Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT8)) } - ("console", "groupEnd") => { - crate::builtins::js_console_group_end(); - f64::from_bits(JSValue::undefined().bits()) + ("util", "isInt16Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT16)) } - ("console", "profile") | ("console", "profileEnd") | ("console", "timeStamp") => { - f64::from_bits(JSValue::undefined().bits()) + ("util", "isUint16Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT16)) } - ("console", "context") => crate::builtins::js_console_context(arg(0)), - ("console", "createTask") => crate::builtins::js_console_create_task(arg(0)), - ("stream", _) => dispatch_stream_native_module_method(method_name, args_ptr, args_len) - .unwrap_or_else(|| f64::from_bits(JSValue::undefined().bits())), - ("readline", "clearLine") => { - crate::readline_helpers::js_readline_clear_line_args(pack_args()) + ("util", "isInt32Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_INT32)) } - ("readline", "clearScreenDown") => { - crate::readline_helpers::js_readline_clear_screen_down_args(pack_args()) + ("util", "isUint32Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT32)) } - ("readline", "cursorTo") => { - crate::readline_helpers::js_readline_cursor_to_args(pack_args()) + ("util", "isFloat32Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_FLOAT32)) } - ("readline", "moveCursor") => { - crate::readline_helpers::js_readline_move_cursor_args(pack_args()) + ("util", "isFloat64Array") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_FLOAT64)) } - ("readline", "emitKeypressEvents") => { - crate::readline_helpers::js_readline_emit_keypress_events_args(pack_args()) + ("util", "isUint8ClampedArray") => { + bool_tag(typed_kind(arg(0)) == Some(crate::typedarray::KIND_UINT8_CLAMPED)) } + ("util", "isMap") => bool_tag(crate::map::is_registered_map(ptr_addr(arg(0)))), + ("util", "isSet") => bool_tag(crate::set::is_registered_set(ptr_addr(arg(0)))), - // ── node:dns / node:dns/promises configuration ── - ("dns", "getServers") => crate::dns::dns_get_servers_value(), - ("dns", "setServers") => crate::dns::dns_set_servers_value(arg(0)), - ("dns/promises", "getServers") => crate::dns::dns_promises_get_servers_value(), - ("dns/promises", "setServers") => crate::dns::dns_promises_set_servers_value(arg(0)), - ("dns" | "dns/promises", "getDefaultResultOrder") => { - crate::dns::dns_get_default_result_order_value() + // ── util.types namespace ── + ("util.types", "isArgumentsObject") => { + crate::object::js_util_types_is_arguments_object(arg(0)) } - ("dns" | "dns/promises", "setDefaultResultOrder") => { - crate::dns::dns_set_default_result_order_value(arg(0)) + ("util.types", "isPromise") => crate::object::js_util_types_is_promise(arg(0)), + ("util.types", "isBigIntObject") => crate::object::js_util_types_is_big_int_object(arg(0)), + ("util.types", "isArrayBuffer") => crate::object::js_util_types_is_array_buffer(arg(0)), + ("util.types", "isSharedArrayBuffer") => { + crate::object::js_util_types_is_shared_array_buffer(arg(0)) } - - // #2130: captured-then-called child_process methods (`const spawn = - // require('child_process').spawn; spawn(...)`, Node's canonical test - // idiom). The bound-method closure produced by `cp.spawn` (and the - // other entries allowlisted in `is_native_module_callable_export`) - // funnels back here when invoked. The method-call form - // (`cp.spawn(...)`) is lowered to the same FFIs through dedicated - // codegen arms (`expr/child_proc.rs`); this arm mirrors them for the - // value-call form. `cmd` / `file` / `module` strings come in NaN-boxed - // (SSO-safe via `js_string_materialize_to_heap`); `args` is the array - // pointer (or null); `opts` is the options-object pointer (or 0). - ("child_process", "spawn") => { - let cmd = crate::string::js_string_materialize_to_heap(arg(0)) as i64; - let args_p = optional_ptr_addr(arg(1)) as i64; - let opts_p = optional_ptr_addr(arg(2)) as i64; - crate::child_process::reactor::js_child_process_spawn_streams(cmd, args_p, opts_p) + ("util.types", "isAnyArrayBuffer") => { + crate::object::js_util_types_is_any_array_buffer(arg(0)) } - ("child_process", "spawnSync") => { - let cmd = crate::string::js_string_materialize_to_heap(arg(0)); - let args_p = optional_ptr_addr(arg(1)) as *const crate::array::ArrayHeader; - let opts_p = optional_ptr_addr(arg(2)) as *const ObjectHeader; - let result = crate::child_process::js_child_process_spawn_sync(cmd, args_p, opts_p); - ptr_to_f64(result as *const u8) + ("util.types", "isArrayBufferView") => { + crate::object::js_util_types_is_array_buffer_view(arg(0)) } - ("child_process", "execSync") => { - let cmd = crate::string::js_string_materialize_to_heap(arg(0)); - let opts_p = optional_ptr_addr(arg(1)) as *const ObjectHeader; - crate::child_process::js_child_process_exec_sync(cmd, opts_p) + ("util.types", "isDataView") => crate::object::js_util_types_is_data_view(arg(0)), + ("util.types", "isTypedArray") => crate::object::js_util_types_is_typed_array(arg(0)), + ("util.types", "isUint8Array") => crate::object::js_util_types_is_uint8_array(arg(0)), + ("util.types", "isInt8Array") => crate::object::js_util_types_is_int8_array(arg(0)), + ("util.types", "isInt16Array") => crate::object::js_util_types_is_int16_array(arg(0)), + ("util.types", "isUint16Array") => crate::object::js_util_types_is_uint16_array(arg(0)), + ("util.types", "isInt32Array") => crate::object::js_util_types_is_int32_array(arg(0)), + ("util.types", "isUint32Array") => crate::object::js_util_types_is_uint32_array(arg(0)), + ("util.types", "isFloat16Array") => crate::object::js_util_types_is_float16_array(arg(0)), + ("util.types", "isFloat32Array") => crate::object::js_util_types_is_float32_array(arg(0)), + ("util.types", "isFloat64Array") => crate::object::js_util_types_is_float64_array(arg(0)), + ("util.types", "isUint8ClampedArray") => { + crate::object::js_util_types_is_uint8_clamped_array(arg(0)) } - ("child_process", "exec") => { - let cmd = crate::string::js_string_materialize_to_heap(arg(0)); - crate::child_process::js_child_process_exec(cmd, arg(1), arg(2)) + ("util.types", "isBigInt64Array") => { + crate::object::js_util_types_is_big_int64_array(arg(0)) } - ("child_process", "execFile") => { - let file = crate::string::js_string_materialize_to_heap(arg(0)) as i64; - crate::child_process::js_child_process_exec_file(file, arg(1), arg(2), arg(3)) + ("util.types", "isBigUint64Array") => { + crate::object::js_util_types_is_big_uint64_array(arg(0)) } - ("child_process", "execFileSync") => { - let file = crate::string::js_string_materialize_to_heap(arg(0)) as i64; - crate::child_process::js_child_process_exec_file_sync(file, arg(1), arg(2)) + ("util.types", "isMap") => crate::object::js_util_types_is_map(arg(0)), + ("util.types", "isMapIterator") => crate::object::js_util_types_is_map_iterator(arg(0)), + ("util.types", "isProxy") => crate::object::js_util_types_is_proxy(arg(0)), + ("util.types", "isExternal") => crate::object::js_util_types_is_external(arg(0)), + ("util.types", "isModuleNamespaceObject") => { + crate::object::js_util_types_is_module_namespace_object(arg(0)) } - ("child_process", "_forkChild") => crate::child_process::js_fork_child(args_len), - ("child_process", "fork") => { - let module = crate::string::js_string_materialize_to_heap(arg(0)) as i64; - let args_p = optional_ptr_addr(arg(1)) as i64; - let opts_p = optional_ptr_addr(arg(2)) as i64; - crate::child_process::fork::js_child_process_fork(module, args_p, opts_p) + ("util.types", "isSet") => crate::object::js_util_types_is_set(arg(0)), + ("util.types", "isSetIterator") => crate::object::js_util_types_is_set_iterator(arg(0)), + ("util.types", "isWeakMap") => crate::object::js_util_types_is_weak_map(arg(0)), + ("util.types", "isWeakSet") => crate::object::js_util_types_is_weak_set(arg(0)), + ("util.types", "isDate") => crate::object::js_util_types_is_date(arg(0)), + ("util.types", "isRegExp") => crate::object::js_util_types_is_reg_exp(arg(0)), + ("util.types", "isAsyncFunction") => crate::object::js_util_types_is_async_function(arg(0)), + ("util.types", "isGeneratorFunction") => { + crate::object::js_util_types_is_generator_function(arg(0)) } - ("cluster", "setupPrimary") | ("cluster", "setupMaster") => { - crate::cluster::js_cluster_setup_primary(arg(0)) + ("util.types", "isGeneratorObject") => { + crate::object::js_util_types_is_generator_object(arg(0)) } - ("cluster", "fork") => crate::cluster::js_cluster_fork(arg(0)), - ("cluster", "disconnect") => crate::cluster::js_cluster_disconnect(arg(0)), - ("cluster", "Worker") => f64::from_bits(JSValue::undefined().bits()), - // #3687: node:cluster default-import EventEmitter surface. - ("cluster", "on") | ("cluster", "addListener") => { - crate::cluster::js_cluster_on(arg(0), arg(1)) + ("util.types", "isNativeError") => crate::object::js_util_types_is_native_error(arg(0)), + ("util.types", "isKeyObject") => crate::object::js_util_types_is_key_object(arg(0)), + ("util.types", "isCryptoKey") => crate::object::js_util_types_is_crypto_key(arg(0)), + ("util.types", "isNumberObject") => crate::object::js_util_types_is_number_object(arg(0)), + ("util.types", "isStringObject") => crate::object::js_util_types_is_string_object(arg(0)), + ("util.types", "isBooleanObject") => crate::object::js_util_types_is_boolean_object(arg(0)), + ("util.types", "isSymbolObject") => crate::object::js_util_types_is_symbol_object(arg(0)), + ("util.types", "isBoxedPrimitive") => { + crate::object::js_util_types_is_boxed_primitive(arg(0)) } - ("cluster", "once") => crate::cluster::js_cluster_once(arg(0), arg(1)), - ("cluster", "prependListener") => { - crate::cluster::js_cluster_prepend_listener(arg(0), arg(1)) + + // ── node:util/types direct module ── + ("util/types", "isArgumentsObject") => { + crate::object::js_util_types_is_arguments_object(arg(0)) } - ("cluster", "prependOnceListener") => { - crate::cluster::js_cluster_prepend_once_listener(arg(0), arg(1)) + ("util/types", "isPromise") => crate::object::js_util_types_is_promise(arg(0)), + ("util/types", "isBigIntObject") => crate::object::js_util_types_is_big_int_object(arg(0)), + ("util/types", "isArrayBuffer") => crate::object::js_util_types_is_array_buffer(arg(0)), + ("util/types", "isSharedArrayBuffer") => { + crate::object::js_util_types_is_shared_array_buffer(arg(0)) } - ("cluster", "emit") => crate::cluster::js_cluster_emit(arg(0), pack_args_from(1)), - ("cluster", "eventNames") => crate::cluster::js_cluster_event_names(), - ("cluster", "listenerCount") => crate::cluster::js_cluster_listener_count(arg(0)), - ("cluster", "removeListener") | ("cluster", "off") => { - crate::cluster::js_cluster_remove_listener(arg(0), arg(1)) + ("util/types", "isAnyArrayBuffer") => { + crate::object::js_util_types_is_any_array_buffer(arg(0)) } - ("cluster", "removeAllListeners") => { - crate::cluster::js_cluster_remove_all_listeners(arg(0)) + ("util/types", "isArrayBufferView") => { + crate::object::js_util_types_is_array_buffer_view(arg(0)) } - - // #1577: captured-then-called crypto methods (`const f = - // crypto.createHash; f(...)`). The impls live in perry-stdlib (which - // depends on this crate), so route through the dispatcher stdlib - // registers at startup via `js_set_native_crypto_dispatch`. Null when - // stdlib isn't linked (e.g. runtime-only tests) → undefined. The - // `randomFillSync` arm above is handled inline and never reaches here. - ("crypto" | "crypto.webcrypto", _) => { - let ptr = - crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) - } + ("util/types", "isDataView") => crate::object::js_util_types_is_data_view(arg(0)), + ("util/types", "isTypedArray") => crate::object::js_util_types_is_typed_array(arg(0)), + ("util/types", "isUint8Array") => crate::object::js_util_types_is_uint8_array(arg(0)), + ("util/types", "isInt8Array") => crate::object::js_util_types_is_int8_array(arg(0)), + ("util/types", "isInt16Array") => crate::object::js_util_types_is_int16_array(arg(0)), + ("util/types", "isUint16Array") => crate::object::js_util_types_is_uint16_array(arg(0)), + ("util/types", "isInt32Array") => crate::object::js_util_types_is_int32_array(arg(0)), + ("util/types", "isUint32Array") => crate::object::js_util_types_is_uint32_array(arg(0)), + ("util/types", "isFloat16Array") => crate::object::js_util_types_is_float16_array(arg(0)), + ("util/types", "isFloat32Array") => crate::object::js_util_types_is_float32_array(arg(0)), + ("util/types", "isFloat64Array") => crate::object::js_util_types_is_float64_array(arg(0)), + ("util/types", "isUint8ClampedArray") => { + crate::object::js_util_types_is_uint8_clamped_array(arg(0)) } - ("crypto.subtle", _) => { - let ptr = crate::value::JS_NATIVE_WEBCRYPTO_DISPATCH - .load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) - } + ("util/types", "isBigInt64Array") => { + crate::object::js_util_types_is_big_int64_array(arg(0)) } - // Captured-then-called zlib methods (`const f = zlib.gzip; await f(buf)`, - // `util.promisify(zlib.gzip)`). Mirrors the crypto arm above — the - // impls live in perry-stdlib which depends on this crate, so route - // through the dispatcher stdlib registers at startup via - // `js_set_native_zlib_dispatch`. Null when stdlib isn't linked. - ("zlib", _) => { - let ptr = - crate::value::JS_NATIVE_ZLIB_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) - } + ("util/types", "isBigUint64Array") => { + crate::object::js_util_types_is_big_uint64_array(arg(0)) } - ( - "querystring", - "unescapeBuffer" | "unescape" | "escape" | "stringify" | "encode" | "parse" | "decode", - ) => { - let ptr = crate::value::JS_NATIVE_QUERYSTRING_DISPATCH - .load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) - } + ("util/types", "isMap") => crate::object::js_util_types_is_map(arg(0)), + ("util/types", "isMapIterator") => crate::object::js_util_types_is_map_iterator(arg(0)), + ("util/types", "isProxy") => crate::object::js_util_types_is_proxy(arg(0)), + ("util/types", "isExternal") => crate::object::js_util_types_is_external(arg(0)), + ("util/types", "isModuleNamespaceObject") => { + crate::object::js_util_types_is_module_namespace_object(arg(0)) } - ("sqlite", _) => { - let ptr = - crate::value::JS_NATIVE_SQLITE_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: crate::value::JsNativeSqliteDispatchFn = std::mem::transmute(ptr); - dispatch( - method_name.as_ptr(), - method_name.len(), - args_ptr, - args_len, - 0, - ) - } + ("util/types", "isSet") => crate::object::js_util_types_is_set(arg(0)), + ("util/types", "isSetIterator") => crate::object::js_util_types_is_set_iterator(arg(0)), + ("util/types", "isWeakMap") => crate::object::js_util_types_is_weak_map(arg(0)), + ("util/types", "isWeakSet") => crate::object::js_util_types_is_weak_set(arg(0)), + ("util/types", "isDate") => crate::object::js_util_types_is_date(arg(0)), + ("util/types", "isRegExp") => crate::object::js_util_types_is_reg_exp(arg(0)), + ("util/types", "isAsyncFunction") => crate::object::js_util_types_is_async_function(arg(0)), + ("util/types", "isGeneratorFunction") => { + crate::object::js_util_types_is_generator_function(arg(0)) } - ("domain", "Domain" | "createDomain" | "create") => { - let ptr = - crate::value::JS_NATIVE_DOMAIN_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) - } + ("util/types", "isGeneratorObject") => { + crate::object::js_util_types_is_generator_object(arg(0)) } - ("crypto.Certificate", _) => { - let qualified: &[u8] = match method_name { - "verifySpkac" => b"Certificate.verifySpkac", - "exportPublicKey" => b"Certificate.exportPublicKey", - "exportChallenge" => b"Certificate.exportChallenge", - _ => return f64::from_bits(JSValue::undefined().bits()), - }; - let ptr = - crate::value::JS_NATIVE_CRYPTO_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn(*const u8, usize, *const f64, usize) -> f64 = - std::mem::transmute(ptr); - dispatch(qualified.as_ptr(), qualified.len(), args_ptr, args_len) - } + ("util/types", "isNativeError") => crate::object::js_util_types_is_native_error(arg(0)), + ("util/types", "isKeyObject") => crate::object::js_util_types_is_key_object(arg(0)), + ("util/types", "isCryptoKey") => crate::object::js_util_types_is_crypto_key(arg(0)), + ("util/types", "isNumberObject") => crate::object::js_util_types_is_number_object(arg(0)), + ("util/types", "isStringObject") => crate::object::js_util_types_is_string_object(arg(0)), + ("util/types", "isBooleanObject") => crate::object::js_util_types_is_boolean_object(arg(0)), + ("util/types", "isSymbolObject") => crate::object::js_util_types_is_symbol_object(arg(0)), + ("util/types", "isBoxedPrimitive") => { + crate::object::js_util_types_is_boxed_primitive(arg(0)) } + // ── url module (module-level functions return NaN-boxed JS values) ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - // #3906: top-level v8 helpers invoked through a bound callable - // (`const s = v8.serialize; s(x)`). The method-call form - // (`v8.serialize(x)`) already lowers through the codegen - // NATIVE_MODULE_TABLE; these arms keep the value-read/bound-call form - // coherent with the same FFI impls. +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_v8(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { ("v8", "serialize") => crate::node_v8::js_v8_serialize(arg(0)), ("v8", "deserialize") => crate::node_v8::js_v8_deserialize(arg(0)), ("v8", "getHeapStatistics") => crate::node_v8::js_v8_get_heap_statistics(), @@ -1845,13 +2244,6 @@ pub(crate) unsafe fn dispatch_native_module_method( } // node:repl non-interactive server and constructor surface. - ("repl", "start") => crate::node_repl::js_repl_start(arg(0)), - ("repl", "REPLServer") => crate::node_repl::js_repl_repl_server_new(arg(0)), - ("repl", "Recoverable") => crate::node_repl::js_repl_recoverable_new(arg(0)), - - // #3680: `v8.Serializer` / `v8.DefaultSerializer` instance methods. - // The registry id lives in field[1] of the namespace object; the - // runtime re-derives it from the receiver value. ("v8.Serializer", m) | ("v8.DefaultSerializer", m) => { let recv = crate::value::js_nanbox_pointer(obj as i64); match m { @@ -1910,10 +2302,44 @@ pub(crate) unsafe fn dispatch_native_module_method( "createHook" => crate::v8::js_v8_promise_hooks_create_hook(arg(0)), _ => f64::from_bits(JSValue::undefined().bits()), }, + _ => f64::from_bits(JSValue::undefined().bits()), + } +} - ("tls", _) => { +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_vm(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("vm", m) => crate::node_vm::dispatch_vm_method(m, arg(0), arg(1), arg(2)), + // ── tty module ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_wasi(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("wasi", "WASI") => crate::wasi::js_wasi_constructor_call(arg(0)), + + // ── net module legacy/internal helpers ── + _ => f64::from_bits(JSValue::undefined().bits()), + } +} + +#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +pub(crate) unsafe fn nm_dispatch_zlib(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { + let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let _ = (obj, args_ptr, args_len, assert_skip_prototype); + nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + match (module_name, method_name) { + ("zlib", _) => { let ptr = - crate::value::JS_NATIVE_TLS_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); + crate::value::JS_NATIVE_ZLIB_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); if ptr.is_null() { f64::from_bits(JSValue::undefined().bits()) } else { @@ -1922,60 +2348,6 @@ pub(crate) unsafe fn dispatch_native_module_method( dispatch(method_name.as_ptr(), method_name.len(), args_ptr, args_len) } } - - // #2533: captured / aliased server factories - // (`const createServer = options.createServer || createServerHTTP; - // createServer(opts, handler)` — `@hono/node-server`'s `serve()`). The - // method-call form (`http.createServer(...)`) already lowers through a - // dedicated codegen NATIVE_MODULE_TABLE path; the value-read form yields - // a bound-method closure (see `is_native_module_callable_export`) that - // lands here when invoked. The impls live in perry-ext-http-server, so - // route through the dispatcher perry-stdlib registers at startup under - // `external-http-server-pump` (enabled whenever http/https/http2 is - // imported). Null when the http ext crate isn't linked → undefined. The - // dispatcher takes the module name so one callback serves all three. - ("http", "createServer") - | ("http", "Server") - // #4904: captured / aliased client entry points (`const { get } = - // require('http'); get(opts, cb)`) — same bound-value mechanism as - // the server factories; the stdlib dispatcher routes them to - // `js_http_get` / `js_http_request` (and https twins). - | ("http", "request") - | ("http", "get") - | ("https", "request") - | ("https", "get") - | ("https", "createServer") - | ("https", "Server") - | ("http2", "createServer") - | ("http2", "createSecureServer") - | ("http2", "Server") => { - let ptr = - crate::value::JS_NATIVE_HTTP_DISPATCH.load(std::sync::atomic::Ordering::SeqCst); - if ptr.is_null() { - f64::from_bits(JSValue::undefined().bits()) - } else { - let dispatch: unsafe extern "C" fn( - *const u8, - usize, - *const u8, - usize, - *const f64, - usize, - ) -> f64 = std::mem::transmute(ptr); - dispatch( - module_name.as_ptr(), - module_name.len(), - method_name.as_ptr(), - method_name.len(), - args_ptr, - args_len, - ) - } - } - - _ => { - // Method not found on native module — return undefined - f64::from_bits(JSValue::undefined().bits()) - } + _ => f64::from_bits(JSValue::undefined().bits()), } } diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs new file mode 100644 index 000000000..e575863ce --- /dev/null +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -0,0 +1,192 @@ +//! Per-module native-module method-dispatch registry (devirtualization). +//! GENERATED scaffolding — see NM_DEVIRT_PLAN.md. Each `js_nm_install_()` is +//! the SOLE static reference to its `nm_dispatch_` bucket fn; codegen emits a +//! call per statically-imported native module so the linker dead-strips the rest. +//! NOTHING here names all buckets together (that would re-pin everything). +use super::native_module_dispatch::{NmCtx, nm_dispatch_assert, nm_dispatch_async_hooks, nm_dispatch_bigint, nm_dispatch_buffer, nm_dispatch_child_process, nm_dispatch_cluster, nm_dispatch_console, nm_dispatch_crypto, nm_dispatch_dgram, nm_dispatch_dns, nm_dispatch_domain, nm_dispatch_events, nm_dispatch_fs, nm_dispatch_http, nm_dispatch_inspector, nm_dispatch_module, nm_dispatch_net, nm_dispatch_os, nm_dispatch_path, nm_dispatch_perf, nm_dispatch_process, nm_dispatch_punycode, nm_dispatch_querystring, nm_dispatch_readline, nm_dispatch_repl, nm_dispatch_sea, nm_dispatch_sqlite, nm_dispatch_stream, nm_dispatch_timers, nm_dispatch_tls, nm_dispatch_tty, nm_dispatch_url, nm_dispatch_util, nm_dispatch_v8, nm_dispatch_vm, nm_dispatch_wasi, nm_dispatch_zlib}; +use std::sync::atomic::{AtomicPtr, Ordering}; + +type NmDispatchFn = unsafe fn(&NmCtx, &str, &str) -> f64; + +#[derive(Copy, Clone)] +#[repr(usize)] +enum NmBucket { Assert, AsyncHooks, Bigint, Buffer, ChildProcess, Cluster, Console, Crypto, Dgram, Dns, Domain, Events, Fs, Http, Inspector, Module, Net, Os, Path, Perf, Process, Punycode, Querystring, Readline, Repl, Sea, Sqlite, Stream, Timers, Tls, Tty, Url, Util, V8, Vm, Wasi, Zlib, } +const NM_BUCKET_COUNT: usize = 37; + +static NM_DISPATCH_REGISTRY: [AtomicPtr<()>; NM_BUCKET_COUNT] = + [const { AtomicPtr::new(std::ptr::null_mut()) }; NM_BUCKET_COUNT]; + +/// Map a (normalized) module tag to its bucket. Pure string match — references +/// no bucket fn, so it pins nothing. +fn nm_module_index(name: &str) -> Option { + match name { + "assert" => Some(NmBucket::Assert), + "async_hooks" => Some(NmBucket::AsyncHooks), + "bigint" => Some(NmBucket::Bigint), + "buffer" | "buffer.Buffer" => Some(NmBucket::Buffer), + "child_process" => Some(NmBucket::ChildProcess), + "cluster" => Some(NmBucket::Cluster), + "console" => Some(NmBucket::Console), + "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" | "crypto.webcrypto" => Some(NmBucket::Crypto), + "dgram" => Some(NmBucket::Dgram), + "dns" | "dns/promises" => Some(NmBucket::Dns), + "domain" => Some(NmBucket::Domain), + "events" => Some(NmBucket::Events), + "fs" => Some(NmBucket::Fs), + "http" => Some(NmBucket::Http), + "inspector" | "inspector.Network" | "inspector/promises" => Some(NmBucket::Inspector), + "module" => Some(NmBucket::Module), + "net" => Some(NmBucket::Net), + "os" => Some(NmBucket::Os), + "path" | "path.posix" | "path.win32" => Some(NmBucket::Path), + "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => Some(NmBucket::Perf), + "process" => Some(NmBucket::Process), + "punycode" | "punycode.ucs2" => Some(NmBucket::Punycode), + "querystring" => Some(NmBucket::Querystring), + "readline" => Some(NmBucket::Readline), + "repl" => Some(NmBucket::Repl), + "sea" => Some(NmBucket::Sea), + "sqlite" => Some(NmBucket::Sqlite), + "stream" => Some(NmBucket::Stream), + "timers" => Some(NmBucket::Timers), + "tls" => Some(NmBucket::Tls), + "tty" => Some(NmBucket::Tty), + "url" => Some(NmBucket::Url), + "util" | "util.types" | "util/types" => Some(NmBucket::Util), + "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" | "v8.startupSnapshot" => Some(NmBucket::V8), + "vm" => Some(NmBucket::Vm), + "wasi" => Some(NmBucket::Wasi), + "zlib" => Some(NmBucket::Zlib), + _ => None, + } +} + +/// Look up the installed per-module dispatch fn for `name`. `None` if unknown or +/// its `js_nm_install_()` was never emitted (module not statically imported). +pub(crate) fn nm_dispatch_lookup(name: &str) -> Option { + let b = nm_module_index(name)?; + let p = NM_DISPATCH_REGISTRY[b as usize].load(Ordering::Relaxed); + if p.is_null() { + None + } else { + Some(unsafe { std::mem::transmute::<*mut (), NmDispatchFn>(p) }) + } +} + +#[no_mangle] +pub extern "C" fn js_nm_install_assert() { NM_DISPATCH_REGISTRY[NmBucket::Assert as usize].store(nm_dispatch_assert as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_async_hooks() { NM_DISPATCH_REGISTRY[NmBucket::AsyncHooks as usize].store(nm_dispatch_async_hooks as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_bigint() { NM_DISPATCH_REGISTRY[NmBucket::Bigint as usize].store(nm_dispatch_bigint as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_buffer() { NM_DISPATCH_REGISTRY[NmBucket::Buffer as usize].store(nm_dispatch_buffer as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_child_process() { NM_DISPATCH_REGISTRY[NmBucket::ChildProcess as usize].store(nm_dispatch_child_process as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_cluster() { NM_DISPATCH_REGISTRY[NmBucket::Cluster as usize].store(nm_dispatch_cluster as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_console() { NM_DISPATCH_REGISTRY[NmBucket::Console as usize].store(nm_dispatch_console as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_crypto() { NM_DISPATCH_REGISTRY[NmBucket::Crypto as usize].store(nm_dispatch_crypto as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_dgram() { NM_DISPATCH_REGISTRY[NmBucket::Dgram as usize].store(nm_dispatch_dgram as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_dns() { NM_DISPATCH_REGISTRY[NmBucket::Dns as usize].store(nm_dispatch_dns as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_domain() { NM_DISPATCH_REGISTRY[NmBucket::Domain as usize].store(nm_dispatch_domain as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_events() { NM_DISPATCH_REGISTRY[NmBucket::Events as usize].store(nm_dispatch_events as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_fs() { NM_DISPATCH_REGISTRY[NmBucket::Fs as usize].store(nm_dispatch_fs as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_http() { NM_DISPATCH_REGISTRY[NmBucket::Http as usize].store(nm_dispatch_http as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_inspector() { NM_DISPATCH_REGISTRY[NmBucket::Inspector as usize].store(nm_dispatch_inspector as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_module() { NM_DISPATCH_REGISTRY[NmBucket::Module as usize].store(nm_dispatch_module as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_net() { NM_DISPATCH_REGISTRY[NmBucket::Net as usize].store(nm_dispatch_net as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_os() { NM_DISPATCH_REGISTRY[NmBucket::Os as usize].store(nm_dispatch_os as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_path() { NM_DISPATCH_REGISTRY[NmBucket::Path as usize].store(nm_dispatch_path as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_perf() { NM_DISPATCH_REGISTRY[NmBucket::Perf as usize].store(nm_dispatch_perf as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_process() { NM_DISPATCH_REGISTRY[NmBucket::Process as usize].store(nm_dispatch_process as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_punycode() { NM_DISPATCH_REGISTRY[NmBucket::Punycode as usize].store(nm_dispatch_punycode as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_querystring() { NM_DISPATCH_REGISTRY[NmBucket::Querystring as usize].store(nm_dispatch_querystring as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_readline() { NM_DISPATCH_REGISTRY[NmBucket::Readline as usize].store(nm_dispatch_readline as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_repl() { NM_DISPATCH_REGISTRY[NmBucket::Repl as usize].store(nm_dispatch_repl as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_sea() { NM_DISPATCH_REGISTRY[NmBucket::Sea as usize].store(nm_dispatch_sea as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_sqlite() { NM_DISPATCH_REGISTRY[NmBucket::Sqlite as usize].store(nm_dispatch_sqlite as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_stream() { NM_DISPATCH_REGISTRY[NmBucket::Stream as usize].store(nm_dispatch_stream as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_timers() { NM_DISPATCH_REGISTRY[NmBucket::Timers as usize].store(nm_dispatch_timers as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_tls() { NM_DISPATCH_REGISTRY[NmBucket::Tls as usize].store(nm_dispatch_tls as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_tty() { NM_DISPATCH_REGISTRY[NmBucket::Tty as usize].store(nm_dispatch_tty as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_url() { NM_DISPATCH_REGISTRY[NmBucket::Url as usize].store(nm_dispatch_url as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_util() { NM_DISPATCH_REGISTRY[NmBucket::Util as usize].store(nm_dispatch_util as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_v8() { NM_DISPATCH_REGISTRY[NmBucket::V8 as usize].store(nm_dispatch_v8 as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_vm() { NM_DISPATCH_REGISTRY[NmBucket::Vm as usize].store(nm_dispatch_vm as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_wasi() { NM_DISPATCH_REGISTRY[NmBucket::Wasi as usize].store(nm_dispatch_wasi as NmDispatchFn as *mut (), Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_nm_install_zlib() { NM_DISPATCH_REGISTRY[NmBucket::Zlib as usize].store(nm_dispatch_zlib as NmDispatchFn as *mut (), Ordering::Relaxed); } + +/// Dynamic-require fallback: register every bucket. Emitted by codegen only when +/// a native module is imported under an unanalyzable (dynamic) name. +#[no_mangle] +pub extern "C" fn js_nm_install_all() { + js_nm_install_assert(); + js_nm_install_async_hooks(); + js_nm_install_bigint(); + js_nm_install_buffer(); + js_nm_install_child_process(); + js_nm_install_cluster(); + js_nm_install_console(); + js_nm_install_crypto(); + js_nm_install_dgram(); + js_nm_install_dns(); + js_nm_install_domain(); + js_nm_install_events(); + js_nm_install_fs(); + js_nm_install_http(); + js_nm_install_inspector(); + js_nm_install_module(); + js_nm_install_net(); + js_nm_install_os(); + js_nm_install_path(); + js_nm_install_perf(); + js_nm_install_process(); + js_nm_install_punycode(); + js_nm_install_querystring(); + js_nm_install_readline(); + js_nm_install_repl(); + js_nm_install_sea(); + js_nm_install_sqlite(); + js_nm_install_stream(); + js_nm_install_timers(); + js_nm_install_tls(); + js_nm_install_tty(); + js_nm_install_url(); + js_nm_install_util(); + js_nm_install_v8(); + js_nm_install_vm(); + js_nm_install_wasi(); + js_nm_install_zlib(); +} From 31a2ed563f7256ac2612a50e497d11581fff1ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 09:18:55 +0200 Subject: [PATCH 02/13] feat(codegen): emit per-module js_nm_install_() at namespace-create sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire the devirt registry: codegen nm_install_symbol() maps each native-module name to its dispatch-install symbol; all 5 js_create_native_module_namespace sites now emit it before creating the namespace, so the module's bucket is registered before any method call. Externs declared in runtime_decls/objects.rs. Verified byte-identical to node: hello-world, import os (platform/EOL/arch), import path (join/basename/extname), global process (cwd/pid/argv/platform). hello-world emits 0 installs (imports nothing) → method-dispatch handlers for unimported modules drop (child_process syms 136→45, cluster 41→31; residual pinned by the still-monolithic constructor dispatcher = phase 2). --- .../perry-codegen/src/expr/dyn_extern_i18n.rs | 12 ++- crates/perry-codegen/src/expr/new_dynamic.rs | 7 +- .../src/expr/static_field_meta.rs | 10 +- crates/perry-codegen/src/lib.rs | 1 + crates/perry-codegen/src/nm_install.rs | 94 +++++++++++++++++++ .../src/runtime_decls/objects.rs | 39 ++++++++ .../src/object/native_module_dispatch.rs | 2 +- 7 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 crates/perry-codegen/src/nm_install.rs diff --git a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs index f4c567e44..518b20089 100644 --- a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs +++ b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs @@ -199,6 +199,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let mod_label = emit_string_literal_global(ctx, &name); let mod_len = name.len(); let blk = ctx.block(); + if let Some(s) = crate::nm_install::nm_install_symbol(&name) { + blk.call_void(s, &[]); + } let ns_val = blk.call( DOUBLE, "js_create_native_module_namespace", @@ -320,7 +323,11 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let name = name.to_string(); let mod_label = emit_string_literal_global(ctx, &name); let mod_len = name.len(); - ctx.block().call( + let blk = ctx.block(); + if let Some(s) = crate::nm_install::nm_install_symbol(&name) { + blk.call_void(s, &[]); + } + blk.call( DOUBLE, "js_create_native_module_namespace", &[(PTR, &mod_label), (I64, &mod_len.to_string())], @@ -627,6 +634,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let module_label = emit_string_literal_global(ctx, &bare); let module_len = bare.len(); let blk = ctx.block(); + if let Some(s) = crate::nm_install::nm_install_symbol(&bare) { + blk.call_void(s, &[]); + } return Ok(blk.call( DOUBLE, "js_create_native_module_namespace", diff --git a/crates/perry-codegen/src/expr/new_dynamic.rs b/crates/perry-codegen/src/expr/new_dynamic.rs index 566a5a43f..d84245d24 100644 --- a/crates/perry-codegen/src/expr/new_dynamic.rs +++ b/crates/perry-codegen/src/expr/new_dynamic.rs @@ -286,7 +286,12 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let mod_bytes_global = format!("@{}", ctx.strings.entry(mod_idx).bytes_global); let mod_len_str = module_name.len().to_string(); - return Ok(ctx.block().call( + let install_sym = crate::nm_install::nm_install_symbol(module_name); + let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } + return Ok(blk.call( DOUBLE, "js_create_native_module_namespace", &[(PTR, &mod_bytes_global), (I64, &mod_len_str)], diff --git a/crates/perry-codegen/src/expr/static_field_meta.rs b/crates/perry-codegen/src/expr/static_field_meta.rs index 2844d583f..3111573bd 100644 --- a/crates/perry-codegen/src/expr/static_field_meta.rs +++ b/crates/perry-codegen/src/expr/static_field_meta.rs @@ -573,7 +573,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let mod_idx = ctx.strings.intern(name); let mod_bytes_global = format!("@{}", ctx.strings.entry(mod_idx).bytes_global); let mod_len_str = name.len().to_string(); - Ok(ctx.block().call( + // Devirt: register this module's dispatch bucket before the namespace + // exists, so method calls route to it. Sole compile-time ref to the + // bucket's handlers — unimported modules dead-strip. + let install_sym = crate::nm_install::nm_install_symbol(name); + let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } + Ok(blk.call( DOUBLE, "js_create_native_module_namespace", &[(PTR, &mod_bytes_global), (I64, &mod_len_str)], diff --git a/crates/perry-codegen/src/lib.rs b/crates/perry-codegen/src/lib.rs index c0c2a0bdb..2df22575d 100644 --- a/crates/perry-codegen/src/lib.rs +++ b/crates/perry-codegen/src/lib.rs @@ -20,6 +20,7 @@ pub(crate) mod lower_string_method; pub mod module; pub mod nanbox; pub(crate) mod native_value; +pub(crate) mod nm_install; pub mod runtime_decls; pub(crate) mod stmt; pub mod strings; diff --git a/crates/perry-codegen/src/nm_install.rs b/crates/perry-codegen/src/nm_install.rs new file mode 100644 index 000000000..dbd94d471 --- /dev/null +++ b/crates/perry-codegen/src/nm_install.rs @@ -0,0 +1,94 @@ +//! GENERATED (NM_DEVIRT_PLAN.md): native-module dispatch-install symbol selection. +//! Mirrors perry-runtime `nm_module_index`. `js_create_native_module_namespace` +//! sites emit the returned symbol so the per-module dispatch bucket is registered +//! before any method call; unimported modules are never named → dead-stripped. + +/// Map a (possibly `node:`-prefixed) native module name to its dispatch-install +/// symbol, or `None` if the module has no method-dispatch bucket (its methods are +/// field-get callable-exports — dispatch returns undefined either way). +pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { + let name = name.strip_prefix("node:").unwrap_or(name); + match name { + "assert" => Some("js_nm_install_assert"), + "async_hooks" => Some("js_nm_install_async_hooks"), + "bigint" => Some("js_nm_install_bigint"), + "buffer" | "buffer.Buffer" => Some("js_nm_install_buffer"), + "child_process" => Some("js_nm_install_child_process"), + "cluster" => Some("js_nm_install_cluster"), + "console" => Some("js_nm_install_console"), + "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" | "crypto.webcrypto" => Some("js_nm_install_crypto"), + "dgram" => Some("js_nm_install_dgram"), + "dns" | "dns/promises" => Some("js_nm_install_dns"), + "domain" => Some("js_nm_install_domain"), + "events" => Some("js_nm_install_events"), + "fs" => Some("js_nm_install_fs"), + "http" => Some("js_nm_install_http"), + "inspector" | "inspector.Network" | "inspector/promises" => Some("js_nm_install_inspector"), + "module" => Some("js_nm_install_module"), + "net" => Some("js_nm_install_net"), + "os" => Some("js_nm_install_os"), + "path" | "path.posix" | "path.win32" => Some("js_nm_install_path"), + "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => Some("js_nm_install_perf"), + "process" => Some("js_nm_install_process"), + "punycode" | "punycode.ucs2" => Some("js_nm_install_punycode"), + "querystring" => Some("js_nm_install_querystring"), + "readline" => Some("js_nm_install_readline"), + "repl" => Some("js_nm_install_repl"), + "sea" => Some("js_nm_install_sea"), + "sqlite" => Some("js_nm_install_sqlite"), + "stream" => Some("js_nm_install_stream"), + "timers" => Some("js_nm_install_timers"), + "tls" => Some("js_nm_install_tls"), + "tty" => Some("js_nm_install_tty"), + "url" => Some("js_nm_install_url"), + "util" | "util.types" | "util/types" => Some("js_nm_install_util"), + "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" | "v8.startupSnapshot" => Some("js_nm_install_v8"), + "vm" => Some("js_nm_install_vm"), + "wasi" => Some("js_nm_install_wasi"), + "zlib" => Some("js_nm_install_zlib"), + _ => None, + } +} + +/// All dispatch-install symbols + the dynamic fallback — declared so codegen can +/// emit calls to them. +pub(crate) const NM_INSTALL_SYMBOLS: &[&str] = &[ + "js_nm_install_assert", + "js_nm_install_async_hooks", + "js_nm_install_bigint", + "js_nm_install_buffer", + "js_nm_install_child_process", + "js_nm_install_cluster", + "js_nm_install_console", + "js_nm_install_crypto", + "js_nm_install_dgram", + "js_nm_install_dns", + "js_nm_install_domain", + "js_nm_install_events", + "js_nm_install_fs", + "js_nm_install_http", + "js_nm_install_inspector", + "js_nm_install_module", + "js_nm_install_net", + "js_nm_install_os", + "js_nm_install_path", + "js_nm_install_perf", + "js_nm_install_process", + "js_nm_install_punycode", + "js_nm_install_querystring", + "js_nm_install_readline", + "js_nm_install_repl", + "js_nm_install_sea", + "js_nm_install_sqlite", + "js_nm_install_stream", + "js_nm_install_timers", + "js_nm_install_tls", + "js_nm_install_tty", + "js_nm_install_url", + "js_nm_install_util", + "js_nm_install_v8", + "js_nm_install_vm", + "js_nm_install_wasi", + "js_nm_install_zlib", + "js_nm_install_all", +]; diff --git a/crates/perry-codegen/src/runtime_decls/objects.rs b/crates/perry-codegen/src/runtime_decls/objects.rs index c4a68c8c8..f0df8c992 100644 --- a/crates/perry-codegen/src/runtime_decls/objects.rs +++ b/crates/perry-codegen/src/runtime_decls/objects.rs @@ -176,6 +176,45 @@ pub fn declare_phase_b_objects(module: &mut LlModule) { // lowered to `0.0` and any subsequent member access returned // undefined, tripping the spec property-access throw. module.declare_function("js_create_native_module_namespace", DOUBLE, &[PTR, I64]); + // Per-module native-module dispatch-install symbols (devirt). Codegen emits + // the matching one at each js_create_native_module_namespace site. + module.declare_function("js_nm_install_assert", VOID, &[]); + module.declare_function("js_nm_install_async_hooks", VOID, &[]); + module.declare_function("js_nm_install_bigint", VOID, &[]); + module.declare_function("js_nm_install_buffer", VOID, &[]); + module.declare_function("js_nm_install_child_process", VOID, &[]); + module.declare_function("js_nm_install_cluster", VOID, &[]); + module.declare_function("js_nm_install_console", VOID, &[]); + module.declare_function("js_nm_install_crypto", VOID, &[]); + module.declare_function("js_nm_install_dgram", VOID, &[]); + module.declare_function("js_nm_install_dns", VOID, &[]); + module.declare_function("js_nm_install_domain", VOID, &[]); + module.declare_function("js_nm_install_events", VOID, &[]); + module.declare_function("js_nm_install_fs", VOID, &[]); + module.declare_function("js_nm_install_http", VOID, &[]); + module.declare_function("js_nm_install_inspector", VOID, &[]); + module.declare_function("js_nm_install_module", VOID, &[]); + module.declare_function("js_nm_install_net", VOID, &[]); + module.declare_function("js_nm_install_os", VOID, &[]); + module.declare_function("js_nm_install_path", VOID, &[]); + module.declare_function("js_nm_install_perf", VOID, &[]); + module.declare_function("js_nm_install_process", VOID, &[]); + module.declare_function("js_nm_install_punycode", VOID, &[]); + module.declare_function("js_nm_install_querystring", VOID, &[]); + module.declare_function("js_nm_install_readline", VOID, &[]); + module.declare_function("js_nm_install_repl", VOID, &[]); + module.declare_function("js_nm_install_sea", VOID, &[]); + module.declare_function("js_nm_install_sqlite", VOID, &[]); + module.declare_function("js_nm_install_stream", VOID, &[]); + module.declare_function("js_nm_install_timers", VOID, &[]); + module.declare_function("js_nm_install_tls", VOID, &[]); + module.declare_function("js_nm_install_tty", VOID, &[]); + module.declare_function("js_nm_install_url", VOID, &[]); + module.declare_function("js_nm_install_util", VOID, &[]); + module.declare_function("js_nm_install_vm", VOID, &[]); + module.declare_function("js_nm_install_wasi", VOID, &[]); + module.declare_function("js_nm_install_zlib", VOID, &[]); + module.declare_function("js_nm_install_all", VOID, &[]); module.declare_function("js_object_get_field_ic_miss", DOUBLE, &[I64, I64, PTR]); // Object rest destructuring: copy all properties from src except excluded keys. // Takes a src object ptr and an array of NaN-boxed strings (the excluded keys), diff --git a/crates/perry-runtime/src/object/native_module_dispatch.rs b/crates/perry-runtime/src/object/native_module_dispatch.rs index 6f42ae870..42fe36609 100644 --- a/crates/perry-runtime/src/object/native_module_dispatch.rs +++ b/crates/perry-runtime/src/object/native_module_dispatch.rs @@ -746,6 +746,7 @@ pub(crate) unsafe fn nm_dispatch_dgram(ctx: &NmCtx, module_name: &str, method_na let _ = (obj, args_ptr, args_len, assert_skip_prototype); nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); match (module_name, method_name) { + #[cfg(feature = "mod-dgram")] ("dgram", "createSocket") | ("dgram", "Socket") => { crate::dgram::js_dgram_create_socket(pack_args()) } @@ -1645,7 +1646,6 @@ pub(crate) unsafe fn nm_dispatch_punycode(ctx: &NmCtx, module_name: &str, method // `module: "dgram"` usage), so this arm — and the `js_dgram_*` externs // it calls — are absent otherwise. Unreachable when off (a dgram // namespace can't exist without the import that enables the feature). - #[cfg(feature = "mod-dgram")] _ => f64::from_bits(JSValue::undefined().bits()), } } From 0590ceb2ca3f071847d155895d8dea062c6eeb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 09:28:49 +0200 Subject: [PATCH 03/13] docs(nm-devirt): record measured win + follow-ups (phase 1 complete) --- NM_DEVIRT_PLAN.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/NM_DEVIRT_PLAN.md b/NM_DEVIRT_PLAN.md index 09061fc5b..4dfcee1df 100644 --- a/NM_DEVIRT_PLAN.md +++ b/NM_DEVIRT_PLAN.md @@ -54,10 +54,13 @@ sound graceful degradation (install_all), semantics never change with a build fl [x] generated: NmCtx + nm_general_closures! macro + thin router + 37 nm_dispatch_ fns (native_module_dispatch.rs) [x] registry: NmBucket + NM_DISPATCH_REGISTRY + nm_module_index + 37 js_nm_install_() + js_nm_install_all() (native_module_registry.rs) [x] **perry-runtime compiles GREEN** (cargo build -p perry-runtime, 0 errors) -[ ] CODEGEN: emit js_nm_install_() at each js_create_native_module_namespace site (8 sites, main static_field_meta.rs:572); install_all on dynamic; declare externs in runtime_decls. REQUIRED for correctness — until wired, native-module method dispatch returns undefined (registry empty). -[ ] internal creators node_v8/perf_hooks call js_nm_install_v8()/perf() -[ ] full perry build + correctness test (import os/process) + measure hello-world delta -[ ] constructor-dispatcher (js_new_function_construct) — phase 2 +[x] CODEGEN: emit js_nm_install_() at all 5 js_create_native_module_namespace sites (nm_install.rs nm_install_symbol; externs declared in runtime_decls/objects.rs). perry builds green. +[x] CORRECTNESS verified byte-identical to node: hello-world, import os, import path, global process (cwd/pid/argv), util.format/inspect/types, querystring, assert. +[x] **MEASURED: hello-world __text 4,667,824 → 4,058,936 = −608,888 B (−13%); binary 5.4MB → 4.7MB.** (baseline = pristine origin/main perry.) +[ ] FOLLOW-UPS: + - install_all NOT yet emitted for truly-dynamic `import(runtimeVar)` of a native module → that path's method dispatch would return undefined. Wire js_nm_install_all() at the dynamic-import codegen site (the await-import-of-literal path IS covered). Rare; static imports + global process all work. + - node_v8/perf_hooks/cluster internal creators: covered because reachable only when their module is imported (→ codegen install ran). Verify with a v8-serializer / perf_hooks test. +[ ] constructor-dispatcher (js_new_function_construct, class_registry.rs) — phase 2 (residual child_process/cluster/readline syms still pinned by it; another chunk of __text). ## Generators (in /tmp, re-runnable from git HEAD) /tmp/nm_generate.py (dispatch file), /tmp/nm_gen_registry.py (registry). Both read From 4b2b36402a764eebf62acc49c50566ed219dcd56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 09:56:25 +0200 Subject: [PATCH 04/13] feat: dynamic getBuiltinModule install-all fallback (correctness, no size regression) Runtime-resolved builtins (process.getBuiltinModule(spec)) can't get a codegen per-module install since the module name isn't known at compile time. Add an indirect install-all hook: native_module_get_builtin_module_value runs nm_run_install_all_hook() (loads an opaque fn-ptr, never names js_nm_install_all), armed by js_nm_enable_install_all() which only the getBuiltinModule codegen wrapper (js_process_get_builtin_module_devirt) emits. black_box hides the stored pointer so whole-program opt can't speculatively devirtualize the indirect call back into a direct js_nm_install_all reference (that re-pinned every bucket). Verified: hello-world __text 4,058,968 (full stripping preserved, install_all absent); dynamic getBuiltinModule("node:"+x).platform() byte-identical to node. --- .../native_table/node_core_process.rs | 6 ++- .../src/runtime_decls/strings.rs | 1 + crates/perry-runtime/src/object/mod.rs | 1 + .../perry-runtime/src/object/native_module.rs | 11 ++++++ .../src/object/native_module_registry.rs | 37 +++++++++++++++++++ crates/perry-runtime/src/process.rs | 13 +++++++ 6 files changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/perry-codegen/src/lower_call/native_table/node_core_process.rs b/crates/perry-codegen/src/lower_call/native_table/node_core_process.rs index 16c5851aa..90d3b07f4 100644 --- a/crates/perry-codegen/src/lower_call/native_table/node_core_process.rs +++ b/crates/perry-codegen/src/lower_call/native_table/node_core_process.rs @@ -425,7 +425,11 @@ pub(super) const NODE_CORE_PROCESS_ROWS: &[NativeModSig] = &[ has_receiver: false, method: "getBuiltinModule", class_filter: None, - runtime: "js_process_get_builtin_module", + // Devirt: target the wrapper that arms the install-all hook, so only + // programs that actually call getBuiltinModule pull in all dispatch + // buckets (the dynamically-resolved namespace's module is not known at + // compile time). See js_process_get_builtin_module_devirt. + runtime: "js_process_get_builtin_module_devirt", args: &[NA_F64], ret: NR_F64, }, diff --git a/crates/perry-codegen/src/runtime_decls/strings.rs b/crates/perry-codegen/src/runtime_decls/strings.rs index 2376cce09..1118df7c5 100644 --- a/crates/perry-codegen/src/runtime_decls/strings.rs +++ b/crates/perry-codegen/src/runtime_decls/strings.rs @@ -806,6 +806,7 @@ pub fn declare_phase_b_strings(module: &mut LlModule) { module.declare_function("js_process_set_max_listeners", DOUBLE, &[DOUBLE]); module.declare_function("js_process_get_max_listeners", DOUBLE, &[]); module.declare_function("js_process_get_builtin_module", DOUBLE, &[DOUBLE]); + module.declare_function("js_process_get_builtin_module_devirt", DOUBLE, &[DOUBLE]); module.declare_function("js_process_execve", DOUBLE, &[DOUBLE, DOUBLE, DOUBLE]); // #3108: process.sourceMapsEnabled getter + setSourceMapsEnabled(bool). module.declare_function("js_process_source_maps_enabled", DOUBLE, &[]); diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index d225bf2d7..03af5d2b0 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -55,6 +55,7 @@ mod native_module_crypto_random; mod native_module_dispatch; mod native_module_dispatch_crypto; mod native_module_registry; +pub(crate) use native_module_registry::js_nm_enable_install_all; mod native_module_stream; mod native_this_alias; mod object_literal_ops; diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index 718ea16bb..0816b692c 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -3102,6 +3102,17 @@ fn cjs_default_export_value(module_name: &str) -> Option { } pub(crate) fn native_module_get_builtin_module_value(module_name: &str) -> f64 { + // Devirt: this is the runtime-dynamic builtin resolver (`require(spec)`, + // `process.getBuiltinModule(spec)`) — `module_name` is only known at runtime, + // so codegen could not emit the per-module dispatch install. Run the + // install-all hook so a dynamically-resolved namespace can dispatch methods. + // The hook is an INDIRECT pointer (null unless codegen emitted + // `js_nm_enable_install_all()` because the program actually uses dynamic + // require/getBuiltinModule) — so this resolver, which is linked into every + // program via the always-present `process.getBuiltinModule` method table, + // does NOT statically reference `js_nm_install_all` and therefore does not + // pin every bucket. Static imports keep their precise per-module installs. + super::native_module_registry::nm_run_install_all_hook(); cjs_default_export_value(module_name).unwrap_or_else(|| { js_create_native_module_namespace(module_name.as_ptr(), module_name.len()) }) diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index e575863ce..18a031b0d 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -190,3 +190,40 @@ pub extern "C" fn js_nm_install_all() { js_nm_install_wasi(); js_nm_install_zlib(); } + +// ── Dynamic-require install-all hook ─────────────────────────────────────── +// `js_nm_install_all` names every bucket, so it must NOT be statically reachable +// from any always-linked function (the runtime builtin resolver is linked into +// every program via `process.getBuiltinModule`). Instead it is reached only +// through this indirect pointer, set by `js_nm_enable_install_all()` which +// codegen emits ONLY when the program uses dynamic `require`/`getBuiltinModule`. +// Programs without dynamic builtin require never reference the setter → both it +// and `js_nm_install_all` dead-strip, preserving precise per-module stripping. +static NM_INSTALL_ALL_HOOK: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut()); + +/// Arm the install-all fallback. Sole static reference to `js_nm_install_all`. +/// Emitted by codegen at dynamic require / getBuiltinModule lowering sites. +#[no_mangle] +pub extern "C" fn js_nm_enable_install_all() { + // `black_box` hides that the stored value is `js_nm_install_all`. Without it, + // whole-program optimization proves this is the only value ever written to + // the single-pointer NM_INSTALL_ALL_HOOK and speculatively DEVIRTUALIZES the + // indirect call in `nm_run_install_all_hook` into a direct `js_nm_install_all` + // reference — re-pinning every bucket in programs that merely LINK the + // resolver (every program, via the process method table). The opaque pointer + // keeps the call genuinely indirect, so `js_nm_install_all` is reachable only + // through THIS setter, which only getBuiltinModule/require callers emit. + // (The per-bucket NM_DISPATCH_REGISTRY array is immune — its loads are indexed + // by a runtime bucket id, so the optimizer can't pin a specific slot's fn.) + let f = std::hint::black_box(js_nm_install_all as extern "C" fn()); + NM_INSTALL_ALL_HOOK.store(f as *mut (), Ordering::Relaxed); +} + +/// Run the install-all fallback if armed. References only the pointer — never +/// `js_nm_install_all` directly — so it pins nothing on its own. +pub(crate) fn nm_run_install_all_hook() { + let p = NM_INSTALL_ALL_HOOK.load(Ordering::Relaxed); + if !p.is_null() { + unsafe { std::mem::transmute::<*mut (), extern "C" fn()>(p)() }; + } +} diff --git a/crates/perry-runtime/src/process.rs b/crates/perry-runtime/src/process.rs index d4e881f58..e90f890a7 100644 --- a/crates/perry-runtime/src/process.rs +++ b/crates/perry-runtime/src/process.rs @@ -2546,6 +2546,19 @@ fn find_nearest_package_json(specifier: &str, base: &str) -> Option { } } +/// Devirt codegen entry for `process.getBuiltinModule(...)`. Arms the install-all +/// hook (so the dynamically-resolved namespace can dispatch methods) and +/// delegates. Codegen targets THIS symbol, so `js_nm_enable_install_all` — and +/// thus the all-buckets `js_nm_install_all` — is referenced only by programs +/// whose source actually calls `getBuiltinModule`. The plain +/// `js_process_get_builtin_module` (pinned by the runtime process method table in +/// every program) stays free of that reference, preserving per-module stripping. +#[no_mangle] +pub extern "C" fn js_process_get_builtin_module_devirt(id: f64) -> f64 { + crate::object::js_nm_enable_install_all(); + js_process_get_builtin_module(id) +} + /// process.getBuiltinModule(id) -> module namespace | undefined #[no_mangle] pub extern "C" fn js_process_get_builtin_module(id: f64) -> f64 { From 460154983b893572c23c7607fb193aeeb92b712e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 09:57:23 +0200 Subject: [PATCH 05/13] docs(nm-devirt): follow-up #1 (dynamic resolver) complete --- NM_DEVIRT_PLAN.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/NM_DEVIRT_PLAN.md b/NM_DEVIRT_PLAN.md index 4dfcee1df..2b192991a 100644 --- a/NM_DEVIRT_PLAN.md +++ b/NM_DEVIRT_PLAN.md @@ -57,9 +57,11 @@ sound graceful degradation (install_all), semantics never change with a build fl [x] CODEGEN: emit js_nm_install_() at all 5 js_create_native_module_namespace sites (nm_install.rs nm_install_symbol; externs declared in runtime_decls/objects.rs). perry builds green. [x] CORRECTNESS verified byte-identical to node: hello-world, import os, import path, global process (cwd/pid/argv), util.format/inspect/types, querystring, assert. [x] **MEASURED: hello-world __text 4,667,824 → 4,058,936 = −608,888 B (−13%); binary 5.4MB → 4.7MB.** (baseline = pristine origin/main perry.) -[ ] FOLLOW-UPS: - - install_all NOT yet emitted for truly-dynamic `import(runtimeVar)` of a native module → that path's method dispatch would return undefined. Wire js_nm_install_all() at the dynamic-import codegen site (the await-import-of-literal path IS covered). Rare; static imports + global process all work. - - node_v8/perf_hooks/cluster internal creators: covered because reachable only when their module is imported (→ codegen install ran). Verify with a v8-serializer / perf_hooks test. +[x] FOLLOW-UP #1 DONE — dynamic getBuiltinModule/require fallback via indirect install-all hook: + - native_module_get_builtin_module_value → nm_run_install_all_hook() (opaque ptr, names no bucket) + - js_nm_enable_install_all() (black_box'd, sole ref to js_nm_install_all) armed by js_process_get_builtin_module_devirt (codegen getBuiltinModule table target) + - black_box REQUIRED: else whole-program opt devirtualizes the single-ptr indirect call → re-pins all (per-bucket array is immune, runtime-indexed). + - Verified: getBuiltinModule(dynamic+literal), require(literal), global process, static import — all byte-identical to node; hello-world __text 4,058,968 (install_all absent). RESIDUAL EDGE: require(runtimeVar) of builtin (module_require.rs:121, not armed) — narrow, likely deferred anyway. [ ] constructor-dispatcher (js_new_function_construct, class_registry.rs) — phase 2 (residual child_process/cluster/readline syms still pinned by it; another chunk of __text). ## Generators (in /tmp, re-runnable from git HEAD) From 8406fafeaed8bec7641e153c61ac2b3b4f2ce0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 10:54:34 +0200 Subject: [PATCH 06/13] feat: devirtualize node-module-namespaced constructors (devirt phase 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit js_new_function_construct dispatched 'new ns.Ctor()' for tty/fs/vm/tls/wasi/ readline/repl/stream with a direct call to each subsystem's *_new — statically pinning that code in every binary. Extract the 8 into per-module nm_ctor_ fns routed through NM_CTOR_REGISTRY, registered by the SAME js_nm_install_() codegen emits at import (no new codegen). Global builtins (URL/WeakSet/Error/ TypedArray) stay inline; http/events/zlib/sqlite already use dynamic dispatch ptrs. Measured: hello-world __text 4,058,968 -> 3,971,252 (repl ctors fully stripped; total from origin/main baseline 4,667,824 -> 3,971,252 = -696,572 / -14.9%). Correct byte-identical to node: new stream.Readable/Writable/Transform, global new URL/TextEncoder/WeakSet/Error/Uint8Array, + all 6 phase-1 cases (os/path/util/ process/getBuiltinModule/require). Residual node_stream/tls/child_process pinned by intra-subsystem refs + method-dispatch internals = phase 3 (diminishing returns). --- .../src/object/class_registry.rs | 290 ++++++++++-------- crates/perry-runtime/src/object/mod.rs | 1 + .../src/object/native_module_registry.rs | 52 +++- 3 files changed, 210 insertions(+), 133 deletions(-) diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index df92590f1..0720fcc15 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -1632,6 +1632,161 @@ pub extern "C" fn js_new_target_value() -> f64 { /// empty-object allocation when the function value isn't a closure /// (preserves the pre-fix baseline for misuse). #[no_mangle] +// ── Per-module constructor buckets (devirt phase 2) ──────────────────────── +// `new .()` for node-module-namespaced constructors that the +// old monolithic `js_new_function_construct` dispatched with a direct call to +// the subsystem's `*_new` — statically pinning tty/fs/vm/tls/wasi/repl/stream/ +// readline handlers into every binary. Each is now a per-module fn reached only +// through NM_CTOR_REGISTRY, registered by the same `js_nm_install_()` +// that codegen emits when the module is imported. `None` ⇒ not a ctor this +// module owns; caller falls through (e.g. to the http/events/zlib dynamic +// dispatchers, which already strip on their own). Helper to read arg N. +#[inline] +unsafe fn nm_ctor_arg(args_ptr: *const f64, args_len: usize, n: usize) -> f64 { + if !args_ptr.is_null() && args_len > n { + *args_ptr.add(n) + } else { + f64::from_bits(crate::value::TAG_UNDEFINED) + } +} + +pub(crate) unsafe fn nm_ctor_tty( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if matches!(method, "ReadStream" | "WriteStream") { + let fd = nm_ctor_arg(args_ptr, args_len, 0); + return Some(if method == "ReadStream" { + crate::tty::js_tty_read_stream_new(fd) + } else { + crate::tty::js_tty_write_stream_new(fd) + }); + } + None +} + +pub(crate) unsafe fn nm_ctor_fs( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if method == "Utf8Stream" { + return Some(crate::fs::js_fs_utf8_stream_new(nm_ctor_arg(args_ptr, args_len, 0))); + } + if matches!( + method, + "ReadStream" | "FileReadStream" | "WriteStream" | "FileWriteStream" + ) { + let path = nm_ctor_arg(args_ptr, args_len, 0); + let options = nm_ctor_arg(args_ptr, args_len, 1); + return Some(if matches!(method, "ReadStream" | "FileReadStream") { + crate::fs::js_fs_create_read_stream(path, options) + } else { + crate::fs::js_fs_create_write_stream(path, options) + }); + } + None +} + +pub(crate) unsafe fn nm_ctor_vm( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if method == "Script" { + let code = nm_ctor_arg(args_ptr, args_len, 0); + let options = nm_ctor_arg(args_ptr, args_len, 1); + return Some(crate::node_vm::js_vm_script_new(code, options)); + } + None +} + +pub(crate) unsafe fn nm_ctor_tls( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if method == "SecureContext" { + return Some(crate::tls::js_tls_secure_context_new(nm_ctor_arg( + args_ptr, args_len, 0, + ))); + } + None +} + +pub(crate) unsafe fn nm_ctor_wasi( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if method == "WASI" { + return Some(crate::wasi::js_wasi_new(nm_ctor_arg(args_ptr, args_len, 0))); + } + None +} + +pub(crate) unsafe fn nm_ctor_readline( + module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if module == "readline/promises" && method == "Readline" { + let output = nm_ctor_arg(args_ptr, args_len, 0); + let options = nm_ctor_arg(args_ptr, args_len, 1); + return Some(crate::node_submodules::js_readline_promises_readline_new( + output, options, + )); + } + None +} + +pub(crate) unsafe fn nm_ctor_repl( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if matches!(method, "Recoverable" | "REPLServer") { + let first = nm_ctor_arg(args_ptr, args_len, 0); + return Some(if method == "Recoverable" { + crate::node_repl::js_repl_recoverable_new(first) + } else { + crate::node_repl::js_repl_repl_server_new(first) + }); + } + None +} + +pub(crate) unsafe fn nm_ctor_stream( + _module: &str, + method: &str, + args_ptr: *const f64, + args_len: usize, +) -> Option { + if matches!( + method, + "Readable" | "Writable" | "Duplex" | "Transform" | "PassThrough" + ) { + let opts = nm_ctor_arg(args_ptr, args_len, 0); + return Some(match method { + "Readable" => crate::node_stream::js_node_stream_readable_new(opts), + "Writable" => crate::node_stream::js_node_stream_writable_new(opts), + "Duplex" => crate::node_stream::js_node_stream_duplex_new(opts), + "Transform" => crate::node_stream::js_node_stream_transform_new(opts), + "PassThrough" => crate::node_stream::js_node_stream_passthrough_new(opts), + _ => unreachable!(), + }); + } + None +} + pub unsafe extern "C" fn js_new_function_construct( func_value: f64, args_ptr: *const f64, @@ -1704,131 +1859,16 @@ pub unsafe extern "C" fn js_new_function_construct( return dispatch(method.as_ptr(), method.len(), args_ptr, args_len, 1); } } - if module == "tty" && matches!(method.as_str(), "ReadStream" | "WriteStream") { - let fd = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return if method == "ReadStream" { - crate::tty::js_tty_read_stream_new(fd) - } else { - crate::tty::js_tty_write_stream_new(fd) - }; - } - if module == "fs" && method == "Utf8Stream" { - let options = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return crate::fs::js_fs_utf8_stream_new(options); - } - if module == "vm" && method == "Script" { - let code = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - let options = if !args_ptr.is_null() && args_len > 1 { - *args_ptr.add(1) - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return crate::node_vm::js_vm_script_new(code, options); - } - if module == "fs" - && matches!( - method.as_str(), - "ReadStream" | "FileReadStream" | "WriteStream" | "FileWriteStream" - ) - { - let path = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - let options = if !args_ptr.is_null() && args_len > 1 { - *args_ptr.add(1) - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return if matches!(method.as_str(), "ReadStream" | "FileReadStream") { - crate::fs::js_fs_create_read_stream(path, options) - } else { - crate::fs::js_fs_create_write_stream(path, options) - }; - } - if module == "tls" && method == "SecureContext" { - let options = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return crate::tls::js_tls_secure_context_new(options); - } - if module == "wasi" && method == "WASI" { - let options = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return crate::wasi::js_wasi_new(options); - } - if module == "readline/promises" && method == "Readline" { - let output = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - let options = if !args_ptr.is_null() && args_len > 1 { - *args_ptr.add(1) - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return crate::node_submodules::js_readline_promises_readline_new(output, options); - } - if module == "repl" && matches!(method.as_str(), "Recoverable" | "REPLServer") { - let first = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return if method == "Recoverable" { - crate::node_repl::js_repl_recoverable_new(first) - } else { - crate::node_repl::js_repl_repl_server_new(first) - }; - } - // #3663: `new Readable(opts)` (and Writable/Duplex/Transform/PassThrough) - // where the constructor binding came through any aliasing path the - // compiler can't resolve to a bare `Expr::New` — `const { Readable } = - // require('stream')`, `const s = require('stream'); new s.Readable()`, - // or `const R = stream.Readable; new R()`. In each case the callee - // value is the `stream.` bound-method closure, so dispatch to the - // same runtime constructors the named-import path uses. Without this the - // call falls through to the empty-object baseline and the resulting - // object has no EventEmitter/Writable methods, so `.on()`/`.write()`/ - // `.pipe()` throw "is not a function". - if module == "stream" - && matches!( - method.as_str(), - "Readable" | "Writable" | "Duplex" | "Transform" | "PassThrough" - ) - { - let opts = if !args_ptr.is_null() && args_len > 0 { - *args_ptr - } else { - f64::from_bits(crate::value::TAG_UNDEFINED) - }; - return match method.as_str() { - "Readable" => crate::node_stream::js_node_stream_readable_new(opts), - "Writable" => crate::node_stream::js_node_stream_writable_new(opts), - "Duplex" => crate::node_stream::js_node_stream_duplex_new(opts), - "Transform" => crate::node_stream::js_node_stream_transform_new(opts), - "PassThrough" => crate::node_stream::js_node_stream_passthrough_new(opts), - _ => unreachable!(), - }; + // Devirt phase 2: node-module-namespaced constructors (tty/fs/vm/tls/ + // wasi/readline/repl/stream) dispatch through the per-module ctor + // registry, populated by `js_nm_install_()` at import. Each + // unimported module's constructors are referenced only via that install + // symbol, so they dead-strip. `None` falls through to the dynamic- + // dispatch ctors below (http/events/zlib) and the global-name match. + if let Some(ctor) = crate::object::nm_ctor_lookup(&module) { + if let Some(result) = ctor(&module, &method, args_ptr, args_len) { + return result; + } } // #4904: `new http.Agent(opts)` / `new http.ClientRequest(opts)` / // `new http.IncomingMessage(socket)` / `new http.ServerResponse(req)` diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index 03af5d2b0..7d9fe3c25 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -56,6 +56,7 @@ mod native_module_dispatch; mod native_module_dispatch_crypto; mod native_module_registry; pub(crate) use native_module_registry::js_nm_enable_install_all; +pub(crate) use native_module_registry::nm_ctor_lookup; mod native_module_stream; mod native_this_alias; mod object_literal_ops; diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index 18a031b0d..29d3ba93a 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -98,7 +98,7 @@ pub extern "C" fn js_nm_install_domain() { NM_DISPATCH_REGISTRY[NmBucket::Domain #[no_mangle] pub extern "C" fn js_nm_install_events() { NM_DISPATCH_REGISTRY[NmBucket::Events as usize].store(nm_dispatch_events as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] -pub extern "C" fn js_nm_install_fs() { NM_DISPATCH_REGISTRY[NmBucket::Fs as usize].store(nm_dispatch_fs as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_fs() { NM_DISPATCH_REGISTRY[NmBucket::Fs as usize].store(nm_dispatch_fs as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Fs, nm_ctor_fs); } #[no_mangle] pub extern "C" fn js_nm_install_http() { NM_DISPATCH_REGISTRY[NmBucket::Http as usize].store(nm_dispatch_http as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] @@ -120,21 +120,21 @@ pub extern "C" fn js_nm_install_punycode() { NM_DISPATCH_REGISTRY[NmBucket::Puny #[no_mangle] pub extern "C" fn js_nm_install_querystring() { NM_DISPATCH_REGISTRY[NmBucket::Querystring as usize].store(nm_dispatch_querystring as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] -pub extern "C" fn js_nm_install_readline() { NM_DISPATCH_REGISTRY[NmBucket::Readline as usize].store(nm_dispatch_readline as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_readline() { NM_DISPATCH_REGISTRY[NmBucket::Readline as usize].store(nm_dispatch_readline as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Readline, nm_ctor_readline); } #[no_mangle] -pub extern "C" fn js_nm_install_repl() { NM_DISPATCH_REGISTRY[NmBucket::Repl as usize].store(nm_dispatch_repl as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_repl() { NM_DISPATCH_REGISTRY[NmBucket::Repl as usize].store(nm_dispatch_repl as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Repl, nm_ctor_repl); } #[no_mangle] pub extern "C" fn js_nm_install_sea() { NM_DISPATCH_REGISTRY[NmBucket::Sea as usize].store(nm_dispatch_sea as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] pub extern "C" fn js_nm_install_sqlite() { NM_DISPATCH_REGISTRY[NmBucket::Sqlite as usize].store(nm_dispatch_sqlite as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] -pub extern "C" fn js_nm_install_stream() { NM_DISPATCH_REGISTRY[NmBucket::Stream as usize].store(nm_dispatch_stream as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_stream() { NM_DISPATCH_REGISTRY[NmBucket::Stream as usize].store(nm_dispatch_stream as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Stream, nm_ctor_stream); } #[no_mangle] pub extern "C" fn js_nm_install_timers() { NM_DISPATCH_REGISTRY[NmBucket::Timers as usize].store(nm_dispatch_timers as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] -pub extern "C" fn js_nm_install_tls() { NM_DISPATCH_REGISTRY[NmBucket::Tls as usize].store(nm_dispatch_tls as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_tls() { NM_DISPATCH_REGISTRY[NmBucket::Tls as usize].store(nm_dispatch_tls as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Tls, nm_ctor_tls); } #[no_mangle] -pub extern "C" fn js_nm_install_tty() { NM_DISPATCH_REGISTRY[NmBucket::Tty as usize].store(nm_dispatch_tty as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_tty() { NM_DISPATCH_REGISTRY[NmBucket::Tty as usize].store(nm_dispatch_tty as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Tty, nm_ctor_tty); } #[no_mangle] pub extern "C" fn js_nm_install_url() { NM_DISPATCH_REGISTRY[NmBucket::Url as usize].store(nm_dispatch_url as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] @@ -142,9 +142,9 @@ pub extern "C" fn js_nm_install_util() { NM_DISPATCH_REGISTRY[NmBucket::Util as #[no_mangle] pub extern "C" fn js_nm_install_v8() { NM_DISPATCH_REGISTRY[NmBucket::V8 as usize].store(nm_dispatch_v8 as NmDispatchFn as *mut (), Ordering::Relaxed); } #[no_mangle] -pub extern "C" fn js_nm_install_vm() { NM_DISPATCH_REGISTRY[NmBucket::Vm as usize].store(nm_dispatch_vm as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_vm() { NM_DISPATCH_REGISTRY[NmBucket::Vm as usize].store(nm_dispatch_vm as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Vm, nm_ctor_vm); } #[no_mangle] -pub extern "C" fn js_nm_install_wasi() { NM_DISPATCH_REGISTRY[NmBucket::Wasi as usize].store(nm_dispatch_wasi as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_wasi() { NM_DISPATCH_REGISTRY[NmBucket::Wasi as usize].store(nm_dispatch_wasi as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Wasi, nm_ctor_wasi); } #[no_mangle] pub extern "C" fn js_nm_install_zlib() { NM_DISPATCH_REGISTRY[NmBucket::Zlib as usize].store(nm_dispatch_zlib as NmDispatchFn as *mut (), Ordering::Relaxed); } @@ -227,3 +227,39 @@ pub(crate) fn nm_run_install_all_hook() { unsafe { std::mem::transmute::<*mut (), extern "C" fn()>(p)() }; } } + +// ── Per-module constructor registry (devirt phase 2) ─────────────────────── +// `new .()` for node-module-namespaced constructors. Mirrors +// the method-dispatch registry: populated by `js_nm_install_()` (only +// the 8 ctor-owning buckets register a fn), looked up by `js_new_function_construct`. +use super::class_registry::{ + nm_ctor_fs, nm_ctor_readline, nm_ctor_repl, nm_ctor_stream, nm_ctor_tls, nm_ctor_tty, + nm_ctor_vm, nm_ctor_wasi, +}; + +type NmCtorFn = unsafe fn(&str, &str, *const f64, usize) -> Option; + +static NM_CTOR_REGISTRY: [AtomicPtr<()>; NM_BUCKET_COUNT] = + [const { AtomicPtr::new(std::ptr::null_mut()) }; NM_BUCKET_COUNT]; + +/// Look up the installed per-module constructor fn for `module`. `None` if the +/// module owns no namespaced constructors or its install was never emitted. +pub(crate) fn nm_ctor_lookup(module: &str) -> Option { + // `readline/promises` (and friends) bucket on the first path segment. + let b = nm_module_index(module) + .or_else(|| nm_module_index(module.split('/').next().unwrap_or(module)))?; + let p = NM_CTOR_REGISTRY[b as usize].load(Ordering::Relaxed); + if p.is_null() { + None + } else { + Some(unsafe { std::mem::transmute::<*mut (), NmCtorFn>(p) }) + } +} + +/// Register a bucket's constructor fn. Called from the relevant +/// `js_nm_install_()` so a ctor is reachable only when its module is +/// imported. `black_box` is unnecessary here (array slot indexed by a runtime +/// bucket id, like NM_DISPATCH_REGISTRY — not speculatively devirtualizable). +fn nm_register_ctor(b: NmBucket, f: NmCtorFn) { + NM_CTOR_REGISTRY[b as usize].store(f as *mut (), Ordering::Relaxed); +} From 0e5f5c2c84004bf0a5961e77182b8dd9fec200c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 10:54:50 +0200 Subject: [PATCH 07/13] docs(nm-devirt): phase 2 complete --- NM_DEVIRT_PLAN.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/NM_DEVIRT_PLAN.md b/NM_DEVIRT_PLAN.md index 2b192991a..e2b90d4a9 100644 --- a/NM_DEVIRT_PLAN.md +++ b/NM_DEVIRT_PLAN.md @@ -62,7 +62,18 @@ sound graceful degradation (install_all), semantics never change with a build fl - js_nm_enable_install_all() (black_box'd, sole ref to js_nm_install_all) armed by js_process_get_builtin_module_devirt (codegen getBuiltinModule table target) - black_box REQUIRED: else whole-program opt devirtualizes the single-ptr indirect call → re-pins all (per-bucket array is immune, runtime-indexed). - Verified: getBuiltinModule(dynamic+literal), require(literal), global process, static import — all byte-identical to node; hello-world __text 4,058,968 (install_all absent). RESIDUAL EDGE: require(runtimeVar) of builtin (module_require.rs:121, not armed) — narrow, likely deferred anyway. -[ ] constructor-dispatcher (js_new_function_construct, class_registry.rs) — phase 2 (residual child_process/cluster/readline syms still pinned by it; another chunk of __text). +[x] PHASE 2 DONE (commit 8406fafea) — node-module-namespaced constructor devirt: + - 8 direct-call ctor blocks (tty/fs/vm/tls/wasi/readline/repl/stream) in js_new_function_construct + → per-module nm_ctor_ fns (class_registry.rs) routed via NM_CTOR_REGISTRY, registered by the + SAME js_nm_install_() (no new codegen). Globals (URL/WeakSet/Error/TypedArray) stay inline; + http/events/zlib/sqlite already dynamic-dispatch. + - Measured: hello-world __text 4,058,968 → 3,971,252 (repl fully stripped). TOTAL from baseline + 4,667,824 → 3,971,252 = −696,572 (−14.9%); binary 5.4MB → ~4.6MB. + - Correct: new stream.Readable/Writable/Transform, global new URL/TextEncoder/WeakSet/Error/Uint8Array, + + all 6 phase-1 cases. +[ ] PHASE 3 (diminishing returns) — residual node_stream/tls/child_process/cluster pinned by INTRA-subsystem + refs (js_node_stream_from_web→readable_new) + method-dispatch internals that construct streams. Would + need devirtualizing those internal paths too. ## Generators (in /tmp, re-runnable from git HEAD) /tmp/nm_generate.py (dispatch file), /tmp/nm_gen_registry.py (registry). Both read From a5c14dbbffb173c805468bf70a7d096643979d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 11:21:23 +0200 Subject: [PATCH 08/13] refactor(console): coarse console.trace frame instead of std::backtrace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit console.trace() called std::backtrace::Backtrace::force_capture(), printing Rust frames that (a) symbolicate to __mh_execute_header on stripped release builds and (b) are inconsistent with Error().stack, which is intentionally coarse ('Real stack traces are not implemented'). Emit the same 'at ' frame Error.stack uses. Also a prerequisite for dropping the std DWARF symbolizer (gimli/addr2line/dwarf) — force_capture pulls it regardless of panic mode, so it must go before panic_immediate_abort can strip the ~143KB. console.trace output verified: 'Trace: \n at '. --- crates/perry-runtime/src/builtins/console.rs | 120 +++---------------- 1 file changed, 16 insertions(+), 104 deletions(-) diff --git a/crates/perry-runtime/src/builtins/console.rs b/crates/perry-runtime/src/builtins/console.rs index a969cc898..cc302c094 100644 --- a/crates/perry-runtime/src/builtins/console.rs +++ b/crates/perry-runtime/src/builtins/console.rs @@ -1336,11 +1336,13 @@ pub extern "C" fn js_console_assert_spread(cond: f64, args_arr_handle: i64) { // === console.trace === // -// Node writes `Trace: ` + a JS stack trace to **stderr**. Perry can't -// reproduce Node's TS source positions without a source-map / DWARF pass, -// but `std::backtrace::Backtrace::force_capture()` gives us the native -// call stack for free — good enough to see *where* the trace was called -// from, which is what issue #20 is actually asking for. +// Node writes `Trace: ` + a JS stack trace to **stderr**. Perry's stack +// traces are intentionally coarse (see `error.rs` `make_stack`: "Real stack +// traces are not implemented"), so console.trace emits the same `at ` +// frame `Error().stack` does. We deliberately do NOT use +// `std::backtrace::Backtrace::force_capture()`: it pulled the std DWARF +// symbolizer (gimli/addr2line/dwarf, ~143KB) into every binary, and on stripped +// release builds every frame symbolicated to `__mh_execute_header` anyway. #[no_mangle] pub extern "C" fn js_console_trace(value: f64) { let jsval = JSValue::from_bits(value.to_bits()); @@ -1364,56 +1366,12 @@ pub extern "C" fn js_console_trace(value: f64) { } else { eprintln!("Trace: {}", format_jsvalue(value, 0)); } - let bt = std::backtrace::Backtrace::force_capture(); - let rendered = format!("{}", bt); - // Parse the Display output into (header, continuation*) frames. The - // header looks like " N: symbol" and each continuation starts with - // "at …". Drop frames whose header matches internal noise (the - // std::backtrace plumbing itself, plus `js_console_trace` — the user - // already sees `Trace:` above). Collapse consecutive identical headers - // (what you get on stripped builds, where every frame symbolicates to - // `__mh_execute_header`). - let noise = ["backtrace", "Backtrace::", "js_console_trace"]; - let is_header = - |t: &str| t.chars().next().is_some_and(|c| c.is_ascii_digit()) && t.contains(':'); - let mut frames: Vec<(String, Vec)> = Vec::new(); - for line in rendered.lines() { - let t = line.trim_start(); - if t.is_empty() || t.starts_with("note:") { - continue; - } - if is_header(t) { - let sym = t.split_once(':').map(|(_, r)| r.trim()).unwrap_or(t); - frames.push((sym.to_string(), Vec::new())); - } else if let Some(last) = frames.last_mut() { - last.1.push(t.to_string()); - } - } - let mut emitted = 0usize; - let mut prev_sym: Option = None; - let mut dup_run = 0usize; - for (sym, cont) in frames { - if noise.iter().any(|p| sym.contains(p)) { - continue; - } - if prev_sym.as_deref() == Some(sym.as_str()) { - dup_run += 1; - continue; - } - if dup_run > 0 { - eprintln!(" (… {} more identical frames)", dup_run); - dup_run = 0; - } - eprintln!(" {}: {}", emitted, sym); - for c in cont { - eprintln!(" {}", c); - } - emitted += 1; - prev_sym = Some(sym); - } - if dup_run > 0 { - eprintln!(" (… {} more identical frames)", dup_run); - } + // Perry stack traces are intentionally coarse (error.rs make_stack: + // "Real stack traces are not implemented") — match `Error().stack` + // and drop the std DWARF symbolizer (gimli/addr2line/dwarf ~143KB) that + // `Backtrace::force_capture()` pulls into every binary; on stripped + // release builds those frames are all `__mh_execute_header` anyway. + emit_console_trace_stack(); } #[no_mangle] @@ -1433,55 +1391,9 @@ pub extern "C" fn js_console_trace_spread(arr_ptr: *const crate::array::ArrayHea } fn emit_console_trace_stack() { - let bt = std::backtrace::Backtrace::force_capture(); - let rendered = format!("{}", bt); - let noise = [ - "backtrace", - "Backtrace::", - "js_console_trace", - "js_console_trace_spread", - "emit_console_trace_stack", - ]; - let is_header = - |t: &str| t.chars().next().is_some_and(|c| c.is_ascii_digit()) && t.contains(':'); - let mut frames: Vec<(String, Vec)> = Vec::new(); - for line in rendered.lines() { - let t = line.trim_start(); - if t.is_empty() || t.starts_with("note:") { - continue; - } - if is_header(t) { - let sym = t.split_once(':').map(|(_, r)| r.trim()).unwrap_or(t); - frames.push((sym.to_string(), Vec::new())); - } else if let Some(last) = frames.last_mut() { - last.1.push(t.to_string()); - } - } - let mut emitted = 0usize; - let mut prev_sym: Option = None; - let mut dup_run = 0usize; - for (sym, cont) in frames { - if noise.iter().any(|p| sym.contains(p)) { - continue; - } - if prev_sym.as_deref() == Some(sym.as_str()) { - dup_run += 1; - continue; - } - if dup_run > 0 { - eprintln!(" (… {} more identical frames)", dup_run); - dup_run = 0; - } - eprintln!(" {}: {}", emitted, sym); - for c in cont.into_iter().take(2) { - eprintln!(" {}", c); - } - emitted += 1; - prev_sym = Some(sym); - if emitted >= 8 { - break; - } - } + // Coarse JS frame, consistent with `Error().stack`. Avoids + // `std::backtrace::Backtrace` so the std DWARF symbolizer is not linked. + eprintln!(" at "); } // === console.clear === From 9e37185ce321cf1378cebf9ed62666c76795262d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 11:52:52 +0200 Subject: [PATCH 09/13] =?UTF-8?q?feat:=20devirtualize=20node=20submodule?= =?UTF-8?q?=20thunk=20table=20(fs/promises=20etc.)=20=E2=80=94=20devirt=20?= =?UTF-8?q?phase=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The static SUBMODULES array named every submodule's thunks (fs/promises, stream/web, timers/promises, readline/promises, ...), and find_submodule iterated it — so the always-linked native-module property / getBuiltinModule paths pinned all 373 thunks (fs implementation = ~250KB) into every binary. Split into per-submodule statics + SUBMOD_REGISTRY (mirrors the native-module registry): find_submodule does a registry lookup; js_node_submod_install_() is the sole static ref to each spec; codegen emits it at all 6 submodule-resolution sites (namespace import, await-import, namespace.member, named export-as-function ×2, multi-path). Dynamic require/getBuiltinModule arm a black_box'd install-all hook run at the top of the namespace fns. Measured: hello-world __text 3,971,424 -> 3,716,980 (-254KB). Cumulative from origin/main baseline 4,667,824 -> 3,716,980 = -950,844 (-20.4%); ~5.4MB -> ~4.3MB. Correct: import {readFile} from 'node:fs/promises', fs.promises.readFile via native fs, + 9/9 regression sweep (os/path/util/process/getBuiltinModule/require/stream/ globals/fs-promises) byte-identical to node. --- .../perry-codegen/src/expr/dyn_extern_i18n.rs | 19 +- .../perry-codegen/src/expr/instance_misc1.rs | 4 + crates/perry-codegen/src/expr/property_get.rs | 4 + crates/perry-codegen/src/nm_install.rs | 37 ++++ .../src/runtime_decls/strings_part2.rs | 18 ++ .../perry-runtime/src/node_submodules/mod.rs | 189 +++++++++++++----- crates/perry-runtime/src/process.rs | 1 + 7 files changed, 223 insertions(+), 49 deletions(-) diff --git a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs index 518b20089..2a769be57 100644 --- a/crates/perry-codegen/src/expr/dyn_extern_i18n.rs +++ b/crates/perry-codegen/src/expr/dyn_extern_i18n.rs @@ -178,7 +178,11 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let key = key.to_string(); let submod_label = emit_string_literal_global(ctx, &key); let submod_len = key.len(); + let install_sym = crate::nm_install::nm_submod_install_symbol(&key); let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } let ns_val = blk.call( DOUBLE, "js_node_submodule_namespace", @@ -312,7 +316,12 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let key = key.to_string(); let submod_label = emit_string_literal_global(ctx, &key); let submod_len = key.len(); - ctx.block().call( + let install_sym = crate::nm_install::nm_submod_install_symbol(&key); + let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } + blk.call( DOUBLE, "js_node_submodule_namespace", &[(PTR, &submod_label), (I32, &submod_len.to_string())], @@ -405,11 +414,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // object instead of the namespace object itself. if let Some((submod_key, exported_name)) = ctx.import_function_node_submodule.get(name) { + let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key); let submod_label = emit_string_literal_global(ctx, submod_key); let name_label = emit_string_literal_global(ctx, exported_name); let submod_len = submod_key.len(); let name_len = exported_name.len(); let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } return Ok(blk.call( DOUBLE, "js_node_submodule_export_as_function", @@ -524,7 +537,11 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { if let Some(submod_key) = ctx.namespace_node_submodules.get(name) { let submod_label = emit_string_literal_global(ctx, submod_key); let submod_len = submod_key.len(); + let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key); let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } return Ok(blk.call( DOUBLE, "js_node_submodule_namespace", diff --git a/crates/perry-codegen/src/expr/instance_misc1.rs b/crates/perry-codegen/src/expr/instance_misc1.rs index 732369882..9bd2d5e46 100644 --- a/crates/perry-codegen/src/expr/instance_misc1.rs +++ b/crates/perry-codegen/src/expr/instance_misc1.rs @@ -305,11 +305,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { if submod_key == "diagnostics_channel" && matches!(exported_name.as_str(), "Channel" | "BoundedChannel") { + let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key); let submod_label = emit_string_literal_global(ctx, submod_key); let name_label = emit_string_literal_global(ctx, exported_name); let submod_len = submod_key.len(); let name_len = exported_name.len(); let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } let ty_v = blk.call( DOUBLE, "js_node_submodule_export_as_function", diff --git a/crates/perry-codegen/src/expr/property_get.rs b/crates/perry-codegen/src/expr/property_get.rs index 55f7b5ff5..9913e2126 100644 --- a/crates/perry-codegen/src/expr/property_get.rs +++ b/crates/perry-codegen/src/expr/property_get.rs @@ -1193,11 +1193,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // branch still wins because class names get // registered into both maps. if let Some(submod_key) = ctx.namespace_node_submodules.get(name) { + let install_sym = crate::nm_install::nm_submod_install_symbol(submod_key); let submod_label = emit_string_literal_global(ctx, submod_key); let name_label = emit_string_literal_global(ctx, property); let submod_len = submod_key.len(); let name_len = property.len(); let blk = ctx.block(); + if let Some(s) = install_sym { + blk.call_void(s, &[]); + } return Ok(blk.call( DOUBLE, "js_node_submodule_namespace_member", diff --git a/crates/perry-codegen/src/nm_install.rs b/crates/perry-codegen/src/nm_install.rs index dbd94d471..7342fb2a5 100644 --- a/crates/perry-codegen/src/nm_install.rs +++ b/crates/perry-codegen/src/nm_install.rs @@ -92,3 +92,40 @@ pub(crate) const NM_INSTALL_SYMBOLS: &[&str] = &[ "js_nm_install_zlib", "js_nm_install_all", ]; + +/// Submodule (`node:fs/promises`, `node:stream/web`, …) dispatch-install symbol +/// for a sentinel submodule key, or `None` if unknown. Mirrors perry-runtime +/// `submod_index`. Emitted at `js_node_submodule_namespace` sites so a submodule's +/// thunks (fs/promises etc.) are referenced only when it is imported. +pub(crate) fn nm_submod_install_symbol(key: &str) -> Option<&'static str> { + match key { + "vm" => Some("js_node_submod_install_vm"), + "timers" => Some("js_node_submod_install_timers"), + "timers_promises" => Some("js_node_submod_install_timers_promises"), + "fs_promises" => Some("js_node_submod_install_fs_promises"), + "readline_promises" => Some("js_node_submod_install_readline_promises"), + "stream_promises" => Some("js_node_submod_install_stream_promises"), + "stream_consumers" => Some("js_node_submod_install_stream_consumers"), + "stream_web" => Some("js_node_submod_install_stream_web"), + "hono_jsx_server" => Some("js_node_submod_install_hono_jsx_server"), + "hono_jsx_streaming" => Some("js_node_submod_install_hono_jsx_streaming"), + "sys" => Some("js_node_submod_install_sys"), + "diagnostics_channel" => Some("js_node_submod_install_diagnostics_channel"), + "trace_events" => Some("js_node_submod_install_trace_events"), + "test" => Some("js_node_submod_install_test"), + "test_reporters" => Some("js_node_submod_install_test_reporters"), + _ => None, + } +} + +pub(crate) const NM_SUBMOD_INSTALL_SYMBOLS: &[&str] = &[ + "js_node_submod_install_vm", "js_node_submod_install_timers", + "js_node_submod_install_timers_promises", "js_node_submod_install_fs_promises", + "js_node_submod_install_readline_promises", "js_node_submod_install_stream_promises", + "js_node_submod_install_stream_consumers", "js_node_submod_install_stream_web", + "js_node_submod_install_hono_jsx_server", "js_node_submod_install_hono_jsx_streaming", + "js_node_submod_install_sys", "js_node_submod_install_diagnostics_channel", + "js_node_submod_install_trace_events", "js_node_submod_install_test", + "js_node_submod_install_test_reporters", + "js_node_submod_install_all", "js_node_submod_enable_install_all", +]; diff --git a/crates/perry-codegen/src/runtime_decls/strings_part2.rs b/crates/perry-codegen/src/runtime_decls/strings_part2.rs index b9d072edd..b87d2393f 100644 --- a/crates/perry-codegen/src/runtime_decls/strings_part2.rs +++ b/crates/perry-codegen/src/runtime_decls/strings_part2.rs @@ -185,6 +185,24 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) { // a NaN-boxed ObjectHeader pointer whose fields are the function // singletons emitted by `js_node_submodule_export_as_function`. module.declare_function("js_node_submodule_namespace", DOUBLE, &[PTR, I32]); + // Submodule devirt installs (devirt phase 3) + module.declare_function("js_node_submod_install_vm", VOID, &[]); + module.declare_function("js_node_submod_install_timers", VOID, &[]); + module.declare_function("js_node_submod_install_timers_promises", VOID, &[]); + module.declare_function("js_node_submod_install_fs_promises", VOID, &[]); + module.declare_function("js_node_submod_install_readline_promises", VOID, &[]); + module.declare_function("js_node_submod_install_stream_promises", VOID, &[]); + module.declare_function("js_node_submod_install_stream_consumers", VOID, &[]); + module.declare_function("js_node_submod_install_stream_web", VOID, &[]); + module.declare_function("js_node_submod_install_hono_jsx_server", VOID, &[]); + module.declare_function("js_node_submod_install_hono_jsx_streaming", VOID, &[]); + module.declare_function("js_node_submod_install_sys", VOID, &[]); + module.declare_function("js_node_submod_install_diagnostics_channel", VOID, &[]); + module.declare_function("js_node_submod_install_trace_events", VOID, &[]); + module.declare_function("js_node_submod_install_test", VOID, &[]); + module.declare_function("js_node_submod_install_test_reporters", VOID, &[]); + module.declare_function("js_node_submod_install_all", VOID, &[]); + module.declare_function("js_node_submod_enable_install_all", VOID, &[]); // Issue #692: stub for default-imported callables from unresolved modules — // returns NaN-boxed undefined and prints a one-shot diagnostic, so the // program links instead of failing with `undefined reference to 'default'`. diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 91c882899..0d624ae18 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -194,15 +194,16 @@ extern "C" fn thunk_vm_create_context(_closure: *const ClosureHeader, sandbox: f // ----- submodule table ----- -const SUBMODULES: &[SubmoduleSpec] = &[ - SubmoduleSpec { +// ----- per-submodule specs (devirt) ----- +static SUBMOD_VM: SubmoduleSpec = SubmoduleSpec { key: "vm", exports: &[ExportSpec { name: "createContext", thunk: ExportThunk::Fn1(thunk_vm_create_context), }], - }, - SubmoduleSpec { + }; + +static SUBMOD_TIMERS: SubmoduleSpec = SubmoduleSpec { // node:timers namespace object (`import * as timers`). Named imports // bypass this (compile.rs) to keep the global fast-path. (#1213) key: "timers", @@ -232,8 +233,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(timers_ns_clear_immediate), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_TIMERS_PROMISES: SubmoduleSpec = SubmoduleSpec { key: "timers_promises", exports: &[ ExportSpec { @@ -253,8 +255,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(timers_promises_scheduler), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_FS_PROMISES: SubmoduleSpec = SubmoduleSpec { key: "fs_promises", exports: &[ ExportSpec { @@ -390,8 +393,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_fs_promises_constants), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_READLINE_PROMISES: SubmoduleSpec = SubmoduleSpec { key: "readline_promises", exports: &[ ExportSpec { @@ -407,8 +411,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn2(thunk_readline_Readline), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_STREAM_PROMISES: SubmoduleSpec = SubmoduleSpec { key: "stream_promises", exports: &[ ExportSpec { @@ -420,8 +425,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn2(thunk_streamP_finished), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_STREAM_CONSUMERS: SubmoduleSpec = SubmoduleSpec { key: "stream_consumers", exports: &[ ExportSpec { @@ -449,12 +455,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_consumers_blob), }, ], - }, - // #1545: node:stream/web exports the full WHATWG Web Streams class set. - // Every entry maps to the same throwing thunk — its sole purpose is to - // give each name `typeof === "function"` and a namespace slot; real - // construction goes through codegen's builtin `new` dispatch. - SubmoduleSpec { + }; + +static SUBMOD_STREAM_WEB: SubmoduleSpec = SubmoduleSpec { key: "stream_web", exports: &[ ExportSpec { @@ -526,11 +529,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_stream_web_ctor), }, ], - }, - // #1671: hono/jsx/server — the JSX runtime helpers. `jsx`/`jsxs` forward - // to the built-in `js_jsx` renderer; `Fragment` renders its children; - // `JSXNode` is an exposed stub (Perry boxes nodes internally). - SubmoduleSpec { + }; + +static SUBMOD_HONO_JSX_SERVER: SubmoduleSpec = SubmoduleSpec { key: "hono_jsx_server", exports: &[ ExportSpec { @@ -550,11 +551,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_hono_jsxnode), }, ], - }, - // #1671: hono/jsx/streaming — server-side streaming helpers. - // `renderToReadableStream` renders eagerly to a single-chunk ReadableStream; - // `Suspense` renders its children (Perry has no streaming-suspension point). - SubmoduleSpec { + }; + +static SUBMOD_HONO_JSX_STREAMING: SubmoduleSpec = SubmoduleSpec { key: "hono_jsx_streaming", exports: &[ ExportSpec { @@ -566,8 +565,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_hono_suspense), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_SYS: SubmoduleSpec = SubmoduleSpec { key: "sys", exports: &[ ExportSpec { @@ -599,13 +599,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_sys_isArray), }, ], - }, - // #906 follow-up: pino reads `tracingChannel('pino_asJson')` at - // module init time. The thunks here return useful stub values - // (an object with `hasSubscribers: false`) instead of throwing, - // so pino's "no subscribers → fast path" branch is taken and the - // tracing machinery never enters. - SubmoduleSpec { + }; + +static SUBMOD_DIAGNOSTICS_CHANNEL: SubmoduleSpec = SubmoduleSpec { key: "diagnostics_channel", exports: &[ ExportSpec { @@ -641,8 +637,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_TRACE_EVENTS: SubmoduleSpec = SubmoduleSpec { key: "trace_events", exports: &[ ExportSpec { @@ -654,8 +651,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_trace_events_getEnabledCategories), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_TEST: SubmoduleSpec = SubmoduleSpec { key: "test", exports: &[ ExportSpec { @@ -720,8 +718,9 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_test_run), }, ], - }, - SubmoduleSpec { + }; + +static SUBMOD_TEST_REPORTERS: SubmoduleSpec = SubmoduleSpec { key: "test_reporters", exports: &[ ExportSpec { @@ -745,12 +744,101 @@ const SUBMODULES: &[SubmoduleSpec] = &[ thunk: ExportThunk::Fn1(thunk_reporter_lcov), }, ], - }, -]; + }; +// ----- submodule registry (devirt) ----- +use std::sync::atomic::AtomicPtr; +#[derive(Copy, Clone)] +#[repr(usize)] +enum SubmodBucket { Vm, Timers, TimersPromises, FsPromises, ReadlinePromises, StreamPromises, StreamConsumers, StreamWeb, HonoJsxServer, HonoJsxStreaming, Sys, DiagnosticsChannel, TraceEvents, Test, TestReporters, } +const SUBMOD_COUNT: usize = 15; +static SUBMOD_REGISTRY: [AtomicPtr; SUBMOD_COUNT] = + [const { AtomicPtr::new(std::ptr::null_mut()) }; SUBMOD_COUNT]; +fn submod_index(key: &str) -> Option { + match key { + "vm" => Some(SubmodBucket::Vm), + "timers" => Some(SubmodBucket::Timers), + "timers_promises" => Some(SubmodBucket::TimersPromises), + "fs_promises" => Some(SubmodBucket::FsPromises), + "readline_promises" => Some(SubmodBucket::ReadlinePromises), + "stream_promises" => Some(SubmodBucket::StreamPromises), + "stream_consumers" => Some(SubmodBucket::StreamConsumers), + "stream_web" => Some(SubmodBucket::StreamWeb), + "hono_jsx_server" => Some(SubmodBucket::HonoJsxServer), + "hono_jsx_streaming" => Some(SubmodBucket::HonoJsxStreaming), + "sys" => Some(SubmodBucket::Sys), + "diagnostics_channel" => Some(SubmodBucket::DiagnosticsChannel), + "trace_events" => Some(SubmodBucket::TraceEvents), + "test" => Some(SubmodBucket::Test), + "test_reporters" => Some(SubmodBucket::TestReporters), + _ => None, + } +} fn find_submodule(key: &str) -> Option<&'static SubmoduleSpec> { - SUBMODULES.iter().find(|s| s.key == key) + let b = submod_index(key)?; + let p = SUBMOD_REGISTRY[b as usize].load(Ordering::Relaxed); + if p.is_null() { None } else { Some(unsafe { &*(p as *const SubmoduleSpec) }) } } +#[no_mangle] +pub extern "C" fn js_node_submod_install_vm() { SUBMOD_REGISTRY[SubmodBucket::Vm as usize].store(&SUBMOD_VM as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_timers() { SUBMOD_REGISTRY[SubmodBucket::Timers as usize].store(&SUBMOD_TIMERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_timers_promises() { SUBMOD_REGISTRY[SubmodBucket::TimersPromises as usize].store(&SUBMOD_TIMERS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_fs_promises() { SUBMOD_REGISTRY[SubmodBucket::FsPromises as usize].store(&SUBMOD_FS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_readline_promises() { SUBMOD_REGISTRY[SubmodBucket::ReadlinePromises as usize].store(&SUBMOD_READLINE_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_stream_promises() { SUBMOD_REGISTRY[SubmodBucket::StreamPromises as usize].store(&SUBMOD_STREAM_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_stream_consumers() { SUBMOD_REGISTRY[SubmodBucket::StreamConsumers as usize].store(&SUBMOD_STREAM_CONSUMERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_stream_web() { SUBMOD_REGISTRY[SubmodBucket::StreamWeb as usize].store(&SUBMOD_STREAM_WEB as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_hono_jsx_server() { SUBMOD_REGISTRY[SubmodBucket::HonoJsxServer as usize].store(&SUBMOD_HONO_JSX_SERVER as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_hono_jsx_streaming() { SUBMOD_REGISTRY[SubmodBucket::HonoJsxStreaming as usize].store(&SUBMOD_HONO_JSX_STREAMING as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_sys() { SUBMOD_REGISTRY[SubmodBucket::Sys as usize].store(&SUBMOD_SYS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_diagnostics_channel() { SUBMOD_REGISTRY[SubmodBucket::DiagnosticsChannel as usize].store(&SUBMOD_DIAGNOSTICS_CHANNEL as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_trace_events() { SUBMOD_REGISTRY[SubmodBucket::TraceEvents as usize].store(&SUBMOD_TRACE_EVENTS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_test() { SUBMOD_REGISTRY[SubmodBucket::Test as usize].store(&SUBMOD_TEST as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_test_reporters() { SUBMOD_REGISTRY[SubmodBucket::TestReporters as usize].store(&SUBMOD_TEST_REPORTERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +#[no_mangle] +pub extern "C" fn js_node_submod_install_all() { + js_node_submod_install_vm(); + js_node_submod_install_timers(); + js_node_submod_install_timers_promises(); + js_node_submod_install_fs_promises(); + js_node_submod_install_readline_promises(); + js_node_submod_install_stream_promises(); + js_node_submod_install_stream_consumers(); + js_node_submod_install_stream_web(); + js_node_submod_install_hono_jsx_server(); + js_node_submod_install_hono_jsx_streaming(); + js_node_submod_install_sys(); + js_node_submod_install_diagnostics_channel(); + js_node_submod_install_trace_events(); + js_node_submod_install_test(); + js_node_submod_install_test_reporters(); +} +static SUBMOD_INSTALL_ALL_HOOK: AtomicPtr<()> = AtomicPtr::new(std::ptr::null_mut()); +#[no_mangle] +pub extern "C" fn js_node_submod_enable_install_all() { + let f = std::hint::black_box(js_node_submod_install_all as extern "C" fn()); + SUBMOD_INSTALL_ALL_HOOK.store(f as *mut (), Ordering::Relaxed); +} +pub(crate) fn run_submod_install_all_hook() { + let p = SUBMOD_INSTALL_ALL_HOOK.load(Ordering::Relaxed); + if !p.is_null() { unsafe { std::mem::transmute::<*mut (), extern "C" fn()>(p)() }; } +} + + fn find_export(submod: &SubmoduleSpec, name: &str) -> Option<&'static ExportSpec> { submod.exports.iter().find(|e| e.name == name) @@ -1349,6 +1437,10 @@ pub unsafe extern "C" fn js_node_submodule_namespace_member( name_ptr: *const u8, name_len: u32, ) -> f64 { + // Devirt: dynamically-resolved submodules (require / getBuiltinModule / a + // `fs.promises`-style property access) had no codegen install — run the + // install-all hook so the registry is populated. Null/no-op unless armed. + run_submod_install_all_hook(); let submod_bytes = std::slice::from_raw_parts(submod_key_ptr, submod_key_len as usize); let name_bytes = std::slice::from_raw_parts(name_ptr, name_len as usize); let submod_key = match std::str::from_utf8(submod_bytes) { @@ -1430,6 +1522,7 @@ pub unsafe extern "C" fn js_node_submodule_namespace( submod_key_ptr: *const u8, submod_key_len: u32, ) -> f64 { + run_submod_install_all_hook(); let submod_bytes = std::slice::from_raw_parts(submod_key_ptr, submod_key_len as usize); let submod_key = match std::str::from_utf8(submod_bytes) { Ok(s) => s, diff --git a/crates/perry-runtime/src/process.rs b/crates/perry-runtime/src/process.rs index e90f890a7..95442b3a8 100644 --- a/crates/perry-runtime/src/process.rs +++ b/crates/perry-runtime/src/process.rs @@ -2556,6 +2556,7 @@ fn find_nearest_package_json(specifier: &str, base: &str) -> Option { #[no_mangle] pub extern "C" fn js_process_get_builtin_module_devirt(id: f64) -> f64 { crate::object::js_nm_enable_install_all(); + crate::node_submodules::js_node_submod_enable_install_all(); js_process_get_builtin_module(id) } From eae4723f53c6539d5950b9457b56c56aa56ca75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 12:05:06 +0200 Subject: [PATCH 10/13] docs(nm-devirt): phase 3 done + panic-strip findings --- NM_DEVIRT_PLAN.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/NM_DEVIRT_PLAN.md b/NM_DEVIRT_PLAN.md index e2b90d4a9..522e0062e 100644 --- a/NM_DEVIRT_PLAN.md +++ b/NM_DEVIRT_PLAN.md @@ -78,3 +78,32 @@ sound graceful degradation (install_all), semantics never change with a build fl ## Generators (in /tmp, re-runnable from git HEAD) /tmp/nm_generate.py (dispatch file), /tmp/nm_gen_registry.py (registry). Both read pristine source via `git show HEAD:...` so re-running is idempotent. + +## Phase 3 (submodule devirt) — DONE (commit 9e37185ce) +SUBMODULES static table → per-submodule statics + SUBMOD_REGISTRY; find_submodule via +registry; js_node_submod_install_() emitted at all 6 codegen submodule-resolution +sites; black_box'd install-all hook for dynamic require/getBuiltinModule. hello-world +__text 3,971,424 → 3,716,980 (−254KB). CUMULATIVE baseline 4,667,824 → 3,716,980 = +−950,844 (−20.4%), ~5.4MB → ~4.3MB. 9/9 correctness sweep + fs/promises (named import +and fs.promises via native) byte-identical to node. + +## console.trace — DONE (commit a5c14dbbf) +Coarse `at ` frame instead of std::backtrace::force_capture (consistent with +Error.stack; prereq for any future panic-symbolizer strip). No size change alone (the +143KB gimli is pulled by std's panic runtime, not console.trace). + +## Panic-symbolizer strip (~220KB) — ATTEMPTED, REVERTED (toolchain-fragile) +build-std + panic_immediate_abort to drop std's default panic hook + DWARF symbolizer. +Blockers found on current nightly: + 1. panic_immediate_abort is now a real STRATEGY: needs `-Cpanic=immediate-abort` + (+ -Zunstable-options) + -Zbuild-std, NOT the old `-Zbuild-std-features=panic_immediate_abort`. + 2. Native build (host==target) → host build-scripts/proc-macros use the PRECOMPILED host + core (default panic), but the rustflag forces immediate-abort on them → "core compiled + with incompatible panic strategy" (proc-macro2 build script fails). + 3. Fix requires explicit `--target ` to separate host (precompiled) from + target (build-std immediate-abort) — which then breaks the auto-opt output-path + resolution (libs move to target//release/). 3 fragile, nightly-version-specific + pieces → defer to a focused effort with proper --target + path handling. +The PERRY_MIN_SIZE=1 opt-in wiring was reverted (kept the tree clean). console.trace prereq +stays. Other no-tradeoff levers remain: json (47KB, event-loop pump), js_native_call_method +monolith (34KB devirt), feature-gating url/intl/bigint (160KB, other branch's mechanism). From 513323e40cc36cbebf63bb12ba578b3d89e185c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 13:23:48 +0200 Subject: [PATCH 11/13] fix(test): rustfmt + lazy-install registry fallback for unit tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rustfmt the generated devirt code (nm_install.rs, native_module_dispatch/registry, class_registry, node_submodules) — CI lint (fmt) was red. - Unit tests call dispatch/ctor/submodule helpers directly, without the codegen js_nm_install_() that precedes use in real programs, so the registries were empty (SUBMODULES array also removed). Add a #[cfg(test)]-only lazy install-all fallback in nm_dispatch_lookup/nm_ctor_lookup/find_submodule so tests exercise the real registry path; replace the removed SUBMODULES iteration with a test-only ALL_SUBMODULE_SPECS list. Production builds are unchanged (#[cfg(test)] excluded). --- crates/perry-codegen/src/nm_install.rs | 34 +- .../perry-runtime/src/node_submodules/mod.rs | 1209 +++++++------ .../src/node_submodules/tests.rs | 2 +- .../src/object/class_registry.rs | 4 +- .../src/object/native_module_dispatch.rs | 1542 ++++++++++++++--- .../src/object/native_module_registry.rs | 356 +++- 6 files changed, 2332 insertions(+), 815 deletions(-) diff --git a/crates/perry-codegen/src/nm_install.rs b/crates/perry-codegen/src/nm_install.rs index 7342fb2a5..a97bfcd73 100644 --- a/crates/perry-codegen/src/nm_install.rs +++ b/crates/perry-codegen/src/nm_install.rs @@ -16,7 +16,8 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { "child_process" => Some("js_nm_install_child_process"), "cluster" => Some("js_nm_install_cluster"), "console" => Some("js_nm_install_console"), - "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" | "crypto.webcrypto" => Some("js_nm_install_crypto"), + "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" + | "crypto.webcrypto" => Some("js_nm_install_crypto"), "dgram" => Some("js_nm_install_dgram"), "dns" | "dns/promises" => Some("js_nm_install_dns"), "domain" => Some("js_nm_install_domain"), @@ -28,7 +29,9 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { "net" => Some("js_nm_install_net"), "os" => Some("js_nm_install_os"), "path" | "path.posix" | "path.win32" => Some("js_nm_install_path"), - "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => Some("js_nm_install_perf"), + "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => { + Some("js_nm_install_perf") + } "process" => Some("js_nm_install_process"), "punycode" | "punycode.ucs2" => Some("js_nm_install_punycode"), "querystring" => Some("js_nm_install_querystring"), @@ -42,7 +45,8 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { "tty" => Some("js_nm_install_tty"), "url" => Some("js_nm_install_url"), "util" | "util.types" | "util/types" => Some("js_nm_install_util"), - "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" | "v8.startupSnapshot" => Some("js_nm_install_v8"), + "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" + | "v8.startupSnapshot" => Some("js_nm_install_v8"), "vm" => Some("js_nm_install_vm"), "wasi" => Some("js_nm_install_wasi"), "zlib" => Some("js_nm_install_zlib"), @@ -119,13 +123,21 @@ pub(crate) fn nm_submod_install_symbol(key: &str) -> Option<&'static str> { } pub(crate) const NM_SUBMOD_INSTALL_SYMBOLS: &[&str] = &[ - "js_node_submod_install_vm", "js_node_submod_install_timers", - "js_node_submod_install_timers_promises", "js_node_submod_install_fs_promises", - "js_node_submod_install_readline_promises", "js_node_submod_install_stream_promises", - "js_node_submod_install_stream_consumers", "js_node_submod_install_stream_web", - "js_node_submod_install_hono_jsx_server", "js_node_submod_install_hono_jsx_streaming", - "js_node_submod_install_sys", "js_node_submod_install_diagnostics_channel", - "js_node_submod_install_trace_events", "js_node_submod_install_test", + "js_node_submod_install_vm", + "js_node_submod_install_timers", + "js_node_submod_install_timers_promises", + "js_node_submod_install_fs_promises", + "js_node_submod_install_readline_promises", + "js_node_submod_install_stream_promises", + "js_node_submod_install_stream_consumers", + "js_node_submod_install_stream_web", + "js_node_submod_install_hono_jsx_server", + "js_node_submod_install_hono_jsx_streaming", + "js_node_submod_install_sys", + "js_node_submod_install_diagnostics_channel", + "js_node_submod_install_trace_events", + "js_node_submod_install_test", "js_node_submod_install_test_reporters", - "js_node_submod_install_all", "js_node_submod_enable_install_all", + "js_node_submod_install_all", + "js_node_submod_enable_install_all", ]; diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 0d624ae18..7dba9d92e 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -196,561 +196,577 @@ extern "C" fn thunk_vm_create_context(_closure: *const ClosureHeader, sandbox: f // ----- per-submodule specs (devirt) ----- static SUBMOD_VM: SubmoduleSpec = SubmoduleSpec { - key: "vm", - exports: &[ExportSpec { - name: "createContext", - thunk: ExportThunk::Fn1(thunk_vm_create_context), - }], - }; + key: "vm", + exports: &[ExportSpec { + name: "createContext", + thunk: ExportThunk::Fn1(thunk_vm_create_context), + }], +}; static SUBMOD_TIMERS: SubmoduleSpec = SubmoduleSpec { - // node:timers namespace object (`import * as timers`). Named imports - // bypass this (compile.rs) to keep the global fast-path. (#1213) - key: "timers", - exports: &[ - ExportSpec { - name: "setTimeout", - thunk: ExportThunk::Fn3(timers_ns_set_timeout), - }, - ExportSpec { - name: "setInterval", - thunk: ExportThunk::Fn3(timers_ns_set_interval), - }, - ExportSpec { - name: "setImmediate", - thunk: ExportThunk::Fn2(timers_ns_set_immediate), - }, - ExportSpec { - name: "clearTimeout", - thunk: ExportThunk::Fn1(timers_ns_clear_timeout), - }, - ExportSpec { - name: "clearInterval", - thunk: ExportThunk::Fn1(timers_ns_clear_interval), - }, - ExportSpec { - name: "clearImmediate", - thunk: ExportThunk::Fn1(timers_ns_clear_immediate), - }, - ], - }; + // node:timers namespace object (`import * as timers`). Named imports + // bypass this (compile.rs) to keep the global fast-path. (#1213) + key: "timers", + exports: &[ + ExportSpec { + name: "setTimeout", + thunk: ExportThunk::Fn3(timers_ns_set_timeout), + }, + ExportSpec { + name: "setInterval", + thunk: ExportThunk::Fn3(timers_ns_set_interval), + }, + ExportSpec { + name: "setImmediate", + thunk: ExportThunk::Fn2(timers_ns_set_immediate), + }, + ExportSpec { + name: "clearTimeout", + thunk: ExportThunk::Fn1(timers_ns_clear_timeout), + }, + ExportSpec { + name: "clearInterval", + thunk: ExportThunk::Fn1(timers_ns_clear_interval), + }, + ExportSpec { + name: "clearImmediate", + thunk: ExportThunk::Fn1(timers_ns_clear_immediate), + }, + ], +}; static SUBMOD_TIMERS_PROMISES: SubmoduleSpec = SubmoduleSpec { - key: "timers_promises", - exports: &[ - ExportSpec { - name: "setTimeout", - thunk: ExportThunk::Fn3(timers_promises_set_timeout), - }, - ExportSpec { - name: "setImmediate", - thunk: ExportThunk::Fn2(timers_promises_set_immediate), - }, - ExportSpec { - name: "setInterval", - thunk: ExportThunk::Fn3(timers_promises_set_interval), - }, - ExportSpec { - name: "scheduler", - thunk: ExportThunk::Fn1(timers_promises_scheduler), - }, - ], - }; + key: "timers_promises", + exports: &[ + ExportSpec { + name: "setTimeout", + thunk: ExportThunk::Fn3(timers_promises_set_timeout), + }, + ExportSpec { + name: "setImmediate", + thunk: ExportThunk::Fn2(timers_promises_set_immediate), + }, + ExportSpec { + name: "setInterval", + thunk: ExportThunk::Fn3(timers_promises_set_interval), + }, + ExportSpec { + name: "scheduler", + thunk: ExportThunk::Fn1(timers_promises_scheduler), + }, + ], +}; static SUBMOD_FS_PROMISES: SubmoduleSpec = SubmoduleSpec { - key: "fs_promises", - exports: &[ - ExportSpec { - name: "readFile", - thunk: ExportThunk::Fn2(thunk_fs_promises_readFile), - }, - ExportSpec { - name: "open", - thunk: ExportThunk::Fn3(thunk_fs_promises_open), - }, - ExportSpec { - name: "writeFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_writeFile), - }, - ExportSpec { - name: "appendFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_appendFile), - }, - ExportSpec { - name: "chmod", - thunk: ExportThunk::Fn2(thunk_fs_promises_chmod), - }, - ExportSpec { - name: "chown", - thunk: ExportThunk::Fn3(thunk_fs_promises_chown), - }, - ExportSpec { - name: "lchown", - thunk: ExportThunk::Fn3(thunk_fs_promises_lchown), - }, - ExportSpec { - name: "lchmod", - thunk: ExportThunk::Fn2(thunk_fs_promises_lchmod), - }, - ExportSpec { - name: "mkdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdir), - }, - ExportSpec { - name: "readdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_readdir), - }, - ExportSpec { - name: "stat", - thunk: ExportThunk::Fn2(thunk_fs_promises_stat), - }, - ExportSpec { - name: "statfs", - thunk: ExportThunk::Fn2(thunk_fs_promises_statfs), - }, - ExportSpec { - name: "lstat", - thunk: ExportThunk::Fn2(thunk_fs_promises_lstat), - }, - ExportSpec { - name: "rm", - thunk: ExportThunk::Fn2(thunk_fs_promises_rm), - }, - ExportSpec { - name: "rmdir", - thunk: ExportThunk::Fn2(thunk_fs_promises_rmdir), - }, - ExportSpec { - name: "unlink", - thunk: ExportThunk::Fn1(thunk_fs_promises_unlink), - }, - ExportSpec { - name: "rename", - thunk: ExportThunk::Fn2(thunk_fs_promises_rename), - }, - ExportSpec { - name: "copyFile", - thunk: ExportThunk::Fn3(thunk_fs_promises_copyFile), - }, - ExportSpec { - name: "cp", - thunk: ExportThunk::Fn3(thunk_fs_promises_cp), - }, - ExportSpec { - name: "truncate", - thunk: ExportThunk::Fn2(thunk_fs_promises_truncate), - }, - ExportSpec { - name: "utimes", - thunk: ExportThunk::Fn3(thunk_fs_promises_utimes), - }, - ExportSpec { - name: "lutimes", - thunk: ExportThunk::Fn3(thunk_fs_promises_lutimes), - }, - ExportSpec { - name: "link", - thunk: ExportThunk::Fn2(thunk_fs_promises_link), - }, - ExportSpec { - name: "symlink", - thunk: ExportThunk::Fn3(thunk_fs_promises_symlink), - }, - ExportSpec { - name: "readlink", - thunk: ExportThunk::Fn2(thunk_fs_promises_readlink), - }, - ExportSpec { - name: "realpath", - thunk: ExportThunk::Fn2(thunk_fs_promises_realpath), - }, - ExportSpec { - name: "mkdtemp", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtemp), - }, - ExportSpec { - name: "mkdtempDisposable", - thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtempDisposable), - }, - ExportSpec { - name: "opendir", - thunk: ExportThunk::Fn1(thunk_fs_promises_opendir), - }, - ExportSpec { - name: "glob", - thunk: ExportThunk::Fn2(thunk_fs_promises_glob), - }, - ExportSpec { - name: "watch", - thunk: ExportThunk::Fn2(thunk_fs_promises_watch), - }, - ExportSpec { - name: "access", - thunk: ExportThunk::Fn2(thunk_fs_promises_access), - }, - ExportSpec { - name: "constants", - thunk: ExportThunk::Fn1(thunk_fs_promises_constants), - }, - ], - }; + key: "fs_promises", + exports: &[ + ExportSpec { + name: "readFile", + thunk: ExportThunk::Fn2(thunk_fs_promises_readFile), + }, + ExportSpec { + name: "open", + thunk: ExportThunk::Fn3(thunk_fs_promises_open), + }, + ExportSpec { + name: "writeFile", + thunk: ExportThunk::Fn3(thunk_fs_promises_writeFile), + }, + ExportSpec { + name: "appendFile", + thunk: ExportThunk::Fn3(thunk_fs_promises_appendFile), + }, + ExportSpec { + name: "chmod", + thunk: ExportThunk::Fn2(thunk_fs_promises_chmod), + }, + ExportSpec { + name: "chown", + thunk: ExportThunk::Fn3(thunk_fs_promises_chown), + }, + ExportSpec { + name: "lchown", + thunk: ExportThunk::Fn3(thunk_fs_promises_lchown), + }, + ExportSpec { + name: "lchmod", + thunk: ExportThunk::Fn2(thunk_fs_promises_lchmod), + }, + ExportSpec { + name: "mkdir", + thunk: ExportThunk::Fn2(thunk_fs_promises_mkdir), + }, + ExportSpec { + name: "readdir", + thunk: ExportThunk::Fn2(thunk_fs_promises_readdir), + }, + ExportSpec { + name: "stat", + thunk: ExportThunk::Fn2(thunk_fs_promises_stat), + }, + ExportSpec { + name: "statfs", + thunk: ExportThunk::Fn2(thunk_fs_promises_statfs), + }, + ExportSpec { + name: "lstat", + thunk: ExportThunk::Fn2(thunk_fs_promises_lstat), + }, + ExportSpec { + name: "rm", + thunk: ExportThunk::Fn2(thunk_fs_promises_rm), + }, + ExportSpec { + name: "rmdir", + thunk: ExportThunk::Fn2(thunk_fs_promises_rmdir), + }, + ExportSpec { + name: "unlink", + thunk: ExportThunk::Fn1(thunk_fs_promises_unlink), + }, + ExportSpec { + name: "rename", + thunk: ExportThunk::Fn2(thunk_fs_promises_rename), + }, + ExportSpec { + name: "copyFile", + thunk: ExportThunk::Fn3(thunk_fs_promises_copyFile), + }, + ExportSpec { + name: "cp", + thunk: ExportThunk::Fn3(thunk_fs_promises_cp), + }, + ExportSpec { + name: "truncate", + thunk: ExportThunk::Fn2(thunk_fs_promises_truncate), + }, + ExportSpec { + name: "utimes", + thunk: ExportThunk::Fn3(thunk_fs_promises_utimes), + }, + ExportSpec { + name: "lutimes", + thunk: ExportThunk::Fn3(thunk_fs_promises_lutimes), + }, + ExportSpec { + name: "link", + thunk: ExportThunk::Fn2(thunk_fs_promises_link), + }, + ExportSpec { + name: "symlink", + thunk: ExportThunk::Fn3(thunk_fs_promises_symlink), + }, + ExportSpec { + name: "readlink", + thunk: ExportThunk::Fn2(thunk_fs_promises_readlink), + }, + ExportSpec { + name: "realpath", + thunk: ExportThunk::Fn2(thunk_fs_promises_realpath), + }, + ExportSpec { + name: "mkdtemp", + thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtemp), + }, + ExportSpec { + name: "mkdtempDisposable", + thunk: ExportThunk::Fn2(thunk_fs_promises_mkdtempDisposable), + }, + ExportSpec { + name: "opendir", + thunk: ExportThunk::Fn1(thunk_fs_promises_opendir), + }, + ExportSpec { + name: "glob", + thunk: ExportThunk::Fn2(thunk_fs_promises_glob), + }, + ExportSpec { + name: "watch", + thunk: ExportThunk::Fn2(thunk_fs_promises_watch), + }, + ExportSpec { + name: "access", + thunk: ExportThunk::Fn2(thunk_fs_promises_access), + }, + ExportSpec { + name: "constants", + thunk: ExportThunk::Fn1(thunk_fs_promises_constants), + }, + ], +}; static SUBMOD_READLINE_PROMISES: SubmoduleSpec = SubmoduleSpec { - key: "readline_promises", - exports: &[ - ExportSpec { - name: "createInterface", - thunk: ExportThunk::Fn1(thunk_readline_createInterface), - }, - ExportSpec { - name: "Interface", - thunk: ExportThunk::Fn1(thunk_readline_Interface), - }, - ExportSpec { - name: "Readline", - thunk: ExportThunk::Fn2(thunk_readline_Readline), - }, - ], - }; + key: "readline_promises", + exports: &[ + ExportSpec { + name: "createInterface", + thunk: ExportThunk::Fn1(thunk_readline_createInterface), + }, + ExportSpec { + name: "Interface", + thunk: ExportThunk::Fn1(thunk_readline_Interface), + }, + ExportSpec { + name: "Readline", + thunk: ExportThunk::Fn2(thunk_readline_Readline), + }, + ], +}; static SUBMOD_STREAM_PROMISES: SubmoduleSpec = SubmoduleSpec { - key: "stream_promises", - exports: &[ - ExportSpec { - name: "pipeline", - thunk: ExportThunk::Fn3(thunk_streamP_pipeline), - }, - ExportSpec { - name: "finished", - thunk: ExportThunk::Fn2(thunk_streamP_finished), - }, - ], - }; + key: "stream_promises", + exports: &[ + ExportSpec { + name: "pipeline", + thunk: ExportThunk::Fn3(thunk_streamP_pipeline), + }, + ExportSpec { + name: "finished", + thunk: ExportThunk::Fn2(thunk_streamP_finished), + }, + ], +}; static SUBMOD_STREAM_CONSUMERS: SubmoduleSpec = SubmoduleSpec { - key: "stream_consumers", - exports: &[ - ExportSpec { - name: "text", - thunk: ExportThunk::Fn1(thunk_consumers_text), - }, - ExportSpec { - name: "json", - thunk: ExportThunk::Fn1(thunk_consumers_json), - }, - ExportSpec { - name: "buffer", - thunk: ExportThunk::Fn1(thunk_consumers_buffer), - }, - ExportSpec { - name: "arrayBuffer", - thunk: ExportThunk::Fn1(thunk_consumers_arrayBuffer), - }, - ExportSpec { - name: "bytes", - thunk: ExportThunk::Fn1(thunk_consumers_bytes), - }, - ExportSpec { - name: "blob", - thunk: ExportThunk::Fn1(thunk_consumers_blob), - }, - ], - }; + key: "stream_consumers", + exports: &[ + ExportSpec { + name: "text", + thunk: ExportThunk::Fn1(thunk_consumers_text), + }, + ExportSpec { + name: "json", + thunk: ExportThunk::Fn1(thunk_consumers_json), + }, + ExportSpec { + name: "buffer", + thunk: ExportThunk::Fn1(thunk_consumers_buffer), + }, + ExportSpec { + name: "arrayBuffer", + thunk: ExportThunk::Fn1(thunk_consumers_arrayBuffer), + }, + ExportSpec { + name: "bytes", + thunk: ExportThunk::Fn1(thunk_consumers_bytes), + }, + ExportSpec { + name: "blob", + thunk: ExportThunk::Fn1(thunk_consumers_blob), + }, + ], +}; static SUBMOD_STREAM_WEB: SubmoduleSpec = SubmoduleSpec { - key: "stream_web", - exports: &[ - ExportSpec { - name: "ReadableStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ReadableStreamDefaultReader", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ReadableStreamBYOBReader", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ReadableStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ReadableByteStreamController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ReadableStreamBYOBRequest", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "WritableStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "WritableStreamDefaultWriter", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "WritableStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "TransformStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "TransformStreamDefaultController", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "ByteLengthQueuingStrategy", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "CountQueuingStrategy", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "TextEncoderStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "TextDecoderStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "CompressionStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ExportSpec { - name: "DecompressionStream", - thunk: ExportThunk::Fn1(thunk_stream_web_ctor), - }, - ], - }; + key: "stream_web", + exports: &[ + ExportSpec { + name: "ReadableStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ReadableStreamDefaultReader", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ReadableStreamBYOBReader", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ReadableStreamDefaultController", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ReadableByteStreamController", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ReadableStreamBYOBRequest", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "WritableStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "WritableStreamDefaultWriter", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "WritableStreamDefaultController", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "TransformStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "TransformStreamDefaultController", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "ByteLengthQueuingStrategy", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "CountQueuingStrategy", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "TextEncoderStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "TextDecoderStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "CompressionStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ExportSpec { + name: "DecompressionStream", + thunk: ExportThunk::Fn1(thunk_stream_web_ctor), + }, + ], +}; static SUBMOD_HONO_JSX_SERVER: SubmoduleSpec = SubmoduleSpec { - key: "hono_jsx_server", - exports: &[ - ExportSpec { - name: "jsx", - thunk: ExportThunk::Fn2(thunk_hono_jsx), - }, - ExportSpec { - name: "jsxs", - thunk: ExportThunk::Fn2(thunk_hono_jsxs), - }, - ExportSpec { - name: "Fragment", - thunk: ExportThunk::Fn1(thunk_hono_fragment), - }, - ExportSpec { - name: "JSXNode", - thunk: ExportThunk::Fn1(thunk_hono_jsxnode), - }, - ], - }; + key: "hono_jsx_server", + exports: &[ + ExportSpec { + name: "jsx", + thunk: ExportThunk::Fn2(thunk_hono_jsx), + }, + ExportSpec { + name: "jsxs", + thunk: ExportThunk::Fn2(thunk_hono_jsxs), + }, + ExportSpec { + name: "Fragment", + thunk: ExportThunk::Fn1(thunk_hono_fragment), + }, + ExportSpec { + name: "JSXNode", + thunk: ExportThunk::Fn1(thunk_hono_jsxnode), + }, + ], +}; static SUBMOD_HONO_JSX_STREAMING: SubmoduleSpec = SubmoduleSpec { - key: "hono_jsx_streaming", - exports: &[ - ExportSpec { - name: "renderToReadableStream", - thunk: ExportThunk::Fn2(thunk_hono_render_to_readable_stream), - }, - ExportSpec { - name: "Suspense", - thunk: ExportThunk::Fn1(thunk_hono_suspense), - }, - ], - }; + key: "hono_jsx_streaming", + exports: &[ + ExportSpec { + name: "renderToReadableStream", + thunk: ExportThunk::Fn2(thunk_hono_render_to_readable_stream), + }, + ExportSpec { + name: "Suspense", + thunk: ExportThunk::Fn1(thunk_hono_suspense), + }, + ], +}; static SUBMOD_SYS: SubmoduleSpec = SubmoduleSpec { - key: "sys", - exports: &[ - ExportSpec { - name: "format", - thunk: ExportThunk::Fn1(thunk_sys_format), - }, - ExportSpec { - name: "inspect", - thunk: ExportThunk::Fn1(thunk_sys_inspect), - }, - ExportSpec { - name: "debuglog", - thunk: ExportThunk::Fn1(thunk_sys_debuglog), - }, - ExportSpec { - name: "deprecate", - thunk: ExportThunk::Fn1(thunk_sys_deprecate), - }, - ExportSpec { - name: "promisify", - thunk: ExportThunk::Fn1(thunk_sys_promisify), - }, - ExportSpec { - name: "callbackify", - thunk: ExportThunk::Fn1(thunk_sys_callbackify), - }, - ExportSpec { - name: "isArray", - thunk: ExportThunk::Fn1(thunk_sys_isArray), - }, - ], - }; + key: "sys", + exports: &[ + ExportSpec { + name: "format", + thunk: ExportThunk::Fn1(thunk_sys_format), + }, + ExportSpec { + name: "inspect", + thunk: ExportThunk::Fn1(thunk_sys_inspect), + }, + ExportSpec { + name: "debuglog", + thunk: ExportThunk::Fn1(thunk_sys_debuglog), + }, + ExportSpec { + name: "deprecate", + thunk: ExportThunk::Fn1(thunk_sys_deprecate), + }, + ExportSpec { + name: "promisify", + thunk: ExportThunk::Fn1(thunk_sys_promisify), + }, + ExportSpec { + name: "callbackify", + thunk: ExportThunk::Fn1(thunk_sys_callbackify), + }, + ExportSpec { + name: "isArray", + thunk: ExportThunk::Fn1(thunk_sys_isArray), + }, + ], +}; static SUBMOD_DIAGNOSTICS_CHANNEL: SubmoduleSpec = SubmoduleSpec { - key: "diagnostics_channel", - exports: &[ - ExportSpec { - name: "tracingChannel", - thunk: ExportThunk::Fn1(thunk_diag_tracing_channel), - }, - ExportSpec { - name: "boundedChannel", - thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), - }, - ExportSpec { - name: "channel", - thunk: ExportThunk::Fn1(thunk_diag_channel), - }, - ExportSpec { - name: "subscribe", - thunk: ExportThunk::Fn2(thunk_diag_subscribe), - }, - ExportSpec { - name: "unsubscribe", - thunk: ExportThunk::Fn2(thunk_diag_unsubscribe), - }, - ExportSpec { - name: "hasSubscribers", - thunk: ExportThunk::Fn1(thunk_diag_has_subscribers), - }, - ExportSpec { - name: "Channel", - thunk: ExportThunk::Fn1(thunk_diag_noop), - }, - ExportSpec { - name: "BoundedChannel", - thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), - }, - ], - }; + key: "diagnostics_channel", + exports: &[ + ExportSpec { + name: "tracingChannel", + thunk: ExportThunk::Fn1(thunk_diag_tracing_channel), + }, + ExportSpec { + name: "boundedChannel", + thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), + }, + ExportSpec { + name: "channel", + thunk: ExportThunk::Fn1(thunk_diag_channel), + }, + ExportSpec { + name: "subscribe", + thunk: ExportThunk::Fn2(thunk_diag_subscribe), + }, + ExportSpec { + name: "unsubscribe", + thunk: ExportThunk::Fn2(thunk_diag_unsubscribe), + }, + ExportSpec { + name: "hasSubscribers", + thunk: ExportThunk::Fn1(thunk_diag_has_subscribers), + }, + ExportSpec { + name: "Channel", + thunk: ExportThunk::Fn1(thunk_diag_noop), + }, + ExportSpec { + name: "BoundedChannel", + thunk: ExportThunk::Fn1(thunk_diag_bounded_channel), + }, + ], +}; static SUBMOD_TRACE_EVENTS: SubmoduleSpec = SubmoduleSpec { - key: "trace_events", - exports: &[ - ExportSpec { - name: "createTracing", - thunk: ExportThunk::Fn1(thunk_trace_events_createTracing), - }, - ExportSpec { - name: "getEnabledCategories", - thunk: ExportThunk::Fn1(thunk_trace_events_getEnabledCategories), - }, - ], - }; + key: "trace_events", + exports: &[ + ExportSpec { + name: "createTracing", + thunk: ExportThunk::Fn1(thunk_trace_events_createTracing), + }, + ExportSpec { + name: "getEnabledCategories", + thunk: ExportThunk::Fn1(thunk_trace_events_getEnabledCategories), + }, + ], +}; static SUBMOD_TEST: SubmoduleSpec = SubmoduleSpec { - key: "test", - exports: &[ - ExportSpec { - name: "default", - thunk: ExportThunk::Fn3(thunk_test), - }, - ExportSpec { - name: "test", - thunk: ExportThunk::Fn3(thunk_test), - }, - ExportSpec { - name: "skip", - thunk: ExportThunk::Fn3(thunk_test_skip), - }, - ExportSpec { - name: "todo", - thunk: ExportThunk::Fn3(thunk_test_todo), - }, - ExportSpec { - name: "only", - thunk: ExportThunk::Fn3(thunk_test_only), - }, - ExportSpec { - name: "suite", - thunk: ExportThunk::Fn3(thunk_test), - }, - ExportSpec { - name: "describe", - thunk: ExportThunk::Fn3(thunk_test), - }, - ExportSpec { - name: "it", - thunk: ExportThunk::Fn3(thunk_test), - }, - ExportSpec { - name: "before", - thunk: ExportThunk::Fn1(thunk_test_hook), - }, - ExportSpec { - name: "after", - thunk: ExportThunk::Fn1(thunk_test_hook), - }, - ExportSpec { - name: "beforeEach", - thunk: ExportThunk::Fn1(thunk_test_hook), - }, - ExportSpec { - name: "afterEach", - thunk: ExportThunk::Fn1(thunk_test_hook), - }, - ExportSpec { - name: "run", - thunk: ExportThunk::Fn1(thunk_test_run), - }, - // Object-valued exports are handled by `special_export_value`. - ExportSpec { - name: "mock", - thunk: ExportThunk::Fn1(thunk_test_run), - }, - ExportSpec { - name: "snapshot", - thunk: ExportThunk::Fn1(thunk_test_run), - }, - ], - }; + key: "test", + exports: &[ + ExportSpec { + name: "default", + thunk: ExportThunk::Fn3(thunk_test), + }, + ExportSpec { + name: "test", + thunk: ExportThunk::Fn3(thunk_test), + }, + ExportSpec { + name: "skip", + thunk: ExportThunk::Fn3(thunk_test_skip), + }, + ExportSpec { + name: "todo", + thunk: ExportThunk::Fn3(thunk_test_todo), + }, + ExportSpec { + name: "only", + thunk: ExportThunk::Fn3(thunk_test_only), + }, + ExportSpec { + name: "suite", + thunk: ExportThunk::Fn3(thunk_test), + }, + ExportSpec { + name: "describe", + thunk: ExportThunk::Fn3(thunk_test), + }, + ExportSpec { + name: "it", + thunk: ExportThunk::Fn3(thunk_test), + }, + ExportSpec { + name: "before", + thunk: ExportThunk::Fn1(thunk_test_hook), + }, + ExportSpec { + name: "after", + thunk: ExportThunk::Fn1(thunk_test_hook), + }, + ExportSpec { + name: "beforeEach", + thunk: ExportThunk::Fn1(thunk_test_hook), + }, + ExportSpec { + name: "afterEach", + thunk: ExportThunk::Fn1(thunk_test_hook), + }, + ExportSpec { + name: "run", + thunk: ExportThunk::Fn1(thunk_test_run), + }, + // Object-valued exports are handled by `special_export_value`. + ExportSpec { + name: "mock", + thunk: ExportThunk::Fn1(thunk_test_run), + }, + ExportSpec { + name: "snapshot", + thunk: ExportThunk::Fn1(thunk_test_run), + }, + ], +}; static SUBMOD_TEST_REPORTERS: SubmoduleSpec = SubmoduleSpec { - key: "test_reporters", - exports: &[ - ExportSpec { - name: "spec", - thunk: ExportThunk::Fn1(thunk_reporter_spec), - }, - ExportSpec { - name: "tap", - thunk: ExportThunk::Fn1(thunk_reporter_tap), - }, - ExportSpec { - name: "dot", - thunk: ExportThunk::Fn1(thunk_reporter_dot), - }, - ExportSpec { - name: "junit", - thunk: ExportThunk::Fn1(thunk_reporter_junit), - }, - ExportSpec { - name: "lcov", - thunk: ExportThunk::Fn1(thunk_reporter_lcov), - }, - ], - }; + key: "test_reporters", + exports: &[ + ExportSpec { + name: "spec", + thunk: ExportThunk::Fn1(thunk_reporter_spec), + }, + ExportSpec { + name: "tap", + thunk: ExportThunk::Fn1(thunk_reporter_tap), + }, + ExportSpec { + name: "dot", + thunk: ExportThunk::Fn1(thunk_reporter_dot), + }, + ExportSpec { + name: "junit", + thunk: ExportThunk::Fn1(thunk_reporter_junit), + }, + ExportSpec { + name: "lcov", + thunk: ExportThunk::Fn1(thunk_reporter_lcov), + }, + ], +}; // ----- submodule registry (devirt) ----- use std::sync::atomic::AtomicPtr; #[derive(Copy, Clone)] #[repr(usize)] -enum SubmodBucket { Vm, Timers, TimersPromises, FsPromises, ReadlinePromises, StreamPromises, StreamConsumers, StreamWeb, HonoJsxServer, HonoJsxStreaming, Sys, DiagnosticsChannel, TraceEvents, Test, TestReporters, } +enum SubmodBucket { + Vm, + Timers, + TimersPromises, + FsPromises, + ReadlinePromises, + StreamPromises, + StreamConsumers, + StreamWeb, + HonoJsxServer, + HonoJsxStreaming, + Sys, + DiagnosticsChannel, + TraceEvents, + Test, + TestReporters, +} const SUBMOD_COUNT: usize = 15; static SUBMOD_REGISTRY: [AtomicPtr; SUBMOD_COUNT] = [const { AtomicPtr::new(std::ptr::null_mut()) }; SUBMOD_COUNT]; @@ -777,38 +793,149 @@ fn submod_index(key: &str) -> Option { fn find_submodule(key: &str) -> Option<&'static SubmoduleSpec> { let b = submod_index(key)?; let p = SUBMOD_REGISTRY[b as usize].load(Ordering::Relaxed); - if p.is_null() { None } else { Some(unsafe { &*(p as *const SubmoduleSpec) }) } + if !p.is_null() { + return Some(unsafe { &*(p as *const SubmoduleSpec) }); + } + // Unit tests call the namespace/export helpers directly, without the + // codegen-emitted `js_node_submod_install_()` that precedes use in real + // programs. Lazily populate the registry so tests exercise the real lookup + // path. (Not compiled into production builds.) + #[cfg(test)] + { + js_node_submod_install_all(); + let p = SUBMOD_REGISTRY[b as usize].load(Ordering::Relaxed); + if !p.is_null() { + return Some(unsafe { &*(p as *const SubmoduleSpec) }); + } + } + None } + +/// Test-only: every submodule spec, for exhaustiveness checks (the production +/// `find_submodule` resolves through the registry, not an iterable array). +#[cfg(test)] +pub(super) const ALL_SUBMODULE_SPECS: &[&SubmoduleSpec] = &[ + &SUBMOD_VM, + &SUBMOD_TIMERS, + &SUBMOD_TIMERS_PROMISES, + &SUBMOD_FS_PROMISES, + &SUBMOD_READLINE_PROMISES, + &SUBMOD_STREAM_PROMISES, + &SUBMOD_STREAM_CONSUMERS, + &SUBMOD_STREAM_WEB, + &SUBMOD_HONO_JSX_SERVER, + &SUBMOD_HONO_JSX_STREAMING, + &SUBMOD_SYS, + &SUBMOD_DIAGNOSTICS_CHANNEL, + &SUBMOD_TRACE_EVENTS, + &SUBMOD_TEST, + &SUBMOD_TEST_REPORTERS, +]; #[no_mangle] -pub extern "C" fn js_node_submod_install_vm() { SUBMOD_REGISTRY[SubmodBucket::Vm as usize].store(&SUBMOD_VM as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_vm() { + SUBMOD_REGISTRY[SubmodBucket::Vm as usize].store( + &SUBMOD_VM as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_timers() { SUBMOD_REGISTRY[SubmodBucket::Timers as usize].store(&SUBMOD_TIMERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_timers() { + SUBMOD_REGISTRY[SubmodBucket::Timers as usize].store( + &SUBMOD_TIMERS as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_timers_promises() { SUBMOD_REGISTRY[SubmodBucket::TimersPromises as usize].store(&SUBMOD_TIMERS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_timers_promises() { + SUBMOD_REGISTRY[SubmodBucket::TimersPromises as usize].store( + &SUBMOD_TIMERS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_fs_promises() { SUBMOD_REGISTRY[SubmodBucket::FsPromises as usize].store(&SUBMOD_FS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_fs_promises() { + SUBMOD_REGISTRY[SubmodBucket::FsPromises as usize].store( + &SUBMOD_FS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_readline_promises() { SUBMOD_REGISTRY[SubmodBucket::ReadlinePromises as usize].store(&SUBMOD_READLINE_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_readline_promises() { + SUBMOD_REGISTRY[SubmodBucket::ReadlinePromises as usize].store( + &SUBMOD_READLINE_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_stream_promises() { SUBMOD_REGISTRY[SubmodBucket::StreamPromises as usize].store(&SUBMOD_STREAM_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_stream_promises() { + SUBMOD_REGISTRY[SubmodBucket::StreamPromises as usize].store( + &SUBMOD_STREAM_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_stream_consumers() { SUBMOD_REGISTRY[SubmodBucket::StreamConsumers as usize].store(&SUBMOD_STREAM_CONSUMERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_stream_consumers() { + SUBMOD_REGISTRY[SubmodBucket::StreamConsumers as usize].store( + &SUBMOD_STREAM_CONSUMERS as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_stream_web() { SUBMOD_REGISTRY[SubmodBucket::StreamWeb as usize].store(&SUBMOD_STREAM_WEB as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_stream_web() { + SUBMOD_REGISTRY[SubmodBucket::StreamWeb as usize].store( + &SUBMOD_STREAM_WEB as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_hono_jsx_server() { SUBMOD_REGISTRY[SubmodBucket::HonoJsxServer as usize].store(&SUBMOD_HONO_JSX_SERVER as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_hono_jsx_server() { + SUBMOD_REGISTRY[SubmodBucket::HonoJsxServer as usize].store( + &SUBMOD_HONO_JSX_SERVER as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_hono_jsx_streaming() { SUBMOD_REGISTRY[SubmodBucket::HonoJsxStreaming as usize].store(&SUBMOD_HONO_JSX_STREAMING as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_hono_jsx_streaming() { + SUBMOD_REGISTRY[SubmodBucket::HonoJsxStreaming as usize].store( + &SUBMOD_HONO_JSX_STREAMING as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_sys() { SUBMOD_REGISTRY[SubmodBucket::Sys as usize].store(&SUBMOD_SYS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_sys() { + SUBMOD_REGISTRY[SubmodBucket::Sys as usize].store( + &SUBMOD_SYS as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_diagnostics_channel() { SUBMOD_REGISTRY[SubmodBucket::DiagnosticsChannel as usize].store(&SUBMOD_DIAGNOSTICS_CHANNEL as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_diagnostics_channel() { + SUBMOD_REGISTRY[SubmodBucket::DiagnosticsChannel as usize].store( + &SUBMOD_DIAGNOSTICS_CHANNEL as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_trace_events() { SUBMOD_REGISTRY[SubmodBucket::TraceEvents as usize].store(&SUBMOD_TRACE_EVENTS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_trace_events() { + SUBMOD_REGISTRY[SubmodBucket::TraceEvents as usize].store( + &SUBMOD_TRACE_EVENTS as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_test() { SUBMOD_REGISTRY[SubmodBucket::Test as usize].store(&SUBMOD_TEST as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_test() { + SUBMOD_REGISTRY[SubmodBucket::Test as usize].store( + &SUBMOD_TEST as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_node_submod_install_test_reporters() { SUBMOD_REGISTRY[SubmodBucket::TestReporters as usize].store(&SUBMOD_TEST_REPORTERS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed); } +pub extern "C" fn js_node_submod_install_test_reporters() { + SUBMOD_REGISTRY[SubmodBucket::TestReporters as usize].store( + &SUBMOD_TEST_REPORTERS as *const SubmoduleSpec as *mut SubmoduleSpec, + Ordering::Relaxed, + ); +} #[no_mangle] pub extern "C" fn js_node_submod_install_all() { js_node_submod_install_vm(); @@ -835,11 +962,11 @@ pub extern "C" fn js_node_submod_enable_install_all() { } pub(crate) fn run_submod_install_all_hook() { let p = SUBMOD_INSTALL_ALL_HOOK.load(Ordering::Relaxed); - if !p.is_null() { unsafe { std::mem::transmute::<*mut (), extern "C" fn()>(p)() }; } + if !p.is_null() { + unsafe { std::mem::transmute::<*mut (), extern "C" fn()>(p)() }; + } } - - fn find_export(submod: &SubmoduleSpec, name: &str) -> Option<&'static ExportSpec> { submod.exports.iter().find(|e| e.name == name) } diff --git a/crates/perry-runtime/src/node_submodules/tests.rs b/crates/perry-runtime/src/node_submodules/tests.rs index 28af9ea49..df2ea6b05 100644 --- a/crates/perry-runtime/src/node_submodules/tests.rs +++ b/crates/perry-runtime/src/node_submodules/tests.rs @@ -42,7 +42,7 @@ const FS_PROMISES_EXPORTS: &[&str] = &[ #[test] fn known_submodules_have_at_least_one_export() { - for s in SUBMODULES { + for s in super::ALL_SUBMODULE_SPECS { assert!( !s.exports.is_empty(), "submodule {} has zero exports", diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index e1055bcc8..b6ea97b51 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -1674,7 +1674,9 @@ pub(crate) unsafe fn nm_ctor_fs( args_len: usize, ) -> Option { if method == "Utf8Stream" { - return Some(crate::fs::js_fs_utf8_stream_new(nm_ctor_arg(args_ptr, args_len, 0))); + return Some(crate::fs::js_fs_utf8_stream_new(nm_ctor_arg( + args_ptr, args_len, 0, + ))); } if matches!( method, diff --git a/crates/perry-runtime/src/object/native_module_dispatch.rs b/crates/perry-runtime/src/object/native_module_dispatch.rs index 42fe36609..44cbabcbb 100644 --- a/crates/perry-runtime/src/object/native_module_dispatch.rs +++ b/crates/perry-runtime/src/object/native_module_dispatch.rs @@ -130,100 +130,118 @@ pub(crate) struct NmCtx { /// General (non-path) marshalling closures from the old prologue. Identifiers are /// passed in so the `let` bindings carry call-site hygiene (visible to the arms). -macro_rules! nm_general_closures { ($obj:ident, $args_ptr:ident, $args_len:ident, $arg:ident, $i32_arg:ident, $bool_to_f64:ident, $str_to_f64:ident, $pack_args:ident, $pack_args_from:ident, $bool_tag:ident, $ptr_addr:ident, $optional_ptr_addr:ident, $_arg_event_ptr:ident, $arg_bits:ident, $_arg_closure_ptr:ident, $ptr_to_f64:ident, $typed_kind:ident) => { - let $arg = |n: usize| -> f64 { - if n < $args_len && !$args_ptr.is_null() { - *$args_ptr.add(n) - } else { - f64::from_bits(JSValue::undefined().bits()) - } - }; - let $i32_arg = |n: usize| -> i32 { - let v = $arg(n); - let bits = v.to_bits(); - if (bits >> 48) == 0x7FFE { - return (bits & 0xFFFF_FFFF) as u32 as i32; - } - if v.is_nan() || v.is_infinite() { - 0 - } else { - v as i32 - } - }; - let $bool_to_f64 = |v: i32| -> f64 { - if v != 0 { - f64::from_bits(0x7FFC_0000_0000_0004) // TAG_TRUE - } else { - f64::from_bits(0x7FFC_0000_0000_0003) // TAG_FALSE - } - }; - let $str_to_f64 = - |ptr: *mut crate::StringHeader| -> f64 { f64::from_bits(JSValue::string_ptr(ptr).bits()) }; - let $pack_args = || -> *mut crate::array::ArrayHeader { - let mut arr = crate::array::js_array_alloc($args_len as u32); - for i in 0..$args_len { - arr = crate::array::js_array_push_f64(arr, $arg(i)); - } - arr - }; - let $pack_args_from = |start: usize| -> *mut crate::array::ArrayHeader { - let len = $args_len.saturating_sub(start); - let mut arr = crate::array::js_array_alloc(len as u32); - for i in start..$args_len { - arr = crate::array::js_array_push_f64(arr, $arg(i)); - } - arr - }; - let $bool_tag = |v: bool| -> f64 { - if v { - f64::from_bits(0x7FFC_0000_0000_0004) - } else { - f64::from_bits(0x7FFC_0000_0000_0003) - } - }; - let $ptr_addr = |v: f64| -> usize { - let bits = v.to_bits(); - if (bits >> 48) >= 0x7FF8 { - (bits & 0x0000_FFFF_FFFF_FFFF) as usize - } else { - bits as usize - } - }; - let $optional_ptr_addr = |v: f64| -> usize { - let value = JSValue::from_bits(v.to_bits()); - if value.is_undefined() || value.is_null() { - 0 - } else { - $ptr_addr(v) - } - }; - let $_arg_event_ptr = |n: usize| -> *const crate::StringHeader { - crate::value::js_get_string_pointer_unified($arg(n)) as *const crate::StringHeader - }; - let $arg_bits = |n: usize| -> i64 { $arg(n).to_bits() as i64 }; - let $_arg_closure_ptr = |n: usize| -> *const crate::closure::ClosureHeader { - if n >= $args_len { - return std::ptr::null(); - } - let v = $arg(n); - let jsv = JSValue::from_bits(v.to_bits()); - if jsv.is_undefined() || jsv.is_null() { - std::ptr::null() - } else { - $ptr_addr(v) as *const crate::closure::ClosureHeader - } - }; - let $ptr_to_f64 = |ptr: *const u8| -> f64 { f64::from_bits(JSValue::pointer(ptr).bits()) }; - let $typed_kind = |v: f64| -> Option { - let addr = $ptr_addr(v); - if crate::buffer::is_uint8array_buffer(addr) { - Some(crate::typedarray::KIND_UINT8) - } else { - crate::typedarray::lookup_typed_array_kind(addr) - } +macro_rules! nm_general_closures { + ($obj:ident, $args_ptr:ident, $args_len:ident, $arg:ident, $i32_arg:ident, $bool_to_f64:ident, $str_to_f64:ident, $pack_args:ident, $pack_args_from:ident, $bool_tag:ident, $ptr_addr:ident, $optional_ptr_addr:ident, $_arg_event_ptr:ident, $arg_bits:ident, $_arg_closure_ptr:ident, $ptr_to_f64:ident, $typed_kind:ident) => { + let $arg = |n: usize| -> f64 { + if n < $args_len && !$args_ptr.is_null() { + *$args_ptr.add(n) + } else { + f64::from_bits(JSValue::undefined().bits()) + } + }; + let $i32_arg = |n: usize| -> i32 { + let v = $arg(n); + let bits = v.to_bits(); + if (bits >> 48) == 0x7FFE { + return (bits & 0xFFFF_FFFF) as u32 as i32; + } + if v.is_nan() || v.is_infinite() { + 0 + } else { + v as i32 + } + }; + let $bool_to_f64 = |v: i32| -> f64 { + if v != 0 { + f64::from_bits(0x7FFC_0000_0000_0004) // TAG_TRUE + } else { + f64::from_bits(0x7FFC_0000_0000_0003) // TAG_FALSE + } + }; + let $str_to_f64 = |ptr: *mut crate::StringHeader| -> f64 { + f64::from_bits(JSValue::string_ptr(ptr).bits()) + }; + let $pack_args = || -> *mut crate::array::ArrayHeader { + let mut arr = crate::array::js_array_alloc($args_len as u32); + for i in 0..$args_len { + arr = crate::array::js_array_push_f64(arr, $arg(i)); + } + arr + }; + let $pack_args_from = |start: usize| -> *mut crate::array::ArrayHeader { + let len = $args_len.saturating_sub(start); + let mut arr = crate::array::js_array_alloc(len as u32); + for i in start..$args_len { + arr = crate::array::js_array_push_f64(arr, $arg(i)); + } + arr + }; + let $bool_tag = |v: bool| -> f64 { + if v { + f64::from_bits(0x7FFC_0000_0000_0004) + } else { + f64::from_bits(0x7FFC_0000_0000_0003) + } + }; + let $ptr_addr = |v: f64| -> usize { + let bits = v.to_bits(); + if (bits >> 48) >= 0x7FF8 { + (bits & 0x0000_FFFF_FFFF_FFFF) as usize + } else { + bits as usize + } + }; + let $optional_ptr_addr = |v: f64| -> usize { + let value = JSValue::from_bits(v.to_bits()); + if value.is_undefined() || value.is_null() { + 0 + } else { + $ptr_addr(v) + } + }; + let $_arg_event_ptr = |n: usize| -> *const crate::StringHeader { + crate::value::js_get_string_pointer_unified($arg(n)) as *const crate::StringHeader + }; + let $arg_bits = |n: usize| -> i64 { $arg(n).to_bits() as i64 }; + let $_arg_closure_ptr = |n: usize| -> *const crate::closure::ClosureHeader { + if n >= $args_len { + return std::ptr::null(); + } + let v = $arg(n); + let jsv = JSValue::from_bits(v.to_bits()); + if jsv.is_undefined() || jsv.is_null() { + std::ptr::null() + } else { + $ptr_addr(v) as *const crate::closure::ClosureHeader + } + }; + let $ptr_to_f64 = |ptr: *const u8| -> f64 { f64::from_bits(JSValue::pointer(ptr).bits()) }; + let $typed_kind = |v: f64| -> Option { + let addr = $ptr_addr(v); + if crate::buffer::is_uint8array_buffer(addr) { + Some(crate::typedarray::KIND_UINT8) + } else { + crate::typedarray::lookup_typed_array_kind(addr) + } + }; + let _ = ( + &$arg, + &$i32_arg, + &$bool_to_f64, + &$str_to_f64, + &$pack_args, + &$pack_args_from, + &$bool_tag, + &$ptr_addr, + &$optional_ptr_addr, + &$_arg_event_ptr, + &$arg_bits, + &$_arg_closure_ptr, + &$ptr_to_f64, + &$typed_kind, + ); }; - let _ = (&$arg, &$i32_arg, &$bool_to_f64, &$str_to_f64, &$pack_args, &$pack_args_from, &$bool_tag, &$ptr_addr, &$optional_ptr_addr, &$_arg_event_ptr, &$arg_bits, &$_arg_closure_ptr, &$ptr_to_f64, &$typed_kind,); -} } +} /// Thin router — extract+normalize the module name, dispatch via the per-module /// registry. Names no bucket fn, so unused modules dead-strip. @@ -271,18 +289,52 @@ pub(crate) unsafe fn dispatch_native_module_method( "punycode.default" => ("punycode", false), _ => (module_name, false), }; - let ctx = NmCtx { obj, args_ptr, args_len, assert_skip_prototype }; + let ctx = NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + }; match super::native_module_registry::nm_dispatch_lookup(module_name) { Some(f) => f(&ctx, module_name, method_name), None => f64::from_bits(JSValue::undefined().bits()), } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_assert(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("assert", "default") | ("assert/strict", "default") => js_assert_ok(arg(0), arg(1)), ("assert", "strict") | ("assert/strict", "strict") => js_assert_ok(arg(0), arg(1)), @@ -358,11 +410,44 @@ pub(crate) unsafe fn nm_dispatch_assert(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_async_hooks(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_async_hooks( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("async_hooks", "createHook") => { ptr_to_f64(crate::async_hooks::js_async_hooks_create_hook(arg(0)) as *const u8) @@ -378,11 +463,40 @@ pub(crate) unsafe fn nm_dispatch_async_hooks(ctx: &NmCtx, module_name: &str, met } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_bigint(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("bigint", "asIntN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), true), ("bigint", "asUintN") => crate::object::bigint_as_n_dispatch(arg(0), arg(1), false), @@ -390,11 +504,40 @@ pub(crate) unsafe fn nm_dispatch_bigint(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_buffer(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("buffer.Buffer", "from") => { let data = arg(0); @@ -484,11 +627,44 @@ pub(crate) unsafe fn nm_dispatch_buffer(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_child_process(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_child_process( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("child_process", "spawn") => { let cmd = crate::string::js_string_materialize_to_heap(arg(0)) as i64; @@ -531,11 +707,40 @@ pub(crate) unsafe fn nm_dispatch_child_process(ctx: &NmCtx, module_name: &str, m } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_cluster(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("cluster", "setupPrimary") | ("cluster", "setupMaster") => { crate::cluster::js_cluster_setup_primary(arg(0)) @@ -574,11 +779,40 @@ pub(crate) unsafe fn nm_dispatch_cluster(ctx: &NmCtx, module_name: &str, method_ } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_console(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("console", "Console") => crate::builtins::js_console_new2(arg(0), arg(1)), ("console", "log") | ("console", "info") | ("console", "debug") | ("console", "dirxml") => { @@ -661,11 +895,40 @@ pub(crate) unsafe fn nm_dispatch_console(ctx: &NmCtx, module_name: &str, method_ } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_crypto(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("crypto", "randomFillSync") if args_len >= 1 => { super::native_module_crypto_random::random_fill_sync(arg(0), arg(1), arg(2)) @@ -740,11 +1003,40 @@ pub(crate) unsafe fn nm_dispatch_crypto(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_dgram(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { #[cfg(feature = "mod-dgram")] ("dgram", "createSocket") | ("dgram", "Socket") => { @@ -756,11 +1048,40 @@ pub(crate) unsafe fn nm_dispatch_dgram(ctx: &NmCtx, module_name: &str, method_na } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_dns(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("dns", "getServers") => crate::dns::dns_get_servers_value(), ("dns", "setServers") => crate::dns::dns_set_servers_value(arg(0)), @@ -787,11 +1108,40 @@ pub(crate) unsafe fn nm_dispatch_dns(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_domain(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("domain", "Domain" | "createDomain" | "create") => { let ptr = @@ -808,11 +1158,40 @@ pub(crate) unsafe fn nm_dispatch_domain(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_events(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("events", "init") => f64::from_bits(crate::value::TAG_UNDEFINED), ("events", "EventEmitterAsyncResource") => { @@ -826,11 +1205,40 @@ pub(crate) unsafe fn nm_dispatch_events(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_fs(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("fs", "_toUnixTimestamp") => crate::fs::js_fs_to_unix_timestamp(arg(0)), ("fs", "existsSync") => bool_to_f64(crate::fs::js_fs_exists_sync(arg(0))), @@ -981,11 +1389,40 @@ pub(crate) unsafe fn nm_dispatch_fs(ctx: &NmCtx, module_name: &str, method_name: } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_http(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("http", "validateHeaderName") => js_http_validate_header_name(arg(0), arg(1)), ("http", "validateHeaderValue") => js_http_validate_header_value(arg(0), arg(1)), @@ -1038,11 +1475,44 @@ pub(crate) unsafe fn nm_dispatch_http(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_inspector(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_inspector( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("inspector", "open") => { crate::node_inspector::js_node_inspector_open(arg(0), arg(1), arg(2)) @@ -1071,11 +1541,40 @@ pub(crate) unsafe fn nm_dispatch_inspector(ctx: &NmCtx, module_name: &str, metho } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_module(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("module", "createRequire") => crate::module_require::js_module_create_require(arg(0)), ("module", "enableCompileCache") => crate::process::js_module_enable_compile_cache(arg(0)), @@ -1107,11 +1606,40 @@ pub(crate) unsafe fn nm_dispatch_module(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_net(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("net", "_normalizeArgs") => crate::net_validate::js_net_normalize_args(arg(0)), ("net", "_createServerHandle") => crate::net_validate::js_net_create_server_handle_stub( @@ -1129,11 +1657,40 @@ pub(crate) unsafe fn nm_dispatch_net(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_os(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("os", "tmpdir") => str_to_f64(crate::os::js_os_tmpdir()), ("os", "homedir") => str_to_f64(crate::os::js_os_homedir()), @@ -1176,11 +1733,40 @@ pub(crate) unsafe fn nm_dispatch_os(ctx: &NmCtx, module_name: &str, method_name: } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_path(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); let require_path_str_ptr = |n: usize| -> *const crate::StringHeader { if n < args_len { let v = arg(n); @@ -1372,11 +1958,40 @@ pub(crate) unsafe fn nm_dispatch_path(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_perf(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("perf_hooks", "now") => crate::date::js_performance_now(), ("perf_hooks", "mark") => crate::perf_hooks::js_perf_mark(arg(0), arg(1)), @@ -1456,11 +2071,40 @@ pub(crate) unsafe fn nm_dispatch_perf(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_process(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("process", "on") => crate::os::js_process_on(arg_bits(0), arg_bits(1)), ("process", "addListener") => crate::os::js_process_add_listener(arg_bits(0), arg_bits(1)), @@ -1626,11 +2270,44 @@ pub(crate) unsafe fn nm_dispatch_process(ctx: &NmCtx, module_name: &str, method_ } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_punycode(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_punycode( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("punycode", "decode") => crate::punycode::js_punycode_decode(arg(0)), ("punycode", "encode") => crate::punycode::js_punycode_encode(arg(0)), @@ -1650,11 +2327,44 @@ pub(crate) unsafe fn nm_dispatch_punycode(ctx: &NmCtx, module_name: &str, method } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_querystring(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_querystring( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ( "querystring", @@ -1674,11 +2384,44 @@ pub(crate) unsafe fn nm_dispatch_querystring(ctx: &NmCtx, module_name: &str, met } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] -pub(crate) unsafe fn nm_dispatch_readline(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] +pub(crate) unsafe fn nm_dispatch_readline( + ctx: &NmCtx, + module_name: &str, + method_name: &str, +) -> f64 { + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("readline", "clearLine") => { crate::readline_helpers::js_readline_clear_line_args(pack_args()) @@ -1701,11 +2444,40 @@ pub(crate) unsafe fn nm_dispatch_readline(ctx: &NmCtx, module_name: &str, method } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_repl(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("repl", "start") => crate::node_repl::js_repl_start(arg(0)), ("repl", "REPLServer") => crate::node_repl::js_repl_repl_server_new(arg(0)), @@ -1718,11 +2490,40 @@ pub(crate) unsafe fn nm_dispatch_repl(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_sea(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("sea", "isSea") => crate::node_sea::js_sea_is_sea(), ("sea", "getAsset") => crate::node_sea::js_sea_get_asset(arg(0), arg(1)), @@ -1738,11 +2539,40 @@ pub(crate) unsafe fn nm_dispatch_sea(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_sqlite(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("sqlite", _) => { let ptr = @@ -1764,11 +2594,40 @@ pub(crate) unsafe fn nm_dispatch_sqlite(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_stream(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("stream", _) => dispatch_stream_native_module_method(method_name, args_ptr, args_len) .unwrap_or_else(|| f64::from_bits(JSValue::undefined().bits())), @@ -1776,11 +2635,40 @@ pub(crate) unsafe fn nm_dispatch_stream(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_timers(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("timers", "setTimeout") if args_len >= 2 => { let cb = arg(0); @@ -1879,11 +2767,40 @@ pub(crate) unsafe fn nm_dispatch_timers(ctx: &NmCtx, module_name: &str, method_n } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_tls(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("tls", "getCiphers") => crate::tls::js_tls_get_ciphers(), ("tls", "getCACertificates") => crate::tls::js_tls_get_ca_certificates(arg(0)), @@ -1922,11 +2839,40 @@ pub(crate) unsafe fn nm_dispatch_tls(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_tty(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("tty", "isatty") => crate::tty::js_tty_isatty(arg(0)), ("tty", "ReadStream") => crate::tty::js_tty_read_stream_new(arg(0)), @@ -1937,11 +2883,40 @@ pub(crate) unsafe fn nm_dispatch_tty(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_url(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("url", "fileURLToPath") => crate::url::js_url_file_url_to_path(arg(0), arg(1)), ("url", "fileURLToPathBuffer") => { @@ -1963,11 +2938,40 @@ pub(crate) unsafe fn nm_dispatch_url(ctx: &NmCtx, module_name: &str, method_name } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_util(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("util", "format") => crate::builtins::js_util_format(pack_args()), ("util", "formatWithOptions") => { @@ -2217,11 +3221,40 @@ pub(crate) unsafe fn nm_dispatch_util(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_v8(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("v8", "serialize") => crate::node_v8::js_v8_serialize(arg(0)), ("v8", "deserialize") => crate::node_v8::js_v8_deserialize(arg(0)), @@ -2306,11 +3339,40 @@ pub(crate) unsafe fn nm_dispatch_v8(ctx: &NmCtx, module_name: &str, method_name: } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_vm(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("vm", m) => crate::node_vm::dispatch_vm_method(m, arg(0), arg(1), arg(2)), // ── tty module ── @@ -2318,11 +3380,40 @@ pub(crate) unsafe fn nm_dispatch_vm(ctx: &NmCtx, module_name: &str, method_name: } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_wasi(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("wasi", "WASI") => crate::wasi::js_wasi_constructor_call(arg(0)), @@ -2331,11 +3422,40 @@ pub(crate) unsafe fn nm_dispatch_wasi(ctx: &NmCtx, module_name: &str, method_nam } } -#[allow(unused_variables, unused_mut, unused_unsafe, clippy::let_and_return, clippy::all)] +#[allow( + unused_variables, + unused_mut, + unused_unsafe, + clippy::let_and_return, + clippy::all +)] pub(crate) unsafe fn nm_dispatch_zlib(ctx: &NmCtx, module_name: &str, method_name: &str) -> f64 { - let NmCtx { obj, args_ptr, args_len, assert_skip_prototype } = *ctx; + let NmCtx { + obj, + args_ptr, + args_len, + assert_skip_prototype, + } = *ctx; let _ = (obj, args_ptr, args_len, assert_skip_prototype); - nm_general_closures!(obj, args_ptr, args_len, arg, i32_arg, bool_to_f64, str_to_f64, pack_args, pack_args_from, bool_tag, ptr_addr, optional_ptr_addr, _arg_event_ptr, arg_bits, _arg_closure_ptr, ptr_to_f64, typed_kind); + nm_general_closures!( + obj, + args_ptr, + args_len, + arg, + i32_arg, + bool_to_f64, + str_to_f64, + pack_args, + pack_args_from, + bool_tag, + ptr_addr, + optional_ptr_addr, + _arg_event_ptr, + arg_bits, + _arg_closure_ptr, + ptr_to_f64, + typed_kind + ); match (module_name, method_name) { ("zlib", _) => { let ptr = diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index 29d3ba93a..c3869cdf3 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -3,14 +3,62 @@ //! the SOLE static reference to its `nm_dispatch_` bucket fn; codegen emits a //! call per statically-imported native module so the linker dead-strips the rest. //! NOTHING here names all buckets together (that would re-pin everything). -use super::native_module_dispatch::{NmCtx, nm_dispatch_assert, nm_dispatch_async_hooks, nm_dispatch_bigint, nm_dispatch_buffer, nm_dispatch_child_process, nm_dispatch_cluster, nm_dispatch_console, nm_dispatch_crypto, nm_dispatch_dgram, nm_dispatch_dns, nm_dispatch_domain, nm_dispatch_events, nm_dispatch_fs, nm_dispatch_http, nm_dispatch_inspector, nm_dispatch_module, nm_dispatch_net, nm_dispatch_os, nm_dispatch_path, nm_dispatch_perf, nm_dispatch_process, nm_dispatch_punycode, nm_dispatch_querystring, nm_dispatch_readline, nm_dispatch_repl, nm_dispatch_sea, nm_dispatch_sqlite, nm_dispatch_stream, nm_dispatch_timers, nm_dispatch_tls, nm_dispatch_tty, nm_dispatch_url, nm_dispatch_util, nm_dispatch_v8, nm_dispatch_vm, nm_dispatch_wasi, nm_dispatch_zlib}; +use super::native_module_dispatch::{ + nm_dispatch_assert, nm_dispatch_async_hooks, nm_dispatch_bigint, nm_dispatch_buffer, + nm_dispatch_child_process, nm_dispatch_cluster, nm_dispatch_console, nm_dispatch_crypto, + nm_dispatch_dgram, nm_dispatch_dns, nm_dispatch_domain, nm_dispatch_events, nm_dispatch_fs, + nm_dispatch_http, nm_dispatch_inspector, nm_dispatch_module, nm_dispatch_net, nm_dispatch_os, + nm_dispatch_path, nm_dispatch_perf, nm_dispatch_process, nm_dispatch_punycode, + nm_dispatch_querystring, nm_dispatch_readline, nm_dispatch_repl, nm_dispatch_sea, + nm_dispatch_sqlite, nm_dispatch_stream, nm_dispatch_timers, nm_dispatch_tls, nm_dispatch_tty, + nm_dispatch_url, nm_dispatch_util, nm_dispatch_v8, nm_dispatch_vm, nm_dispatch_wasi, + nm_dispatch_zlib, NmCtx, +}; use std::sync::atomic::{AtomicPtr, Ordering}; type NmDispatchFn = unsafe fn(&NmCtx, &str, &str) -> f64; #[derive(Copy, Clone)] #[repr(usize)] -enum NmBucket { Assert, AsyncHooks, Bigint, Buffer, ChildProcess, Cluster, Console, Crypto, Dgram, Dns, Domain, Events, Fs, Http, Inspector, Module, Net, Os, Path, Perf, Process, Punycode, Querystring, Readline, Repl, Sea, Sqlite, Stream, Timers, Tls, Tty, Url, Util, V8, Vm, Wasi, Zlib, } +enum NmBucket { + Assert, + AsyncHooks, + Bigint, + Buffer, + ChildProcess, + Cluster, + Console, + Crypto, + Dgram, + Dns, + Domain, + Events, + Fs, + Http, + Inspector, + Module, + Net, + Os, + Path, + Perf, + Process, + Punycode, + Querystring, + Readline, + Repl, + Sea, + Sqlite, + Stream, + Timers, + Tls, + Tty, + Url, + Util, + V8, + Vm, + Wasi, + Zlib, +} const NM_BUCKET_COUNT: usize = 37; static NM_DISPATCH_REGISTRY: [AtomicPtr<()>; NM_BUCKET_COUNT] = @@ -27,7 +75,8 @@ fn nm_module_index(name: &str) -> Option { "child_process" => Some(NmBucket::ChildProcess), "cluster" => Some(NmBucket::Cluster), "console" => Some(NmBucket::Console), - "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" | "crypto.webcrypto" => Some(NmBucket::Crypto), + "crypto" | "crypto.Certificate" | "crypto.KeyObject" | "crypto.subtle" + | "crypto.webcrypto" => Some(NmBucket::Crypto), "dgram" => Some(NmBucket::Dgram), "dns" | "dns/promises" => Some(NmBucket::Dns), "domain" => Some(NmBucket::Domain), @@ -39,7 +88,9 @@ fn nm_module_index(name: &str) -> Option { "net" => Some(NmBucket::Net), "os" => Some(NmBucket::Os), "path" | "path.posix" | "path.win32" => Some(NmBucket::Path), - "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => Some(NmBucket::Perf), + "perf_histogram" | "perf_hooks" | "perf_observer" | "perf_observer_list" => { + Some(NmBucket::Perf) + } "process" => Some(NmBucket::Process), "punycode" | "punycode.ucs2" => Some(NmBucket::Punycode), "querystring" => Some(NmBucket::Querystring), @@ -53,7 +104,8 @@ fn nm_module_index(name: &str) -> Option { "tty" => Some(NmBucket::Tty), "url" => Some(NmBucket::Url), "util" | "util.types" | "util/types" => Some(NmBucket::Util), - "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" | "v8.startupSnapshot" => Some(NmBucket::V8), + "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" + | "v8.startupSnapshot" => Some(NmBucket::V8), "vm" => Some(NmBucket::Vm), "wasi" => Some(NmBucket::Wasi), "zlib" => Some(NmBucket::Zlib), @@ -66,87 +118,282 @@ fn nm_module_index(name: &str) -> Option { pub(crate) fn nm_dispatch_lookup(name: &str) -> Option { let b = nm_module_index(name)?; let p = NM_DISPATCH_REGISTRY[b as usize].load(Ordering::Relaxed); - if p.is_null() { - None - } else { - Some(unsafe { std::mem::transmute::<*mut (), NmDispatchFn>(p) }) + if !p.is_null() { + return Some(unsafe { std::mem::transmute::<*mut (), NmDispatchFn>(p) }); } + // Unit tests call dispatch directly, without the codegen-emitted + // `js_nm_install_()` that precedes use in real programs. Lazily + // populate so tests exercise the real registry path. (Not in production.) + #[cfg(test)] + { + js_nm_install_all(); + let p = NM_DISPATCH_REGISTRY[b as usize].load(Ordering::Relaxed); + if !p.is_null() { + return Some(unsafe { std::mem::transmute::<*mut (), NmDispatchFn>(p) }); + } + } + None } #[no_mangle] -pub extern "C" fn js_nm_install_assert() { NM_DISPATCH_REGISTRY[NmBucket::Assert as usize].store(nm_dispatch_assert as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_assert() { + NM_DISPATCH_REGISTRY[NmBucket::Assert as usize].store( + nm_dispatch_assert as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_async_hooks() { NM_DISPATCH_REGISTRY[NmBucket::AsyncHooks as usize].store(nm_dispatch_async_hooks as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_async_hooks() { + NM_DISPATCH_REGISTRY[NmBucket::AsyncHooks as usize].store( + nm_dispatch_async_hooks as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_bigint() { NM_DISPATCH_REGISTRY[NmBucket::Bigint as usize].store(nm_dispatch_bigint as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_bigint() { + NM_DISPATCH_REGISTRY[NmBucket::Bigint as usize].store( + nm_dispatch_bigint as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_buffer() { NM_DISPATCH_REGISTRY[NmBucket::Buffer as usize].store(nm_dispatch_buffer as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_buffer() { + NM_DISPATCH_REGISTRY[NmBucket::Buffer as usize].store( + nm_dispatch_buffer as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_child_process() { NM_DISPATCH_REGISTRY[NmBucket::ChildProcess as usize].store(nm_dispatch_child_process as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_child_process() { + NM_DISPATCH_REGISTRY[NmBucket::ChildProcess as usize].store( + nm_dispatch_child_process as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_cluster() { NM_DISPATCH_REGISTRY[NmBucket::Cluster as usize].store(nm_dispatch_cluster as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_cluster() { + NM_DISPATCH_REGISTRY[NmBucket::Cluster as usize].store( + nm_dispatch_cluster as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_console() { NM_DISPATCH_REGISTRY[NmBucket::Console as usize].store(nm_dispatch_console as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_console() { + NM_DISPATCH_REGISTRY[NmBucket::Console as usize].store( + nm_dispatch_console as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_crypto() { NM_DISPATCH_REGISTRY[NmBucket::Crypto as usize].store(nm_dispatch_crypto as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_crypto() { + NM_DISPATCH_REGISTRY[NmBucket::Crypto as usize].store( + nm_dispatch_crypto as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_dgram() { NM_DISPATCH_REGISTRY[NmBucket::Dgram as usize].store(nm_dispatch_dgram as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_dgram() { + NM_DISPATCH_REGISTRY[NmBucket::Dgram as usize].store( + nm_dispatch_dgram as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_dns() { NM_DISPATCH_REGISTRY[NmBucket::Dns as usize].store(nm_dispatch_dns as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_dns() { + NM_DISPATCH_REGISTRY[NmBucket::Dns as usize].store( + nm_dispatch_dns as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_domain() { NM_DISPATCH_REGISTRY[NmBucket::Domain as usize].store(nm_dispatch_domain as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_domain() { + NM_DISPATCH_REGISTRY[NmBucket::Domain as usize].store( + nm_dispatch_domain as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_events() { NM_DISPATCH_REGISTRY[NmBucket::Events as usize].store(nm_dispatch_events as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_events() { + NM_DISPATCH_REGISTRY[NmBucket::Events as usize].store( + nm_dispatch_events as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_fs() { NM_DISPATCH_REGISTRY[NmBucket::Fs as usize].store(nm_dispatch_fs as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Fs, nm_ctor_fs); } +pub extern "C" fn js_nm_install_fs() { + NM_DISPATCH_REGISTRY[NmBucket::Fs as usize] + .store(nm_dispatch_fs as NmDispatchFn as *mut (), Ordering::Relaxed); + nm_register_ctor(NmBucket::Fs, nm_ctor_fs); +} #[no_mangle] -pub extern "C" fn js_nm_install_http() { NM_DISPATCH_REGISTRY[NmBucket::Http as usize].store(nm_dispatch_http as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_http() { + NM_DISPATCH_REGISTRY[NmBucket::Http as usize].store( + nm_dispatch_http as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_inspector() { NM_DISPATCH_REGISTRY[NmBucket::Inspector as usize].store(nm_dispatch_inspector as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_inspector() { + NM_DISPATCH_REGISTRY[NmBucket::Inspector as usize].store( + nm_dispatch_inspector as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_module() { NM_DISPATCH_REGISTRY[NmBucket::Module as usize].store(nm_dispatch_module as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_module() { + NM_DISPATCH_REGISTRY[NmBucket::Module as usize].store( + nm_dispatch_module as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_net() { NM_DISPATCH_REGISTRY[NmBucket::Net as usize].store(nm_dispatch_net as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_net() { + NM_DISPATCH_REGISTRY[NmBucket::Net as usize].store( + nm_dispatch_net as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_os() { NM_DISPATCH_REGISTRY[NmBucket::Os as usize].store(nm_dispatch_os as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_os() { + NM_DISPATCH_REGISTRY[NmBucket::Os as usize] + .store(nm_dispatch_os as NmDispatchFn as *mut (), Ordering::Relaxed); +} #[no_mangle] -pub extern "C" fn js_nm_install_path() { NM_DISPATCH_REGISTRY[NmBucket::Path as usize].store(nm_dispatch_path as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_path() { + NM_DISPATCH_REGISTRY[NmBucket::Path as usize].store( + nm_dispatch_path as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_perf() { NM_DISPATCH_REGISTRY[NmBucket::Perf as usize].store(nm_dispatch_perf as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_perf() { + NM_DISPATCH_REGISTRY[NmBucket::Perf as usize].store( + nm_dispatch_perf as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_process() { NM_DISPATCH_REGISTRY[NmBucket::Process as usize].store(nm_dispatch_process as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_process() { + NM_DISPATCH_REGISTRY[NmBucket::Process as usize].store( + nm_dispatch_process as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_punycode() { NM_DISPATCH_REGISTRY[NmBucket::Punycode as usize].store(nm_dispatch_punycode as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_punycode() { + NM_DISPATCH_REGISTRY[NmBucket::Punycode as usize].store( + nm_dispatch_punycode as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_querystring() { NM_DISPATCH_REGISTRY[NmBucket::Querystring as usize].store(nm_dispatch_querystring as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_querystring() { + NM_DISPATCH_REGISTRY[NmBucket::Querystring as usize].store( + nm_dispatch_querystring as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_readline() { NM_DISPATCH_REGISTRY[NmBucket::Readline as usize].store(nm_dispatch_readline as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Readline, nm_ctor_readline); } +pub extern "C" fn js_nm_install_readline() { + NM_DISPATCH_REGISTRY[NmBucket::Readline as usize].store( + nm_dispatch_readline as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Readline, nm_ctor_readline); +} #[no_mangle] -pub extern "C" fn js_nm_install_repl() { NM_DISPATCH_REGISTRY[NmBucket::Repl as usize].store(nm_dispatch_repl as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Repl, nm_ctor_repl); } +pub extern "C" fn js_nm_install_repl() { + NM_DISPATCH_REGISTRY[NmBucket::Repl as usize].store( + nm_dispatch_repl as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Repl, nm_ctor_repl); +} #[no_mangle] -pub extern "C" fn js_nm_install_sea() { NM_DISPATCH_REGISTRY[NmBucket::Sea as usize].store(nm_dispatch_sea as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_sea() { + NM_DISPATCH_REGISTRY[NmBucket::Sea as usize].store( + nm_dispatch_sea as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_sqlite() { NM_DISPATCH_REGISTRY[NmBucket::Sqlite as usize].store(nm_dispatch_sqlite as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_sqlite() { + NM_DISPATCH_REGISTRY[NmBucket::Sqlite as usize].store( + nm_dispatch_sqlite as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_stream() { NM_DISPATCH_REGISTRY[NmBucket::Stream as usize].store(nm_dispatch_stream as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Stream, nm_ctor_stream); } +pub extern "C" fn js_nm_install_stream() { + NM_DISPATCH_REGISTRY[NmBucket::Stream as usize].store( + nm_dispatch_stream as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Stream, nm_ctor_stream); +} #[no_mangle] -pub extern "C" fn js_nm_install_timers() { NM_DISPATCH_REGISTRY[NmBucket::Timers as usize].store(nm_dispatch_timers as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_timers() { + NM_DISPATCH_REGISTRY[NmBucket::Timers as usize].store( + nm_dispatch_timers as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_tls() { NM_DISPATCH_REGISTRY[NmBucket::Tls as usize].store(nm_dispatch_tls as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Tls, nm_ctor_tls); } +pub extern "C" fn js_nm_install_tls() { + NM_DISPATCH_REGISTRY[NmBucket::Tls as usize].store( + nm_dispatch_tls as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Tls, nm_ctor_tls); +} #[no_mangle] -pub extern "C" fn js_nm_install_tty() { NM_DISPATCH_REGISTRY[NmBucket::Tty as usize].store(nm_dispatch_tty as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Tty, nm_ctor_tty); } +pub extern "C" fn js_nm_install_tty() { + NM_DISPATCH_REGISTRY[NmBucket::Tty as usize].store( + nm_dispatch_tty as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Tty, nm_ctor_tty); +} #[no_mangle] -pub extern "C" fn js_nm_install_url() { NM_DISPATCH_REGISTRY[NmBucket::Url as usize].store(nm_dispatch_url as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_url() { + NM_DISPATCH_REGISTRY[NmBucket::Url as usize].store( + nm_dispatch_url as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_util() { NM_DISPATCH_REGISTRY[NmBucket::Util as usize].store(nm_dispatch_util as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_util() { + NM_DISPATCH_REGISTRY[NmBucket::Util as usize].store( + nm_dispatch_util as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} #[no_mangle] -pub extern "C" fn js_nm_install_v8() { NM_DISPATCH_REGISTRY[NmBucket::V8 as usize].store(nm_dispatch_v8 as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_v8() { + NM_DISPATCH_REGISTRY[NmBucket::V8 as usize] + .store(nm_dispatch_v8 as NmDispatchFn as *mut (), Ordering::Relaxed); +} #[no_mangle] -pub extern "C" fn js_nm_install_vm() { NM_DISPATCH_REGISTRY[NmBucket::Vm as usize].store(nm_dispatch_vm as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Vm, nm_ctor_vm); } +pub extern "C" fn js_nm_install_vm() { + NM_DISPATCH_REGISTRY[NmBucket::Vm as usize] + .store(nm_dispatch_vm as NmDispatchFn as *mut (), Ordering::Relaxed); + nm_register_ctor(NmBucket::Vm, nm_ctor_vm); +} #[no_mangle] -pub extern "C" fn js_nm_install_wasi() { NM_DISPATCH_REGISTRY[NmBucket::Wasi as usize].store(nm_dispatch_wasi as NmDispatchFn as *mut (), Ordering::Relaxed); nm_register_ctor(NmBucket::Wasi, nm_ctor_wasi); } +pub extern "C" fn js_nm_install_wasi() { + NM_DISPATCH_REGISTRY[NmBucket::Wasi as usize].store( + nm_dispatch_wasi as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); + nm_register_ctor(NmBucket::Wasi, nm_ctor_wasi); +} #[no_mangle] -pub extern "C" fn js_nm_install_zlib() { NM_DISPATCH_REGISTRY[NmBucket::Zlib as usize].store(nm_dispatch_zlib as NmDispatchFn as *mut (), Ordering::Relaxed); } +pub extern "C" fn js_nm_install_zlib() { + NM_DISPATCH_REGISTRY[NmBucket::Zlib as usize].store( + nm_dispatch_zlib as NmDispatchFn as *mut (), + Ordering::Relaxed, + ); +} /// Dynamic-require fallback: register every bucket. Emitted by codegen only when /// a native module is imported under an unanalyzable (dynamic) name. @@ -249,11 +496,20 @@ pub(crate) fn nm_ctor_lookup(module: &str) -> Option { let b = nm_module_index(module) .or_else(|| nm_module_index(module.split('/').next().unwrap_or(module)))?; let p = NM_CTOR_REGISTRY[b as usize].load(Ordering::Relaxed); - if p.is_null() { - None - } else { - Some(unsafe { std::mem::transmute::<*mut (), NmCtorFn>(p) }) + if !p.is_null() { + return Some(unsafe { std::mem::transmute::<*mut (), NmCtorFn>(p) }); + } + // See nm_dispatch_lookup: unit tests construct directly without the codegen + // install; lazily populate so tests exercise the real registry. + #[cfg(test)] + { + js_nm_install_all(); + let p = NM_CTOR_REGISTRY[b as usize].load(Ordering::Relaxed); + if !p.is_null() { + return Some(unsafe { std::mem::transmute::<*mut (), NmCtorFn>(p) }); + } } + None } /// Register a bucket's constructor fn. Called from the relevant From a34fc1e90e443de0ffd83ae1dbc9400e8ede82c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 13:28:35 +0200 Subject: [PATCH 12/13] chore(lint): allowlist native_module_dispatch.rs for the 2000-LOC cap The devirt split (37 per-module nm_dispatch_ fns + closure preludes) grew the file from ~1975 to 3473 LOC. Same rationale as the other allowlisted dispatch tables (class_registry, native_module, native_call_method): one logical generated dispatch surface, kept together. --- scripts/check_file_size.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/check_file_size.sh b/scripts/check_file_size.sh index 07a080bcb..caa383562 100755 --- a/scripts/check_file_size.sh +++ b/scripts/check_file_size.sh @@ -139,6 +139,13 @@ crates/perry-runtime/src/object/global_this.rs # Crossed the limit at 2014 LOC after the #2135 worker_threads # value-export arm. Split tracked under #1435. crates/perry-runtime/src/object/native_module.rs +# Per-module native-method dispatch buckets (devirtualization): the old single +# ~1975-LOC `dispatch_native_module_method` match was split into 37 per-module +# `nm_dispatch_` fns reached through a registry so the linker can +# dead-strip unimported modules (−20% hello-world __text). The bucket fns repeat +# the match-arm + closure-prelude shape, so the file grew past 2000; the arms are +# intentionally kept together (generated, one logical dispatch surface). +crates/perry-runtime/src/object/native_module_dispatch.rs # globalThis constructor/namespace registry; current main crossed the threshold # after WebCrypto + DOM/Event global exposure landed. Split tracked under #1435. crates/perry-runtime/src/object/global_this.rs From 9eb12be13492f9d541e486aca8357b89c1c59234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Tue, 16 Jun 2026 13:54:33 +0200 Subject: [PATCH 13/13] fix(devirt): address CodeRabbit review (4 findings) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - objects.rs: declare js_nm_install_v8 (the decl-gen regex [a-z_]+ skipped the digit in 'v8' → codegen could emit an undeclared call on node:v8 paths). - nm_module_index + codegen nm_install_symbol: map tags that appear only as the *second* literal of a multi-pattern arm (assert/strict, http2, https, punycode.default, v8.DefaultSerializer/Deserializer) — they bucketed by first literal so the index missed them and those modules dispatched undefined. Verified: import assert from 'node:assert/strict' now works. - class_registry.rs: move #[no_mangle] back onto js_new_function_construct (the phase-2 ctor block was inserted between the attribute and the fn, so it bound to nm_ctor_arg and left the FFI entrypoint Rust-mangled). - node_submodules: js_node_submod_install_fs_promises/_sys also install the native fs/util buckets they delegate to (fs.constants, sys→util). 8/8 correctness sweep byte-identical to node; hello-world __text 3,710,432 (win kept). --- crates/perry-codegen/src/nm_install.rs | 16 +++++++++++----- .../perry-codegen/src/runtime_decls/objects.rs | 1 + crates/perry-runtime/src/node_submodules/mod.rs | 4 ++++ .../perry-runtime/src/object/class_registry.rs | 2 +- crates/perry-runtime/src/object/mod.rs | 3 +++ .../src/object/native_module_registry.rs | 16 +++++++++++----- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/crates/perry-codegen/src/nm_install.rs b/crates/perry-codegen/src/nm_install.rs index a97bfcd73..8d4fae88b 100644 --- a/crates/perry-codegen/src/nm_install.rs +++ b/crates/perry-codegen/src/nm_install.rs @@ -9,7 +9,7 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { let name = name.strip_prefix("node:").unwrap_or(name); match name { - "assert" => Some("js_nm_install_assert"), + "assert" | "assert/strict" => Some("js_nm_install_assert"), "async_hooks" => Some("js_nm_install_async_hooks"), "bigint" => Some("js_nm_install_bigint"), "buffer" | "buffer.Buffer" => Some("js_nm_install_buffer"), @@ -23,7 +23,7 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { "domain" => Some("js_nm_install_domain"), "events" => Some("js_nm_install_events"), "fs" => Some("js_nm_install_fs"), - "http" => Some("js_nm_install_http"), + "http" | "http2" | "https" => Some("js_nm_install_http"), "inspector" | "inspector.Network" | "inspector/promises" => Some("js_nm_install_inspector"), "module" => Some("js_nm_install_module"), "net" => Some("js_nm_install_net"), @@ -33,7 +33,7 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { Some("js_nm_install_perf") } "process" => Some("js_nm_install_process"), - "punycode" | "punycode.ucs2" => Some("js_nm_install_punycode"), + "punycode" | "punycode.ucs2" | "punycode.default" => Some("js_nm_install_punycode"), "querystring" => Some("js_nm_install_querystring"), "readline" => Some("js_nm_install_readline"), "repl" => Some("js_nm_install_repl"), @@ -45,8 +45,14 @@ pub(crate) fn nm_install_symbol(name: &str) -> Option<&'static str> { "tty" => Some("js_nm_install_tty"), "url" => Some("js_nm_install_url"), "util" | "util.types" | "util/types" => Some("js_nm_install_util"), - "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" - | "v8.startupSnapshot" => Some("js_nm_install_v8"), + "v8" + | "v8.Deserializer" + | "v8.GCProfiler" + | "v8.Serializer" + | "v8.promiseHooks" + | "v8.startupSnapshot" + | "v8.DefaultSerializer" + | "v8.DefaultDeserializer" => Some("js_nm_install_v8"), "vm" => Some("js_nm_install_vm"), "wasi" => Some("js_nm_install_wasi"), "zlib" => Some("js_nm_install_zlib"), diff --git a/crates/perry-codegen/src/runtime_decls/objects.rs b/crates/perry-codegen/src/runtime_decls/objects.rs index f0df8c992..8bbfa981d 100644 --- a/crates/perry-codegen/src/runtime_decls/objects.rs +++ b/crates/perry-codegen/src/runtime_decls/objects.rs @@ -211,6 +211,7 @@ pub fn declare_phase_b_objects(module: &mut LlModule) { module.declare_function("js_nm_install_tty", VOID, &[]); module.declare_function("js_nm_install_url", VOID, &[]); module.declare_function("js_nm_install_util", VOID, &[]); + module.declare_function("js_nm_install_v8", VOID, &[]); module.declare_function("js_nm_install_vm", VOID, &[]); module.declare_function("js_nm_install_wasi", VOID, &[]); module.declare_function("js_nm_install_zlib", VOID, &[]); diff --git a/crates/perry-runtime/src/node_submodules/mod.rs b/crates/perry-runtime/src/node_submodules/mod.rs index 7dba9d92e..44d644326 100644 --- a/crates/perry-runtime/src/node_submodules/mod.rs +++ b/crates/perry-runtime/src/node_submodules/mod.rs @@ -854,6 +854,8 @@ pub extern "C" fn js_node_submod_install_timers_promises() { } #[no_mangle] pub extern "C" fn js_node_submod_install_fs_promises() { + // `fs/promises` exposes `constants` backed by the native `fs` module. + crate::object::js_nm_install_fs(); SUBMOD_REGISTRY[SubmodBucket::FsPromises as usize].store( &SUBMOD_FS_PROMISES as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed, @@ -903,6 +905,8 @@ pub extern "C" fn js_node_submod_install_hono_jsx_streaming() { } #[no_mangle] pub extern "C" fn js_node_submod_install_sys() { + // `node:sys` is a deprecated alias that delegates to the native `util` module. + crate::object::js_nm_install_util(); SUBMOD_REGISTRY[SubmodBucket::Sys as usize].store( &SUBMOD_SYS as *const SubmoduleSpec as *mut SubmoduleSpec, Ordering::Relaxed, diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index b6ea97b51..415b069ce 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -1631,7 +1631,6 @@ pub extern "C" fn js_new_target_value() -> f64 { /// f64 array of length `args_len`. Falls back to a class_id=0 /// empty-object allocation when the function value isn't a closure /// (preserves the pre-fix baseline for misuse). -#[no_mangle] // ── Per-module constructor buckets (devirt phase 2) ──────────────────────── // `new .()` for node-module-namespaced constructors that the // old monolithic `js_new_function_construct` dispatched with a direct call to @@ -1789,6 +1788,7 @@ pub(crate) unsafe fn nm_ctor_stream( None } +#[no_mangle] pub unsafe extern "C" fn js_new_function_construct( func_value: f64, args_ptr: *const f64, diff --git a/crates/perry-runtime/src/object/mod.rs b/crates/perry-runtime/src/object/mod.rs index 7d9fe3c25..b19803a79 100644 --- a/crates/perry-runtime/src/object/mod.rs +++ b/crates/perry-runtime/src/object/mod.rs @@ -57,6 +57,9 @@ mod native_module_dispatch_crypto; mod native_module_registry; pub(crate) use native_module_registry::js_nm_enable_install_all; pub(crate) use native_module_registry::nm_ctor_lookup; +// Re-exported for submodule installers that delegate to a native module +// (`fs/promises` → `fs.constants`, `sys` → `util`). +pub(crate) use native_module_registry::{js_nm_install_fs, js_nm_install_util}; mod native_module_stream; mod native_this_alias; mod object_literal_ops; diff --git a/crates/perry-runtime/src/object/native_module_registry.rs b/crates/perry-runtime/src/object/native_module_registry.rs index c3869cdf3..414a51900 100644 --- a/crates/perry-runtime/src/object/native_module_registry.rs +++ b/crates/perry-runtime/src/object/native_module_registry.rs @@ -68,7 +68,7 @@ static NM_DISPATCH_REGISTRY: [AtomicPtr<()>; NM_BUCKET_COUNT] = /// no bucket fn, so it pins nothing. fn nm_module_index(name: &str) -> Option { match name { - "assert" => Some(NmBucket::Assert), + "assert" | "assert/strict" => Some(NmBucket::Assert), "async_hooks" => Some(NmBucket::AsyncHooks), "bigint" => Some(NmBucket::Bigint), "buffer" | "buffer.Buffer" => Some(NmBucket::Buffer), @@ -82,7 +82,7 @@ fn nm_module_index(name: &str) -> Option { "domain" => Some(NmBucket::Domain), "events" => Some(NmBucket::Events), "fs" => Some(NmBucket::Fs), - "http" => Some(NmBucket::Http), + "http" | "http2" | "https" => Some(NmBucket::Http), "inspector" | "inspector.Network" | "inspector/promises" => Some(NmBucket::Inspector), "module" => Some(NmBucket::Module), "net" => Some(NmBucket::Net), @@ -92,7 +92,7 @@ fn nm_module_index(name: &str) -> Option { Some(NmBucket::Perf) } "process" => Some(NmBucket::Process), - "punycode" | "punycode.ucs2" => Some(NmBucket::Punycode), + "punycode" | "punycode.ucs2" | "punycode.default" => Some(NmBucket::Punycode), "querystring" => Some(NmBucket::Querystring), "readline" => Some(NmBucket::Readline), "repl" => Some(NmBucket::Repl), @@ -104,8 +104,14 @@ fn nm_module_index(name: &str) -> Option { "tty" => Some(NmBucket::Tty), "url" => Some(NmBucket::Url), "util" | "util.types" | "util/types" => Some(NmBucket::Util), - "v8" | "v8.Deserializer" | "v8.GCProfiler" | "v8.Serializer" | "v8.promiseHooks" - | "v8.startupSnapshot" => Some(NmBucket::V8), + "v8" + | "v8.Deserializer" + | "v8.GCProfiler" + | "v8.Serializer" + | "v8.promiseHooks" + | "v8.startupSnapshot" + | "v8.DefaultSerializer" + | "v8.DefaultDeserializer" => Some(NmBucket::V8), "vm" => Some(NmBucket::Vm), "wasi" => Some(NmBucket::Wasi), "zlib" => Some(NmBucket::Zlib),