From 3986aec50ec61cdea574283047e0bf313a972907 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 20:25:46 +0000 Subject: [PATCH] =?UTF-8?q?test:=20alloc=20=E4=B8=8E=20AllocMap=20?= =?UTF-8?q?=E5=8D=95=E6=B5=8B=E7=A7=BB=E5=85=A5=20regalloc=20=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_regalloc/src/alloc_map.rs | 15 --- crates/oxide_regalloc/src/lib.rs | 127 ------------------- crates/oxide_regalloc/tests/alloc_api.rs | 148 +++++++++++++++++++++++ 3 files changed, 148 insertions(+), 142 deletions(-) create mode 100644 crates/oxide_regalloc/tests/alloc_api.rs diff --git a/crates/oxide_regalloc/src/alloc_map.rs b/crates/oxide_regalloc/src/alloc_map.rs index 39b23172..adebd1af 100644 --- a/crates/oxide_regalloc/src/alloc_map.rs +++ b/crates/oxide_regalloc/src/alloc_map.rs @@ -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()); - } -} diff --git a/crates/oxide_regalloc/src/lib.rs b/crates/oxide_regalloc/src/lib.rs index 1854cf73..31c48bad 100644 --- a/crates/oxide_regalloc/src/lib.rs +++ b/crates/oxide_regalloc/src/lib.rs @@ -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 已回写"); - } } diff --git a/crates/oxide_regalloc/tests/alloc_api.rs b/crates/oxide_regalloc/tests/alloc_api.rs new file mode 100644 index 00000000..f7c46f51 --- /dev/null +++ b/crates/oxide_regalloc/tests/alloc_api.rs @@ -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()); +}