mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
perf: run 装载期常量转换与 si 侧表改 Vm 模块级缓存
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user