diff --git a/crates/oxide_cli/src/bench/leak_detect.rs b/crates/oxide_cli/src/bench/leak_detect.rs index 1eed48a1..08056def 100644 --- a/crates/oxide_cli/src/bench/leak_detect.rs +++ b/crates/oxide_cli/src/bench/leak_detect.rs @@ -231,8 +231,12 @@ pub fn run_mem_vm_creation_leak(kernel: &Arc) -> ExitCode { /// 路径的每轮内存增量(斜率 + 窗口总增量)。 /// /// # 注意事项 -/// 键名逐轮递增保证走新键写入路径(既有槽位写不 bump 世代、不脏家族); -/// 修复释放路径后期望走平,残留本体钉在页粒度噪声级。 +/// 键名逐轮递增保证走新键写入路径(既有槽位写不 bump 世代、不脏家族)。 +/// 跨轮继承锚点(登记表对象数 / global 属性槽数)采样自每轮 full_reset +/// 后:wrapper 复用与槽位原位更新生效时两者应持平,增长即泄漏签名。 +/// RSS 残差模型(post-91.1 裁定):object/function 家族脏重建每轮钉住 4 +/// 件本体(重指遗漏兜底,不进门)+ 逐轮唯一键在共享内核 shape/perm 缓存 +/// 的追加式增长(append-only 缓存,非 session 数据泄漏)。 pub fn run_mem_dirty_rebuild_leak(kernel: &Arc) -> ExitCode { const ROUNDS: usize = 3000; const SAMPLE_EVERY: usize = 10; @@ -242,6 +246,10 @@ pub fn run_mem_dirty_rebuild_leak(kernel: &Arc) -> ExitCode { let mut sampler = LeakSampler::new(20); let mut series: Vec<(usize, f64)> = Vec::new(); + // 跨轮继承锚点(登记表对象数 / global 属性槽数):wrapper 复用与槽位 + // 原位更新生效时两者应跨轮持平,增长即泄漏的直接签名(无页粒度噪音)。 + let mut first_anchor: Option<(usize, usize)> = None; + let mut last_anchor: (usize, usize) = (0, 0); for round in 0..ROUNDS { // 键名逐轮递增:新键写才 bump 原型世代,重建才有脏家族可换。 let source = format!( @@ -265,6 +273,12 @@ pub fn run_mem_dirty_rebuild_leak(kernel: &Arc) -> ExitCode { } vm.full_reset(); if round % SAMPLE_EVERY == 0 { + let registry = vm.session().builtin_world().leaked_object_count(); + let global_slots = unsafe { (*vm.session().global_object().as_ptr()).prop_vec_len() }; + if first_anchor.is_none() { + first_anchor = Some((registry, global_slots)); + } + last_anchor = (registry, global_slots); if let Some(kb) = read_vmrss_kb() { let v = kb as f64; series.push((round, v)); @@ -277,6 +291,18 @@ pub fn run_mem_dirty_rebuild_leak(kernel: &Arc) -> ExitCode { } } } + if let Some((r0, g0)) = first_anchor { + let (r1, g1) = last_anchor; + eprintln!( + "[dirty_rebuild] anchors: registry {} -> {} ({:+}) global_slots {} -> {} ({:+})", + r0, + r1, + r1 as i64 - r0 as i64, + g0, + g1, + g1 as i64 - g0 as i64 + ); + } report_series("dirty_rebuild", "round", &series) } diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 76160f25..9f56dc34 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -39,6 +39,17 @@ macro_rules! bind_methods_static { ); })* }; + ($target:expr, $sf:expr, $sh:expr, $world:expr, $label:expr, + $(($name:literal, $func:expr, $nargs:expr)),* $(,)?) => { + $({ + let _raw: *const () = $func as *const (); + // SAFETY: $func 是 NativeFn 函数项;强转并包装合法。 + let _func_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(_raw) }; + let _ = $crate::builtin::BuiltinWorld::bind_method_labeled_static( + $target, $sh, $sf, $name, _func_ptr, $nargs, $world, $label, + ); + })* + }; } /// Object 静态方法与原型方法的 native 函数指针集合,由 builtin 绑定层填充后交给 @@ -302,7 +313,38 @@ pub struct BuiltinWorld { /// 这些对象本体在堆上、不属任何 arena,`session` 收尾时按表统一释放 /// (属性区 + 本体);选择性重建换 world 时本表整体并入新 world /// (`inherit_leaked_objects`),仍由 session 收尾统一释放,不悬垂、不双放。 - leaked_objects: std::cell::RefCell>, + /// + /// 可复用 native 函数 wrapper 带复用键([`FnWrapperKey`]):选择性重建 + /// 重绑按键命中前轮旧 wrapper,迁移到重建 P 对象槽位,登记表跨轮不增长。 + leaked_objects: std::cell::RefCell>, +} + +/// native 函数 wrapper 的复用键:(目标家族,目标站点标签,属性槽位键,wrapper 名)。 +/// +/// 家族目标是 [`BuiltinWorld::all_p_fields`] 枚举的稳定下标(重建跨轮不变, +/// 1..=N),此时标签恒 0;非 P 目标(global 对象、Box 自建构造器、宿主对象、 +/// VM 内建原型)家族为 0,以绑定站点标签(站点名的 perm intern 键)区分 +/// 同名方法槽位——如 Generator/AsyncGenerator 原型同名的 next/return/throw。 +/// 选择性重建重绑按键查找前轮旧 wrapper 并迁移,避免每轮新建导致 +/// 登记表无界累积;同键重复登记意味着复用键设计缺陷(debug 断言守约)。 +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct FnWrapperKey { + family: u16, + label: u32, + slot: u32, + name: u32, +} + +impl FnWrapperKey { + pub const fn new(family: u16, label: u32, slot: u32, name: u32) -> Self { + Self { family, label, slot, name } + } +} + +/// 登记表条目:对象指针 + 可选复用键(`None` = 不可复用对象)。 +struct LeakedSlot { + ptr: *mut JsObject, + key: Option, } fn intern_label(string_forge: &PermInterner, label: &str) -> u32 { @@ -688,10 +730,65 @@ impl BuiltinWorld { JsValue::from_js_object(self.function_proto.as_ptr() as *mut JsObject) } - /// 登记一个绑定层经 `Box::into_raw` 持有的函数/宿主对象,供 - /// [`Self::teardown_heap_data`] 在 session 收尾时统一释放。 + /// 登记一个绑定层经 `Box::into_raw` 持有的函数/宿主对象(不可复用对象), + /// 供 [`Self::teardown_heap_data`] 在 session 收尾时统一释放;可复用 + /// native 函数 wrapper 走 [`Self::track_fn_wrapper`]。 pub fn track_leaked_object(&self, obj_ptr: *mut JsObject) { - self.leaked_objects.borrow_mut().push(obj_ptr); + self.leaked_objects.borrow_mut().push(LeakedSlot { ptr: obj_ptr, key: None }); + } + + /// 登记一个可复用 native 函数 wrapper(带复用键),随登记表在 session + /// 收尾统一释放。 + /// + /// # 注意事项 + /// 同键重复登记意味着复用键设计缺陷(同家族槽位对应两个不同 wrapper + /// 对象)——debug 断言立即失败。 + pub fn track_fn_wrapper(&self, obj_ptr: *mut JsObject, key: FnWrapperKey) { + debug_assert!( + !self.leaked_objects.borrow().iter().any(|s| s.key == Some(key)), + "同键 native 函数 wrapper 重复登记" + ); + self.leaked_objects + .borrow_mut() + .push(LeakedSlot { ptr: obj_ptr, key: Some(key) }); + } + + /// 查找复用键相同且 native 函数/参数个数匹配的既有 wrapper(选择性重建 + /// 重绑的复用入口)。 + /// + /// # 边界与前提 + /// 登记表指针 session 存活期内有效(full_reset 安全点无并发读者); + /// native 函数与参数个数一并校验,防绑定表漂移时误换旧实现。 + pub fn find_fn_wrapper( + &self, key: FnWrapperKey, native_fn_ptr: NativeFnPtr, arg_count: u8, + ) -> Option<*mut JsObject> { + self.leaked_objects + .borrow() + .iter() + .find(|s| { + s.key == Some(key) && { + // SAFETY: 登记表指针 session 存活期内有效。 + let obj = unsafe { &*s.ptr }; + obj.native_fn().map(|p| p.0) == Some(native_fn_ptr.0) && obj.native_arg_count() == arg_count + } + }) + .map(|s| s.ptr) + } + + /// wrapper 复用键的目标家族标签:目标对象是本 world 固定 P 字段时返回 + /// 其枚举下标 + 1(`all_p_fields` 顺序跨重建轮不变),非 P 目标返回 0。 + pub fn wrapper_family_of(&self, obj: *const JsObject) -> u16 { + for (i, p) in self.all_p_fields().iter().enumerate() { + if std::ptr::eq(p.as_ptr(), obj) { + return (i + 1) as u16; + } + } + 0 + } + + /// 登记表对象数(泄漏校准的跨轮继承采样锚点)。 + pub fn leaked_object_count(&self) -> usize { + self.leaked_objects.borrow().len() } /// 选择性重建时把旧 world 的登记表整体并入新 world(见 @@ -770,7 +867,8 @@ impl BuiltinWorld { } } } - for &ptr in self.leaked_objects.borrow().iter() { + for slot in self.leaked_objects.borrow().iter() { + let ptr = slot.ptr; // SAFETY: 登记表指针 session 存活期内有效,重指安全点无并发读者。 unsafe { repoint(&mut *ptr); @@ -788,8 +886,8 @@ impl BuiltinWorld { ); } } - for &ptr in self.leaked_objects.borrow().iter() { - let cur = unsafe { (*ptr).proto() }; + for slot in self.leaked_objects.borrow().iter() { + let cur = unsafe { (*slot.ptr).proto() }; debug_assert!( !cur.is_object() || !remap.contains_key(&cur.as_js_object_ptr()), "登记表 wrapper proto 槽不得残留被替换旧指针" @@ -933,7 +1031,8 @@ impl BuiltinWorld { /// session 收尾统一释放;被替换家族的旧 P 字段属性区在重建收尾 /// (`retire_replaced`)恰好释放一次,与本路径对象集不相交,不双放。 pub fn teardown_heap_data(&self) { - for ptr in self.leaked_objects.borrow_mut().drain(..) { + for slot in self.leaked_objects.borrow_mut().drain(..) { + let ptr = slot.ptr; if ptr.is_null() { continue; } @@ -1962,46 +2061,103 @@ impl BuiltinWorld { /// 按指定属性键安装方法 wrapper(键不要求字符串 intern,well-known symbol 键用此路径)。 /// /// `method_name` 只用于 wrapper 的 `name` 属性;`key` 是属性的实际存储键。 + /// 目标站点标签取 0:P 目标由家族下标区分,非 P 目标须经 + /// [`Self::bind_method_key_labeled_static`] 传站点标签。 #[expect(clippy::too_many_arguments)] pub fn bind_method_key_static( proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, key: u32, method_name: &str, native_fn_ptr: NativeFnPtr, arg_count: u8, world: &BuiltinWorld, ) -> Result<(), String> { - let wrapper_proto_val = world.fn_proto_val(); - let wrapper_proto_ptr = if wrapper_proto_val.is_object() { - wrapper_proto_val.as_js_object_ptr() - } else { - std::ptr::null_mut() + Self::bind_method_key_labeled_static( + proto, + shape_forge, + string_forge, + key, + method_name, + native_fn_ptr, + arg_count, + world, + 0, + ) + } + + /// [`Self::bind_method_static`] 的站点标签版本:属性键由方法名 intern 得出, + /// 供非 P 目标(VM 内建原型 Box 对象)的静态绑定宏使用。 + #[expect(clippy::too_many_arguments)] + pub fn bind_method_labeled_static( + proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str, + native_fn_ptr: NativeFnPtr, arg_count: u8, world: &BuiltinWorld, label: u32, + ) -> Result<(), String> { + let si = string_forge.intern(method_name).0; + Self::bind_method_key_labeled_static( + proto, + shape_forge, + string_forge, + si, + method_name, + native_fn_ptr, + arg_count, + world, + label, + ) + } + + /// 按指定属性键安装方法 wrapper,显式指定非 P 目标的绑定站点标签。 + /// + /// 同一 session 内多个非 P 目标(VM 内建原型 Box 对象等)可绑定同名方法槽 + /// (如 next/return/throw):站点标签(站点名 perm intern 键)使复用键跨 + /// 站点唯一,重绑时只迁移本站点的旧 wrapper。 + #[expect(clippy::too_many_arguments)] + pub fn bind_method_key_labeled_static( + proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, key: u32, method_name: &str, + native_fn_ptr: NativeFnPtr, arg_count: u8, world: &BuiltinWorld, label: u32, + ) -> Result<(), String> { + let si = string_forge.intern(method_name).0; + // 选择性重建复用:同键前轮旧 wrapper 迁移到新 proto 槽位——其 proto 槽 + // 已由重建收尾重指(或 Function 家族保留而不变),length/name 属性区 + // 随对象保留,不再新建对象、登记表跨轮不增长。 + let reuse_key = FnWrapperKey::new(world.wrapper_family_of(proto as *const JsObject), label, key, si); + let wrapper_ptr = match world.find_fn_wrapper(reuse_key, native_fn_ptr, arg_count) { + Some(ptr) => ptr, + None => { + let wrapper_proto_val = world.fn_proto_val(); + let wrapper_proto_ptr = if wrapper_proto_val.is_object() { + wrapper_proto_val.as_js_object_ptr() + } else { + std::ptr::null_mut() + }; + let mut wrapper = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + if !wrapper_proto_ptr.is_null() { + wrapper.set_proto(wrapper_proto_val).ok(); + } + wrapper.set_function(true); + // NativeFnPtr 不变量由调用方维护(见 bind_method / bind_method_static + // 的调用方,均使用函数项表达式)。 + wrapper.set_native_fn(Some(native_fn_ptr)); + wrapper.set_native_arg_count(arg_count); + // 设置 .length (ES spec: Function.length = formal parameter count, + // {[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}) + let si_length = string_forge.intern("length").0; + let length_shape = shape_forge.make_shape(wrapper.shape_id(), si_length); + wrapper.set_shape_id(length_shape); + wrapper.ensure_hash_props().push(JsValue::int(arg_count as i32)); + let length_pos = wrapper.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + wrapper.set_data_meta(length_pos, oxide_types::object::PropAttributes::new(false, false, true)); + // 设置 .name ({[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}) + let si_name = string_forge.intern("name").0; + let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name); + wrapper.set_shape_id(name_shape); + wrapper + .ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(si))); + let name_pos = wrapper.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + wrapper.set_data_meta(name_pos, oxide_types::object::PropAttributes::new(false, false, true)); + // 登记进 world 释放表:session 收尾时统一释放 wrapper 的属性区与本体。 + let wrapper_ptr = Box::into_raw(wrapper); + world.track_fn_wrapper(wrapper_ptr, reuse_key); + wrapper_ptr + } }; - let mut wrapper = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); - if !wrapper_proto_ptr.is_null() { - wrapper.set_proto(wrapper_proto_val).ok(); - } - wrapper.set_function(true); - // NativeFnPtr 不变量由调用方维护(见 bind_method / bind_method_static - // 的调用方,均使用函数项表达式)。 - wrapper.set_native_fn(Some(native_fn_ptr)); - wrapper.set_native_arg_count(arg_count); - // 设置 .length (ES spec: Function.length = formal parameter count, - // {[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}) - let si_length = string_forge.intern("length").0; - let length_shape = shape_forge.make_shape(wrapper.shape_id(), si_length); - wrapper.set_shape_id(length_shape); - wrapper.ensure_hash_props().push(JsValue::int(arg_count as i32)); - let length_pos = wrapper.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - wrapper.set_data_meta(length_pos, oxide_types::object::PropAttributes::new(false, false, true)); - // 设置 .name ({[[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true}) - let si_name = string_forge.intern("name").0; - let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name); - wrapper.set_shape_id(name_shape); - wrapper - .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(method_name).0))); - let name_pos = wrapper.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - wrapper.set_data_meta(name_pos, oxide_types::object::PropAttributes::new(false, false, true)); - // 登记进 world 释放表:session 收尾时统一释放 wrapper 的属性区与本体。 - let wrapper_ptr = Box::into_raw(wrapper); - world.track_leaked_object(wrapper_ptr); let wrapper_val = JsValue::from_js_object(wrapper_ptr); let new_shape = shape_forge.make_shape(proto.shape_id(), key); proto.set_shape_id(new_shape); @@ -2198,6 +2354,6 @@ mod tests { assert!(std::ptr::eq(unsafe { (*object_ctor).proto().as_js_object_ptr() }, new_fn_proto)); assert!(std::ptr::eq(unsafe { (*wrapper).proto().as_js_object_ptr() }, new_fn_proto)); // 登记表并入新 world:保留 wrapper 仍须由 session 收尾统一释放。 - assert!(session.builtin_world.leaked_objects.borrow().contains(&wrapper)); + assert!(session.builtin_world.leaked_objects.borrow().iter().any(|s| s.ptr == wrapper)); } } diff --git a/crates/oxide_vm/src/async_func.rs b/crates/oxide_vm/src/async_func.rs index ee7311f4..b0b05e4e 100644 --- a/crates/oxide_vm/src/async_func.rs +++ b/crates/oxide_vm/src/async_func.rs @@ -402,30 +402,41 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) { let sh = vm.kernel_core.shape_forge().as_ref(); let fn_proto_val = vm.session.builtin_world().fn_proto_val(); - // %AsyncFunction% 构造器:proto = Function.prototype,调用抛错(动态创建未实现)。 - let mut af_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - af_ctor.set_function(true); - af_ctor.set_native_arg_count(1); + // %AsyncFunction% 占位构造器:proto = Function.prototype,调用抛错(动态 + // 创建未实现)。选择性重建复用:前轮占位构造器按键迁移(prototype 槽在 + // 下方 P 槽换入后重指新原型),不再每轮新建对象。 + let af_ctor_label = sf.intern("AsyncFunctionCtor").0; + let af_reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, af_ctor_label, 0, 0); // SAFETY: async_function_stub 是 NativeFn 函数项。 - af_ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(async_function_stub as *const ()) })); + let af_stub_ptr = unsafe { NativeFnPtr::from_raw(async_function_stub as *const ()) }; + let (af_ctor_ptr, af_ctor_is_new) = match vm.session.builtin_world().find_fn_wrapper(af_reuse_key, af_stub_ptr, 1) { + Some(ptr) => (ptr, false), + None => { + let mut af_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + af_ctor.set_function(true); + af_ctor.set_native_arg_count(1); + af_ctor.set_native_fn(Some(af_stub_ptr)); + let name_si = sf.intern("name").0; + let ctor_shape = sh.make_shape(af_ctor.shape_id(), name_si); + af_ctor.set_shape_id(ctor_shape); + let npos = af_ctor.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncFunction").0))); + af_ctor.set_data_meta(npos, PropAttributes::new(false, false, true)); + let proto_si = sf.intern("prototype").0; + let ctor_shape2 = sh.make_shape(af_ctor.shape_id(), proto_si); + af_ctor.set_shape_id(ctor_shape2); + let af_ctor_ptr = Box::into_raw(af_ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + vm.session.builtin_world().track_fn_wrapper(af_ctor_ptr, af_reuse_key); + (af_ctor_ptr, true) + } + }; // %AsyncFunction.prototype%:proto = Function.prototype,constructor = %AsyncFunction%。 let mut af_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - let name_si = sf.intern("name").0; - let ctor_shape = sh.make_shape(af_ctor.shape_id(), name_si); - af_ctor.set_shape_id(ctor_shape); - let npos = af_ctor.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncFunction").0))); - af_ctor.set_data_meta(npos, PropAttributes::new(false, false, true)); - let proto_si = sf.intern("prototype").0; - let ctor_shape2 = sh.make_shape(af_ctor.shape_id(), proto_si); - af_ctor.set_shape_id(ctor_shape2); // af_proto.constructor = %AsyncFunction%(构造器登记进 world 释放表,与 builtin 方法 wrapper 同生命周期)。 let ctor_si = sf.intern("constructor").0; let pshape = sh.make_shape(af_proto.shape_id(), ctor_si); af_proto.set_shape_id(pshape); - // 登记进释放表:session 收尾统一释放构造器本体与属性区。 - let af_ctor_ptr = Box::into_raw(af_ctor); - vm.session.builtin_world().track_leaked_object(af_ctor_ptr); let cpos = af_proto.push_prop(JsValue::from_js_object(af_ctor_ptr)); af_proto.set_data_meta(cpos, PropAttributes::new(false, false, true)); // af_proto[Symbol.toStringTag] = "AsyncFunction"(数据属性,w/e/c = false/false/true)。 @@ -438,12 +449,18 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) { // proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再把 // 构造器 prototype 属性指向 P 槽实例,使其与动态异步函数使用的 [[Prototype]] // 同一对象,保证 `AsyncFunction.prototype === (async () => {}).__proto__`。 + // 复用构造器 prototype 槽原位改指新 P 原型(旧原型已随 P 换出释放)。 Vm::swap_intrinsic_proto(&mut vm.async_function_proto, *af_proto); - // SAFETY: af_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),对象已建满、本 Vm 独占。 + // SAFETY: af_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),本 Vm 独占。 unsafe { let ctor_mut = &mut *af_ctor_ptr; - let ppos = ctor_mut.push_prop(JsValue::from_js_object(vm.async_function_proto.as_mut_ptr())); - ctor_mut.set_data_meta(ppos, PropAttributes::new(false, false, false)); + let proto_val = JsValue::from_js_object(vm.async_function_proto.as_ptr() as *mut JsObject); + if af_ctor_is_new { + let ppos = ctor_mut.push_prop(proto_val); + ctor_mut.set_data_meta(ppos, PropAttributes::new(false, false, false)); + } else if let Some(pos) = sh.lookup_position(ctor_mut.shape_id(), sf.intern("prototype").0) { + ctor_mut.set_prop_at(pos, proto_val); + } } } diff --git a/crates/oxide_vm/src/async_generator.rs b/crates/oxide_vm/src/async_generator.rs index 9bd112cc..4201231d 100644 --- a/crates/oxide_vm/src/async_generator.rs +++ b/crates/oxide_vm/src/async_generator.rs @@ -738,11 +738,14 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { // %AsyncGeneratorPrototype%:proto = Object.prototype,方法 next/return/throw。 let mut ag_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto_val)); + // 站点标签:与 Generator 原型的同名方法槽(next/return/throw)区分复用键。 + let ag_label = sf.intern("AsyncGeneratorPrototype").0; oxide_kernel::bind_methods_static!( &mut ag_proto, sf, sh, world, + ag_label, ("next", async_generator_next as *const (), 1), ("return", async_generator_return as *const (), 1), ("throw", async_generator_throw as *const (), 1), @@ -755,7 +758,7 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { ag_proto.set_data_meta(tag_pos, PropAttributes::new(false, false, true)); // @@asyncIterator:返回自身(异步生成器是异步可迭代对象,供 for-await-of 消费)。 let aiter_key = oxide_types::private_key::make_well_known_symbol_key(8); - let _ = oxide_kernel::builtin::BuiltinWorld::bind_method_key_static( + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method_key_labeled_static( &mut ag_proto, sh, sf, @@ -764,30 +767,43 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { unsafe { oxide_types::object::NativeFnPtr::from_raw(async_generator_symbol_async_iterator as *const ()) }, 0, world, + ag_label, ); Vm::swap_intrinsic_proto(&mut vm.async_generator_proto, *ag_proto); - // %AsyncGeneratorFunction.prototype%:proto = Function.prototype,constructor = %AsyncGeneratorFunction%。 - let mut agf_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - agf_ctor.set_function(true); - agf_ctor.set_native_arg_count(1); - // %AsyncGeneratorFunction% 构造器:动态创建未实现,调用抛 TypeError。 - agf_ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(async_generator_function_stub as *const ()) })); + // %AsyncGeneratorFunction.prototype%:proto = Function.prototype, + // constructor = %AsyncGeneratorFunction%。占位构造器动态创建未实现, + // 调用抛 TypeError。选择性重建复用:前轮占位构造器按键迁移(prototype + // 槽在下方 P 槽换入后重指新原型),不再每轮新建对象。 + let agf_ctor_label = sf.intern("AsyncGeneratorFunctionCtor").0; + let agf_reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, agf_ctor_label, 0, 0); + // SAFETY: async_generator_function_stub 是 NativeFn 函数项。 + let agf_stub_ptr = unsafe { NativeFnPtr::from_raw(async_generator_function_stub as *const ()) }; + let (agf_ctor_ptr, agf_ctor_is_new) = match world.find_fn_wrapper(agf_reuse_key, agf_stub_ptr, 1) { + Some(ptr) => (ptr, false), + None => { + let mut agf_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + agf_ctor.set_function(true); + agf_ctor.set_native_arg_count(1); + agf_ctor.set_native_fn(Some(agf_stub_ptr)); + // prototype/name 属性(构造器形状 [prototype, name])。 + let proto_si = sf.intern("prototype").0; + let ctor_shape = sh.make_shape(EMPTY_SHAPE_ID, proto_si); + agf_ctor.set_shape_id(ctor_shape); + let name_si = sf.intern("name").0; + let name_shape = sh.make_shape(agf_ctor.shape_id(), name_si); + agf_ctor.set_shape_id(name_shape); + let agf_ctor_ptr = Box::into_raw(agf_ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + world.track_fn_wrapper(agf_ctor_ptr, agf_reuse_key); + (agf_ctor_ptr, true) + } + }; let mut agf_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - // prototype/name 属性(构造器形状 [prototype, name])。 - let proto_si = sf.intern("prototype").0; - let ctor_shape = sh.make_shape(agf_ctor.shape_id(), proto_si); - agf_ctor.set_shape_id(ctor_shape); - let name_si = sf.intern("name").0; - let name_shape = sh.make_shape(agf_ctor.shape_id(), name_si); - agf_ctor.set_shape_id(name_shape); // agf_proto.constructor = %AsyncGeneratorFunction%(构造器登记进 world 释放表,与 builtin 方法 wrapper 同生命周期)。 let ctor_si = sf.intern("constructor").0; let ctor2_shape = sh.make_shape(agf_proto.shape_id(), ctor_si); agf_proto.set_shape_id(ctor2_shape); - // 登记进释放表:session 收尾统一释放构造器本体与属性区。 - let agf_ctor_ptr = Box::into_raw(agf_ctor); - world.track_leaked_object(agf_ctor_ptr); let cpos = agf_proto.push_prop(JsValue::from_js_object(agf_ctor_ptr)); agf_proto.set_data_meta(cpos, PropAttributes::new(false, false, true)); // agf_proto.prototype = %AsyncGeneratorPrototype%。 @@ -805,15 +821,29 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) { // proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再写 // 构造器 prototype/name 属性(按模板序 prototype 在前),prototype 指向 P 槽实例, - // 使其与动态异步生成器函数使用的 [[Prototype]] 同一对象。 + // 使其与动态异步生成器函数使用的 [[Prototype]] 同一对象。复用构造器槽位已填充, + // prototype 原位改指新 P 原型(旧原型已随 P 换出释放)。 Vm::swap_intrinsic_proto(&mut vm.async_generator_function_proto, *agf_proto); - // SAFETY: agf_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),对象已建满、本 Vm 独占。 + // SAFETY: agf_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),本 Vm 独占。 unsafe { let ctor_mut = &mut *agf_ctor_ptr; - let ppos = ctor_mut.push_prop(JsValue::from_js_object(vm.async_generator_function_proto.as_mut_ptr())); - ctor_mut.set_data_meta(ppos, PropAttributes::new(false, false, false)); - let npos = ctor_mut.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncGeneratorFunction").0))); - ctor_mut.set_data_meta(npos, PropAttributes::new(false, false, true)); + let proto_val = JsValue::from_js_object(vm.async_generator_function_proto.as_ptr() as *mut JsObject); + let name_val = JsValue::perm_string(sf.string_ptr(sf.intern("AsyncGeneratorFunction").0)); + if agf_ctor_is_new { + let ppos = ctor_mut.push_prop(proto_val); + ctor_mut.set_data_meta(ppos, PropAttributes::new(false, false, false)); + let npos = ctor_mut.push_prop(name_val); + ctor_mut.set_data_meta(npos, PropAttributes::new(false, false, true)); + } else { + let si_prototype = sf.intern("prototype").0; + if let Some(pos) = sh.lookup_position(ctor_mut.shape_id(), si_prototype) { + ctor_mut.set_prop_at(pos, proto_val); + } + let si_name = sf.intern("name").0; + if let Some(pos) = sh.lookup_position(ctor_mut.shape_id(), si_name) { + ctor_mut.set_prop_at(pos, name_val); + } + } } } diff --git a/crates/oxide_vm/src/bindings/bind_async_disposable_stack.rs b/crates/oxide_vm/src/bindings/bind_async_disposable_stack.rs index 94fc52c7..6111d5ae 100644 --- a/crates/oxide_vm/src/bindings/bind_async_disposable_stack.rs +++ b/crates/oxide_vm/src/bindings/bind_async_disposable_stack.rs @@ -2,7 +2,7 @@ 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::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; use crate::async_disposable::dispose_async; @@ -91,35 +91,56 @@ pub fn bind_async_disposable_stack(core: &Arc, session: &KernelSessi 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::async_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.async_disposable_stack_proto.as_ptr() as *mut JsObject)); - ctor.ensure_hash_props() - .push(JsValue::perm_string(sf.string_ptr(sf.intern("AsyncDisposableStack").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)); - let ctor_ptr = Box::into_raw(ctor); - // 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。 - world.track_leaked_object(ctor_ptr); + let name_si = sf.intern("AsyncDisposableStack").0; + // 选择性重建复用:非 P 目标(家族 0)按键查找前轮旧构造器迁移,避免每轮 + // 新建导致登记表无界累积;槽键取 global 槽名,该命名空间内全局唯一。 + let ctor_fn = + oxide_builtins::disposable_stack::async_disposable_stack_constructor:: as *const (); + // SAFETY: ctor_fn 是转成 *const () 的 NativeFn 函数项指针。 + let ctor_fn_ptr = unsafe { NativeFnPtr::from_raw(ctor_fn) }; + let reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, 0, name_si, name_si); + let ctor_ptr = match world.find_fn_wrapper(reuse_key, ctor_fn_ptr, 0) { + Some(ptr) => { + // 迁移内引用:prototype 槽改指新 AsyncDisposableStack.prototype + // (旧原型已被重建替换释放)。 + let ctor = unsafe { &mut *ptr }; + let si_prototype = sf.intern("prototype").0; + if let Some(pos) = sh.lookup_position(ctor.shape_id(), si_prototype) { + ctor.set_prop_at( + pos, + JsValue::from_js_object(world.async_disposable_stack_proto.as_ptr() as *mut JsObject), + ); + } + ptr + } + None => { + // 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, ctor_fn, 0); + 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.async_disposable_stack_proto.as_ptr() as *mut JsObject)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); + 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)); + let ctor_ptr = Box::into_raw(ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + world.track_fn_wrapper(ctor_ptr, reuse_key); + ctor_ptr + } + }; JsValue::from_js_object(ctor_ptr) }; diff --git a/crates/oxide_vm/src/bindings/bind_disposable_stack.rs b/crates/oxide_vm/src/bindings/bind_disposable_stack.rs index 9803441c..85c4e82c 100644 --- a/crates/oxide_vm/src/bindings/bind_disposable_stack.rs +++ b/crates/oxide_vm/src/bindings/bind_disposable_stack.rs @@ -2,7 +2,7 @@ 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::object::{JsObject, NativeFnPtr, PropAttributes}; use oxide_types::value::JsValue; use crate::bindings::{ @@ -109,35 +109,55 @@ pub fn bind_disposable_stack(core: &Arc, session: &KernelSession, gl 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)); - let ctor_ptr = Box::into_raw(ctor); - // 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。 - world.track_leaked_object(ctor_ptr); + let name_si = sf.intern("DisposableStack").0; + // 选择性重建复用:非 P 目标(家族 0)按键查找前轮旧构造器迁移,避免每轮 + // 新建导致登记表无界累积;槽键取 global 槽名,该命名空间内全局唯一。 + let ctor_fn = oxide_builtins::disposable_stack::disposable_stack_constructor:: as *const (); + // SAFETY: ctor_fn 是转成 *const () 的 NativeFn 函数项指针。 + let ctor_fn_ptr = unsafe { NativeFnPtr::from_raw(ctor_fn) }; + let reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, 0, name_si, name_si); + let ctor_ptr = match world.find_fn_wrapper(reuse_key, ctor_fn_ptr, 0) { + Some(ptr) => { + // 迁移内引用:prototype 槽改指新 DisposableStack.prototype + // (旧原型已被重建替换释放)。 + let ctor = unsafe { &mut *ptr }; + let si_prototype = sf.intern("prototype").0; + if let Some(pos) = sh.lookup_position(ctor.shape_id(), si_prototype) { + ctor.set_prop_at( + pos, + JsValue::from_js_object(world.disposable_stack_proto.as_ptr() as *mut JsObject), + ); + } + ptr + } + None => { + // 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, ctor_fn, 0); + 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(name_si))); + 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)); + let ctor_ptr = Box::into_raw(ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + world.track_fn_wrapper(ctor_ptr, reuse_key); + ctor_ptr + } + }; JsValue::from_js_object(ctor_ptr) }; diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 08b09bed..9177e1dd 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -12,55 +12,86 @@ fn bind_error_subtype_constructor( ) { let sf = core.perm_interner().as_ref(); let sh = core.shape_forge().as_ref(); - // 子类型构造器 [[Prototype]] 指向 Error 构造器(规范:NativeError 构造器继承 Error - // 构造器,而非直接继承 Function.prototype;instanceof 走 @@hasInstance 不受影响)。 - let error_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject; - let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr))); - ctor.set_function(true); - // Error 子类型须标记可构造:new TypeError(...) 走 NEW_EXPRESSION 校验。 - ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR; - // SAFETY: ctor_fn 是调用方转成 *const () 的 NativeFn 函数项指针。 - ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(ctor_fn) })); - ctor.set_native_arg_count(arg_count); - - let si_prototype = sf.intern("prototype").0; - let si_name = sf.intern("name").0; - let si_length = sf.intern("length").0; + let world = session.builtin_world(); let name_si = sf.intern(name).0; + // SAFETY: ctor_fn 是调用方转成 *const () 的 NativeFn 函数项指针。 + let ctor_fn_ptr = unsafe { NativeFnPtr::from_raw(ctor_fn) }; + // 选择性重建复用:非 P 目标(家族 0)按键查找前轮旧构造器迁移,避免每轮 + // 新建导致登记表无界累积;槽键取 global 槽名,该命名空间内全局唯一。 + let reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, 0, name_si, name_si); + let si_prototype = sf.intern("prototype").0; + let ctor_ptr = match world.find_fn_wrapper(reuse_key, ctor_fn_ptr, arg_count) { + Some(ptr) => { + // 迁移内引用:prototype 槽改指新子类型原型(旧原型已被重建替换释放)。 + let ctor = unsafe { &mut *ptr }; + if let Some(pos) = sh.lookup_position(ctor.shape_id(), si_prototype) { + ctor.set_prop_at(pos, JsValue::from_js_object(proto_ptr)); + } + ptr + } + None => { + // 子类型构造器 [[Prototype]] 指向 Error 构造器(规范:NativeError + // 构造器继承 Error 构造器,而非直接继承 Function.prototype;instanceof + // 走 @@hasInstance 不受影响)。 + let error_ctor_ptr = world.error_constructor.as_ptr() as *mut JsObject; + let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr))); + ctor.set_function(true); + // Error 子类型须标记可构造:new TypeError(...) 走 NEW_EXPRESSION 校验。 + ctor.type_tag = JsObject::OBJ_TYPE_CONSTRUCTOR; + ctor.set_native_fn(Some(ctor_fn_ptr)); + ctor.set_native_arg_count(arg_count); - 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(proto_ptr)); - ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); - ctor.ensure_hash_props().push(JsValue::int(arg_count as i32)); - // 构造器 prototype 槽按规范 { writable:false, enumerable:false, configurable:false }。 - ctor.set_data_meta(0u32, PropAttributes::new(false, false, false)); - // 构造器 name 槽按规范 { writable:false, enumerable:false, configurable:true }。 - ctor.set_data_meta(1u32, PropAttributes::new(false, false, true)); - // 构造器 length 按规范为不可写不可枚举(Function.length 属性描述符约定)。 - ctor.set_data_meta(2u32, PropAttributes::new(false, false, true)); + let si_name = sf.intern("name").0; + let si_length = sf.intern("length").0; - let ctor_ptr = Box::into_raw(ctor); - // 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。 - session.builtin_world().track_leaked_object(ctor_ptr); + 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(proto_ptr)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); + ctor.ensure_hash_props().push(JsValue::int(arg_count as i32)); + // 构造器 prototype 槽按规范 { writable:false, enumerable:false, configurable:false }。 + ctor.set_data_meta(0u32, PropAttributes::new(false, false, false)); + // 构造器 name 槽按规范 { writable:false, enumerable:false, configurable:true }。 + ctor.set_data_meta(1u32, PropAttributes::new(false, false, true)); + // 构造器 length 按规范为不可写不可枚举(Function.length 属性描述符约定)。 + ctor.set_data_meta(2u32, PropAttributes::new(false, false, true)); + let ctor_ptr = Box::into_raw(ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + world.track_fn_wrapper(ctor_ptr, reuse_key); + ctor_ptr + } + }; + let ctor_val = JsValue::from_js_object(ctor_ptr); + + // 原型上的 constructor 既有槽原位更新,无槽时开新槽(重建原型无既有槽); + // 非枚举数据属性(与 Error.prototype.constructor 一致)。 let proto = unsafe { &mut *proto_ptr }; - let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0); - proto.set_shape_id(proto_ctor_shape); - proto.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr)); - // 原型上的 constructor 按规范为非枚举数据属性(与 Error.prototype.constructor 一致)。 - let ctor_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - proto.set_data_meta(ctor_pos, PropAttributes::new(true, false, true)); + let si_constructor = sf.intern("constructor").0; + if let Some(pos) = sh.lookup_position(proto.shape_id(), si_constructor) { + proto.set_prop_at(pos, ctor_val); + } else { + let proto_ctor_shape = sh.make_shape(proto.shape_id(), si_constructor); + proto.set_shape_id(proto_ctor_shape); + proto.ensure_hash_props().push(ctor_val); + let ctor_pos = proto.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + proto.set_data_meta(ctor_pos, PropAttributes::new(true, false, true)); + } - let global_shape = sh.make_shape(global.shape_id(), name_si); - global.set_shape_id(global_shape); - global.ensure_hash_props().push(JsValue::from_js_object(ctor_ptr)); - // 全局子类型构造器槽位非枚举(规范全局构造器描述符约定)。 - let global_pos = global.prop_vec_len().saturating_sub(1) as u32; - global.set_data_meta(global_pos, PropAttributes::new(true, false, true)); - global.bump_generation(); + // 全局子类型构造器槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), + // 无槽时开新槽;描述符非枚举(规范全局构造器描述符约定)。 + if let Some(pos) = sh.lookup_position(global.shape_id(), name_si) { + global.set_prop_at(pos, ctor_val); + } else { + let global_shape = sh.make_shape(global.shape_id(), name_si); + global.set_shape_id(global_shape); + global.ensure_hash_props().push(ctor_val); + let global_pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(global_pos, PropAttributes::new(true, false, true)); + global.bump_generation(); + } } /// 把 Error 及各子类型(TypeError/ReferenceError/...)构造器与原型方法绑定到 global。 @@ -83,15 +114,20 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut core.shape_forge().as_ref(), ); + // 全局 Error 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), + // 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。 let si_err = core.perm_interner().intern("Error").0; - let err_shape = core.shape_forge().make_shape(global.shape_id(), si_err); let err_val = JsValue::from_js_object(session.builtin_world().error_constructor.as_ptr() as *mut JsObject); - global.set_shape_id(err_shape); - global.ensure_hash_props().push(err_val); - // 全局 Error 构造器槽位非枚举(规范 { writable:true, enumerable:false, configurable:true })。 - let err_pos = global.prop_vec_len().saturating_sub(1) as u32; - global.set_data_meta(err_pos, PropAttributes::new(true, false, true)); - global.bump_generation(); + if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si_err) { + global.set_prop_at(pos, err_val); + } else { + let err_shape = core.shape_forge().make_shape(global.shape_id(), si_err); + global.set_shape_id(err_shape); + global.ensure_hash_props().push(err_val); + let err_pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(err_pos, PropAttributes::new(true, false, true)); + global.bump_generation(); + } bind_error_subtype_constructor( core, diff --git a/crates/oxide_vm/src/bindings/bind_math.rs b/crates/oxide_vm/src/bindings/bind_math.rs index 78f397a2..3c838093 100644 --- a/crates/oxide_vm/src/bindings/bind_math.rs +++ b/crates/oxide_vm/src/bindings/bind_math.rs @@ -77,13 +77,18 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J math.set_data_meta(pos, PropAttributes::new(false, false, false)); } + // 全局 Math 槽位既有槽原位更新(旧对象指针不得滞留在属性 vec),无槽时 + // 开新槽;描述符非枚举(规范全局命名空间对象属性均 enumerable:false)。 let si_m = core.perm_interner().intern("Math").0; - let m_shape = core.shape_forge().make_shape(global.shape_id(), si_m); let m_val = JsValue::from_js_object(session.builtin_world().math_object.as_ptr() as *mut JsObject); - global.set_shape_id(m_shape); - global.ensure_hash_props().push(m_val); - // 全局 Math 槽位非枚举(规范全局命名空间对象属性均 enumerable:false)。 - let pos = global.prop_vec_len().saturating_sub(1) as u32; - global.set_data_meta(pos, PropAttributes::new(true, false, true)); - global.bump_generation(); + if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si_m) { + global.set_prop_at(pos, m_val); + } else { + let m_shape = core.shape_forge().make_shape(global.shape_id(), si_m); + global.set_shape_id(m_shape); + global.ensure_hash_props().push(m_val); + let pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(pos, PropAttributes::new(true, false, true)); + global.bump_generation(); + } } diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index dc22d7eb..6c556d90 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -73,13 +73,18 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut 0, ); + // 全局 String 槽位既有槽原位更新(旧家族构造器指针不得滞留在属性 vec), + // 无槽时开新槽;描述符非枚举(规范 { writable:true, enumerable:false, configurable:true })。 let si_str = core.perm_interner().intern("String").0; - let str_shape = core.shape_forge().make_shape(global.shape_id(), si_str); let str_val = JsValue::from_js_object(session.builtin_world().string_constructor.as_ptr() as *mut JsObject); - global.set_shape_id(str_shape); - global.ensure_hash_props().push(str_val); - // 全局构造器槽位非枚举(规范 { writable:true, enumerable:false, configurable:true })。 - let pos = global.prop_vec_len().saturating_sub(1) as u32; - global.set_data_meta(pos, PropAttributes::new(true, false, true)); - global.bump_generation(); + if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si_str) { + global.set_prop_at(pos, str_val); + } else { + let str_shape = core.shape_forge().make_shape(global.shape_id(), si_str); + global.set_shape_id(str_shape); + global.ensure_hash_props().push(str_val); + let pos = global.prop_vec_len().saturating_sub(1) as u32; + global.set_data_meta(pos, PropAttributes::new(true, false, true)); + global.bump_generation(); + } } diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index ce1b28de..68d755fd 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -71,19 +71,26 @@ macro_rules! bind_constructor { }}; ($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $ctor_fn:path, $nargs:literal, hash: $hash:literal) => {{ let si = $core.perm_interner().intern($name).0; - let shape = $core.shape_forge().make_shape($global.shape_id(), si); let val = $crate::JsValue::from_js_object($ctor_ptr); - $global.set_shape_id(shape); - if $hash { - $global.ensure_hash_props().push(val); - $global.bump_generation(); + if let Some(pos) = $core.shape_forge().lookup_position($global.shape_id(), si) { + // 既有槽(重建后保留的 global):原位更新槽值,不追加新槽——旧家族 + // 构造器指针不得滞留属性 vec,shape 链不得继续增长。 + $global.set_prop_at(pos, val); } else { - $global.push_prop(val); + let shape = $core.shape_forge().make_shape($global.shape_id(), si); + $global.set_shape_id(shape); + if $hash { + $global.ensure_hash_props().push(val); + } else { + $global.push_prop(val); + } + // 全局构造器槽位:规范描述符 { writable:true, enumerable:false, + // configurable:true },不设 meta 时默认全枚举会泄漏进 + // Object.keys(globalThis) / for-in。 + let global_pos = $global.prop_vec_len().saturating_sub(1) as u32; + $global.set_data_meta(global_pos, oxide_types::object::PropAttributes::new(true, false, true)); + $global.bump_generation(); } - // 全局构造器槽位:规范描述符 { writable:true, enumerable:false, configurable:true }, - // 不设 meta 时默认全枚举会泄漏进 Object.keys(globalThis) / for-in。 - let global_pos = $global.prop_vec_len().saturating_sub(1) as u32; - $global.set_data_meta(global_pos, oxide_types::object::PropAttributes::new(true, false, true)); let ctor = unsafe { &mut *$ctor_ptr }; let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (); ctor.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(ptr) })); @@ -137,36 +144,48 @@ pub(crate) fn bind_accessor_getter( let shape_forge = core.shape_forge().as_ref(); let string_forge = core.perm_interner().as_ref(); let world = session.builtin_world(); - let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); - - let getter_name = format!("get {name}"); - let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - getter.set_function(true); - // SAFETY: getter_fn 是转成 *const () 的 NativeFn 函数项指针。 - getter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) })); - getter.set_native_arg_count(0); - - let si_name = string_forge.intern("name").0; - let name_shape = shape_forge.make_shape(getter.shape_id(), si_name); - getter.set_shape_id(name_shape); - getter - .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(&getter_name).0))); - let name_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - getter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); - - let si_length = string_forge.intern("length").0; - let length_shape = shape_forge.make_shape(getter.shape_id(), si_length); - getter.set_shape_id(length_shape); - getter.ensure_hash_props().push(JsValue::int(0)); - let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - getter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); - - let getter_ptr = Box::into_raw(getter); - world.track_leaked_object(getter_ptr); - let getter_val = JsValue::from_js_object(getter_ptr); let si = string_forge.intern(name).0; + let getter_name = format!("get {name}"); + let si_label = string_forge.intern(&getter_name).0; + // SAFETY: getter_fn 是转成 *const () 的 NativeFn 函数项指针。 + let getter_fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) }; + // 选择性重建复用:同家族同槽旧 getter 迁移到新 proto 的访问器槽(proto + // 槽已由重建收尾重指),不再新建对象。 + let reuse_key = + oxide_kernel::builtin::FnWrapperKey::new(world.wrapper_family_of(proto as *const JsObject), 0, si, si_label); + let getter_ptr = match world.find_fn_wrapper(reuse_key, getter_fn_ptr, 0) { + Some(ptr) => ptr, + None => { + let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); + let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + getter.set_function(true); + getter.set_native_fn(Some(getter_fn_ptr)); + getter.set_native_arg_count(0); + + let si_name = string_forge.intern("name").0; + let name_shape = shape_forge.make_shape(getter.shape_id(), si_name); + getter.set_shape_id(name_shape); + getter + .ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(si_label))); + let name_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + getter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + + let si_length = string_forge.intern("length").0; + let length_shape = shape_forge.make_shape(getter.shape_id(), si_length); + getter.set_shape_id(length_shape); + getter.ensure_hash_props().push(JsValue::int(0)); + let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + getter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + + let getter_ptr = Box::into_raw(getter); + world.track_fn_wrapper(getter_ptr, reuse_key); + getter_ptr + } + }; + let getter_val = JsValue::from_js_object(getter_ptr); + let new_shape = shape_forge.make_shape(proto.shape_id(), si); proto.set_shape_id(new_shape); let pos = proto.push_prop(JsValue::undefined()); @@ -193,51 +212,50 @@ pub(crate) fn bind_accessor_getset( let shape_forge = core.shape_forge().as_ref(); let string_forge = core.perm_interner().as_ref(); let world = session.builtin_world(); - let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); - - let mut getter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - getter.set_function(true); - // SAFETY: getter_fn 是转成 *const () 的 NativeFn 函数项指针。 - getter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) })); - getter.set_native_arg_count(0); - // 给函数对象开 name/length 槽位(描述符均 { writable:false, enumerable:false, configurable:true })。 - let si_name = string_forge.intern("name").0; - let name_shape = shape_forge.make_shape(getter.shape_id(), si_name); - getter.set_shape_id(name_shape); - getter - .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(getter_name).0))); - let name_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - getter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + let family = world.wrapper_family_of(proto as *const JsObject); let si_length = string_forge.intern("length").0; - let length_shape = shape_forge.make_shape(getter.shape_id(), si_length); - getter.set_shape_id(length_shape); - getter.ensure_hash_props().push(JsValue::int(0)); - let length_pos = getter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - getter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); - let getter_ptr = Box::into_raw(getter); - world.track_leaked_object(getter_ptr); - let getter_val = JsValue::from_js_object(getter_ptr); + let si_name = string_forge.intern("name").0; - let mut setter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - setter.set_function(true); - // SAFETY: setter_fn 是转成 *const () 的 NativeFn 函数项指针。 - setter.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(setter_fn) })); - setter.set_native_arg_count(1); - let name_shape = shape_forge.make_shape(setter.shape_id(), si_name); - setter.set_shape_id(name_shape); - setter - .ensure_hash_props() - .push(JsValue::perm_string(string_forge.string_ptr(string_forge.intern(setter_name).0))); - let name_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - setter.set_data_meta(name_pos, PropAttributes::new(false, false, true)); - let length_shape = shape_forge.make_shape(setter.shape_id(), si_length); - setter.set_shape_id(length_shape); - setter.ensure_hash_props().push(JsValue::int(1)); - let length_pos = setter.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); - setter.set_data_meta(length_pos, PropAttributes::new(false, false, true)); - let setter_ptr = Box::into_raw(setter); - world.track_leaked_object(setter_ptr); + // 选择性重建复用:同家族同槽旧 getter/setter 迁移到新 proto 的访问器槽 + // (proto 槽已由重建收尾重指),不再新建对象。 + let build = |si_label: u32, + fn_ptr_native: oxide_types::object::NativeFnPtr, + arg_count: u8, + length_val: i32| + -> *mut JsObject { + let reuse_key = oxide_kernel::builtin::FnWrapperKey::new(family, 0, key, si_label); + if let Some(ptr) = world.find_fn_wrapper(reuse_key, fn_ptr_native, arg_count) { + return ptr; + } + let fn_proto_val = JsValue::from_js_object(world.function_proto.as_ptr() as *mut JsObject); + let mut func = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + func.set_function(true); + func.set_native_fn(Some(fn_ptr_native)); + func.set_native_arg_count(arg_count); + // 给函数对象开 name/length 槽位(描述符均 { writable:false, enumerable:false, configurable:true })。 + let name_shape = shape_forge.make_shape(func.shape_id(), si_name); + func.set_shape_id(name_shape); + func.ensure_hash_props() + .push(JsValue::perm_string(string_forge.string_ptr(si_label))); + let name_pos = func.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + func.set_data_meta(name_pos, PropAttributes::new(false, false, true)); + let length_shape = shape_forge.make_shape(func.shape_id(), si_length); + func.set_shape_id(length_shape); + func.ensure_hash_props().push(JsValue::int(length_val)); + let length_pos = func.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + func.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + let func_ptr = Box::into_raw(func); + world.track_fn_wrapper(func_ptr, reuse_key); + func_ptr + }; + // SAFETY: getter_fn / setter_fn 是转成 *const () 的 NativeFn 函数项指针。 + let getter_fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(getter_fn) }; + let setter_fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(setter_fn) }; + let si_getter = string_forge.intern(getter_name).0; + let si_setter = string_forge.intern(setter_name).0; + let getter_ptr = build(si_getter, getter_fn_ptr, 0, 0); + let setter_ptr = build(si_setter, setter_fn_ptr, 1, 1); + let getter_val = JsValue::from_js_object(getter_ptr); let setter_val = JsValue::from_js_object(setter_ptr); // 访问器属性槽:enumerable=false、configurable=true(get/set 函数对象已持有)。 @@ -573,13 +591,20 @@ fn sync_iterator_function_prototype(core: &Arc, session: &KernelSess iterator.set_prop_at(proto_pos, new_proto); } +/// 在目标对象(global 或命名空间对象)上绑定一个具名槽:既有槽(选择性 +/// 重建后保留的目标)原位更新槽值,无槽时开新槽。内置全局值统一非枚举: +/// 描述符 { writable:true, enumerable:false, configurable:true },与构造器/ +/// 命名空间对象(Math/JSON/Reflect/Temporal 等)规范一致。 pub(crate) fn bind_global_value(core: &Arc, global: &mut JsObject, name: &str, value: JsValue) { let si = core.perm_interner().intern(name).0; + if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), si) { + // 原位更新:旧对象指针不得滞留在属性 vec,shape 链不得继续增长。 + global.set_prop_at(pos, value); + return; + } let shape = core.shape_forge().make_shape(global.shape_id(), si); global.set_shape_id(shape); global.ensure_hash_props().push(value); - // 内置全局值统一非枚举:描述符 { writable:true, enumerable:false, configurable:true }, - // 与构造器/命名空间对象(Math/JSON/Reflect/Temporal 等)规范一致。 let pos = global.prop_vec_len().saturating_sub(1) as u32; global.set_data_meta(pos, PropAttributes::new(true, false, true)); global.bump_generation(); @@ -604,28 +629,46 @@ fn bind_error_subtype_global( // fallback 自建路径与 bind_error 的 Box 自建路径保持同一形态:[[Prototype]] 指向 // Error 构造器(NativeError 构造器继承 Error 构造器),prototype/name 描述符按规范。 - let error_ctor_ptr = session.builtin_world().error_constructor.as_ptr() as *mut JsObject; - let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr))); - ctor.set_function(true); - configure_native_constructor(&mut ctor, ctor_fn, arg_count); - + // 家族重建后新原型无既有槽:按复用键查找前轮旧构造器迁移,避免每轮新建。 + let world = session.builtin_world(); 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 name_si = sf.intern(name).0; - let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype); - let ctor_shape2 = sh.make_shape(ctor_shape1, si_name); - ctor.set_shape_id(ctor_shape2); - ctor.ensure_hash_props() - .push(JsValue::from_js_object(proto.as_ptr() as *mut JsObject)); - ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); - // prototype/name 描述符与 Box 自建路径一致:{f,f,f} / {f,f,t}。 - ctor.set_data_meta(0u32, PropAttributes::new(false, false, false)); - ctor.set_data_meta(1u32, PropAttributes::new(false, false, true)); + // SAFETY: ctor_fn 是转成 *const () 的 NativeFn 函数项指针。 + let ctor_fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(ctor_fn) }; + let reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, 0, name_si, name_si); + let ctor_ptr = match world.find_fn_wrapper(reuse_key, ctor_fn_ptr, arg_count) { + Some(ptr) => { + // 迁移内引用:prototype 槽改指新子类型原型(旧原型已被重建替换)。 + let ctor = unsafe { &mut *ptr }; + if let Some(pos) = sh.lookup_position(ctor.shape_id(), si_prototype) { + ctor.set_prop_at(pos, JsValue::from_js_object(proto.as_ptr() as *mut JsObject)); + } + ptr + } + None => { + let error_ctor_ptr = world.error_constructor.as_ptr() as *mut JsObject; + let mut ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_ctor_ptr))); + ctor.set_function(true); + configure_native_constructor(&mut ctor, ctor_fn, arg_count); - let ctor_ptr = Box::into_raw(ctor); - session.builtin_world().track_leaked_object(ctor_ptr); + let si_name = sf.intern("name").0; + let ctor_shape1 = sh.make_shape(EMPTY_SHAPE_ID, si_prototype); + let ctor_shape2 = sh.make_shape(ctor_shape1, si_name); + ctor.set_shape_id(ctor_shape2); + ctor.ensure_hash_props() + .push(JsValue::from_js_object(proto.as_ptr() as *mut JsObject)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); + // prototype/name 描述符与 Box 自建路径一致:{f,f,f} / {f,f,t}。 + ctor.set_data_meta(0u32, PropAttributes::new(false, false, false)); + ctor.set_data_meta(1u32, PropAttributes::new(false, false, true)); + + let ctor_ptr = Box::into_raw(ctor); + world.track_fn_wrapper(ctor_ptr, reuse_key); + ctor_ptr + } + }; bind_existing_global(core, global, name, JsValue::from_js_object(ctor_ptr)); } diff --git a/crates/oxide_vm/src/generator.rs b/crates/oxide_vm/src/generator.rs index 212c8ca7..ec537d94 100644 --- a/crates/oxide_vm/src/generator.rs +++ b/crates/oxide_vm/src/generator.rs @@ -733,11 +733,14 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { let iterator_proto_val = JsValue::from_js_object(vm.session.builtin_world().iterator_proto.as_ptr() as *mut JsObject); let mut gen_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, iterator_proto_val)); + // 站点标签:与 AsyncGenerator 原型的同名方法槽(next/return/throw)区分复用键。 + let gen_label = sf.intern("GeneratorPrototype").0; oxide_kernel::bind_methods_static!( &mut gen_proto, sf, sh, world, + gen_label, ("next", generator_next as *const (), 1), ("return", generator_return as *const (), 1), ("throw", generator_throw as *const (), 1), @@ -750,7 +753,7 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { gen_proto.set_data_meta(tag_pos, oxide_types::object::PropAttributes::new(false, false, true)); // @@iterator:返回自身(生成器是可迭代对象)。 let iter_key = oxide_types::private_key::make_well_known_symbol_key(0); - let _ = oxide_kernel::builtin::BuiltinWorld::bind_method_key_static( + let _ = oxide_kernel::builtin::BuiltinWorld::bind_method_key_labeled_static( &mut gen_proto, sh, sf, @@ -759,30 +762,43 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { unsafe { oxide_types::object::NativeFnPtr::from_raw(generator_symbol_iterator as *const ()) }, 0, world, + gen_label, ); Vm::swap_intrinsic_proto(&mut vm.generator_proto, *gen_proto); // %GeneratorFunction.prototype%:proto = Function.prototype,constructor = %GeneratorFunction%。 - let mut gf_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - gf_ctor.set_function(true); - gf_ctor.set_native_arg_count(1); let mut gf_proto = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); - // %GeneratorFunction% 构造器:动态生成器函数创建未实现,调用抛 TypeError。 - gf_ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(generator_function_stub as *const ()) })); - // prototype/name 属性(构造器形状 [prototype, name])。 - let proto_si = sf.intern("prototype").0; - let ctor_shape = sh.make_shape(gf_ctor.shape_id(), proto_si); - gf_ctor.set_shape_id(ctor_shape); - let name_si = sf.intern("name").0; - let name_shape = sh.make_shape(gf_ctor.shape_id(), name_si); - gf_ctor.set_shape_id(name_shape); + // %GeneratorFunction% 占位构造器:动态生成器函数创建未实现,调用抛 + // TypeError。选择性重建复用:前轮占位构造器按键迁移(prototype 槽在 + // 下方 P 槽换入后重指新原型),不再每轮新建对象。 + let gf_ctor_label = sf.intern("GeneratorFunctionCtor").0; + let gf_reuse_key = oxide_kernel::builtin::FnWrapperKey::new(0, gf_ctor_label, 0, 0); + // SAFETY: generator_function_stub 是 NativeFn 函数项。 + let gf_stub_ptr = unsafe { NativeFnPtr::from_raw(generator_function_stub as *const ()) }; + let (gf_ctor_ptr, gf_ctor_is_new) = match world.find_fn_wrapper(gf_reuse_key, gf_stub_ptr, 1) { + Some(ptr) => (ptr, false), + None => { + let mut gf_ctor = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val)); + gf_ctor.set_function(true); + gf_ctor.set_native_arg_count(1); + gf_ctor.set_native_fn(Some(gf_stub_ptr)); + // prototype/name 属性(构造器形状 [prototype, name])。 + let proto_si = sf.intern("prototype").0; + let ctor_shape = sh.make_shape(EMPTY_SHAPE_ID, proto_si); + gf_ctor.set_shape_id(ctor_shape); + let name_si = sf.intern("name").0; + let name_shape = sh.make_shape(gf_ctor.shape_id(), name_si); + gf_ctor.set_shape_id(name_shape); + let gf_ctor_ptr = Box::into_raw(gf_ctor); + // 登记进 world 释放表(带复用键):session 收尾统一释放构造器本体与属性区。 + world.track_fn_wrapper(gf_ctor_ptr, gf_reuse_key); + (gf_ctor_ptr, true) + } + }; // gf_proto.constructor = gf_ctor(构造器登记进 world 释放表,与 builtin 方法 wrapper 同生命周期)。 let ctor_si = sf.intern("constructor").0; let ctor2_shape = sh.make_shape(gf_proto.shape_id(), ctor_si); gf_proto.set_shape_id(ctor2_shape); - // 登记进释放表:session 收尾统一释放构造器本体与属性区。 - let gf_ctor_ptr = Box::into_raw(gf_ctor); - world.track_leaked_object(gf_ctor_ptr); let cpos = gf_proto.push_prop(JsValue::from_js_object(gf_ctor_ptr)); gf_proto.set_data_meta(cpos, oxide_types::object::PropAttributes::new(false, false, true)); // gf_proto.prototype = %GeneratorPrototype%(默认原型,default-proto 测试读取)。 @@ -800,15 +816,29 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) { // proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再写 // 构造器 prototype/name 属性(按模板序 prototype 在前),prototype 指向 P 槽实例, - // 使其与动态生成器函数使用的 [[Prototype]] 同一对象。 + // 使其与动态生成器函数使用的 [[Prototype]] 同一对象。复用构造器槽位已填充, + // prototype 原位改指新 P 原型(旧原型已随 P 换出释放)。 Vm::swap_intrinsic_proto(&mut vm.generator_function_proto, *gf_proto); - // SAFETY: gf_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),对象已建满、本 Vm 独占。 + // SAFETY: gf_ctor_ptr 为 Box 原分配(已登记释放表、生命周期覆盖 session),本 Vm 独占。 unsafe { let ctor_mut = &mut *gf_ctor_ptr; - let ppos = ctor_mut.push_prop(JsValue::from_js_object(vm.generator_function_proto.as_mut_ptr())); - ctor_mut.set_data_meta(ppos, oxide_types::object::PropAttributes::new(false, false, false)); - let npos = ctor_mut.push_prop(JsValue::perm_string(sf.string_ptr(sf.intern("GeneratorFunction").0))); - ctor_mut.set_data_meta(npos, oxide_types::object::PropAttributes::new(false, false, true)); + let proto_val = JsValue::from_js_object(vm.generator_function_proto.as_ptr() as *mut JsObject); + let name_val = JsValue::perm_string(sf.string_ptr(sf.intern("GeneratorFunction").0)); + if gf_ctor_is_new { + let ppos = ctor_mut.push_prop(proto_val); + ctor_mut.set_data_meta(ppos, oxide_types::object::PropAttributes::new(false, false, false)); + let npos = ctor_mut.push_prop(name_val); + ctor_mut.set_data_meta(npos, oxide_types::object::PropAttributes::new(false, false, true)); + } else { + let si_prototype = sf.intern("prototype").0; + if let Some(pos) = sh.lookup_position(ctor_mut.shape_id(), si_prototype) { + ctor_mut.set_prop_at(pos, proto_val); + } + let si_name = sf.intern("name").0; + if let Some(pos) = sh.lookup_position(ctor_mut.shape_id(), si_name) { + ctor_mut.set_prop_at(pos, name_val); + } + } } } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 2c61ab7a..d8d7778e 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -827,6 +827,93 @@ mod tests { assert!(!vm.session.is_dirty_since_snapshot()); } + /// P 原型上方法槽的 wrapper 对象原始指针(rebuild 跨轮复用验证用)。 + fn method_wrapper_ptr(vm: &Vm, proto_ptr: *const JsObject, name: &str) -> *const JsObject { + let si = vm.kernel_core.perm_interner().intern(name).0; + // SAFETY: proto_ptr 是本 session 的 P 原型,安全点内无并发读者。 + let proto = unsafe { &*proto_ptr }; + let pos = vm + .kernel_core + .shape_forge() + .lookup_position(proto.shape_id(), si) + .unwrap_or_else(|| panic!("{name} 槽位应存在")); + let val = proto.get_prop_at(pos); + assert!(val.is_object(), "{name} 槽位应为 wrapper 对象: {val:?}"); + val.as_js_object_ptr() + } + + /// global 属性槽数(槽位原位更新跨轮不追加验证用)。 + fn global_slot_count(vm: &Vm) -> usize { + let g = vm.session.global_object.as_ptr() as *mut JsObject; + // SAFETY: global 是本 session 对象,安全点内无并发读者。 + unsafe { (*g).prop_vec_len() } + } + + /// 选择性重建 wrapper 复用与 global 槽原位更新:每轮「原型脏写 + full_reset」 + /// 后存活方法 wrapper 应为同一对象(同 raw 指针、同 shape),释放表计数与 + /// global 属性槽数跨轮不增长,重建后方法行为正确。 + #[test] + fn full_reset_rebuild_reuses_method_wrappers_across_rounds() { + let mut vm = Vm::new(); + let _ = run_source(&mut vm, "0"); + let push_before = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); + let push_shape_before = unsafe { &*push_before }.shape_id(); + let registry_before = vm.session.builtin_world().leaked_object_count(); + let slots_before = global_slot_count(&vm); + + for i in 0..3u32 { + // 新键写才 bump 原型世代;四家族逐轮全脏,function 家族重建同时 + // 覆盖 wrapper proto 槽重指路径。 + let source = format!( + "Object.prototype['w{i}'] = 1; Array.prototype['w{i}'] = 2; String.prototype['w{i}'] = 3; \ + Function.prototype['w{i}'] = 4; 0" + ); + let _ = run_source(&mut vm, &source); + vm.full_reset(); + } + + let push_after = method_wrapper_ptr(&vm, vm.session.builtin_world().array_proto.as_ptr(), "push"); + assert!(std::ptr::eq(push_before, push_after), "存活方法 wrapper 应复用(同 raw 指针)"); + assert_eq!(push_shape_before, unsafe { &*push_after }.shape_id(), "wrapper shape 应稳定"); + let registry_after = vm.session.builtin_world().leaked_object_count(); + assert!( + registry_after <= registry_before, + "释放表计数跨轮不应增长: {registry_before} -> {registry_after}" + ); + let slots_after = global_slot_count(&vm); + assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); + assert_eq!(run_source(&mut vm, "[1, 2].push(3)"), JsValue::int(3)); + assert_eq!(run_source(&mut vm, "String.prototype.charCodeAt.call('A', 0)"), JsValue::int(65)); + } + + /// global 槽位原位更新锚点:错误/资源栈家族脏重建(子类型构造器经 Box 自建 + /// 路径)多轮后 global 属性槽数不增长、Error 槽指向新构造器。 + #[test] + fn full_reset_rebuild_keeps_global_slot_count_flat() { + let mut vm = Vm::new(); + let _ = run_source(&mut vm, "0"); + let slots_before = global_slot_count(&vm); + + for i in 0..3u32 { + // 新键写脏错误家族(子类型原型重建触发构造器 Box 路径)与对象家族。 + let source = format!( + "Error.prototype['e{i}'] = 1; TypeError.prototype['e{i}'] = 2; Object.prototype['e{i}'] = 3; 0" + ); + let _ = run_source(&mut vm, &source); + vm.full_reset(); + } + + let slots_after = global_slot_count(&vm); + assert!(slots_after <= slots_before, "global 槽数跨轮不应增长: {slots_before} -> {slots_after}"); + // Error 槽应指向本轮重建的构造器(非滞留旧指针)。 + assert!(std::ptr::eq( + global_prop(&vm, "Error").as_js_object_ptr(), + vm.session.builtin_world().error_constructor.as_ptr() as *mut JsObject + )); + assert!(global_prop(&vm, "TypeError").is_object()); + assert_eq!(run_source(&mut vm, "new TypeError('x') instanceof TypeError"), JsValue::bool(true)); + } + fn vm_with_low_threshold() -> Vm { let mut cfg = KernelConfig::minimal(); cfg.set_session_gc_threshold(1);