perf: run 装载期常量转换与 si 侧表改 Vm 模块级缓存

This commit is contained in:
2026-10-12 19:47:16 +00:00
parent 91991c3802
commit d026ea626e
10 changed files with 233 additions and 92 deletions
+1 -1
View File
@@ -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);
+11 -10
View File
@@ -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() {
+36 -3
View File
@@ -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<CompiledModule>,
/// 已转换常量池(BigInt 条目指针指向 bigint_storage 内对应元素)。
pub(crate) immutables: Arc<Vec<JsValue>>,
/// BigInt 存储:immutables 中 BigInt 指针的宿主(下标一一对应),
/// 由缓存条目独占,随 Vm 消亡。
#[allow(dead_code)]
pub(crate) bigint_storage: Arc<Vec<num_bigint::BigInt>>,
/// NEW_OBJECT 静态键 si 侧表(纯 u32,无 GC 边)。
pub(crate) si_table: Arc<Vec<Option<u32>>>,
}
/// 基于寄存器的 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<u32, Box<TableGen>>,
/// 模块级装载缓存:键 = 编译模块 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<u64, JsValue>)持本 Vm session 堆的 Worker
// 对象指针,归不变量 1 覆盖:对象由本 Vm 独占分配、地址稳定、随 Vm 整体
// 迁移,跨线程移动不悬垂。
//
// 7. module_cache 字段(HashMap<*const CompiledModule, ModuleLoadEntry>):
// 条目自持模块 Arc 与常量 BigInt 存储(Vec<BigInt>),存储随 Vm 整体迁移
// (同不变量 1 条款:Vm 独占分配、地址稳定、跨线程移动不悬垂);键是
// Arc 指针身份,条目自持 Arc 保证键指针在条目存活期内不被分配器复用。
unsafe impl Send for Vm {}
impl Vm {
+51 -31
View File
@@ -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<Vec<Arc<CompiledModule>>>,
/// 每次 run 转换一次的不可变常量缓存:下标 0 = 顶层模块,sub_idx = modules[sub_idx]。
/// 每个 `OnceLock` 保存该模块常量本次运行中只转换一次的 `JsValue` 结果。
/// 不可变常量是标量 + perm 字符串,只读,GC 根收集按表代际遍历。
pub(crate) immutables: Vec<OnceLock<Vec<JsValue>>>,
/// NEW_OBJECT 静态键 si 侧表,与 `immutables` 平行(下标 = flat_id,内部下标 =
/// 常量池下标):`Some(si)` 是模块装载期预 intern 的键 si,`None` 表示该常量
/// 不是静态键。填充与 `immutables` 激活同点(`activate_immutables`),构造期
/// 直读免逐构造键推导。
pub(crate) si_tables: Vec<OnceLock<Vec<Option<u32>>>>,
/// 不可变常量缓存槽位:下标 0 = 顶层模块,sub_idx = modules[sub_idx]。
/// 每个 `OnceLock` 克隆模块级装载缓存条目(`Vm::module_cache`)中该模块
/// 只转换一次的 `JsValue` 结果 Arc 入槽;槽位只作代际视图,转换本体归
/// 缓存条目所有(跨 `full_reset` 存活)。不可变常量是标量 + perm 字符串 +
/// 条目自持 BigInt,只读,GC 根收集按缓存条目遍历。
pub(crate) immutables: Vec<OnceLock<Arc<Vec<JsValue>>>>,
/// NEW_OBJECT 静态键 si 侧表槽位,与 `immutables` 平行(下标 = flat_id,
/// 内部下标 = 常量池下标):`Some(si)` 是模块装载期预 intern 的键 si,
/// `None` 表示该常量不是静态键。填充与 `immutables` 激活同点
/// (`activate_immutables`),构造期直读免逐构造键推导。
pub(crate) si_tables: Vec<OnceLock<Arc<Vec<Option<u32>>>>>,
}
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<CompiledModule>) {
// 用裸指针访问缓存槽,避免 get_or_init(借用表 immutables)与查/建
// module_cache 的借用发生冲突。成立前提:代际表归注册表所有、只读、
// Box 承载地址稳定。
let table = self.tables.get_mut(&gen).expect("激活的代际表须在注册表中");
let slot: *const OnceLock<Vec<JsValue>> = &table.immutables[cache_idx];
// si 侧表与 immutables 同点填充:模块字节码从平表取(同裸指针手法,
// 平表 Arc 归注册表所有、地址稳定),预 intern 静态键 si。
let si_slot: *const OnceLock<Vec<Option<u32>>> = &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<Arc<Vec<JsValue>>> = &table.immutables[cache_idx];
// si 侧表槽位与 immutables 同点填充(条目共享同一份 si 表 Arc)。
let si_slot: *const OnceLock<Arc<Vec<Option<u32>>>> = &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<Option<u32>> {
pub(crate) fn fill_si_table(&mut self, values: &[JsValue], bytecode: &[opcode::Instr]) -> Vec<Option<u32>> {
let mut si = vec![None; values.len()];
let mut i = 0;
while i < bytecode.len() {