mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
149 lines
5.2 KiB
Rust
149 lines
5.2 KiB
Rust
//! alloc 改写 pass 集成测试:经 crate 根 pub 入口 alloc() 验证空函数退化/过期 LiveInfo
|
|
//! 重跑/参数段重映射/嵌套槽传播/Err 传播/嵌套递归改写,及 AllocMap 构造零值。
|
|
|
|
use oxide_bytecode::opcode::OpCode;
|
|
use oxide_cfg::build_cfg;
|
|
use oxide_ir::inst::Inst;
|
|
use oxide_ir::operand::Operand;
|
|
use oxide_ir::IRFunction;
|
|
use oxide_liveness::liveness;
|
|
use oxide_liveness::LiveInfo;
|
|
use oxide_regalloc::{alloc, AllocMap};
|
|
|
|
#[test]
|
|
fn alloc_empty_ok() {
|
|
let mut f = IRFunction::new();
|
|
assert!(alloc(&mut f, &LiveInfo::new()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_stale_live_reruns() {
|
|
let mut f = IRFunction::new();
|
|
f.insts
|
|
.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
|
|
// 空 LiveInfo(维度不符)→ 内部重跑
|
|
let stale = LiveInfo::new();
|
|
assert!(alloc(&mut f, &stale).is_ok());
|
|
// 重写后全部槽 ≤253
|
|
for inst in &f.insts {
|
|
for o in [&inst.rd, &inst.a, &inst.b] {
|
|
if let Operand::Reg(r) = o {
|
|
assert!(*r <= 253, "重写后槽 {r} 超 253");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_remaps_high_parameter_segment() {
|
|
let mut f = IRFunction::new();
|
|
f.param_layout = oxide_ir::ParamLayout { base: 300, count: 1 };
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(300), Operand::None, Operand::None));
|
|
let cfg = build_cfg(&f);
|
|
let live = liveness(&f, &cfg);
|
|
alloc(&mut f, &live).unwrap();
|
|
assert_eq!(f.param_layout, oxide_ir::ParamLayout { base: 1, count: 1 });
|
|
assert!(matches!(f.insts[0].rd, Operand::Reg(1)));
|
|
assert_eq!(f.n_registers, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_propagates_moved_parameter_to_nested_slot_reads() {
|
|
let mut f = IRFunction::new();
|
|
f.param_layout = oxide_ir::ParamLayout { base: 300, count: 1 };
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(300), Operand::None, Operand::None));
|
|
let mut child = IRFunction::new();
|
|
child.param_layout = oxide_ir::ParamLayout { base: 301, count: 0 };
|
|
child
|
|
.insts
|
|
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(302), Operand::Reg(300), Operand::None));
|
|
child
|
|
.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(302), Operand::None, Operand::None));
|
|
f.nested.push(child);
|
|
let cfg = build_cfg(&f);
|
|
let live = liveness(&f, &cfg);
|
|
alloc(&mut f, &live).unwrap();
|
|
assert!(matches!(f.nested[0].insts[0].a, Operand::Reg(1)));
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_propagates_high_escaped_slot_to_nested_reads() {
|
|
let mut f = IRFunction::new();
|
|
f.insts.push(Inst::load_const(Operand::Reg(254), 0));
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
|
|
let mut child = IRFunction::new();
|
|
child.param_layout = oxide_ir::ParamLayout { base: 300, count: 0 };
|
|
child
|
|
.insts
|
|
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(301), Operand::Reg(254), Operand::None));
|
|
child
|
|
.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(301), Operand::None, Operand::None));
|
|
f.nested.push(child);
|
|
let cfg = build_cfg(&f);
|
|
let live = liveness(&f, &cfg);
|
|
alloc(&mut f, &live).unwrap();
|
|
let physical = match f.insts[0].rd {
|
|
Operand::Reg(reg) => reg,
|
|
_ => panic!("LOAD_CONST 目标应为寄存器"),
|
|
};
|
|
assert!((1..=253).contains(&physical));
|
|
assert!(matches!(f.nested[0].insts[0].a, Operand::Reg(reg) if reg == physical));
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_err_propagates() {
|
|
// 255 参数 CALL:参数窗口吞并全部可分配色 → k=0 → 无可行染色 → Err
|
|
let mut f = IRFunction::new();
|
|
f.insts
|
|
.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3000), 255));
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
|
|
let cfg = build_cfg(&f);
|
|
let live = liveness(&f, &cfg);
|
|
let err = alloc(&mut f, &live).unwrap_err();
|
|
assert!(err.contains("too many registers"), "Err: {err}");
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_recurses_nested() {
|
|
let mut f = IRFunction::new();
|
|
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
|
|
f.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
|
|
let mut sub = IRFunction::new();
|
|
sub.insts.push(Inst::load_const(Operand::Reg(3), 0));
|
|
sub.insts
|
|
.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
|
|
f.nested.push(sub);
|
|
let cfg = build_cfg(&f);
|
|
let live = liveness(&f, &cfg);
|
|
alloc(&mut f, &live).unwrap();
|
|
// 父与子函数全部槽 ≤253
|
|
for inst in &f.insts {
|
|
for o in [&inst.rd, &inst.a, &inst.b] {
|
|
if let Operand::Reg(r) = o {
|
|
assert!(*r <= 253);
|
|
}
|
|
}
|
|
}
|
|
assert!(!f.nested[0].insts.is_empty(), "子函数也被重写");
|
|
assert!(f.nested[0].n_registers <= 253, "子函数 n_registers 已回写");
|
|
}
|
|
|
|
#[test]
|
|
fn alloc_map_new_is_empty() {
|
|
let m = AllocMap::new();
|
|
assert!(m.map.is_empty());
|
|
assert!(m.spills.is_empty());
|
|
assert_eq!(m.phys_peak, 0);
|
|
assert_eq!(m.arg_window_base, 254);
|
|
assert!(AllocMap::default().map.is_empty());
|
|
}
|