mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
153 lines
6.8 KiB
Rust
153 lines
6.8 KiB
Rust
//! RegAlloc on/off 运行时等价(主验证)+ 大函数/spill 样例运行正确。
|
||
//!
|
||
//! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(regalloc on/off)→ lower → run,
|
||
//! 对比顶层执行结果。**不经 Compiler::compile**(测试层直调底层函数)。
|
||
|
||
use std::sync::Arc;
|
||
|
||
use oxide_bytecode::opcode::OpCode;
|
||
use oxide_vm::vm::Vm;
|
||
use oxide_vm::JsValue;
|
||
|
||
/// parse→emit→dce→(regalloc?)→lower→run 全链路。
|
||
fn run_source(src: &str, regalloc: bool) -> Result<JsValue, String> {
|
||
let allocator = oxide_parser::Allocator::default();
|
||
let program = oxide_parser::parse(&allocator, src).expect("parse failed");
|
||
let mut ir = oxide_emit::Emitter::new()
|
||
.emit_program(&program, false, false)
|
||
.expect("emit failed");
|
||
let _ = oxide_dce::dce(&mut ir);
|
||
if regalloc {
|
||
let cfg = oxide_cfg::build_cfg(&ir);
|
||
let live = oxide_liveness::liveness(&ir, &cfg);
|
||
oxide_dce::dce_precise(&mut ir, &live);
|
||
let cfg2 = oxide_cfg::build_cfg(&ir);
|
||
let live2 = oxide_liveness::liveness(&ir, &cfg2);
|
||
oxide_regalloc::alloc(&mut ir, &live2)?;
|
||
}
|
||
let module = oxide_ir::lower::lower(&ir).expect("lower failed");
|
||
// 测试走底层链路(不经 Compiler::compile),需手动扁平化子模块,
|
||
// 否则 CREATE_CLOSURE 的相对索引在运行期越界。
|
||
let mut module = module;
|
||
oxide_compiler::flatten::flatten_submodules(&mut module);
|
||
let mut vm = Vm::new();
|
||
vm.run(&Arc::new(module))
|
||
}
|
||
|
||
/// 结果规约成可断言形态:`(是否 Ok, 字符串化结果或错误消息)`。
|
||
fn normalize(r: Result<JsValue, String>) -> (bool, String) {
|
||
match r {
|
||
Ok(v) => (true, v.to_string()),
|
||
Err(e) => (false, e),
|
||
}
|
||
}
|
||
|
||
/// 主等价断言:RegAlloc on/off 运行时结果一致。
|
||
#[test]
|
||
fn regalloc_preserves_semantics() {
|
||
let samples = [
|
||
// switch+const(const guard 回归)
|
||
"function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);",
|
||
// 类字段(escaped 槽行为锚)
|
||
"var x = 0; class C { p = (x = 1); } new C(); x;",
|
||
// try-catch
|
||
"function f() { try { throw 1; } catch (e) { return e + 1; } } f();",
|
||
// 嵌套调用(参数连续性压力)
|
||
"function f(a, b, c) { return a * 100 + b * 10 + c; } function g() { return f(1, 2, 3) + f(4, 5, 6); } g();",
|
||
// 循环(label 重指向压力)
|
||
"function f(n) { var s = 0; for (var i = 0; i < n; i++) { s += i * i; } return s; } f(10);",
|
||
// 解构
|
||
"function f() { var [a, b] = [1, 2]; var { c, d } = { c: 3, d: 4 }; return a + b + c + d; } f();",
|
||
// elision 解构赋值:返回值寄存器与 FOR_OF_NEXT 结果须异色(def 干涉边回归)
|
||
"var result; var vals = [1, 2, 3]; result = [,] = vals; result[0] + result[1] + result[2];",
|
||
"var result; var vals = [1, 2, 3]; var x, y, z; result = [x, y, z] = vals; x + y + z;",
|
||
// 嵌套数组解构(内层迭代器覆盖外层 last result,done 检查须正确)
|
||
"var x; var vals = [[1]]; var result = [[x]] = vals; x;",
|
||
// 闭包(upvalue cell 路径)
|
||
"function f() { var x = 1; return function() { return x++; }; } var g = f(); var r = [g(), g(), g()]; r[0] + r[1] + r[2];",
|
||
// 箭头函数
|
||
"var f = (a, b) => a * b; f(6, 7);",
|
||
// 内置函数(builtin vreg 移动)
|
||
"function f(a) { return Math.max(a, Math.min(2, 3)); } f(5);",
|
||
// 递归
|
||
"function f(n) { return n <= 1 ? 1 : n * f(n - 1); } f(8);",
|
||
// const 重赋值(两跑同抛)
|
||
"const x = 1; x = 2;",
|
||
];
|
||
for src in samples {
|
||
let off = normalize(run_source(src, false));
|
||
let on = normalize(run_source(src, true));
|
||
assert_eq!(on, off, "RegAlloc on/off 语义不一致: {src}");
|
||
}
|
||
}
|
||
|
||
/// 生成 N 个变量的求和函数源码(程序化,结果确定 = N*(N-1)/2)。
|
||
fn gen_sum_function(n: u32) -> String {
|
||
let mut src = String::from("function f() { ");
|
||
for i in 0..n {
|
||
src.push_str(&format!("var v{i} = {i}; "));
|
||
}
|
||
src.push_str("return ");
|
||
for i in 0..n {
|
||
if i > 0 {
|
||
src.push('+');
|
||
}
|
||
src.push_str(&format!("v{i}"));
|
||
}
|
||
src.push_str("; } f();");
|
||
src
|
||
}
|
||
|
||
/// 长活 L + n 次短活:L 干扰 n 个短活临时值 → 触发 spill(L degree ≥ k)。
|
||
fn gen_spill_function(n: u32) -> String {
|
||
let mut src = String::from("function f(a) { var L = a; var s = 0; ");
|
||
for _ in 0..n {
|
||
src.push_str("s = s + L; ");
|
||
}
|
||
src.push_str("return s; } f(1);");
|
||
src
|
||
}
|
||
|
||
/// 大函数(200+ 变量)与 spill 压力样例 on 运行正确。
|
||
/// off 路径 lower 失败(vreg 超 253 上限)——不能做 on/off 相等,on 运行正确即
|
||
/// 寄存器复用与 spill 的证据。
|
||
#[test]
|
||
fn large_and_spill_samples_run_correctly() {
|
||
// 220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决
|
||
let src_large = gen_sum_function(220);
|
||
let expected_large = 220 * 219 / 2;
|
||
let out = run_source(&src_large, true).expect("大函数样例 on 应编译成功");
|
||
assert_eq!(out.to_string(), expected_large.to_string(), "大函数样例结果错误");
|
||
|
||
// spill 压力:长活 L + 254 短活 → L 溢出,验证 SPILL/UNSPILL 真实发生且结果正确
|
||
let src_spill = gen_spill_function(254);
|
||
let out = run_source(&src_spill, true).expect("spill 样例 on 应编译成功");
|
||
assert_eq!(out.to_string(), "254", "spill 样例结果错误");
|
||
|
||
// 字节码扫描:spill 样例经 alloc + lower 后应含 SPILL 与 UNSPILL opcode
|
||
let allocator = oxide_parser::Allocator::default();
|
||
let program = oxide_parser::parse(&allocator, &src_spill).expect("parse");
|
||
let mut ir = oxide_emit::Emitter::new().emit_program(&program, false, false).expect("emit");
|
||
let _ = oxide_dce::dce(&mut ir);
|
||
let cfg = oxide_cfg::build_cfg(&ir);
|
||
let live = oxide_liveness::liveness(&ir, &cfg);
|
||
oxide_dce::dce_precise(&mut ir, &live);
|
||
let cfg2 = oxide_cfg::build_cfg(&ir);
|
||
let live2 = oxide_liveness::liveness(&ir, &cfg2);
|
||
oxide_regalloc::alloc(&mut ir, &live2).expect("alloc");
|
||
let module = oxide_ir::lower::lower(&ir).expect("lower");
|
||
// 递归扫描模块树(spill 发生在嵌套函数 f 中,非顶层 f(1) 调用)
|
||
fn has_op(m: &oxide_bytecode::module::CompiledModule, target: OpCode) -> bool {
|
||
if m.bytecode
|
||
.iter()
|
||
.any(|&instr| OpCode::try_from(instr as u8).ok() == Some(target))
|
||
{
|
||
return true;
|
||
}
|
||
m.sub_modules.iter().any(|s| has_op(s, target))
|
||
}
|
||
let has_spill = has_op(&module, OpCode::SPILL);
|
||
let has_unspill = has_op(&module, OpCode::UNSPILL);
|
||
assert!(has_spill && has_unspill, "spill 样例应真实插入 SPILL/UNSPILL");
|
||
}
|