diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 81409f91..3c2b1d1e 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -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 ()) })); } -/// 释放 ArrayBuffer 的字节缓冲(native_fn 槽中的 `Box>`),返回字节数。 -pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 { +/// 只读核算 ArrayBuffer 字节缓冲字节(不释放)。 +pub fn array_buffer_native_size(obj: &JsObject) -> u64 { let Some(data_ptr) = array_buffer_vec_ptr(obj) else { return 0; }; if data_ptr.is_null() { return 0; } - // SAFETY: ArrayBuffer 在 native_fn 槽中存 `Box>`。 + unsafe { + (std::mem::size_of::>() + (*data_ptr).capacity()) as u64 + } +} + +/// 释放 ArrayBuffer 的字节缓冲(native_fn 槽中的 `Box>`),返回字节数。 +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 bytes = std::mem::size_of::>() + data.capacity(); + drop(data); obj.set_native_fn(None); - bytes as u64 + bytes } pub(crate) fn array_buffer_data_ptr(vm: &mut H, this_val: JsValue) -> Result<*mut Vec, JsValue> { diff --git a/crates/oxide_builtins/src/data_view.rs b/crates/oxide_builtins/src/data_view.rs index 1c217c40..9f629d93 100644 --- a/crates/oxide_builtins/src/data_view.rs +++ b/crates/oxide_builtins/src/data_view.rs @@ -235,17 +235,30 @@ where } } -/// 释放 DataView 视图数据(`Box`),返回释放字节数。 -pub fn drop_data_view_native(obj: &mut JsObject) -> u64 { +/// 只读核算 DataView 视图数据字节(不释放)。 +pub fn data_view_native_size(obj: &JsObject) -> u64 { let Some(ptr) = data_view_data_ptr(obj) else { return 0; }; if ptr.is_null() { return 0; } + std::mem::size_of::() as u64 +} + +/// 释放 DataView 视图数据(`Box`),返回释放字节数。 +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)) }; obj.set_native_fn(None); - std::mem::size_of::() as u64 + bytes } /// `DataView.prototype.getInt8(byteOffset)`:读取 1 字节有符号整数。 diff --git a/crates/oxide_builtins/src/disposable_stack.rs b/crates/oxide_builtins/src/disposable_stack.rs index a98f83b2..e547f05f 100644 --- a/crates/oxide_builtins/src/disposable_stack.rs +++ b/crates/oxide_builtins/src/disposable_stack.rs @@ -608,8 +608,8 @@ where } } -/// 释放状态盒(对象被回收时),返回释放的字节数供泄漏统计。 -pub fn drop_dispose_native(obj: &mut JsObject) -> u64 { +/// 只读核算 DisposableStack 状态盒字节(不释放)。 +pub fn disposable_stack_native_size(obj: &JsObject) -> u64 { if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { return 0; } @@ -617,13 +617,24 @@ pub fn drop_dispose_native(obj: &mut JsObject) -> u64 { if ptr.is_null() { return 0; } - // SAFETY: 指针来自 Box::into_raw,只在对象被回收时释放一次。 unsafe { - let cap = Box::from_raw(ptr); - let bytes = - std::mem::size_of::() + cap.entries.capacity() * std::mem::size_of::(); - drop(cap); - obj.set_native_data(std::ptr::null_mut()); - bytes as u64 + (std::mem::size_of::() + + (*ptr).entries.capacity() * std::mem::size_of::()) 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 { + let cap = Box::from_raw(ptr); + drop(cap); + obj.set_native_data(std::ptr::null_mut()); + } + bytes +} diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index 3850422e..19b7dfdb 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -132,8 +132,9 @@ where } } -/// 释放 Map 的 native 数据(IndexMap),返回释放的字节数供泄漏统计。 -pub fn drop_map_native(obj: &mut JsObject) -> u64 { +/// 只读核算 Map 的 native 数据字节(不释放)。 +/// 与 `drop_map_native` 释放口径一致(capacity),供 GC 账目核算。 +pub fn map_native_size(obj: &JsObject) -> u64 { if !obj.is_map() { return 0; } @@ -142,14 +143,25 @@ pub fn drop_map_native(obj: &mut JsObject) -> u64 { return 0; } unsafe { - let boxed: Box = Box::from_raw(inner); - let bytes = std::mem::size_of::() + boxed.capacity() * std::mem::size_of::<(SetKey, JsValue)>(); - drop(boxed); - obj.set_native_data(std::ptr::null_mut()); - bytes as u64 + (std::mem::size_of::() + (*inner).capacity() * std::mem::size_of::<(SetKey, JsValue)>()) 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 对象,若提供可迭代实参 /// 则逐元素(须为对象)取 `[0]`/`[1]` 作为键值调用 set。 /// diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index 9879276a..ae03aa4a 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -129,8 +129,8 @@ pub fn regexp_constructor(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(obj_ptr)) } -/// 释放 RegExp 对象 native_fn 槽中编译的 `regress::Regex`,返回释放字节数。 -pub fn drop_regexp_native(obj: &mut JsObject) -> u64 { +/// 只读核算 RegExp 编译正则字节(不释放)。 +pub fn regexp_native_size(obj: &JsObject) -> u64 { if !obj.is_regexp_obj() { return 0; } @@ -141,9 +141,23 @@ pub fn drop_regexp_native(obj: &mut JsObject) -> u64 { if regex_ptr.is_null() { return 0; } + std::mem::size_of::() 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)) }; obj.set_native_fn(None); - std::mem::size_of::() as u64 + bytes } /// 深拷贝已编译正则到新对象(GC 搬移 / 晋升流程用)。 diff --git a/crates/oxide_builtins/src/set.rs b/crates/oxide_builtins/src/set.rs index 00ee3c87..09aa015a 100644 --- a/crates/oxide_builtins/src/set.rs +++ b/crates/oxide_builtins/src/set.rs @@ -173,8 +173,9 @@ where } } -/// 释放 Set 的 native 数据(IndexSet),返回释放的字节数供泄漏统计。 -pub fn drop_set_native(obj: &mut JsObject) -> u64 { +/// 只读核算 Set 的 native 数据字节(不释放)。 +/// 与 `drop_set_native` 释放口径一致(capacity),供 GC 账目核算。 +pub fn set_native_size(obj: &JsObject) -> u64 { if !obj.is_set() { return 0; } @@ -183,14 +184,25 @@ pub fn drop_set_native(obj: &mut JsObject) -> u64 { return 0; } unsafe { - let boxed: Box = Box::from_raw(inner); - let bytes = std::mem::size_of::() + boxed.capacity() * std::mem::size_of::(); - drop(boxed); - obj.set_native_data(std::ptr::null_mut()); - bytes as u64 + (std::mem::size_of::() + (*inner).capacity() * std::mem::size_of::()) 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 对象,若提供可迭代实参 /// 则按插入序逐个 add。 /// diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index cb92a099..3c1b3097 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -157,17 +157,30 @@ where } } -/// 释放 TypedArray 的视图数据(`Box`),返回释放字节数。 -pub fn drop_typed_array_native(obj: &mut JsObject) -> u64 { +/// 只读核算 TypedArray 视图数据字节(不释放)。 +pub fn typed_array_native_size(obj: &JsObject) -> u64 { let Some(ptr) = typed_array_data_ptr(obj) else { return 0; }; if ptr.is_null() { return 0; } + std::mem::size_of::() as u64 +} + +/// 释放 TypedArray 的视图数据(`Box`),返回释放字节数。 +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)) }; obj.set_native_fn(None); - std::mem::size_of::() as u64 + bytes } pub(crate) fn get_typed_array_data(vm: &mut H, this_val: JsValue) -> Result { diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 68cc28f2..cce106b9 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -665,6 +665,8 @@ impl JsObject { pub const SESSION_EPOCH_BIT: u8 = 0x01; /// `is_session_epoch` 字段中的 GC 标记位。 pub const GC_MARK_BIT: u8 = 0x02; + /// `is_session_epoch` 字段中的 epoch 归属标记位。 + pub const EPOCH_BIT: u8 = 0x04; /// 是否 Date 外来对象。 #[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 对象。 /// /// 用于把持久对象快照进当前调用上下文,修改不反向传播到源对象。 diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index 61134e99..f2b1b2ba 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -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); } -/// 释放异步状态盒(对象被 GC 回收时),返回释放字节数。 -pub(crate) fn drop_async_native(obj: &JsObject) -> u64 { +/// 只读核算异步状态盒字节(不释放),供 GC 账目核算。 +pub(crate) fn async_native_size(obj: &JsObject) -> u64 { if !obj.is_async_obj() { return 0; } @@ -555,9 +555,22 @@ pub(crate) fn drop_async_native(obj: &JsObject) -> u64 { if ptr.is_null() { return 0; } - // SAFETY: 指针来自 create_async_context 的 Box::into_raw,只在 GC 回收时释放一次。 - let state = unsafe { Box::from_raw(ptr) }; - std::mem::size_of::() as u64 - + state.suspended.heap_bytes() - + state.args.capacity() as u64 * std::mem::size_of::() as u64 + unsafe { + std::mem::size_of::() as u64 + + (*ptr).suspended.heap_bytes() + + (*ptr).args.capacity() as u64 * std::mem::size_of::() 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 } diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index e6b052c2..67a777da 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -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); } -/// 释放异步生成器状态盒(对象被 GC 回收时),返回释放字节数。 -pub(crate) fn drop_async_generator_native(obj: &JsObject) -> u64 { +/// 只读核算异步生成器状态盒字节(不释放),供 GC 账目核算。 +pub(crate) fn async_generator_native_size(obj: &JsObject) -> u64 { if !obj.is_async_generator_obj() { return 0; } @@ -961,9 +961,22 @@ pub(crate) fn drop_async_generator_native(obj: &JsObject) -> u64 { if ptr.is_null() { return 0; } - // SAFETY: 指针来自 create_async_generator_object 的 Box::into_raw,只在 GC 回收时释放一次。 - let state = unsafe { Box::from_raw(ptr) }; - std::mem::size_of::() as u64 - + state.suspended.heap_bytes() - + state.args.capacity() as u64 * std::mem::size_of::() as u64 + unsafe { + std::mem::size_of::() as u64 + + (*ptr).suspended.heap_bytes() + + (*ptr).args.capacity() as u64 * std::mem::size_of::() 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 } diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index bb62ecfb..304a23ef 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -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); } -/// 释放生成器状态盒(对象被 GC 回收时),返回释放字节数。 -pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 { +/// 只读核算生成器状态盒字节(不释放),供 GC 账目核算。 +pub(crate) fn generator_native_size(obj: &JsObject) -> u64 { if !obj.is_generator_obj() { return 0; } @@ -912,9 +912,23 @@ pub(crate) fn drop_generator_native(obj: &JsObject) -> u64 { if ptr.is_null() { return 0; } - // SAFETY: 指针来自 create_generator_object 的 Box::into_raw,只在 GC 回收时释放一次。 - let state = unsafe { Box::from_raw(ptr) }; - std::mem::size_of::() as u64 - + state.suspended.heap_bytes() - + state.args.capacity() as u64 * std::mem::size_of::() as u64 + // SAFETY: 通过裸指针读取 capacity 字段,不解引用整个 Box(不移动/释放)。 + unsafe { + std::mem::size_of::() as u64 + + (*ptr).suspended.heap_bytes() + + (*ptr).args.capacity() as u64 * std::mem::size_of::() 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 } diff --git a/crates/oxide_vm/src/promise.rs b/crates/oxide_vm/src/promise.rs index e7843f90..2d754188 100644 --- a/crates/oxide_vm/src/promise.rs +++ b/crates/oxide_vm/src/promise.rs @@ -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); } -/// 释放 Promise 状态盒(对象被回收时),返回释放字节数。 -pub(crate) fn drop_promise_native(obj: &JsObject) -> u64 { +/// 只读核算 Promise 状态盒字节(不释放),供 GC 账目核算。 +pub(crate) fn promise_native_size(obj: &JsObject) -> u64 { if !obj.is_promise_obj() { return 0; } @@ -1835,10 +1835,23 @@ pub(crate) fn drop_promise_native(obj: &JsObject) -> u64 { if ptr.is_null() { return 0; } - // SAFETY: 指针来自 Box::into_raw,只在对象被回收时释放一次。 + unsafe { + std::mem::size_of::() as u64 + + (*ptr).reactions.capacity() as u64 * std::mem::size_of::() 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) }; - std::mem::size_of::() as u64 - + state.reactions.capacity() as u64 * std::mem::size_of::() as u64 + drop(state); + bytes } /// 供外部遍历微任务队列(GC mark/rewrite 用)。 diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index 7bc13ee4..d5a49212 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -34,7 +34,7 @@ impl Vm { return src; } let src_ref = unsafe { &*src }; - if src_ref.is_session_epoch() || !self.epoch.is_epoch_ptr(src.cast::()) { + if src_ref.is_session_epoch() || !src_ref.is_epoch() { return src; } 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; forwarding.insert(src, dst); self.gc_state.session_object_ptrs.push(dst); - self.gc_state.session_bytes_allocated += std::mem::size_of::(); let dst_ref = unsafe { &mut *dst }; dst_ref.rewrite_object_values(|value| self.promote_value_if_epoch_object(value, forwarding)); @@ -97,6 +96,9 @@ impl Vm { // 已编译正则是 Box 深拷贝到新对象:源盒随 epoch 释放,互不共享。 regexp::clone_regexp_native(src_ref, dst_ref); } + // 账目计入:对象头 + 堆数据(属性/元素/meta Vec + native 状态盒),用 clone 后的 dst 核算。 + self.gc_state.session_bytes_allocated += std::mem::size_of::() + + crate::session_gc::SessionGc::object_heap_data_bytes(dst_ref) as usize; dst } @@ -107,7 +109,7 @@ impl Vm { return value; } let ptr = value.as_js_object_ptr(); - if ptr.is_null() || !self.epoch.is_epoch_ptr(ptr.cast::()) { + if ptr.is_null() || !unsafe { &*ptr }.is_epoch() { return value; } JsValue::from_js_object(self.promote_object_inner(ptr, forwarding)) @@ -118,7 +120,7 @@ impl Vm { return value; } let value_ptr = value.as_js_object_ptr(); - if value_ptr.is_null() || !self.epoch.is_epoch_ptr(value_ptr.cast::()) { + if value_ptr.is_null() || !unsafe { &*value_ptr }.is_epoch() { return value; } JsValue::from_js_object(self.promote_object(value_ptr)) @@ -133,12 +135,15 @@ mod tests { fn plain_object(vm: &mut Vm) -> *mut JsObject { let proto = vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject; - vm.epoch - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto))) + let ptr = vm.epoch + .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 { - value.is_object() && vm.epoch.is_epoch_ptr(value.as_js_object_ptr().cast::()) + value.is_object() && unsafe { (&*value.as_js_object_ptr()).is_epoch() } } #[test] @@ -221,6 +226,8 @@ mod tests { 2, vm.epoch.bump(), )); + // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 + unsafe { (*arr).set_is_epoch(true) }; unsafe { (*arr).set_prop_at(0, JsValue::from_js_object(elem_child)); (*arr).set_accessor_meta( diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index c54bda5d..f8637133 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -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; + if !elems_ptr.is_null() { + unsafe { + bytes += size_of::>() as u64 + + ((*elems_ptr).capacity() * size_of::()) as u64; + } + } + + let elems_meta_ptr = obj.array_elements_meta_raw() as *const Vec>; + if !elems_meta_ptr.is_null() { + unsafe { + bytes += size_of::>>() as u64 + + ((*elems_meta_ptr).capacity() * size_of::>()) as u64; + } + } + + let hash_ptr = obj.hash_props_raw() as *const Vec; + if !hash_ptr.is_null() { + unsafe { + bytes += size_of::>() as u64 + + ((*hash_ptr).capacity() * size_of::()) as u64; + } + } + + let meta_ptr = obj.prop_meta_raw() as *const Vec>; + if !meta_ptr.is_null() { + unsafe { + bytes += size_of::>>() as u64 + + ((*meta_ptr).capacity() * size_of::>()) 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 { let mut edges = Vec::new(); if let Some(elements) = obj.array_elements_vec() { @@ -502,7 +554,15 @@ impl SessionGc { forwarding.clear(); vm.gc_state.forwarding = forwarding; vm.gc_state.session_epoch = new_arena; - vm.gc_state.session_bytes_allocated = vm.gc_state.session_object_ptrs.len() * size_of::(); + 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::() as u64 + Self::object_heap_data_bytes(obj) + }) + .sum::() as usize; self.clear_all_marks(vm); @@ -570,6 +630,16 @@ impl SessionGc { && 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 回收;执行期走增量水位,活串超阈值时不每指令 @@ -664,7 +734,15 @@ impl SessionGc { self.debug_assert_marked_object_strings_live(vm); // 扣减字符串账目(保留对象账目),再补回存活串字节。 - let object_bytes = vm.gc_state.session_object_ptrs.len() * size_of::(); + let object_bytes: usize = vm.gc_state + .session_object_ptrs + .iter() + .filter(|&&ptr| !ptr.is_null()) + .map(|&ptr| { + let obj = unsafe { &*ptr }; + size_of::() as u64 + Self::object_heap_data_bytes(obj) + }) + .sum::() as usize; vm.gc_state.session_bytes_allocated = object_bytes; 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 { format!( "[GC] collection #{}: {} scanned, {} live, {} dead, {} freed, {}μs", @@ -771,10 +861,13 @@ mod tests { fn plain_object(vm: &mut Vm) -> *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, 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 { @@ -984,6 +1077,8 @@ mod tests { 2, vm.epoch.bump(), )); + // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 + unsafe { (*arr).set_is_epoch(true) }; let live_elem = plain_object(&mut vm); let dead_elem = plain_object(&mut vm); unsafe { @@ -1058,6 +1153,8 @@ mod tests { oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto), )); + // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 + unsafe { (*obj).set_is_epoch(true) }; let val = JsValue::from_js_object(obj); vm.regs[reg as usize] = val; val @@ -1070,6 +1167,8 @@ mod tests { oxide_kernel::shape_forge::EMPTY_SHAPE_ID, JsValue::from_js_object(proto), )); + // 测试辅助函数绕过 alloc_object,需手动置位 EPOCH_BIT。 + unsafe { (*obj).set_is_epoch(true) }; let val = JsValue::from_js_object(obj); vm.regs[reg as usize] = val; val diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 8b3c7357..6b734a6b 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -900,6 +900,16 @@ impl Vm { 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 的统计(回收次数、存活/死亡对象数、释放字节等)。 pub fn session_gc_stats(&self) -> &SessionGc { &self.gc_state.session_gc @@ -1432,12 +1442,12 @@ impl Vm { let mut steps: u64 = 0; loop { steps += 1; - // 执行期字符串 GC 安全点:仅在顶层 dispatch(native_call_depth == 0) + // 执行期 GC 安全点:仅在顶层 dispatch(native_call_depth == 0) // 的指令边界触发——嵌套 dispatch(builtin 经 call_function_sync 重入 // 执行 JS 回调、generator/async 恢复)期间,调用方寄存器窗口副本存于 - // inline 状态(非 GC 根),此时回收会把调用方 regs 中的活串当死串释放。 - // 返回顶层后检查恢复,存活串此时已回拷为执行根。账目未超水位时仅 - // 3 次字段比较。 + // inline 状态(非 GC 根),此时回收会把调用方 regs 中的活值当死值释放。 + // 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅 + // 少量字段比较。 if self.native_call_depth == 0 && self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark { self.maybe_collect_session_strings(); diff --git a/crates/oxide_vm/src/vm_state.rs b/crates/oxide_vm/src/vm_state.rs index d36c08d8..6fec962b 100644 --- a/crates/oxide_vm/src/vm_state.rs +++ b/crates/oxide_vm/src/vm_state.rs @@ -42,11 +42,22 @@ pub(crate) struct GcState { /// 仅当账目超过水位才在指令边界触发回收——活串超阈值时不会每指令重复 /// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。 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>, } impl GcState { 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); } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 10233043..ce9c93a0 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -98,6 +98,8 @@ impl Vm { session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_bytes_allocated: 0, string_gc_watermark: gc_threshold, + gc_threshold_cached: gc_threshold, + gc_watermark: gc_threshold, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, symbols: SymbolState { @@ -202,6 +204,8 @@ impl Vm { session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_bytes_allocated: 0, string_gc_watermark: gc_threshold, + gc_threshold_cached: gc_threshold, + gc_watermark: gc_threshold, forwarding: std::collections::HashMap::with_hasher(rustc_hash::FxBuildHasher), }, symbols: SymbolState {