fix: 全局对象成员写与删后镜像槽反向同步防裸读失步

This commit is contained in:
2026-09-14 13:10:17 +00:00
parent 4d880c34c9
commit d877e63cf1
7 changed files with 270 additions and 28 deletions
+4
View File
@@ -419,6 +419,9 @@ impl Vm {
{
self.profiling.record_ic_hit();
ic_trace!("IC_SET hit shape={} slot={} depth={}", cached_shape_id, cached_slot, cached_depth);
// IC 命中快路径防御:今日全局对象带 meta 不可达,防 meta 不变量
// 被未来优化破坏后镜像静默失步(键由 (shape, slot) 反查)。
self.sync_global_builtin_mirror_slot(obj, cached_shape_id, cached_slot, cached_depth, value);
return Ok(());
}
@@ -443,6 +446,7 @@ impl Vm {
self.profiling.record_ic_miss();
prop_cache_miss();
obj.set_prop_shape(pos, value);
self.sync_global_builtin_mirror(obj, prop_name_si, value);
ic_helper::write_ic_back(self.bytecode_mut(), ic_pc, obj.shape_id(), pos, 0);
ic_debug!("IC_SET write-back shape={} slot={}", obj.shape_id(), pos);
} else {
+17 -14
View File
@@ -859,6 +859,19 @@ impl Vm {
.map(|m| m.as_ref())
}
/// 活动帧所属代际平表里 `active_flat_id` 指向的模块条目(镜像槽同步 /
/// 重载的名集载体)。
///
/// # 边界与前提
/// - 活动代际表在执行期恒在注册表中(首 run 前为预登记的空表占位),
/// `active_flat_id` 越界返回 None(调用方按无模块处理)。
pub(crate) fn active_module(&self) -> Option<&CompiledModule> {
self.active_table()
.modules
.get(self.active_flat_id as usize)
.map(|m| m.as_ref())
}
/// 子模块表注册表的当前代际条目总数(run 边界回收行为的测试钉)。
pub fn table_gen_count(&self) -> usize {
self.tables.len()
@@ -1772,20 +1785,7 @@ impl Vm {
self.bytecode = sub_bytecode;
self.activate_immutables(gen, sub_idx, &sub.constants);
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize));
for (name, reg) in &sub.builtin_reg_map.clone() {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let global = self.session.global_object();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
self.regs[*reg as usize] = global.get_prop_at(pos);
} else {
vm_debug!(
"push_frame: builtin '{}' reg={} NOT on global object (stays {})",
name,
reg,
self.regs[*reg as usize]
);
}
}
self.reload_builtin_mirror_slots(&sub.builtin_reg_map);
self.active_reg_limit = sub_n_registers.max(1);
self.pc = 0;
@@ -2633,6 +2633,9 @@ impl oxide_runtime_api::VmHost for Vm {
fn set_or_create_prop_value(&mut self, obj: &mut JsObject, prop_name_si: u32, val: JsValue) {
self.set_or_create_prop_value(obj, prop_name_si, val)
}
fn sync_global_builtin_mirror(&mut self, obj: &JsObject, key_si: u32, val: JsValue) {
self.sync_global_builtin_mirror(obj, key_si, val)
}
fn lookup_str(&self, val: JsValue) -> Option<String> {
self.lookup_str(val)
}
+101
View File
@@ -152,6 +152,95 @@ impl Vm {
None
}
/// 全局 builtin 属性 A 侧写成功后反向同步当前帧镜像槽(成员写 / define /
/// delete 成功后调用)。
///
/// # 边界与前提
/// - 仅当接收者为会话全局对象时生效:非全局写经指针判等短路(热路径成本
/// = 一次指针比较)。
/// - 名集以活动模块 `builtin_reg_map` 为准(与编译期登记同源):键不在表
/// 内即无镜像槽,不写。
/// # 副作用
/// - 写当前帧寄存器文件镜像槽。
/// # 注意事项
/// - 只可挂在真实 A 侧写发生之后;写失败路径(只读 no-op / 抛错)不得
/// 调用,否则污染镜像槽。
pub(crate) fn sync_global_builtin_mirror(&mut self, obj: &JsObject, key_si: u32, val: JsValue) {
if !std::ptr::eq(obj as *const JsObject, self.session.global_object().as_ptr()) {
return;
}
let Some(module) = self.active_module() else {
return;
};
for (name, reg) in &module.builtin_reg_map {
if self.kernel_core.perm_interner().intern(name.as_str()).0 == key_si {
self.regs[*reg as usize] = val;
return;
}
}
}
/// IC 命中快路径的防御变体(命中分支不解析键):按 (shape, slot) 定位被
/// 写属性,自镜像名集反查键后同步。
///
/// # 边界与前提
/// - 仅当写落在全局对象自身时生效:原型链目标命中(depth > 0)不改全局
/// 自身属性,不同步。
/// - 全局对象自构造起恒带 prop_meta,IC 命中分支对今日不可达;本挂点防
/// 该不变量被未来优化破坏后镜像静默失步。
pub(crate) fn sync_global_builtin_mirror_slot(
&mut self, obj: &JsObject, shape_id: u32, slot: u32, depth: u8, val: JsValue,
) {
if !std::ptr::eq(obj as *const JsObject, self.session.global_object().as_ptr()) {
return;
}
if depth != 0 || shape_id != obj.shape_id() {
return;
}
let Some(module) = self.active_module() else {
return;
};
for (name, reg) in &module.builtin_reg_map {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
if self.kernel_core.shape_forge().lookup_position(shape_id, si) == Some(slot) {
self.regs[*reg as usize] = val;
return;
}
}
}
/// 重载活动帧模块(`active_flat_id`)的 builtin 镜像槽:先按值取出名集再
/// 写寄存器,避免借用交叉。帧恢复 / 重执行边界调用。
pub(crate) fn reload_active_module_mirror_slots(&mut self) {
let map = self.active_module().map(|m| m.builtin_reg_map.clone()).unwrap_or_default();
self.reload_builtin_mirror_slots(&map);
}
/// 从全局对象 A 侧重载模块 builtin 名集的镜像槽:属性在位取原始存储值,
/// 缺位写 undefined。
///
/// # 边界与前提
/// - 缺位臂语义为"写 undefined",各入口(run / 帧 / inline / 恢复)统一,
/// 使成员形删除后的裸读与入口预载看到同一值。
/// # 副作用
/// - 只写镜像槽寄存器(槽下标受编译期登记约束)。
pub(crate) fn reload_builtin_mirror_slots(&mut self, map: &[(String, u32)]) {
if map.is_empty() {
return;
}
let global = self.session.global_object();
for (name, reg) in map {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let val = self
.kernel_core
.shape_forge()
.lookup_position(global.shape_id(), si)
.map(|pos| global.get_prop_at(pos))
.unwrap_or_else(JsValue::undefined);
self.regs[*reg as usize] = val;
}
}
/// `strict` 为写方(执行赋值的那个函数/脚本)的严格模式:写失败时严格抛
/// TypeError,sloppy 静默 no-op(ECMA-262 OrdinarySetOwnProperty 失败分支)。
pub(crate) fn ordinary_set(
@@ -285,6 +374,7 @@ impl Vm {
}
// pos 是存储索引(get_own_property_slot 对数组已加元素区偏移)。
obj.set_prop_storage(pos as usize, val);
self.sync_global_builtin_mirror(obj, prop_name_si, val);
return Ok(());
}
@@ -469,11 +559,15 @@ impl Vm {
}
if crate::ic_helper::ic_set_hit_own(obj, &self.bytecode, ext_pc, val) {
self.profiling.record_ic_hit();
// IC 写命中快路径防御:今日全局对象带 meta 不可达,防不变量被破坏
// 后静默失步。
self.sync_global_builtin_mirror(obj, prop_name_si, val);
return Ok(());
}
self.profiling.record_ic_miss();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_shape(pos, val);
self.sync_global_builtin_mirror(obj, prop_name_si, val);
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, obj.shape_id(), pos, 0);
} else if self.named_prop_create_needs_ordinary_set(obj, prop_name_si) {
// 数组 length / 整数索引键写新属性:分流回 ordinary_set(ArraySetLength /
@@ -533,6 +627,7 @@ impl Vm {
obj.set_shape_id(new_shape_id);
obj.push_prop(val);
obj.bump_generation();
self.sync_global_builtin_mirror(obj, prop_name_si, val);
crate::ic_helper::write_ic_back(self.bytecode_mut(), ext_pc, new_shape_id, slot, 0);
self.kernel_core.prop_forge().upsert(
new_shape_id,
@@ -580,6 +675,7 @@ impl Vm {
}
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(obj.shape_id(), prop_name_si) {
obj.set_prop_shape(pos, val);
self.sync_global_builtin_mirror(obj, prop_name_si, val);
} else {
// 不可扩展对象禁止新增命名属性(兜底路径,常规入口已拦截)。
if !obj.is_extensible() {
@@ -590,6 +686,7 @@ impl Vm {
// 数组对象:属性追加到 hash_props 属性区(元素之后),array_prop_count 不变。
obj.push_prop(val);
obj.bump_generation();
self.sync_global_builtin_mirror(obj, prop_name_si, val);
}
}
@@ -662,6 +759,7 @@ impl Vm {
obj.set_prop_storage(pos, val);
obj.set_data_meta(pos, attributes);
obj.bump_generation();
self.sync_global_builtin_mirror(obj, prop_name_si, val);
Ok(())
}
@@ -713,6 +811,9 @@ impl Vm {
obj.set_prop_storage(pos, JsValue::undefined());
obj.set_accessor_meta(pos, get, set, attributes);
obj.bump_generation();
// accessor 化同步原始存储值(undefined),与入口预载读裸槽同不变式;
// getter 穿透属镜像模型结构残面,不在本挂点扩面。
self.sync_global_builtin_mirror(obj, prop_name_si, JsValue::undefined());
Ok(())
}
+10 -14
View File
@@ -331,6 +331,9 @@ impl Vm {
let vm = self;
let _window_regs: Vec<JsValue> = Vec::new();
inline_core_fields!(vm, saved, inline_restore, _window_regs);
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
// (active_flat_id 已随宏回写还原到调用方)。
vm.reload_active_module_mirror_slots();
}
/// 首次执行的 VM 就绪:清空执行核心(regs/pc/bytecode/各栈段/迭代器/内联态),
@@ -459,13 +462,7 @@ impl Vm {
receiver
};
self.regs[255] = JsValue::undefined();
for (name, reg) in &sub.builtin_reg_map {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let global = self.session.global_object();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
self.regs[*reg as usize] = global.get_prop_at(pos);
}
}
self.reload_builtin_mirror_slots(&sub.builtin_reg_map);
let _ = callee;
vm_trace!(
@@ -522,6 +519,9 @@ impl Vm {
// active_reg_limit 还原为调用方真实值(caller_reg_limit 可能被存活上界截断)。
self.active_reg_limit = frame.caller_active_reg_limit;
self.pc = frame.return_addr;
// 窗口回拷已复活调用方陈旧镜像槽:重载调用方模块的 builtin 名集
// (active_flat_id 已随保存栈弹回还原到调用方)。
self.reload_active_module_mirror_slots();
}
/// 重新执行当前已加载的 bytecode:清空执行状态并重置 IC 缓存后再次 dispatch。
@@ -535,6 +535,8 @@ impl Vm {
self.clear_execution_state();
self.active_reg_limit = self.root_reg_limit;
self.regs[254] = self.top_level_this;
// 寄存器文件清空后镜像槽须重载(与 run 入口同语义),否则裸读回陈旧值。
self.reload_active_module_mirror_slots();
crate::ic_helper::clear_ic_caches(self.bytecode_mut());
self.dispatch()
}
@@ -606,13 +608,7 @@ impl Vm {
self.root_reg_limit = module.n_registers.max(1);
self.active_reg_limit = self.root_reg_limit;
for (name, reg) in &module.builtin_reg_map {
let si = self.kernel_core.perm_interner().intern(name.as_str()).0;
let global = self.session.global_object();
if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) {
self.regs[*reg as usize] = global.get_prop_at(pos);
}
}
self.reload_builtin_mirror_slots(&module.builtin_reg_map);
// 顶层 this:脚本为全局对象(ECMA-262 全局执行上下文);
// ES module 顶层环境 GetThisBinding 返回 undefined。记录到