mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
test: 各 Pass 模块补模块级单测,测试随实现拆分
This commit is contained in:
@@ -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 边"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 对称");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 后继位置是块头");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 实块)");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user