mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: CFG 异常边覆盖 try 体全部块,补全干涉图 keep 与 callee 边
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
//! Pass 3:异常边。
|
||||
//!
|
||||
//! 块内扫 TRY_BEGIN / TRY_FINALLY_BEGIN(块内标记,非 terminator),
|
||||
//! 从所在 BB 连 Exception 边到处理入口(仅起始 BB,不扩散)。
|
||||
//! TRY_BEGIN:匹配 TRY_BEGIN/TRY_END 对(栈匹配,处理嵌套),从 try 体
|
||||
//! [p_begin, p_end) 内所有重叠块连 Exception 边到 catch 入口。try 体内任意点
|
||||
//! 可抛异常,catch 须读到的活变量在整段 try 体保持存活,故异常边须覆盖整段。
|
||||
//!
|
||||
//! TRY_FINALLY_BEGIN:从所在 BB 连 Exception 边到 finally 入口(仅起始 BB)。
|
||||
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
use oxide_ir::operand::Operand;
|
||||
@@ -9,29 +12,62 @@ use oxide_ir::IRFunction;
|
||||
|
||||
use crate::{BasicBlock, EdgeKind};
|
||||
|
||||
/// 补异常边:从 TRY 标记所在 BB 出发,指向处理入口块。
|
||||
/// 补异常边:TRY_BEGIN 从 try 体全部重叠块出发,TRY_FINALLY_BEGIN 从所在块出发。
|
||||
pub(super) fn add_exception_edges(f: &IRFunction, blocks: &mut [BasicBlock]) {
|
||||
for i in 0..blocks.len() {
|
||||
let range = blocks[i].inst_range.clone();
|
||||
// TRY_BEGIN/TRY_END 栈匹配(处理嵌套),收集 (p_begin, p_end, catch_label)。
|
||||
let mut try_spans: Vec<(usize, usize, u32)> = Vec::new();
|
||||
let mut stack: Vec<(usize, u32)> = Vec::new();
|
||||
for (idx, inst) in f.insts.iter().enumerate() {
|
||||
match inst.op {
|
||||
OpCode::TRY_BEGIN => {
|
||||
if let Operand::Label(l) = inst.b {
|
||||
stack.push((idx, l));
|
||||
}
|
||||
}
|
||||
OpCode::TRY_END => {
|
||||
if let Some((p_begin, catch_label)) = stack.pop() {
|
||||
try_spans.push((p_begin, idx, catch_label));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// 每个 try 体:从 [p_begin, p_end) 内所有重叠块连 Exception 边到 catch 入口。
|
||||
for (p_begin, p_end, catch_label) in try_spans {
|
||||
if let Some(p) = f.label_pos.get(catch_label as usize).and_then(|p| *p) {
|
||||
let target = crate::split::block_id_of(p, blocks);
|
||||
for block in blocks.iter_mut() {
|
||||
if block.inst_range.start < p_end
|
||||
&& block.inst_range.end > p_begin
|
||||
&& !block.succs.contains(&(target, EdgeKind::Exception))
|
||||
{
|
||||
block.succs.push((target, EdgeKind::Exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// TRY_FINALLY_BEGIN:从所在块连 Exception 边到 finally 入口(仅起始块)。
|
||||
// 先收集 (block_index, target) 对,再统一加边(避免 iter_mut 与 block_id_of 借用冲突)。
|
||||
let mut finally_edges: Vec<(usize, usize)> = Vec::new();
|
||||
for (i, block) in blocks.iter().enumerate() {
|
||||
let range = block.inst_range.clone();
|
||||
for inst_idx in range.start..range.end {
|
||||
let inst = &f.insts[inst_idx];
|
||||
if !matches!(inst.op, OpCode::TRY_BEGIN | OpCode::TRY_FINALLY_BEGIN) {
|
||||
if f.insts[inst_idx].op != OpCode::TRY_FINALLY_BEGIN {
|
||||
continue;
|
||||
}
|
||||
if let Operand::Label(l) = inst.b {
|
||||
if let Operand::Label(l) = f.insts[inst_idx].b {
|
||||
match f.label_pos.get(l as usize).and_then(|p| *p) {
|
||||
Some(p) => {
|
||||
let target = crate::split::block_id_of(p, blocks);
|
||||
// push 前去重:块内多个 TRY 标记可指向同一目标,只保留一条边。
|
||||
if !blocks[i].succs.contains(&(target, EdgeKind::Exception)) {
|
||||
blocks[i].succs.push((target, EdgeKind::Exception));
|
||||
}
|
||||
}
|
||||
Some(p) => finally_edges.push((i, crate::split::block_id_of(p, blocks))),
|
||||
None => debug_assert!(false, "unresolved label {l}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, target) in finally_edges {
|
||||
if !blocks[i].succs.contains(&(target, EdgeKind::Exception)) {
|
||||
blocks[i].succs.push((target, EdgeKind::Exception));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -40,32 +76,34 @@ mod tests {
|
||||
use crate::BasicBlock;
|
||||
use oxide_ir::inst::Inst;
|
||||
|
||||
/// TRY_BEGIN 所在 BB 出发 Exception 边 → catch 入口块(仅起始 BB)。
|
||||
/// TRY_BEGIN 从 try 体全部重叠块出发 Exception 边 → catch 入口块。
|
||||
#[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::try_begin(0)); // 0: TRY_BEGIN → catch(label 0 → inst 3)
|
||||
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)];
|
||||
.push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); // 2
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口
|
||||
f.label_pos = vec![Some(3)];
|
||||
f.label_count = 1;
|
||||
|
||||
let mut blocks = vec![
|
||||
BasicBlock {
|
||||
inst_range: 0..2,
|
||||
inst_range: 0..3,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
},
|
||||
BasicBlock {
|
||||
inst_range: 2..3,
|
||||
inst_range: 3..4,
|
||||
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_eq!(blocks[0].succs, vec![(1, EdgeKind::Exception)], "try 体块出 Exception 边到 catch 入口");
|
||||
assert!(blocks[1].succs.is_empty(), "catch 入口块无出边");
|
||||
}
|
||||
|
||||
@@ -76,18 +114,20 @@ mod tests {
|
||||
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)];
|
||||
.push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); // 2
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: L0 目标
|
||||
f.label_pos = vec![Some(3)];
|
||||
f.label_count = 1;
|
||||
|
||||
let mut blocks = vec![
|
||||
BasicBlock {
|
||||
inst_range: 0..2,
|
||||
inst_range: 0..3,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
},
|
||||
BasicBlock {
|
||||
inst_range: 2..3,
|
||||
inst_range: 3..4,
|
||||
preds: Vec::new(),
|
||||
succs: Vec::new(),
|
||||
},
|
||||
@@ -99,4 +139,40 @@ mod tests {
|
||||
"两个 TRY 标记指向同一目标只保留一条 Exception 边"
|
||||
);
|
||||
}
|
||||
|
||||
/// try 体跨多块时,每块都出 Exception 边到 catch 入口(非仅 TRY_BEGIN 所在块)。
|
||||
#[test]
|
||||
fn exception_edges_from_all_try_body_blocks() {
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → catch(label 0 → inst 6)
|
||||
f.insts.push(Inst::jmp_if_false(1, 3)); // 1: 条件分支
|
||||
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 0)); // 2: CALL(then)
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: false 分支
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); // 4
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 5: 汇合
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 6: catch 入口
|
||||
f.label_pos = vec![Some(6), None, None, Some(3)];
|
||||
f.label_count = 4;
|
||||
|
||||
let cfg = crate::build_cfg(&f);
|
||||
// 块:[0..2) TRY_BEGIN+JMP_IF_FALSE, [2..3) CALL, [3..6) false+TRY_END+NOP,
|
||||
// [6..7) catch, [0..0) exit
|
||||
let catch_block = cfg.blocks.iter().position(|b| b.inst_range == (6..7)).unwrap();
|
||||
// CALL 块(then 分支)须有 Exception 边到 catch 入口
|
||||
let call_block = cfg.blocks.iter().position(|b| b.inst_range == (2..3)).unwrap();
|
||||
assert!(
|
||||
cfg.blocks[call_block].succs.contains(&(catch_block, EdgeKind::Exception)),
|
||||
"CALL 块(try 体深处)应有 Exception 边到 catch 入口"
|
||||
);
|
||||
// false 分支块也须有 Exception 边到 catch 入口
|
||||
let false_block = cfg.blocks.iter().position(|b| b.inst_range == (3..6)).unwrap();
|
||||
assert!(
|
||||
cfg.blocks[false_block].succs.contains(&(catch_block, EdgeKind::Exception)),
|
||||
"false 分支块(try 体深处)应有 Exception 边到 catch 入口"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ fn if_else_conditional_jump_splits_block_with_dual_edges() {
|
||||
assert_eq!(succs_total(&cfg), preds_total(&cfg));
|
||||
}
|
||||
|
||||
/// try/catch:TRY_BEGIN 是块内标记不切块,所在 BB 出 Exception 边到 catch 入口。
|
||||
/// try/catch:TRY_BEGIN 是块内标记不切块,try 体块出 Exception 边到 catch 入口。
|
||||
#[test]
|
||||
fn try_begin_does_not_split_block_and_emits_exception_edge() {
|
||||
let mut f = IRFunction::new();
|
||||
@@ -87,7 +87,7 @@ fn try_begin_does_not_split_block_and_emits_exception_edge() {
|
||||
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
|
||||
.push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); // 2
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); // 3: catch 入口
|
||||
f.insts
|
||||
@@ -98,10 +98,10 @@ fn try_begin_does_not_split_block_and_emits_exception_edge() {
|
||||
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 入口块。
|
||||
// Exception 边从 try 体块出发指向 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]);
|
||||
assert_eq!(cfg.blocks[2].preds, vec![1]);
|
||||
assert_eq!(succs_total(&cfg), preds_total(&cfg));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user