From d026ea626ec6820424a0b0a0256b0fc7859fcdae Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 12 Oct 2026 19:47:16 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20run=20=E8=A3=85=E8=BD=BD=E6=9C=9F?= =?UTF-8?q?=E5=B8=B8=E9=87=8F=E8=BD=AC=E6=8D=A2=E4=B8=8E=20si=20=E4=BE=A7?= =?UTF-8?q?=E8=A1=A8=E6=94=B9=20Vm=20=E6=A8=A1=E5=9D=97=E7=BA=A7=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ++++++------- Cargo.toml | 2 +- crates/oxide_vm/src/suspended.rs | 14 +++-- crates/oxide_vm/src/vm/call.rs | 2 +- crates/oxide_vm/src/vm/gc_hooks.rs | 21 ++++--- crates/oxide_vm/src/vm/mod.rs | 39 +++++++++++- crates/oxide_vm/src/vm/tables.rs | 82 +++++++++++++++---------- crates/oxide_vm/src/vm_runtime.rs | 4 +- crates/oxide_vm/src/vm_support.rs | 74 ++++++++++++++++------ crates/oxide_vm/src/vm_support/tests.rs | 45 ++++++++++++++ 10 files changed, 233 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a15344ac..90069bdf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.17.6" +version = "0.17.7" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.17.6" +version = "0.17.7" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.17.6" +version = "0.17.7" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.17.6" +version = "0.17.7" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.17.6" +version = "0.17.7" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.17.6" +version = "0.17.7" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.17.6" +version = "0.17.7" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.17.6" +version = "0.17.7" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.17.6" +version = "0.17.7" [[package]] name = "oxide_parser" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.17.6" +version = "0.17.7" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.17.6" +version = "0.17.7" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.17.6" +version = "0.17.7" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.17.6" +version = "0.17.7" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 2d1e6498..a99e6366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.17.6" +version = "0.17.7" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index d2ffabf3..626f5365 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -140,11 +140,15 @@ impl SuspendedFrame { } else { vm.current_gen }; - let table = vm.tables.get(&gen); - let module = table.and_then(|t| t.modules.get(self.sub_idx as usize)); - let constants = module.map(|m| m.constants.clone()); - match constants { - Some(c) => vm.activate_immutables(gen, self.sub_idx as usize, &c), + // 平表条目取 Arc 克隆(与调用方共享同一编译产物):后续 activate_immutables + // 等 &mut 调用期间不持有对注册表的借用。 + let module = vm + .tables + .get(&gen) + .and_then(|t| t.modules.get(self.sub_idx as usize)) + .cloned(); + match module { + Some(m) => vm.activate_immutables(gen, self.sub_idx as usize, &m), None => return Err("suspended state module table is no longer available".into()), } // 模板缓存键维度随模块恢复:与 activate_immutables 同位还原。 diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index b7000ebe..f156f210 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -393,7 +393,7 @@ impl Vm { self.pc = 0; self.bytecode = sub_bytecode; - self.activate_immutables(gen, sub_idx, &sub.constants); + self.activate_immutables(gen, sub_idx, &sub); self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); self.reload_builtin_mirror_slots(&sub.builtin_reg_map, 0); diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index 921bdd23..83e18058 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -61,15 +61,12 @@ impl Vm { for value in &self.regs { f(RootGroup::Regs, *value); } - // 各代际 immutables 缓存含 session BigInt(new_bigint 分配),未入根则被 - // sweep 释放 → 常量池加载时悬垂。perm 字符串无害(不在 session 集合中)。 - for table in self.tables.values() { - for once_lock in &table.immutables { - if let Some(immutable_vec) = once_lock.get() { - for &value in immutable_vec.iter() { - f(RootGroup::Immutables, value); - } - } + // 模块级装载缓存的常量池含 BigInt(条目自持存储,不在 session_bigint_ptrs + // 内):未入根则逐组计数失真(BigInt 不在串表内、sweep 不触达,无悬垂)。 + // perm 字符串无害(不在 session 集合中)。 + for entry in self.module_cache.values() { + for &value in entry.immutables.iter() { + f(RootGroup::Immutables, value); } } for frame in &self.frames { @@ -265,6 +262,9 @@ impl Vm { /// O(1) 读,供单 run 分配上限判定。收集释放后读数回落存活集量级; /// 轻量 reset 保留账目与表(仅复位峰值与水位),full_reset 清零重起算。 /// + /// 注意:BigInt 表长分量不含模块级装载缓存的常量 BigInt(条目自持存储, + /// 一次性装载成本、非 run 内分配),单 run 分配上限语义为此略放宽,可接受。 + /// /// 注意:手工堆账目只在对象分配/字符串分配/GC 回收点更新,执行期对象 /// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合 /// [`Self::run_alloc_bytes_full`] 的深采样层。 @@ -286,7 +286,8 @@ impl Vm { /// 存活对象头加堆数据(属性/元素/meta 向量容量、upvalue 列表容量、native /// 状态盒)+ 存活串(头加 payload)+ BigInt 表长 × 尺寸 + cell 表长 × /// 尺寸。重算即属性区扩容等账目盲区的兜底,读数为当前真实总量,零增长时 - /// 与 [`Self::run_alloc_bytes`] 的轻层公式逐位相等。 + /// 与 [`Self::run_alloc_bytes`] 的轻层公式逐位相等。BigInt 表长分量同 + /// 轻层口径:不含模块级装载缓存的常量 BigInt(条目自持存储)。 pub(crate) fn run_alloc_bytes_full(&self) -> u64 { let mut bytes = 0u64; for &ptr in self.realm.gc.borrow().session_object_ptrs.iter() { diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 2c975e06..899fd3b4 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -9,7 +9,6 @@ use std::sync::Arc; #[cfg(test)] use std::sync::OnceLock; -#[cfg(test)] use oxide_bytecode::module::CompiledModule; use oxide_bytecode::opcode; use smallvec::SmallVec; @@ -185,6 +184,27 @@ pub(crate) enum DisposeResumeKind { Escape(Completion), } +/// 模块级装载缓存条目:常量池与 si 侧表各存一份,BigInt 存储随条目自持。 +/// +/// 生命周期与 Vm 同消亡(跨 `full_reset` 存活):常量 BigInt 不在 +/// `session_bigint_ptrs` 内,session 拆除不悬垂;`Drop for Vm` 随条目 Vec 整体 +/// 释放,无手工 `Box::from_raw`、无双放。 +pub(crate) struct ModuleLoadEntry { + /// 保持模块 Arc 存活:键指针在条目存活期内恒有效,杜绝分配器复用旧指针 + /// 撞出新模块的假命中。不得为省一个 Arc 引用而改为裸指针键。 + /// 只保活不读值。 + #[allow(dead_code)] + pub(crate) module: Arc, + /// 已转换常量池(BigInt 条目指针指向 bigint_storage 内对应元素)。 + pub(crate) immutables: Arc>, + /// BigInt 存储:immutables 中 BigInt 指针的宿主(下标一一对应), + /// 由缓存条目独占,随 Vm 消亡。 + #[allow(dead_code)] + pub(crate) bigint_storage: Arc>, + /// NEW_OBJECT 静态键 si 侧表(纯 u32,无 GC 边)。 + pub(crate) si_table: Arc>>, +} + /// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。 /// /// 执行入口为 [`Vm::run`](见 `vm_runtime` 模块);内存模型为统一入口分配 + @@ -198,8 +218,9 @@ pub struct Vm { /// 零拷贝),子模块平表源保持惰性 COW,IC 写回经 `bytecode_mut` 的 /// `Arc::make_mut` 写时复制,保证独占后才改写(miss 时才深拷贝,频率低)。 pub(crate) bytecode: Arc<[opcode::Instr]>, - /// 当前活动模块已转换不可变常量的只读视图(指向当前表代际的 immutables 内部)。 - /// 用胖 `*const`:常量 Vec 归表代际所有且 OnceLock 只填一次(堆分配地址稳定)。 + /// 当前活动模块已转换不可变常量的只读视图(指向模块级装载缓存条目的 + /// 常量 Vec 内部)。用胖 `*const`:常量 Vec 归缓存条目所有(随 Vm 同消亡、 + /// 跨 full_reset 存活)且只填充一次(堆分配地址稳定)。 pub(crate) active_immutables: *const [JsValue], pub(crate) frames: SmallVec<[CallFrame; 16]>, /// 帧身份单调计数器:压帧时分配 `CallFrame.frame_id`(mapped arguments 状态盒 @@ -247,6 +268,13 @@ pub struct Vm { /// 存活函数对象引用的代际(`reclaim_unreferenced_tables`),内存有界于 /// 存活函数对象数,不随 run 数单调增。 pub(crate) tables: HashMap>, + /// 模块级装载缓存:键 = 编译模块 Arc 指针身份,条目自持模块 Arc 防指针 + /// 复用撞键。命中时零转换、零字节码扫描、零 BigInt 分配;未命中时转换一次 + /// 并填缓存。生命周期与 Vm 同消亡:`full_reset` 的 `teardown_session_heap_data` + /// 不触碰条目 BigInt 存储(不在 `session_bigint_ptrs` 内),拆除不悬垂。 + /// 条目数有界于该 Vm 跑过的不同模块数(CodeForge 容量加动态编译面), + /// 不随 run 数增长。 + pub(crate) module_cache: HashMap<*const CompiledModule, ModuleLoadEntry>, /// 当前 run 正在装载的表代际(构造器预登记 gen 0 空表占位,run() 换表时 +1)。 pub(crate) current_gen: u32, /// 帧切换时暂存调用方字节码的 Arc 栈(与 `bytecode` 同共享语义)。 @@ -495,6 +523,11 @@ impl Drop for Vm { // 6. worker_objects 字段(HashMap)持本 Vm session 堆的 Worker // 对象指针,归不变量 1 覆盖:对象由本 Vm 独占分配、地址稳定、随 Vm 整体 // 迁移,跨线程移动不悬垂。 +// +// 7. module_cache 字段(HashMap<*const CompiledModule, ModuleLoadEntry>): +// 条目自持模块 Arc 与常量 BigInt 存储(Vec),存储随 Vm 整体迁移 +// (同不变量 1 条款:Vm 独占分配、地址稳定、跨线程移动不悬垂);键是 +// Arc 指针身份,条目自持 Arc 保证键指针在条目存活期内不被分配器复用。 unsafe impl Send for Vm {} impl Vm { diff --git a/crates/oxide_vm/src/vm/tables.rs b/crates/oxide_vm/src/vm/tables.rs index 53e99a26..8db7953d 100644 --- a/crates/oxide_vm/src/vm/tables.rs +++ b/crates/oxide_vm/src/vm/tables.rs @@ -3,17 +3,18 @@ use std::sync::{Arc, OnceLock}; -use oxide_bytecode::module::{CompiledModule, Constant}; +use oxide_bytecode::module::CompiledModule; use oxide_bytecode::opcode; use oxide_types::object::JsObject; use oxide_types::value::JsValue; use super::Vm; -/// 单代子模块平表:模块平表 + 每模块不可变常量缓存,注册进 `Vm::tables` 按代际管理。 +/// 单代子模块平表:模块平表 + 每模块不可变常量缓存槽位,注册进 `Vm::tables` +/// 按代际管理。 /// /// 以 `Box` 承载(HashMap 扩容搬 Box 指针不搬被指物):`active_immutables` 与 -/// `saved_immutables_stack` 是伸入 immutables 内部常量 Vec 的胖指针,表地址须 +/// `saved_immutables_stack` 是伸入模块级装载缓存条目常量 Vec 的胖指针,表地址须 /// 在其整个生命周期内稳定。动态扩表(`create_dynamic_function` / /// `create_dynamic_script`)对 modules 外层 Arc 做 `make_mut`:彼时该 Arc 的 /// 强引用唯一持有者是本表(函数对象只持 u32 下标不持 Arc),原地扩展不分叉。 @@ -21,15 +22,17 @@ pub(crate) struct TableGen { /// 全局扁平模块表:下标 = 模块 `flat_id`(顶层 0,子模块 flatten 后全局唯一)。 /// 函数对象 `sub_module_index` 即该平表下标,逃逸函数也能自足解析。 pub(crate) modules: Arc>>, - /// 每次 run 转换一次的不可变常量缓存:下标 0 = 顶层模块,sub_idx = modules[sub_idx]。 - /// 每个 `OnceLock` 保存该模块常量本次运行中只转换一次的 `JsValue` 结果。 - /// 不可变常量是标量 + perm 字符串,只读,GC 根收集按表代际遍历。 - pub(crate) immutables: Vec>>, - /// NEW_OBJECT 静态键 si 侧表,与 `immutables` 平行(下标 = flat_id,内部下标 = - /// 常量池下标):`Some(si)` 是模块装载期预 intern 的键 si,`None` 表示该常量 - /// 不是静态键。填充与 `immutables` 激活同点(`activate_immutables`),构造期 - /// 直读免逐构造键推导。 - pub(crate) si_tables: Vec>>>, + /// 不可变常量缓存槽位:下标 0 = 顶层模块,sub_idx = modules[sub_idx]。 + /// 每个 `OnceLock` 克隆模块级装载缓存条目(`Vm::module_cache`)中该模块 + /// 只转换一次的 `JsValue` 结果 Arc 入槽;槽位只作代际视图,转换本体归 + /// 缓存条目所有(跨 `full_reset` 存活)。不可变常量是标量 + perm 字符串 + + /// 条目自持 BigInt,只读,GC 根收集按缓存条目遍历。 + pub(crate) immutables: Vec>>>, + /// NEW_OBJECT 静态键 si 侧表槽位,与 `immutables` 平行(下标 = flat_id, + /// 内部下标 = 常量池下标):`Some(si)` 是模块装载期预 intern 的键 si, + /// `None` 表示该常量不是静态键。填充与 `immutables` 激活同点 + /// (`activate_immutables`),构造期直读免逐构造键推导。 + pub(crate) si_tables: Vec>>>>, } impl Vm { @@ -39,9 +42,9 @@ impl Vm { if self.active_immutables.is_null() { &[] } else { - // SAFETY: active_immutables 指向某表代际 immutables 内 OnceLock 的常量 - // Vec,Vec 只填充一次且堆地址稳定;所指代际表在注册表内存活(当前 - // 代际恒在,旧代际由存活函数对象引用保活)。 + // SAFETY: active_immutables 指向模块级装载缓存条目(module_cache)的 + // 常量 Vec,条目随 Vm 同消亡(跨 full_reset 存活),Vec 只填充一次且 + // 堆地址稳定。 unsafe { &*self.active_immutables } } } @@ -94,26 +97,43 @@ impl Vm { self.tables.len() } - /// 激活代际 `gen` 平表中模块 `cache_idx` 的不可变常量,只转换一次存入该代际 - /// `immutables[cache_idx]`,并把 `active_immutables` 指向该 Vec。 - /// `constants` 由调用方传入(它已持有 `&module.constants`)。 + /// 激活代际 `gen` 平表中模块 `cache_idx` 的不可变常量,并把 + /// `active_immutables` 指向该模块已转换常量池。 + /// + /// 转换本体归模块级装载缓存(`Vm::module_cache`,以模块 Arc 指针身份为键) + /// 所有:命中时零转换、零字节码扫描、零 BigInt 分配;未命中时 + /// `convert_module_constants` 转换一次并填缓存。本方法只把条目 Arc 克隆入 + /// 该代际槽位(O(1)),槽位随代际回收,条目随 Vm 消亡。 /// /// # 边界与前提 /// - `gen` 的表须已在注册表中(当前代际由 run() 登记,旧代际由存活函数 /// 对象引用保活);`cache_idx` 须小于该代际平表长度。 - pub(crate) fn activate_immutables(&mut self, gen: u32, cache_idx: usize, constants: &[Constant]) { - // 用裸指针访问缓存槽,避免 get_or_init(借用表 immutables)与 &self 的 - // convert_immutables 闭包和随后对 active_immutables 的写入发生借用冲突。 - // 成立前提:代际表归注册表所有、只读、Box 承载地址稳定。 + /// - `module` 是平表条目同一 Arc 实例(调用方克隆),作缓存键。 + pub(crate) fn activate_immutables(&mut self, gen: u32, cache_idx: usize, module: &Arc) { + // 用裸指针访问缓存槽,避免 get_or_init(借用表 immutables)与查/建 + // module_cache 的借用发生冲突。成立前提:代际表归注册表所有、只读、 + // Box 承载地址稳定。 let table = self.tables.get_mut(&gen).expect("激活的代际表须在注册表中"); - let slot: *const OnceLock> = &table.immutables[cache_idx]; - // si 侧表与 immutables 同点填充:模块字节码从平表取(同裸指针手法, - // 平表 Arc 归注册表所有、地址稳定),预 intern 静态键 si。 - let si_slot: *const OnceLock>> = &table.si_tables[cache_idx]; - let bc: *const [opcode::Instr] = &table.modules[cache_idx].bytecode[..]; - let vec = unsafe { &*slot }.get_or_init(|| self.convert_immutables(constants)); - unsafe { &*si_slot }.get_or_init(|| self.fill_si_table(vec, unsafe { &*bc })); - self.active_immutables = vec.as_slice() as *const [JsValue]; + let slot: *const OnceLock>> = &table.immutables[cache_idx]; + // si 侧表槽位与 immutables 同点填充(条目共享同一份 si 表 Arc)。 + let si_slot: *const OnceLock>>> = &table.si_tables[cache_idx]; + let key = Arc::as_ptr(module); + let entry = match self.module_cache.get(&key) { + Some(e) => e, + None => { + let (immutables, bigint_storage, si_table) = self.convert_module_constants(module); + let new_entry = super::ModuleLoadEntry { + module: Arc::clone(module), + immutables, + bigint_storage, + si_table, + }; + self.module_cache.entry(key).or_insert(new_entry) + } + }; + unsafe { &*slot }.get_or_init(|| Arc::clone(&entry.immutables)); + unsafe { &*si_slot }.get_or_init(|| Arc::clone(&entry.si_table)); + self.active_immutables = entry.immutables.as_slice() as *const [JsValue]; } /// 填充模块的 NEW_OBJECT 静态键 si 侧表:按 dispatch 主循环同口径 @@ -124,7 +144,7 @@ impl Vm { /// # 边界与前提 /// - 键值取自已转换的不可变常量(`values`),与构造期读同一池。 /// - 键推导失败(非字符串键的防御面)保持 None,构造期回退旧路径同口径重推。 - fn fill_si_table(&mut self, values: &[JsValue], bytecode: &[opcode::Instr]) -> Vec> { + pub(crate) fn fill_si_table(&mut self, values: &[JsValue], bytecode: &[opcode::Instr]) -> Vec> { let mut si = vec![None; values.len()]; let mut i = 0; while i < bytecode.len() { diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 9e9260d0..2b82cc8c 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -476,7 +476,7 @@ impl Vm { } self.pc = 0; self.bytecode = Arc::clone(&sub.bytecode); - self.activate_immutables(gen, sub_idx, &sub.constants); + self.activate_immutables(gen, sub_idx, &sub); self.active_flat_id = sub_idx as u32; self.active_table_gen = gen; self.active_reg_limit = sub.n_registers.max(1); @@ -726,7 +726,7 @@ impl Vm { } else { self.bytecode = Arc::clone(&module.bytecode); } - self.activate_immutables(self.current_gen, 0, &module.constants); + self.activate_immutables(self.current_gen, 0, module); self.root_reg_limit = module.n_registers.max(1); self.active_reg_limit = self.root_reg_limit; diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index f8ab3f51..14b34e35 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -4,7 +4,7 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::sync::Arc; -use oxide_bytecode::module::Constant; +use oxide_bytecode::module::{CompiledModule, Constant}; use crate::bindings; use crate::realm::Realm; @@ -76,6 +76,10 @@ impl FunctionFlags { } } +/// 模块级装载缓存条目三件套:已转换常量池、BigInt 自持存储、si 侧表。 +pub(crate) type ModuleLoadTriple = + (Arc>, Arc>, Arc>>); + impl Vm { /// Cons(rope)节点保留的单元阈值:拼接总长(UTF-16 单元数)≤ 该值时 /// 急切扁平。小链的"链接 + 首次消费扁平化"双重分配高于直接拷贝,直接 @@ -177,6 +181,7 @@ impl Vm { si_tables: Vec::new(), }), )]), + module_cache: std::collections::HashMap::new(), current_gen: 0, saved_bytecode_stack: Vec::new(), saved_immutables_stack: Vec::new(), @@ -344,6 +349,7 @@ impl Vm { si_tables: Vec::new(), }), )]), + module_cache: std::collections::HashMap::new(), current_gen: 0, saved_bytecode_stack: Vec::new(), saved_immutables_stack: Vec::new(), @@ -524,6 +530,9 @@ impl Vm { self.bytecode = Arc::default(); // 注册表随 session 重建整体清空:session 对象(唯一按代际保活表者)全部 // 消失,残留表无引用源。回到构造期同态:gen 0 空表占位 + current_gen = 0。 + // 模块级装载缓存(module_cache)此处**不得**清空:条目 BigInt 存储归条目 + // 自持(不在 session_bigint_ptrs 内),session 拆除不悬垂;误清则收益归零 + // (同模块跨 run 重装退化回每 run 全量重转换),无正确性错误。 self.tables.clear(); self.current_gen = 0; self.tables.insert( @@ -922,7 +931,8 @@ impl Vm { /// 分配一个 BigInt 值:把 `i128` 堆分配为 box 并返回携带指针的 `JsValue`。 /// /// box 指针登记进 `realm.gc.session_bigint_ptrs`,在 `full_reset` 统一释放。 - /// `&self` 使 `convert_immutables`(常量池 → JsValue)也能分配。 + /// `&self` 供运行时面调用(`BigInt(42)` 字面量、算术结果等);模块常量池 + /// BigInt 走模块级装载缓存的条目自持存储,不经本入口。 pub fn new_bigint(&self, v: num_bigint::BigInt) -> JsValue { let ptr = Box::into_raw(Box::new(v)); self.realm.gc.borrow_mut().session_bigint_ptrs.borrow_mut().push(ptr); @@ -935,23 +945,51 @@ impl Vm { unsafe { &*val.as_bigint_ptr() } } - fn convert_constant(&self, constant: &Constant) -> JsValue { - match constant { - Constant::Number(v) => JsValue::float(*v), - Constant::Int(v) => JsValue::int(*v), - Constant::BigInt(v) => self.new_bigint(v.clone()), - Constant::String(s) => self.perm_string(s), - Constant::Boolean(b) => JsValue::bool(*b), - Constant::Null => JsValue::null(), - Constant::Undefined => JsValue::undefined(), + /// 把模块常量池转换为模块级装载缓存条目三件套:已转换常量池、 + /// BigInt 自持存储、si 侧表。不可能失败——CreateClosure/CreateRegExp + /// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。 + /// + /// # 步骤 + /// 1. 按常量池 BigInt 计数精确预分配自持存储(`push` 后取地址作指针, + /// 存储扩容即指针悬垂)。 + /// 2. 逐常量转换:标量臂与既有口径一致;BigInt 条目入自持存储, + /// 指针指向存储内对应元素。 + /// 3. 经 `fill_si_table` 同口径填充 si 侧表。 + /// + /// # 注意事项 + /// - BigInt 常量只写条目自持存储,不登记 `session_bigint_ptrs`: + /// `new_bigint` 保持运行时专属(`BigInt(42)` 字面量、算术结果等), + /// 两表 box 生命周期不相交,full_reset 拆除与条目 Drop 各释各的,无双放。 + pub(crate) fn convert_module_constants(&mut self, module: &CompiledModule) -> ModuleLoadTriple { + let bigint_count = module + .constants + .iter() + .filter(|c| matches!(c, Constant::BigInt(_))) + .count(); + let mut bigint_storage = Vec::with_capacity(bigint_count); + let mut values = Vec::with_capacity(module.constants.len()); + for constant in &module.constants { + match constant { + Constant::Number(v) => values.push(JsValue::float(*v)), + Constant::Int(v) => values.push(JsValue::int(*v)), + Constant::BigInt(v) => { + bigint_storage.push(v.clone()); + // 存储按计数精确预分配、循环内不再扩容:push 后取地址, + // 指针在条目存活期内恒有效。 + // SAFETY: 存储未扩容,as_mut_ptr 指向的缓冲在条目存活期内 + // 不被释放也不被搬移,加偏移取的是刚 push 的元素。 + let ptr = unsafe { bigint_storage.as_mut_ptr().add(bigint_storage.len() - 1) }; + values.push(JsValue::bigint(ptr)); + } + Constant::String(s) => values.push(self.perm_string(s)), + Constant::Boolean(b) => values.push(JsValue::bool(*b)), + Constant::Null => values.push(JsValue::null()), + Constant::Undefined => values.push(JsValue::undefined()), + } } - } - - /// 把模块的不可变常量池转换为 `JsValue`。不可能失败——CreateClosure/CreateRegExp - /// 已把函数与正则移出常量池,池中只剩标量 + 永久 intern 字符串值。`&self` - /// 使其可运行于 `OnceLock::get_or_init` 内部。 - pub(crate) fn convert_immutables(&self, constants: &[Constant]) -> Vec { - constants.iter().map(|c| self.convert_constant(c)).collect() + let immutables = Arc::new(values); + let si_table = Arc::new(self.fill_si_table(&immutables, &module.bytecode)); + (immutables, Arc::new(bigint_storage), si_table) } } diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index ea7c69f5..74808837 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -498,6 +498,51 @@ fn immutables_filled_once_per_module() { // 子模块常量改走 temp_immutables(避免缓存下标冲突) } +/// 模块级装载缓存:同模块跨 run(无 full_reset)常量转换只发生一次—— +/// 常量 BigInt 入条目自持存储、不入 session 表,session 表长不随 run 数增长, +/// 缓存条目不重复创建。 +#[test] +fn module_cache_converts_constants_once_across_runs() { + let mut vm = Vm::new(); + let module = compile_module("var x = 10n; x"); + vm.run(&module).expect("run1"); + // 常量 BigInt 走条目自持存储,session 表不留痕。 + assert!( + vm.realm.gc.borrow().session_bigint_ptrs.borrow().is_empty(), + "常量 BigInt 不得登记 session 表" + ); + vm.run(&module).expect("run2"); + // 第二次 run 命中缓存:不重转换、不重登记,表长不增、条目不增。 + assert!( + vm.realm.gc.borrow().session_bigint_ptrs.borrow().is_empty(), + "同模块跨 run 不得重复登记常量 BigInt 入 session 表" + ); + assert_eq!(vm.module_cache.len(), 1, "同模块跨 run 不得新建缓存条目"); +} + +/// 模块级装载缓存跨 full_reset 存活:条目自持 BigInt 存储不在 +/// session_bigint_ptrs 内,session 拆除后指针不悬垂,常量值仍可读。 +#[test] +fn module_cache_bigint_readable_after_full_reset() { + let mut vm = Vm::new(); + let module = compile_module("var x = 10n; x"); + let result = vm.run(&module).expect("run"); + assert!(result.is_bigint()); + vm.full_reset(); + // full_reset 不清装载缓存:条目 BigInt 存储归条目自持,拆除不悬垂。 + let entry = vm + .module_cache + .get(&Arc::as_ptr(&module)) + .expect("full_reset 不得清装载缓存"); + let v = entry + .immutables + .iter() + .find(|v| v.is_bigint()) + .copied() + .expect("常量池含 BigInt 常量"); + assert_eq!(vm.bigint_value(v), &num_bigint::BigInt::from(10)); +} + #[test] fn dynamic_function_basic_arity() { let mut vm = Vm::new();