From c49cdbf9c57ba3462b57c138666b1a85411c9555 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 13 Sep 2026 00:41:48 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=BF=AD=E4=BB=A3=E5=99=A8=E5=8C=85?= =?UTF-8?q?=E8=A3=85=E5=AF=B9=E8=B1=A1=E5=88=86=E9=85=8D=E8=B5=B0=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E5=85=A5=E5=8F=A3=E7=99=BB=E8=AE=B0=20epoch=20?= =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E8=A1=A8=E2=80=94=E2=80=94=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=9C=9F=20epoch=20=E6=8D=A2=E6=96=B0=E5=8F=AA=E6=99=8B?= =?UTF-8?q?=E5=8D=87=E8=A1=A8=E5=86=85=E5=AF=B9=E8=B1=A1=EF=BC=8C=E7=BB=95?= =?UTF-8?q?=E8=BF=87=E7=99=BB=E8=AE=B0=E7=9A=84=E5=8C=85=E8=A3=85=E5=99=A8?= =?UTF-8?q?/=E7=BB=93=E6=9E=9C=E5=AF=B9=E8=B1=A1=E5=9C=A8=E6=8D=A2?= =?UTF-8?q?=E6=96=B0=E5=90=8E=E6=82=AC=E5=9E=82=EF=BC=8C=E9=97=AD=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array.rs | 4 +- crates/oxide_builtins/src/iterator.rs | 16 +- crates/oxide_builtins/src/regexp.rs | 4 +- crates/oxide_builtins/src/string.rs | 8 +- crates/oxide_vm/tests/probe_b154_tests.rs | 191 ++++++++++++++++++++++ 5 files changed, 199 insertions(+), 24 deletions(-) create mode 100644 crates/oxide_vm/tests/probe_b154_tests.rs diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index e70b7ba5..d0861cfe 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -1755,9 +1755,7 @@ pub(crate) fn make_array_iterator(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; diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 177cea26..9bf0f78b 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -434,9 +434,7 @@ fn make_iterator_helper( 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( 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(vm: &mut H, wrapper: &JsObject, index_si: u32) -> us /// 构造迭代器结果对象 `{value, done}`(生成器 next/return 结果复用)。 pub fn make_iter_result(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( } 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; diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index ae03aa4a..97a9c125 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -563,9 +563,7 @@ pub fn regexp_symbol_match_all(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; diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index 9c53f9a5..548970ef 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -1522,9 +1522,7 @@ fn builder_wrapper(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(vm: &mut H, args: &[u8]) -> NativeResult fn make_match_done_result(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 }; diff --git a/crates/oxide_vm/tests/probe_b154_tests.rs b/crates/oxide_vm/tests/probe_b154_tests.rs new file mode 100644 index 00000000..3d3674df --- /dev/null +++ b/crates/oxide_vm/tests/probe_b154_tests.rs @@ -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 => "".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)); +}