mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 迭代器包装对象分配走统一入口登记 epoch 对象表——执行期 epoch 换新只晋升表内对象,绕过登记的包装器/结果对象在换新后悬垂,闭合
This commit is contained in:
@@ -1755,9 +1755,7 @@ pub(crate) fn make_array_iterator<H: VmHost>(vm: &mut H, this_val: JsValue, kind
|
||||
Err(msg) => return Err(array_type_error(vm, &msg)),
|
||||
};
|
||||
let array_iter_proto = vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject;
|
||||
let iter = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto)));
|
||||
let iter = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto)));
|
||||
let target_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_TARGET_PROP).0;
|
||||
let index_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_INDEX_PROP).0;
|
||||
let kind_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_KIND_PROP).0;
|
||||
|
||||
@@ -434,9 +434,7 @@ fn make_iterator_helper<H: VmHost>(
|
||||
vm: &mut H, inner: JsValue, next: JsValue, kind: IteratorHelperKind, callback: JsValue, counter: JsValue,
|
||||
) -> JsValue {
|
||||
let helper_proto = vm.session().builtin_world().iterator_helper_proto.as_ptr() as *mut JsObject;
|
||||
let wrapper = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(helper_proto)));
|
||||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(helper_proto)));
|
||||
let wrapper_obj = unsafe { &mut *wrapper };
|
||||
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
|
||||
let next_si = vm.kernel_core().perm_interner().intern(NEXT_CACHE_PROP).0;
|
||||
@@ -1232,9 +1230,7 @@ pub(crate) fn try_make_iterator_inner_proto<H: VmHost>(
|
||||
Some(proto) => proto,
|
||||
None => vm.session().builtin_world().iterator_proto.as_ptr() as *mut JsObject,
|
||||
};
|
||||
let wrapper = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(iterator_proto)));
|
||||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(iterator_proto)));
|
||||
|
||||
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
|
||||
let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0;
|
||||
@@ -1576,9 +1572,7 @@ fn current_index<H: VmHost>(vm: &mut H, wrapper: &JsObject, index_si: u32) -> us
|
||||
/// 构造迭代器结果对象 `{value, done}`(生成器 next/return 结果复用)。
|
||||
pub fn make_iter_result<H: VmHost>(vm: &mut H, value: JsValue, done: bool) -> JsValue {
|
||||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
let obj = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||||
let done_si = vm.kernel_core().perm_interner().intern("done").0;
|
||||
let obj_ref = unsafe { &mut *obj };
|
||||
@@ -1854,9 +1848,7 @@ pub(crate) fn make_collection_iterator<H: VmHost>(
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
};
|
||||
let wrapper = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
||||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
|
||||
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
|
||||
let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0;
|
||||
let mode_si = vm.kernel_core().perm_interner().intern(MODE_PROP).0;
|
||||
|
||||
@@ -563,9 +563,7 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
// 复用 String.prototype.matchAll 的迭代器包装(next 经 string_match_all_next 推进,
|
||||
// 挂 %RegExpStringIteratorPrototype% 原型,不设实例 own next)。
|
||||
let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject;
|
||||
let wrapper = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
|
||||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
|
||||
let wrapper_obj = unsafe { &mut *wrapper };
|
||||
let input_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INPUT).0;
|
||||
let index_si = vm.kernel_core().perm_interner().intern(crate::string::MALL_INDEX).0;
|
||||
|
||||
@@ -1522,9 +1522,7 @@ fn builder_wrapper<H: VmHost>(vm: &mut H, input: &str, re_obj: JsValue) -> Nativ
|
||||
// matchAll 迭代器挂 %RegExpStringIteratorPrototype%(链到 %IteratorPrototype%),
|
||||
// next 由原型提供(不挂实例 own)。
|
||||
let regexp_iter_proto = vm.session().builtin_world().regexp_string_iterator_proto.as_ptr() as *mut JsObject;
|
||||
let wrapper = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
|
||||
let wrapper = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(regexp_iter_proto)));
|
||||
|
||||
let wrapper_obj = unsafe { &mut *wrapper };
|
||||
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
|
||||
@@ -1642,9 +1640,7 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
fn make_match_done_result<H: VmHost>(vm: &mut H, value: JsValue) -> NativeResult {
|
||||
let done = value.is_undefined();
|
||||
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
let obj = vm
|
||||
.epoch()
|
||||
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||||
let obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
|
||||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||||
let done_si = vm.kernel_core().perm_interner().intern("done").0;
|
||||
let obj_ref = unsafe { &mut *obj };
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
//! 并行负载下内存面腐化的稳定判别探针(执行期两档收集全路径覆盖)。
|
||||
//!
|
||||
//! 每个测试独享 `Vm`(低 session GC 阈值强制单 run 内反复触发执行期
|
||||
//! epoch 晋升 + session 原地 sweep),JS 负载覆盖:对象元素数组读回、
|
||||
//! 原生盒(Set/Map)对象边、Promise 结算值、挂起帧(生成器/异步)
|
||||
//! 持串/BigInt 边跨收集边界恢复;断言读回值的 tag 位与载荷,
|
||||
//! 悬垂读命中复用块即红。
|
||||
//!
|
||||
//! 盒键只用对象键:字符串键的 SameValueZero 值等值在 SetKey 位比较下
|
||||
//! 未闭合(动态构造串键按指针判异),属独立语义缺陷,不在此面覆盖。
|
||||
//!
|
||||
//! 运行纪律:与负载子集二进制同批 `--test-threads` 梯度并行,
|
||||
//! `MALLOC_PERTURB_` 放大已释放块扰动;先串行基线确认扰动不引入新红。
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_kernel::kernel::{KernelConfig, KernelCore};
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::promise::promise_settled_value;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn vm_with_threshold(bytes: usize) -> Vm {
|
||||
let mut config = KernelConfig::minimal();
|
||||
config.set_session_gc_threshold(bytes);
|
||||
Vm::with_kernel_core(KernelCore::new(config))
|
||||
}
|
||||
|
||||
/// 执行源码并格式化完成值:串取内容、Promise 取 drain 后结算值。
|
||||
fn eval_src(vm: &mut Vm, source: &str) -> String {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let result = vm.run(&Arc::new(module)).expect("run");
|
||||
format_value(vm, result)
|
||||
}
|
||||
|
||||
fn format_value(vm: &Vm, val: JsValue) -> String {
|
||||
if val.is_string() {
|
||||
vm.lookup_str(val).expect("串值").to_string()
|
||||
} else if val.is_object() {
|
||||
// SAFETY: 完成值对象由 VM 自有,本 session 内有效。
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
if obj.is_promise_obj() {
|
||||
match promise_settled_value(obj) {
|
||||
Some((true, v)) => format_value(vm, v),
|
||||
Some((false, v)) => format!("rejected:{}", format_value(vm, v)),
|
||||
None => "<pending>".to_string(),
|
||||
}
|
||||
} else {
|
||||
format!("{val:?}")
|
||||
}
|
||||
} else {
|
||||
format!("{val:?}")
|
||||
}
|
||||
}
|
||||
|
||||
/// 对象元素数组跨执行期收集读回:元素经 epoch 晋升 session,churn 制造
|
||||
/// 死 session 对象供原地 sweep;替换后元素值与串载荷逐一比对。
|
||||
#[test]
|
||||
fn inrun_array_element_readback_after_churn() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"var a = []; \
|
||||
for (var i = 0; i < 120; i++) { a.push({ v: i, s: 'p' + i }); } \
|
||||
var t; \
|
||||
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; a[i % 120] = { v: i % 120, s: 'q' + (i % 120) }; } \
|
||||
var bad = -1; \
|
||||
for (var i = 0; i < 120; i++) { \
|
||||
if (a[i].v !== i || a[i].s !== 'q' + i) { bad = i; break; } \
|
||||
} \
|
||||
bad < 0 ? 'ok' : 'bad:' + bad",
|
||||
);
|
||||
assert_eq!(out, "ok", "元素读回应全对");
|
||||
}
|
||||
|
||||
/// 挂起生成器跨执行期收集恢复:挂起帧寄存器与状态盒边在晋升/sweep 后
|
||||
/// 仍指向存活值,恢复迭代序列与数组读回均正确。
|
||||
#[test]
|
||||
fn inrun_suspended_generator_resumes_after_collect() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"var a = []; \
|
||||
for (var i = 0; i < 80; i++) { a.push({ v: i }); } \
|
||||
function* g() { yield a[0].v; yield a[79].v; } \
|
||||
var it = g(); \
|
||||
var first = it.next().value; \
|
||||
var t; \
|
||||
for (var i = 0; i < 30000; i++) { t = { c: i }; } \
|
||||
var second = it.next().value; \
|
||||
var bad = (first !== 0 || second !== 79) ? 'gen' : ''; \
|
||||
for (var i = 0; i < 80; i++) { if (a[i].v !== i) { bad = 'arr' + i; break; } } \
|
||||
bad || 'ok'",
|
||||
);
|
||||
assert_eq!(out, "ok", "挂起恢复与数组读回应全对");
|
||||
}
|
||||
|
||||
/// 挂起帧持长串与 BigInt 边跨执行期收集:帧内边并入统一 mark 分发,
|
||||
/// 恢复后结算值读回(悬垂串/BigInt 即错值)。
|
||||
#[test]
|
||||
fn inrun_suspended_frame_string_bigint_edges() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"function* g(x, y) { \
|
||||
var t; \
|
||||
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'z' + i }; } \
|
||||
yield String(x) + y; \
|
||||
} \
|
||||
var it = g('a'.repeat(64), 10n); \
|
||||
it.next().value",
|
||||
);
|
||||
assert_eq!(out, format!("{}10", "a".repeat(64)), "挂起帧串/BigInt 边读回");
|
||||
}
|
||||
|
||||
/// 异步函数挂起帧持 BigInt 跨执行期收集恢复:结算值经微任务 drain 读回。
|
||||
#[test]
|
||||
fn inrun_async_suspended_bigint_readback() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"async function f(x) { await 0; return x + 1n; } \
|
||||
var t; \
|
||||
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; } \
|
||||
f(41n).then(function (v) { return String(v); })",
|
||||
);
|
||||
assert_eq!(out, "42", "BigInt 结算值读回");
|
||||
}
|
||||
|
||||
/// Set/Map 盒持对象键跨执行期收集:键存活、读回值与 size 正确
|
||||
/// (盒边在 mark 侧并入统一分发,悬垂即错值/崩溃)。
|
||||
#[test]
|
||||
fn inrun_native_box_edges_survive_collect() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"var s = new Set(); var m = new Map(); var keys = []; \
|
||||
for (var i = 0; i < 60; i++) { var k = { id: i }; keys.push(k); s.add(k); m.set(k, i); } \
|
||||
var t; \
|
||||
for (var i = 0; i < 30000; i++) { t = { c: i, s: 'churn' + i }; } \
|
||||
var bad = -1; \
|
||||
for (var i = 0; i < 60; i++) { \
|
||||
var v = m.get(keys[i]); \
|
||||
if (v !== i || !s.has(keys[i])) { bad = i; break; } \
|
||||
} \
|
||||
if (bad < 0 && s.size !== 60) bad = 1000; \
|
||||
bad < 0 ? 'ok' : 'bad:' + bad",
|
||||
);
|
||||
assert_eq!(out, "ok", "盒键读回应全对");
|
||||
}
|
||||
|
||||
/// 大面积 churn 制造死 session 对象(原地 sweep 释放),存活根集
|
||||
/// (global 子树 + 挂起生成器)读回不腐化。
|
||||
#[test]
|
||||
fn inrun_dead_session_sweep_keeps_live_roots() {
|
||||
let mut vm = vm_with_threshold(4096);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"globalThis.holder = []; \
|
||||
for (var i = 0; i < 100; i++) { globalThis.holder.push({ v: i }); } \
|
||||
function* g() { for (var i = 0; i < 10; i++) { yield i; } } \
|
||||
var it = g(); it.next(); \
|
||||
for (var round = 0; round < 6; round++) { \
|
||||
var next = []; \
|
||||
for (var i = 0; i < 100; i++) { next.push({ v: i + round }); } \
|
||||
globalThis.holder = next; \
|
||||
} \
|
||||
var bad = -1; \
|
||||
for (var i = 0; i < 100; i++) { if (globalThis.holder[i].v !== i + 5) { bad = i; break; } } \
|
||||
bad < 0 ? 'ok:' + it.next().value : 'bad:' + bad",
|
||||
);
|
||||
assert_eq!(out, "ok:1", "holder 读回全对且迭代器恢复");
|
||||
}
|
||||
|
||||
/// Set 串值经 values() 迭代器读回跨执行期收集:迭代器包装对象已登记
|
||||
/// epoch 对象表,晋升后不死;读回串载荷即验证整条边链。
|
||||
#[test]
|
||||
fn inrun_set_string_value_survives_collect() {
|
||||
let mut vm = vm_with_threshold(512);
|
||||
let out = eval_src(
|
||||
&mut vm,
|
||||
"(function(){ \
|
||||
var s = 'setbox'.repeat(8); \
|
||||
var st = new Set(); st.add(s); globalThis.st = st; })(); \
|
||||
for (var i = 0; i < 2000; i++) { var t = 'z'.repeat(48); } \
|
||||
globalThis.st.values().next().value",
|
||||
);
|
||||
assert_eq!(out, "setbox".repeat(8));
|
||||
}
|
||||
Reference in New Issue
Block a user