fix: 函数对象(含 prototype 子对象)直接 session arena 分配——逃逸写不再克隆分裂 identity,类原型/super 派发完整,epoch 边界前 session 原地晋升 epoch 子引用防悬垂

This commit is contained in:
2026-09-10 01:45:51 +08:00
parent b77a9322e5
commit 479238dc24
4 changed files with 224 additions and 10 deletions
+85 -4
View File
@@ -102,6 +102,8 @@ impl Vm {
dst
}
/// 克隆改写与 reset 边界晋升共用的单引用改写:epoch 对象克隆晋升进
/// session(forwarding 去重共享与环);其余值原样保留。
pub(crate) fn promote_value_if_epoch_object(
&mut self, value: JsValue, forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
) -> JsValue {
@@ -109,21 +111,100 @@ impl Vm {
return value;
}
let ptr = value.as_js_object_ptr();
if ptr.is_null() || !unsafe { &*ptr }.is_epoch() {
if ptr.is_null() {
return value;
}
JsValue::from_js_object(self.promote_object_inner(ptr, forwarding))
// SAFETY: 执行核心产出的对象值,指针在 session 生命周期内有效。
if unsafe { &*ptr }.is_epoch() {
return JsValue::from_js_object(self.promote_object_inner(ptr, forwarding));
}
value
}
/// epoch 边界前的 session 原地晋升:遍历全部 session 对象,把仍指向 epoch
/// 对象的引用克隆进 session(JS 边 + native 状态盒),消除 epoch 重置后的
/// 悬垂指针。
///
/// 覆盖两类绕过写屏障的引用来源:函数对象直接 session 分配后捕获的
/// `captured_this`/`home_object`/upvalue cell/属性值(SET_HOME_OBJECT 与
/// 函数目标豁免均直落 epoch 值);Map/Set 等原生盒按键值直插 epoch 值。
/// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/
/// cell 值;克隆子树经 forwarding 去重,环与共享引用各克隆一次。
pub(crate) fn promote_session_epoch_refs(&mut self) {
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
if objects.is_empty() {
return;
}
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
for &ptr in &objects {
if ptr.is_null() {
continue;
}
// SAFETY: ptr 来自 session_epoch.alloc,arena 存活期内有效。
unsafe {
let obj = &mut *ptr;
obj.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, &mut forwarding));
if obj.is_map() {
map::rewrite_map_native(obj, |value| self.promote_value_if_epoch_object(value, &mut forwarding));
} else if obj.is_set() {
set::rewrite_set_native(obj, |value| self.promote_value_if_epoch_object(value, &mut forwarding));
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
disposable_stack::rewrite_dispose_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_typed_array_obj() {
typed_array::rewrite_typed_array_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_data_view_obj() {
data_view::rewrite_data_view_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_generator_obj() {
crate::generator::rewrite_generator_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_promise_obj() {
crate::promise::rewrite_promise_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_async_obj() {
crate::async_func::rewrite_async_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
} else if obj.is_async_generator_obj() {
crate::async_generator::rewrite_async_generator_native(obj, |value| {
self.promote_value_if_epoch_object(value, &mut forwarding)
});
}
}
}
forwarding.clear();
self.gc_state.forwarding = forwarding;
// 晋升过程新克隆的对象已推入 gc_state 侧的表,拼回旧表保持原序在前。
self.gc_state.session_object_ptrs.extend(objects);
}
/// 逃逸写屏障:写向全局/session 对象的对象值不指向 epoch,否则 epoch
/// 重置后悬垂。session 函数目标豁免晋升——克隆会令写入侧与字节码持有的
/// 原始对象分裂(类构造器在原型上续建方法会落到非克隆体);函数持有的
/// epoch 子引用由 `promote_session_epoch_refs` 在 epoch 边界统一修复。
pub(crate) fn promote_if_needed_for_write_ptr(&mut self, target_ptr: *mut JsObject, value: JsValue) -> JsValue {
if !value.is_object() || !self.is_session_escape_root_ptr(target_ptr) {
return value;
}
let value_ptr = value.as_js_object_ptr();
if value_ptr.is_null() || !unsafe { &*value_ptr }.is_epoch() {
if value_ptr.is_null() {
return value;
}
JsValue::from_js_object(self.promote_object(value_ptr))
// SAFETY: 执行核心产出的对象值,指针在 session 生命周期内有效。
if unsafe { &*target_ptr }.is_function() {
return value;
}
if unsafe { &*value_ptr }.is_epoch() {
return JsValue::from_js_object(self.promote_object(value_ptr));
}
value
}
}
+16 -3
View File
@@ -408,6 +408,9 @@ impl Vm {
pub fn reset(&mut self) {
self.clear_execution_state();
self.maybe_collect_session_gc();
// session 对象可持有 epoch 子引用(函数对象捕获、原生盒直插):epoch
// 重置前把 epoch 子引用原地克隆晋升进 session,避免悬垂指针。
self.promote_session_epoch_refs();
self.bytecode = Arc::default();
self.immutables_cache.clear();
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
@@ -542,7 +545,12 @@ impl Vm {
obj.set_arrow(true);
obj.set_captured_this(self.regs[254]);
}
let obj_ptr = self.alloc_object(obj);
// 函数对象直接 session 分配:寿命从调用级延至 session 级(session GC
// mark/sweep 回收)。若按 epoch 分配,写入全局等逃逸根时 promote 屏障会
// 深克隆进 session,全局属性与局部槽指针分裂、严格相等恒 false。
obj.set_session_epoch(true);
let obj_ptr = self.gc_state.session_epoch.alloc(obj) as *mut JsObject;
self.gc_state.session_object_ptrs.push(obj_ptr);
let func_val = JsValue::object(obj_ptr as *mut u8);
if !is_arrow {
@@ -554,8 +562,13 @@ impl Vm {
} else {
JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject)
};
let prototype_obj = self.epoch.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, proto_of_proto));
self.gc_state.track_epoch_object(prototype_obj);
// prototype 子对象与函数本体同走 session 分配:`f.prototype ===
// globalThis.f.prototype` 要求两侧同一对象,epoch 分配会在逃逸写时
// 被递归克隆出第二份。
let mut prototype = JsObject::new_empty(EMPTY_SHAPE_ID, proto_of_proto);
prototype.set_session_epoch(true);
let prototype_obj = self.gc_state.session_epoch.alloc(prototype) as *mut JsObject;
self.gc_state.session_object_ptrs.push(prototype_obj);
let prototype_val = JsValue::from_js_object(prototype_obj);
if !is_generator {