From 263bdb5d270aa09f854aee011c8dc625170aedc2 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 17:50:18 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=E5=86=85=E8=81=94=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=A7=BB=E5=85=A5=20oxide=5Fdce=20=E9=9B=86=E6=88=90=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_dce/src/lib.rs | 58 ----- crates/oxide_dce/src/precise_sweep.rs | 247 -------------------- crates/oxide_dce/tests/conservative_unit.rs | 57 +++++ crates/oxide_dce/tests/precise_unit.rs | 247 ++++++++++++++++++++ 4 files changed, 304 insertions(+), 305 deletions(-) create mode 100644 crates/oxide_dce/tests/conservative_unit.rs create mode 100644 crates/oxide_dce/tests/precise_unit.rs diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index 3108baf8..487bda70 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -56,61 +56,3 @@ pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { // mark-sweep 一次性重建 rebuild::pass_c_sweep(f, &keep); } - -#[cfg(test)] -mod tests { - use super::*; - use oxide_bytecode::opcode::OpCode; - use oxide_ir::inst::Inst; - use oxide_ir::operand::Operand; - - /// 空函数退化:insts 空时 dce 直接返回,状态不变。 - #[test] - fn empty_function_unchanged() { - let mut f = IRFunction::new(); - dce(&mut f); - assert!(f.insts.is_empty()); - assert!(f.label_pos.is_empty()); - assert_eq!(f.label_count, 0); - } - - /// 幂等性:dce 两次结果一致(不动点后无二次改写)。 - #[test] - fn dce_is_idempotent() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: 死 - f.insts - .push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1))); // 1: 死 - f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 2: 存活(CALL 保活 r0) - f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 3 - f.insts - .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 - f.label_pos = vec![Some(0)]; - - dce(&mut f); - let once_insts = f.insts.clone(); - let once_labels = f.label_pos.clone(); - dce(&mut f); - assert_eq!(f.insts, once_insts, "二次 dce 后 insts 不变"); - assert_eq!(f.label_pos, once_labels, "二次 dce 后 label_pos 不变"); - } - - /// if/else 双分支完整链路:Jump + Fallthrough 双出边全可达,指令全保留,label 重映射正确。 - #[test] - fn if_else_both_branches_reachable_kept() { - let mut f = IRFunction::new(); - f.insts.push(Inst::jmp_if_false(0, 0)); // 0: cond → L0(else) - f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then - f.insts.push(Inst::jmp(1)); // 2: → L1(end) - f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1)); // 3: else - f.insts - .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 - f.label_pos = vec![Some(3), Some(4)]; - f.label_count = 2; - - dce(&mut f); - assert_eq!(f.insts.len(), 5, "双分支全保留"); - assert_eq!(f.label_pos, vec![Some(3), Some(4)], "存活 label 重映射到新下标"); - assert_eq!(f.label_count, 2, "label_count 保持原值,不参与重建"); - } -} diff --git a/crates/oxide_dce/src/precise_sweep.rs b/crates/oxide_dce/src/precise_sweep.rs index 73ac4443..a7805b2d 100644 --- a/crates/oxide_dce/src/precise_sweep.rs +++ b/crates/oxide_dce/src/precise_sweep.rs @@ -65,250 +65,3 @@ pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mu } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::dce_precise; - use oxide_ir::inst::Inst; - - /// helper:稀疏号集 → u64 位集 live_after。 - fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec> { - let words = (reg_count + 1).div_ceil(64); - after - .iter() - .map(|set| { - let mut v = vec![0u64; words]; - for &r in *set { - v[(r as usize) >> 6] |= 1u64 << ((r as usize) & 63); - } - v - }) - .collect() - } - - fn run_precise(f: &mut IRFunction, after: &[&[u32]], reg_count: usize) { - let mut live = LiveInfo::new(); - live.inst_live_after = live_sets(after, reg_count); - dce_precise(f, &live); - } - - /// 精确轮标志性测试:寄存器复用后被后写杀死的死写(保守 use 计数抓不到)。 - #[test] - fn reuse_killed_dead_write_deleted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::ADD, - Operand::Reg(5), - Operand::Reg(1), - Operand::Reg(2), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::ADD, - Operand::Reg(5), - Operand::Reg(3), - Operand::Reg(4), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - // inst_live_after = [{3,4}, {5}, {}]:inst0 def 5 在 live_after[0] 无 5(后写杀死) - run_precise(&mut f, &[&[3, 4], &[5], &[]], 5); - assert_eq!(f.insts.len(), 2, "死写(前一个 ADD)应被删"); - } - - /// 局部死 STORE_VAR 删除(四条件全满足)。 - #[test] - fn local_dead_store_var_deleted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(0), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - // is_top_level 默认 false;inst_live_after = [{1,5}, {5}, {}] - run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 2, "局部死 STORE_VAR 应被删(LOAD_CONST 单遍不级联保留)"); - } - - /// 顶层 STORE_VAR 永不删(顶层赋值全局可观察)。 - #[test] - fn top_level_store_var_kept() { - let mut f = IRFunction::new(); - f.is_top_level = true; - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(0), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 3, "顶层 STORE_VAR 不可删"); - } - - /// escaped 槽 STORE_VAR 永不删(nested 直读)。 - #[test] - fn escaped_slot_store_var_kept() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(0), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - let mut sub = IRFunction::new(); - sub.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::LOAD_VAR, - Operand::Reg(5), - Operand::Reg(2), - Operand::None, - )); - f.nested.push(sub); - run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 3, "被嵌套函数直引的 escaped 槽 STORE_VAR 不可删"); - } - - /// const 赋值路径 b=1 保留(运行时抛 TypeError 可观察)。 - #[test] - fn const_guard_store_var_kept() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(1), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 3, "b=1 const 赋值路径不可删"); - } - - /// label 目标指令永不删。 - #[test] - fn label_target_inst_never_deleted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // label 0 目标,纯且结果未用 - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - f.label_pos = vec![Some(0)]; - run_precise(&mut f, &[&[5], &[]], 5); - assert_eq!(f.insts.len(), 2, "label 目标指令不可删"); - } - - /// SPILL/UNSPILL 永不删(is_pure=false);活 MOV 保留。 - #[test] - fn spill_unspill_never_deleted_mov_kept_when_live() { - let mut f = IRFunction::new(); - f.insts.push(Inst::inst_spill(Operand::Reg(1), 0)); - f.insts.push(Inst::inst_unspill(Operand::Reg(2), 0)); - f.insts.push(Inst::inst_mov(Operand::Reg(3), Operand::Reg(2))); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(3), - Operand::None, - Operand::None, - )); - run_precise(&mut f, &[&[], &[2], &[3], &[]], 5); - assert_eq!(f.insts.len(), 4, "SPILL/UNSPILL 永不删,活 MOV 保留"); - } - - /// 过期 LiveInfo 维度守卫:直接 return 零删除。 - #[test] - fn stale_liveinfo_no_op() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(0), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - let live = LiveInfo::new(); // 空 LiveInfo:维度不符 - dce_precise(&mut f, &live); - assert_eq!(f.insts.len(), 3, "过期 LiveInfo 不得删除任何指令"); - } - - /// 收敛性:单遍不级联设计下,重复跑会逐轮收敛(删 STORE_VAR 后其源 LOAD_CONST 变死), - /// 收敛到不动点后不再改变。 - #[test] - fn dce_precise_converges() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::STORE_VAR, - Operand::Reg(2), - Operand::Reg(1), - Operand::Imm(0), - )); - f.insts.push(Inst::new( - oxide_bytecode::opcode::OpCode::RETURN, - Operand::Reg(5), - Operand::None, - Operand::None, - )); - // 第一遍:手填 live(STORE_VAR 死) - run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); - assert_eq!(f.insts.len(), 2, "第一遍删 STORE_VAR"); - // 第二遍:重算 live → LOAD_CONST 变死 - let cfg = oxide_cfg::build_cfg(&f); - let live = oxide_liveness::liveness(&f, &cfg); - dce_precise(&mut f, &live); - assert_eq!(f.insts.len(), 1, "第二遍删 LOAD_CONST(级联收敛)"); - let after2 = f.insts.clone(); - // 第三遍:不动点,不再改变 - let cfg = oxide_cfg::build_cfg(&f); - let live = oxide_liveness::liveness(&f, &cfg); - dce_precise(&mut f, &live); - assert_eq!(f.insts, after2, "第三遍不再删除(收敛)"); - } - - /// 空函数退化。 - #[test] - fn empty_function_unchanged() { - let mut f = IRFunction::new(); - let live = LiveInfo::new(); - dce_precise(&mut f, &live); - assert!(f.insts.is_empty()); - } -} diff --git a/crates/oxide_dce/tests/conservative_unit.rs b/crates/oxide_dce/tests/conservative_unit.rs new file mode 100644 index 00000000..85152a5d --- /dev/null +++ b/crates/oxide_dce/tests/conservative_unit.rs @@ -0,0 +1,57 @@ +//! 保守轮单元断言:空函数退化、幂等性、双分支可达保留。 + +use oxide_bytecode::opcode::OpCode; +use oxide_dce::dce; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; + +/// 空函数退化:insts 空时 dce 直接返回,状态不变。 +#[test] +fn empty_function_unchanged() { + let mut f = IRFunction::new(); + dce(&mut f); + assert!(f.insts.is_empty()); + assert!(f.label_pos.is_empty()); + assert_eq!(f.label_count, 0); +} + +/// 幂等性:dce 两次结果一致(不动点后无二次改写)。 +#[test] +fn dce_is_idempotent() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: 死 + f.insts + .push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(1), Operand::Reg(1))); // 1: 死 + f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 2: 存活(CALL 保活 r0) + f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 3 + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 + f.label_pos = vec![Some(0)]; + + dce(&mut f); + let once_insts = f.insts.clone(); + let once_labels = f.label_pos.clone(); + dce(&mut f); + assert_eq!(f.insts, once_insts, "二次 dce 后 insts 不变"); + assert_eq!(f.label_pos, once_labels, "二次 dce 后 label_pos 不变"); +} + +/// if/else 双分支完整链路:Jump + Fallthrough 双出边全可达,指令全保留,label 重映射正确。 +#[test] +fn if_else_both_branches_reachable_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::jmp_if_false(0, 0)); // 0: cond → L0(else) + f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then + f.insts.push(Inst::jmp(1)); // 2: → L1(end) + f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1)); // 3: else + f.insts + .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 + f.label_pos = vec![Some(3), Some(4)]; + f.label_count = 2; + + dce(&mut f); + assert_eq!(f.insts.len(), 5, "双分支全保留"); + assert_eq!(f.label_pos, vec![Some(3), Some(4)], "存活 label 重映射到新下标"); + assert_eq!(f.label_count, 2, "label_count 保持原值,不参与重建"); +} diff --git a/crates/oxide_dce/tests/precise_unit.rs b/crates/oxide_dce/tests/precise_unit.rs new file mode 100644 index 00000000..3d9971f7 --- /dev/null +++ b/crates/oxide_dce/tests/precise_unit.rs @@ -0,0 +1,247 @@ +//! 精确轮单元断言:寄存器复用死写、STORE_VAR 特例、label 目标、收敛行为。 + +use oxide_dce::dce_precise; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; +use oxide_liveness::LiveInfo; + +/// helper:稀疏号集 → u64 位集 live_after。 +fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec> { + let words = (reg_count + 1).div_ceil(64); + after + .iter() + .map(|set| { + let mut v = vec![0u64; words]; + for &r in *set { + v[(r as usize) >> 6] |= 1u64 << ((r as usize) & 63); + } + v + }) + .collect() +} + +fn run_precise(f: &mut IRFunction, after: &[&[u32]], reg_count: usize) { + let mut live = LiveInfo::new(); + live.inst_live_after = live_sets(after, reg_count); + dce_precise(f, &live); +} + +/// 精确轮标志性测试:寄存器复用后被后写杀死的死写(保守 use 计数抓不到)。 +#[test] +fn reuse_killed_dead_write_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::ADD, + Operand::Reg(5), + Operand::Reg(1), + Operand::Reg(2), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::ADD, + Operand::Reg(5), + Operand::Reg(3), + Operand::Reg(4), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + // inst_live_after = [{3,4}, {5}, {}]:inst0 def 5 在 live_after[0] 无 5(后写杀死) + run_precise(&mut f, &[&[3, 4], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "死写(前一个 ADD)应被删"); +} + +/// 局部死 STORE_VAR 删除(四条件全满足)。 +#[test] +fn local_dead_store_var_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + // is_top_level 默认 false;inst_live_after = [{1,5}, {5}, {}] + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "局部死 STORE_VAR 应被删(LOAD_CONST 单遍不级联保留)"); +} + +/// 顶层 STORE_VAR 永不删(顶层赋值全局可观察)。 +#[test] +fn top_level_store_var_kept() { + let mut f = IRFunction::new(); + f.is_top_level = true; + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "顶层 STORE_VAR 不可删"); +} + +/// escaped 槽 STORE_VAR 永不删(nested 直读)。 +#[test] +fn escaped_slot_store_var_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + let mut sub = IRFunction::new(); + sub.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::LOAD_VAR, + Operand::Reg(5), + Operand::Reg(2), + Operand::None, + )); + f.nested.push(sub); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "被嵌套函数直引的 escaped 槽 STORE_VAR 不可删"); +} + +/// const 赋值路径 b=1 保留(运行时抛 TypeError 可观察)。 +#[test] +fn const_guard_store_var_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(1), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "b=1 const 赋值路径不可删"); +} + +/// label 目标指令永不删。 +#[test] +fn label_target_inst_never_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // label 0 目标,纯且结果未用 + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + f.label_pos = vec![Some(0)]; + run_precise(&mut f, &[&[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "label 目标指令不可删"); +} + +/// SPILL/UNSPILL 永不删(is_pure=false);活 MOV 保留。 +#[test] +fn spill_unspill_never_deleted_mov_kept_when_live() { + let mut f = IRFunction::new(); + f.insts.push(Inst::inst_spill(Operand::Reg(1), 0)); + f.insts.push(Inst::inst_unspill(Operand::Reg(2), 0)); + f.insts.push(Inst::inst_mov(Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(3), + Operand::None, + Operand::None, + )); + run_precise(&mut f, &[&[], &[2], &[3], &[]], 5); + assert_eq!(f.insts.len(), 4, "SPILL/UNSPILL 永不删,活 MOV 保留"); +} + +/// 过期 LiveInfo 维度守卫:直接 return 零删除。 +#[test] +fn stale_liveinfo_no_op() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + let live = LiveInfo::new(); // 空 LiveInfo:维度不符 + dce_precise(&mut f, &live); + assert_eq!(f.insts.len(), 3, "过期 LiveInfo 不得删除任何指令"); +} + +/// 收敛性:单遍不级联设计下,重复跑会逐轮收敛(删 STORE_VAR 后其源 LOAD_CONST 变死), +/// 收敛到不动点后不再改变。 +#[test] +fn dce_precise_converges() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::STORE_VAR, + Operand::Reg(2), + Operand::Reg(1), + Operand::Imm(0), + )); + f.insts.push(Inst::new( + oxide_bytecode::opcode::OpCode::RETURN, + Operand::Reg(5), + Operand::None, + Operand::None, + )); + // 第一遍:手填 live(STORE_VAR 死) + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "第一遍删 STORE_VAR"); + // 第二遍:重算 live → LOAD_CONST 变死 + let cfg = oxide_cfg::build_cfg(&f); + let live = oxide_liveness::liveness(&f, &cfg); + dce_precise(&mut f, &live); + assert_eq!(f.insts.len(), 1, "第二遍删 LOAD_CONST(级联收敛)"); + let after2 = f.insts.clone(); + // 第三遍:不动点,不再改变 + let cfg = oxide_cfg::build_cfg(&f); + let live = oxide_liveness::liveness(&f, &cfg); + dce_precise(&mut f, &live); + assert_eq!(f.insts, after2, "第三遍不再删除(收敛)"); +} + +/// 空函数退化。 +#[test] +fn empty_function_unchanged() { + let mut f = IRFunction::new(); + let live = LiveInfo::new(); + dce_precise(&mut f, &live); + assert!(f.insts.is_empty()); +}