diff --git a/crates/oxide_builtins/src/disposable_stack.rs b/crates/oxide_builtins/src/disposable_stack.rs new file mode 100644 index 0000000..7b15e1b --- /dev/null +++ b/crates/oxide_builtins/src/disposable_stack.rs @@ -0,0 +1,412 @@ +//! DisposableStack(同步资源栈)内置对象实现。 +//! +//! 状态盒 `DisposeCapability` 以 `Box::into_raw` 存入对象 `native_data`:`state` +//! 记录栈状态,`entries` 按入栈序存待释放资源。同步栈 dispose 只用 +//! Pending/Disposed 两态;Disposing 为异步资源栈 disposeAsync 预留。GC 四函数 +//! (edges/rewrite/clone/drop)供 session 层跨 epoch 追踪,签名与 Map/Promise +//! 状态盒同构。 + +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use oxide_runtime_api::{NativeResult, VmHost}; + +/// 分配空状态盒并存入新 DisposableStack 对象(proto 固定为 world 字段)。 +fn alloc_disposable_stack(vm: &mut H, proto_val: JsValue) -> *mut JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val); + obj.type_tag = JsObject::OBJ_TYPE_DISPOSABLE_STACK; + obj.set_native_data(Box::into_raw(Box::new(DisposeCapability { + state: DisposeState::Pending, + entries: Vec::new(), + })) as *mut u8); + vm.alloc_object(obj) +} + +macro_rules! native_try { + ($expr:expr) => { + match $expr { + Ok(value) => value, + Err(err) => return NativeResult::Err(err), + } + }; +} + +/// 取出栈对象 native-data 槽中的 `DisposeCapability` 可变指针。 +/// +/// # 边界与前提 +/// - this 非对象 → TypeError; +/// - type_tag 非 24/25 → TypeError(无槽对象); +/// - native_data 为空 → TypeError(防御,正常构造路径不会出现)。 +fn require_dispose_capability(vm: &mut H, this_val: JsValue) -> Result<*mut DisposeCapability, JsValue> { + if !this_val.is_object() { + return Err(crate::error::create_type_error(vm, "called on non-DisposableStack object")); + } + let obj_ptr = this_val.as_js_object_ptr(); + if obj_ptr.is_null() { + return Err(crate::error::create_type_error(vm, "DisposableStack internal state invalid")); + } + // SAFETY: obj_ptr 是当前 epoch 分配的 JsObject 非空指针;native 执行期间 epoch 不重置。 + let obj = unsafe { &*obj_ptr }; + if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { + return Err(crate::error::create_type_error( + vm, + "DisposableStack.prototype method called on incompatible receiver", + )); + } + let cap_ptr = get_capability_ptr(obj); + if cap_ptr.is_null() { + return Err(crate::error::create_type_error(vm, "DisposableStack internal state invalid")); + } + Ok(cap_ptr) +} + +fn disposed_reference_error(vm: &mut H) -> JsValue { + crate::error::create_reference_error(vm, "DisposableStack has been disposed") +} + +/// `DisposableStack` 构造函数:创建带空状态盒的 DisposableStack 对象。 +/// +/// # 步骤 +/// 1. 校验 NewTarget:`this` 的原型链须命中 DisposableStack.prototype +/// (`new DisposableStack()` 直接命中,子类 `super()` 经子类原型链命中; +/// 普通调用抛 TypeError)。 +/// 2. 建空状态盒对象,proto 固定为 world.disposable_stack_proto。 +pub fn disposable_stack_constructor(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let is_new_call = this_val.is_object() && { + let stack_proto = vm.session().builtin_world().disposable_stack_proto.as_ptr() as *mut JsObject; + let this_ptr = this_val.as_js_object_ptr(); + if this_ptr.is_null() { + false + } else { + let mut proto = unsafe { &*this_ptr }.proto(); + let mut found = false; + for _ in 0..16 { + if !proto.is_object() { + break; + } + let proto_ptr = proto.as_js_object_ptr(); + if proto_ptr.is_null() { + break; + } + if std::ptr::eq(proto_ptr, stack_proto) { + found = true; + break; + } + proto = unsafe { &*proto_ptr }.proto(); + } + found + } + }; + if !is_new_call { + return NativeResult::Err(crate::error::create_type_error(vm, "DisposableStack must be called with new")); + } + let proto_val = + JsValue::from_js_object(vm.session().builtin_world().disposable_stack_proto.as_ptr() as *mut JsObject); + let stack = alloc_disposable_stack(vm, proto_val); + NativeResult::Ok(JsValue::from_js_object(stack)) +} + +/// `DisposableStack.prototype.use(value)`:把资源的 `value[@@dispose]` 方法入栈。 +/// +/// # 步骤 +/// 1. 校验栈对象与状态;Disposed 抛 ReferenceError。 +/// 2. value 为 null/undefined → 直接返回 value 不入栈。 +/// 3. value 非对象 → TypeError。 +/// 4. 读 `value[@@dispose]`(只读一次,getter 抛错透传原值);缺/null/undefined/ +/// 非 callable → TypeError。 +/// 5. 入栈 `{value, method, sync, receiver 模式}`,返回 value。 +pub fn disposable_stack_use(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let cap_ref = unsafe { &mut *cap }; + if cap_ref.state != DisposeState::Pending { + return NativeResult::Err(disposed_reference_error(vm)); + } + let value = vm.reg(if args.len() > 1 { args[1] } else { 0 }); + if value.is_null() || value.is_undefined() { + return NativeResult::Ok(value); + } + if !value.is_object() { + return NativeResult::Err(crate::error::create_type_error(vm, "value is not an Object")); + } + let value_obj = unsafe { &*value.as_js_object_ptr() }; + let dispose_key = oxide_types::private_key::make_well_known_symbol_key(12); + let method = match vm.ordinary_get(value_obj, dispose_key, value) { + Ok(m) => m, + Err(err) => return NativeResult::Err(crate::iterator::engine_error(vm, &err)), + }; + if method.is_null() || method.is_undefined() || !crate::iterator::is_callable(method) { + return NativeResult::Err(crate::error::create_type_error(vm, "value[Symbol.dispose] is not callable")); + } + cap_ref.entries.push(DisposeEntry { value, method, hint: 0, arg_style: false }); + NativeResult::Ok(value) +} + +/// `DisposableStack.prototype.adopt(value, onDispose)`:把 value 与其释放回调入栈。 +/// +/// # 步骤 +/// 1. 校验栈对象与状态;Disposed 抛 ReferenceError。 +/// 2. onDispose 非 callable → TypeError。 +/// 3. 入栈 `{value, onDispose, sync, arg-style}`(dispose 时 `Call(onDispose, undefined, «value»)`), +/// 返回 value。 +pub fn disposable_stack_adopt(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let cap_ref = unsafe { &mut *cap }; + if cap_ref.state != DisposeState::Pending { + return NativeResult::Err(disposed_reference_error(vm)); + } + let value = vm.reg(if args.len() > 1 { args[1] } else { 0 }); + let on_dispose = vm.reg(if args.len() > 2 { args[2] } else { 0 }); + if !crate::iterator::is_callable(on_dispose) { + return NativeResult::Err(crate::error::create_type_error(vm, "onDispose is not callable")); + } + cap_ref.entries.push(DisposeEntry { value, method: on_dispose, hint: 0, arg_style: true }); + NativeResult::Ok(value) +} + +/// `DisposableStack.prototype.defer(onDispose)`:把无参数释放回调入栈。 +/// +/// # 步骤 +/// 1. 校验栈对象与状态;Disposed 抛 ReferenceError。 +/// 2. onDispose 非 callable → TypeError。 +/// 3. 入栈 `{undefined, onDispose, sync, receiver 模式}`(dispose 时 `Call(onDispose, undefined, «»)`), +/// 返回 undefined。 +pub fn disposable_stack_defer(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let cap_ref = unsafe { &mut *cap }; + if cap_ref.state != DisposeState::Pending { + return NativeResult::Err(disposed_reference_error(vm)); + } + let on_dispose = vm.reg(if args.len() > 1 { args[1] } else { 0 }); + if !crate::iterator::is_callable(on_dispose) { + return NativeResult::Err(crate::error::create_type_error(vm, "onDispose is not callable")); + } + cap_ref + .entries + .push(DisposeEntry { value: JsValue::undefined(), method: on_dispose, hint: 0, arg_style: false }); + NativeResult::Ok(JsValue::undefined()) +} + +/// `DisposableStack.prototype.dispose()`:逆序执行全部释放回调并置栈为 Disposed。 +/// +/// # 步骤 +/// 1. 校验栈对象;state 非 Pending → 直接返回 undefined(幂等,Disposing/Disposed 同返)。 +/// 2. 先置 state=Disposed 再执行(防执行中 use/adopt 重入入栈)。 +/// 3. 逆序逐条调用:arg-style 走 `Call(method, undefined, «value»)`,否则 +/// `Call(method, value, «»)`;method 为 undefined 时跳过(防御)。 +/// 4. 单错原样抛;多错用 SuppressedError 链式合并(error=后抛、suppressed=前值), +/// 循环不因错误中断,剩余条目继续执行。 +/// +/// # 边界与前提 +/// - 回调抛原始值(含 primitive)经 take_uncaught_value 恢复并存入 SuppressedError; +/// - 无 uncaught 时回退按错误文本建普通 Error。 +pub fn disposable_stack_dispose(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let cap_ref = unsafe { &mut *cap }; + if cap_ref.state != DisposeState::Pending { + return NativeResult::Ok(JsValue::undefined()); + } + // 先置位再执行:执行中调用 use/adopt/defer 一律命中 Disposed 分支。 + cap_ref.state = DisposeState::Disposed; + let mut completion: Option = None; for entry in cap_ref.entries.iter().rev() { + if entry.method.is_undefined() { + continue; + } + let call_result = if entry.arg_style { + vm.call_function_sync(entry.method, JsValue::undefined(), &[entry.value]) + } else { + vm.call_function_sync(entry.method, entry.value, &[]) + }; + if let Err(err) = call_result { + // 恢复本次回调抛出的原始异常值;无 uncaught 时按错误文本建普通 Error。 + let err_val = vm + .take_uncaught_value() + .unwrap_or_else(|| crate::error::create_kind_error(vm, "Error", &err)); + completion = Some(match completion { + None => err_val, + Some(prev) => crate::error::create_suppressed_error(vm, err_val, prev), + }); + } + } + // 条目逐条执行完毕,清空释放引用(规范 DisposeResources 逐条移除)。 + cap_ref.entries.clear(); + match completion { + Some(err_val) => NativeResult::Err(err_val), + None => NativeResult::Ok(JsValue::undefined()), + } +} + +/// `DisposableStack.prototype.move()`:把全部 entries 转移到新 DisposableStack。 +/// +/// # 步骤 +/// 1. 校验栈对象;state 非 Pending → ReferenceError。 +/// 2. `mem::take` 转移 entries;建新栈(proto 固定为 DisposableStack.prototype, +/// 非子类原型)并装入转移的 entries。 +/// 3. 源栈置 Disposed(不再执行任何释放)。 +pub fn disposable_stack_move(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let cap_ref = unsafe { &mut *cap }; + if cap_ref.state != DisposeState::Pending { + return NativeResult::Err(disposed_reference_error(vm)); + } + let taken = std::mem::take(&mut cap_ref.entries); + let proto_val = + JsValue::from_js_object(vm.session().builtin_world().disposable_stack_proto.as_ptr() as *mut JsObject); + let new_stack = alloc_disposable_stack(vm, proto_val); + // SAFETY: alloc_disposable_stack 刚分配的对象,native_data 为新建空状态盒。 + unsafe { + let new_obj = &mut *new_stack; + let new_cap = new_obj.native_data() as *mut DisposeCapability; + (*new_cap).entries = taken; + } + cap_ref.state = DisposeState::Disposed; + NativeResult::Ok(JsValue::from_js_object(new_stack)) +} + +/// `get DisposableStack.prototype.disposed`:栈是否已 dispose/move。 +pub fn disposable_stack_disposed_getter(vm: &mut H, args: &[u8]) -> NativeResult { + let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); + let cap = native_try!(require_dispose_capability(vm, this_val)); + // SAFETY: cap 由 require 校验,native 执行期间对象存活。 + let state = unsafe { (*cap).state }; + NativeResult::Ok(JsValue::bool(state != DisposeState::Pending)) +} + + +/// 栈生命周期状态:Pending 可入栈,Disposed 后所有操作抛 ReferenceError; +/// Disposing 为异步资源栈 disposeAsync 执行期标记(当前未构造,语义预留)。 +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) enum DisposeState { + Pending, + #[allow(dead_code)] + Disposing, + Disposed, +} + +/// 单条待释放资源:dispose 时按 `arg_style` 选择调用约定。 +pub(crate) struct DisposeEntry { + /// use() 的资源值(dispose 时作 receiver);adopt() 的资源值(作参数);defer() 恒 undefined。 + pub value: JsValue, + /// 释放方法:use() 为 @@dispose 函数;adopt()/defer() 为用户回调。 + pub method: JsValue, + /// 0=sync-dispose,1=async-dispose(异步资源栈用)。 + pub hint: u8, + /// true=adopt 闭包语义(`Call(method, undefined, «value»)`); + /// false=use/defer(`Call(method, value, «»)`)。 + pub arg_style: bool, +} + +/// 状态盒:栈状态 + 按入栈序的资源条目。 +pub(crate) struct DisposeCapability { + pub state: DisposeState, + pub entries: Vec, +} + +fn get_capability_ptr(obj: &JsObject) -> *mut DisposeCapability { + obj.native_data() as *mut DisposeCapability +} + +/// 收集栈内作为对象引用的 value/method(GC 根边),供跨 epoch 遍历/重写时追踪。 +pub fn dispose_edges(obj: &JsObject) -> Vec { + if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { + return Vec::new(); + } + let ptr = get_capability_ptr(obj); + if ptr.is_null() { + return Vec::new(); + } + // SAFETY: native_data 持有 `alloc_capability` 写入的有效 Box 指针,本函数 + // 只在对象存活期间被 GC/绑定层调用。 + unsafe { + (*ptr) + .entries + .iter() + .flat_map(|entry| [entry.value, entry.method]) + .filter(|value: &JsValue| value.is_object()) + .collect() + } +} + +/// 克隆状态盒到新对象,用 `rewrite` 改写其中的对象引用 +/// (供跨 epoch 的对象重写/克隆流程使用)。 +pub fn clone_dispose_native_with_rewrite(src: &JsObject, dst: &mut JsObject, mut rewrite: F) +where + F: FnMut(JsValue) -> JsValue, +{ + if !src.is_disposable_stack_obj() && !src.is_async_disposable_stack_obj() { + return; + } + let ptr = get_capability_ptr(src); + if ptr.is_null() { + dst.set_native_data(std::ptr::null_mut()); + return; + } + // SAFETY: 同上,src 状态盒在 GC 移动期间仍存活。 + let src_cap = unsafe { &*ptr }; + let cloned = DisposeCapability { + state: src_cap.state, + entries: src_cap + .entries + .iter() + .map(|entry| DisposeEntry { + value: if entry.value.is_object() { rewrite(entry.value) } else { entry.value }, + method: if entry.method.is_object() { rewrite(entry.method) } else { entry.method }, + hint: entry.hint, + arg_style: entry.arg_style, + }) + .collect(), + }; + dst.set_native_data(Box::into_raw(Box::new(cloned)) as *mut u8); +} + +/// 原地重写状态盒,用 `rewrite` 改写其中的对象引用。 +pub fn rewrite_dispose_native(obj: &mut JsObject, mut rewrite: F) +where + F: FnMut(JsValue) -> JsValue, +{ + if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { + return; + } + let ptr = get_capability_ptr(obj); + if ptr.is_null() { + return; + } + // SAFETY: 同上,改写发生在 GC 移动/清扫期间,对象仍存活。 + unsafe { + for entry in &mut (*ptr).entries { + entry.value = rewrite(entry.value); + entry.method = rewrite(entry.method); + } + } +} + +/// 释放状态盒(对象被回收时),返回释放的字节数供泄漏统计。 +pub fn drop_dispose_native(obj: &mut JsObject) -> u64 { + if !obj.is_disposable_stack_obj() && !obj.is_async_disposable_stack_obj() { + return 0; + } + let ptr = get_capability_ptr(obj); + 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 + } +} diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index d002435..c666745 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -19,6 +19,8 @@ pub mod boolean; pub mod builtins_log; /// DataView 内置对象实现(对 ArrayBuffer 的定点读写视图)。 pub mod data_view; +/// DisposableStack/AsyncDisposableStack 内置对象实现(资源栈状态盒与 GC 追踪)。 +pub mod disposable_stack; /// Date 内置对象实现(时间戳存取、get/set 系列与 to*String 系列)。 pub mod date; /// Error 内置对象实现(各类 Error 构造函数与 stack/toString)。 diff --git a/crates/oxide_vm/src/bindings/bind_disposable_stack.rs b/crates/oxide_vm/src/bindings/bind_disposable_stack.rs new file mode 100644 index 0000000..8234d44 --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_disposable_stack.rs @@ -0,0 +1,150 @@ +use std::sync::Arc; + +use oxide_kernel::kernel::{KernelCore, KernelSession}; +use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::value::JsValue; + +use crate::bindings::{ + apply_binding_table, bind_accessor_getter, bind_global_value, bind_well_known_data_property, + bind_well_known_method_alias, configure_native_constructor, +}; + +/// 在 `DisposableStack.prototype` 上安装方法/别名/访问器/@@toStringTag。 +/// +/// 幂等:proto 上已有 `use` 方法(全新 proto 或已完整绑定)时整组跳过, +/// 保留原型经 dirty reset 重复经过时安全。`@@dispose` 别名须在 `dispose` +/// 绑定之后调用(读源槽 lookup_position,顺序敏感)。 +pub fn bind_disposable_stack_protos(core: &Arc, session: &KernelSession) { + let world = session.builtin_world(); + let proto_ptr = world.disposable_stack_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + let si_use = core.perm_interner().intern("use").0; + if core.shape_forge().lookup_position(proto.shape_id(), si_use).is_some() { + return; + } + + apply_binding_table( + world, + proto, + core, + &[ + ("use", oxide_builtins::disposable_stack::disposable_stack_use:: as *const (), 1), + ("adopt", oxide_builtins::disposable_stack::disposable_stack_adopt:: as *const (), 2), + ("defer", oxide_builtins::disposable_stack::disposable_stack_defer:: as *const (), 1), + ("dispose", oxide_builtins::disposable_stack::disposable_stack_dispose:: as *const (), 0), + ("move", oxide_builtins::disposable_stack::disposable_stack_move:: as *const (), 0), + ], + ); + bind_accessor_getter( + core, + session, + proto, + "disposed", + oxide_builtins::disposable_stack::disposable_stack_disposed_getter:: as *const (), + ); + // @@dispose 与 dispose 为同一函数对象(先绑 dispose 再取源槽)。 + bind_well_known_method_alias(core, proto, "dispose", 12); + let sf = core.perm_interner().as_ref(); + let tag = JsValue::perm_string(sf.string_ptr(sf.intern("DisposableStack").0)); + bind_well_known_data_property(core, proto, 9, tag, PropAttributes::new(false, false, true)); +} + +/// 在 proto 上写 `constructor` 数据属性(非枚举,描述符 {t,f,t})。 +fn write_proto_constructor(core: &Arc, proto: &mut JsObject, ctor_val: JsValue) { + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + let si_constructor = sf.intern("constructor").0; + if sh.lookup_position(proto.shape_id(), si_constructor).is_some() { + return; + } + let new_shape = sh.make_shape(proto.shape_id(), si_constructor); + proto.set_shape_id(new_shape); + let pos = proto.push_prop(ctor_val); + proto.set_data_meta(pos, PropAttributes::new(true, false, true)); + proto.bump_generation(); +} + +/// 把 `DisposableStack` 构造器绑定到 global(init 与 full_reset global 重建共用)。 +/// +/// proto.constructor 已有则复用(dirty reset 保留路径);否则 Box 自建构造器 +/// (proto=Function.prototype,prototype 槽=DisposableStack.prototype,描述符按规范)。 +/// 幂等:protos 安装与 global 槽写入均自带重复跳过。 +pub fn bind_disposable_stack(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let world = session.builtin_world(); + let proto_ptr = world.disposable_stack_proto.as_ptr() as *mut JsObject; + let proto = unsafe { &mut *proto_ptr }; + let si_constructor = core.perm_interner().intern("constructor").0; + let ctor_val = match core.shape_forge().lookup_position(proto.shape_id(), si_constructor) { + Some(pos) => { + let existing = proto.get_prop_at(pos); + if existing.is_object() { + existing + } else { + JsValue::undefined() + } + } + None => JsValue::undefined(), + }; + let ctor_val = if ctor_val.is_object() { + ctor_val + } else { + // Box 自建:[[Prototype]] 指向 Function.prototype,prototype/name 描述符按规范。 + let function_proto = world.function_proto.as_ptr() as *mut JsObject; + let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto))); + ctor.set_function(true); + configure_native_constructor( + &mut ctor, + oxide_builtins::disposable_stack::disposable_stack_constructor:: as *const (), + 0, + ); + let sf = core.perm_interner().as_ref(); + let sh = core.shape_forge().as_ref(); + let si_prototype = sf.intern("prototype").0; + let si_name = sf.intern("name").0; + let si_length = sf.intern("length").0; + let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype); + let ctor_shape2 = sh.make_shape(ctor_shape1, si_name); + let ctor_shape3 = sh.make_shape(ctor_shape2, si_length); + ctor.set_shape_id(ctor_shape3); + ctor.ensure_hash_props() + .push(JsValue::from_js_object(world.disposable_stack_proto.as_ptr() as *mut JsObject)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(sf.intern("DisposableStack").0))); + ctor.ensure_hash_props().push(JsValue::int(0)); + ctor.set_data_meta(0u32, PropAttributes::new(false, false, false)); + ctor.set_data_meta(1u32, PropAttributes::new(false, false, true)); + ctor.set_data_meta(2u32, PropAttributes::new(false, false, true)); + JsValue::from_js_object(Box::into_raw(ctor)) + }; + + write_proto_constructor(core, proto, ctor_val); + bind_disposable_stack_protos(core, session); + bind_global_value(core, global, "DisposableStack", ctor_val); +} + +/// 对齐保留 global 上 `DisposableStack` 函数对象的 `prototype` 属性:object 家族 +/// 重建会产生新的 `DisposableStack.prototype`,保留的构造器须指向新原型, +/// 否则 reset 后 `instanceof` 沿旧原型链恒 false。 +/// +/// global 同时重建(global dirty)或初始化时无 `DisposableStack` 属性,直接跳过 +/// ——彼时由 `bind_disposable_stack` 以新原型创建函数对象。 +pub fn sync_disposable_stack_ctor(core: &Arc, session: &KernelSession, global: &mut JsObject) { + let si_stack = core.perm_interner().intern("DisposableStack").0; + let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si_stack) else { + return; + }; + let stack_val = global.get_prop_at(pos); + if !stack_val.is_object() { + return; + } + let ctor = unsafe { &mut *stack_val.as_js_object_ptr() }; + let si_prototype = core.perm_interner().intern("prototype").0; + let Some(proto_pos) = core.shape_forge().lookup_position(ctor.shape_id(), si_prototype) else { + return; + }; + let new_proto = JsValue::from_js_object(session.builtin_world().disposable_stack_proto.as_ptr() as *mut JsObject); + ctor.set_prop_at(proto_pos, new_proto); + + let proto_ptr = session.builtin_world().disposable_stack_proto.as_ptr() as *mut JsObject; + write_proto_constructor(core, unsafe { &mut *proto_ptr }, stack_val); +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 82d2ce5..0ee7a6c 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -14,6 +14,8 @@ pub mod bind_boolean; pub mod bind_data_view; /// Date 构造器与原型的 native 方法绑定。 pub mod bind_date; +/// DisposableStack 构造器与原型的 native 方法绑定(含 dirty reset 同步)。 +pub mod bind_disposable_stack; /// Error 家族构造器与原型的 native 方法绑定(含各子类型构造器创建)。 pub mod bind_error; /// Function 构造器与原型的 native 方法绑定。 @@ -972,6 +974,7 @@ pub fn bind_global_builtin_slots(core: &Arc, session: &KernelSession bind_reflect_global(core, session, global); bind_iterator_global(core, session, global); + bind_disposable_stack::bind_disposable_stack(core, session, global); bind_stub_globals(core, session, global); bind_bigint::bind_bigint(core, session, global); bind_global_functions(core, session, global); @@ -991,10 +994,12 @@ pub fn rebind_dirty_builtins(core: &Arc, session: &mut KernelSession if dirty.map_or(true, |d| d.object) { bind_object::bind_object(core, session, global); - // object 家族重建连带重建 6 个迭代器原型(其链到新 Object.prototype), - // 须同步安装原型方法并让保留 global 上的 Iterator 指向新 %IteratorPrototype%。 + // object 家族重建连带重建 6 个迭代器原型与两资源栈原型(其链到新 + // Object.prototype),须同步安装原型方法并让保留的 global 构造器指向新原型。 bind_iterator_protos(core, session); sync_iterator_function_prototype(core, session, global); + bind_disposable_stack::bind_disposable_stack_protos(core, session); + bind_disposable_stack::sync_disposable_stack_ctor(core, session, global); } if dirty.map_or(true, |d| d.array) { bind_array::bind_array(core, session, global); @@ -1061,6 +1066,7 @@ pub fn init_kernel_builtins(core: &Arc, session: &mut KernelSession) let global_ptr = session.global_object().as_ptr() as *mut oxide_types::object::JsObject; let global = unsafe { &mut *global_ptr }; bind_iterator::bind_iterator(core, session, global); + bind_disposable_stack::bind_disposable_stack(core, session, global); bind_reflect::bind_reflect(core, session, global); bind_global::bind_global(core, session, global); bind_global_value(core, global, "globalThis", JsValue::from_js_object(global_ptr)); diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index ab7d116..7bc13ee 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -6,7 +6,7 @@ use oxide_types::value::JsValue; use rustc_hash::FxBuildHasher; use crate::vm::Vm; -use oxide_builtins::{array_buffer, data_view, map, regexp, set, typed_array}; +use oxide_builtins::{array_buffer, data_view, disposable_stack, map, regexp, set, typed_array}; impl Vm { pub(crate) fn is_session_escape_root_ptr(&self, target_ptr: *mut JsObject) -> bool { @@ -57,6 +57,11 @@ impl Vm { set::clone_set_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() { + // 资源栈状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。 + disposable_stack::clone_dispose_native_with_rewrite(src_ref, dst_ref, |value| { + self.promote_value_if_epoch_object(value, forwarding) + }); } else if src_ref.is_typed_array_obj() { typed_array::clone_typed_array_native_with_rewrite(src_ref, dst_ref, |value| { self.promote_value_if_epoch_object(value, forwarding) diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 45cdb6b..c54bda5 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -8,7 +8,7 @@ use oxide_types::value::JsValue; use rustc_hash::FxBuildHasher; use crate::vm::Vm; -use oxide_builtins::{array_buffer, data_view, map, regexp, set, typed_array}; +use oxide_builtins::{array_buffer, data_view, disposable_stack, map, regexp, set, typed_array}; /// session 级 mark-sweep GC 的状态与统计。 /// @@ -96,6 +96,9 @@ impl SessionGc { if obj.is_set() { edges.extend(set::set_native_edges(obj)); } + if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() { + edges.extend(disposable_stack::dispose_edges(obj)); + } if obj.is_typed_array_obj() { edges.extend(typed_array::typed_array_native_edges(obj)); } @@ -223,6 +226,13 @@ impl SessionGc { } } } + if obj.is_disposable_stack_obj() || obj.is_async_disposable_stack_obj() { + for value in disposable_stack::dispose_edges(obj) { + if value.is_string() { + Self::mark_string_live(live, value.as_string_ptr_mut()); + } + } + } if obj.is_generator_obj() { for ptr in crate::generator::generator_native_string_edges(obj) { Self::mark_string_live(live, ptr); @@ -363,6 +373,7 @@ impl SessionGc { freed_bytes += map::drop_map_native(obj); freed_bytes += set::drop_set_native(obj); + freed_bytes += disposable_stack::drop_dispose_native(obj); freed_bytes += array_buffer::drop_array_buffer_native(obj); freed_bytes += regexp::drop_regexp_native(obj); freed_bytes += typed_array::drop_typed_array_native(obj); @@ -422,6 +433,8 @@ impl SessionGc { map::clone_map_native_with_rewrite(old_ref, new_ref, |value| value); } else if old_ref.is_set() { set::clone_set_native_with_rewrite(old_ref, new_ref, |value| value); + } 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); } else if old_ref.is_array_buffer_obj() { array_buffer::clone_array_buffer_native(old_ref, new_ref); } else if old_ref.is_typed_array_obj() { @@ -464,6 +477,8 @@ impl SessionGc { map::rewrite_map_native(obj, |value| rewrite_forwarded_value(value, &forwarding)); } else if obj.is_set() { set::rewrite_set_native(obj, |value| rewrite_forwarded_value(value, &forwarding)); + } 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)); } else if obj.is_typed_array_obj() { typed_array::rewrite_typed_array_native(obj, |value| rewrite_forwarded_value(value, &forwarding)); } else if obj.is_data_view_obj() {