test(cfg): 各 Pass 模块补模块级单测,测试随实现拆分

This commit is contained in:
爱特每羊队
2026-08-06 14:09:03 +08:00
parent f7111b4658
commit ca46cf842e
4 changed files with 217 additions and 0 deletions
+48
View File
@@ -33,3 +33,51 @@ pub(super) fn add_exception_edges(f: &IRFunction, blocks: &mut [BasicBlock]) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BasicBlock;
use oxide_ir::inst::Inst;
/// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(D-07/D-08 仅起始 BB)。
#[test]
fn try_begin_emits_exception_edge_to_handler() {
let mut f = IRFunction::new();
f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → catch(label 0 → inst 2)
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: catch 入口
f.label_pos = vec![Some(2)];
f.label_count = 1;
let mut blocks = vec![
BasicBlock { inst_range: 0..2, preds: Vec::new(), succs: Vec::new() },
BasicBlock { inst_range: 2..3, preds: Vec::new(), succs: Vec::new() },
];
add_exception_edges(&f, &mut blocks);
assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Exception)], "TRY_BEGIN 所在 BB 出 Exception 边");
assert!(blocks[1].succs.is_empty(), "catch 入口块无出边");
}
/// 块内多个 TRY 标记指向同一目标:Exception 边去重(Pitfall 4)。
#[test]
fn duplicate_exception_targets_deduplicated() {
let mut f = IRFunction::new();
f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → L0
f.insts.push(Inst::try_finally_begin(0)); // 1: TRY_FINALLY_BEGIN → 同一 L0
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 2: L0 目标
f.label_pos = vec![Some(2)];
f.label_count = 1;
let mut blocks = vec![
BasicBlock { inst_range: 0..2, preds: Vec::new(), succs: Vec::new() },
BasicBlock { inst_range: 2..3, preds: Vec::new(), succs: Vec::new() },
];
add_exception_edges(&f, &mut blocks);
assert_eq!(
blocks[0].succs,
vec![(1, EdgeKind::Exception)],
"两个 TRY 标记指向同一目标只保留一条 Exception 边"
);
}
}
+35
View File
@@ -19,3 +19,38 @@ pub(super) fn finalize(mut blocks: Vec<BasicBlock>, exit_id: usize) -> Cfg {
Cfg { blocks, entry: 0, exit: exit_id }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{BasicBlock, EdgeKind};
/// exit 哨兵恒在 blocks 末尾(空块 0..0),preds 反推正确。
#[test]
fn exit_sentinel_appended_with_preds_reversed() {
let blocks_in = vec![
BasicBlock { inst_range: 0..1, preds: Vec::new(), succs: vec![(1, EdgeKind::Jump)] },
BasicBlock { inst_range: 1..2, preds: Vec::new(), succs: vec![(2, EdgeKind::Fallthrough)] },
];
let cfg = finalize(blocks_in, 2);
assert_eq!(cfg.blocks.len(), 3, "2 实块 + exit 哨兵");
assert_eq!(cfg.entry, 0);
assert_eq!(cfg.exit, 2);
assert_eq!(cfg.blocks[2].inst_range, 0..0, "exit 是空块");
assert_eq!(cfg.blocks[1].preds, vec![0], "块 1 preds 反推自块 0 的 Jump 边");
assert_eq!(cfg.blocks[2].preds, vec![1], "exit preds 反推自块 1 的 Fallthrough 边");
}
/// succs/preds 对称:total 边数一致(Pitfall 4 防御性约束)。
#[test]
fn preds_and_succs_symmetric() {
let blocks_in = vec![
BasicBlock { inst_range: 0..1, preds: Vec::new(), succs: vec![(1, EdgeKind::Jump)] },
BasicBlock { inst_range: 1..2, preds: Vec::new(), succs: vec![(2, EdgeKind::Fallthrough)] },
];
let cfg = finalize(blocks_in, 2);
let succs_total: usize = cfg.blocks.iter().map(|b| b.succs.len()).sum();
let preds_total: usize = cfg.blocks.iter().map(|b| b.preds.len()).sum();
assert_eq!(succs_total, preds_total, "succs/preds 对称");
}
}
+61
View File
@@ -34,3 +34,64 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
}
heads
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
/// 线性无跳转函数:仅 entry 为块头,其余位置非块头。
#[test]
fn linear_function_only_entry_is_head() {
let mut f = IRFunction::new();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None));
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None));
let heads = partition_blocks(&f);
assert_eq!(heads, vec![true, false, false, false], "仅 inst 0 是块头");
}
/// label 目标是块头:`label_pos` 指向的指令位置置 heads。
#[test]
fn label_target_is_head() {
let mut f = IRFunction::new();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: label 0 目标
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
f.label_pos = vec![Some(1)];
f.label_count = 1;
let heads = partition_blocks(&f);
assert!(heads[0] && heads[1], "entry 与 label 目标都是块头");
assert!(!heads[2], "非块头位置保持 false");
}
/// 条件跳转的 fallthrough 后继是块头(否则 cond+then 融块、条件边丢失)。
#[test]
fn conditional_jump_fallthrough_successor_is_head() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp_if_false(1, 0)); // 0: cond → L0(else)
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
f.label_pos = vec![Some(2)];
f.label_count = 1;
let heads = partition_blocks(&f);
assert!(heads[1], "条件跳转 fallthrough 后继是块头");
}
/// 无条件 JMP 的后继位置成块头(JMP 不 fallthrough,后续代码需独立成块,修复 Jump 边丢失)。
#[test]
fn unconditional_jmp_successor_is_head() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp(0)); // 0: → L0
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: 独立块头
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
f.label_pos = vec![Some(2)];
f.label_count = 1;
let heads = partition_blocks(&f);
assert!(heads[1], "无条件 JMP 后继位置是块头");
}
}
+73
View File
@@ -74,3 +74,76 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec<BasicBlock
pub(super) fn block_id_of(p: usize, blocks: &[BasicBlock]) -> usize {
blocks.partition_point(|b| b.inst_range.start <= p) - 1
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
/// JMP → Jump 边指向 label 目标块。
#[test]
fn unconditional_jmp_produces_jump_edge() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp(0)); // 0: → L0
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 1: L0 目标
f.label_pos = vec![Some(1)];
f.label_count = 1;
// heads: 0(entry)+ 1(label 目标)+ JMP 后继 1
let heads = crate::partition::partition_blocks(&f);
let (blocks, _exit_id) = split_and_edges(&f, &heads);
assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Jump)], "JMP 产出 Jump 边");
assert_eq!(blocks[1].succs, vec![(2, EdgeKind::Fallthrough)], "RETURN 汇 exit");
}
/// 条件跳转双出边:Jump → 目标块 + Fallthrough → 下一块。
#[test]
fn conditional_jump_dual_edges() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp_if_false(1, 0)); // 0: cond → L0(else)
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1: then
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2: else 入口
f.label_pos = vec![Some(2)];
f.label_count = 1;
let heads = crate::partition::partition_blocks(&f);
let (blocks, _exit_id) = split_and_edges(&f, &heads);
assert_eq!(
blocks[0].succs,
vec![(2, EdgeKind::Jump), (1, EdgeKind::Fallthrough)],
"条件跳转双出边:Jump→else + Fallthrough→then"
);
}
/// 非 terminator 块尾:Fallthrough → 下一块;RETURN 汇 exit 哨兵。
#[test]
fn non_terminator_fallthrough_and_return_to_exit() {
let mut f = IRFunction::new();
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 0
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 1
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
let heads = crate::partition::partition_blocks(&f);
let (blocks, exit_id) = split_and_edges(&f, &heads);
assert_eq!(blocks.len(), 1, "线性函数单块");
assert_eq!(exit_id, 1, "exit 哨兵 id = 实块数");
assert_eq!(blocks[0].succs, vec![(1, EdgeKind::Fallthrough)], "RETURN Fallthrough 汇 exit");
}
/// exit_id 恒为实块数(split 的 exit 约定,finalize 追加哨兵)。
#[test]
fn exit_id_equals_block_count() {
let mut f = IRFunction::new();
f.insts.push(Inst::jmp_if_false(1, 0));
f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1));
f.insts.push(Inst::jmp(1));
f.insts.push(Inst::call(Operand::Reg(4), Operand::Reg(0), Operand::Reg(3), 1));
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None));
f.label_pos = vec![Some(3), Some(4)];
f.label_count = 2;
let heads = crate::partition::partition_blocks(&f);
let (blocks, exit_id) = split_and_edges(&f, &heads);
assert_eq!(exit_id, blocks.len(), "exit_id = 实块数(4 实块)");
}
}