mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
fix: test262 runner 进程 RSS 无界增长——session 收尾统一释放内建裸堆区/upvalue 别名 + 每测试分配上限 256MiB(任务 116)
- 泄漏修复:KernelSession 收尾(teardown_intrinsic_protos/free_session_upvalues) 释放内建对象 Box::into_raw 裸堆区(release_raw_heap 四区置空幂等)与 session upvalue 原件/晋升克隆别名去重释放;bindings 原生盒负载边、 session_gc 边记录、vm_props 属性区同步收尾 - 失控测试封顶:runner kernel max_alloc_bytes=256MiB(dispatch 采样口径 run_alloc_bytes/run_alloc_bytes_full),超限计 memory-limit 失败而非拖垮 进程 RSS(arena 高水位 GB 级 × 多 worker = 宿主 OOM) - 归属:RSS 泄漏 pre-existing(HEAD 无 WIP 同现);identifiers escaped unicode 挂死族另立 B132(pre-existing,supervise 超时自愈) - 验证:build/clippy/四 crate 测试全绿零新增告警;language 前 5000 测试 单窗峰值 330MB(旧 ~4.5MB/测试 单调增长→88GB OOM 签名消失), 窗口间峰值 0.17-1.6GB 有界、逐测试释放回基线
This commit is contained in:
@@ -12,7 +12,6 @@ use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::{to_boolean, NativeResult};
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
@@ -420,11 +419,13 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) {
|
||||
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%(构造器对象泄漏持有,与 builtin 方法 wrapper 同生命周期)。
|
||||
// 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)。
|
||||
@@ -437,8 +438,8 @@ pub(crate) fn init_async_intrinsics(vm: &mut Vm) {
|
||||
// proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再把
|
||||
// 构造器 prototype 属性指向 P 槽实例,使其与动态异步函数使用的 [[Prototype]]
|
||||
// 同一对象,保证 `AsyncFunction.prototype === (async () => {}).__proto__`。
|
||||
vm.async_function_proto = P::new(*af_proto);
|
||||
// SAFETY: af_ctor_ptr 为 Box 原分配(泄漏持有、生命周期覆盖 session),对象已建满、本 Vm 独占。
|
||||
Vm::swap_intrinsic_proto(&mut vm.async_function_proto, *af_proto);
|
||||
// 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()));
|
||||
|
||||
@@ -18,7 +18,6 @@ use std::sync::Arc;
|
||||
use oxide_builtins::iterator::make_iter_result;
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
@@ -734,6 +733,7 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) {
|
||||
let sf = vm.kernel_core.perm_interner().as_ref();
|
||||
let sh = vm.kernel_core.shape_forge().as_ref();
|
||||
let fn_proto_val = vm.session.builtin_world().fn_proto_val();
|
||||
let world = vm.session.builtin_world();
|
||||
let object_proto_val = JsValue::from_js_object(vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
|
||||
|
||||
// %AsyncGeneratorPrototype%:proto = Object.prototype,方法 next/return/throw。
|
||||
@@ -742,7 +742,7 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) {
|
||||
&mut ag_proto,
|
||||
sf,
|
||||
sh,
|
||||
fn_proto_val,
|
||||
world,
|
||||
("next", async_generator_next as *const (), 1),
|
||||
("return", async_generator_return as *const (), 1),
|
||||
("throw", async_generator_throw as *const (), 1),
|
||||
@@ -763,9 +763,9 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) {
|
||||
"@@asyncIterator",
|
||||
unsafe { oxide_types::object::NativeFnPtr::from_raw(async_generator_symbol_async_iterator as *const ()) },
|
||||
0,
|
||||
fn_proto_val,
|
||||
world,
|
||||
);
|
||||
vm.async_generator_proto = P::new(*ag_proto);
|
||||
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));
|
||||
@@ -781,11 +781,13 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) {
|
||||
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%(构造器对象泄漏持有,与 builtin 方法 wrapper 同生命周期)。
|
||||
// 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%。
|
||||
@@ -804,8 +806,8 @@ pub(crate) fn init_async_generator_intrinsics(vm: &mut Vm) {
|
||||
// proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再写
|
||||
// 构造器 prototype/name 属性(按模板序 prototype 在前),prototype 指向 P 槽实例,
|
||||
// 使其与动态异步生成器函数使用的 [[Prototype]] 同一对象。
|
||||
vm.async_generator_function_proto = P::new(*agf_proto);
|
||||
// SAFETY: agf_ctor_ptr 为 Box 原分配(泄漏持有、生命周期覆盖 session),对象已建满、本 Vm 独占。
|
||||
Vm::swap_intrinsic_proto(&mut vm.async_generator_function_proto, *agf_proto);
|
||||
// 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()));
|
||||
|
||||
@@ -117,7 +117,10 @@ pub fn bind_async_disposable_stack(core: &Arc<KernelCore>, session: &KernelSessi
|
||||
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))
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
|
||||
world.track_leaked_object(ctor_ptr);
|
||||
JsValue::from_js_object(ctor_ptr)
|
||||
};
|
||||
|
||||
write_proto_constructor(core, proto, ctor_val);
|
||||
|
||||
@@ -135,7 +135,10 @@ pub fn bind_disposable_stack(core: &Arc<KernelCore>, session: &KernelSession, gl
|
||||
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))
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
|
||||
world.track_leaked_object(ctor_ptr);
|
||||
JsValue::from_js_object(ctor_ptr)
|
||||
};
|
||||
|
||||
write_proto_constructor(core, proto, ctor_val);
|
||||
|
||||
@@ -43,6 +43,8 @@ fn bind_error_subtype_constructor(
|
||||
ctor.set_data_meta(2u32, PropAttributes::new(false, false, true));
|
||||
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
|
||||
session.builtin_world().track_leaked_object(ctor_ptr);
|
||||
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
let proto_ctor_shape = sh.make_shape(proto.shape_id(), sf.intern("constructor").0);
|
||||
|
||||
@@ -34,5 +34,8 @@ pub fn bind_iterator(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
&[("from", oxide_builtins::iterator::iterator_from::<crate::vm::Vm> as *const (), 1)],
|
||||
);
|
||||
|
||||
bind_global_value(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator)));
|
||||
let iterator_ptr = Box::into_raw(iterator);
|
||||
// 登记进 world 释放表:session 收尾统一释放构造器本体与属性区。
|
||||
session.builtin_world().track_leaked_object(iterator_ptr);
|
||||
bind_global_value(core, global, "Iterator", JsValue::from_js_object(iterator_ptr));
|
||||
}
|
||||
|
||||
@@ -62,5 +62,8 @@ pub fn bind_reflect(core: &Arc<KernelCore>, session: &KernelSession, global: &mu
|
||||
],
|
||||
);
|
||||
|
||||
bind_global_value(core, global, "Reflect", JsValue::from_js_object(Box::into_raw(reflect)));
|
||||
let reflect_ptr = Box::into_raw(reflect);
|
||||
// 登记进 world 释放表:session 收尾统一释放对象本体与属性区。
|
||||
session.builtin_world().track_leaked_object(reflect_ptr);
|
||||
bind_global_value(core, global, "Reflect", JsValue::from_js_object(reflect_ptr));
|
||||
}
|
||||
|
||||
@@ -129,14 +129,15 @@ pub(crate) fn apply_binding_table(
|
||||
/// 2. 为属性名开 shape 槽位并写入访问器 meta。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// getter 函数对象经 `Box::into_raw` 持有,与 `bind_method` 的方法 wrapper 同一生命周期约定
|
||||
/// (内置对象在 session 生命周期内不被回收)。
|
||||
/// getter 函数对象登记进 world 释放表,与 `bind_method` 的方法 wrapper 同一生命周期约定
|
||||
/// (session 收尾时统一释放)。
|
||||
pub(crate) fn bind_accessor_getter(
|
||||
core: &Arc<KernelCore>, session: &KernelSession, proto: &mut JsObject, name: &str, getter_fn: *const (),
|
||||
) {
|
||||
let shape_forge = core.shape_forge().as_ref();
|
||||
let string_forge = core.perm_interner().as_ref();
|
||||
let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject);
|
||||
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));
|
||||
@@ -161,7 +162,9 @@ pub(crate) fn bind_accessor_getter(
|
||||
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_val = JsValue::from_js_object(Box::into_raw(getter));
|
||||
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 new_shape = shape_forge.make_shape(proto.shape_id(), si);
|
||||
@@ -180,8 +183,8 @@ pub(crate) fn bind_accessor_getter(
|
||||
/// 2. 为键开 shape 槽位并写入访问器 meta(enumerable=false, configurable=true)。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// getter/setter 函数对象经 `Box::into_raw` 持有,与 `bind_accessor_getter` 的
|
||||
/// 方法 wrapper 同一生命周期约定(内置对象在 session 生命周期内不被回收)。
|
||||
/// getter/setter 函数对象登记进 world 释放表,session 收尾时统一释放,
|
||||
/// 与 `bind_accessor_getter` 的 getter 及方法 wrapper 同一生命周期约定。
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn bind_accessor_getset(
|
||||
core: &Arc<KernelCore>, session: &KernelSession, proto: &mut JsObject, key: u32, getter_name: &str,
|
||||
@@ -189,7 +192,8 @@ pub(crate) fn bind_accessor_getset(
|
||||
) {
|
||||
let shape_forge = core.shape_forge().as_ref();
|
||||
let string_forge = core.perm_interner().as_ref();
|
||||
let fn_proto_val = JsValue::from_js_object(session.builtin_world().function_proto.as_ptr() as *mut JsObject);
|
||||
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);
|
||||
@@ -211,7 +215,9 @@ pub(crate) fn bind_accessor_getset(
|
||||
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_val = JsValue::from_js_object(Box::into_raw(getter));
|
||||
let getter_ptr = Box::into_raw(getter);
|
||||
world.track_leaked_object(getter_ptr);
|
||||
let getter_val = JsValue::from_js_object(getter_ptr);
|
||||
|
||||
let mut setter = Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, fn_proto_val));
|
||||
setter.set_function(true);
|
||||
@@ -230,7 +236,9 @@ pub(crate) fn bind_accessor_getset(
|
||||
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_val = JsValue::from_js_object(Box::into_raw(setter));
|
||||
let setter_ptr = Box::into_raw(setter);
|
||||
world.track_leaked_object(setter_ptr);
|
||||
let setter_val = JsValue::from_js_object(setter_ptr);
|
||||
|
||||
// 访问器属性槽:enumerable=false、configurable=true(get/set 函数对象已持有)。
|
||||
let new_shape = shape_forge.make_shape(proto.shape_id(), key);
|
||||
@@ -301,7 +309,7 @@ pub(crate) fn bind_well_known_method(
|
||||
method_name,
|
||||
fn_ptr,
|
||||
nargs,
|
||||
world.fn_proto_val(),
|
||||
world,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -617,6 +625,7 @@ fn bind_error_subtype_global(
|
||||
ctor.set_data_meta(1u32, PropAttributes::new(false, false, true));
|
||||
|
||||
let ctor_ptr = Box::into_raw(ctor);
|
||||
session.builtin_world().track_leaked_object(ctor_ptr);
|
||||
bind_existing_global(core, global, name, JsValue::from_js_object(ctor_ptr));
|
||||
}
|
||||
|
||||
@@ -702,7 +711,9 @@ fn bind_reflect_global(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_existing_global(core, global, "Reflect", JsValue::from_js_object(Box::into_raw(reflect)));
|
||||
let reflect_ptr = Box::into_raw(reflect);
|
||||
session.builtin_world().track_leaked_object(reflect_ptr);
|
||||
bind_existing_global(core, global, "Reflect", JsValue::from_js_object(reflect_ptr));
|
||||
}
|
||||
|
||||
fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
@@ -722,7 +733,9 @@ fn bind_iterator_global(core: &Arc<KernelCore>, session: &KernelSession, global:
|
||||
core,
|
||||
&[("from", oxide_builtins::iterator::iterator_from::<crate::vm::Vm> as *const (), 1)],
|
||||
);
|
||||
bind_existing_global(core, global, "Iterator", JsValue::from_js_object(Box::into_raw(iterator)));
|
||||
let iterator_ptr = Box::into_raw(iterator);
|
||||
session.builtin_world().track_leaked_object(iterator_ptr);
|
||||
bind_existing_global(core, global, "Iterator", JsValue::from_js_object(iterator_ptr));
|
||||
// 迭代器原型方法(%IteratorPrototype% 的 @@iterator 与各集合原型 next)由
|
||||
// `bind_iterator_protos` 在 object 家族重建时统一安装,这里不重复绑定,
|
||||
// 避免保留原型经 dirty reset 时属性槽膨胀。
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::sync::Arc;
|
||||
use oxide_builtins::iterator::{is_callable, make_iter_result};
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::{to_boolean, NativeResult};
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
@@ -728,6 +727,7 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) {
|
||||
let sf = vm.kernel_core.perm_interner().as_ref();
|
||||
let sh = vm.kernel_core.shape_forge().as_ref();
|
||||
let fn_proto_val = vm.session.builtin_world().fn_proto_val();
|
||||
let world = vm.session.builtin_world();
|
||||
// %GeneratorPrototype%:proto = %IteratorPrototype%(生成器是迭代器,继承
|
||||
// @@iterator 与 Iterator helper 方法),方法 next/return/throw。
|
||||
let iterator_proto_val =
|
||||
@@ -737,7 +737,7 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) {
|
||||
&mut gen_proto,
|
||||
sf,
|
||||
sh,
|
||||
fn_proto_val,
|
||||
world,
|
||||
("next", generator_next as *const (), 1),
|
||||
("return", generator_return as *const (), 1),
|
||||
("throw", generator_throw as *const (), 1),
|
||||
@@ -758,9 +758,9 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) {
|
||||
"@@iterator",
|
||||
unsafe { oxide_types::object::NativeFnPtr::from_raw(generator_symbol_iterator as *const ()) },
|
||||
0,
|
||||
fn_proto_val,
|
||||
world,
|
||||
);
|
||||
vm.generator_proto = P::new(*gen_proto);
|
||||
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));
|
||||
@@ -776,11 +776,13 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) {
|
||||
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);
|
||||
// gf_proto.constructor = gf_ctor(构造器对象泄漏持有,与 builtin 方法 wrapper 同生命周期)。
|
||||
// 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 测试读取)。
|
||||
@@ -799,8 +801,8 @@ pub(crate) fn init_generator_intrinsics(vm: &mut Vm) {
|
||||
// proto 本体只存在于 P 槽(Arc 副本,原 Box 随函数结束释放):装入 P 槽后再写
|
||||
// 构造器 prototype/name 属性(按模板序 prototype 在前),prototype 指向 P 槽实例,
|
||||
// 使其与动态生成器函数使用的 [[Prototype]] 同一对象。
|
||||
vm.generator_function_proto = P::new(*gf_proto);
|
||||
// SAFETY: gf_ctor_ptr 为 Box 原分配(泄漏持有、生命周期覆盖 session),对象已建满、本 Vm 独占。
|
||||
Vm::swap_intrinsic_proto(&mut vm.generator_function_proto, *gf_proto);
|
||||
// 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()));
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::NativeResult;
|
||||
use oxide_types::mem::P;
|
||||
use oxide_types::object::{JsObject, NativeFnPtr, PropAttributes};
|
||||
use oxide_types::private_key::make_int_key;
|
||||
use oxide_types::value::JsValue;
|
||||
@@ -705,6 +704,7 @@ impl Vm {
|
||||
let sf = self.kernel_core.perm_interner().as_ref();
|
||||
let sh = self.kernel_core.shape_forge().as_ref();
|
||||
let fn_proto_val = self.session.builtin_world().fn_proto_val();
|
||||
let world = self.session.builtin_world();
|
||||
let object_proto_val =
|
||||
JsValue::from_js_object(self.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
|
||||
|
||||
@@ -719,7 +719,7 @@ impl Vm {
|
||||
&mut proto,
|
||||
sf,
|
||||
sh,
|
||||
fn_proto_val,
|
||||
world,
|
||||
("then", promise_then as *const (), 2),
|
||||
("catch", promise_catch as *const (), 1),
|
||||
("finally", promise_finally as *const (), 1),
|
||||
@@ -757,7 +757,7 @@ impl Vm {
|
||||
&mut ctor,
|
||||
sf,
|
||||
sh,
|
||||
fn_proto_val,
|
||||
world,
|
||||
("resolve", promise_static_resolve as *const (), 1),
|
||||
("reject", promise_static_reject as *const (), 1),
|
||||
("all", promise_static_all as *const (), 1),
|
||||
@@ -768,8 +768,8 @@ impl Vm {
|
||||
);
|
||||
|
||||
// 固定地址后互相接线:proto.constructor ↔ ctor.prototype。
|
||||
self.promise_proto = P::new(*proto);
|
||||
self.promise_constructor = P::new(*ctor);
|
||||
Self::swap_intrinsic_proto(&mut self.promise_proto, *proto);
|
||||
Self::swap_intrinsic_proto(&mut self.promise_constructor, *ctor);
|
||||
let proto_mut = unsafe { &mut *self.promise_proto.as_mut_ptr() };
|
||||
proto_mut.set_prop_at(0u32, JsValue::from_js_object(self.promise_constructor.as_ptr() as *mut JsObject));
|
||||
let ctor_mut = unsafe { &mut *self.promise_constructor.as_mut_ptr() };
|
||||
@@ -1169,8 +1169,8 @@ impl Vm {
|
||||
ctor.set_data_meta(2u32, PropAttributes::new(false, false, false));
|
||||
|
||||
// 固定地址后互相接线:proto.constructor ↔ ctor.prototype。
|
||||
self.aggregate_error_proto = P::new(*proto);
|
||||
self.aggregate_error_constructor = P::new(*ctor);
|
||||
Self::swap_intrinsic_proto(&mut self.aggregate_error_proto, *proto);
|
||||
Self::swap_intrinsic_proto(&mut self.aggregate_error_constructor, *ctor);
|
||||
let proto_mut = unsafe { &mut *self.aggregate_error_proto.as_mut_ptr() };
|
||||
proto_mut
|
||||
.set_prop_at(0u32, JsValue::from_js_object(self.aggregate_error_constructor.as_ptr() as *mut JsObject));
|
||||
|
||||
@@ -54,8 +54,9 @@ impl SessionGc {
|
||||
}
|
||||
}
|
||||
|
||||
/// 只读核算 session 对象的堆数据字节(属性/元素/meta Vec capacity + native 状态盒)。
|
||||
/// 与 `drop_object_heap_data` 释放口径一致(capacity),不释放、不置空任何指针。
|
||||
/// 只读核算 session 对象的堆数据字节(属性/元素/meta Vec capacity + upvalue 列表
|
||||
/// capacity + native 状态盒)。与收尾释放口径一致(capacity);upvalue 列表因原件与
|
||||
/// 晋升克隆别名,释放走收尾集中去重而非逐对象路径。不释放、不置空任何指针。
|
||||
pub(crate) fn object_heap_data_bytes(obj: &JsObject) -> u64 {
|
||||
let mut bytes = 0u64;
|
||||
|
||||
@@ -89,6 +90,14 @@ impl SessionGc {
|
||||
}
|
||||
}
|
||||
|
||||
let upvalues_ptr = obj.upvalues as *const Vec<*mut oxide_types::object::Cell>;
|
||||
if !upvalues_ptr.is_null() {
|
||||
unsafe {
|
||||
bytes += size_of::<Vec<*mut oxide_types::object::Cell>>() as u64
|
||||
+ ((*upvalues_ptr).capacity() * size_of::<*mut oxide_types::object::Cell>()) as u64;
|
||||
}
|
||||
}
|
||||
|
||||
bytes += map::map_native_size(obj);
|
||||
bytes += set::set_native_size(obj);
|
||||
bytes += disposable_stack::disposable_stack_native_size(obj);
|
||||
|
||||
@@ -86,9 +86,9 @@ pub fn agent(vm: &mut Vm, _args: &[u8]) -> NativeResult {
|
||||
/// 3. `global` 数据属性指向当前 session global;`$262` 本体挂到 global。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 宿主对象经 `Box::into_raw` 持有,随 session 生命周期存活(与 Reflect/Iterator
|
||||
/// 等全局对象同一约定);每次调用新建对象,重复绑定会泄漏旧对象(全量初始化
|
||||
/// 与 full_reset 频率低,可接受)。
|
||||
/// - 宿主对象登记进 world 释放表,session 收尾时统一释放(与 Reflect/Iterator
|
||||
/// 等全局对象同一约定);每次调用新建对象,同一 session 内重复绑定会泄漏旧对象
|
||||
/// (全量初始化与 full_reset 频率低,可接受)。
|
||||
pub fn bind_test262_host(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let object_proto = session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
let mut host = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
|
||||
@@ -106,5 +106,7 @@ pub fn bind_test262_host(core: &Arc<KernelCore>, session: &KernelSession, global
|
||||
);
|
||||
let global_this = JsValue::from_js_object(global as *mut JsObject);
|
||||
bind_global_value(core, &mut host, "global", global_this);
|
||||
bind_global_value(core, global, "$262", JsValue::from_js_object(Box::into_raw(Box::new(host))));
|
||||
let host_ptr = Box::into_raw(Box::new(host));
|
||||
session.builtin_world().track_leaked_object(host_ptr);
|
||||
bind_global_value(core, global, "$262", JsValue::from_js_object(host_ptr));
|
||||
}
|
||||
|
||||
@@ -555,7 +555,8 @@ pub struct Vm {
|
||||
impl Drop for Vm {
|
||||
fn drop(&mut self) {
|
||||
// 直接 drop(test262 每测试新建即弃)不经 reset/full_reset 路径:
|
||||
// 统一收尾释放全部 session 堆数据,防逐测试累积泄漏。
|
||||
// 统一收尾释放全部 session 堆数据与内建原型属性区,防逐测试累积泄漏。
|
||||
self.teardown_intrinsic_protos();
|
||||
self.teardown_session_heap_data();
|
||||
}
|
||||
}
|
||||
@@ -974,6 +975,47 @@ impl Vm {
|
||||
self.gc_state.session_bytes_peak
|
||||
}
|
||||
|
||||
/// 本 run 累计分配字节:epoch arena + session 对象 arena + session 手工堆
|
||||
/// 账目(串/BigInt/cell/属性向量)。单次 run 内单调不减(执行期对象不回收、
|
||||
/// 串 GC 只降手工堆账目而 arena 不减);run 边界(reset)后重新起算,
|
||||
/// 供单 run 分配上限判定。
|
||||
///
|
||||
/// 注意:手工堆账目只在 promote/字符串分配点更新,执行期对象属性区
|
||||
/// (元素/属性向量扩容)增长对其不可见——上限判定须配合
|
||||
/// [`Self::run_alloc_bytes_full`] 的深采样层。
|
||||
pub(crate) fn run_alloc_bytes(&self) -> usize {
|
||||
self.epoch.bump().allocated_bytes()
|
||||
+ self.gc_state.session_epoch.allocated_bytes()
|
||||
+ self.gc_state.session_bytes_allocated
|
||||
}
|
||||
|
||||
/// 本 run 累计分配字节的全量重算版:arena 计数器之外,逐一重算已登记对象
|
||||
/// 的堆数据(属性/元素向量 + upvalue 列表 + native 状态盒)、session 串、
|
||||
/// BigInt 与 upvalue cell 的容量。采样深度高于账目更新点,
|
||||
/// 兜住属性区扩容这类账目盲区。
|
||||
pub(crate) fn run_alloc_bytes_full(&self) -> u64 {
|
||||
let mut bytes = self.run_alloc_bytes() as u64;
|
||||
for &ptr in self
|
||||
.gc_state
|
||||
.epoch_object_ptrs
|
||||
.iter()
|
||||
.chain(self.gc_state.session_object_ptrs.iter())
|
||||
{
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。
|
||||
bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr });
|
||||
}
|
||||
for &ptr in &self.gc_state.session_string_ptrs {
|
||||
// SAFETY: ptr 来自字符串表登记,收尾前始终有效。
|
||||
bytes += (std::mem::size_of::<oxide_types::object::JsString>() + unsafe { (*ptr).len() }) as u64;
|
||||
}
|
||||
bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()) as u64;
|
||||
bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()) as u64;
|
||||
bytes
|
||||
}
|
||||
|
||||
/// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。
|
||||
///
|
||||
/// # 副作用
|
||||
@@ -1525,9 +1567,10 @@ impl Vm {
|
||||
}
|
||||
|
||||
pub(crate) fn dispatch(&mut self) -> Result<JsValue, String> {
|
||||
// config.max_steps 逐指令只读且循环内不变:提到循环外,免每次经 kernel_core
|
||||
// Arc 指针追寻读取(热点内唯一的 config 访问)。
|
||||
// config.max_steps / max_alloc_bytes 逐指令只读且循环内不变:提到循环外,
|
||||
// 免每次经 kernel_core Arc 指针追寻读取(热点内仅有的 config 访问)。
|
||||
let max_steps = self.kernel_core.config.max_steps;
|
||||
let max_alloc_bytes = self.kernel_core.config.max_alloc_bytes;
|
||||
let mut steps: u64 = 0;
|
||||
loop {
|
||||
steps += 1;
|
||||
@@ -1554,6 +1597,21 @@ impl Vm {
|
||||
return Err(format!("VM step limit exceeded at pc={}", self.pc));
|
||||
}
|
||||
}
|
||||
// 单 run 分配上限:账目盲区(属性区扩容)靠两层采样兜住——轻层每
|
||||
// 64 指令读三个计数器,深层每 2^18 指令全量重算(含逐对象属性区
|
||||
// 重算);超限 run 按步数超限同款处理(默认 skip / --no-skip 下
|
||||
// fail),防单测试 arena 高水位拖垮宿主。
|
||||
if let Some(cap) = max_alloc_bytes {
|
||||
let deep = (steps & 0x3FFFF) == 0;
|
||||
if deep || (steps & 0x3F) == 0 {
|
||||
let used = if deep { self.run_alloc_bytes_full() as usize } else { self.run_alloc_bytes() };
|
||||
if used > cap {
|
||||
vm_warn!("dispatch: memory limit {cap} exceeded (used {used}) at pc={}", self.pc);
|
||||
self.profiling.set_instruction_count(steps);
|
||||
return Err(format!("VM memory limit {cap} exceeded (used {used}) at pc={}", self.pc));
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.pc >= self.bytecode.len() {
|
||||
let tail: Vec<String> = self
|
||||
.bytecode
|
||||
|
||||
@@ -21,6 +21,18 @@ impl Vm {
|
||||
/// 超过阈值后 O(n²) 拷贝成本超过节点开销,Cons 的 O(1) 链接才值得。
|
||||
pub(crate) const CONS_FLATTEN_BYTES: usize = 128;
|
||||
|
||||
/// 替换本 VM 独占的内建 P 对象:先恰好释放旧副本的属性区一次,
|
||||
/// 再让旧 Arc 归零(旧副本无 Drop 口径兜底,full_reset 重初始化
|
||||
/// 路径必须显式释放,否则逐测试累积)。
|
||||
pub(crate) fn swap_intrinsic_proto(slot: &mut P<JsObject>, new_obj: JsObject) {
|
||||
// SAFETY: 旧副本为本 VM 独占(其余引用均为本次替换前克隆),
|
||||
// 属性区仅此一处释放并置空(幂等)。
|
||||
unsafe {
|
||||
(&mut *slot.as_mut_ptr()).release_raw_heap();
|
||||
}
|
||||
*slot = P::new(new_obj);
|
||||
}
|
||||
|
||||
/// 以最小配置创建独立 VM:新建 `KernelCore` + `KernelSession` 并初始化内置对象。
|
||||
pub fn new() -> Self {
|
||||
let core = KernelCore::new(KernelConfig::minimal());
|
||||
@@ -339,13 +351,15 @@ impl Vm {
|
||||
self.active_reg_limit = 0;
|
||||
}
|
||||
|
||||
/// 释放全部 session 堆数据:epoch 对象堆数据 + session 对象堆数据 + session 串 +
|
||||
/// BigInt box + upvalue cell box。
|
||||
/// 释放全部 session 堆数据:epoch 对象堆数据 + session 对象堆数据 + upvalue
|
||||
/// 列表 + session 串 + BigInt box + upvalue cell box。
|
||||
///
|
||||
/// 供 `full_reset` 与 `Drop` 共用——对象本体(bumpalo arena / epoch bump)由调用方
|
||||
/// 重置,本函数只释放手工管理的 Box 指针(属性向量、各原生盒、串、BigInt、cell)。
|
||||
/// 原生盒在 GC 搬移/晋升时已深拷贝为单所有权,此处恰好释放一次。
|
||||
pub(crate) fn teardown_session_heap_data(&mut self) {
|
||||
// upvalue 列表先于对象表清空释放:去重枚举依赖两份对象表尚存。
|
||||
self.free_session_upvalues();
|
||||
self.free_epoch_object_heap_data();
|
||||
for ptr in self.gc_state.session_object_ptrs.drain(..) {
|
||||
crate::session_gc::SessionGc::drop_object_heap_data(ptr, true);
|
||||
@@ -355,6 +369,64 @@ impl Vm {
|
||||
self.gc_state.free_cells();
|
||||
}
|
||||
|
||||
/// 集中释放 upvalue 列表(`Box<Vec<*mut Cell>>`):原件与晋升克隆经
|
||||
/// `clone_for_session_epoch` 共享同一 Box 分配,逐对象路径释放会双放,
|
||||
/// 只在此处按指针去重后统一释放。须在两份对象表清空之前调用
|
||||
/// (枚举仍存对象完成去重),此时对象已死、Box 无其他读者。
|
||||
fn free_session_upvalues(&mut self) {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let ptrs = self
|
||||
.gc_state
|
||||
.epoch_object_ptrs
|
||||
.iter()
|
||||
.chain(self.gc_state.session_object_ptrs.iter());
|
||||
for &ptr in ptrs {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: ptr 来自 epoch/session 对象表登记,收尾时仍指向 arena 内合法对象。
|
||||
let up = unsafe { (*ptr).upvalues };
|
||||
if up.is_null() || !seen.insert(up) {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: up 由 set_upvalues 的 Box::into_raw 分配,去重保证恰好释放一次。
|
||||
unsafe {
|
||||
drop(Box::from_raw(up as *mut Vec<*mut oxide_types::object::Cell>));
|
||||
(*ptr).upvalues = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 释放 VM 内建原型 P 对象(生成器/Promise/异步族与 Object 原型)的堆外属性区。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// 对象本体随 Arc 引用归零释放(字段 drop);属性区须在此显式释放一次
|
||||
/// (`JsObject` 无 Drop 口径)。仅释放本 VM 独占(强引用计数为 1)的副本:
|
||||
/// `object_prototype` 与 session world 共享 Arc,session 存活期下一测试仍经
|
||||
/// world 引用同一对象,其属性区归 session 收尾(`teardown_builtins`)释放。
|
||||
pub(crate) fn teardown_intrinsic_protos(&mut self) {
|
||||
for p in [
|
||||
&self.object_prototype,
|
||||
&self.generator_proto,
|
||||
&self.generator_function_proto,
|
||||
&self.promise_constructor,
|
||||
&self.promise_proto,
|
||||
&self.aggregate_error_constructor,
|
||||
&self.aggregate_error_proto,
|
||||
&self.async_function_proto,
|
||||
&self.async_generator_proto,
|
||||
&self.async_generator_function_proto,
|
||||
] {
|
||||
if p.strong_count() != 1 {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: 独占副本的属性区仅此一处释放并置空(幂等)。
|
||||
unsafe {
|
||||
(&mut *p.as_mut_ptr()).release_raw_heap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn free_epoch_object_heap_data(&mut self) {
|
||||
let mut freed = 0u64;
|
||||
for ptr in self.gc_state.epoch_object_ptrs.drain(..) {
|
||||
@@ -417,6 +489,7 @@ impl Vm {
|
||||
self.bytecode = Arc::default();
|
||||
self.immutables_cache.clear();
|
||||
self.active_immutables = std::ptr::slice_from_raw_parts(std::ptr::null(), 0);
|
||||
self.free_epoch_dead_upvalues();
|
||||
self.free_epoch_object_heap_data();
|
||||
self.epoch.reset();
|
||||
self.gc_state.epoch_object_ptrs.clear();
|
||||
@@ -424,6 +497,41 @@ impl Vm {
|
||||
self.active_reg_limit = 0;
|
||||
}
|
||||
|
||||
/// 释放随本次 epoch 重置死亡的 epoch 对象的 upvalue 列表。
|
||||
///
|
||||
/// 原件与晋升克隆共享同一 Box 分配:共享项归 session 克隆持有,
|
||||
/// 留待收尾(`free_session_upvalues`)统一释放,此处只放独占项。
|
||||
fn free_epoch_dead_upvalues(&mut self) {
|
||||
let mut shared: std::collections::HashSet<usize> = std::collections::HashSet::new();
|
||||
for &ptr in self.gc_state.session_object_ptrs.iter() {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: session 对象表此刻未清空,指向 session arena 内合法对象。
|
||||
let up = unsafe { (*ptr).upvalues } as usize;
|
||||
if up != 0 {
|
||||
shared.insert(up);
|
||||
}
|
||||
}
|
||||
let mut seen: std::collections::HashSet<usize> = std::collections::HashSet::new();
|
||||
for &ptr in self.gc_state.epoch_object_ptrs.iter() {
|
||||
if ptr.is_null() {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: epoch 对象表此刻未清空,指向 epoch arena 内合法对象。
|
||||
let up = unsafe { (*ptr).upvalues } as usize;
|
||||
if up == 0 || shared.contains(&up) || !seen.insert(up) {
|
||||
continue;
|
||||
}
|
||||
// SAFETY: up 由 set_upvalues 的 Box::into_raw 分配,去重与共享集
|
||||
// 保证本路径恰好释放一次。
|
||||
unsafe {
|
||||
drop(Box::from_raw(up as *mut Vec<*mut oxide_types::object::Cell>));
|
||||
(*ptr).upvalues = std::ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 分配一个可被 session GC 回收的字符串 `JsValue`(session-heap 字符串)。
|
||||
pub fn new_string(&mut self, s: &str) -> JsValue {
|
||||
self.new_string_owned(s.to_string())
|
||||
@@ -721,6 +829,34 @@ mod tests {
|
||||
Vm::with_kernel_core(KernelCore::new(cfg))
|
||||
}
|
||||
|
||||
/// 单 run 分配上限拦截失控分配:死循环持续 push 的 run 须在触达步数上限前
|
||||
/// 以 memory limit 错误终止。
|
||||
#[test]
|
||||
fn run_alloc_cap_stops_runaway_allocation() {
|
||||
let mut cfg = KernelConfig::minimal();
|
||||
cfg.max_alloc_bytes = Some(256 * 1024);
|
||||
let mut vm = Vm::with_kernel_core(KernelCore::new(cfg));
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "var a = []; while (true) { a.push(1); }").expect("parse failed");
|
||||
let module = oxide_compiler::compiler::Compiler::new()
|
||||
.compile(&program)
|
||||
.expect("compile failed");
|
||||
let err = vm.run(&module).expect_err("runaway allocation must hit the alloc cap");
|
||||
assert!(err.contains("memory limit"), "unexpected error: {err}");
|
||||
assert!(!err.contains("step limit"), "cap 应先于步数上限生效: {err}");
|
||||
}
|
||||
|
||||
/// 上限不误伤正常规模分配:同配置下 1 万元素数组构造正常完成。
|
||||
#[test]
|
||||
fn run_alloc_cap_allows_normal_allocation() {
|
||||
let mut cfg = KernelConfig::minimal();
|
||||
cfg.max_alloc_bytes = Some(4 * 1024 * 1024);
|
||||
let mut vm = Vm::with_kernel_core(KernelCore::new(cfg));
|
||||
let result = run_source(&mut vm, "var a = []; for (var i = 0; i < 10000; i++) { a.push(i); } a.length");
|
||||
assert!(result.is_int(), "expected int length, got {result:?}");
|
||||
assert_eq!(result.as_int(), 10000);
|
||||
}
|
||||
|
||||
/// 直接恢复生成器一步:等价 `it.next()`,但避免跨 run(sub_modules 重建会使
|
||||
/// 挂起帧失效)——回归聚焦 GC 搬移后的状态盒有效性。
|
||||
fn resume_one_step(vm: &mut Vm, gen: JsValue) -> JsValue {
|
||||
|
||||
Reference in New Issue
Block a user