diff --git a/crates/oxide_vm/src/test262_host.rs b/crates/oxide_vm/src/test262_host.rs index 376628d1..6095f678 100644 --- a/crates/oxide_vm/src/test262_host.rs +++ b/crates/oxide_vm/src/test262_host.rs @@ -69,14 +69,20 @@ pub fn create_realm(vm: &mut Vm, _args: &[u8]) -> NativeResult { not_supported(vm, "createRealm") } -/// `$262.gc()`:请求一次 session 全量回收(不计返回值)。 +/// `$262.gc()`:请求一次 session 全量强制收集,返回 undefined。 +/// +/// 收集延迟到下一个顶层指令边界执行,不在 native 重入点就地跑:重入期间 +/// dispatch 调用链上的调用方寄存器窗口副本与 callee 引用是 Rust 局部值 +/// (非 GC 根),移动式 sweep 就地搬移会使它们悬垂,返回后恢复动作还会 +/// 把陈旧指针写回 `regs[254]`。顶层指令边界是无在途局部值的安全点, +/// 收集经既有完整收集入口(与引擎正常触发路径同一函数)执行。 +/// 对 JS 可观察语义等价于同步:收集在下一条指令求值前完成。 /// /// # 副作用 -/// - 执行 mark-sweep 搬移存活对象并释放不可达对象,会重写 session 对象指针。 +/// - 置强制收集旗标;下一个顶层指令边界执行 mark + 移动式 sweep + +/// 字符串/BigInt 清扫,重写 session 对象指针。 pub fn gc(vm: &mut Vm, _args: &[u8]) -> NativeResult { - let mut session_gc = std::mem::take(&mut vm.gc_state.session_gc); - session_gc.sweep(vm); - vm.gc_state.session_gc = session_gc; + vm.gc_state.pending_forced_collect = true; NativeResult::Ok(JsValue::undefined()) } diff --git a/crates/oxide_vm/src/vm/dispatch_loop.rs b/crates/oxide_vm/src/vm/dispatch_loop.rs index 797c4dea..402ce824 100644 --- a/crates/oxide_vm/src/vm/dispatch_loop.rs +++ b/crates/oxide_vm/src/vm/dispatch_loop.rs @@ -121,6 +121,12 @@ impl Vm { // 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅 // 少量字段比较。 if self.native_call_depth == 0 { + // 宿主 `$262.gc()` 置位的强制收集在此边界消费:移动式 sweep + // 唯一安全点(无在途 dispatch 局部值),与正常触发路径同一入口。 + if self.gc_state.pending_forced_collect { + self.gc_state.pending_forced_collect = false; + self.collect_session_gc(); + } // 峰值高水位:同一边界采样 session 堆账目上界 let bytes = self.gc_state.session_bytes_allocated; if bytes > self.gc_state.session_bytes_peak { diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index 03775694..797112ca 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -58,6 +58,9 @@ pub(crate) struct GcState { /// 门控未过(挂起态持有 for-in)后的重扫锚:包络再增长一个阈值才重扫 /// 三型状态盒(O(对象数) 扫描),避免持挂起 for-in 的 run 每指令边界重扫。 pub(crate) gc_gate_retry_alloc: usize, + /// 宿主强制收集旗标(`$262.gc()`):native 重入中置位,由下一个顶层 + /// 指令边界(移动式 sweep 的唯一安全点)消费并执行完整收集。 + pub(crate) pending_forced_collect: bool, pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 917e30b3..2883f07d 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -134,6 +134,7 @@ impl Vm { gc_threshold_cached: gc_threshold, gc_watermark: gc_threshold, gc_gate_retry_alloc: 0, + pending_forced_collect: false, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, symbols: SymbolState { @@ -267,6 +268,7 @@ impl Vm { gc_threshold_cached: gc_threshold, gc_watermark: gc_threshold, gc_gate_retry_alloc: 0, + pending_forced_collect: false, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, symbols: SymbolState { diff --git a/crates/oxide_vm/tests/test262_host_tests.rs b/crates/oxide_vm/tests/test262_host_tests.rs index 5e882a0f..14da0b50 100644 --- a/crates/oxide_vm/tests/test262_host_tests.rs +++ b/crates/oxide_vm/tests/test262_host_tests.rs @@ -4,7 +4,9 @@ use std::sync::Arc; use oxide_compiler::compiler::Compiler; +use oxide_kernel::kernel::{KernelConfig, KernelCore}; use oxide_parser::Allocator; +use oxide_types::value::JsValue; use oxide_vm::vm::Vm; /// 单测辅助:源串 → 结果串(字符串值解包实际内容,运行错误以 `vm error: ...` 呈现)。 @@ -25,6 +27,14 @@ fn eval(source: &str) -> String { } } +/// 单测辅助:在调用方提供的 VM 上执行源串,返回原始结果值(低阈值配置钉用)。 +fn eval_on(vm: &mut Vm, source: &str) -> Result { + let allocator = Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse error: {}", e[0].message))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("compile error: {e}"))?; + vm.run(&Arc::new(module)) +} + /// `$262` 作为编译期已知全局绑定:typeof 为 object,`global` 即 globalThis。 #[test] fn host_object_exists_and_exposes_global() { @@ -85,3 +95,25 @@ fn detach_array_buffer_detaches() { fn gc_runs_without_error() { assert_eq!(eval("$262.gc(); 'ok'"), "ok"); } + +/// `$262.gc()` 存活钉(低阈值形态):小阈值长循环反复触发执行期收集, +/// 全局可达的 session 对象经强制收集后身份与属性读回保持完整—— +/// 收集前的 mark 必须覆盖全部可达对象,漏标即误回收、读回悬垂。 +#[test] +fn gc_keeps_reachable_session_objects_alive() { + let mut config = KernelConfig::minimal(); + config.set_session_gc_threshold(4096); + let mut vm = Vm::with_kernel_core(KernelCore::new(config)); + let result = eval_on( + &mut vm, + "(function () { \ + globalThis.o = { x: 41, s: 'keep' }; \ + globalThis.o.x = 42; \ + for (var i = 0; i < 500000; i++) { var t = 'str' + i; } \ + $262.gc(); \ + return globalThis.o.x + globalThis.o.s.length === 46 \ + && $262.global.o === globalThis.o; })()", + ) + .unwrap(); + assert!(result.as_bool()); +}