From 4095ad2c1d18f29addccc6f1175be73136518e30 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 11 Sep 2026 16:29:38 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20JS=20=E9=87=8D=E5=85=A5=E8=BE=B9?= =?UTF-8?q?=E7=95=8C=20hop=20=E8=AE=A1=E6=95=B0=E6=AF=8F=2064=20=E8=B7=B3?= =?UTF-8?q?=E5=BC=BA=E5=88=B6=E9=A1=B6=E5=B1=82=E5=88=86=E9=85=8D=E4=B8=8A?= =?UTF-8?q?=E9=99=90=E9=87=87=E6=A0=B7=E2=80=94=E2=80=94=E5=A0=B5=20native?= =?UTF-8?q?=20=E6=B3=B5=E9=80=81=E5=B0=8F=E9=87=8D=E5=85=A5=E5=8F=8C?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E7=BB=93=E6=9E=84=E6=80=A7=E6=B0=B8=E4=B8=8D?= =?UTF-8?q?=E9=87=87=E6=A0=B7=E7=9B=B2=E5=8C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/vm.rs | 24 ++++++++++++++++++++++++ crates/oxide_vm/src/vm_support.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index d4d2678f..fbde4bea 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -478,6 +478,11 @@ pub struct Vm { pub(crate) root_reg_limit: u8, pub(crate) active_reg_limit: u8, pub(crate) native_call_depth: usize, + /// JS 重入 hop 计数:native 体内(`native_call_depth > 0`)每次嵌套 dispatch + /// 进入计一跳。每 64 跳强制一次顶层分配上限采样——顶层 dispatch 冻结在 native + /// 体内时 `steps` 不推进、重入 dispatch 又太短到不了循环内采样点,分配上限 + /// 否则结构性永不采样(小重入泵送盲区)。run 边界重置。 + pub(crate) reentry_hops: u64, /// inline 同步调用(`call_bytecode_function_inline`,frames 为空)的实参区位置。 /// frames 非空时 CREATE_ARGUMENTS 优先读当前帧的实参区;此字段只服务内联路径。 pub(crate) inline_args_base: u32, @@ -1587,6 +1592,25 @@ impl Vm { let max_steps = self.kernel_core.config.max_steps; let max_alloc_bytes = self.kernel_core.config.max_alloc_bytes; let mut steps: u64 = 0; + // 小重入泵送盲区:native 终端循环泵送短 JS 重入时,顶层 steps 不推进、 + // 每次重入 dispatch 太短到不了循环内 64 指令采样点,分配上限结构性永不 + // 采样。重入边界按 hop 计数,每 64 跳强制一次顶层轻采样兜底。 + if self.native_call_depth > 0 { + self.reentry_hops += 1; + if self.reentry_hops & 0x3F == 0 { + if let Some(cap) = max_alloc_bytes { + let used = self.run_alloc_bytes(); + if used > cap { + vm_warn!( + "dispatch: memory limit {cap} exceeded at reentry hop {} (used {used}) at pc={}", + self.reentry_hops, + self.pc + ); + return Err(format!("VM memory limit {cap} exceeded (used {used}) at pc={}", self.pc)); + } + } + } + } loop { steps += 1; // 执行期 GC 安全点:仅在顶层 dispatch(native_call_depth == 0) diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 49736b96..2c61ab7a 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -83,6 +83,7 @@ impl Vm { root_reg_limit: 0, active_reg_limit: 0, native_call_depth: 0, + reentry_hops: 0, inline_args_base: 0, inline_args_count: 0, accessor_frame_target_reg: None, @@ -196,6 +197,7 @@ impl Vm { root_reg_limit: 0, active_reg_limit: 0, native_call_depth: 0, + reentry_hops: 0, inline_args_base: 0, inline_args_count: 0, accessor_frame_target_reg: None, @@ -473,6 +475,8 @@ impl Vm { self.async_gen_dispatch = false; self.async_gen_suspended = false; self.native_call_depth = 0; + // 重入 hop 计数是执行期状态:分配上限按 run 起算,跨 run/reset 不保留。 + self.reentry_hops = 0; // inline 窗口缓冲池内容为已废弃快照,跨 run/reset 不保留。 self.inline_reg_pool = None; // 微任务队列是执行期状态:跨 run 不保留。 @@ -857,6 +861,28 @@ mod tests { assert_eq!(result.as_int(), 10000); } + /// native 终端循环泵送小 JS 重入:每次重入 dispatch 远短于循环内 64 指令采样点, + /// 顶层 steps 不推进——重入边界每 64 hop 强制采样兜底,失控分配以 memory limit + /// 终止而非无限循环。 + #[test] + fn reentry_pump_hits_alloc_cap() { + let mut cfg = KernelConfig::minimal(); + cfg.max_alloc_bytes = Some(256 * 1024); + let mut vm = Vm::with_kernel_core(KernelCore::new(cfg)); + // 永不 done 的生成器经鸭子对象交给 toArray:native 循环每步泵送两个短重入 + // (闭包调用 + 生成器恢复)并新产一个结果对象,分配无界增长。 + let source = "var g = (function* () { for (var i = 0; ; ++i) { yield i; } })(); \ + var obj = { next: function () { return g.next(); } }; \ + Iterator.from(obj).toArray();"; + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = oxide_compiler::compiler::Compiler::new() + .compile(&program) + .expect("compile failed"); + let err = vm.run(&module).expect_err("reentry pump must hit the alloc cap"); + assert!(err.contains("memory limit"), "unexpected error: {err}"); + } + /// 直接恢复生成器一步:等价 `it.next()`,但避免跨 run(sub_modules 重建会使 /// 挂起帧失效)——回归聚焦 GC 搬移后的状态盒有效性。 fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue {