From 542236a15a2a61cb5f136a288d69122cd616d239 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 11 Sep 2026 21:42:55 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20clear=5Ftransient=20=E8=A1=A5=E6=B8=85?= =?UTF-8?q?=20overflow=20=E6=80=81=EF=BC=88id=20=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E5=AE=8C=E6=95=B4=E5=A4=8D=E4=BD=8D=EF=BC=89+=20kernel=20?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20active=5Fvms=20=E8=AE=A1=E6=95=B0=EF=BC=8C?= =?UTF-8?q?sweep/=E9=87=8D=E5=BB=BA/kernel-drop=20=E4=B8=89=E5=A4=84=20deb?= =?UTF-8?q?ug=5Fassert=20=E5=AE=88=E5=8D=AB'=E6=B8=85=E7=A9=BA=E6=97=B6?= =?UTF-8?q?=E7=82=B9=E6=97=A0=E5=AD=98=E6=B4=BB=20VM'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_kernel/src/kernel.rs | 48 +++++++++++++++++++++++--- crates/oxide_kernel/src/shape_forge.rs | 24 ++++++++++++- crates/oxide_test262/src/main.rs | 2 ++ crates/oxide_vm/src/vm.rs | 2 ++ crates/oxide_vm/src/vm_support.rs | 36 +++++++++++++++++++ 5 files changed, 107 insertions(+), 5 deletions(-) diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index 386e25cb..89f04ecf 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -1,6 +1,7 @@ #![allow(clippy::arc_with_non_send_sync)] use std::num::NonZeroUsize; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use oxide_types::mem::P; @@ -136,6 +137,10 @@ pub struct KernelCore { pub shape_forge: Arc, pub code_forge: Arc, pub prop_forge: Arc, + /// 边界不变量守卫计数:当前持有本 kernel `Arc` 的 Vm 数。 + /// `Vm` 构造器增、`Drop for Vm` 减(恰好一次),供 sweep / kernel 重建 / + /// kernel drop 处断言"无存活 VM"边界。 + active_vms: AtomicUsize, } impl KernelCore { @@ -159,6 +164,7 @@ impl KernelCore { shape_forge, code_forge, prop_forge, + active_vms: AtomicUsize::new(0), }); kernel_info!("KernelCore initialized: max_cached_modules={}, min_pool={}", max_cached, min_pool); core @@ -221,12 +227,19 @@ impl KernelCore { /// /// # 边界与前提 /// - 调用点须无存活 VM:对象头与 IC 词持 shape id,清空使 id 空间复位,跨复位 - /// 存活的 VM 会因 id 复用碰撞静默错槽;引擎不自动重建也不自动守卫,该前提 - /// 由宿主契约承担(runner 由循环结构满足:VM 每测试新建即弃,sweep 点在 - /// VM 作用域外)。 + /// 存活的 VM 会因 id 复用碰撞静默错槽;该前提由 debug_assert 守卫(开发期 + /// fail-fast),release 构建无断言,宿主可经 [`Self::active_vms`] 自检; + /// - 引擎不自动重建:id 空间的另一类复位(kernel 整体重建)由宿主在 + /// "无存活 VM" 边界驱动(runner 由循环结构满足:VM 每测试新建即弃, + /// 重建/sweep 点都在 VM 作用域外)。 pub fn sweep_runner_forges(&self) { + // id 空间复位在有存活 VM 时是静默错槽隐患,守卫先于阈值判断。 + debug_assert!( + self.active_vms.load(Ordering::Relaxed) == 0, + "sweep_runner_forges requires no live VMs (shape id space reset)" + ); // 键 interner 是 append-only(无逐次清理);仅当瞬时 shape/prop 表超阈值 - // 时清表(批内兜底),调用点无存活 VM 的前提见文档。 + // 时清表(批内兜底)。 if self.shape_forge.len() > 50_000 { self.shape_forge.clear_transient(); self.prop_forge.clear(); @@ -263,6 +276,33 @@ impl KernelCore { let cap = self.config.perm_interner_max_entries?; (self.perm_interner.entry_count() > cap).then_some(cap.next_power_of_two().saturating_mul(2)) } + + /// 边界不变量守卫计数:登记一个 Vm 出生(`Vm` 两条构造路径完整构造后调用)。 + pub fn note_vm_started(&self) { + self.active_vms.fetch_add(1, Ordering::Relaxed); + } + + /// 边界不变量守卫计数:注销一个 Vm 死亡(`Drop for Vm` 恰好调用一次)。 + pub fn note_vm_ended(&self) { + self.active_vms.fetch_sub(1, Ordering::Relaxed); + } + + /// 读取当前持有本 kernel 的存活 Vm 数;release 宿主可在 id 空间复位边界自检。 + pub fn active_vms(&self) -> usize { + self.active_vms.load(Ordering::Relaxed) + } +} + +impl Drop for KernelCore { + fn drop(&mut self) { + // kernel 能 drop 说明持 Arc 的 Vm 已全部归零;计数非零 = 纯计数漂移 + // (note_vm_started/note_vm_ended 配对挂接回归探测)。 + debug_assert_eq!( + self.active_vms.load(Ordering::Relaxed), + 0, + "KernelCore dropped with live VMs (note_vm_ended counter drift)" + ); + } } /// 每会话可变状态:内置原型对象与全局对象。 diff --git a/crates/oxide_kernel/src/shape_forge.rs b/crates/oxide_kernel/src/shape_forge.rs index 779104be..211c8c2d 100644 --- a/crates/oxide_kernel/src/shape_forge.rs +++ b/crates/oxide_kernel/src/shape_forge.rs @@ -159,16 +159,21 @@ impl ShapeForge { self.len() == 0 } - /// 清空所有非根 shape、transition 与 position 缓存,id 计数器复位。 + /// 清空所有非根 shape、transition、position 缓存与 overflow 态,id 计数器复位。 /// /// 用于 session 重建:丢弃一次性对象产生的形状,只保留跨 session 共享的根节点。 + /// overflow 态一并复位(不清则旧 overflow 条目把 key 映射回已被截断的 id, + /// 造成静默错深度与去重失效),id 空间完整复位。 pub fn clear_transient(&self) { let mut shapes = self.shapes.write().unwrap(); if shapes.len() > 1 { shapes.truncate(1); } + drop(shapes); self.transitions.clear(); self.positions.clear(); + self.overflow_active.store(false, Ordering::Relaxed); + self.overflow_map.write().unwrap().clear(); self.next_id.store(2, Ordering::Relaxed); } @@ -320,6 +325,23 @@ mod tests { assert_eq!(forge.shapes.read().unwrap().len(), 1); } + /// overflow 态复位钉:曾触发 overflow 后清空,overflow 标志与映射须一并清空, + /// 后续 make_shape 从新空间取小 id(而非旧 overflow 映射 id)。 + #[test] + fn clear_transient_resets_overflow_state() { + let forge = ShapeForge::new(); + let key = ShapeForge::pack_key(EMPTY_SHAPE_ID, 1_000_500); + forge.overflow_active.store(true, Ordering::Relaxed); + forge.overflow_map.write().unwrap().insert(key, 0xDEAD); + + forge.clear_transient(); + + assert!(!forge.overflow_active.load(Ordering::Relaxed)); + assert!(forge.overflow_map.read().unwrap().is_empty()); + let id = forge.make_shape(EMPTY_SHAPE_ID, 1_000_500); + assert_eq!(id, 2); + } + #[test] fn two_branches_share_ancestor() { let forge = ShapeForge::new(); diff --git a/crates/oxide_test262/src/main.rs b/crates/oxide_test262/src/main.rs index 8488f67b..f8db1c9f 100644 --- a/crates/oxide_test262/src/main.rs +++ b/crates/oxide_test262/src/main.rs @@ -1802,6 +1802,8 @@ fn run_tests() -> bool { if tests_since_kernel_reset >= kernel_batch { // 重建即全新 forge(结构性必清):旧核连同其表整体丢弃, // 先 sweep 是对 doomed 核白做一次 O(50k) 清理。 + // 重建边界契约:旧核上无存活 VM(VM 每测试局部,此处作用域外)。 + debug_assert!(kernel.active_vms() == 0, "kernel rebuild requires no live VMs"); kernel = build_runner_kernel(); tests_since_kernel_reset = 0; } else if done % 500 == 0 { diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 3edde3a3..21f0489f 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -571,6 +571,8 @@ pub struct Vm { impl Drop for Vm { fn drop(&mut self) { + // 边界守卫计数:与构造器登记恰好配对(Rust 所有权保证恰好一次)。 + self.kernel_core.note_vm_ended(); // 直接 drop(test262 每测试新建即弃)不经 reset/full_reset 路径: // 统一收尾释放全部 session 堆数据与内建原型属性区,防逐测试累积泄漏。 self.teardown_intrinsic_protos(); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 2909b304..8e45534a 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -145,6 +145,8 @@ impl Vm { vm.init_async_generator_intrinsics(); // Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。 vm.session.record_snapshot(); + // 边界守卫计数:VM 完整构造后登记,与 `Drop for Vm` 的注销恰好配对。 + vm.kernel_core.note_vm_started(); vm_info!("Vm created"); vm } @@ -260,6 +262,8 @@ impl Vm { vm.init_async_generator_intrinsics(); // Promise 全局绑定发生在快照采集之后,重录快照避免首次 full_reset 误判脏。 vm.session.record_snapshot(); + // 边界守卫计数:VM 完整构造后登记,与 `Drop for Vm` 的注销恰好配对。 + vm.kernel_core.note_vm_started(); vm_info!("Vm created (pool)"); vm } @@ -820,6 +824,38 @@ mod tests { vm.run(&module).expect("vm run failed") } + /// 计数往返:两条构造路径(独立核/共享核)的登记与 Drop 注销恰好配对, + /// 全部 drop 后计数归零,kernel 可干净 drop(Drop 断言无残留)。 + #[test] + fn active_vms_count_roundtrip() { + let vm = Vm::new(); + assert_eq!(vm.kernel_core.active_vms(), 1); + drop(vm); + + let core = KernelCore::new(KernelConfig::minimal()); + assert_eq!(core.active_vms(), 0); + let v1 = Vm::with_kernel_core(Arc::clone(&core)); + let v2 = Vm::with_kernel_core(Arc::clone(&core)); + let v3 = Vm::with_kernel_core(Arc::clone(&core)); + assert_eq!(core.active_vms(), 3); + drop(v3); + assert_eq!(core.active_vms(), 2); + drop(v1); + drop(v2); + assert_eq!(core.active_vms(), 0); + drop(core); + } + + /// 守卫判别:持活 VM 调 sweep 触发 debug_assert。VM 声明在 kernel 之后, + /// panic unwind 时先注销计数再 drop kernel,drop 断言不受干扰。 + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "no live VMs"))] + fn sweep_runner_forges_rejects_live_vm() { + let core = KernelCore::new(KernelConfig::minimal()); + let _vm = Vm::with_kernel_core(Arc::clone(&core)); + core.sweep_runner_forges(); + } + #[test] fn full_reset_with_session_objects_forces_global_rebuild() { let mut vm = Vm::new();