feat(regalloc): 指令改写 + 元数据回写 + alloc 入口,RegAlloc on/off 运行时等价(D-16/D-20)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
//! REG-04 断言:RegAlloc on ≤253 且 < off(寄存器收缩);B005 大函数 on 编译成功。
|
||||
|
||||
fn n_registers(src: &str, regalloc: bool) -> Result<u8, 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).expect("emit failed");
|
||||
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)?;
|
||||
// 返回模块树最大 n_registers(嵌套函数是实际寄存器压力来源)
|
||||
Ok(module_max_n_registers(&module))
|
||||
}
|
||||
|
||||
fn module_max_n_registers(m: &oxide_bytecode::module::CompiledModule) -> u8 {
|
||||
let mut max = m.n_registers;
|
||||
for sub in &m.sub_modules {
|
||||
max = max.max(module_max_n_registers(sub));
|
||||
}
|
||||
max
|
||||
}
|
||||
|
||||
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 个短活临时值 → L degree ≥ k → spill L → fresh 单点可着色。
|
||||
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
|
||||
}
|
||||
|
||||
/// 中等函数 on ≤253 且 < off(寄存器复用收缩)。
|
||||
#[test]
|
||||
fn n_registers_shrinks_with_regalloc() {
|
||||
let samples = [
|
||||
gen_sum_function(50),
|
||||
"function f(a, b, c) { return a * 100 + b * 10 + c; } function g() { return f(1, 2, 3); } g();".to_string(),
|
||||
"function f(n) { var s = 0; for (var i = 0; i < n; i++) { s += i * i; } return s; } f(10);".to_string(),
|
||||
"function f() { try { throw 1; } catch (e) { return e + 1; } } f();".to_string(),
|
||||
"function f() { var x = 1; return function() { return x++; }; } var g = f(); g();".to_string(),
|
||||
];
|
||||
for src in &samples {
|
||||
let on = n_registers(src, true).expect("on 应成功");
|
||||
let off = n_registers(src, false).expect("off 应成功");
|
||||
assert!(on <= 253, "on ≤253: {on} for {src}");
|
||||
assert!(on < off, "on({on}) < off({off}) for {src}");
|
||||
}
|
||||
}
|
||||
|
||||
/// B005 大函数:on 编译成功 ≤253;off 报 RangeError(B005 解决证据)。
|
||||
#[test]
|
||||
fn b005_large_function_fits_in_253() {
|
||||
let src = gen_sum_function(220);
|
||||
let on = n_registers(&src, true);
|
||||
assert!(on.is_ok(), "B005 样例 on 应编译成功");
|
||||
assert!(on.unwrap() <= 253, "on ≤253");
|
||||
let off = n_registers(&src, false);
|
||||
assert!(off.is_err(), "B005 样例 off 应报 RangeError");
|
||||
assert!(off.unwrap_err().contains("too many registers"), "off Err 消息");
|
||||
}
|
||||
|
||||
/// spill 压力样例(长活 L + 254 短活)on ≤253 且成功。
|
||||
#[test]
|
||||
fn spill_sample_under_limit() {
|
||||
let src = gen_spill_function(254);
|
||||
let on = n_registers(&src, true);
|
||||
assert!(on.is_ok(), "spill 样例 on 应编译成功: {on:?}");
|
||||
assert!(on.unwrap() <= 253, "on ≤253");
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//! RegAlloc on/off 运行时等价(D-21 主验证)+ B005/spill 样例运行正确(REG-04)。
|
||||
//!
|
||||
//! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(regalloc on/off)→ lower → run,
|
||||
//! 对比顶层执行结果。**不经 Compiler::compile**(测试层直调底层函数)。
|
||||
|
||||
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).expect("emit failed");
|
||||
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");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&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(B011 回归)
|
||||
"function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);",
|
||||
// 类字段(B012 行为锚)
|
||||
"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();",
|
||||
// 闭包(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
|
||||
}
|
||||
|
||||
/// B005 大函数(200+ 变量)与 spill 压力样例 on 运行正确。
|
||||
/// off 路径 lower 失败(B005 上限)——不能做 on/off 相等,on 运行正确即 REG-04 证据。
|
||||
#[test]
|
||||
fn b005_and_spill_samples_run_correctly() {
|
||||
// B005:220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决
|
||||
let src_b005 = gen_sum_function(220);
|
||||
let expected_b005 = 220 * 219 / 2;
|
||||
let out = run_source(&src_b005, true).expect("B005 样例 on 应编译成功");
|
||||
assert_eq!(out.to_string(), expected_b005.to_string(), "B005 样例结果错误");
|
||||
|
||||
// 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).expect("emit");
|
||||
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");
|
||||
}
|
||||
Reference in New Issue
Block a user