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:
2026-09-11 03:55:59 +00:00
parent a5258f2739
commit 227dfe008f
19 changed files with 566 additions and 100 deletions
+185 -44
View File
@@ -28,14 +28,14 @@ macro_rules! bind_methods {
#[macro_export]
macro_rules! bind_methods_static {
($target:expr, $sf:expr, $sh:expr, $wrapper_proto:expr,
($target:expr, $sf:expr, $sh:expr, $world: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_static(
$target, $sh, $sf, $name, _func_ptr, $nargs, $wrapper_proto,
$target, $sh, $sf, $name, _func_ptr, $nargs, $world,
);
})*
};
@@ -297,6 +297,12 @@ pub struct BuiltinWorld {
pub async_disposable_stack_proto: P<JsObject>,
pub stub_objects: Vec<P<JsObject>>,
pub console_object: P<JsObject>,
/// 绑定层经 `Box::into_raw` 持有的函数/宿主对象登记表(方法 wrapper、
/// 访问器、错误构造器、Reflect/Iterator、内建原型构造器、`$262` 宿主等)。
/// 这些对象本体在堆上、不属任何 arena,`session` 收尾时按表统一释放
/// (属性区 + 本体);选择性重建保留旧 world 时本表随旧 world 整体
/// 丢弃(泄漏口径与改造前一致,无悬垂风险)。
leaked_objects: std::cell::RefCell<Vec<*mut JsObject>>,
}
fn intern_label(string_forge: &PermInterner, label: &str) -> u32 {
@@ -682,6 +688,160 @@ impl BuiltinWorld {
JsValue::from_js_object(self.function_proto.as_ptr() as *mut JsObject)
}
/// 登记一个绑定层经 `Box::into_raw` 持有的函数/宿主对象,供
/// [`Self::teardown_heap_data`] 在 session 收尾时统一释放。
pub fn track_leaked_object(&self, obj_ptr: *mut JsObject) {
self.leaked_objects.borrow_mut().push(obj_ptr);
}
/// 选择性重建时把旧 world 的登记表整体并入新 world(见
/// [`crate::kernel::KernelSession::selective_reset`])。
pub fn inherit_leaked_objects(&self, from: &BuiltinWorld) {
self.leaked_objects.borrow_mut().append(&mut from.leaked_objects.borrow_mut());
}
/// 释放本 world 拥有的全部手工堆数据。
///
/// # 口径
/// 1. `Box::into_raw` 持有的函数/宿主对象(登记表):先释放其堆外属性区,
/// 再释放对象本体;
/// 2. 全部 P 对象字段(原型/构造器家族、迭代器原型族、stub 族、console)
/// 的堆外属性区——对象本体随 Arc 引用归零释放。
///
/// # 注意事项
/// 仅由 session 收尾调用(`KernelSession` 的 `Drop` 与 session 替换前),
/// 幂等:登记表按值取走,属性区释放后置空。选择性重建(dirty rebuild)
/// 不走本路径——旧 world 整体丢弃,登记表与属性区同泄漏(改造前口径),
/// 保留对象的引用不受影响,不引入悬垂。
pub fn teardown_heap_data(&self) {
for ptr in self.leaked_objects.borrow_mut().drain(..) {
if ptr.is_null() {
continue;
}
// SAFETY: ptr 是绑定层 Box::into_raw 产物,session 存活期内有效,
// 此处恰好释放一次(登记表按值取走,重入时表已空)。
unsafe {
let obj = &mut *ptr;
obj.release_raw_heap();
drop(Box::from_raw(ptr));
}
}
// P 字段逐一枚举:新增字段须在此同步补一行,否则收尾时属性区永久泄漏。
for p in [
&self.object_proto,
&self.array_proto,
&self.function_proto,
&self.string_proto,
&self.number_proto,
&self.boolean_proto,
&self.error_proto,
&self.symbol_proto,
&self.object_constructor,
&self.array_constructor,
&self.function_constructor,
&self.string_constructor,
&self.number_constructor,
&self.boolean_constructor,
&self.error_constructor,
&self.symbol_constructor,
&self.type_error_proto,
&self.reference_error_proto,
&self.range_error_proto,
&self.syntax_error_proto,
&self.uri_error_proto,
&self.eval_error_proto,
&self.suppressed_error_proto,
&self.math_object,
&self.json_object,
&self.date_constructor,
&self.date_proto,
&self.set_constructor,
&self.set_proto,
&self.map_constructor,
&self.map_proto,
&self.regexp_constructor,
&self.regexp_proto,
&self.array_buffer_constructor,
&self.array_buffer_proto,
&self.data_view_constructor,
&self.data_view_proto,
&self.typed_array_proto,
&self.typed_array_constructor,
&self.int8array_constructor,
&self.int8array_proto,
&self.uint8array_constructor,
&self.uint8array_proto,
&self.uint8clampedarray_constructor,
&self.uint8clampedarray_proto,
&self.int16array_constructor,
&self.int16array_proto,
&self.uint16array_constructor,
&self.uint16array_proto,
&self.int32array_constructor,
&self.int32array_proto,
&self.uint32array_constructor,
&self.uint32array_proto,
&self.float32array_constructor,
&self.float32array_proto,
&self.float64array_constructor,
&self.float64array_proto,
&self.bigint64array_constructor,
&self.bigint64array_proto,
&self.biguint64array_constructor,
&self.biguint64array_proto,
&self.sym_match,
&self.sym_replace,
&self.sym_search,
&self.sym_split,
&self.sym_iterator,
&self.sym_to_primitive,
&self.sym_has_instance,
&self.sym_match_all,
&self.sym_async_iterator,
&self.sym_to_string_tag,
&self.sym_species,
&self.sym_async_dispose,
&self.sym_dispose,
&self.temporal_object,
&self.temporal_now_object,
&self.instant_constructor,
&self.instant_proto,
&self.plain_date_constructor,
&self.plain_date_proto,
&self.plain_time_constructor,
&self.plain_time_proto,
&self.duration_constructor,
&self.duration_proto,
&self.zoned_date_time_constructor,
&self.zoned_date_time_proto,
&self.plain_date_time_constructor,
&self.plain_date_time_proto,
&self.bigint_constructor,
&self.bigint_proto,
&self.iterator_proto,
&self.array_iterator_proto,
&self.map_iterator_proto,
&self.set_iterator_proto,
&self.string_iterator_proto,
&self.regexp_string_iterator_proto,
&self.iterator_helper_proto,
&self.disposable_stack_proto,
&self.async_disposable_stack_proto,
&self.console_object,
] {
// SAFETY: p 是本 world 的 P 对象,属性区仅在此释放并置空(幂等)。
unsafe {
(&mut *p.as_mut_ptr()).release_raw_heap();
}
}
for p in &self.stub_objects {
// SAFETY: 同上,stub 对象归本 world 所有。
unsafe {
(&mut *p.as_mut_ptr()).release_raw_heap();
}
}
}
/// 按 [`BuiltinId`] 取对应内置对象的指针引用。
pub fn get_by_id(&self, id: BuiltinId) -> &P<JsObject> {
match id {
@@ -951,6 +1111,7 @@ impl BuiltinWorld {
async_disposable_stack_proto,
stub_objects,
console_object,
leaked_objects: std::cell::RefCell::new(Vec::new()),
};
wire_builtin_world_links(&world);
kernel_info!("BuiltinWorld initialized");
@@ -963,18 +1124,10 @@ impl BuiltinWorld {
) -> BuiltinWorld {
let labels = builtin_labels(string_forge);
// 方法 wrapper(Box::into_raw 永久泄漏)的 proto 持有旧 function_proto 裸指针,
// 旧 function_proto 又经 proto/constructor 引用旧 object 家族。function/object
// 重建时这 4 个对象若随 Arc 归零释放,保留 wrapper 会沿悬垂原型链
// use-after-free:泄漏保活旧家族对,与 wrapper 的永久泄漏同一约定
// (每次 dirty rebuild 至多泄漏 4 个对象,低频可接受)。
if dirty.function || dirty.object {
std::mem::forget(current.function_proto.clone());
std::mem::forget(current.function_constructor.clone());
std::mem::forget(current.object_proto.clone());
std::mem::forget(current.object_constructor.clone());
}
// 被弃家族的旧 P 对象随旧 world Arc 归零释放;保留 wrapper 的 proto 字段
// 虽沿旧对象悬空,但 reset 后执行状态已清空、旧对象不在任何指针表,
// 无活引用可达;wrapper 本体经释放表在 session 收尾统一释放,
// 释放路径不读 proto 字段,悬空不触发。
let (object_proto, object_constructor) = if dirty.object {
make_named_pair(string_forge, shape_forge, labels, "Object")
} else {
@@ -1361,6 +1514,7 @@ impl BuiltinWorld {
async_disposable_stack_proto,
stub_objects,
console_object,
leaked_objects: std::cell::RefCell::new(Vec::new()),
};
wire_builtin_world_links(&world);
world
@@ -1487,7 +1641,7 @@ impl BuiltinWorld {
"@@iterator",
func_ptr,
0,
self.fn_proto_val(),
self,
);
debug_assert!(shape_forge.lookup_position(proto.shape_id(), iterator_key).is_some());
}
@@ -1629,12 +1783,11 @@ impl BuiltinWorld {
) {
let proto_ptr = P::as_ptr(&self.function_proto) as *mut JsObject;
let proto = unsafe { &mut *proto_ptr };
let fp = self.fn_proto_val();
bind_methods_static!(
proto,
string_forge,
shape_forge,
fp,
self,
("call", methods.call, 1),
("apply", methods.apply, 2),
("bind", methods.bind, 1),
@@ -1650,7 +1803,7 @@ impl BuiltinWorld {
"[Symbol.hasInstance]",
unsafe { NativeFnPtr::from_raw(methods.has_instance) },
1,
fp,
self,
);
}
@@ -1659,35 +1812,19 @@ impl BuiltinWorld {
&self, proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str,
native_fn_ptr: NativeFnPtr, arg_count: u8,
) -> Result<(), String> {
Self::bind_method_static(
proto,
shape_forge,
string_forge,
method_name,
native_fn_ptr,
arg_count,
self.fn_proto_val(),
)
Self::bind_method_static(proto, shape_forge, string_forge, method_name, native_fn_ptr, arg_count, self)
}
/// 构造并安装一个 native 方法 wrapper 函数对象(设置 `length`/`name` 属性与参数元数据)。
///
/// 无状态版本,不依赖 `BuiltinWorld` 实例,供静态绑定宏在初始化阶段直接调用。
/// 无状态版本,供静态绑定宏在初始化阶段直接调用;需要传入当前
/// `BuiltinWorld`:wrapper 的原型取自它,wrapper 本体登记进它的释放表。
pub fn bind_method_static(
proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str,
native_fn_ptr: NativeFnPtr, arg_count: u8, wrapper_proto: JsValue,
native_fn_ptr: NativeFnPtr, arg_count: u8, world: &BuiltinWorld,
) -> Result<(), String> {
let si = string_forge.intern(method_name).0;
Self::bind_method_key_static(
proto,
shape_forge,
string_forge,
si,
method_name,
native_fn_ptr,
arg_count,
wrapper_proto,
)
Self::bind_method_key_static(proto, shape_forge, string_forge, si, method_name, native_fn_ptr, arg_count, world)
}
/// 按指定属性键安装方法 wrapper(键不要求字符串 intern,well-known symbol 键用此路径)。
@@ -1696,16 +1833,17 @@ impl BuiltinWorld {
#[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, wrapper_proto: JsValue,
native_fn_ptr: NativeFnPtr, arg_count: u8, world: &BuiltinWorld,
) -> Result<(), String> {
let wrapper_proto_ptr = if wrapper_proto.is_object() {
wrapper_proto.as_js_object_ptr()
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).ok();
wrapper.set_proto(wrapper_proto_val).ok();
}
wrapper.set_function(true);
// NativeFnPtr 不变量由调用方维护(见 bind_method / bind_method_static
@@ -1729,7 +1867,10 @@ impl BuiltinWorld {
.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));
let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper));
// 登记进 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);
proto.ensure_hash_props().push(wrapper_val);
+38 -4
View File
@@ -16,7 +16,7 @@ use crate::string_forge::PermInterner;
use oxide_log;
use oxide_log::{Level, SUBSYSTEM_COUNT};
/// kernel 运行配置:VM 池规模、步数/调用深度上限、session GC 阈值、
/// kernel 运行配置:VM 池规模、步数/调用深度/单 run 分配上限、session GC 阈值、
/// 日志级别与内置对象预热开关。由三个预设构造器([`KernelConfig::minimal`] /
/// [`KernelConfig::standard`] / [`KernelConfig::full`])或默认值创建。
#[derive(Clone)]
@@ -25,6 +25,10 @@ pub struct KernelConfig {
pub max_pool_size: Option<usize>,
pub max_dead_strings: Option<usize>,
pub max_steps: Option<u64>,
/// 单次 run 的分配上限(epoch + session arena + session 堆账目字节):
/// 超限的 run 以 `VM memory limit exceeded` 失败,防单测试 arena 高水位
/// 拖垮宿主内存。None 为无上限(CLI/嵌入默认;runner 按场景设置)。
pub max_alloc_bytes: Option<usize>,
pub max_call_depth: usize,
pub session_gc_threshold: usize,
pub max_cached_modules: usize,
@@ -42,6 +46,7 @@ impl KernelConfig {
max_pool_size: Some(8),
max_dead_strings: Some(10_000),
max_steps: None,
max_alloc_bytes: None,
max_call_depth: 1024,
session_gc_threshold: 33_554_432,
max_cached_modules: 512,
@@ -59,6 +64,7 @@ impl KernelConfig {
max_pool_size: Some(32),
max_dead_strings: Some(10_000),
max_steps: None,
max_alloc_bytes: None,
max_call_depth: 1024,
session_gc_threshold: 33_554_432,
max_cached_modules: 512,
@@ -76,6 +82,7 @@ impl KernelConfig {
max_pool_size: None,
max_dead_strings: Some(5_000),
max_steps: None,
max_alloc_bytes: None,
max_call_depth: 1024,
session_gc_threshold: 33_554_432,
max_cached_modules: 512,
@@ -698,26 +705,53 @@ impl KernelSession {
self.dirty_since_snapshot().any()
}
/// 收尾释放 session 拥有的手工堆数据:builtin world(方法 wrapper 登记表 +
/// 全部 P 对象属性区)与 global 对象属性区。对象本体随 Arc 引用归零释放。
///
/// # 注意事项
/// 幂等(登记表按值取走、属性区释放后置空),session 生命周期内可安全重入;
/// 仅应在 session 真正终止时调用——选择性重置换出的旧 world/global 不走本路径。
pub fn teardown_builtins(&mut self) {
self.builtin_world.teardown_heap_data();
// SAFETY: global 归本 session 所有,收尾时恰好释放其属性区一次。
let global = unsafe { &mut *(self.global_object.as_ptr() as *mut JsObject) };
global.release_raw_heap();
}
/// 选择性重置:只重建被污染的对象(global 或相应 builtin 家族),并返回脏集合。
///
/// 相比全量重建,可保留未污染的内置对象指针与世代,减少隔离成本。
pub fn selective_reset(&mut self, core: &Arc<KernelCore>) -> BuiltinDirtySet {
let dirty = self.dirty_since_snapshot();
if dirty.global {
// 旧 global 的属性区在替换前释放,避免随旧引用永久泄漏。
let old_global = unsafe { &mut *(self.global_object.as_ptr() as *mut JsObject) };
old_global.release_raw_heap();
self.global_object = Self::new_global_object(core);
}
if dirty.any_builtin_dirty() {
self.builtin_world = Arc::new(BuiltinWorld::rebuild_with_dirty(
&self.builtin_world,
let old_world = self.builtin_world.clone();
let new_world = BuiltinWorld::rebuild_with_dirty(
&old_world,
core.perm_interner.as_ref(),
core.shape_forge.as_ref(),
&dirty,
));
);
// 旧 world 的登记表并入新 world:存活 wrapper(未污染家族别名)仍须在
// session 收尾统一释放;已弃 wrapper 随之恰好释放一次,不二次持有。
new_world.inherit_leaked_objects(&old_world);
self.builtin_world = Arc::new(new_world);
}
dirty
}
}
impl Drop for KernelSession {
fn drop(&mut self) {
self.teardown_builtins();
}
}
#[cfg(test)]
mod tests {
use super::*;
+20
View File
@@ -870,6 +870,7 @@ fn classify_vm_error(e: &str, neg: Option<&Negative>, no_skip: bool) -> TestOutc
|| e.contains("not supported")
|| e.contains("unsupported")
|| e.contains("step limit")
|| e.contains("memory limit")
|| e.contains("NEW_EXPRESSION")
|| e.contains("GET_PROP_DYNAMIC on non-object")
|| e.contains("SET_PROP_DYNAMIC on non-object")
@@ -1467,6 +1468,11 @@ fn build_runner_kernel() -> Arc<KernelCore> {
kernel_config.max_call_depth = 256;
kernel_config.min_pool_size = 1;
kernel_config.max_pool_size = Some(1);
// 单测试分配上限:死循环类测试触步数上限时持续分配,arena 高水位可达 GB
// 级;多 worker 并发下进程 RSS 包络被各 worker 当前高水位顶起,全量运行
// 必然 OOM。正规测试峰值 ≤132MB(Array 块 p100 实测),256MiB 留有余量
// 且把失控测试的驻留面封顶到上限本身。
kernel_config.max_alloc_bytes = Some(256 * 1024 * 1024);
KernelCore::new(kernel_config)
}
@@ -1509,6 +1515,8 @@ fn categorize_fail(msg: &str) -> (String, String) {
("vm: not yet implemented".into(), String::new())
} else if reason.contains("step limit") {
("vm: step limit".into(), String::new())
} else if reason.contains("memory limit") {
("vm: memory limit".into(), String::new())
} else if reason.contains("not defined") {
("vm: not defined".into(), parse_undefined_ident(msg).unwrap_or("").to_string())
} else if reason.contains("unsupported") {
@@ -1933,6 +1941,14 @@ mod tests {
}
}
/// 分配上限超限(与步数上限同款运行期限制):默认 skip,`--no-skip` 下 fail。
#[test]
fn memory_limit_errors_skip_by_default() {
let e = "vm error: VM memory limit 268435456 exceeded (used 268436480) at pc=14";
assert_outcome(e, None, false, &TestOutcome::Skip("".into()));
assert_outcome(e, None, true, &TestOutcome::Fail("".into()));
}
/// 能力未实现形态保留 skip;`--no-skip` 下转 fail。
#[test]
fn unimplemented_shapes_stay_skipped() {
@@ -2030,6 +2046,10 @@ mod tests {
("uncaught ReferenceError: foo is not defined", ("vm: not defined", "foo")),
("compile error: Identifier 'x' is not defined", ("compile: not defined", "x")),
("vm error: IC_GET_PROP on non-object", ("vm: IC_GET_PROP on non-object", "")),
(
"vm error: VM memory limit 268435456 exceeded (used 268436480) at pc=14",
("vm: memory limit", ""),
),
("vm error: TypeError: method called on incompatible receiver", ("vm: other", "")),
("engine panic: boom", ("engine panic", "")),
(
+6
View File
@@ -32,6 +32,12 @@ impl<T> P<T> {
pub fn as_mut_ptr(&self) -> *mut T {
self.as_ptr() as *mut T
}
/// 当前强引用数。收尾路径据此区分「本 VM 独占」与「与 world 等共享」
/// 的 P 对象,避免对仍被其他引用方使用的对象做归属误判。
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
}
impl<T> Clone for P<T> {
+28
View File
@@ -959,6 +959,34 @@ impl JsObject {
self.prop_meta
}
/// 释放四处堆外裸指针区(命名属性向量/元数据、数组元素区/元素元数据)并置空。
///
/// 供 session 收尾调用:对象本体可能仍被 Arc 引用(此后属性区不再被读取),
/// 每区至多释放一次、重复调用为 no-op。upvalue 列表不在本函数口径内
/// (原件与晋升克隆间别名,须收尾时去重统一释放)。
pub fn release_raw_heap(&mut self) {
// SAFETY: 四个区各由 ensure_* 路径经 Box::into_raw 分配一次(或为空指针);
// Box::from_raw 回收后即刻置空,杜绝二次释放。
unsafe {
if !self.array_elements.is_null() {
drop(Box::from_raw(self.array_elements as *mut Vec<JsValue>));
self.array_elements = std::ptr::null_mut();
}
if !self.array_elements_meta.is_null() {
drop(Box::from_raw(self.array_elements_meta as *mut Vec<Option<PropMetaEntry>>));
self.array_elements_meta = std::ptr::null_mut();
}
if !self.hash_props.is_null() {
drop(Box::from_raw(self.hash_props as *mut Vec<JsValue>));
self.hash_props = std::ptr::null_mut();
}
if !self.prop_meta.is_null() {
drop(Box::from_raw(self.prop_meta as *mut Vec<Option<PropMetaEntry>>));
self.prop_meta = std::ptr::null_mut();
}
}
}
/// 原生 / 外来对象 payload 指针。
pub fn native_data(&self) -> *mut u8 {
self.native_data
+5 -4
View File
@@ -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()));
+9 -7
View File
@@ -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));
}
+4 -1
View File
@@ -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));
}
+25 -12
View File
@@ -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 时属性槽膨胀。
+9 -7
View File
@@ -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()));
+7 -7
View File
@@ -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));
+11 -2
View File
@@ -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);
+6 -4
View File
@@ -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));
}
+61 -3
View File
@@ -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
+138 -2
View File
@@ -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 {