mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21: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:
@@ -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);
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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", "")),
|
||||
(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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