test: alloc 与 AllocMap 单测移入 regalloc 集成测试

This commit is contained in:
2026-09-15 20:25:46 +00:00
parent 2a2db136de
commit 3986aec50e
3 changed files with 148 additions and 142 deletions
-15
View File
@@ -70,18 +70,3 @@ impl Default for AllocMap {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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());
}
}
-127
View File
@@ -110,93 +110,6 @@ mod tests {
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
#[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 = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::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 = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::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 = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::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 ancestor_remap_preserves_descendant_vreg_collision() {
let mut child = IRFunction::new();
@@ -224,44 +137,4 @@ mod tests {
assert_eq!(child.nested[0].insts[0].a, Operand::Reg(7));
assert_eq!(child.nested[0].insts[1].a, Operand::Reg(300));
}
#[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 = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::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 = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::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 已回写");
}
}
+148
View File
@@ -0,0 +1,148 @@
//! 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());
}