docs: 补充注释与格式化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! Pass 3:异常边。
|
||||
//!
|
||||
//! 块内扫 TRY_BEGIN / TRY_FINALLY_BEGIN(块内标记,非 terminator,Pitfall 1),
|
||||
//! 从所在 BB 连 Exception 边到处理入口(D-07/D-08:仅起始 BB,不扩散)。
|
||||
//! 块内扫 TRY_BEGIN / TRY_FINALLY_BEGIN(块内标记,非 terminator),
|
||||
//! 从所在 BB 连 Exception 边到处理入口(仅起始 BB,不扩散)。
|
||||
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::operand::Operand;
|
||||
@@ -22,7 +22,7 @@ pub(super) fn add_exception_edges(f: &IRFunction, blocks: &mut [BasicBlock]) {
|
||||
match f.label_pos.get(l as usize).and_then(|p| *p) {
|
||||
Some(p) => {
|
||||
let target = crate::split::block_id_of(p, blocks);
|
||||
// push 前去重(Pitfall 4:块内多个 TRY 标记可指向同一目标)。
|
||||
// push 前去重:块内多个 TRY 标记可指向同一目标,只保留一条边。
|
||||
if !blocks[i].succs.contains(&(target, EdgeKind::Exception)) {
|
||||
blocks[i].succs.push((target, EdgeKind::Exception));
|
||||
}
|
||||
@@ -40,38 +40,57 @@ mod tests {
|
||||
use crate::BasicBlock;
|
||||
use oxide_ir::inst::Inst;
|
||||
|
||||
/// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(D-07/D-08 仅起始 BB)。
|
||||
/// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(仅起始 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.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() },
|
||||
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)。
|
||||
/// 块内多个 TRY 标记指向同一目标:Exception 边去重。
|
||||
#[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.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() },
|
||||
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!(
|
||||
|
||||
@@ -4,7 +4,11 @@ use crate::{BasicBlock, Cfg};
|
||||
|
||||
/// 追加 exit 哨兵块(`0..0`,置末尾),从各块 succs 反推 preds。
|
||||
pub(super) fn finalize(mut blocks: Vec<BasicBlock>, exit_id: usize) -> Cfg {
|
||||
blocks.push(BasicBlock { inst_range: 0..0, preds: Vec::new(), succs: Vec::new() });
|
||||
blocks.push(BasicBlock {
|
||||
inst_range: 0..0,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
});
|
||||
let mut succs_total = 0usize;
|
||||
let mut preds_total = 0usize;
|
||||
for i in 0..blocks.len() {
|
||||
@@ -17,7 +21,11 @@ pub(super) fn finalize(mut blocks: Vec<BasicBlock>, exit_id: usize) -> Cfg {
|
||||
}
|
||||
debug_assert_eq!(succs_total, preds_total, "succs/preds 不对称(Pitfall 4)");
|
||||
|
||||
Cfg { blocks, entry: 0, exit: exit_id }
|
||||
Cfg {
|
||||
blocks,
|
||||
entry: 0,
|
||||
exit: exit_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -29,8 +37,16 @@ mod tests {
|
||||
#[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)] },
|
||||
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 哨兵");
|
||||
@@ -41,12 +57,20 @@ mod tests {
|
||||
assert_eq!(cfg.blocks[2].preds, vec![1], "exit preds 反推自块 1 的 Fallthrough 边");
|
||||
}
|
||||
|
||||
/// succs/preds 对称:total 边数一致(Pitfall 4 防御性约束)。
|
||||
/// succs/preds 对称:total 边数一致(防御性约束)。
|
||||
#[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)] },
|
||||
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();
|
||||
|
||||
+57
-35
@@ -1,9 +1,8 @@
|
||||
//! CFG 分析 pass:IRFunction → 基本块划分 + 控制流图。
|
||||
//!
|
||||
//! 消费 `oxide_ir::IRFunction`(只读,D-06 索引关联,不拷贝指令),产出一次性
|
||||
//! 分析快照(D-01:pass 输出视图,不住进 IR)。本 crate 只管 CFG(D-02):
|
||||
//! 不建通用分析框架、不递归 nested(D-11)。无生命周期参数——CFG 只存指令
|
||||
//! 下标区间,使用方同时持有 `&IRFunction` 与 `&Cfg` 联合访问。
|
||||
//! 消费 `oxide_ir::IRFunction`(只读,不拷贝指令,仅存指令下标区间),产出一次性
|
||||
//! 分析快照——pass 输出视图不住进 IR。本 crate 只管 CFG:不建通用分析框架、
|
||||
//! 不递归 nested。无生命周期参数——CFG 与 `&IRFunction` 由使用方联合持有访问。
|
||||
//!
|
||||
//! 构建是四阶段分发:`partition`(块头)→ `split`(切块+出边)→ `exception`
|
||||
//! (异常边)→ `finalize`(exit 哨兵 + preds 反推)。每阶段是独立文件的纯函数,
|
||||
@@ -17,10 +16,10 @@ mod split;
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::IRFunction;
|
||||
|
||||
/// 基本块 id:blocks 向量下标(D-03)。
|
||||
/// 基本块 id:blocks 向量下标。
|
||||
pub type BBId = usize;
|
||||
|
||||
/// 边三分类(D-04):无条件/条件跳转、顺序落入、异常处理入口。
|
||||
/// 边三分类:无条件/条件跳转、顺序落入、异常处理入口。
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EdgeKind {
|
||||
Jump,
|
||||
@@ -28,8 +27,8 @@ pub enum EdgeKind {
|
||||
Exception,
|
||||
}
|
||||
|
||||
/// 基本块:`inst_range` 直接索引 `IRFunction.insts` 切片(D-03,不拷贝指令),
|
||||
/// preds/succs 双向就位,liveness 数据流迭代可直接用。
|
||||
/// 基本块:`inst_range` 直接索引 `IRFunction.insts` 切片(不拷贝指令),
|
||||
/// preds/succs 双向就位,供 liveness 数据流迭代直接消费。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BasicBlock {
|
||||
pub inst_range: std::ops::Range<usize>,
|
||||
@@ -37,7 +36,7 @@ pub struct BasicBlock {
|
||||
pub succs: Vec<(BBId, EdgeKind)>,
|
||||
}
|
||||
|
||||
/// 控制流图快照:实块按指令序编号,exit 哨兵(空块 `0..0`)恒在 blocks 末尾(D-05)。
|
||||
/// 控制流图快照:实块按指令序编号,exit 哨兵(空块 `0..0`)恒在 blocks 末尾。
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Cfg {
|
||||
pub blocks: Vec<BasicBlock>,
|
||||
@@ -48,7 +47,11 @@ pub struct Cfg {
|
||||
impl Cfg {
|
||||
/// 构造空 CFG:blocks 空、entry/exit 置 0。
|
||||
pub fn new() -> Self {
|
||||
Self { blocks: Vec::new(), entry: 0, exit: 0 }
|
||||
Self {
|
||||
blocks: Vec::new(),
|
||||
entry: 0,
|
||||
exit: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,12 +61,12 @@ impl Default for Cfg {
|
||||
}
|
||||
}
|
||||
|
||||
/// terminator 判定(D-09):块尾指令决定出边。
|
||||
/// terminator 判定:块尾指令决定出边。
|
||||
///
|
||||
/// 注意与 `oxide_ir::lower.rs::is_jump_op` 的差异:lowering 需要为 TRY_BEGIN /
|
||||
/// TRY_FINALLY_BEGIN 回填偏移所以把它们并列进 is_jump_op;但 CFG 里它们是**块内
|
||||
/// 标记**(异常边起点,见 Pass 3),不是控制流终结——抄用 is_jump_op 会导致 try 体
|
||||
/// 被切块、异常边爆炸(RESEARCH Pitfall 1)。
|
||||
/// 被切块、异常边爆炸。
|
||||
fn is_terminator(op: OpCode) -> bool {
|
||||
matches!(
|
||||
op,
|
||||
@@ -77,16 +80,20 @@ fn is_terminator(op: OpCode) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// 构建 CFG:`&IRFunction → Cfg`。纯函数,只读 IR(D-06 方案 A)。
|
||||
/// 构建 CFG:`&IRFunction → Cfg`。纯函数,只读 IR。
|
||||
///
|
||||
/// 四阶段分发:`partition::partition_blocks`(Pass 1 块头)→ `split::split_and_edges`
|
||||
/// (Pass 2 切块 + 出边)→ `exception::add_exception_edges`(Pass 3 异常边)
|
||||
/// → `finalize::finalize`(Pass 4 exit 哨兵 + preds 反推)。幂等(CFG-03)。
|
||||
/// → `finalize::finalize`(Pass 4 exit 哨兵 + preds 反推)。幂等:同一 IR 两次构建结果相等。
|
||||
pub fn build_cfg(f: &IRFunction) -> Cfg {
|
||||
// 空 IRFunction 退化形态:仅 exit 哨兵一块(Pitfall 5)。
|
||||
// 空 IRFunction 退化形态:仅 exit 哨兵一块。
|
||||
if f.insts.is_empty() {
|
||||
return Cfg {
|
||||
blocks: vec![BasicBlock { inst_range: 0..0, preds: Vec::new(), succs: Vec::new() }],
|
||||
blocks: vec![BasicBlock {
|
||||
inst_range: 0..0,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
}],
|
||||
entry: 0,
|
||||
exit: 0,
|
||||
};
|
||||
@@ -116,9 +123,12 @@ mod tests {
|
||||
#[test]
|
||||
fn linear_function_is_single_block_plus_exit() {
|
||||
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));
|
||||
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 cfg = build_cfg(&f);
|
||||
assert_eq!(cfg.blocks.len(), 2); // 1 实块 + exit 哨兵
|
||||
@@ -139,7 +149,8 @@ mod tests {
|
||||
f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then a()
|
||||
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 b()
|
||||
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
f.label_pos = vec![Some(3), Some(4)]; // L0→else 块头(3), L1→end(4)
|
||||
f.label_count = 2;
|
||||
|
||||
@@ -171,22 +182,26 @@ mod tests {
|
||||
assert_eq!(succs_total(&cfg), preds_total(&cfg));
|
||||
}
|
||||
|
||||
/// try/catch:TRY_BEGIN 是块内标记不切块(Pitfall 1),所在 BB 出 Exception 边到 catch 入口。
|
||||
/// try/catch:TRY_BEGIN 是块内标记不切块,所在 BB 出 Exception 边到 catch 入口。
|
||||
#[test]
|
||||
fn try_begin_does_not_split_block_and_emits_exception_edge() {
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN(块内标记)
|
||||
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.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口
|
||||
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
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.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
f.label_pos = vec![Some(3)];
|
||||
f.label_count = 1;
|
||||
|
||||
let cfg = build_cfg(&f);
|
||||
assert_eq!(cfg.blocks.len(), 3); // 2 实块 + exit 哨兵(TRY_BEGIN 不切块)
|
||||
assert_eq!(cfg.blocks[0].inst_range, 0..3); // try 体未被切块
|
||||
// Exception 边从 TRY_BEGIN 所在 BB 出发指向 catch 入口块(D-07/D-08)
|
||||
// Exception 边从 TRY_BEGIN 所在 BB 出发指向 catch 入口块。
|
||||
assert!(cfg.blocks[0].succs.contains(&(1, EdgeKind::Exception)));
|
||||
assert_eq!(cfg.blocks[1].succs, vec![(2, EdgeKind::Fallthrough)]);
|
||||
assert_eq!(cfg.blocks[2].preds, vec![0, 1]);
|
||||
@@ -198,10 +213,14 @@ mod tests {
|
||||
fn try_finally_begin_emits_exception_edge() {
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::try_finally_begin(0)); // 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
|
||||
f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: finally 入口
|
||||
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
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.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: finally 入口
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 4
|
||||
f.label_pos = vec![Some(3)];
|
||||
f.label_count = 1;
|
||||
|
||||
@@ -216,8 +235,10 @@ mod tests {
|
||||
#[test]
|
||||
fn trailing_return_flows_to_exit() {
|
||||
let mut f = IRFunction::new();
|
||||
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));
|
||||
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 cfg = build_cfg(&f);
|
||||
assert_eq!(cfg.blocks.len(), 2);
|
||||
@@ -225,7 +246,7 @@ mod tests {
|
||||
assert_eq!(cfg.blocks[1].preds, vec![0]);
|
||||
}
|
||||
|
||||
/// 空 IRFunction:退化 CFG,entry == exit == 0,blocks 长度 1(Pitfall 5)。
|
||||
/// 空 IRFunction:退化 CFG,entry == exit == 0,blocks 长度 1。
|
||||
#[test]
|
||||
fn empty_function_yields_degenerate_cfg() {
|
||||
let f = IRFunction::new();
|
||||
@@ -238,13 +259,14 @@ mod tests {
|
||||
assert!(cfg.blocks[0].preds.is_empty());
|
||||
}
|
||||
|
||||
/// 幂等性(CFG-03):同一 IR 两次构建结果相等。
|
||||
/// 幂等性:同一 IR 两次构建结果相等。
|
||||
#[test]
|
||||
fn build_cfg_is_idempotent() {
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::jmp_if_false(1, 0)); // 0: → L0(回环目标)
|
||||
f.insts.push(Inst::jmp(0)); // 1: → L0
|
||||
f.insts.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 2
|
||||
f.label_pos = vec![Some(2), Some(0)]; // L0→2(尾部), L1→0(回环)
|
||||
f.label_count = 2;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::operand::Operand;
|
||||
use oxide_ir::IRFunction;
|
||||
|
||||
/// 识别块头位置:返回 `heads`,长度 `insts.len()+1`(+1 容纳指向末尾的空尾块,Pitfall 3)。
|
||||
/// 识别块头位置:返回 `heads`,长度 `insts.len()+1`(+1 容纳指向末尾的空尾块)。
|
||||
pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
|
||||
let len = f.insts.len();
|
||||
let mut heads = vec![false; len + 1];
|
||||
@@ -25,10 +25,7 @@ pub(super) fn partition_blocks(f: &IRFunction) -> Vec<bool> {
|
||||
// 跳转的 fallthrough 后继是块头:条件跳转否则 cond+then 融块、条件边丢失;
|
||||
// 无条件 JMP 同样必须切块头——JMP 不 fallthrough,但其后继位置需成块边界,
|
||||
// 否则 JMP 与后续指令融块、块尾判定错、Jump 边丢失(DCE 可达性误删目标块)。
|
||||
if matches!(
|
||||
inst.op,
|
||||
OpCode::JMP | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH
|
||||
) {
|
||||
if matches!(inst.op, OpCode::JMP | OpCode::JMP_IF_TRUE | OpCode::JMP_IF_FALSE | OpCode::JMP_IF_NULLISH) {
|
||||
heads[i + 1] = true;
|
||||
}
|
||||
}
|
||||
@@ -44,9 +41,12 @@ mod tests {
|
||||
#[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));
|
||||
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 是块头");
|
||||
@@ -56,9 +56,12 @@ mod tests {
|
||||
#[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.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;
|
||||
|
||||
@@ -72,8 +75,10 @@ mod tests {
|
||||
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.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;
|
||||
|
||||
@@ -86,8 +91,10 @@ mod tests {
|
||||
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.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;
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec<BasicBlock
|
||||
let mut blocks: Vec<BasicBlock> = Vec::with_capacity(head_positions.len());
|
||||
for (idx, &start) in head_positions.iter().enumerate() {
|
||||
let end = head_positions.get(idx + 1).copied().unwrap_or(len);
|
||||
blocks.push(BasicBlock { inst_range: start..end, preds: Vec::new(), succs: Vec::new() });
|
||||
blocks.push(BasicBlock {
|
||||
inst_range: start..end,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
});
|
||||
}
|
||||
let exit_id = blocks.len(); // exit 哨兵块号 = 实块数
|
||||
|
||||
@@ -58,10 +62,10 @@ pub(super) fn split_and_edges(f: &IRFunction, heads: &[bool]) -> (Vec<BasicBlock
|
||||
blocks[i].succs.push((i + 1, EdgeKind::Fallthrough));
|
||||
}
|
||||
OpCode::RETURN | OpCode::HALT => {
|
||||
// RETURN/HALT 汇入 exit 哨兵,用 Fallthrough 表达正常流出口(A1 语义归属)。
|
||||
// RETURN/HALT 汇入 exit 哨兵,用 Fallthrough 表达正常流出口。
|
||||
blocks[i].succs.push((exit_id, EdgeKind::Fallthrough));
|
||||
}
|
||||
OpCode::THROW => { /* 无出边:异常传播,D-07 不建恢复路径 */ }
|
||||
OpCode::THROW => { /* 无出边:异常传播,不建恢复路径 */ }
|
||||
_ => unreachable!("is_terminator 已穷尽其余分支"),
|
||||
}
|
||||
}
|
||||
@@ -85,7 +89,8 @@ mod tests {
|
||||
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.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None)); // 1: L0 目标
|
||||
f.label_pos = vec![Some(1)];
|
||||
f.label_count = 1;
|
||||
|
||||
@@ -101,8 +106,10 @@ mod tests {
|
||||
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.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;
|
||||
|
||||
@@ -119,9 +126,12 @@ mod tests {
|
||||
#[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
|
||||
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);
|
||||
@@ -138,7 +148,8 @@ mod tests {
|
||||
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.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::None, Operand::None, Operand::None));
|
||||
f.label_pos = vec![Some(3), Some(4)];
|
||||
f.label_count = 2;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! 集成回归锚(D-12 第 2 层):真实 JS 编译产物 parse→emit→build_cfg。
|
||||
//! 集成回归锚:真实 JS 编译产物 parse→emit→build_cfg。
|
||||
//!
|
||||
//! 验证 CFG 不崩且结构合理。仅新增本文件,不修改既有测试文件(D-13)。
|
||||
//! 验证 CFG 不崩且结构合理。
|
||||
|
||||
use oxide_cfg::build_cfg;
|
||||
use oxide_cfg::EdgeKind;
|
||||
@@ -13,7 +13,7 @@ fn cfg_from_source(src: &str) -> oxide_cfg::Cfg {
|
||||
build_cfg(&ir)
|
||||
}
|
||||
|
||||
/// succs/preds 对称性断言(Pitfall 4)。
|
||||
/// succs/preds 对称性断言。
|
||||
fn assert_symmetric(cfg: &oxide_cfg::Cfg) {
|
||||
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();
|
||||
@@ -33,10 +33,13 @@ fn assert_targets_resolvable(cfg: &oxide_cfg::Cfg) {
|
||||
#[test]
|
||||
fn try_catch_regression_anchor() {
|
||||
let cfg = cfg_from_source("try { var x = 1; } catch (e) { var y = 2; }");
|
||||
// (a) TRY_BEGIN 出发的异常边至少一条(D-07/D-08)
|
||||
let has_exception = cfg.blocks.iter().any(|b| b.succs.iter().any(|&(_, k)| k == EdgeKind::Exception));
|
||||
// (a) TRY_BEGIN 出发的异常边至少一条
|
||||
let has_exception = cfg
|
||||
.blocks
|
||||
.iter()
|
||||
.any(|b| b.succs.iter().any(|&(_, k)| k == EdgeKind::Exception));
|
||||
assert!(has_exception, "期望存在 Exception 边");
|
||||
// (b) 顶层 HALT 收尾 → exit 哨兵 preds 非空(D-05)
|
||||
// (b) 顶层 HALT 收尾 → exit 哨兵 preds 非空
|
||||
assert!(!cfg.blocks[cfg.exit].preds.is_empty(), "exit 哨兵应有 pred(HALT 汇入)");
|
||||
// (c) 全部边目标可解析
|
||||
assert_targets_resolvable(&cfg);
|
||||
@@ -44,11 +47,12 @@ fn try_catch_regression_anchor() {
|
||||
assert_symmetric(&cfg);
|
||||
}
|
||||
|
||||
/// 每个 nested 子函数各自独立可建 CFG(D-11:不递归,但各函数可建)。
|
||||
/// 每个 nested 子函数各自独立可建 CFG(不递归,但各函数可建)。
|
||||
#[test]
|
||||
fn nested_function_cfgs_are_independent() {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, "function outer(){ var x = 0; while(x<3){x=x+1;} }").expect("parse failed");
|
||||
let program =
|
||||
oxide_parser::parse(&allocator, "function outer(){ var x = 0; while(x<3){x=x+1;} }").expect("parse failed");
|
||||
let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed");
|
||||
for nested in &ir.nested {
|
||||
let cfg = build_cfg(nested);
|
||||
@@ -56,7 +60,7 @@ fn nested_function_cfgs_are_independent() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 线性程序:1 实块 + exit 哨兵(CFG-01 线性函数单块)。
|
||||
/// 线性程序:1 实块 + exit 哨兵。
|
||||
#[test]
|
||||
fn linear_program_single_block_plus_exit() {
|
||||
let cfg = cfg_from_source("var x = 1;");
|
||||
@@ -64,7 +68,7 @@ fn linear_program_single_block_plus_exit() {
|
||||
assert_eq!(cfg.exit, 1);
|
||||
}
|
||||
|
||||
/// 顶层 if/else:条件分裂块 + entry 可达覆盖全部块 + 对称(CFG-01 对 if/else)。
|
||||
/// 顶层 if/else:条件分裂块 + entry 可达覆盖全部块 + 对称。
|
||||
#[test]
|
||||
fn if_else_conditional_edges() {
|
||||
let cfg = cfg_from_source("var x = 1; if(x){ var a = 1; } else { var b = 2; }");
|
||||
|
||||
Reference in New Issue
Block a user