From 013c0eb3cfde7958be2172453d838aa858805d1f Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 5 Oct 2026 22:00:50 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20CFG=20=E5=BC=82=E5=B8=B8=E8=BE=B9?= =?UTF-8?q?=E8=A6=86=E7=9B=96=20try=20=E4=BD=93=E5=85=A8=E9=83=A8=E5=9D=97?= =?UTF-8?q?=EF=BC=8C=E8=A1=A5=E5=85=A8=E5=B9=B2=E6=B6=89=E5=9B=BE=20keep?= =?UTF-8?q?=20=E4=B8=8E=20callee=20=E8=BE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 36 +++--- Cargo.toml | 2 +- crates/oxide_cfg/src/exception.rs | 128 ++++++++++++++++----- crates/oxide_cfg/tests/build_unit.rs | 8 +- crates/oxide_dce/src/reachability.rs | 2 +- crates/oxide_liveness/src/dataflow.rs | 4 +- crates/oxide_liveness/src/ranges.rs | 4 +- crates/oxide_regalloc/src/call_window.rs | 10 +- crates/oxide_regalloc/tests/call_window.rs | 3 +- 9 files changed, 136 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17e3ce45..fa64bd55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.13" +version = "0.8.14" dependencies = [ "chrono", "fast-float", @@ -1120,7 +1120,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.13" +version = "0.8.14" dependencies = [ "num-bigint", "oxide_log", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.13" +version = "0.8.14" dependencies = [ "ansi_term", "bumpalo", @@ -1159,7 +1159,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.13" +version = "0.8.14" dependencies = [ "lru", "oxide_bytecode", @@ -1168,7 +1168,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.13" +version = "0.8.14" dependencies = [ "num-bigint", "oxc_allocator", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.13" +version = "0.8.14" dependencies = [ "bumpalo", "dashmap", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.13" +version = "0.8.14" dependencies = [ "tracing", "tracing-appender", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1292,7 +1292,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.13" +version = "0.8.14" dependencies = [ "num-bigint", "num-traits", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.13" +version = "0.8.14" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.13" +version = "0.8.14" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.13" +version = "0.8.14" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 8151a91b..d5c32f93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.13" +version = "0.8.14" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_cfg/src/exception.rs b/crates/oxide_cfg/src/exception.rs index fb7ce938..969dff61 100644 --- a/crates/oxide_cfg/src/exception.rs +++ b/crates/oxide_cfg/src/exception.rs @@ -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 入口" + ); + } } diff --git a/crates/oxide_cfg/tests/build_unit.rs b/crates/oxide_cfg/tests/build_unit.rs index 4987fc3b..d35388ef 100644 --- a/crates/oxide_cfg/tests/build_unit.rs +++ b/crates/oxide_cfg/tests/build_unit.rs @@ -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)); } diff --git a/crates/oxide_dce/src/reachability.rs b/crates/oxide_dce/src/reachability.rs index 732b18e9..1d7ba352 100644 --- a/crates/oxide_dce/src/reachability.rs +++ b/crates/oxide_dce/src/reachability.rs @@ -76,7 +76,7 @@ mod tests { f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → catch 入口(label 0 → inst 3) f.insts.push(Inst::load_const(Operand::Reg(5), 1)); // 1 f.insts - .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + .push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); // 2 f.insts.push(Inst::load_const(Operand::Reg(6), 2)); // 3: catch 入口 f.insts .push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); // 4 diff --git a/crates/oxide_liveness/src/dataflow.rs b/crates/oxide_liveness/src/dataflow.rs index b9b559a9..3508f03e 100644 --- a/crates/oxide_liveness/src/dataflow.rs +++ b/crates/oxide_liveness/src/dataflow.rs @@ -236,7 +236,7 @@ mod tests { fn try_catch_reg0_not_polluting_entry() { // 0: try_begin(L_catch) // 1: ADD r3=r3+r2 - // 2: RETURN Reg(3) + // 2: TRY_END // 3: STORE_VAR(Reg(5), None, None) catch 入口:a=None→0 读 reg 0 // 4: RETURN Reg(5) let mut f = empty_function(); @@ -244,7 +244,7 @@ mod tests { f.insts .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); f.insts - .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + .push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); f.insts .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); f.insts diff --git a/crates/oxide_liveness/src/ranges.rs b/crates/oxide_liveness/src/ranges.rs index 33b4ce84..cfdc8048 100644 --- a/crates/oxide_liveness/src/ranges.rs +++ b/crates/oxide_liveness/src/ranges.rs @@ -118,14 +118,14 @@ mod tests { #[test] fn catch_entry_reg0_not_live_before_block() { - // 0: try_begin(3) 1: ADD r3=r3+r2 2: RETURN r3 + // 0: try_begin(3) 1: ADD r3=r3+r2 2: TRY_END // 3: STORE_VAR(r5, None, None) 4: RETURN r5 let mut f = empty_function(); f.insts.push(Inst::try_begin(3)); f.insts .push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); f.insts - .push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + .push(Inst::new(OpCode::TRY_END, Operand::None, Operand::None, Operand::None)); f.insts .push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); f.insts diff --git a/crates/oxide_regalloc/src/call_window.rs b/crates/oxide_regalloc/src/call_window.rs index 4cdcfe42..031ac236 100644 --- a/crates/oxide_regalloc/src/call_window.rs +++ b/crates/oxide_regalloc/src/call_window.rs @@ -5,8 +5,8 @@ //! (运行时回退到调用方 `active_reg_limit` 全量窗口)。`regs[254]/[255]` //! (this/new.target)由帧单独保存不占窗口,计算时排除。生成器 / 异步函数体 //! 内部调用点不编码——挂起恢复按全量寄存器快照搬移,保持保守语义。含 TRY -//! 指令的函数同样不编码——异常 handler 的存活集不经分支/循环内调用点传播, -//! 截断窗口会丢仅 handler 存活的槽(见 `encode_call_window` 内 has_try 说明)。 +//! 指令的函数同样不编码——保守回退全量窗口(异常 handler 存活集经 try 体全部 +//! 块传播,但 keep 全量窗口避免截断边界风险,见 `encode_call_window` 内 has_try)。 use std::borrow::Cow; @@ -25,9 +25,9 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) { if f.insts.is_empty() && f.nested.is_empty() { return; } - // 含 try/catch/finally 的函数跳过编码:异常边只从 TRY 标记所在 BB 发出, - // 分支/循环 BB 内调用点的 liveness 不含仅 catch/finally 存活的寄存器, - // 截断窗口会丢槽(unwind 恢复后 handler 读到 callee 残留)。整体回退全量窗口。 + // 含 try/catch/finally 的函数跳过编码:保守回退全量窗口。异常边现从 try 体 + // 全部块发出,handler 存活集已传播到 try 体内调用点,但 keep 全量窗口避免 + // 截断边界风险(unwind 恢复后 handler 读到 callee 残留)。 let has_try = f .insts .iter() diff --git a/crates/oxide_regalloc/tests/call_window.rs b/crates/oxide_regalloc/tests/call_window.rs index 3f73eed2..c6479655 100644 --- a/crates/oxide_regalloc/tests/call_window.rs +++ b/crates/oxide_regalloc/tests/call_window.rs @@ -92,8 +92,7 @@ fn call_window_stale_live_reruns() { #[test] fn call_window_skips_function_with_try() { - // 含 TRY_BEGIN 的函数整体跳过编码(全量窗口):异常边只从 TRY 所在 BB 出发, - // 分支/循环内调用点的 liveness 不含仅 handler 存活的寄存器,截断窗口会丢槽。 + // 含 TRY_BEGIN 的函数整体跳过编码(全量窗口):保守回退,避免截断边界风险。 let mut f = IRFunction::new(); f.insts.push(Inst::try_begin(0)); // 0: TRY_BEGIN → L0 f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));