This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_vm/tests/gc_runtime_tests.rs
T
pjh123 239309925e perf: 执行期 session 字符串 GC 阈值回收(超 32MiB 触发,跳过对象 sweep)
new_string_owned 分配前做廉价阈值检查(1 次 usize 比较 + 阈值读取),
超 session_gc_threshold 即调 collect_strings_only:clear_all_marks →
完整 mark(现成)→ sweep_session_strings(现成),跳过对象 sweep 搬移,
builtin 层对象裸指针跨分配点零悬垂面。触发检查在登记之前——返回值
尚未根化,登记后回收会被本次 GC 判死致悬垂。分配前触发亦使 GC 期间
新串仍在栈上未登记,天然安全。

长循环字符串累积场景(REPL 常驻 / test262 长测试 / kernel_batch 复用)
session 内存从无界变为有界(≤ 阈值 + 存活串);bench gc_trigger_count
恢复有效(string_build 由 0 → 2 次触发)。07.2 rope 死节点回收依赖本机制。
2026-08-15 12:12:03 +08:00

82 lines
3.3 KiB
Rust

use oxide_compiler::compiler::Compiler;
use oxide_kernel::kernel::{KernelConfig, KernelCore};
use oxide_parser::Allocator;
use oxide_vm::vm::Vm;
fn compile(source: &str) -> oxide_bytecode::module::CompiledModule {
let allocator = Allocator::default();
let program = oxide_parser::parse(&allocator, source).expect("parse");
Compiler::new().compile(&program).expect("compile")
}
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))
}
/// 长循环拼接产生海量 session 字符串:执行期自动触发仅字符串 GC,
/// 结果正确且 session 字节有界(不触发则累计 ~20 MiB 远超低阈值)。
#[test]
fn long_string_loop_triggers_runtime_gc_and_bounds_session() {
let mut vm = vm_with_threshold(4096);
let module = compile("var s; for (var i = 0; i < 500000; i++) { s = 'str' + i; } s.length");
let result = vm.run(&module).expect("run");
assert_eq!(format!("{}", result), "9");
assert!(vm.session_gc_stats().total_collections > 0, "执行期应触发字符串 GC");
assert!(
vm.session_bytes_allocated() < 1_000_000,
"session 字节应受阈值约束,实际 {}",
vm.session_bytes_allocated()
);
}
/// 拼接结果写 global(逃逸根),跨 run 后触发执行期 GC 仍可读——
/// 验证非会话存活串不因执行期回收丢失。
#[test]
fn session_string_survives_across_runs_with_runtime_gc() {
let mut vm = vm_with_threshold(4096);
let first = compile("globalThis.kept = 'he' + 'llo'; 0");
vm.run(&first).expect("run1");
let second = compile("var s; for (var i = 0; i < 500000; i++) { s = 'y' + i; } globalThis.kept");
let result = vm.run(&second).expect("run2");
let text = vm.lookup_str(result).expect("kept 应为字符串").to_string();
assert_eq!(text, "hello");
assert!(vm.session_gc_stats().total_collections > 0, "run2 应触发执行期字符串 GC");
}
/// reset(轻量重置)保留 session 字符串:跨 eval 存活语义不被执行期 GC 破坏。
#[test]
fn reset_preserves_session_strings_after_runtime_gc() {
let mut vm = vm_with_threshold(4096);
let first = compile("globalThis.kept = 'x' + 'y'; 0");
vm.run(&first).expect("run1");
vm.reset();
let second = compile("globalThis.kept");
let result = vm.run(&second).expect("run2");
let text = vm.lookup_str(result).expect("kept 应为字符串").to_string();
assert_eq!(text, "xy");
}
/// full_reset 清空 session 内存:执行期回收释放过的死串不与完全重置的
/// 整体释放路径重复释放(无双重释放崩溃),重置后引擎可继续运行。
#[test]
fn full_reset_clears_session_after_runtime_gc() {
let mut vm = vm_with_threshold(4096);
let first = compile("var s; for (var i = 0; i < 200000; i++) { s = 'a' + i; } s.length");
vm.run(&first).expect("run1");
assert!(vm.session_gc_stats().total_collections > 0, "run1 应触发执行期字符串 GC");
vm.full_reset();
assert_eq!(vm.session_bytes_allocated(), 0, "full_reset 后 session 字节应为 0");
let second = compile("'ok' + '!'");
let result = vm.run(&second).expect("run2");
let text = vm.lookup_str(result).expect("拼接结果应为字符串").to_string();
assert_eq!(text, "ok!");
}