diff --git a/changelog.d/10532-call-arity-limits.md b/changelog.d/10532-call-arity-limits.md new file mode 100644 index 0000000000..dd093b2889 --- /dev/null +++ b/changelog.d/10532-call-arity-limits.md @@ -0,0 +1,11 @@ +### Fixed + +- **Calls with more than 16 arguments: closure-value calls failed to compile, dynamic calls dropped arguments, and `Reflect.apply` forwarded at most four (#10420, #10425).** Calling a function *value* (arrow, `var f = function f`, imported `var`/`const`, rest params) with 17+ arguments was a hard codegen error (`closure call with 18 args (max 16)`) at three `bail!` sites, and the node-submodule call path truncated to 16. qs 6.15.3's recursive 18-argument `stringify` could not compile. Several other ceilings silently dropped arguments: + - the `__perry_wrap_*` function-value wrappers took at most 16 params, while the runtime dispatched the registered full-width ABI into them, so `apply`/`call`/spread/object-literal-method calls of an 18-param function passed `0` for params 17 and 18 (class-method wrappers stopped at 32); + - `dispatch_with_arity` (33+ declared params) and `dispatch_rest_bundled` (16+ fixed params, which includes every `arguments` user with 16+ params) returned `undefined` without calling the body; + - `Reflect.apply` sent every list of four or more arguments through `js_closure_call4`; + - setTimeout/setInterval/`process.nextTick` clamped trailing arguments to nine. + + Closure-value call sites now share `lower_call::emit_closure_handle_call`: up to 16 arguments keep the `js_closure_call{N}` fast path (IR unchanged); wider calls marshal an entry-block buffer into `js_closure_call_array`. Wrappers take every declared parameter. Bodies wider than the runtime's exact transmute arms are called through a padded ladder of widths (64/256/1024 slots, `undefined`-filled) in `closure::wide_call`, which relies on the same caller-cleanup ABI property the existing more-args-than-declared transmutes use. Past 1024 slots the call throws `RangeError`. The wide `js_closure_call_array` arm now resolves its route with one memoized strategy probe instead of re-reading the body record, which makes a 20-argument closure-value call 10.5% cheaper in instructions. Reflect.apply, the timers and nextTick pass longer lists to the variadic entry point. + + Validation: new gap test `test_gap_10420_call_arity_limits` (17/18/32/64-argument calls across arrow, function-expression, imported and rest callees; apply/call/spread/Reflect.apply/Reflect.construct/bind; class and object-literal methods; `arguments.length`; timers; 3- and 16-argument controls) fails to compile on the baseline and matches Node 26.5.1 here, plus codegen and runtime unit tests. Gap suite: no regressions against the baseline. Instruction counts for 3-argument closure-value loops, `fn.apply` with 5 arguments and `benchmarks/suite/14_closure.ts`/`09_method_calls.ts` are unchanged. qs 6.15.3 compiles from source; its next blocker is #10482. diff --git a/crates/perry-codegen/src/codegen/artifacts.rs b/crates/perry-codegen/src/codegen/artifacts.rs index 4bb32f4e03..695ecc65a0 100644 --- a/crates/perry-codegen/src/codegen/artifacts.rs +++ b/crates/perry-codegen/src/codegen/artifacts.rs @@ -777,16 +777,17 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { // dead-code elimination at link time will remove unused ones. for f in &hir.functions { let original_name = func_names.get(&f.id).cloned().unwrap(); - // Wrapper signature: i64 closure_ptr + N doubles for args. Cap at 16 to - // match the `js_closure_call0..16` dispatch family (the closure-call ABI - // tops out at 16 positional args; a function with more must be reached - // via a rest-bundling path). Pre-fix this was capped at 5 with a stale - // "js_closure_call only goes up to 5 args" comment, so any function - // invoked as a closure value (object-literal method, callback, `apply` - // target) with 6+ params silently dropped every argument past the 5th - // — e.g. test262's `TemporalHelpers.assertDuration(d, y, mo, w, d, h, …)` - // (11 args) read `hours` onward as 0. - let arity = f.params.len().min(16); + // Wrapper signature: i64 closure_ptr + one double per declared param — + // the full ABI arity `user_fn_wrapper_arity` registers for dynamic + // dispatch. Any cap here silently drops arguments: at 5, test262's + // `TemporalHelpers.assertDuration(d, y, mo, w, d, h, …)` (11 args) read + // `hours` onward as 0; at 16 (#10420), every `apply`/`call`/spread or + // object-literal-method call of an 18-param function passed `0` for + // params 17 and 18, because the runtime dispatched the registered + // 18-slot signature into a 16-param wrapper. Calls wider than the + // `js_closure_call0..16` family reach the wrapper through + // `js_closure_call_array`. + let arity = f.params.len(); let arg_names: Vec = (0..arity).map(|i| format!("%a{}", i)).collect(); let mut wrap_params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; for name in &arg_names { @@ -839,7 +840,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { continue; } - let arity = f.params.len().min(16); + let arity = f.params.len(); let mut params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; params.extend((0..arity).map(|i| (DOUBLE, format!("%a{}", i)))); let alias = llmod.define_function(&alias_wrap, DOUBLE, params); @@ -1064,9 +1065,9 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { let target_wrap = format!("__perry_wrap_{}", target); if !llmod.has_function(&raw_wrap) && emitted_aliases.insert(raw_wrap.clone()) { if llmod.has_function(&target_wrap) { - // Match the canonical wrapper's closure-call ABI (up to - // 16 positional arguments), including renamed exports. - let arity = function.map(|f| f.params.len().min(16)).unwrap_or(5); + // Match the canonical wrapper's closure-call ABI (one + // double per declared param), including renamed exports. + let arity = function.map(|f| f.params.len()).unwrap_or(5); let mut params = vec![(I64, "%this_closure".to_string())]; params.extend((0..arity).map(|i| (DOUBLE, format!("%a{}", i)))); let wf = llmod.define_function(&raw_wrap, DOUBLE, params.clone()); @@ -1234,7 +1235,7 @@ pub(super) fn emit_module_artifacts(c: ModuleArtifactsCtx<'_>) -> Result<()> { if !emitted_wrappers.insert(wrap_name.clone()) { continue; } - let arity = method.params.len().min(32); + let arity = method.params.len(); let mut wrap_params: Vec<(LlvmType, String)> = vec![(I64, "%this_closure".to_string())]; for i in 0..arity { wrap_params.push((DOUBLE, format!("%a{}", i))); diff --git a/crates/perry-codegen/src/codegen/helpers.rs b/crates/perry-codegen/src/codegen/helpers.rs index aa7ff04a61..5f8b1e1b16 100644 --- a/crates/perry-codegen/src/codegen/helpers.rs +++ b/crates/perry-codegen/src/codegen/helpers.rs @@ -1494,7 +1494,7 @@ pub(super) fn emit_namespace_populator( // not be collapsed to `_constructor` here (#7964). sanitize_member(source_local) ); - let arity = (*param_count).min(16); + let arity = *param_count; let mut wrapper_params: Vec = vec![I64]; wrapper_params.extend(std::iter::repeat_n(DOUBLE, arity)); ctx.pending_declares diff --git a/crates/perry-codegen/src/expr/static_method.rs b/crates/perry-codegen/src/expr/static_method.rs index ce0c5dd37b..f3c6d2d1cf 100644 --- a/crates/perry-codegen/src/expr/static_method.rs +++ b/crates/perry-codegen/src/expr/static_method.rs @@ -360,33 +360,15 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { let blk = ctx.block(); unbox_to_i64(blk, closure_box) }; - if lowered.len() <= 16 { - let runtime_fn = format!("js_closure_call{}", lowered.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for value in lowered.iter() { - call_args.push((DOUBLE, value.as_str())); - } - return Ok(ctx.block().call(DOUBLE, &runtime_fn, &call_args)); - } - // #3527: namespace members backed by exported // closure getters need the same arbitrary-arity // array dispatch as ordinary closure values. // Effect's `Layer.mergeAll` is a rest closure // and OpenCode passes 18 layers here. - let n = lowered.len(); - let buf = ctx.func.alloca_entry_array(DOUBLE, n); - let blk = ctx.block(); - for (i, value) in lowered.iter().enumerate() { - let slot = blk.gep(DOUBLE, &buf, &[(I64, &i.to_string())]); - blk.store(DOUBLE, value, &slot); - } - let argc = n.to_string(); - Ok(blk.call( - DOUBLE, - "js_closure_call_array", - &[(I64, &closure_handle), (PTR, &buf), (I64, &argc)], + Ok(crate::lower_call::emit_closure_handle_call( + ctx, + &closure_handle, + &lowered, )) }, )?; diff --git a/crates/perry-codegen/src/lower_call/closure_call_arity_tests.rs b/crates/perry-codegen/src/lower_call/closure_call_arity_tests.rs new file mode 100644 index 0000000000..b517a1a2ea --- /dev/null +++ b/crates/perry-codegen/src/lower_call/closure_call_arity_tests.rs @@ -0,0 +1,142 @@ +//! #10420: calls through a function VALUE have no argument-count ceiling. +//! +//! A closure-typed local called with more than 16 arguments used to fail to +//! compile (`closure call with 18 args (max 16)`), and the `__perry_wrap_*` +//! value wrappers of top-level functions were capped at 16 params, so every +//! dynamic call of an 18-param function (`apply`/`call`/spread/object-literal +//! method) passed `0` for params 17 and 18. Up to 16 arguments must keep the +//! per-arity `js_closure_call{N}` fast path; wider calls marshal a stack buffer +//! into `js_closure_call_array`. + +use crate::{compile_module, CompileOptions}; +use perry_hir::types::{FunctionType, Type}; +use perry_hir::{Expr, Function, Module, Param, Stmt}; + +fn param(id: u32) -> Param { + Param { + id, + name: format!("p{id}"), + ty: Type::Any, + default: None, + decorators: Vec::new(), + is_rest: false, + arguments_object: None, + } +} + +fn params(base: u32, count: usize) -> Vec { + (0..count as u32).map(|i| param(base + i)).collect() +} + +fn function_type(count: usize) -> Type { + Type::Function(FunctionType { + params: (0..count) + .map(|i| (format!("p{i}"), Type::Any, false)) + .collect(), + return_type: Box::new(Type::Any), + is_async: false, + is_generator: false, + }) +} + +fn ir(module: &Module) -> String { + let opts = CompileOptions { + emit_ir_only: true, + ..Default::default() + }; + String::from_utf8(compile_module(module, opts).expect("fixture must compile")) + .expect("LLVM IR is UTF-8") +} + +/// `const f: (p0, …) => any = (p0, …) => p; f(1, 2, …, argc)`. +fn closure_value_call_ir(file: &str, argc: usize) -> String { + let mut module = Module::new(file); + module.init.push(Stmt::Let { + id: 1, + name: "f".to_string(), + ty: function_type(argc), + mutable: false, + init: Some(Expr::Closure { + func_id: 7, + params: params(100, argc), + return_type: Type::Any, + body: vec![Stmt::Return(Some(Expr::LocalGet(100 + argc as u32 - 1)))], + captures: Vec::new(), + mutable_captures: Vec::new(), + captures_this: false, + captures_new_target: false, + enclosing_class: None, + is_arrow: true, + is_async: false, + is_generator: false, + is_strict: true, + }), + }); + module.init.push(Stmt::Expr(Expr::Call { + callee: Box::new(Expr::LocalGet(1)), + args: (1..=argc).map(|i| Expr::Number(i as f64)).collect(), + type_args: Vec::new(), + byte_offset: 0, + })); + ir(&module) +} + +#[test] +fn a_wide_closure_value_call_compiles_through_the_array_path() { + let ir = closure_value_call_ir("closure_call_arity_wide.ts", 18); + assert!( + ir.contains("call double @js_closure_call_array(i64 ") && ir.contains(", i64 18)"), + "an 18-argument closure-value call must dispatch through \ + js_closure_call_array with argc 18:\n{ir}" + ); + assert!( + !ir.contains("@js_closure_call18("), + "no fixed-arity entry point exists past 16:\n{ir}" + ); +} + +#[test] +fn a_narrow_closure_value_call_keeps_the_fixed_arity_fast_path() { + let ir = closure_value_call_ir("closure_call_arity_narrow.ts", 3); + assert!( + ir.contains("call double @js_closure_call3(i64 "), + "a 3-argument closure-value call must keep js_closure_call3:\n{ir}" + ); + assert!( + !ir.contains("call double @js_closure_call_array("), + "a 3-argument call must not take the array path:\n{ir}" + ); +} + +#[test] +fn function_value_wrappers_take_every_declared_param() { + let mut module = Module::new("closure_call_arity_wrapper.ts"); + module.functions = vec![Function { + id: 1, + name: "wide".to_string(), + type_params: Vec::new(), + params: params(10, 18), + return_type: Type::Any, + body: vec![Stmt::Return(Some(Expr::LocalGet(27)))], + is_async: false, + is_generator: false, + is_strict: true, + was_plain_async: false, + was_unrolled: false, + is_exported: false, + captures: Vec::new(), + decorators: Vec::new(), + }]; + let ir = ir(&module); + let define = ir + .lines() + .find(|line| { + line.starts_with("define") && line.contains("@__perry_wrap_") && line.contains("wide(") + }) + .unwrap_or_else(|| panic!("expected the value wrapper of `wide`:\n{ir}")); + assert_eq!( + define.matches("double %a").count(), + 18, + "the wrapper must forward all 18 params, not a 16-param prefix: {define}" + ); +} diff --git a/crates/perry-codegen/src/lower_call/console_promise.rs b/crates/perry-codegen/src/lower_call/console_promise.rs index e0240a0a19..2961323cbb 100644 --- a/crates/perry-codegen/src/lower_call/console_promise.rs +++ b/crates/perry-codegen/src/lower_call/console_promise.rs @@ -1640,43 +1640,22 @@ fn lower_closure_call_rooted<'a>( lowered_args.push(group.reread(ctx, arg_base + i)?); } - let result = if lowered_args.len() <= 16 { - let runtime_fn = if receiverless_one_arg { - "js_closure_call1_receiverless".to_string() - } else { - format!("js_closure_call{}", lowered_args.len()) - }; - let blk = ctx.block(); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = vec![(I64, &closure_handle)]; - for v in &lowered_args { - call_args.push((DOUBLE, v.as_str())); - } - blk.call(DOUBLE, &runtime_fn, &call_args) + let result = if receiverless_one_arg { + ctx.block().call( + DOUBLE, + "js_closure_call1_receiverless", + &[(I64, &closure_handle), (DOUBLE, &lowered_args[0])], + ) } else { - // #3527: > 16 args — stack-allocate a `[N x double]` array (entry-block - // alloca, see #167), store each lowered arg, and dispatch through the - // variadic `js_closure_call_array(closure_i64, args_ptr, argc)`. This - // mirrors the `js_native_call_value` marshaling used elsewhere in - // lower_call. `args_ptr` is non-null here since argc > 16 > 0. + // #3527: > 16 args marshal into an entry-block `[N x double]` buffer and + // dispatch through the variadic `js_closure_call_array`. // // #7154: the stores happen below the unbox now. A stack buffer is not a // GC root, so filling it above an allocating rebind would freeze // pre-move addresses into it — the same staleness one indirection // further out. The stores have no observable effect, so moving them // below the throw-capable unbox changes nothing else. - let n = lowered_args.len(); - let buf = ctx.func.alloca_entry_array(DOUBLE, n); - let blk = ctx.block(); - for (i, v) in lowered_args.iter().enumerate() { - let slot = blk.gep(DOUBLE, &buf, &[(I64, &format!("{}", i))]); - blk.store(DOUBLE, v, &slot); - } - let argc = n.to_string(); - blk.call( - DOUBLE, - "js_closure_call_array", - &[(I64, &closure_handle), (PTR, &buf), (I64, &argc)], - ) + super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args) }; // #7211: re-read the saved implicit `this` from its slot. Mandatory, not diff --git a/crates/perry-codegen/src/lower_call/early_branches.rs b/crates/perry-codegen/src/lower_call/early_branches.rs index cfe395a87d..88c93a3785 100644 --- a/crates/perry-codegen/src/lower_call/early_branches.rs +++ b/crates/perry-codegen/src/lower_call/early_branches.rs @@ -10,7 +10,7 @@ //! Each `try_lower_*` returns `Ok(Some(s))` when it handled the call, //! `Ok(None)` to let the caller try the next branch. -use anyhow::{bail, Result}; +use anyhow::Result; use perry_hir::types::Type as HirType; use perry_hir::Expr; @@ -351,20 +351,12 @@ pub fn try_lower_current_step_closure_call( for a in args { lowered_args.push(lower_expr(ctx, a)?); } - if lowered_args.len() > 16 { - bail!( - "perry-codegen Phase D.1: CurrentStepClosure call with {} args (max 16)", - lowered_args.len() - ); - } - let blk = ctx.block(); - let closure_handle = unbox_to_i64(blk, &recv_box); - let runtime_fn = format!("js_closure_call{}", lowered_args.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = vec![(I64, &closure_handle)]; - for v in &lowered_args { - call_args.push((DOUBLE, v.as_str())); - } - return Ok(Some(blk.call(DOUBLE, &runtime_fn, &call_args))); + let closure_handle = unbox_to_i64(ctx.block(), &recv_box); + return Ok(Some(super::emit_closure_handle_call( + ctx, + &closure_handle, + &lowered_args, + ))); } Ok(None) } @@ -445,12 +437,10 @@ pub fn try_lower_closure_typed_local_call( // FuncRef calls (direct function-symbol dispatch) keep their // static-bundling at lower_call.rs:444+ because they don't go // through js_closure_callN. - if lowered_args.len() > 16 { - bail!( - "perry-codegen Phase D.1: closure call with {} args (max 16)", - lowered_args.len() - ); - } + // + // #10420: no arity ceiling here. More than 16 arguments dispatch + // through `js_closure_call_array` (`emit_closure_handle_call`); the + // exact-closure direct arm below is an ordinary N-argument call. // Re-read below the argument lowering, THEN unmask: the unmask // must consume the post-relocation address. let recv_box = callee_group.reread(ctx, callee_root)?; @@ -531,12 +521,8 @@ pub fn try_lower_closure_typed_local_call( ctx.current_block = fallback_idx; let prev_this = crate::rooting::implicit_this_save(ctx, &undef_this); - let runtime_fn = format!("js_closure_call{}", lowered_args.len()); - let mut fallback_args: Vec<(crate::types::LlvmType, &str)> = - Vec::with_capacity(lowered_args.len() + 1); - fallback_args.push((I64, &closure_handle)); - fallback_args.extend(lowered_args.iter().map(|value| (DOUBLE, value.as_str()))); - let fallback_value = ctx.block().call(DOUBLE, &runtime_fn, &fallback_args); + let fallback_value = + super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); crate::rooting::implicit_this_restore(ctx, prev_this); let after_fallback = ctx.block().label.clone(); if !ctx.block().is_terminated() { @@ -1183,13 +1169,8 @@ pub fn try_lower_closure_typed_local_call( } else { None }; - let runtime_fn = format!("js_closure_call{}", lowered_args.len()); - let mut fallback_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in &lowered_args { - fallback_args.push((DOUBLE, v.as_str())); - } - let fallback_value = ctx.block().call(DOUBLE, &runtime_fn, &fallback_args); + let fallback_value = + super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); // Inner save, released inside its own arm — so the outer // slot (restored in the merge block) is still live and the // temp-root depth matches on both paths into the merge. @@ -1223,12 +1204,7 @@ pub fn try_lower_closure_typed_local_call( // read `this`, so the reset is unconditional here. // #7211: rooted save/restore across the runtime-resolved callee. let prev_this = crate::rooting::implicit_this_save(ctx, &undef_this); - let runtime_fn = format!("js_closure_call{}", lowered_args.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = vec![(I64, &closure_handle)]; - for v in &lowered_args { - call_args.push((DOUBLE, v.as_str())); - } - let result = ctx.block().call(DOUBLE, &runtime_fn, &call_args); + let result = super::emit_closure_handle_call(ctx, &closure_handle, &lowered_args); crate::rooting::implicit_this_restore(ctx, prev_this); callee_group.release(ctx); return Ok(Some(result)); diff --git a/crates/perry-codegen/src/lower_call/extern_func.rs b/crates/perry-codegen/src/lower_call/extern_func.rs index c04d15dad8..bbbee2bdb7 100644 --- a/crates/perry-codegen/src/lower_call/extern_func.rs +++ b/crates/perry-codegen/src/lower_call/extern_func.rs @@ -1355,18 +1355,12 @@ pub fn try_lower_extern_func_call( let blk = ctx.block(); let closure_bits = blk.bitcast_double_to_i64(&closure_value); let closure_handle = blk.and(I64, &closure_bits, POINTER_MASK_I64); - let call_name = format!("js_closure_call{}", lowered_args.len().min(16)); - let mut decl_types = vec![I64]; - decl_types.extend(std::iter::repeat_n(DOUBLE, lowered_args.len().min(16))); - ctx.pending_declares - .push((call_name.clone(), DOUBLE, decl_types)); - let mut call_args: Vec<(crate::types::LlvmType, String)> = vec![(I64, closure_handle)]; - for arg in lowered_args.into_iter().take(16) { - call_args.push((DOUBLE, arg)); - } - let arg_refs: Vec<(crate::types::LlvmType, &str)> = - call_args.iter().map(|(t, s)| (*t, s.as_str())).collect(); - return Ok(Some(ctx.block().call(DOUBLE, &call_name, &arg_refs))); + // #10420: no 16-argument truncation — wider calls take the array path. + return Ok(Some(super::emit_closure_handle_call( + ctx, + &closure_handle, + &lowered_args, + ))); } // perry/system dispatch: map JS names (isDarkMode, getDeviceIdiom, // keychainSave, etc.) to their perry_system_* / perry_* C symbols. @@ -1866,12 +1860,6 @@ pub fn try_lower_extern_func_call( // an arrow-bound exported value (hono's `mergePath` from utils/url.js, // any `export const foo = () => …` cross-module use). if ctx.imported_vars.contains(name) { - if args.len() > 16 { - anyhow::bail!( - "perry-codegen Phase D.1: closure call with {} args (max 16)", - args.len() - ); - } ctx.pending_declares.push((fname.clone(), DOUBLE, vec![])); // Fetch the callee before evaluating arguments, as JavaScript requires, // but keep that closure rooted while argument expressions run. Next's @@ -1895,16 +1883,14 @@ pub fn try_lower_extern_func_call( |ctx, closure_box| { // Re-read and unbox only after every collecting argument and // after the argument group's own re-reads have completed. + // #10420: more than 16 arguments dispatch through the array path. let lowered = lowered_args.borrow(); - let blk = ctx.block(); - let closure_handle = unbox_to_i64(blk, closure_box); - let runtime_fn = format!("js_closure_call{}", lowered.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for value in lowered.iter() { - call_args.push((DOUBLE, value.as_str())); - } - Ok(blk.call(DOUBLE, &runtime_fn, &call_args)) + let closure_handle = unbox_to_i64(ctx.block(), closure_box); + Ok(super::emit_closure_handle_call( + ctx, + &closure_handle, + &lowered, + )) }, )?; return Ok(Some(result)); diff --git a/crates/perry-codegen/src/lower_call/mod.rs b/crates/perry-codegen/src/lower_call/mod.rs index deee0dc456..77cffef49f 100644 --- a/crates/perry-codegen/src/lower_call/mod.rs +++ b/crates/perry-codegen/src/lower_call/mod.rs @@ -39,6 +39,10 @@ mod builtin; mod builtin_table_gate; mod capture_writeback; mod closure_analysis; +/// #10420: closure-value calls with more than 16 arguments compile and take +/// the array path; function-value wrappers keep every declared param. +#[cfg(test)] +mod closure_call_arity_tests; mod console_promise; /// Rooting and evaluation-order coverage for the `console.*` arms slice 6 /// repaired (#7649) — see the module header for why these assert on IR. @@ -257,6 +261,51 @@ pub(crate) fn emit_rooted_call( result } +/// Widest call the per-arity `js_closure_call{N}` runtime entry points take. +pub(crate) const MAX_FIXED_CLOSURE_CALL_ARGS: usize = 16; + +/// Dispatch an unboxed closure handle over already-lowered arguments through +/// the closure-call ABI. +/// +/// Up to [`MAX_FIXED_CLOSURE_CALL_ARGS`] arguments use the per-arity +/// `js_closure_call{N}` register entry points — the fast path, unchanged. +/// Wider calls marshal the arguments into an entry-block `[N x double]` buffer +/// and dispatch through the variadic `js_closure_call_array(closure, args_ptr, +/// argc)`, which owns arbitrary-arity dispatch including rest bundling (#3527). +/// #10420: every closure-value call site used to either reject a 17th argument +/// at compile time or truncate the list to 16; they all route here now. +/// +/// The buffer is NOT a GC root, so callers pass values that are already valid +/// below their last collection point; nothing emitted between the stores and +/// the call can collect. +pub(crate) fn emit_closure_handle_call( + ctx: &mut FnCtx<'_>, + closure_handle: &str, + args: &[String], +) -> String { + use crate::types::{DOUBLE, I64, PTR}; + if args.len() <= MAX_FIXED_CLOSURE_CALL_ARGS { + let runtime_fn = format!("js_closure_call{}", args.len()); + let mut call_args: Vec<(crate::types::LlvmType, &str)> = Vec::with_capacity(args.len() + 1); + call_args.push((I64, closure_handle)); + call_args.extend(args.iter().map(|value| (DOUBLE, value.as_str()))); + return ctx.block().call(DOUBLE, &runtime_fn, &call_args); + } + let n = args.len(); + let buf = ctx.func.alloca_entry_array(DOUBLE, n); + let blk = ctx.block(); + for (i, value) in args.iter().enumerate() { + let slot = blk.gep(DOUBLE, &buf, &[(I64, &i.to_string())]); + blk.store(DOUBLE, value, &slot); + } + let argc = n.to_string(); + blk.call( + DOUBLE, + "js_closure_call_array", + &[(I64, closure_handle), (PTR, &buf), (I64, &argc)], + ) +} + /// One array a rest/`arguments` call has to materialize from its argument /// list: every argument from `from` onwards, optionally flagged as an /// `arguments` object. diff --git a/crates/perry-codegen/src/lower_call/namespace_call.rs b/crates/perry-codegen/src/lower_call/namespace_call.rs index 3d5a91e551..70260d7f1b 100644 --- a/crates/perry-codegen/src/lower_call/namespace_call.rs +++ b/crates/perry-codegen/src/lower_call/namespace_call.rs @@ -349,32 +349,14 @@ pub fn try_lower_namespace_member_call( let blk = ctx.block(); unbox_to_i64(blk, closure_box) }; - if lowered.len() <= 16 { - let runtime_fn = format!("js_closure_call{}", lowered.len()); - let mut call_args: Vec<(crate::types::LlvmType, &str)> = - vec![(I64, &closure_handle)]; - for v in lowered.iter() { - call_args.push((DOUBLE, v.as_str())); - } - return Ok(ctx.block().call(DOUBLE, &runtime_fn, &call_args)); - } - // #3527: the runtime's array dispatcher owns arbitrary-arity // closure calls (including rest bundling). Keep the fixed-N // entry points as the small fast path and marshal wider calls // into a stack buffer after all rooted operand re-reads. - let n = lowered.len(); - let buf = ctx.func.alloca_entry_array(DOUBLE, n); - let blk = ctx.block(); - for (i, value) in lowered.iter().enumerate() { - let slot = blk.gep(DOUBLE, &buf, &[(I64, &i.to_string())]); - blk.store(DOUBLE, value, &slot); - } - let argc = n.to_string(); - Ok(blk.call( - DOUBLE, - "js_closure_call_array", - &[(I64, &closure_handle), (PTR, &buf), (I64, &argc)], + Ok(super::emit_closure_handle_call( + ctx, + &closure_handle, + &lowered, )) }, )?; diff --git a/crates/perry-runtime/src/builtins/globals.rs b/crates/perry-runtime/src/builtins/globals.rs index 6fc89dac9e..acbded0288 100644 --- a/crates/perry-runtime/src/builtins/globals.rs +++ b/crates/perry-runtime/src/builtins/globals.rs @@ -1151,7 +1151,7 @@ pub extern "C" fn js_drain_queued_microtasks() { pub(crate) fn drain_queued_microtasks_count() -> i32 { use crate::closure::{ js_closure_call0, js_closure_call1, js_closure_call2, js_closure_call3, js_closure_call4, - js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, js_closure_call9, + js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, }; let mut ran = 0; loop { @@ -1203,14 +1203,10 @@ pub(crate) fn drain_queued_microtasks_count() -> i32 { 8 => { js_closure_call8(cb_ptr, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); } - _ => { - // >= 9 args: clamp to 9. Mirrors the setTimeout - // dispatch fallback; real-world nextTick rarely - // exceeds 1-2 trailing args. - js_closure_call9( - cb_ptr, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], - ); - } + // #10420: more than 8 trailing args used to clamp to 9. + n => unsafe { + crate::closure::js_closure_call_array(cb_ptr as i64, a.as_ptr(), n as i64); + }, } crate::async_hooks::after(async_id); crate::async_hooks::destroy(async_id); diff --git a/crates/perry-runtime/src/closure/dispatch.rs b/crates/perry-runtime/src/closure/dispatch.rs index 54ec8dc8e4..b9c829849a 100644 --- a/crates/perry-runtime/src/closure/dispatch.rs +++ b/crates/perry-runtime/src/closure/dispatch.rs @@ -31,7 +31,6 @@ pub use errors::throw_not_callable; pub use validate::{clean_closure_ptr, dispatch_proxy_callee_or_throw, get_valid_func_ptr}; -pub(crate) use calln::{dispatch_registered_call, dispatch_rest_or_declared_arity}; pub use calln::{ js_closure_call0, js_closure_call1, js_closure_call10, js_closure_call11, js_closure_call12, js_closure_call13, js_closure_call14, js_closure_call15, js_closure_call16, diff --git a/crates/perry-runtime/src/closure/dispatch/value_call.rs b/crates/perry-runtime/src/closure/dispatch/value_call.rs index f2ecef6f9f..329d1381ab 100644 --- a/crates/perry-runtime/src/closure/dispatch/value_call.rs +++ b/crates/perry-runtime/src/closure/dispatch/value_call.rs @@ -655,10 +655,16 @@ pub unsafe extern "C" fn js_closure_call_array( // arg slice and dispatch through the strategy resolver so the // closure body is called with ALL its args (the old `_ => // js_closure_call16(...)` silently dropped args 16.. — breaking - // qs's recursive `stringify`, which self-calls with 18 args). For - // a plain (Direct) closure with no registered rest/arity, dispatch - // through `dispatch_with_arity` with the provided count so the body - // is transmuted to its real N-arg signature. + // qs's recursive `stringify`, which self-calls with 18 args). + // + // #10420: ONE memoized strategy probe decides the route, as in + // `js_closure_callN` — the registry helpers this arm used to chain + // re-read the body record on every call. A body with a registered + // arity is called at exactly that width: padded when it declares + // more than `n`, and never handed slots it does not declare (so a + // `fn.apply(null, arr)` with thousands of elements costs the body's + // own width). An unregistered body is a runtime-provided callee with + // a handful of params; clamp it to the widest dynamic call. _ => { let mut full: Vec = Vec::with_capacity(n); for i in 0..n { @@ -668,18 +674,20 @@ pub unsafe extern "C" fn js_closure_call_array( if func_ptr.is_null() { throw_not_callable(); } - if let Some(result) = dispatch_registered_call(closure, func_ptr, &full) { - return result; + match resolve_strategy(func_ptr).kind() { + DispatchKind::BoundMethod => dispatch_bound_method(closure, &full), + DispatchKind::BoundFunction => dispatch_bound_function(closure, &full), + DispatchKind::Rest(fixed_arity, synth) => { + dispatch_rest_bundled(closure, func_ptr, &full, fixed_arity, synth) + } + DispatchKind::Arity(declared) => { + dispatch_with_arity(closure, func_ptr, &full, declared) + } + DispatchKind::Direct => { + let width = n.min(crate::closure::MAX_DYNAMIC_CALL_WIDTH) as u32; + dispatch_with_arity(closure, func_ptr, &full, width) + } } - if let Some(result) = - dispatch_rest_or_declared_arity(closure, func_ptr, &full, n as u32) - { - return result; - } - // Direct closure: declared arity == provided count. Reuse the - // arity dispatcher (it transmutes to the concrete N-arg fn and - // forwards the slice unchanged when provided == declared). - dispatch_with_arity(closure, func_ptr, &full, n as u32) } } } diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index 4effa0b213..a7e4210220 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -12,6 +12,7 @@ mod dynamic_props; mod registry; mod unbox; mod v8_stubs; +mod wide_call; #[cfg(test)] mod tests; @@ -30,6 +31,7 @@ pub use alloc::{ pub(crate) use registry::closure_registry_census; pub(crate) use registry::DispatchKind; +pub(crate) use wide_call::{dispatch_wide_abi, MAX_DYNAMIC_CALL_WIDTH}; /// `PERRY_GC_CENSUS`: every closure-keyed side table outside the registries. pub(crate) fn closure_side_table_census() -> Vec { diff --git a/crates/perry-runtime/src/closure/registry.rs b/crates/perry-runtime/src/closure/registry.rs index 2ed6d2ba16..23fe0b38ef 100644 --- a/crates/perry-runtime/src/closure/registry.rs +++ b/crates/perry-runtime/src/closure/registry.rs @@ -1002,10 +1002,8 @@ pub unsafe fn build_rest_array(values: &[f64], arguments_object: bool) -> f64 { /// `fixed_arity` onwards is bundled into a fresh JS Array passed as the /// last arg. The body is then invoked with exactly `fixed_arity + 1` doubles. /// -/// Currently supports `fixed_arity` in `0..=15` — the same ceiling as -/// `js_closure_callN`. A program that defines a rest closure with more than -/// 15 fixed params before the rest is unsupported (and would already trip -/// the `Phase D.1: closure call with N args (max 16)` guard in lower_call). +/// `fixed_arity` 0..=15 have exact arms; wider bodies go through the padded +/// ladder in `wide_call` (#10420). #[inline(never)] pub unsafe fn dispatch_rest_bundled( closure: *const ClosureHeader, @@ -1282,10 +1280,18 @@ pub unsafe fn dispatch_rest_bundled( 14 => rest_arm!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 15 => rest_arm!(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), _ => { - // Unsupported arity — fall back to undefined so we don't - // mis-call the body and trigger UB. This mirrors the upper - // bound that lower_call's static-bundling path enforces. - f64::from_bits(crate::value::TAG_UNDEFINED) + // #10420: 16+ fixed params used to return `undefined` without + // calling the body. Lay out the full ABI — fixed params, rest + // array, then the `arguments` object when the body has one — and + // call through the padded ladder. + let mut slots: Vec = Vec::with_capacity(k + 2); + slots.extend((0..k).map(|i| a!(i))); + slots.push(rest_double); + if let Some(arguments_double) = all_arguments_double { + slots.push(arguments_double); + } + let width = slots.len(); + super::dispatch_wide_abi(closure, func_ptr, &slots, width) } } } @@ -1326,10 +1332,11 @@ pub unsafe fn dispatch_with_arity( } // One match arm per declared arity. Each arm transmutes `func_ptr` to // the concrete `(closure, f64 x N)` signature and forwards the (padded) - // args. Arities up to 32 are supported so high-arity closures dispatched + // args. Arities up to 32 have exact arms so high-arity closures dispatched // dynamically — e.g. qs's recursive `stringify`, which declares 18 // params and self-calls with 18 args (#3527) — call their body - // correctly instead of mis-calling and corrupting registers. The + // correctly instead of mis-calling and corrupting registers; wider + // bodies take the padded ladder in `wide_call` (#10420). The // `arm!` macro builds the fn type and the (padded) call args from the // arg-index token list; `arm!(@ty $i)` maps any index token to `f64`. macro_rules! arm { @@ -1405,11 +1412,9 @@ pub unsafe fn dispatch_with_arity( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ), - _ => { - // Unsupported arity (>32 declared params). Fall back to - // undefined rather than mis-calling and triggering UB. - f64::from_bits(crate::value::TAG_UNDEFINED) - } + // #10420: more than 32 declared params used to return `undefined` + // without calling the body. + _ => super::dispatch_wide_abi(closure, func_ptr, args, k), } } diff --git a/crates/perry-runtime/src/closure/wide_call.rs b/crates/perry-runtime/src/closure/wide_call.rs new file mode 100644 index 0000000000..841e81a0c8 --- /dev/null +++ b/crates/perry-runtime/src/closure/wide_call.rs @@ -0,0 +1,270 @@ +//! Closure-body calls wider than the exact per-arity dispatch arms (#10420). +//! +//! Dynamic dispatch reaches a closure body by transmuting its `func_ptr` to +//! `extern "C" fn(*const ClosureHeader, f64, …) -> f64` with the body's ABI +//! width: its declared params, plus the rest array and the `arguments` slot +//! when it has them. Rust has no variadic function types, so every width needs +//! its own transmute — `dispatch_with_arity` spells out 0..=32 and +//! `dispatch_rest_bundled` 0..=15 fixed params. Past those, both returned +//! `undefined` WITHOUT calling the body. +//! +//! Wider bodies go through a short ladder of PADDED widths instead: the slots +//! are copied into a buffer of the next ladder width, the tail is filled with +//! `undefined`, and the body is called through that wider signature. That is +//! sound for the same reason every `js_closure_callN` call with more arguments +//! than the body declares already is: on each ABI Perry targets (SysV x86-64, +//! AAPCS64 including Apple's variant, Win64) scalar arguments are assigned +//! left to right and the stack argument area belongs to the caller, so a body +//! declaring N doubles reads exactly the first N slots and ignores the rest. + +use super::ClosureHeader; + +/// Widest closure-body ABI dynamic dispatch can reach. A body wider than this +/// declares more than a thousand parameters; calling it through `apply`/`call`/ +/// spread/a closure value throws a `RangeError` instead of silently skipping +/// the body. +pub(crate) const MAX_DYNAMIC_CALL_WIDTH: usize = 1024; + +/// Call `func_ptr` as a closure body whose ABI takes `width` doubles, passing +/// `args[i]` for `i < min(args.len(), width)` and `undefined` for the rest. +/// +/// Callers use this only past their exact arms, so `width` is at least 16. +/// `width` above [`MAX_DYNAMIC_CALL_WIDTH`] throws `RangeError`. +/// +/// # Safety +/// `func_ptr` must be a validated, non-sentinel closure body whose ABI width +/// is at most `width`. +#[inline(never)] +pub(crate) unsafe fn dispatch_wide_abi( + closure: *const ClosureHeader, + func_ptr: *const u8, + args: &[f64], + width: usize, +) -> f64 { + let undef = f64::from_bits(crate::value::TAG_UNDEFINED); + let provided = args.len().min(width); + + // `padded!(slots, [0], 1, + + …)` doubles the index list once per `+` + // (six doublings = 64 indices) and emits the transmuted call with one + // `f64` parameter per index. The indices are constant expressions into a + // fixed-size array, so the loads carry no bounds checks. + macro_rules! padded { + (@f64 $i:expr) => { f64 }; + ($slots:ident, [$($i:expr),+], $step:expr, + $($more:tt)*) => { + padded!($slots, [$($i,)+ $($i + $step),+], $step * 2, $($more)*) + }; + ($slots:ident, [$($i:expr),+], $step:expr,) => {{ + #[cfg(panic = "abort")] + let f: extern "C" fn(*const ClosureHeader $(, padded!(@f64 $i))+) -> f64 = + std::mem::transmute(func_ptr); + #[cfg(not(panic = "abort"))] + let f: extern "C-unwind" fn(*const ClosureHeader $(, padded!(@f64 $i))+) -> f64 = + std::mem::transmute(func_ptr); + f(closure $(, $slots[$i])+) + }}; + } + macro_rules! fill { + ($len:literal) => {{ + let mut slots = [undef; $len]; + slots[..provided].copy_from_slice(&args[..provided]); + slots + }}; + } + + match width { + 0..=64 => { + let slots = fill!(64); + padded!(slots, [0usize], 1usize, + + + + + +) + } + 65..=256 => { + let slots = fill!(256); + padded!(slots, [0usize], 1usize, + + + + + + + +) + } + 257..=MAX_DYNAMIC_CALL_WIDTH => { + let slots = fill!(1024); + padded!(slots, [0usize], 1usize, + + + + + + + + + +) + } + _ => throw_too_wide(width), + } +} + +#[cold] +#[inline(never)] +fn throw_too_wide(width: usize) -> ! { + let message = format!( + "Maximum call width exceeded: the callee takes {width} parameter slots \ + (at most {MAX_DYNAMIC_CALL_WIDTH} can be passed dynamically)" + ); + let msg = crate::string::js_string_from_bytes(message.as_ptr(), message.len() as u32); + let err = crate::error::js_rangeerror_new(msg); + crate::exception::js_throw(crate::value::js_nanbox_pointer(err as i64)) +} + +#[cfg(test)] +mod tests { + use super::*; + + const UNDEF: u64 = crate::value::TAG_UNDEFINED; + + extern "C" fn body_40( + _: *const ClosureHeader, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, + a13: f64, + a14: f64, + a15: f64, + a16: f64, + a17: f64, + a18: f64, + a19: f64, + a20: f64, + a21: f64, + a22: f64, + a23: f64, + a24: f64, + a25: f64, + a26: f64, + a27: f64, + a28: f64, + a29: f64, + a30: f64, + a31: f64, + a32: f64, + a33: f64, + a34: f64, + a35: f64, + a36: f64, + a37: f64, + a38: f64, + a39: f64, + ) -> f64 { + let all = [ + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, + a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, + a36, a37, a38, a39, + ]; + // Encode "which slots are undefined" and the sum of the defined ones so + // a dropped, shifted, or zero-filled slot changes the result. + let mut sum = 0.0; + let mut undefined_mask = 0u64; + for (i, v) in all.iter().enumerate() { + if v.to_bits() == UNDEF { + undefined_mask |= 1 << i; + } else { + sum += *v * (i as f64 + 1.0); + } + } + sum + (undefined_mask as f64) * 1.0e6 + } + + fn expected(provided: usize) -> f64 { + let mut sum = 0.0; + let mut undefined_mask = 0u64; + for i in 0..40 { + if i < provided { + sum += (i as f64 + 100.0) * (i as f64 + 1.0); + } else { + undefined_mask |= 1 << i; + } + } + sum + (undefined_mask as f64) * 1.0e6 + } + + #[test] + fn padded_widths_deliver_every_slot_in_order() { + let body = body_40 as *const u8; + let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); + // Exactly the declared width, through each ladder rung. + for width in [40usize, 64, 65, 256, 257, 1024] { + let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, width) }; + assert_eq!(result, expected(40), "width {width}"); + } + } + + #[test] + fn missing_slots_are_padded_with_undefined() { + let body = body_40 as *const u8; + let args: Vec = (0..23).map(|i| i as f64 + 100.0).collect(); + let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, 40) }; + assert_eq!(result, expected(23)); + } + + /// `dispatch_with_arity` past its 32 exact arms used to return `undefined` + /// without calling the body. + #[test] + fn declared_arity_past_the_exact_arms_reaches_the_body() { + let body = body_40 as *const u8; + let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); + let result = + unsafe { crate::closure::dispatch_with_arity(std::ptr::null(), body, &args, 40) }; + assert_eq!(result, expected(40)); + } + + extern "C" fn rest_after_16( + _: *const ClosureHeader, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + a6: f64, + a7: f64, + a8: f64, + a9: f64, + a10: f64, + a11: f64, + a12: f64, + a13: f64, + a14: f64, + a15: f64, + rest: f64, + ) -> f64 { + let fixed = [ + a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, + ]; + let rest = crate::value::js_nanbox_get_pointer(rest) as *const crate::array::ArrayHeader; + let rest_len = crate::array::js_array_length(rest); + let last = crate::array::js_array_get_f64(rest, rest_len - 1); + fixed.iter().sum::() * 1_000.0 + f64::from(rest_len) * 100.0 + last + } + + /// `dispatch_rest_bundled` with 16+ fixed params used to return `undefined` + /// without calling the body. + #[test] + fn rest_bundling_past_fifteen_fixed_params_reaches_the_body() { + let body = rest_after_16 as *const u8; + let args: Vec = (1..=20).map(f64::from).collect(); + let result = unsafe { + crate::closure::dispatch_rest_bundled( + std::ptr::null(), + body, + &args, + 16, + crate::closure::registry::RestDispatchKind::UserRest, + ) + }; + // fixed = 1..=16 (sum 136), rest = [17, 18, 19, 20]. + assert_eq!(result, 136.0 * 1_000.0 + 4.0 * 100.0 + 20.0); + } + + #[test] + fn slots_past_the_width_are_not_forwarded() { + let body = body_40 as *const u8; + let args: Vec = (0..40).map(|i| i as f64 + 100.0).collect(); + // A width of 30 means the body only owns 30 slots; 30..40 read as undefined. + let result = unsafe { dispatch_wide_abi(std::ptr::null(), body, &args, 30) }; + assert_eq!(result, expected(30)); + } +} diff --git a/crates/perry-runtime/src/proxy.rs b/crates/perry-runtime/src/proxy.rs index 5251672421..f90c9d087c 100644 --- a/crates/perry-runtime/src/proxy.rs +++ b/crates/perry-runtime/src/proxy.rs @@ -920,7 +920,14 @@ fn call_with_this_and_args(f: f64, this_arg: f64, args: &[f64]) -> f64 { 1 => js_closure_call1(closure, a(0)), 2 => js_closure_call2(closure, a(0), a(1)), 3 => js_closure_call3(closure, a(0), a(1), a(2)), - _ => crate::closure::js_closure_call4(closure, a(0), a(1), a(2), a(3)), + 4 => crate::closure::js_closure_call4(closure, a(0), a(1), a(2), a(3)), + // #10425: this arm was `_ => js_closure_call4(…)`, so every argument + // after the fourth was dropped. The variadic entry point owns + // arbitrary-arity dispatch, rest bundling included. (`call_trap` + // above keeps its catch-all: a proxy trap receives at most four.) + n => unsafe { + crate::closure::js_closure_call_array(closure as i64, args.as_ptr(), n as i64) + }, }; crate::object::js_implicit_this_set(prev.get_nanbox_f64()); result diff --git a/crates/perry-runtime/src/proxy/reflect_misc.rs b/crates/perry-runtime/src/proxy/reflect_misc.rs index 9435174a02..a79c6ec259 100644 --- a/crates/perry-runtime/src/proxy/reflect_misc.rs +++ b/crates/perry-runtime/src/proxy/reflect_misc.rs @@ -366,3 +366,32 @@ pub(super) extern "C" fn proxy_revoke_trampoline( js_proxy_revoke(proxy); f64::from_bits(TAG_UNDEFINED) } + +#[cfg(test)] +mod tests { + use super::*; + + extern "C" fn weighted_six( + _: *const crate::closure::ClosureHeader, + a0: f64, + a1: f64, + a2: f64, + a3: f64, + a4: f64, + a5: f64, + ) -> f64 { + a0 + 10.0 * a1 + 100.0 * a2 + 1_000.0 * a3 + 10_000.0 * a4 + 100_000.0 * a5 + } + + /// #10425: `Reflect.apply` dispatched every list of four or more arguments + /// through `js_closure_call4`, so the body read whatever the fifth and sixth + /// argument registers held. + #[test] + fn reflect_apply_forwards_every_argument() { + let closure = crate::closure::js_closure_alloc(weighted_six as *const u8, 0); + let f = crate::value::js_nanbox_pointer(closure as i64); + let args = array_from_args(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]); + let result = js_reflect_apply(f, f64::from_bits(TAG_UNDEFINED), args); + assert_eq!(result, 654_321.0); + } +} diff --git a/crates/perry-runtime/src/timer.rs b/crates/perry-runtime/src/timer.rs index e99d6bb8c0..7e5941ad83 100644 --- a/crates/perry-runtime/src/timer.rs +++ b/crates/perry-runtime/src/timer.rs @@ -1238,7 +1238,7 @@ pub extern "C" fn js_callback_timer_tick() -> i32 { crate::perf_hooks::note_event_loop_start(); use crate::closure::{ js_closure_call0, js_closure_call1, js_closure_call2, js_closure_call3, js_closure_call4, - js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, js_closure_call9, + js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, }; if in_timer_callback_dispatch() { @@ -1349,12 +1349,10 @@ pub extern "C" fn js_callback_timer_tick() -> i32 { 8 => { js_closure_call8(cb, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); } - _ => { - // >= 9 args: clamp to 9. Real-world setTimeout - // rarely exceeds 1-2 trailing args; this is a - // conservative safety net rather than spec coverage. - js_closure_call9(cb, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); - } + // #10420: more than 8 trailing args used to clamp to 9. + n => unsafe { + crate::closure::js_closure_call_array(cb as i64, a.as_ptr(), n as i64); + }, } }); // #3870: Node runs a microtask checkpoint after *each* timer @@ -1679,7 +1677,7 @@ pub extern "C" fn js_interval_timer_tick() -> i32 { crate::promise::bump(&PROFILE_INTERVAL_TIMER_TICKS); use crate::closure::{ js_closure_call0, js_closure_call1, js_closure_call2, js_closure_call3, js_closure_call4, - js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, js_closure_call9, + js_closure_call5, js_closure_call6, js_closure_call7, js_closure_call8, }; if in_timer_callback_dispatch() { @@ -1746,7 +1744,10 @@ pub extern "C" fn js_interval_timer_tick() -> i32 { 6 => js_closure_call6(cb, a[0], a[1], a[2], a[3], a[4], a[5]), 7 => js_closure_call7(cb, a[0], a[1], a[2], a[3], a[4], a[5], a[6]), 8 => js_closure_call8(cb, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]), - _ => js_closure_call9(cb, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]), + // #10420: more than 8 trailing args used to clamp to 9. + n => unsafe { + crate::closure::js_closure_call_array(cb as i64, a.as_ptr(), n as i64) + }, }; }); crate::async_hooks::after(async_id); diff --git a/test-files/_helpers/call_arity_10420.ts b/test-files/_helpers/call_arity_10420.ts new file mode 100644 index 0000000000..2595752b54 --- /dev/null +++ b/test-files/_helpers/call_arity_10420.ts @@ -0,0 +1,133 @@ +// Helper module for test_gap_10420_call_arity_limits.ts (#10420): function +// VALUES exported across a module boundary -- an arrow `const`, a qs-shaped +// named function expression bound to a `var`, and a rest arrow. + +export function sig(values: any[]): string { + let sum = 0; + for (let i = 0; i < values.length; i++) sum += values[i]; + return `${values.length}:${values[0]}:${values[values.length - 1]}:${sum}`; +} + +export const importedArrow3 = ( + p0: any, p1: any, p2: any, +): string => + sig([ + p0, p1, p2, + ]); + +export var importedFexpr3 = function importedFexpr3( + p0: any, p1: any, p2: any, +): string { + return sig([ + p0, p1, p2, + ]); +}; + +export const importedArrow16 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +): string => + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); + +export var importedFexpr16 = function importedFexpr16( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); +}; + +export const importedArrow17 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +): string => + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); + +export var importedFexpr17 = function importedFexpr17( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); +}; + +export const importedArrow18 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, +): string => + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); + +export var importedFexpr18 = function importedFexpr18( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); +}; + +export const importedArrow32 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, +): string => + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); + +export var importedFexpr32 = function importedFexpr32( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); +}; + +export const importedArrow64 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, p35: any, p36: any, + p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, p44: any, p45: any, + p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, + p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, +): string => + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); + +export var importedFexpr64 = function importedFexpr64( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, p35: any, p36: any, + p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, p44: any, p45: any, + p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, + p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); +}; + +export const importedRest = (...values: any[]): string => sig(values); diff --git a/test-files/test_gap_10420_call_arity_limits.ts b/test-files/test_gap_10420_call_arity_limits.ts new file mode 100644 index 0000000000..8aa79ffe73 --- /dev/null +++ b/test-files/test_gap_10420_call_arity_limits.ts @@ -0,0 +1,993 @@ +// #10420 / #10425: calls with more than 16 arguments. +// +// A function VALUE called with 17+ arguments failed to compile (`closure call +// with 18 args (max 16)`), and apply/call/spread/object-literal methods passed +// `0` for every parameter past the 16th (the `__perry_wrap_*` value wrappers +// were capped at 16 params). `Reflect.apply` forwarded at most four arguments. +// qs 6.15.3's recursive 18-argument `stringify` is the package-audit shape. +// Sizes 3 and 16 are the fixed-arity fast-path controls; 17/18/32/64 take the +// variadic path. + +import { + sig, + importedRest, + importedArrow3, + importedArrow16, + importedArrow17, + importedArrow18, + importedArrow32, + importedArrow64, + importedFexpr3, + importedFexpr16, + importedFexpr17, + importedFexpr18, + importedFexpr32, + importedFexpr64, +} from "./_helpers/call_arity_10420.ts"; + +function range(n: number): number[] { + const out: number[] = []; + for (let i = 1; i <= n; i++) out.push(i); + return out; +} + +function show(label: string, value: any): void { + console.log(label + " " + String(value)); +} + +// ---- 3 parameters ---- +const arrow3 = ( + p0: any, p1: any, p2: any, +): string => sig([ + p0, p1, p2, +]); + +var fexpr3 = function fexpr3( + p0: any, p1: any, p2: any, +): string { + if (p0 > 1) { + return fexpr3( + p0 - 1, p1, p2, + ); + } + return sig([ + p0, p1, p2, + ]); +}; + +function decl3( + p0: any, p1: any, p2: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, + ]); +} + +function Ctor3( + this: any, p0: any, p1: any, p2: any, +) { + this.value = sig([ + p0, p1, p2, + ]); +} + +class Klass3 { + value: string; + constructor( + p0: any, p1: any, p2: any, + ) { + this.value = sig([ + p0, p1, p2, + ]); + } + method( + p0: any, p1: any, p2: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, + ]); + } +} + +const objlit3 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, + ]); + }, +}; + +{ + const args = range(3); + const values: any[] = [arrow3, fexpr3, decl3, importedArrow3, importedFexpr3]; + show("arrow3 direct", arrow3( + 1, 2, 3, + )); + show("fexpr3 recursive", fexpr3( + 4, 2, 3, + )); + show("decl3 direct", decl3( + 1, 2, 3, + )); + show("importedArrow3 direct", importedArrow3( + 1, 2, 3, + )); + show("importedFexpr3 direct", importedFexpr3( + 1, 2, 3, + )); + show("importedRest 3 direct", importedRest( + 1, 2, 3, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 3 direct", rest( + 1, 2, 3, + )); + show("rest 3 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value3[" + i + "] call", fn( + 1, 2, 3, + )); + show("value3[" + i + "] apply", fn.apply(null, args)); + show("value3[" + i + "] .call", fn.call(null, ...args)); + show("value3[" + i + "] spread", fn(...args)); + show("value3[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value3[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value3[" + i + "] short", fn(1, 2, 3)); + } + show("decl3 underapplied", decl3.apply(null, args.slice(0, 2))); + show("decl3 overapplied", decl3.apply(null, range(6))); + show("Reflect.construct Ctor3", Reflect.construct(Ctor3, args).value); + show("Reflect.construct Klass3", Reflect.construct(Klass3, args).value); + show("new Klass3 spread", new Klass3(...args).value); + const k = new Klass3(...args); + show("Klass3 method direct", k.method( + 1, 2, 3, + )); + show("Klass3 method apply", k.method.apply(k, args)); + show("Klass3 method spread", k.method(...args)); + show("Klass3 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit3 method direct", objlit3.method( + 1, 2, 3, + )); + show("objlit3 method apply", objlit3.method.apply(objlit3, args)); + show("objlit3 method .call", objlit3.method.call(objlit3, ...args)); + show("objlit3 method spread", objlit3.method(...args)); + show("objlit3 method Reflect.apply", Reflect.apply(objlit3.method, objlit3, args)); +} + +// ---- 16 parameters ---- +const arrow16 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +): string => sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, +]); + +var fexpr16 = function fexpr16( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +): string { + if (p0 > 1) { + return fexpr16( + p0 - 1, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ); + } + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); +}; + +function decl16( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); +} + +function Ctor16( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, +) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); +} + +class Klass16 { + value: string; + constructor( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, + ) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); + } + method( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); + } +} + +const objlit16 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, + ]); + }, +}; + +{ + const args = range(16); + const values: any[] = [arrow16, fexpr16, decl16, importedArrow16, importedFexpr16]; + show("arrow16 direct", arrow16( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("fexpr16 recursive", fexpr16( + 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("decl16 direct", decl16( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("importedArrow16 direct", importedArrow16( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("importedFexpr16 direct", importedFexpr16( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("importedRest 16 direct", importedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 16 direct", rest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("rest 16 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value16[" + i + "] call", fn( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("value16[" + i + "] apply", fn.apply(null, args)); + show("value16[" + i + "] .call", fn.call(null, ...args)); + show("value16[" + i + "] spread", fn(...args)); + show("value16[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value16[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value16[" + i + "] short", fn(1, 2, 3)); + } + show("decl16 underapplied", decl16.apply(null, args.slice(0, 15))); + show("decl16 overapplied", decl16.apply(null, range(19))); + show("Reflect.construct Ctor16", Reflect.construct(Ctor16, args).value); + show("Reflect.construct Klass16", Reflect.construct(Klass16, args).value); + show("new Klass16 spread", new Klass16(...args).value); + const k = new Klass16(...args); + show("Klass16 method direct", k.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("Klass16 method apply", k.method.apply(k, args)); + show("Klass16 method spread", k.method(...args)); + show("Klass16 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit16 method direct", objlit16.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + )); + show("objlit16 method apply", objlit16.method.apply(objlit16, args)); + show("objlit16 method .call", objlit16.method.call(objlit16, ...args)); + show("objlit16 method spread", objlit16.method(...args)); + show("objlit16 method Reflect.apply", Reflect.apply(objlit16.method, objlit16, args)); +} + +// ---- 17 parameters ---- +const arrow17 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +): string => sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, +]); + +var fexpr17 = function fexpr17( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +): string { + if (p0 > 1) { + return fexpr17( + p0 - 1, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ); + } + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); +}; + +function decl17( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); +} + +function Ctor17( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, +) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); +} + +class Klass17 { + value: string; + constructor( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + ) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); + } + method( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); + } +} + +const objlit17 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]); + }, +}; + +{ + const args = range(17); + const values: any[] = [arrow17, fexpr17, decl17, importedArrow17, importedFexpr17]; + show("arrow17 direct", arrow17( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("fexpr17 recursive", fexpr17( + 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("decl17 direct", decl17( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("importedArrow17 direct", importedArrow17( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("importedFexpr17 direct", importedFexpr17( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("importedRest 17 direct", importedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 17 direct", rest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("rest 17 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value17[" + i + "] call", fn( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("value17[" + i + "] apply", fn.apply(null, args)); + show("value17[" + i + "] .call", fn.call(null, ...args)); + show("value17[" + i + "] spread", fn(...args)); + show("value17[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value17[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value17[" + i + "] short", fn(1, 2, 3)); + } + show("decl17 underapplied", decl17.apply(null, args.slice(0, 16))); + show("decl17 overapplied", decl17.apply(null, range(20))); + show("Reflect.construct Ctor17", Reflect.construct(Ctor17, args).value); + show("Reflect.construct Klass17", Reflect.construct(Klass17, args).value); + show("new Klass17 spread", new Klass17(...args).value); + const k = new Klass17(...args); + show("Klass17 method direct", k.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("Klass17 method apply", k.method.apply(k, args)); + show("Klass17 method spread", k.method(...args)); + show("Klass17 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit17 method direct", objlit17.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + )); + show("objlit17 method apply", objlit17.method.apply(objlit17, args)); + show("objlit17 method .call", objlit17.method.call(objlit17, ...args)); + show("objlit17 method spread", objlit17.method(...args)); + show("objlit17 method Reflect.apply", Reflect.apply(objlit17.method, objlit17, args)); +} + +// ---- 18 parameters ---- +const arrow18 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, +): string => sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, +]); + +var fexpr18 = function fexpr18( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, +): string { + if (p0 > 1) { + return fexpr18( + p0 - 1, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ); + } + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); +}; + +function decl18( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); +} + +function Ctor18( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, +) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); +} + +class Klass18 { + value: string; + constructor( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, + ) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); + } + method( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); + } +} + +const objlit18 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + ]); + }, +}; + +{ + const args = range(18); + const values: any[] = [arrow18, fexpr18, decl18, importedArrow18, importedFexpr18]; + show("arrow18 direct", arrow18( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("fexpr18 recursive", fexpr18( + 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("decl18 direct", decl18( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("importedArrow18 direct", importedArrow18( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("importedFexpr18 direct", importedFexpr18( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("importedRest 18 direct", importedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 18 direct", rest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("rest 18 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value18[" + i + "] call", fn( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("value18[" + i + "] apply", fn.apply(null, args)); + show("value18[" + i + "] .call", fn.call(null, ...args)); + show("value18[" + i + "] spread", fn(...args)); + show("value18[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value18[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value18[" + i + "] short", fn(1, 2, 3)); + } + show("decl18 underapplied", decl18.apply(null, args.slice(0, 17))); + show("decl18 overapplied", decl18.apply(null, range(21))); + show("Reflect.construct Ctor18", Reflect.construct(Ctor18, args).value); + show("Reflect.construct Klass18", Reflect.construct(Klass18, args).value); + show("new Klass18 spread", new Klass18(...args).value); + const k = new Klass18(...args); + show("Klass18 method direct", k.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("Klass18 method apply", k.method.apply(k, args)); + show("Klass18 method spread", k.method(...args)); + show("Klass18 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit18 method direct", objlit18.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + )); + show("objlit18 method apply", objlit18.method.apply(objlit18, args)); + show("objlit18 method .call", objlit18.method.call(objlit18, ...args)); + show("objlit18 method spread", objlit18.method(...args)); + show("objlit18 method Reflect.apply", Reflect.apply(objlit18.method, objlit18, args)); +} + +// ---- 32 parameters ---- +const arrow32 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, +): string => sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, + p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, +]); + +var fexpr32 = function fexpr32( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, +): string { + if (p0 > 1) { + return fexpr32( + p0 - 1, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ); + } + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); +}; + +function decl32( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); +} + +function Ctor32( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, + p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, +) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); +} + +class Klass32 { + value: string; + constructor( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, + ) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); + } + method( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); + } +} + +const objlit32 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, + ]); + }, +}; + +{ + const args = range(32); + const values: any[] = [arrow32, fexpr32, decl32, importedArrow32, importedFexpr32]; + show("arrow32 direct", arrow32( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("fexpr32 recursive", fexpr32( + 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("decl32 direct", decl32( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("importedArrow32 direct", importedArrow32( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("importedFexpr32 direct", importedFexpr32( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("importedRest 32 direct", importedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 32 direct", rest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("rest 32 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value32[" + i + "] call", fn( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("value32[" + i + "] apply", fn.apply(null, args)); + show("value32[" + i + "] .call", fn.call(null, ...args)); + show("value32[" + i + "] spread", fn(...args)); + show("value32[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value32[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value32[" + i + "] short", fn(1, 2, 3)); + } + show("decl32 underapplied", decl32.apply(null, args.slice(0, 31))); + show("decl32 overapplied", decl32.apply(null, range(35))); + show("Reflect.construct Ctor32", Reflect.construct(Ctor32, args).value); + show("Reflect.construct Klass32", Reflect.construct(Klass32, args).value); + show("new Klass32 spread", new Klass32(...args).value); + const k = new Klass32(...args); + show("Klass32 method direct", k.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("Klass32 method apply", k.method.apply(k, args)); + show("Klass32 method spread", k.method(...args)); + show("Klass32 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit32 method direct", objlit32.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, + )); + show("objlit32 method apply", objlit32.method.apply(objlit32, args)); + show("objlit32 method .call", objlit32.method.call(objlit32, ...args)); + show("objlit32 method spread", objlit32.method(...args)); + show("objlit32 method Reflect.apply", Reflect.apply(objlit32.method, objlit32, args)); +} + +// ---- 64 parameters ---- +const arrow64 = ( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, p35: any, p36: any, + p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, p44: any, p45: any, + p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, + p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, +): string => sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, + p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, + p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, + p56, p57, p58, p59, p60, p61, p62, p63, +]); + +var fexpr64 = function fexpr64( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, p35: any, p36: any, + p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, p44: any, p45: any, + p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, + p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, +): string { + if (p0 > 1) { + return fexpr64( + p0 - 1, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, + p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, + p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, + p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ); + } + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); +}; + +function decl64( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, p17: any, p18: any, + p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, p26: any, p27: any, + p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, p35: any, p36: any, + p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, p44: any, p45: any, + p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, + p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, +): string { + return arguments.length + "/" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); +} + +function Ctor64( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, p25: any, + p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, p32: any, p33: any, p34: any, + p35: any, p36: any, p37: any, p38: any, p39: any, p40: any, p41: any, p42: any, p43: any, + p44: any, p45: any, p46: any, p47: any, p48: any, p49: any, p50: any, p51: any, p52: any, + p53: any, p54: any, p55: any, p56: any, p57: any, p58: any, p59: any, p60: any, p61: any, + p62: any, p63: any, +) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); +} + +class Klass64 { + value: string; + constructor( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, p32: any, + p33: any, p34: any, p35: any, p36: any, p37: any, p38: any, p39: any, p40: any, + p41: any, p42: any, p43: any, p44: any, p45: any, p46: any, p47: any, p48: any, + p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, p55: any, p56: any, + p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, + ) { + this.value = sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); + } + method( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, + p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, p32: any, + p33: any, p34: any, p35: any, p36: any, p37: any, p38: any, p39: any, p40: any, + p41: any, p42: any, p43: any, p44: any, p45: any, p46: any, p47: any, p48: any, + p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, p55: any, p56: any, + p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, + ): string { + return "klass:" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); + } +} + +const objlit64 = { + tag: "obj", + method( + this: any, p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, + p8: any, p9: any, p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, + p17: any, p18: any, p19: any, p20: any, p21: any, p22: any, p23: any, p24: any, + p25: any, p26: any, p27: any, p28: any, p29: any, p30: any, p31: any, p32: any, + p33: any, p34: any, p35: any, p36: any, p37: any, p38: any, p39: any, p40: any, + p41: any, p42: any, p43: any, p44: any, p45: any, p46: any, p47: any, p48: any, + p49: any, p50: any, p51: any, p52: any, p53: any, p54: any, p55: any, p56: any, + p57: any, p58: any, p59: any, p60: any, p61: any, p62: any, p63: any, + ): string { + return this.tag + ":" + sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, + p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, + p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, + p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, + ]); + }, +}; + +{ + const args = range(64); + const values: any[] = [arrow64, fexpr64, decl64, importedArrow64, importedFexpr64]; + show("arrow64 direct", arrow64( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("fexpr64 recursive", fexpr64( + 4, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("decl64 direct", decl64( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("importedArrow64 direct", importedArrow64( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("importedFexpr64 direct", importedFexpr64( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("importedRest 64 direct", importedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + const rest = (...values: any[]): string => "rest:" + sig(values); + show("rest 64 direct", rest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("rest 64 apply", rest.apply(null, args)); + for (let i = 0; i < values.length; i++) { + const fn = values[i]; + show("value64[" + i + "] call", fn( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("value64[" + i + "] apply", fn.apply(null, args)); + show("value64[" + i + "] .call", fn.call(null, ...args)); + show("value64[" + i + "] spread", fn(...args)); + show("value64[" + i + "] Reflect.apply", Reflect.apply(fn, null, args)); + show("value64[" + i + "] bind", fn.bind(null, 1, 2)(...args.slice(2))); + show("value64[" + i + "] short", fn(1, 2, 3)); + } + show("decl64 underapplied", decl64.apply(null, args.slice(0, 63))); + show("decl64 overapplied", decl64.apply(null, range(67))); + show("Reflect.construct Ctor64", Reflect.construct(Ctor64, args).value); + show("Reflect.construct Klass64", Reflect.construct(Klass64, args).value); + show("new Klass64 spread", new Klass64(...args).value); + const k = new Klass64(...args); + show("Klass64 method direct", k.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("Klass64 method apply", k.method.apply(k, args)); + show("Klass64 method spread", k.method(...args)); + show("Klass64 method Reflect.apply", Reflect.apply(k.method, k, args)); + show("objlit64 method direct", objlit64.method( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, + )); + show("objlit64 method apply", objlit64.method.apply(objlit64, args)); + show("objlit64 method .call", objlit64.method.call(objlit64, ...args)); + show("objlit64 method spread", objlit64.method(...args)); + show("objlit64 method Reflect.apply", Reflect.apply(objlit64.method, objlit64, args)); +} + +// #10425's own repro: Reflect.apply with a native callee and a rest callee. +show("Reflect.apply Math.max", Reflect.apply(Math.max, null, [1, 2, 3, 4, 5, 6])); +function restJoin(...a: number[]): string { + return a.length + ":" + a.join(","); +} +show("Reflect.apply rest", Reflect.apply(restJoin, null, [1, 2, 3, 4, 5, 6, 7])); +show("Reflect.construct Array", Reflect.construct(Array, [1, 2, 3, 4, 5, 6]).length); +show("apply rest", restJoin.apply(null, [1, 2, 3, 4, 5, 6, 7])); +function thisProbe(this: any, a: any, b: any, c: any, d: any, e: any): string { + return this.name + ":" + [a, b, c, d, e].join(","); +} +show("Reflect.apply this+5", Reflect.apply(thisProbe, { name: "recv" }, [1, 2, 3, 4, 5])); + +// A rest parameter after 17 fixed parameters. +function fixedRest( + p0: any, p1: any, p2: any, p3: any, p4: any, p5: any, p6: any, p7: any, p8: any, p9: any, + p10: any, p11: any, p12: any, p13: any, p14: any, p15: any, p16: any, ...rest: any[] +): string { + return sig([ + p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, + ]) + "+" + sig(rest); +} +show("fixedRest direct", fixedRest( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +)); +show("fixedRest apply", fixedRest.apply(null, range(20))); +show("fixedRest Reflect.apply", Reflect.apply(fixedRest, null, range(20))); +const fixedRestValue: any = fixedRest; +show("fixedRest value spread", fixedRestValue(...range(20))); + +// A callee declaring one parameter still sees every argument via `arguments`. +function countArgs(a: any): string { + return arguments.length + ":" + a + ":" + arguments[arguments.length - 1]; +} +show("arguments 100 apply", countArgs.apply(null, range(100))); +show("arguments 100 Reflect.apply", Reflect.apply(countArgs, null, range(100))); +show("Math.max 200 apply", Math.max.apply(null, range(200))); + +// Timer and nextTick callbacks forward their extra arguments too. +setTimeout( + (...values: any[]) => { + show("setTimeout 12 args", sig(values)); + const interval = setInterval( + (...more: any[]) => { + clearInterval(interval); + show("setInterval 10 args", sig(more)); + }, + 1, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + ); + }, + 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, +); +process.nextTick( + (...values: any[]) => show("nextTick 11 args", sig(values)), + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +);