mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 12:11:12 +08:00
feat: WeakMap 条目表与 GC 六站点弱边接线
This commit is contained in:
@@ -68,3 +68,5 @@ pub mod ta_codec;
|
|||||||
pub mod temporal;
|
pub mod temporal;
|
||||||
/// TypedArray 内置对象实现(元素访问、fill/slice/subarray/set 等)。
|
/// TypedArray 内置对象实现(元素访问、fill/slice/subarray/set 等)。
|
||||||
pub mod typed_array;
|
pub mod typed_array;
|
||||||
|
/// WeakMap 条目表实现(弱键 → 强值盒与 GC 六站点接线函数族)。
|
||||||
|
pub mod weak_map;
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
//! WeakMap 条目表:弱键 → 强值。
|
||||||
|
//!
|
||||||
|
//! 键为弱引用(GC mark 不产键边,死键由收集路径按转发表判定后丢条目),
|
||||||
|
//! 值为强引用(值边进 mark 边扫描与晋升改写)。存储盒经 `Box::into_raw` 挂
|
||||||
|
//! `JsObject.native_data`,GC 六站点(mark 边 / 移动式 sweep / 晋升克隆 /
|
||||||
|
//! 原地晋升 / drop / 字节账目)经本模块五函数族接线,口径与 Map/Set 同形。
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use oxide_types::object::JsObject;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
|
||||||
|
/// WeakMap 弱键:对象臂按指针恒等(SameValue),symbol 臂按 si 值恒等
|
||||||
|
/// (interner 保证同符号同 si)。两臂互斥,不可弱持的值不建键。
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum WeakKey {
|
||||||
|
/// 对象键:裸指针仅作哈希键与成员查,生命周期由 GC 转发表判定。
|
||||||
|
Obj(*const JsObject),
|
||||||
|
/// symbol 键:interned si 值。
|
||||||
|
Symbol(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for WeakKey {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
match self {
|
||||||
|
Self::Obj(ptr) => ((*ptr) as usize).hash(state),
|
||||||
|
Self::Symbol(si) => si.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// WeakMap 内部槽 [[WeakMapData]]:`HashMap` 免迭代序需求(247 域无迭代器
|
||||||
|
/// 协议),无墓碑槽复杂度。
|
||||||
|
pub(crate) struct WeakMapInner {
|
||||||
|
entries: HashMap<WeakKey, JsValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WeakMapInner {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self { entries: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按活条目数构造:原地重写整表重建时不缩容量口径。
|
||||||
|
pub(crate) fn with_capacity(capacity: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
entries: HashMap::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn len(&self) -> usize {
|
||||||
|
self.entries.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get(&self, key: &WeakKey) -> Option<JsValue> {
|
||||||
|
self.entries.get(key).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn insert(&mut self, key: WeakKey, value: JsValue) {
|
||||||
|
self.entries.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn iter(&self) -> impl Iterator<Item = (WeakKey, JsValue)> + '_ {
|
||||||
|
self.entries.iter().map(|(k, v)| (*k, *v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 由可弱持值建弱键;不可弱持(非对象非 symbol、HTML DDA)返回 `None`。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 对象臂按指针恒等;symbol 臂按 si 值恒等。
|
||||||
|
/// - DDA 对象按规范 `CanBeHeldWeakly` 排除。
|
||||||
|
pub fn weak_key_of(key: JsValue) -> Option<WeakKey> {
|
||||||
|
if key.is_object() {
|
||||||
|
let ptr = key.as_js_object_ptr();
|
||||||
|
if ptr.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: 对象值由执行核心产出,指针在 session 生命周期内有效。
|
||||||
|
if unsafe { (&*ptr).is_html_dda_obj() } {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(WeakKey::Obj(ptr as *const JsObject))
|
||||||
|
} else if key.is_symbol() {
|
||||||
|
Some(WeakKey::Symbol(key.as_symbol_index()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读出 WeakMap 对象的条目表指针;非 WeakMap 或盒缺失返回空指针。
|
||||||
|
/// 品牌三分枝(非对象 / 无内部槽 / 盒缺失)的守卫入口在方法族,此处只读。
|
||||||
|
pub(crate) fn weak_map_inner_of(obj: &JsObject) -> *const WeakMapInner {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return std::ptr::null();
|
||||||
|
}
|
||||||
|
obj.native_data() as *const WeakMapInner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 收集 WeakMap 值边(GC mark 强边):键为弱边不产边。
|
||||||
|
/// 消费侧按值类型分发到对象栈与串/BigInt 存活集。
|
||||||
|
pub fn weak_map_native_edges(obj: &JsObject) -> Vec<JsValue> {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let inner = weak_map_inner_of(obj);
|
||||||
|
if inner.is_null() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
// SAFETY: mark 期对象存活,条目表指针指向有效盒。
|
||||||
|
unsafe { (*inner).entries.values().copied().collect() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 克隆 WeakMap 条目表到新对象:键原样搬运(生死判定交晋升后的弱键定夺
|
||||||
|
/// 路径),值经 `rewrite` 改写(强边)。源盒由释放路径单独释放,互不共享。
|
||||||
|
pub fn clone_weak_map_native_with_rewrite<F>(src: &JsObject, dst: &mut JsObject, mut rewrite: F)
|
||||||
|
where
|
||||||
|
F: FnMut(JsValue) -> JsValue,
|
||||||
|
{
|
||||||
|
if !src.is_weak_map_obj() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// SAFETY: 源盒指针由构造路径写入,克隆完成 native_data 重指前读取有效。
|
||||||
|
let inner = weak_map_inner_of(src);
|
||||||
|
if inner.is_null() {
|
||||||
|
dst.set_native_data(std::ptr::null_mut());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut cloned = WeakMapInner::new();
|
||||||
|
unsafe {
|
||||||
|
for (key, value) in (*inner).iter() {
|
||||||
|
let new_value = if value.is_object() { rewrite(value) } else { value };
|
||||||
|
cloned.entries.insert(key, new_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dst.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 原地重写 WeakMap 条目表的键与值:键经 `key_resolve` 判定(返回 `None`
|
||||||
|
/// 即死键,条目丢弃;`Some` 改指新键),值经 `value_rewrite` 改写(强边)。
|
||||||
|
/// 按原表整表重建,免条目级原位换键的容量抖动。
|
||||||
|
pub fn rewrite_weak_map_native<K, V>(obj: &mut JsObject, mut key_resolve: K, mut value_rewrite: V)
|
||||||
|
where
|
||||||
|
K: FnMut(WeakKey) -> Option<WeakKey>,
|
||||||
|
V: FnMut(JsValue) -> JsValue,
|
||||||
|
{
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// native_data 为构造路径写入的有效 Box 指针,原地重写期间独占。
|
||||||
|
let inner = obj.native_data() as *mut WeakMapInner;
|
||||||
|
if inner.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let mut rewritten = WeakMapInner::with_capacity((*inner).len());
|
||||||
|
for (key, value) in (*inner).iter() {
|
||||||
|
let Some(new_key) = key_resolve(key) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let new_value = if value.is_object() { value_rewrite(value) } else { value };
|
||||||
|
rewritten.entries.insert(new_key, new_value);
|
||||||
|
}
|
||||||
|
*inner = rewritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 原地重写 WeakMap 条目表的值边(强边):键不动,值经 `rewrite` 改写。
|
||||||
|
/// 供晋升主改写路径使用——键的生死须待转发表收敛后由弱键定夺路径判定,
|
||||||
|
/// 本路径提前改写键会引入晋升序依赖。
|
||||||
|
pub fn rewrite_weak_map_native_values<F>(obj: &mut JsObject, mut rewrite: F)
|
||||||
|
where
|
||||||
|
F: FnMut(JsValue) -> JsValue,
|
||||||
|
{
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// native_data 同 rewrite_weak_map_native:构造路径写入的有效 Box 指针。
|
||||||
|
let inner = obj.native_data() as *mut WeakMapInner;
|
||||||
|
if inner.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let mut rewritten = WeakMapInner::with_capacity((*inner).len());
|
||||||
|
for (key, value) in (*inner).iter() {
|
||||||
|
let new_value = if value.is_object() { rewrite(value) } else { value };
|
||||||
|
rewritten.entries.insert(key, new_value);
|
||||||
|
}
|
||||||
|
*inner = rewritten;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 只读核算 WeakMap 条目表字节(不释放),供 GC 账目核算;与
|
||||||
|
/// `drop_weak_map_native` 同口径(盒本体 + 桶容量按条目尺寸计)。
|
||||||
|
pub fn weak_map_native_size(obj: &JsObject) -> u64 {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let inner = weak_map_inner_of(obj);
|
||||||
|
if inner.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let inner = &*inner;
|
||||||
|
(std::mem::size_of::<WeakMapInner>() + inner.entries.capacity() * std::mem::size_of::<(WeakKey, JsValue)>())
|
||||||
|
as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 释放 WeakMap 条目表,返回释放字节数供泄漏统计;释放后置空保证对象侧
|
||||||
|
/// 幂等(收尾统一释放按表枚举不会再见)。
|
||||||
|
pub fn drop_weak_map_native(obj: &mut JsObject) -> u64 {
|
||||||
|
let bytes = weak_map_native_size(obj);
|
||||||
|
if bytes == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let inner = obj.native_data() as *mut WeakMapInner;
|
||||||
|
// SAFETY: inner 非空(weak_map_native_size 已验证),Box::from_raw 恰好释放一次。
|
||||||
|
unsafe {
|
||||||
|
drop(Box::from_raw(inner));
|
||||||
|
}
|
||||||
|
obj.set_native_data(std::ptr::null_mut());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读条目数(测试探针):非 WeakMap 或盒缺失返回 0。
|
||||||
|
pub fn weak_map_entry_count(obj: &JsObject) -> usize {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let inner = weak_map_inner_of(obj);
|
||||||
|
if inner.is_null() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
unsafe { (*inner).len() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 读给定弱键对应的值(测试探针):非 WeakMap / 盒缺失 / 键缺失返回 undefined。
|
||||||
|
pub fn weak_map_get(obj: &JsObject, key: JsValue) -> JsValue {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return JsValue::undefined();
|
||||||
|
}
|
||||||
|
let Some(key) = weak_key_of(key) else {
|
||||||
|
return JsValue::undefined();
|
||||||
|
};
|
||||||
|
let inner = weak_map_inner_of(obj);
|
||||||
|
if inner.is_null() {
|
||||||
|
return JsValue::undefined();
|
||||||
|
}
|
||||||
|
unsafe { (*inner).get(&key).unwrap_or(JsValue::undefined()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 建空条目盒(测试探针):供单测在构造体落地前先行制造带盒的 WeakMap 形对象。
|
||||||
|
pub fn weak_map_alloc_box() -> *mut u8 {
|
||||||
|
Box::into_raw(Box::new(WeakMapInner::new())) as *mut u8
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 向 WeakMap 形对象插一条(测试探针):键不可弱持则静默忽略。
|
||||||
|
pub fn weak_map_insert(obj: &mut JsObject, key: JsValue, value: JsValue) {
|
||||||
|
if !obj.is_weak_map_obj() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Some(key) = weak_key_of(key) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let inner = obj.native_data() as *mut WeakMapInner;
|
||||||
|
if inner.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// SAFETY: 单测在盒写入前对象独占,指针指向有效盒。
|
||||||
|
unsafe { (*inner).insert(key, value) }
|
||||||
|
}
|
||||||
@@ -213,6 +213,9 @@ impl JsObject {
|
|||||||
/// 语义面;接收者品牌守卫按本 tag 拒绝,ArrayBuffer 方法族对其抛
|
/// 语义面;接收者品牌守卫按本 tag 拒绝,ArrayBuffer 方法族对其抛
|
||||||
/// TypeError。释放与深拷贝守卫同 ArrayBuffer。
|
/// TypeError。释放与深拷贝守卫同 ArrayBuffer。
|
||||||
pub const OBJ_TYPE_SHARED_ARRAY_BUFFER: u8 = 30;
|
pub const OBJ_TYPE_SHARED_ARRAY_BUFFER: u8 = 30;
|
||||||
|
/// WeakMap 对象:条目表(弱键 → 强值)存于 `native_data`。键为弱边
|
||||||
|
/// (GC mark 不产边),值为强边。
|
||||||
|
pub const OBJ_TYPE_WEAK_MAP: u8 = 31;
|
||||||
/// `is_session_epoch` 字段中的 session 标记位。
|
/// `is_session_epoch` 字段中的 session 标记位。
|
||||||
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
||||||
/// `is_session_epoch` 字段中的 GC 标记位。
|
/// `is_session_epoch` 字段中的 GC 标记位。
|
||||||
@@ -278,6 +281,11 @@ impl JsObject {
|
|||||||
pub fn is_shared_array_buffer_obj(&self) -> bool {
|
pub fn is_shared_array_buffer_obj(&self) -> bool {
|
||||||
self.type_tag == Self::OBJ_TYPE_SHARED_ARRAY_BUFFER
|
self.type_tag == Self::OBJ_TYPE_SHARED_ARRAY_BUFFER
|
||||||
}
|
}
|
||||||
|
/// 是否 WeakMap 对象(条目表存于 `native_data`)。
|
||||||
|
#[inline]
|
||||||
|
pub fn is_weak_map_obj(&self) -> bool {
|
||||||
|
self.type_tag == Self::OBJ_TYPE_WEAK_MAP
|
||||||
|
}
|
||||||
/// 是否 DataView 对象。
|
/// 是否 DataView 对象。
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_data_view_obj(&self) -> bool {
|
pub fn is_data_view_obj(&self) -> bool {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use oxide_types::value::JsValue;
|
|||||||
use rustc_hash::FxBuildHasher;
|
use rustc_hash::FxBuildHasher;
|
||||||
|
|
||||||
use crate::vm::Vm;
|
use crate::vm::Vm;
|
||||||
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array};
|
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map};
|
||||||
|
|
||||||
impl Vm {
|
impl Vm {
|
||||||
/// 单对象晋升测试入口:取/清共享转发表后调 `promote_object_inner`。
|
/// 单对象晋升测试入口:取/清共享转发表后调 `promote_object_inner`。
|
||||||
@@ -51,6 +51,12 @@ impl Vm {
|
|||||||
set::clone_set_native_with_rewrite(src_ref, dst_ref, |value| {
|
set::clone_set_native_with_rewrite(src_ref, dst_ref, |value| {
|
||||||
self.promote_value_if_epoch_object(value, forwarding)
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
});
|
});
|
||||||
|
} else if src_ref.is_weak_map_obj() {
|
||||||
|
// 值边随克隆晋升(强边);弱键原样搬运,生死交晋升收敛后的
|
||||||
|
// 弱键定夺路径按同一转发表判定。
|
||||||
|
weak_map::clone_weak_map_native_with_rewrite(src_ref, dst_ref, |value| {
|
||||||
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
|
});
|
||||||
} else if src_ref.is_disposable_stack_obj() || src_ref.is_async_disposable_stack_obj() {
|
} else if src_ref.is_disposable_stack_obj() || src_ref.is_async_disposable_stack_obj() {
|
||||||
// 资源栈状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
// 资源栈状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
|
||||||
disposable_stack::clone_dispose_native_with_rewrite(src_ref, dst_ref, |value| {
|
disposable_stack::clone_dispose_native_with_rewrite(src_ref, dst_ref, |value| {
|
||||||
@@ -150,6 +156,8 @@ impl Vm {
|
|||||||
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
|
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
|
||||||
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
|
||||||
self.rewrite_session_epoch_refs(&objects, &mut forwarding);
|
self.rewrite_session_epoch_refs(&objects, &mut forwarding);
|
||||||
|
// 弱键按收敛后的转发表定生死:未入表 epoch 键随 epoch 释放而亡。
|
||||||
|
self.rewrite_weak_map_keys_after_promotion(&objects, &mut forwarding);
|
||||||
forwarding.clear();
|
forwarding.clear();
|
||||||
self.gc_state.forwarding = forwarding;
|
self.gc_state.forwarding = forwarding;
|
||||||
// 改写期新克隆已随晋升推入 gc_state 侧的表(此刻仅含克隆体),把取出的
|
// 改写期新克隆已随晋升推入 gc_state 侧的表(此刻仅含克隆体),把取出的
|
||||||
@@ -184,6 +192,12 @@ impl Vm {
|
|||||||
map::rewrite_map_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding));
|
map::rewrite_map_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding));
|
||||||
} else if obj.is_set() {
|
} else if obj.is_set() {
|
||||||
set::rewrite_set_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding));
|
set::rewrite_set_native(obj, |value| self.promote_value_if_epoch_object(value, forwarding));
|
||||||
|
} else if obj.is_weak_map_obj() {
|
||||||
|
// 值边同强边改写;弱键留给转发表收敛后的定夺路径,
|
||||||
|
// 提前改写会引入晋升序依赖(键的强可达根尚未遍历完)。
|
||||||
|
weak_map::rewrite_weak_map_native_values(obj, |value| {
|
||||||
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
|
});
|
||||||
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
||||||
disposable_stack::rewrite_dispose_native(obj, |value| {
|
disposable_stack::rewrite_dispose_native(obj, |value| {
|
||||||
self.promote_value_if_epoch_object(value, forwarding)
|
self.promote_value_if_epoch_object(value, forwarding)
|
||||||
@@ -229,6 +243,51 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 晋升收敛后的弱键定夺:对候选集(session 对象列表 + 已晋升克隆)中的
|
||||||
|
/// 每个弱表按转发表判定弱键——表内键改指新址,未入表 epoch 键为死键丢
|
||||||
|
/// 条目,session/P 键保留。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 每个弱表原位重建条目表;无死键时条目与容量口径不变。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 须在主改写路径(JS 边 + 值边)对转发表收敛完成后调用;转发表届时
|
||||||
|
/// 即最终强可达集。调用方负责其后的清表。
|
||||||
|
/// - global 上的弱表经其晋升克隆进入转发表值集,候选集无需单列 global。
|
||||||
|
pub(crate) fn rewrite_weak_map_keys_after_promotion(
|
||||||
|
&mut self, objects: &[*mut JsObject], forwarding: &mut HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||||
|
) {
|
||||||
|
for &ptr in objects {
|
||||||
|
if ptr.is_null() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// SAFETY: session 对象在 arena 存活期内有效。
|
||||||
|
unsafe {
|
||||||
|
let obj = &mut *ptr;
|
||||||
|
if obj.is_weak_map_obj() {
|
||||||
|
weak_map::rewrite_weak_map_native(
|
||||||
|
obj,
|
||||||
|
|key| crate::session_gc::resolve_weak_key_after_promotion(key, forwarding),
|
||||||
|
|value| value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for &new_ptr in forwarding.values() {
|
||||||
|
// SAFETY: 晋升克隆分配于 session arena,存活期内有效。
|
||||||
|
unsafe {
|
||||||
|
let obj = &mut *new_ptr;
|
||||||
|
if obj.is_weak_map_obj() {
|
||||||
|
weak_map::rewrite_weak_map_native(
|
||||||
|
obj,
|
||||||
|
|key| crate::session_gc::resolve_weak_key_after_promotion(key, forwarding),
|
||||||
|
|value| value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
|
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
|
||||||
/// 并把根引用改写到克隆体。
|
/// 并把根引用改写到克隆体。
|
||||||
///
|
///
|
||||||
@@ -262,6 +321,8 @@ impl Vm {
|
|||||||
self.promote_object_inner(ptr, &mut forwarding);
|
self.promote_object_inner(ptr, &mut forwarding);
|
||||||
}
|
}
|
||||||
crate::session_gc::rewrite_vm_roots(self, &forwarding);
|
crate::session_gc::rewrite_vm_roots(self, &forwarding);
|
||||||
|
// 弱表键定夺:根直接持有的弱表克隆其弱键按转发表判生死。
|
||||||
|
self.rewrite_weak_map_keys_after_promotion(&[], &mut forwarding);
|
||||||
forwarding.clear();
|
forwarding.clear();
|
||||||
self.gc_state.forwarding = forwarding;
|
self.gc_state.forwarding = forwarding;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use oxide_types::value::JsValue;
|
|||||||
use rustc_hash::FxBuildHasher;
|
use rustc_hash::FxBuildHasher;
|
||||||
|
|
||||||
use crate::vm::Vm;
|
use crate::vm::Vm;
|
||||||
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array};
|
use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map};
|
||||||
|
|
||||||
/// session 级 mark-sweep GC 的状态与统计。
|
/// session 级 mark-sweep GC 的状态与统计。
|
||||||
///
|
///
|
||||||
@@ -124,6 +124,7 @@ impl SessionGc {
|
|||||||
bytes += crate::promise::promise_native_size(obj);
|
bytes += crate::promise::promise_native_size(obj);
|
||||||
bytes += crate::async_func::async_native_size(obj);
|
bytes += crate::async_func::async_native_size(obj);
|
||||||
bytes += crate::async_generator::async_generator_native_size(obj);
|
bytes += crate::async_generator::async_generator_native_size(obj);
|
||||||
|
bytes += weak_map::weak_map_native_size(obj);
|
||||||
|
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
@@ -174,6 +175,12 @@ impl SessionGc {
|
|||||||
Self::process_edge(value, vm, stack, live_strings, live_bigints);
|
Self::process_edge(value, vm, stack, live_strings, live_bigints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if obj.is_weak_map_obj() {
|
||||||
|
// 仅值边进 mark(强边);键为弱边,不入栈不置位。
|
||||||
|
for value in weak_map::weak_map_native_edges(obj) {
|
||||||
|
Self::process_edge(value, vm, stack, live_strings, live_bigints);
|
||||||
|
}
|
||||||
|
}
|
||||||
if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
||||||
for value in disposable_stack::dispose_edges(obj) {
|
for value in disposable_stack::dispose_edges(obj) {
|
||||||
Self::process_edge(value, vm, stack, live_strings, live_bigints);
|
Self::process_edge(value, vm, stack, live_strings, live_bigints);
|
||||||
@@ -546,6 +553,7 @@ impl SessionGc {
|
|||||||
freed_bytes += crate::promise::drop_promise_native(obj);
|
freed_bytes += crate::promise::drop_promise_native(obj);
|
||||||
freed_bytes += crate::async_func::drop_async_native(obj);
|
freed_bytes += crate::async_func::drop_async_native(obj);
|
||||||
freed_bytes += crate::async_generator::drop_async_generator_native(obj);
|
freed_bytes += crate::async_generator::drop_async_generator_native(obj);
|
||||||
|
freed_bytes += weak_map::drop_weak_map_native(obj);
|
||||||
|
|
||||||
freed_bytes
|
freed_bytes
|
||||||
}
|
}
|
||||||
@@ -623,6 +631,10 @@ impl SessionGc {
|
|||||||
map::clone_map_native_with_rewrite(old_ref, new_ref, |value| value);
|
map::clone_map_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
} else if old_ref.is_set() {
|
} else if old_ref.is_set() {
|
||||||
set::clone_set_native_with_rewrite(old_ref, new_ref, |value| value);
|
set::clone_set_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
|
} else if old_ref.is_weak_map_obj() {
|
||||||
|
// 弱键原样搬运:键生死按转发表收敛后的判定(phase 2 与
|
||||||
|
// 晋升定夺路径同判据),此处只深拷贝盒与改写值边。
|
||||||
|
weak_map::clone_weak_map_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
} else if old_ref.is_disposable_stack_obj() || old_ref.is_async_disposable_stack_obj() {
|
} else if old_ref.is_disposable_stack_obj() || old_ref.is_async_disposable_stack_obj() {
|
||||||
disposable_stack::clone_dispose_native_with_rewrite(old_ref, new_ref, |value| value);
|
disposable_stack::clone_dispose_native_with_rewrite(old_ref, new_ref, |value| value);
|
||||||
} else if old_ref.is_array_buffer_obj() {
|
} else if old_ref.is_array_buffer_obj() {
|
||||||
@@ -672,6 +684,13 @@ impl SessionGc {
|
|||||||
map::rewrite_map_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
map::rewrite_map_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
||||||
} else if obj.is_set() {
|
} else if obj.is_set() {
|
||||||
set::rewrite_set_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
set::rewrite_set_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
||||||
|
} else if obj.is_weak_map_obj() {
|
||||||
|
// 键按转发表 + mark 位定生死(死键条目丢弃),值走强边转发改写。
|
||||||
|
weak_map::rewrite_weak_map_native(
|
||||||
|
obj,
|
||||||
|
|key| resolve_weak_key_sweep(key, &forwarding),
|
||||||
|
|value| rewrite_forwarded_value(value, &forwarding),
|
||||||
|
);
|
||||||
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
} else if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() {
|
||||||
disposable_stack::rewrite_dispose_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
disposable_stack::rewrite_dispose_native(obj, |value| rewrite_forwarded_value(value, &forwarding));
|
||||||
} else if obj.is_typed_array_obj() {
|
} else if obj.is_typed_array_obj() {
|
||||||
@@ -1047,6 +1066,8 @@ impl SessionGc {
|
|||||||
.filter(|&ptr| !ptr.is_null() && unsafe { (*ptr).is_gc_marked() })
|
.filter(|&ptr| !ptr.is_null() && unsafe { (*ptr).is_gc_marked() })
|
||||||
.collect();
|
.collect();
|
||||||
vm.rewrite_session_epoch_refs(&marked_session, &mut forwarding);
|
vm.rewrite_session_epoch_refs(&marked_session, &mut forwarding);
|
||||||
|
// 弱表键定夺:主改写只走强边,epoch 键按收敛后的转发表定生死。
|
||||||
|
vm.rewrite_weak_map_keys_after_promotion(&marked_session, &mut forwarding);
|
||||||
forwarding.clear();
|
forwarding.clear();
|
||||||
vm.gc_state.forwarding = forwarding;
|
vm.gc_state.forwarding = forwarding;
|
||||||
|
|
||||||
@@ -1230,6 +1251,59 @@ fn rewrite_forwarded_value(
|
|||||||
.unwrap_or(value)
|
.unwrap_or(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 移动式 sweep 改写期的弱键判定:转发表内键改指新址;未入表的 session/
|
||||||
|
/// epoch 键按 mark 位定生死(已标 = 强可达、随后晋升或原地存活,未标 =
|
||||||
|
/// 死键丢弃);P 键不可死,恒保留。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 未入表键读归属位与 mark 位须解引用旧指针:调用点(sweep 改写相)
|
||||||
|
/// 旧 arena 尚未归还、清位发生在改写相之后,对象头位域可读。
|
||||||
|
/// - 死键指针只作哈希键与位域读取,不 deref 其已释放的堆数据。
|
||||||
|
pub(crate) fn resolve_weak_key_sweep(
|
||||||
|
key: weak_map::WeakKey, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||||
|
) -> Option<weak_map::WeakKey> {
|
||||||
|
let weak_map::WeakKey::Obj(ptr) = key else {
|
||||||
|
return Some(key);
|
||||||
|
};
|
||||||
|
let mut_ptr = ptr as *mut JsObject;
|
||||||
|
if let Some(&new) = forwarding.get(&mut_ptr) {
|
||||||
|
return Some(weak_map::WeakKey::Obj(new as *const JsObject));
|
||||||
|
}
|
||||||
|
// SAFETY: 见函数边界与前提——sweep 改写相旧 arena 存活,对象头位域可读。
|
||||||
|
let old = unsafe { &*mut_ptr };
|
||||||
|
if (old.is_session_epoch() || old.is_epoch()) && !old.is_gc_marked() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 晋升收敛后的弱键定夺:转发表内键改指新址;未入表 epoch 键 = 本轮晋升
|
||||||
|
/// 未覆盖 = 死键丢弃;session 键不搬移、原样保留(惰性判定交下一轮 sweep);
|
||||||
|
/// P 键不可死,恒保留。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 晋升定夺路径(原地晋升 / in-run 晋升档)的转发表已收敛为最终强可达集,
|
||||||
|
/// 判据不含 mark 位(reset 边界路径此前已清位)。
|
||||||
|
/// - 解引用仅限未入表键的归属位读取,调用点旧 arena 存活(epoch 释放 /
|
||||||
|
/// 清表发生在定夺之后)。
|
||||||
|
pub(crate) fn resolve_weak_key_after_promotion(
|
||||||
|
key: weak_map::WeakKey, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
|
||||||
|
) -> Option<weak_map::WeakKey> {
|
||||||
|
let weak_map::WeakKey::Obj(ptr) = key else {
|
||||||
|
return Some(key);
|
||||||
|
};
|
||||||
|
let mut_ptr = ptr as *mut JsObject;
|
||||||
|
if let Some(&new) = forwarding.get(&mut_ptr) {
|
||||||
|
return Some(weak_map::WeakKey::Obj(new as *const JsObject));
|
||||||
|
}
|
||||||
|
// SAFETY: 见函数边界与前提——定夺点旧 arena 存活,对象头位域可读。
|
||||||
|
let old = unsafe { &*mut_ptr };
|
||||||
|
if old.is_epoch() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(key)
|
||||||
|
}
|
||||||
|
|
||||||
/// 按 forwarding 表把全部 VM 根引用重写到搬移后的新地址。与 `for_each_value`
|
/// 按 forwarding 表把全部 VM 根引用重写到搬移后的新地址。与 `for_each_value`
|
||||||
/// 共用同一字段清单(经 `rewrite_values` 遍历)且须同步:遗漏字段会在搬移后
|
/// 共用同一字段清单(经 `rewrite_values` 遍历)且须同步:遗漏字段会在搬移后
|
||||||
/// 保留指向旧 arena 的悬垂指针。
|
/// 保留指向旧 arena 的悬垂指针。
|
||||||
|
|||||||
@@ -510,6 +510,119 @@ fn session_gc_traces_set_object_key() {
|
|||||||
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 造带条目盒的 WeakMap 形 epoch 对象(构造体落地前的单测代用形态)。
|
||||||
|
fn weak_map_object(vm: &mut Vm) -> *mut JsObject {
|
||||||
|
let mut obj = JsObject::new_empty(oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::undefined());
|
||||||
|
obj.type_tag = JsObject::OBJ_TYPE_WEAK_MAP;
|
||||||
|
obj.set_native_data(oxide_builtins::weak_map::weak_map_alloc_box());
|
||||||
|
let ptr = vm.epoch.alloc(obj);
|
||||||
|
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
|
||||||
|
unsafe { (*ptr).set_is_epoch(true) };
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 弱键不产 mark 边:强可达键跨晋升 + 完整收集保留,条目完整、值边改写后
|
||||||
|
/// 按原键读回同一克隆体。
|
||||||
|
#[test]
|
||||||
|
fn weak_map_entry_survives_promotion_with_live_key() {
|
||||||
|
let mut vm = vm_with_low_threshold();
|
||||||
|
let key = plain_object(&mut vm);
|
||||||
|
let value = plain_object(&mut vm);
|
||||||
|
let wm = weak_map_object(&mut vm);
|
||||||
|
unsafe {
|
||||||
|
oxide_builtins::weak_map::weak_map_insert(
|
||||||
|
&mut *wm,
|
||||||
|
JsValue::from_js_object(key),
|
||||||
|
JsValue::from_js_object(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm);
|
||||||
|
vm.regs[1] = JsValue::from_js_object(key);
|
||||||
|
vm.regs[2] = JsValue::from_js_object(value);
|
||||||
|
let wm_session = vm.promote_object(wm);
|
||||||
|
vm.regs.fill(JsValue::undefined());
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm_session);
|
||||||
|
vm.regs[1] = JsValue::from_js_object(key);
|
||||||
|
// 值的强根取晋升克隆(表内读回):原件随 epoch 出局。
|
||||||
|
let value_root = oxide_builtins::weak_map::weak_map_get(unsafe { &*wm_session }, JsValue::from_js_object(key));
|
||||||
|
vm.regs[2] = value_root;
|
||||||
|
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||||
|
gc.collect(&mut vm);
|
||||||
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
|
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 1, "强可达键的条目须在收集后存活");
|
||||||
|
let stored = oxide_builtins::weak_map::weak_map_get(live_wm, JsValue::from_js_object(key));
|
||||||
|
assert_eq!(stored, vm.regs[2], "值须按原键读回同一克隆体");
|
||||||
|
assert!(!std::ptr::eq(stored.as_js_object_ptr(), value), "值边须改写到晋升克隆");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 弱键强不可达 = 死键:收集按转发表判定丢条目,表不留死键残影。
|
||||||
|
#[test]
|
||||||
|
fn weak_map_entry_dropped_when_key_unrooted() {
|
||||||
|
let mut vm = vm_with_low_threshold();
|
||||||
|
let value = plain_object(&mut vm);
|
||||||
|
let wm = weak_map_object(&mut vm);
|
||||||
|
// 键仅由表内弱边引用:无强根。
|
||||||
|
let key = plain_object(&mut vm);
|
||||||
|
unsafe {
|
||||||
|
oxide_builtins::weak_map::weak_map_insert(
|
||||||
|
&mut *wm,
|
||||||
|
JsValue::from_js_object(key),
|
||||||
|
JsValue::from_js_object(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm);
|
||||||
|
vm.regs[2] = JsValue::from_js_object(value);
|
||||||
|
let wm_session = vm.promote_object(wm);
|
||||||
|
vm.regs.fill(JsValue::undefined());
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm_session);
|
||||||
|
vm.regs[2] = JsValue::from_js_object(value);
|
||||||
|
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||||
|
gc.collect(&mut vm);
|
||||||
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
|
assert_eq!(oxide_builtins::weak_map::weak_map_entry_count(live_wm), 0, "死键条目须在收集时丢弃");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 值边为强边:值对象唯一引用仅来自值边时仍入存活集,搬移后条目按转发表
|
||||||
|
/// 改写指到新址。
|
||||||
|
#[test]
|
||||||
|
fn weak_map_value_edge_keeps_value_alive() {
|
||||||
|
let mut vm = vm_with_low_threshold();
|
||||||
|
let key = plain_object(&mut vm);
|
||||||
|
let value = plain_object(&mut vm);
|
||||||
|
let wm = weak_map_object(&mut vm);
|
||||||
|
unsafe {
|
||||||
|
oxide_builtins::weak_map::weak_map_insert(
|
||||||
|
&mut *wm,
|
||||||
|
JsValue::from_js_object(key),
|
||||||
|
JsValue::from_js_object(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 值先晋升进 session:其独立根随后撤销,存活仅靠弱表值边。
|
||||||
|
vm.regs[2] = JsValue::from_js_object(value);
|
||||||
|
let value_session = vm.promote_object(value);
|
||||||
|
vm.regs[2] = JsValue::from_js_object(value_session);
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm);
|
||||||
|
vm.regs[1] = JsValue::from_js_object(key);
|
||||||
|
let wm_session = vm.promote_object(wm);
|
||||||
|
vm.regs.fill(JsValue::undefined());
|
||||||
|
vm.regs[0] = JsValue::from_js_object(wm_session);
|
||||||
|
vm.regs[1] = JsValue::from_js_object(key);
|
||||||
|
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
|
||||||
|
gc.collect(&mut vm);
|
||||||
|
vm.gc_state.session_gc = gc;
|
||||||
|
|
||||||
|
let live_wm = unsafe { &*vm.regs[0].as_js_object_ptr() };
|
||||||
|
let stored = oxide_builtins::weak_map::weak_map_get(live_wm, JsValue::from_js_object(key));
|
||||||
|
assert!(stored.is_object(), "唯一经值边引用的值须存活");
|
||||||
|
assert!(!std::ptr::eq(stored.as_js_object_ptr(), value_session), "值边须在搬移后改写到新址");
|
||||||
|
assert!(vm.is_session_ptr(stored.as_js_object_ptr()));
|
||||||
|
assert_eq!(vm.gc_state.session_object_ptrs.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
/// 盒持唯一引用的 session 串与 BigInt 经 Map native 边进入存活集:
|
/// 盒持唯一引用的 session 串与 BigInt 经 Map native 边进入存活集:
|
||||||
/// 清寄存器仅留 Map 根后完整收集,两值仍登记在 session 表。
|
/// 清寄存器仅留 Map 根后完整收集,两值仍登记在 session 表。
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user