perf: GC 字节账目补齐 + is_epoch_ptr O(1) + 分配热路径触发

This commit is contained in:
2026-08-22 08:10:32 +08:00
parent 95155ce450
commit 469ef7543b
17 changed files with 369 additions and 78 deletions
+19 -5
View File
@@ -87,19 +87,33 @@ pub fn clone_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) {
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) })); new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
} }
/// 释放 ArrayBuffer 的字节缓冲(native_fn 槽中的 `Box<Vec<u8>>`),返回字节数。 /// 只读核算 ArrayBuffer 字节缓冲字节(不释放)。
pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { pub fn array_buffer_native_size(obj: &JsObject) -> u64 {
let Some(data_ptr) = array_buffer_vec_ptr(obj) else { let Some(data_ptr) = array_buffer_vec_ptr(obj) else {
return 0; return 0;
}; };
if data_ptr.is_null() { if data_ptr.is_null() {
return 0; return 0;
} }
// SAFETY: ArrayBuffer 在 native_fn 槽中存 `Box<Vec<u8>>`。 unsafe {
(std::mem::size_of::<Vec<u8>>() + (*data_ptr).capacity()) as u64
}
}
/// 释放 ArrayBuffer 的字节缓冲(native_fn 槽中的 `Box<Vec<u8>>`),返回字节数。
pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 {
let bytes = array_buffer_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(data_ptr) = array_buffer_vec_ptr(obj) else {
return 0;
};
// SAFETY: data_ptr 非空(array_buffer_native_size 已验证),Box::from_raw 恰好释放一次。
let data = unsafe { Box::from_raw(data_ptr) }; let data = unsafe { Box::from_raw(data_ptr) };
let bytes = std::mem::size_of::<Vec<u8>>() + data.capacity(); drop(data);
obj.set_native_fn(None); obj.set_native_fn(None);
bytes as u64 bytes
} }
pub(crate) fn array_buffer_data_ptr<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<*mut Vec<u8>, JsValue> { pub(crate) fn array_buffer_data_ptr<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<*mut Vec<u8>, JsValue> {
+16 -3
View File
@@ -235,17 +235,30 @@ where
} }
} }
/// 释放 DataView 视图数据(`Box<DataViewData>`),返回释放字节数。 /// 只读核算 DataView 视图数据字节(不释放)。
pub fn drop_data_view_native(obj: &mut JsObject) -> u64 { pub fn data_view_native_size(obj: &JsObject) -> u64 {
let Some(ptr) = data_view_data_ptr(obj) else { let Some(ptr) = data_view_data_ptr(obj) else {
return 0; return 0;
}; };
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
std::mem::size_of::<DataViewData>() as u64
}
/// 释放 DataView 视图数据(`Box<DataViewData>`),返回释放字节数。
pub fn drop_data_view_native(obj: &mut JsObject) -> u64 {
let bytes = data_view_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = data_view_data_ptr(obj) else {
return 0;
};
// SAFETY: ptr 非空(data_view_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { drop(Box::from_raw(ptr)) }; unsafe { drop(Box::from_raw(ptr)) };
obj.set_native_fn(None); obj.set_native_fn(None);
std::mem::size_of::<DataViewData>() as u64 bytes
} }
/// `DataView.prototype.getInt8(byteOffset)`:读取 1 字节有符号整数。 /// `DataView.prototype.getInt8(byteOffset)`:读取 1 字节有符号整数。
+17 -6
View File
@@ -608,8 +608,8 @@ where
} }
} }
/// 释放状态盒(对象被回收时),返回释放的字节数供泄漏统计。 /// 只读核算 DisposableStack 状态盒字节(不释放)。
pub fn drop_dispose_native(obj: &mut JsObject) -> u64 { pub fn disposable_stack_native_size(obj: &JsObject) -> u64 {
if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() {
return 0; return 0;
} }
@@ -617,13 +617,24 @@ pub fn drop_dispose_native(obj: &mut JsObject) -> u64 {
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
// SAFETY: 指针来自 Box::into_raw,只在对象被回收时释放一次。 unsafe {
(std::mem::size_of::<DisposeCapability>()
+ (*ptr).entries.capacity() * std::mem::size_of::<DisposeEntry>()) as u64
}
}
/// 释放状态盒(对象被回收时),返回释放的字节数供泄漏统计。
pub fn drop_dispose_native(obj: &mut JsObject) -> u64 {
let bytes = disposable_stack_native_size(obj);
if bytes == 0 {
return 0;
}
let ptr = get_capability_ptr(obj);
// SAFETY: ptr 非空(disposable_stack_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { unsafe {
let cap = Box::from_raw(ptr); let cap = Box::from_raw(ptr);
let bytes =
std::mem::size_of::<DisposeCapability>() + cap.entries.capacity() * std::mem::size_of::<DisposeEntry>();
drop(cap); drop(cap);
obj.set_native_data(std::ptr::null_mut()); obj.set_native_data(std::ptr::null_mut());
bytes as u64
} }
bytes
} }
+19 -7
View File
@@ -132,8 +132,9 @@ where
} }
} }
/// 释放 Map 的 native 数据(IndexMap),返回释放的字节数供泄漏统计。 /// 只读核算 Map 的 native 数据字节(不释放)。
pub fn drop_map_native(obj: &mut JsObject) -> u64 { /// 与 `drop_map_native` 释放口径一致(capacity),供 GC 账目核算。
pub fn map_native_size(obj: &JsObject) -> u64 {
if !obj.is_map() { if !obj.is_map() {
return 0; return 0;
} }
@@ -142,14 +143,25 @@ pub fn drop_map_native(obj: &mut JsObject) -> u64 {
return 0; return 0;
} }
unsafe { unsafe {
let boxed: Box<MapInner> = Box::from_raw(inner); (std::mem::size_of::<MapInner>() + (*inner).capacity() * std::mem::size_of::<(SetKey, JsValue)>()) as u64
let bytes = std::mem::size_of::<MapInner>() + boxed.capacity() * std::mem::size_of::<(SetKey, JsValue)>();
drop(boxed);
obj.set_native_data(std::ptr::null_mut());
bytes as u64
} }
} }
/// 释放 Map 的 native 数据(IndexMap),返回释放的字节数供泄漏统计。
pub fn drop_map_native(obj: &mut JsObject) -> u64 {
let bytes = map_native_size(obj);
if bytes == 0 {
return 0;
}
let inner = obj.native_data() as *mut MapInner;
// SAFETY: inner 非空(map_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe {
drop(Box::from_raw(inner));
}
obj.set_native_data(std::ptr::null_mut());
bytes
}
/// `Map` 构造函数:创建带空 IndexMap native 数据的 Map 对象,若提供可迭代实参 /// `Map` 构造函数:创建带空 IndexMap native 数据的 Map 对象,若提供可迭代实参
/// 则逐元素(须为对象)取 `[0]`/`[1]` 作为键值调用 set。 /// 则逐元素(须为对象)取 `[0]`/`[1]` 作为键值调用 set。
/// ///
+17 -3
View File
@@ -129,8 +129,8 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(JsValue::from_js_object(obj_ptr)) NativeResult::Ok(JsValue::from_js_object(obj_ptr))
} }
/// 释放 RegExp 对象 native_fn 槽中编译的 `regress::Regex`,返回释放字节数。 /// 只读核算 RegExp 编译正则字节(不释放)。
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 { pub fn regexp_native_size(obj: &JsObject) -> u64 {
if !obj.is_regexp_obj() { if !obj.is_regexp_obj() {
return 0; return 0;
} }
@@ -141,9 +141,23 @@ pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
if regex_ptr.is_null() { if regex_ptr.is_null() {
return 0; return 0;
} }
std::mem::size_of::<regress::Regex>() as u64
}
/// 释放 RegExp 对象 native_fn 槽中编译的 `regress::Regex`,返回释放字节数。
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
let bytes = regexp_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let regex_ptr = ptr.as_ptr() as *mut regress::Regex;
// SAFETY: regex_ptr 非空(regexp_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { drop(Box::from_raw(regex_ptr)) }; unsafe { drop(Box::from_raw(regex_ptr)) };
obj.set_native_fn(None); obj.set_native_fn(None);
std::mem::size_of::<regress::Regex>() as u64 bytes
} }
/// 深拷贝已编译正则到新对象(GC 搬移 / 晋升流程用)。 /// 深拷贝已编译正则到新对象(GC 搬移 / 晋升流程用)。
+19 -7
View File
@@ -173,8 +173,9 @@ where
} }
} }
/// 释放 Set 的 native 数据(IndexSet),返回释放的字节数供泄漏统计。 /// 只读核算 Set 的 native 数据字节(不释放)。
pub fn drop_set_native(obj: &mut JsObject) -> u64 { /// 与 `drop_set_native` 释放口径一致(capacity),供 GC 账目核算。
pub fn set_native_size(obj: &JsObject) -> u64 {
if !obj.is_set() { if !obj.is_set() {
return 0; return 0;
} }
@@ -183,14 +184,25 @@ pub fn drop_set_native(obj: &mut JsObject) -> u64 {
return 0; return 0;
} }
unsafe { unsafe {
let boxed: Box<SetInner> = Box::from_raw(inner); (std::mem::size_of::<SetInner>() + (*inner).capacity() * std::mem::size_of::<SetKey>()) as u64
let bytes = std::mem::size_of::<SetInner>() + boxed.capacity() * std::mem::size_of::<SetKey>();
drop(boxed);
obj.set_native_data(std::ptr::null_mut());
bytes as u64
} }
} }
/// 释放 Set 的 native 数据(IndexSet),返回释放的字节数供泄漏统计。
pub fn drop_set_native(obj: &mut JsObject) -> u64 {
let bytes = set_native_size(obj);
if bytes == 0 {
return 0;
}
let inner = obj.native_data() as *mut SetInner;
// SAFETY: inner 非空(set_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe {
drop(Box::from_raw(inner));
}
obj.set_native_data(std::ptr::null_mut());
bytes
}
/// `Set` 构造函数:创建带空 IndexSet native 数据的 Set 对象,若提供可迭代实参 /// `Set` 构造函数:创建带空 IndexSet native 数据的 Set 对象,若提供可迭代实参
/// 则按插入序逐个 add。 /// 则按插入序逐个 add。
/// ///
+16 -3
View File
@@ -157,17 +157,30 @@ where
} }
} }
/// 释放 TypedArray 的视图数据(`Box<TypedArrayData>`),返回释放字节数。 /// 只读核算 TypedArray 视图数据字节(不释放)。
pub fn drop_typed_array_native(obj: &mut JsObject) -> u64 { pub fn typed_array_native_size(obj: &JsObject) -> u64 {
let Some(ptr) = typed_array_data_ptr(obj) else { let Some(ptr) = typed_array_data_ptr(obj) else {
return 0; return 0;
}; };
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
std::mem::size_of::<TypedArrayData>() as u64
}
/// 释放 TypedArray 的视图数据(`Box<TypedArrayData>`),返回释放字节数。
pub fn drop_typed_array_native(obj: &mut JsObject) -> u64 {
let bytes = typed_array_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(ptr) = typed_array_data_ptr(obj) else {
return 0;
};
// SAFETY: ptr 非空(typed_array_native_size 已验证),Box::from_raw 恰好释放一次。
unsafe { drop(Box::from_raw(ptr)) }; unsafe { drop(Box::from_raw(ptr)) };
obj.set_native_fn(None); obj.set_native_fn(None);
std::mem::size_of::<TypedArrayData>() as u64 bytes
} }
pub(crate) fn get_typed_array_data<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<TypedArrayData, JsValue> { pub(crate) fn get_typed_array_data<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<TypedArrayData, JsValue> {
+18
View File
@@ -665,6 +665,8 @@ impl JsObject {
pub const SESSION_EPOCH_BIT: u8 = 0x01; pub const SESSION_EPOCH_BIT: u8 = 0x01;
/// `is_session_epoch` 字段中的 GC 标记位。 /// `is_session_epoch` 字段中的 GC 标记位。
pub const GC_MARK_BIT: u8 = 0x02; pub const GC_MARK_BIT: u8 = 0x02;
/// `is_session_epoch` 字段中的 epoch 归属标记位。
pub const EPOCH_BIT: u8 = 0x04;
/// 是否 Date 外来对象。 /// 是否 Date 外来对象。
#[inline] #[inline]
@@ -876,6 +878,22 @@ impl JsObject {
} }
} }
/// 是否为 epoch 分配(非 session)的对象。
#[inline]
pub fn is_epoch(&self) -> bool {
self.is_session_epoch & Self::EPOCH_BIT != 0
}
/// 设置 / 清除 epoch 归属标记。
#[inline]
pub fn set_is_epoch(&mut self, value: bool) {
if value {
self.is_session_epoch |= Self::EPOCH_BIT;
} else {
self.is_session_epoch &= !Self::EPOCH_BIT;
}
}
/// 深拷贝本对象到 session epoch:复制属性向量与元数据,标记为新 session 对象。 /// 深拷贝本对象到 session epoch:复制属性向量与元数据,标记为新 session 对象。
/// ///
/// 用于把持久对象快照进当前调用上下文,修改不反向传播到源对象。 /// 用于把持久对象快照进当前调用上下文,修改不反向传播到源对象。
+19 -6
View File
@@ -546,8 +546,8 @@ pub(crate) fn clone_async_native_with_rewrite(
new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
} }
/// 释放异步状态盒(对象被 GC 回收时),返回释放字节数。 /// 只读核算异步状态盒字节(不释放),供 GC 账目核算。
pub(crate) fn drop_async_native(obj: &JsObject) -> u64 { pub(crate) fn async_native_size(obj: &JsObject) -> u64 {
if !obj.is_async_obj() { if !obj.is_async_obj() {
return 0; return 0;
} }
@@ -555,9 +555,22 @@ pub(crate) fn drop_async_native(obj: &JsObject) -> u64 {
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
// SAFETY: 指针来自 create_async_context 的 Box::into_raw,只在 GC 回收时释放一次。 unsafe {
let state = unsafe { Box::from_raw(ptr) };
std::mem::size_of::<AsyncState>() as u64 std::mem::size_of::<AsyncState>() as u64
+ state.suspended.heap_bytes() + (*ptr).suspended.heap_bytes()
+ state.args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64 + (*ptr).args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64
}
}
/// 释放异步状态盒(对象被 GC 回收时),返回释放字节数。
pub(crate) fn drop_async_native(obj: &JsObject) -> u64 {
let bytes = async_native_size(obj);
if bytes == 0 {
return 0;
}
let ptr = obj.native_data() as *mut AsyncState;
// SAFETY: ptr 非空(async_native_size 已验证),Box::from_raw 恰好释放一次。
let state = unsafe { Box::from_raw(ptr) };
drop(state);
bytes
} }
+19 -6
View File
@@ -952,8 +952,8 @@ pub(crate) fn clone_async_generator_native_with_rewrite(
new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
} }
/// 释放异步生成器状态盒(对象被 GC 回收时),返回释放字节数。 /// 只读核算异步生成器状态盒字节(不释放),供 GC 账目核算。
pub(crate) fn drop_async_generator_native(obj: &JsObject) -> u64 { pub(crate) fn async_generator_native_size(obj: &JsObject) -> u64 {
if !obj.is_async_generator_obj() { if !obj.is_async_generator_obj() {
return 0; return 0;
} }
@@ -961,9 +961,22 @@ pub(crate) fn drop_async_generator_native(obj: &JsObject) -> u64 {
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
// SAFETY: 指针来自 create_async_generator_object 的 Box::into_raw,只在 GC 回收时释放一次。 unsafe {
let state = unsafe { Box::from_raw(ptr) };
std::mem::size_of::<AsyncGeneratorState>() as u64 std::mem::size_of::<AsyncGeneratorState>() as u64
+ state.suspended.heap_bytes() + (*ptr).suspended.heap_bytes()
+ state.args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64 + (*ptr).args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64
}
}
/// 释放异步生成器状态盒(对象被 GC 回收时),返回释放字节数。
pub(crate) fn drop_async_generator_native(obj: &JsObject) -> u64 {
let bytes = async_generator_native_size(obj);
if bytes == 0 {
return 0;
}
let ptr = obj.native_data() as *mut AsyncGeneratorState;
// SAFETY: ptr 非空(async_generator_native_size 已验证),Box::from_raw 恰好释放一次。
let state = unsafe { Box::from_raw(ptr) };
drop(state);
bytes
} }
+20 -6
View File
@@ -903,8 +903,8 @@ pub(crate) fn clone_generator_native_with_rewrite(
new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
} }
/// 释放生成器状态盒(对象被 GC 回收时),返回释放字节数。 /// 只读核算生成器状态盒字节(不释放),供 GC 账目核算。
pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 { pub(crate) fn generator_native_size(obj: &JsObject) -> u64 {
if !obj.is_generator_obj() { if !obj.is_generator_obj() {
return 0; return 0;
} }
@@ -912,9 +912,23 @@ pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 {
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
// SAFETY: 指针来自 create_generator_object 的 Box::into_raw,只在 GC 回收时释放一次。 // SAFETY: 通过裸指针读取 capacity 字段,不解引用整个 Box(不移动/释放)。
let state = unsafe { Box::from_raw(ptr) }; unsafe {
std::mem::size_of::<GeneratorState>() as u64 std::mem::size_of::<GeneratorState>() as u64
+ state.suspended.heap_bytes() + (*ptr).suspended.heap_bytes()
+ state.args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64 + (*ptr).args.capacity() as u64 * std::mem::size_of::<JsValue>() as u64
}
}
/// 释放生成器状态盒(对象被 GC 回收时),返回释放字节数。
pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 {
let bytes = generator_native_size(obj);
if bytes == 0 {
return 0;
}
let ptr = obj.native_data() as *mut GeneratorState;
// SAFETY: ptr 非空(generator_native_size 已验证),Box::from_raw 恰好释放一次。
let state = unsafe { Box::from_raw(ptr) };
drop(state);
bytes
} }
+18 -5
View File
@@ -1826,8 +1826,8 @@ pub(crate) fn clone_promise_native_with_rewrite(
new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); new.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8);
} }
/// 释放 Promise 状态盒(对象被回收时),返回释放字节数。 /// 只读核算 Promise 状态盒字节(不释放),供 GC 账目核算。
pub(crate) fn drop_promise_native(obj: &JsObject) -> u64 { pub(crate) fn promise_native_size(obj: &JsObject) -> u64 {
if !obj.is_promise_obj() { if !obj.is_promise_obj() {
return 0; return 0;
} }
@@ -1835,10 +1835,23 @@ pub(crate) fn drop_promise_native(obj: &JsObject) -> u64 {
if ptr.is_null() { if ptr.is_null() {
return 0; return 0;
} }
// SAFETY: 指针来自 Box::into_raw,只在对象被回收时释放一次。 unsafe {
let state = unsafe { Box::from_raw(ptr) };
std::mem::size_of::<PromiseState>() as u64 std::mem::size_of::<PromiseState>() as u64
+ state.reactions.capacity() as u64 * std::mem::size_of::<PromiseReaction>() as u64 + (*ptr).reactions.capacity() as u64 * std::mem::size_of::<PromiseReaction>() as u64
}
}
/// 释放 Promise 状态盒(对象被回收时),返回释放字节数。
pub(crate) fn drop_promise_native(obj: &JsObject) -> u64 {
let bytes = promise_native_size(obj);
if bytes == 0 {
return 0;
}
let ptr = obj.native_data() as *mut PromiseState;
// SAFETY: ptr 非空(promise_native_size 已验证),Box::from_raw 恰好释放一次。
let state = unsafe { Box::from_raw(ptr) };
drop(state);
bytes
} }
/// 供外部遍历微任务队列(GC mark/rewrite 用)。 /// 供外部遍历微任务队列(GC mark/rewrite 用)。
+14 -7
View File
@@ -34,7 +34,7 @@ impl Vm {
return src; return src;
} }
let src_ref = unsafe { &*src }; let src_ref = unsafe { &*src };
if src_ref.is_session_epoch() || !self.epoch.is_epoch_ptr(src.cast::<u8>()) { if src_ref.is_session_epoch() || !src_ref.is_epoch() {
return src; return src;
} }
if let Some(dst) = forwarding.get(&src).copied() { if let Some(dst) = forwarding.get(&src).copied() {
@@ -45,7 +45,6 @@ impl Vm {
let dst = self.gc_state.session_epoch.alloc(clone) as *mut JsObject; let dst = self.gc_state.session_epoch.alloc(clone) as *mut JsObject;
forwarding.insert(src, dst); forwarding.insert(src, dst);
self.gc_state.session_object_ptrs.push(dst); self.gc_state.session_object_ptrs.push(dst);
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsObject>();
let dst_ref = unsafe { &mut *dst }; let dst_ref = unsafe { &mut *dst };
dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding)); dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding));
@@ -97,6 +96,9 @@ impl Vm {
// 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。 // 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。
regexp::clone_regexp_native(src_ref, dst_ref); regexp::clone_regexp_native(src_ref, dst_ref);
} }
// 账目计入:对象头 + 堆数据(属性/元素/meta Vec + native 状态盒),用 clone 后的 dst 核算。
self.gc_state.session_bytes_allocated += std::mem::size_of::<JsObject>()
+ crate::session_gc::SessionGc::object_heap_data_bytes(dst_ref) as usize;
dst dst
} }
@@ -107,7 +109,7 @@ impl Vm {
return value; return value;
} }
let ptr = value.as_js_object_ptr(); let ptr = value.as_js_object_ptr();
if ptr.is_null() || !self.epoch.is_epoch_ptr(ptr.cast::<u8>()) { if ptr.is_null() || !unsafe { &*ptr }.is_epoch() {
return value; return value;
} }
JsValue::from_js_object(self.promote_object_inner(ptr, forwarding)) JsValue::from_js_object(self.promote_object_inner(ptr, forwarding))
@@ -118,7 +120,7 @@ impl Vm {
return value; return value;
} }
let value_ptr = value.as_js_object_ptr(); let value_ptr = value.as_js_object_ptr();
if value_ptr.is_null() || !self.epoch.is_epoch_ptr(value_ptr.cast::<u8>()) { if value_ptr.is_null() || !unsafe { &*value_ptr }.is_epoch() {
return value; return value;
} }
JsValue::from_js_object(self.promote_object(value_ptr)) JsValue::from_js_object(self.promote_object(value_ptr))
@@ -133,12 +135,15 @@ mod tests {
fn plain_object(vm: &mut Vm) -> *mut JsObject { fn plain_object(vm: &mut Vm) -> *mut JsObject {
let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject; let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
vm.epoch let ptr = vm.epoch
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto))) .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto)));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*ptr).set_is_epoch(true) };
ptr
} }
fn is_epoch_object(vm: &Vm, value: JsValue) -> bool { fn is_epoch_object(vm: &Vm, value: JsValue) -> bool {
value.is_object() && vm.epoch.is_epoch_ptr(value.as_js_object_ptr().cast::<u8>()) value.is_object() && unsafe { (&*value.as_js_object_ptr()).is_epoch() }
} }
#[test] #[test]
@@ -221,6 +226,8 @@ mod tests {
2, 2,
vm.epoch.bump(), vm.epoch.bump(),
)); ));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*arr).set_is_epoch(true) };
unsafe { unsafe {
(*arr).set_prop_at(0, JsValue::from_js_object(elem_child)); (*arr).set_prop_at(0, JsValue::from_js_object(elem_child));
(*arr).set_accessor_meta( (*arr).set_accessor_meta(
+103 -4
View File
@@ -53,6 +53,58 @@ impl SessionGc {
} }
} }
/// 只读核算 session 对象的堆数据字节(属性/元素/meta Vec capacity + native 状态盒)。
/// 与 `drop_object_heap_data` 释放口径一致(capacity),不释放、不置空任何指针。
pub(crate) fn object_heap_data_bytes(obj: &JsObject) -> u64 {
let mut bytes = 0u64;
let elems_ptr = obj.array_elements_raw() as *const Vec<JsValue>;
if !elems_ptr.is_null() {
unsafe {
bytes += size_of::<Vec<JsValue>>() as u64
+ ((*elems_ptr).capacity() * size_of::<JsValue>()) as u64;
}
}
let elems_meta_ptr = obj.array_elements_meta_raw() as *const Vec<Option<PropMetaEntry>>;
if !elems_meta_ptr.is_null() {
unsafe {
bytes += size_of::<Vec<Option<PropMetaEntry>>>() as u64
+ ((*elems_meta_ptr).capacity() * size_of::<Option<PropMetaEntry>>()) as u64;
}
}
let hash_ptr = obj.hash_props_raw() as *const Vec<JsValue>;
if !hash_ptr.is_null() {
unsafe {
bytes += size_of::<Vec<JsValue>>() as u64
+ ((*hash_ptr).capacity() * size_of::<JsValue>()) as u64;
}
}
let meta_ptr = obj.prop_meta_raw() as *const Vec<Option<PropMetaEntry>>;
if !meta_ptr.is_null() {
unsafe {
bytes += size_of::<Vec<Option<PropMetaEntry>>>() as u64
+ ((*meta_ptr).capacity() * size_of::<Option<PropMetaEntry>>()) as u64;
}
}
bytes += map::map_native_size(obj);
bytes += set::set_native_size(obj);
bytes += disposable_stack::disposable_stack_native_size(obj);
bytes += array_buffer::array_buffer_native_size(obj);
bytes += regexp::regexp_native_size(obj);
bytes += typed_array::typed_array_native_size(obj);
bytes += data_view::data_view_native_size(obj);
bytes += crate::generator::generator_native_size(obj);
bytes += crate::promise::promise_native_size(obj);
bytes += crate::async_func::async_native_size(obj);
bytes += crate::async_generator::async_generator_native_size(obj);
bytes
}
fn object_edges(obj: &JsObject) -> Vec<JsValue> { fn object_edges(obj: &JsObject) -> Vec<JsValue> {
let mut edges = Vec::new(); let mut edges = Vec::new();
if let Some(elements) = obj.array_elements_vec() { if let Some(elements) = obj.array_elements_vec() {
@@ -502,7 +554,15 @@ impl SessionGc {
forwarding.clear(); forwarding.clear();
vm.gc_state.forwarding = forwarding; vm.gc_state.forwarding = forwarding;
vm.gc_state.session_epoch = new_arena; vm.gc_state.session_epoch = new_arena;
vm.gc_state.session_bytes_allocated = vm.gc_state.session_object_ptrs.len() * size_of::<JsObject>(); vm.gc_state.session_bytes_allocated = vm.gc_state
.session_object_ptrs
.iter()
.filter(|&&ptr| !ptr.is_null())
.map(|&ptr| {
let obj = unsafe { &*ptr };
size_of::<JsObject>() as u64 + Self::object_heap_data_bytes(obj)
})
.sum::<u64>() as usize;
self.clear_all_marks(vm); self.clear_all_marks(vm);
@@ -570,6 +630,16 @@ impl SessionGc {
&& vm.gc_state.session_bytes_allocated >= vm.kernel_core().config().session_gc_threshold && vm.gc_state.session_bytes_allocated >= vm.kernel_core().config().session_gc_threshold
} }
/// 执行期完整 GC 的触发判断:账目须超过水位(上次收集后的存活字节 +
/// 阈值增量)。与 `should_collect`(reset 路径用)不同,本方法走增量水位,
/// 防止存活对象超阈值时每指令重复触发无死对象可回收的白跑。
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
#[allow(dead_code)]
pub(crate) fn should_collect_gc(&self, vm: &Vm) -> bool {
(!vm.gc_state.session_object_ptrs.is_empty() || !vm.gc_state.session_string_ptrs.is_empty())
&& vm.gc_state.session_bytes_allocated >= vm.gc_state.gc_watermark
}
/// 执行期字符串回收的触发判断:账目须超过水位(上次收集后的存活字节 + /// 执行期字符串回收的触发判断:账目须超过水位(上次收集后的存活字节 +
/// 阈值增量)。完整收集(reset)仍用 [`Self::should_collect`] 的阈值直接比较, /// 阈值增量)。完整收集(reset)仍用 [`Self::should_collect`] 的阈值直接比较,
/// 保证死对象超阈值即被 reset 回收;执行期走增量水位,活串超阈值时不每指令 /// 保证死对象超阈值即被 reset 回收;执行期走增量水位,活串超阈值时不每指令
@@ -664,7 +734,15 @@ impl SessionGc {
self.debug_assert_marked_object_strings_live(vm); self.debug_assert_marked_object_strings_live(vm);
// 扣减字符串账目(保留对象账目),再补回存活串字节。 // 扣减字符串账目(保留对象账目),再补回存活串字节。
let object_bytes = vm.gc_state.session_object_ptrs.len() * size_of::<JsObject>(); let object_bytes: usize = vm.gc_state
.session_object_ptrs
.iter()
.filter(|&&ptr| !ptr.is_null())
.map(|&ptr| {
let obj = unsafe { &*ptr };
size_of::<JsObject>() as u64 + Self::object_heap_data_bytes(obj)
})
.sum::<u64>() as usize;
vm.gc_state.session_bytes_allocated = object_bytes; vm.gc_state.session_bytes_allocated = object_bytes;
let freed_bytes = self.sweep_session_strings(vm); let freed_bytes = self.sweep_session_strings(vm);
@@ -704,6 +782,18 @@ impl SessionGc {
} }
} }
/// 执行期完整 GC 入口:按水位判定,触发后抬高水位。
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
#[allow(dead_code)]
pub(crate) fn maybe_collect_gc(&mut self, vm: &mut Vm) {
if self.should_collect_gc(vm) {
self.collect(vm);
// 抬高下次触发水位:存活字节 + 阈值增量,避免活对象超阈值时每指令重复触发。
let threshold = vm.gc_state.gc_threshold_cached;
vm.gc_state.gc_watermark = vm.gc_state.session_bytes_allocated.saturating_add(threshold);
}
}
pub(crate) fn stats_summary(&self) -> String { pub(crate) fn stats_summary(&self) -> String {
format!( format!(
"[GC] collection #{}: {} scanned, {} live, {} dead, {} freed, {}μs", "[GC] collection #{}: {} scanned, {} live, {} dead, {} freed, {}μs",
@@ -771,10 +861,13 @@ mod tests {
fn plain_object(vm: &mut Vm) -> *mut JsObject { fn plain_object(vm: &mut Vm) -> *mut JsObject {
let proto_ptr = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject; let proto_ptr = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject;
vm.epoch.alloc(JsObject::new_empty( let ptr = vm.epoch.alloc(JsObject::new_empty(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID, oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto_ptr), JsValue::from_js_object(proto_ptr),
)) ));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*ptr).set_is_epoch(true) };
ptr
} }
fn has_ptr(roots: &[JsValue], ptr: *mut JsObject) -> bool { fn has_ptr(roots: &[JsValue], ptr: *mut JsObject) -> bool {
@@ -984,6 +1077,8 @@ mod tests {
2, 2,
vm.epoch.bump(), vm.epoch.bump(),
)); ));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*arr).set_is_epoch(true) };
let live_elem = plain_object(&mut vm); let live_elem = plain_object(&mut vm);
let dead_elem = plain_object(&mut vm); let dead_elem = plain_object(&mut vm);
unsafe { unsafe {
@@ -1058,6 +1153,8 @@ mod tests {
oxide_kernel::shape_forge::EMPTY_SHAPE_ID, oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto), JsValue::from_js_object(proto),
)); ));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*obj).set_is_epoch(true) };
let val = JsValue::from_js_object(obj); let val = JsValue::from_js_object(obj);
vm.regs[reg as usize] = val; vm.regs[reg as usize] = val;
val val
@@ -1070,6 +1167,8 @@ mod tests {
oxide_kernel::shape_forge::EMPTY_SHAPE_ID, oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(proto), JsValue::from_js_object(proto),
)); ));
// 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。
unsafe { (*obj).set_is_epoch(true) };
let val = JsValue::from_js_object(obj); let val = JsValue::from_js_object(obj);
vm.regs[reg as usize] = val; vm.regs[reg as usize] = val;
val val
+14 -4
View File
@@ -900,6 +900,16 @@ impl Vm {
self.gc_state.session_gc = session_gc; self.gc_state.session_gc = session_gc;
} }
/// 执行期完整 GC(对象+字符串)的 dispatch 安全点入口:按水位判定触发。
/// 仅在 native_call_depth == 0 的指令边界调用——此时无 builtin 局部裸指针,
/// 对象搬移安全。预留给 17.3b(对象侧执行期触发)安全点审计后使用。
#[allow(dead_code)]
pub(crate) fn maybe_collect_session_gc_at_dispatch(&mut self) {
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.maybe_collect_gc(self);
self.gc_state.session_gc = session_gc;
}
/// 只读访问 session GC 的统计(回收次数、存活/死亡对象数、释放字节等)。 /// 只读访问 session GC 的统计(回收次数、存活/死亡对象数、释放字节等)。
pub fn session_gc_stats(&self) -> &SessionGc { pub fn session_gc_stats(&self) -> &SessionGc {
&self.gc_state.session_gc &self.gc_state.session_gc
@@ -1432,12 +1442,12 @@ impl Vm {
let mut steps: u64 = 0; let mut steps: u64 = 0;
loop { loop {
steps += 1; steps += 1;
// 执行期字符串 GC 安全点:仅在顶层 dispatch(native_call_depth == 0) // 执行期 GC 安全点:仅在顶层 dispatch(native_call_depth == 0)
// 的指令边界触发——嵌套 dispatch(builtin 经 call_function_sync 重入 // 的指令边界触发——嵌套 dispatch(builtin 经 call_function_sync 重入
// 执行 JS 回调、generator/async 恢复)期间,调用方寄存器窗口副本存于 // 执行 JS 回调、generator/async 恢复)期间,调用方寄存器窗口副本存于
// inline 状态(非 GC 根),此时回收会把调用方 regs 中的活串当死串释放。 // inline 状态(非 GC 根),此时回收会把调用方 regs 中的活值当死值释放。
// 返回顶层后检查恢复,存活串此时已回拷为执行根。账目未超水位时仅 // 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅
// 3 次字段比较。 // 少量字段比较。
if self.native_call_depth == 0 && self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark if self.native_call_depth == 0 && self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark
{ {
self.maybe_collect_session_strings(); self.maybe_collect_session_strings();
+11
View File
@@ -42,11 +42,22 @@ pub(crate) struct GcState {
/// 仅当账目超过水位才在指令边界触发回收——活串超阈值时不会每指令重复 /// 仅当账目超过水位才在指令边界触发回收——活串超阈值时不会每指令重复
/// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。 /// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。
pub(crate) string_gc_watermark: usize, pub(crate) string_gc_watermark: usize,
/// 缓存的 GC 阈值(从 config 读一次),热路径只做 usize 比较,免 Arc 解引用。
/// 预留给 17.3b(对象侧执行期触发)安全点审计后使用。
#[allow(dead_code)]
pub(crate) gc_threshold_cached: usize,
/// 执行期完整 GC(对象+字符串)的触发水位:本次收集后的存活字节 + 阈值增量。
/// 仅当账目超过水位才在指令边界触发完整回收——存活对象超阈值时不每指令重复
/// 触发无死对象可回收的白跑。预留给 17.3b 安全点审计后使用。
#[allow(dead_code)]
pub(crate) gc_watermark: usize,
pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>, pub(crate) forwarding: HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>,
} }
impl GcState { impl GcState {
pub(crate) fn track_epoch_object(&mut self, ptr: *mut JsObject) { pub(crate) fn track_epoch_object(&mut self, ptr: *mut JsObject) {
// SAFETY: 调用方保证 ptr 是刚 alloc 的 epoch JsObject,登记期存活。
unsafe { (*ptr).set_is_epoch(true) };
self.epoch_object_ptrs.push(ptr); self.epoch_object_ptrs.push(ptr);
} }
+4
View File
@@ -98,6 +98,8 @@ impl Vm {
session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0, session_bytes_allocated: 0,
string_gc_watermark: gc_threshold, string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
}, },
symbols: SymbolState { symbols: SymbolState {
@@ -202,6 +204,8 @@ impl Vm {
session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0, session_bytes_allocated: 0,
string_gc_watermark: gc_threshold, string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold,
forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher),
}, },
symbols: SymbolState { symbols: SymbolState {