diff --git a/crates/oxide_builtins/src/function.rs b/crates/oxide_builtins/src/function.rs index 3e43c2a7..0c51001b 100644 --- a/crates/oxide_builtins/src/function.rs +++ b/crates/oxide_builtins/src/function.rs @@ -90,7 +90,7 @@ pub fn function_call(vm: &mut H, args: &[u8]) -> NativeResult { } /// `Function.prototype.apply(thisArg, argsArray)`:以指定 this 和参数数组调用目标函数。 -/// 数组元素拷入寄存器(上限受寄存器空间限制,最多 55 个参数)。 +/// 数组元素直接物化为实参切片经 TailCall 下发(帧参数区),不受寄存器窗口限制。 pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { if args.is_empty() { return NativeResult::Err(JsValue::undefined()); @@ -98,7 +98,7 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { let target_val = vm.reg(args[0]); let this_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let arg_regs: Vec; + let mut call_args: Vec = Vec::new(); if args.len() > 2 { let arr_val = vm.reg(args[2]); if arr_val.is_object() { @@ -106,27 +106,20 @@ pub fn function_apply(vm: &mut H, args: &[u8]) -> NativeResult { if !arr_ptr.is_null() { let arr = unsafe { &*arr_ptr }; if arr.is_array() { - let max_args = 55usize; // base=200,255 号寄存器之前的安全上限。 let n = arr.prop_count() as usize; - let n = n.min(max_args); - let base = 200u8; - arg_regs = (0..n).map(|i| base + i as u8).collect(); + call_args.reserve(n); for i in 0..n { - vm.set_reg(base + i as u8, arr.get_prop_at(i)); + call_args.push(arr.get_prop_at(i)); } - } else { - arg_regs = Vec::new(); } - } else { - arg_regs = Vec::new(); } - } else { - arg_regs = Vec::new(); } - } else { - arg_regs = Vec::new(); } - invoke_target(vm, target_val, this_val, &arg_regs) + NativeResult::TailCall { + callee: target_val, + this: this_val, + args: call_args, + } } /// `Function.prototype.bind(thisArg, ...args)`:返回绑定 this 与前置实参的新包装函数, diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 54778d71..c641487c 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -72,8 +72,11 @@ pub fn string_from_char_code(vm: &mut H, args: &[u8]) -> NativeResult pub fn string_from_code_point(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("String.fromCodePoint called with {} args", args.len()); let mut out = String::new(); - for &arg_reg in args.iter().skip(1) { - let n = match oxide_runtime_api::to_number_full(vm.reg(arg_reg), vm) { + // 经 native_arg_count/native_arg_at 读取:大实参集(如 harness 的 + // `String.fromCodePoint.apply(null, codePoints)` 每块 10000 码位)在寄存器 + // 窗口外走 spill 溢出区,小实参集与既有寄存器路径一致。 + for i in 0..vm.native_arg_count(args) { + let n = match oxide_runtime_api::to_number_full(vm.native_arg_at(args, i), vm) { Ok(n) => n, Err(_) => { // ToNumber 触发对象 valueOf/toString 抛出的原生异常须原样传播, diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index 69391d8c..2ca4a653 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -72,6 +72,36 @@ pub trait VmHost { fn reg(&self, idx: u8) -> JsValue; fn set_reg(&mut self, idx: u8, val: JsValue); + /// 当前 native 调用 spill 溢出区的实参个数(寄存器窗口 253 之外的部分)。 + /// + /// # 边界与前提 + /// - 仅在 native 实现内部、实参仍有效时读取;无溢出时为 0。 + fn native_overflow_count(&self) -> usize; + /// 读 spill 溢出区第 `i` 个实参(0 基,相对溢出区起点)。 + /// + /// # 边界与前提 + /// - `i` 必须小于 [`Self::native_overflow_count`]。 + fn native_overflow_at(&self, i: usize) -> JsValue; + /// 当前 native 调用的完整实参个数(寄存器窗口 + spill 溢出区,不含 receiver)。 + /// + /// 供支持大实参集的 builtin(如 `String.fromCodePoint`)遍历全部实参; + /// 未迁移的 builtin 仍按 `args` 索引读寄存器,行为不变。 + fn native_arg_count(&self, args: &[u8]) -> usize { + args.len().saturating_sub(1) + self.native_overflow_count() + } + /// 第 `idx` 个实参(0 基,不含 receiver):窗口内读寄存器,窗口外读 spill 溢出区。 + /// + /// # 边界与前提 + /// - `idx` 必须小于 [`Self::native_arg_count`]。 + fn native_arg_at(&self, args: &[u8], idx: usize) -> JsValue { + let window = args.len().saturating_sub(1); + if idx < window { + self.reg(args[idx + 1]) + } else { + self.native_overflow_at(idx - window) + } + } + // 对象分配 / 字符串创建 fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject; fn new_string(&mut self, s: &str) -> JsValue; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 30826083..2a6d7b27 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -379,6 +379,12 @@ pub struct Vm { /// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配, /// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。 pub(crate) spill_stack: Vec, + /// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。 + /// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根), + /// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次 + /// native 调用期间有效,调用返回前截断回收。 + pub(crate) native_overflow_base: usize, + pub(crate) native_overflow_count: usize, pub(crate) try_stack: Vec, pub(crate) exception_value: Option, /// 同步调用抛出的原始 JsValue 侧通道:`call_function_sync`/`unwind` 把错误展平为 @@ -1143,9 +1149,16 @@ impl Vm { self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?; return Ok(JsValue::undefined()); } + // 大实参集(超过寄存器窗口):窗口外的实参转存 spill 栈溢出区(GC 根), + // native 侧经 native_arg_count/native_arg_at 读取;窗口内仍按寄存器 + // 协议打包,保证未迁移的 builtin 行为不变。 + let overflow_base = self.spill_stack.len(); + let saved_overflow_base = self.native_overflow_base; + let saved_overflow_count = self.native_overflow_count; if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { - self.raise_error_kind("RangeError", "Maximum call stack size exceeded")?; - return Ok(JsValue::undefined()); + self.spill_stack.extend_from_slice(&args[Self::SYNC_NATIVE_ARG_LIMIT..]); + self.native_overflow_base = overflow_base; + self.native_overflow_count = args.len() - Self::SYNC_NATIVE_ARG_LIMIT; } // native 回调只写 regs[0..args.len()] 实参区 + regs[253]/[254](receiver/callee), // 窗口 = 调用方活动寄存器 ∪ 实参写入区;窗口外槽回调不触碰,无需保存。 @@ -1155,13 +1168,20 @@ impl Vm { saved_window.extend_from_slice(&self.regs[..window]); let saved_r253 = self.regs[253]; let saved_r254 = self.regs[254]; - let arg_regs = self.pack_sync_native_call_args(receiver, callee, args); + let pack_limit = args.len().min(Self::SYNC_NATIVE_ARG_LIMIT); + let arg_regs = self.pack_sync_native_call_args(receiver, callee, &args[..pack_limit]); // SAFETY: native_fn 经 set_native_fn 以合法 NativeFn 指针设置; // native_fn_ptr_to_fn 是 NativeFnPtr → NativeFn 的唯一强制转换点。 let func: NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; self.native_call_depth += 1; let result = func(self, &arg_regs); self.native_call_depth -= 1; + // 溢出区随本次调用结束截断回收,并还原外层的溢出区描述(嵌套调用安全)。 + if args.len() > Self::SYNC_NATIVE_ARG_LIMIT { + self.spill_stack.truncate(overflow_base); + } + self.native_overflow_base = saved_overflow_base; + self.native_overflow_count = saved_overflow_count; // 窗口回拷 + regs[253]/[254] 单回,缓冲归还池复用。 self.regs[..window].copy_from_slice(&saved_window); self.regs[253] = saved_r253; @@ -1975,6 +1995,12 @@ impl oxide_runtime_api::VmHost for Vm { fn set_reg(&mut self, idx: u8, val: JsValue) { self.set_reg(idx, val); } + fn native_overflow_count(&self) -> usize { + self.native_overflow_count + } + fn native_overflow_at(&self, i: usize) -> JsValue { + self.spill_stack[self.native_overflow_base + i] + } fn alloc_object(&mut self, obj: JsObject) -> *mut JsObject { self.alloc_object(obj) } @@ -2204,7 +2230,7 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec NativeResult { + NativeResult::Ok(JsValue::int(vm.native_arg_count(args) as i32)) + } + + fn native_return_last_full_arg(vm: &mut Vm, args: &[u8]) -> NativeResult { + let n = vm.native_arg_count(args); + NativeResult::Ok(vm.native_arg_at(args, n - 1)) + } + fn native_nested_inline_254(vm: &mut Vm, args: &[u8]) -> NativeResult { // 外层 native 回调:receiver 落 regs[253](native 分支单存槽),内嵌执行 // n_registers=254 的 inline 字节码回调后,receiver 槽必须保持外层值。 @@ -2565,24 +2600,35 @@ mod tests { } #[test] - fn call_function_sync_rejects_unrepresentable_native_arity_without_register_corruption() { + fn call_function_sync_overflow_preserves_large_native_arity_and_registers() { + // 大实参集(超寄存器窗口 253)经 spill 溢出区完整送达 native: + // 全量计数与末位实参都可读,且调用方寄存器不被打包过程污染。 let mut vm = Vm::new(); - let callee = native_function(&mut vm, native_return_arg_count); + let callee = native_function(&mut vm, native_return_full_arg_count); vm.set_reg(1, JsValue::int(7)); vm.set_reg(253, JsValue::int(11)); vm.set_reg(254, JsValue::int(12)); vm.set_reg(255, JsValue::int(13)); - let args = vec![JsValue::undefined(); Vm::SYNC_NATIVE_ARG_LIMIT + 1]; - let err = vm + let args: Vec = (0..(Vm::SYNC_NATIVE_ARG_LIMIT + 100)).map(|i| JsValue::int(i as i32)).collect(); + let count = vm .call_function_sync(callee, JsValue::undefined(), &args) - .expect_err("too many args should fail cleanly"); + .expect("大实参集应经 spill 溢出区完整送达 native"); + assert_eq!(count, JsValue::int(args.len() as i32)); + + let last_callee = native_function(&mut vm, native_return_last_full_arg); + let last = vm + .call_function_sync(last_callee, JsValue::undefined(), &args) + .expect("溢出区末位实参应可读"); + assert_eq!(last, JsValue::int((args.len() - 1) as i32)); - assert!(err.contains("Maximum call stack size exceeded"), "unexpected error: {err}"); assert_eq!(vm.reg(1), JsValue::int(7)); assert_eq!(vm.reg(253), JsValue::int(11)); assert_eq!(vm.reg(254), JsValue::int(12)); assert_eq!(vm.reg(255), JsValue::int(13)); + // 溢出区随调用结束截断回收,不残留 spill 增长。 + assert!(vm.spill_stack.is_empty(), "溢出区应已回收: {:?}", vm.spill_stack); + assert_eq!(vm.native_overflow_count, 0); } /// 构造 `n_registers = 254` 的子模块:RegAlloc 合法产物(builtin 槽落 253 或 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index ea66f389..5115f915 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -60,6 +60,8 @@ impl Vm { saved_immutables_stack: Vec::new(), save_stack: Vec::new(), spill_stack: Vec::new(), + native_overflow_base: 0, + native_overflow_count: 0, try_stack: Vec::new(), exception_value: None, last_uncaught_value: None, @@ -162,6 +164,8 @@ impl Vm { saved_immutables_stack: Vec::new(), save_stack: Vec::new(), spill_stack: Vec::new(), + native_overflow_base: 0, + native_overflow_count: 0, try_stack: Vec::new(), exception_value: None, last_uncaught_value: None, @@ -353,6 +357,8 @@ impl Vm { self.saved_immutables_stack.clear(); self.save_stack.clear(); self.spill_stack.clear(); + self.native_overflow_base = 0; + self.native_overflow_count = 0; self.cell_stack.clear(); self.try_stack.clear(); self.exception_value = None; diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index 8bf2fdf9..8e1b0288 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -116,6 +116,43 @@ fn function_apply_returns_object_stays_valid() { assert_num(result, 10.0); } +#[test] +fn function_apply_passes_large_arg_array_to_bytecode_target() { + // 回归:apply 曾把实参数组静默截断到 55 个;大参数集必须完整到达目标函数。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = []; for (var i = 0; i < 1000; i++) { a[i] = i; } (function(){ return arguments.length; }).apply(null, a)", + ) + .unwrap(); + assert_eq!(result, JsValue::int(1000)); +} + +#[test] +fn function_apply_large_args_reach_last_element() { + // 大参数集经 spill 溢出区送达后,末位实参可被目标读取(不止计数正确)。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = []; for (var i = 0; i < 300; i++) { a[i] = i; } (function(){ return arguments[299]; }).apply(null, a)", + ) + .unwrap(); + assert_num(result, 299.0); +} + +#[test] +fn function_apply_large_args_to_native_from_code_point() { + // native 目标(String.fromCodePoint)接收大实参集:整串完整拼接,验证 + // harness `String.fromCodePoint.apply(null, codePoints)` 不再截断。 + let mut vm = Vm::new(); + let result = eval( + &mut vm, + "var a = []; for (var i = 0; i < 1000; i++) { a[i] = 65 + (i % 26); } String.fromCodePoint.apply(null, a).length", + ) + .unwrap(); + assert_eq!(result, JsValue::int(1000)); +} + #[test] fn getter_returns_object_stays_valid() { // 同类 bug:字节码 accessor 经 ordinary_get 同步路径(target_reg=None)在子 VM