refactor: 退役数组缓冲与正则与 promise 盒克隆重写族死函数

This commit is contained in:
2026-10-02 21:46:41 +00:00
parent b76f2214f9
commit ccd6b06b6e
4 changed files with 7 additions and 239 deletions
+3 -72
View File
@@ -12,8 +12,8 @@ pub(crate) const MAX_ARRAY_BUFFER_LENGTH: usize = 1 << 30;
/// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach; /// ArrayBuffer 载荷:字节缓冲与状态位。`data` 为 `None` 即缓冲区已 detach;
/// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存 /// `max_byte_length` 为存储态上限:0 即定长缓冲(resizable 判据),非 0 存
/// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。 /// 请求上限 + 1(上限 0 与定长必须可分);`immutable` 为字节缓冲写守卫标志。
/// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 三自由函数 /// 载荷经 `Box::into_raw` 存于对象 `native_fn` 槽,GC 两自由函数
/// (clone/size/drop)对整结构体操作,克隆须连标志位一并拷贝。 /// (size/drop)对整结构体操作。
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct ArrayBufferPayload { pub(crate) struct ArrayBufferPayload {
pub(crate) data: Option<Vec<u8>>, pub(crate) data: Option<Vec<u8>>,
@@ -115,20 +115,6 @@ pub(crate) fn array_buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBuffe
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut ArrayBufferPayload) obj.native_fn().map(|ptr| ptr.as_ptr() as *mut ArrayBufferPayload)
} }
/// 克隆 ArrayBuffer 载荷(字节缓冲 + 状态位)到新对象(跨 epoch 克隆流程用)。
pub fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) {
let Some(ptr) = array_buffer_payload_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
// SAFETY: ptr 非空且指向存活载荷盒(克隆/晋升臂由 GC 持有源对象)。
let cloned = unsafe { &*ptr }.clone();
let cloned_ptr = Box::into_raw(Box::new(cloned));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
}
/// 只读核算 ArrayBuffer 载荷字节(不释放):结构体尺寸 + 附着缓冲容量, /// 只读核算 ArrayBuffer 载荷字节(不释放):结构体尺寸 + 附着缓冲容量,
/// detached 载荷仅计结构体尺寸。 /// detached 载荷仅计结构体尺寸。
pub fn array_buffer_native_size(obj: &JsObject) -> u64 { pub fn array_buffer_native_size(obj: &JsObject) -> u64 {
@@ -259,20 +245,6 @@ pub fn drop_shared_array_buffer_native(obj: &mut JsObject) -> u64 {
bytes bytes
} }
/// 克隆 SharedArrayBuffer 载荷(字节缓冲)到新对象(跨 epoch 克隆流程用)。
pub fn clone_shared_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) {
let Some(ptr) = shared_array_buffer_payload_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
// SAFETY: ptr 非空且指向存活载荷盒(克隆/晋升臂由 GC 持有源对象)。
let cloned = unsafe { &*ptr }.clone();
let cloned_ptr = Box::into_raw(Box::new(cloned));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
}
/// `SharedArrayBuffer(length [, options])`:构造语义仅 `new`;length 缺省 0, /// `SharedArrayBuffer(length [, options])`:构造语义仅 `new`;length 缺省 0,
/// 经 ToIndex 传播式(强转副作用原值上抛);`maxByteLength` 选项经传播式 /// 经 ToIndex 传播式(强转副作用原值上抛);`maxByteLength` 选项经传播式
/// Get + ToIndex,`resizable` 键恒忽略(SAB 面不读);分配期上界校验在原型 /// Get + ToIndex,`resizable` 键恒忽略(SAB 面不读);分配期上界校验在原型
@@ -1187,7 +1159,7 @@ mod tests {
use super::*; use super::*;
use oxide_vm::vm::Vm; use oxide_vm::vm::Vm;
/// 手工构造带载荷盒的 ArrayBuffer 对象(不经 Vm,只验 GC 三自由函数)。 /// 手工构造带载荷盒的 ArrayBuffer 对象(不经 Vm,只验 GC 两自由函数)。
fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject { fn ab_object_with_payload(payload: ArrayBufferPayload) -> JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER; obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER;
@@ -1197,47 +1169,6 @@ mod tests {
obj obj
} }
/// 克隆目标对象与真实 GC 克隆体同形:AB 类型标签 + 空 native_fn 槽。
fn ab_clone_target() -> JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
obj.type_tag = JsObject::OBJ_TYPE_ARRAY_BUFFER;
obj
}
#[test]
fn payload_clone_propagates_detached() {
let old = ab_object_with_payload(ArrayBufferPayload {
data: None,
max_byte_length: 0,
immutable: false,
});
let mut new_obj = ab_clone_target();
clone_array_buffer_native(&old, &mut new_obj);
let ptr = array_buffer_payload_ptr(&new_obj).expect("克隆体应携带载荷盒");
// SAFETY: ptr 由 clone_array_buffer_native 新建的存活盒。
let cloned = unsafe { &*ptr };
assert!(cloned.data.is_none());
unsafe { drop(Box::from_raw(ptr)) };
}
#[test]
fn payload_clone_copies_flags() {
let old = ab_object_with_payload(ArrayBufferPayload {
data: Some(vec![1, 2, 3]),
max_byte_length: 42,
immutable: true,
});
let mut new_obj = ab_clone_target();
clone_array_buffer_native(&old, &mut new_obj);
let ptr = array_buffer_payload_ptr(&new_obj).expect("克隆体应携带载荷盒");
// SAFETY: ptr 由 clone_array_buffer_native 新建的存活盒。
let cloned = unsafe { &*ptr };
assert_eq!(cloned.max_byte_length, 42);
assert!(cloned.immutable);
assert_eq!(cloned.data.as_ref().expect("附着缓冲应随克隆").as_slice(), &[1, 2, 3]);
unsafe { drop(Box::from_raw(ptr)) };
}
#[test] #[test]
fn detached_size_and_drop_idempotent() { fn detached_size_and_drop_idempotent() {
let mut obj = ab_object_with_payload(ArrayBufferPayload { let mut obj = ab_object_with_payload(ArrayBufferPayload {
-21
View File
@@ -560,27 +560,6 @@ pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
bytes bytes
} }
/// 深拷贝已编译正则到新对象(GC 搬移 / 晋升流程用)。
///
/// 与 drop 配对:新对象获得独立的 `Box<regress::Regex>`,源对象保留自己的指针,
/// 两侧各自释放恰好一次,杜绝跨 arena 克隆后的指针别名双释放。
/// 作用于全部持编译正则的对象形态(RegExp / matchAll 载体)。
pub fn clone_regexp_native(old_obj: &JsObject, new_obj: &mut JsObject) {
if !old_obj.holds_compiled_regex() {
return;
}
let Some(ptr) = old_obj.native_fn() else {
return;
};
let regex_ptr = ptr.as_ptr() as *const regress::Regex;
if regex_ptr.is_null() {
return;
}
// SAFETY: old_obj 的 native_fn 槽存 `Box<regress::Regex>` 指针,对象存活期间有效。
let cloned_ptr = Box::into_raw(Box::new(unsafe { (&*regex_ptr).clone() }));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
}
/// `RegExp.prototype.test(string)`:判断是否匹配。global/sticky 从 lastIndex /// `RegExp.prototype.test(string)`:判断是否匹配。global/sticky 从 lastIndex
/// 开始匹配,sticky 要求匹配恰在 lastIndex 起点。 /// 开始匹配,sticky 要求匹配恰在 lastIndex 起点。
/// ///
+3 -132
View File
@@ -1,13 +1,11 @@
//! Promise 状态盒的 session GC 支撑:值边、改写、深拷贝迁移、 //! Promise 状态盒的 session GC 支撑:值边、字节核算与释放三类接线。
//! 结算链克隆链维护与字节核算 / 释放。
//! //!
//! 结算链指针是裸指针边、不在 JsValue 改写面:晋升场景由 //! 结算链指针是裸指针边、不是 JsValue。
//! `migrate_settlement_to_newest_clone` 接链,搬移场景按转发表重定位。
use oxide_types::object::JsObject; use oxide_types::object::JsObject;
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
use super::{promise_state_mut, promise_state_ref, PromiseReaction, PromiseState, PromiseStateKind}; use super::{promise_state_ref, PromiseReaction, PromiseState};
/// Promise 状态盒内全部值边(GC mark 边):对象/字符串/BigInt 均产出, /// Promise 状态盒内全部值边(GC mark 边):对象/字符串/BigInt 均产出,
/// 消费侧按值类型分发到对象栈与存活集。 /// 消费侧按值类型分发到对象栈与存活集。
@@ -33,133 +31,6 @@ pub(crate) fn promise_native_edges(obj: &JsObject) -> Vec<JsValue> {
edges edges
} }
/// 用转发函数重写状态盒中的所有 JsValue(promote 用)。
///
/// 结算链指针是裸指针边、不在本函数改写面:晋升克隆新分配于 session arena
/// (原件地址不变),改写场景指针天然有效;结算链接链由
/// `migrate_settlement_to_newest_clone` 负责。
#[allow(dead_code)]
pub(crate) fn rewrite_promise_native(obj: &JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue) {
let Some(state) = promise_state_mut(obj) else {
return;
};
state.result = rewrite(state.result);
state.resolve_fn = rewrite(state.resolve_fn);
state.reject_fn = rewrite(state.reject_fn);
for r in &mut state.reactions {
r.promise = rewrite(r.promise);
r.resolve = rewrite(r.resolve);
r.reject = rewrite(r.reject);
r.handler = rewrite(r.handler);
}
}
/// 深拷贝状态盒到新对象(promote 用):新对象持独立 Box,源盒可安全释放。
///
/// 源的反应**迁移**(非复制)进新对象:源盒随 epoch 释放,留在源上的反应会
/// 永久丢失、原件侧消费者永不触发;每条反应的 JsValue 随状态盒其余字段同一
/// pass 晋升/改写。结算链指针按原值保留(接链由
/// `migrate_settlement_to_newest_clone` 负责)。
#[allow(dead_code)]
pub(crate) fn clone_promise_native_with_rewrite(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue,
) {
// 先取全部 Copy 字段,避免与随后的可变 take 别名校验冲突。
let (state_kind, prev_clone, result, resolve_fn, reject_fn, already_resolved) = {
let Some(state) = promise_state_ref(old) else {
return;
};
(
state.state,
state.promoted_clone,
state.result,
state.resolve_fn,
state.reject_fn,
state.already_resolved,
)
};
// 源反应迁入新对象:源盒随 epoch 释放,反应留源即永久丢失。
let mut reactions: Vec<PromiseReaction> = Vec::new();
if let Some(src) = promise_state_mut(old) {
for r in std::mem::take(&mut src.reactions) {
reactions.push(rewrite_reaction(r, &mut rewrite));
}
}
let cloned = PromiseState {
state: state_kind,
result: rewrite(result),
reactions,
resolve_fn: rewrite(resolve_fn),
reject_fn: rewrite(reject_fn),
already_resolved,
// 结算链指针按原值保留(不改写源指针):晋升后继步
// `migrate_settlement_to_newest_clone` 负责接链与更新源指针,
// 搬移后继步按转发表重定位。
promoted_clone: prev_clone,
};
new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
}
/// 晋升路径专属:新克隆取代前一克隆成为结算枢纽——前一克隆的残留反应取回归并
/// 进新克隆,旧克隆的结算改链到新克隆,原件的传导指针指向新克隆。任一结算
/// 入口(原件 / 旧克隆 / 新克隆)都沿链落到携全部反应的最新克隆,反应恰触发
/// 一次。
///
/// # 边界与前提
/// - 仅 epoch 原件晋升后调用(`promote_object_inner` 的 Promise 分支,紧随
/// `clone_promise_native_with_rewrite`);session 搬移(GC sweep)不改结算
/// 拓扑,不得调用。
/// - 源非 pending 时无事可做直接返回(已结算原件不再传导)。
#[allow(dead_code)]
pub(crate) fn migrate_settlement_to_newest_clone(
old: &JsObject, new: &mut JsObject, mut rewrite: impl FnMut(JsValue) -> JsValue,
) {
// 链上的前克隆 = 源当前指向的克隆(克隆步只保留不改写源指针)。
let (pending, prev) = {
let Some(src) = promise_state_ref(old) else {
return;
};
(src.state == PromiseStateKind::Pending, src.promoted_clone)
};
if !pending {
return;
}
// 前克隆残留并入本克隆(旧克隆腾空,防双触发),并把它的结算改链到新克隆。
if !prev.is_null() {
// SAFETY: prev 是早前晋升产生的 session 克隆,session arena 存活期内有效。
if let Some(prev_state) = promise_state_mut(unsafe { &*prev }) {
if let Some(dst) = promise_state_mut(new) {
for r in std::mem::take(&mut prev_state.reactions) {
dst.reactions.push(rewrite_reaction(r, &mut rewrite));
}
}
prev_state.promoted_clone = new as *mut JsObject;
}
}
// 新克隆即最新:自身无后继,原件传导指针指向它。
if let Some(dst) = promise_state_mut(new) {
dst.promoted_clone = std::ptr::null_mut();
}
if let Some(src) = promise_state_mut(old) {
src.promoted_clone = new as *mut JsObject;
}
}
/// 改写单条反应的四条 JsValue 引用(派生 promise 能力与处理器),源反应迁移
/// 与再晋升合并共用。
#[allow(dead_code)]
fn rewrite_reaction<F: FnMut(JsValue) -> JsValue>(r: PromiseReaction, rewrite: &mut F) -> PromiseReaction {
PromiseReaction {
promise: rewrite(r.promise),
resolve: rewrite(r.resolve),
reject: rewrite(r.reject),
handler: rewrite(r.handler),
is_fulfill: r.is_fulfill,
}
}
/// 只读核算 Promise 状态盒字节(不释放),供 GC 账目核算。 /// 只读核算 Promise 状态盒字节(不释放),供 GC 账目核算。
pub(crate) fn promise_native_size(obj: &JsObject) -> u64 { pub(crate) fn promise_native_size(obj: &JsObject) -> u64 {
if !obj.is_promise_obj() { if !obj.is_promise_obj() {
+1 -14
View File
@@ -4,8 +4,7 @@
//! 是携带目标 promise 的 native 闭包函数(函数对象 prop 存 promise 引用)。 //! 是携带目标 promise 的 native 闭包函数(函数对象 prop 存 promise 引用)。
//! 反应(reaction)与 thenable 委托以 `Microtask` 入队 `Vm::job_queue`, //! 反应(reaction)与 thenable 委托以 `Microtask` 入队 `Vm::job_queue`,
//! 由 `run()` 末尾的 drain 循环 FIFO 执行。所有盒内 JsValue 由 session GC //! 由 `run()` 末尾的 drain 循环 FIFO 执行。所有盒内 JsValue 由 session GC
//! 经 Promise 对象边追踪(见 `promise_native_edges` / `rewrite_promise_native` / //! 经 Promise 对象边追踪(见 `promise_native_edges` / `drop_promise_native`)。
//! `clone_promise_native_with_rewrite` / `drop_promise_native`)。
use oxide_types::object::JsObject; use oxide_types::object::JsObject;
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
@@ -156,15 +155,3 @@ fn promise_state_ref(obj: &JsObject) -> Option<&PromiseState> {
Some(unsafe { &*ptr }) Some(unsafe { &*ptr })
} }
} }
#[expect(clippy::mut_from_ref)]
#[allow(dead_code)]
fn promise_state_mut(obj: &JsObject) -> Option<&mut PromiseState> {
let ptr = obj.native_data() as *mut PromiseState;
if ptr.is_null() {
None
} else {
// SAFETY: 同上,改写发生在 GC 移动/清扫期间,对象仍存活。
Some(unsafe { &mut *ptr })
}
}