From 54720a9bfc4222b3b1e8af50f03fd5f9e282e38c Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 6 Aug 2026 14:01:31 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E6=8B=86=E5=88=86=E5=8D=95?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=BA=E4=B8=89=20Pass=20=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E6=A8=A1=E5=9D=97=EF=BC=88=E5=8F=AF=E8=BE=BE=E6=80=A7?= =?UTF-8?q?/=E8=BF=AD=E4=BB=A3=E5=88=A0=E9=99=A4/=E9=87=8D=E5=BB=BA?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_dce/src/iter_sweep.rs | 220 ++++++++++++++ crates/oxide_dce/src/lib.rs | 412 ++------------------------- crates/oxide_dce/src/reachability.rs | 84 ++++++ crates/oxide_dce/src/rebuild.rs | 80 ++++++ 4 files changed, 408 insertions(+), 388 deletions(-) create mode 100644 crates/oxide_dce/src/iter_sweep.rs create mode 100644 crates/oxide_dce/src/reachability.rs create mode 100644 crates/oxide_dce/src/rebuild.rs diff --git a/crates/oxide_dce/src/iter_sweep.rs b/crates/oxide_dce/src/iter_sweep.rs new file mode 100644 index 00000000..c3c80319 --- /dev/null +++ b/crates/oxide_dce/src/iter_sweep.rs @@ -0,0 +1,220 @@ +//! Pass B:全函数 use 计数迭代删除到不动点(D-02 连锁语义)。 +//! +//! 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加)→ 收集 `def_reg` 为 +//! Some(r) 且 use_count[r]==0 且 `is_pure(f)` 的指令 → 删除并减去其 use_regs → 迭代。 +//! CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删,Pitfall 1),但 CALL +//! 的 use 计数正常聚合——喂活上游写其参数的纯指令。 +//! **label 目标指令强制保留**:块头是控制流汇合点,即使 def 无 use 也删除会使存活 +//! JMP 的 label 引用悬空(lowering "Label not found")——保守保留(D-01 少删不错删)。 + +use oxide_bytecode::opcode::OpCode; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; + +/// 全函数 use 计数迭代删除:就地改写 `keep`(保活/删除标记)。 +pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { + // 寄存器号上界:覆盖可达指令全部 def/use(手工 IR 可能超 n_registers,动态取上界) + let mut max_reg: u32 = 0; + for (i, k) in keep.iter().enumerate() { + if *k { + if let Some(d) = f.insts[i].def_reg() { + max_reg = max_reg.max(d); + } + for u in f.insts[i].use_regs() { + max_reg = max_reg.max(u); + } + } + } + let mut use_count = vec![0usize; max_reg as usize + 1]; + for (i, k) in keep.iter().enumerate() { + if *k { + for u in f.insts[i].use_regs() { + use_count[u as usize] += 1; + } + } + } + // label 目标指令位图:label_pos 指向的 Inst 下标(越界防御性忽略) + let mut label_target = vec![false; f.insts.len()]; + for pos in f.label_pos.iter().flatten() { + if let Some(t) = label_target.get_mut(*pos) { + *t = true; + } + } + // nested 树引用的变量槽位图:闭包可跨函数读外层变量槽(LOAD_VAR.a 读槽 / STORE_VAR.rd 写槽 + // 以寄存器号编码)。D-03 不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的 + // STORE_VAR——否则死 `var k='x'` + 构造器内 `[k]` computed key 读槽被误删(回归锚)。 + let mut escaped_slot = vec![false; max_reg as usize + 1]; + collect_escaped_slots(&f.nested, &mut escaped_slot); + // 迭代删除至不动点:删一条纯指令 → 减其 use_regs → 上游写者可能连锁变死(D-02) + loop { + let mut deleted = false; + for (i, k) in keep.iter_mut().enumerate() { + if !*k || label_target[i] { + continue; + } + let inst = &f.insts[i]; + // CALL 系隐式 def reg0 是注册写入不参与死删收集;is_pure=false 已保证永不删(Pitfall 1) + if let Some(r) = inst.def_reg() { + // STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等 + // 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。D-03 不递归 nested, + // 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活 + //(保守"少删不错删",D-01)。 + let slot_escapes = inst.op == OpCode::STORE_VAR + && matches!(inst.rd, Operand::Reg(_)) + && escaped_slot.get(r as usize).copied().unwrap_or(false); + if use_count[r as usize] == 0 && !slot_escapes && inst.is_pure(f) { + *k = false; + for u in inst.use_regs() { + use_count[u as usize] -= 1; + } + deleted = true; + } + } + } + if !deleted { + break; + } + } +} + +/// 递归收集 nested 树中全部变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。 +/// 槽号 = 寄存器号编码的变量槽;其余操作数(局部寄存器/Imm/This)不构成跨函数引用。 +fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec) { + for sub in nested { + for inst in &sub.insts { + let slot = match inst.op { + OpCode::LOAD_VAR => inst.a, + OpCode::STORE_VAR => inst.rd, + _ => Operand::None, + }; + if let Operand::Reg(r) = slot { + let idx = r as usize; + if idx >= out.len() { + out.resize(idx + 1, false); + } + out[idx] = true; + } + } + collect_escaped_slots(&sub.nested, out); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_ir::inst::Inst; + use oxide_ir::operand::Operand; + + /// 线性死链:LOAD_CONST→ADD→结果丢弃,全删(剩 RETURN)。 + #[test] + fn linear_dead_chain_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0 = const + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1 = r0 + r0 + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); // 2: return r2 + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert_eq!(keep, vec![false, false, true], "死链全删,RETURN 保留"); + } + + /// 有副作用负例:CALL / STORE_VAR / GET_PROP / SET_PROP / INC_POST 永不删。 + #[test] + fn impure_ops_never_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0 + f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 1: CALL + f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 2 + f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 3 + f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(1))); // 4 + f.insts.push(Inst::new(OpCode::GET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 5 + f.insts.push(Inst::new(OpCode::SET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 6 + f.insts.push(Inst::new(OpCode::INC_POST, Operand::Reg(4), Operand::Reg(5), Operand::None)); // 7 + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert!(keep.iter().all(|k| *k), "有副作用指令一律保留"); + } + + /// STORE_VAR 一律保留(Pitfall 6):写变量槽可能被外部观察(脚本顶层/模块作用域变量、 + /// nested 闭包跨函数读槽),即使本函数内无 use 也不删。 + #[test] + fn store_var_never_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 0 + f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None)); // 1 + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert!(keep[1], "STORE_VAR 不删,其源 LOAD_CONST 也被 use 保活"); + assert!(keep[0] && keep[2], "源与 RETURN 保留"); + } + + /// 死闭包:CREATE_CLOSURE 结果未用 → 删除;nested 槽位不动(D-03 不递归不回收)。 + #[test] + fn dead_create_closure_deleted_nested_untouched() { + let mut f = IRFunction::new(); + f.insts.push(Inst::create_closure(Operand::Reg(1), 0)); // 0: r1 = nested[0] + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 1 + f.nested.push(IRFunction::new()); + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert_eq!(keep, vec![false, true], "死 CREATE_CLOSURE 删,RETURN 保留"); + } + + /// 连锁迭代(D-02):写链 LOAD_CONST→ADD→LOAD_CONST 全死,逐轮删除至不动点。 + #[test] + fn chained_dead_writes_deleted_iteratively() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0(ADD 读,轮 2 删) + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1(轮 1 删) + f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 2: r2 无人读(轮 1 删) + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert_eq!(keep, vec![false, false, false, true], "连锁全删至不动点"); + } + + /// label 目标指令强制保留(回归:do-while 形态)。`LOAD_CONST r0` 是 label 0 的目标, + /// 虽然 def 无 use 属死值,但删除会使存活 JMP_IF_TRUE 的 label 引用悬空 + /// (lowering 报 "Label not found")——保守保留(D-01 少删不错删)。 + #[test] + fn label_target_inst_never_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 0: body 头,label 0 目标(死值) + f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 1: 条件 true + f.insts.push(Inst::jmp_if_true(1, 0)); // 2: 回跳 label 0 + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + f.label_pos = vec![Some(0)]; + f.label_count = 1; + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert!(keep.iter().all(|k| *k), "label 目标指令与跳转链全部保留"); + } + + /// STORE_VAR 跨函数逃逸(回归:computed class field key)。顶层写变量槽 Reg(1) 的 + /// STORE_VAR 在本函数无 use,但 nested 构造器内 LOAD_VAR a=Reg(1) 跨函数读该槽—— + /// DCE 只统计当前函数(D-03 不递归),nested 引用槽保活顶层写者。 + #[test] + fn store_var_slot_read_by_nested_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(2), 0)); // 0: 'x' + f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(0))); // 1: k = 'x'(槽 1) + f.insts.push(Inst::create_closure(Operand::Reg(4), 0)); // 2 + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 + // nested 构造器:LOAD_VAR a=Reg(1) 跨函数读外层变量槽 1(computed key) + let mut sub = IRFunction::new(); + sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(1), Operand::None)); + sub.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(7), Operand::None, Operand::None)); + f.nested.push(sub); + let mut keep = vec![true; f.insts.len()]; + + pass_b_dead_code(&f, &mut keep); + assert!(keep[0] && keep[1], "nested 引用槽的写者 STORE_VAR 与源 LOAD_CONST 保留"); + assert!(!keep[2], "死 CREATE_CLOSURE 仍删"); + } +} diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index 33c57567..a3b79012 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -1,17 +1,19 @@ //! DCE 改写 pass:IRFunction → 死代码消除后 compact 重建。 //! //! 改写 pass(D-09):`&mut IRFunction` 就地 compact(D-06 方案 A 索引关联保证无借用冲突), -//! 三 Pass——Pass A 块级可达性(消费 `oxide_cfg::build_cfg`,Exception 边保守,D-07/Pitfall 4)、 -//! Pass B 全函数 use 计数迭代删除死指令到不动点(D-01/D-02 连锁语义)、 -//! Pass C mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移)。 +//! 三 Pass 各居一模块(照 oxide_cfg 四阶段拆文件先例): +//! - `reachability`(Pass A):块级可达性,消费 `oxide_cfg::build_cfg`,Exception 边保守(D-07/Pitfall 4) +//! - `iter_sweep`(Pass B):全函数 use 计数迭代删除死指令到不动点(D-01/D-02 连锁语义) +//! - `rebuild`(Pass C):mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移) +//! //! 本 pass 不碰 nested(D-03 不递归)、常量池(D-14 不清理)、n_registers(D-04 不收缩)、 //! label_count(D-13 不读不改)。中间产物(keep/use_count)用局部 Vec 显式传参, //! 不做 struct 状态持有(无共享可变状态约定)。零 unsafe。 -use oxide_cfg::build_cfg; -use oxide_bytecode::opcode::OpCode; -use oxide_ir::inst::Inst; -use oxide_ir::operand::Operand; +mod iter_sweep; +mod reachability; +mod rebuild; + use oxide_ir::IRFunction; /// 死代码消除:块级不可达删除 + 全函数 use 计数迭代删除到不动点 + mark-sweep 重建。 @@ -21,325 +23,20 @@ pub fn dce(f: &mut IRFunction) { return; } // Pass A:块级可达性(消费 build_cfg,Exception 边保守)→ 指令粒度 keep。 - let mut keep = pass_a_reachable(f); + let mut keep = reachability::pass_a_reachable(f); // Pass B:可达存活指令上全函数 use 计数迭代删除到不动点(D-02 连锁语义)。 - pass_b_dead_code(f, &mut keep); + iter_sweep::pass_b_dead_code(f, &mut keep); // Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(D-13)。 - pass_c_sweep(f, &keep); -} - -/// Pass A:块级可达性。从 entry 沿 succs DFS(**遍历含 EdgeKind::Exception 边**, -/// D-07/Pitfall 4——catch/finally 入口强制可达),不可达块的全部指令标记删除。 -fn pass_a_reachable(f: &IRFunction) -> Vec { - let cfg = build_cfg(f); - let mut reachable = vec![false; cfg.blocks.len()]; - let mut stack = vec![cfg.entry]; - while let Some(b) = stack.pop() { - if reachable[b] { - continue; - } - reachable[b] = true; - // succs 三边全遍历:Jump / Fallthrough / Exception(D-07 保守) - for &(succ, _kind) in &cfg.blocks[b].succs { - stack.push(succ); - } - } - // 块可达 → 展开到指令粒度 keep - let mut keep = vec![false; f.insts.len()]; - for (b, r) in reachable.iter().enumerate() { - if *r { - for i in cfg.blocks[b].inst_range.clone() { - keep[i] = true; - } - } - } - keep -} - -/// Pass B:全函数 use 计数迭代删除到不动点(D-02 连锁语义)。 -/// -/// 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加)→ 收集 `def_reg` 为 -/// Some(r) 且 use_count[r]==0 且 `is_pure(f)` 的指令 → 删除并减去其 use_regs → 迭代。 -/// CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删,Pitfall 1),但 CALL -/// 的 use 计数正常聚合——喂活上游写其参数的纯指令。 -/// **label 目标指令强制保留**:块头是控制流汇合点,即使 def 无 use 也删除会使存活 -/// JMP 的 label 引用悬空(lowering "Label not found")——保守保留(D-01 少删不错删)。 -fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { - // 寄存器号上界:覆盖可达指令全部 def/use(手工 IR 可能超 n_registers,动态取上界) - let mut max_reg: u32 = 0; - for (i, k) in keep.iter().enumerate() { - if *k { - if let Some(d) = f.insts[i].def_reg() { - max_reg = max_reg.max(d); - } - for u in f.insts[i].use_regs() { - max_reg = max_reg.max(u); - } - } - } - let mut use_count = vec![0usize; max_reg as usize + 1]; - for (i, k) in keep.iter().enumerate() { - if *k { - for u in f.insts[i].use_regs() { - use_count[u as usize] += 1; - } - } - } - // label 目标指令位图:label_pos 指向的 Inst 下标(越界防御性忽略) - let mut label_target = vec![false; f.insts.len()]; - for pos in f.label_pos.iter().flatten() { - if let Some(t) = label_target.get_mut(*pos) { - *t = true; - } - } - // nested 树引用的变量槽位图:闭包可跨函数读外层变量槽(LOAD_VAR.a 读槽 / STORE_VAR.rd 写槽 - // 以寄存器号编码)。D-03 不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的 - // STORE_VAR——否则死 `var k='x'` + 构造器内 `[k]` computed key 读槽被误删(回归锚)。 - let mut escaped_slot = vec![false; max_reg as usize + 1]; - collect_escaped_slots(&f.nested, &mut escaped_slot); - // 迭代删除至不动点:删一条纯指令 → 减其 use_regs → 上游写者可能连锁变死(D-02) - loop { - let mut deleted = false; - for (i, k) in keep.iter_mut().enumerate() { - if !*k || label_target[i] { - continue; - } - let inst = &f.insts[i]; - // CALL 系隐式 def reg0 是注册写入不参与死删收集;is_pure=false 已保证永不删(Pitfall 1) - if let Some(r) = inst.def_reg() { - // STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等 - // 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。D-03 不递归 nested, - // 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活 - //(保守"少删不错删",D-01)。 - let slot_escapes = inst.op == OpCode::STORE_VAR - && matches!(inst.rd, Operand::Reg(_)) - && escaped_slot.get(r as usize).copied().unwrap_or(false); - if use_count[r as usize] == 0 && !slot_escapes && inst.is_pure(f) { - *k = false; - for u in inst.use_regs() { - use_count[u as usize] -= 1; - } - deleted = true; - } - } - } - if !deleted { - break; - } - } -} - -/// 递归收集 nested 树中全部变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。 -/// 槽号 = 寄存器号编码的变量槽;其余操作数(局部寄存器/Imm/This)不构成跨函数引用。 -fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec) { - for sub in nested { - for inst in &sub.insts { - let slot = match inst.op { - OpCode::LOAD_VAR => inst.a, - OpCode::STORE_VAR => inst.rd, - _ => Operand::None, - }; - if let Operand::Reg(r) = slot { - let idx = r as usize; - if idx >= out.len() { - out.resize(idx + 1, false); - } - out[idx] = true; - } - } - collect_escaped_slots(&sub.nested, out); - } -} - -/// Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移—— -/// 多删点叠加偏移易错)。sweep:keep 序 compact 重建;label_pos 逐 id 重写, -/// 目标被删的 label 置 None 保留 id 槽位(lowering 只读 label_pos 不读 label_count)。 -fn pass_c_sweep(f: &mut IRFunction, keep: &[bool]) { - // old_to_new:存活位置记新下标,被删记 usize::MAX - let mut old_to_new = vec![usize::MAX; f.insts.len()]; - let mut new_insts: Vec = Vec::with_capacity(keep.iter().filter(|k| **k).count()); - for (old, k) in keep.iter().enumerate() { - if *k { - old_to_new[old] = new_insts.len(); - new_insts.push(f.insts[old].clone()); - } - } - f.insts = new_insts; - // label_pos 逐 id 重写:get + usize::MAX 过滤做越界守卫(T-04-03,不信任 label_pos 长度) - for pos in f.label_pos.iter_mut() { - if let Some(old) = *pos { - *pos = old_to_new.get(old).filter(|n| **n != usize::MAX).copied(); - } - } - // 防御性校验(T-04-03):存活 label 新下标不越界(空尾块映射 new insts.len() 合法透传) - let new_len = f.insts.len(); - debug_assert!(f.label_pos.iter().all(|p| p.map_or(true, |np| np <= new_len))); + rebuild::pass_c_sweep(f, &keep); } #[cfg(test)] mod tests { use super::*; - use oxide_bytecode::module::Constant; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - /// 线性死链:LOAD_CONST→ADD→结果丢弃,全删(剩 RETURN)。 - #[test] - fn linear_dead_chain_fully_deleted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0 = const - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1 = r0 + r0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); // 2: return r2 - - dce(&mut f); - assert_eq!(f.insts.len(), 1); - assert_eq!(f.insts[0].op, OpCode::RETURN); - } - - /// 有副作用负例:CALL / STORE_VAR(b=Imm(1)) / GET_PROP / SET_PROP / INC_POST 永不删。 - #[test] - fn impure_ops_never_deleted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0 - f.insts.push(Inst::call(Operand::Reg(0), Operand::Reg(0), Operand::Reg(0), 0)); // 1: CALL - f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 2 - f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 3 - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(1))); // 4 - f.insts.push(Inst::new(OpCode::GET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 5 - f.insts.push(Inst::new(OpCode::SET_PROP, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2))); // 6 - f.insts.push(Inst::new(OpCode::INC_POST, Operand::Reg(4), Operand::Reg(5), Operand::None)); // 7 - - dce(&mut f); - assert_eq!(f.insts.len(), 8, "有副作用指令一律保留"); - } - - /// STORE_VAR 一律保留(Pitfall 6):写变量槽可能被外部观察(脚本顶层/模块作用域变量、 - /// nested 闭包跨函数读槽),即使本函数内无 use 也不删——b=None/Imm(0) 与 b=Imm(1) 同待遇。 - #[test] - fn store_var_never_deleted() { - // b=None 普通赋值:保留(源寄存器因此保活) - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 0 - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 - dce(&mut f); - assert_eq!(f.insts.len(), 3, "STORE_VAR 与其源 LOAD_CONST 全部保留"); - assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); - assert_eq!(f.insts[1].op, OpCode::STORE_VAR); - assert_eq!(f.insts[2].op, OpCode::RETURN); - - // b=Imm(1) const guard:同样保留 - let mut g = IRFunction::new(); - g.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 0 - g.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(1))); // 1 - g.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 - dce(&mut g); - assert_eq!(g.insts.len(), 3, "const guard 的 STORE_VAR 及其源保留"); - } - - /// if/else 双分支:Jump + Fallthrough 双出边全可达,指令全保留,label 重映射正确。 - #[test] - fn if_else_both_branches_reachable_kept() { - let mut f = IRFunction::new(); - f.insts.push(Inst::jmp_if_false(0, 0)); // 0: cond → L0(else) - f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then - 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 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 - f.label_pos = vec![Some(3), Some(4)]; - f.label_count = 2; - - dce(&mut f); - assert_eq!(f.insts.len(), 5, "双分支全保留"); - assert_eq!(f.label_pos, vec![Some(3), Some(4)], "存活 label 重映射到新下标"); - assert_eq!(f.label_count, 2, "label_count 不动(D-13)"); - } - - /// return 后死块:入口块以 RETURN 结尾汇 exit;label 指向 RETURN 后紧跟的指令 - /// 使其成独立块头,该死块仅含自指 JMP(块中部不出边)→ 无外部入边不可达, - /// 整体删除,死 label 置 None 保留 id(D-13)。 - #[test] - fn unreachable_block_after_return_deleted_with_dead_label() { - let mut f = IRFunction::new(); - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(0), Operand::None, Operand::None)); // 0 - f.insts.push(Inst::jmp(0)); // 1: 死块(L0 目标),自指 JMP - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); // 2 - f.label_pos = vec![Some(1)]; - f.label_count = 1; - - dce(&mut f); - assert_eq!(f.insts.len(), 1, "死块全部删除"); - assert_eq!(f.insts[0].op, OpCode::RETURN); - assert_eq!(f.insts[0].rd, Operand::Reg(0)); - assert_eq!(f.label_pos, vec![None], "死 label 置 None 保留 id(D-13)"); - } - - /// Exception 边保守(Pitfall 4):TRY_BEGIN 的 Exception 边使 catch 入口强制可达, - /// 即使没有正常边指向它;catch 入口与 TRY_BEGIN 均保留。 - #[test] - fn exception_edge_keeps_catch_entry() { - let mut f = IRFunction::new(); - 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 - 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 - f.label_pos = vec![Some(3)]; - f.label_count = 1; - - dce(&mut f); - assert_eq!(f.insts.len(), 5, "catch 入口经 Exception 边保留"); - assert_eq!(f.insts[0].op, OpCode::TRY_BEGIN); - assert_eq!(f.insts[3].op, OpCode::LOAD_CONST, "catch 入口指令保留"); - assert_eq!(f.label_pos, vec![Some(3)], "catch 入口 label 重映射正确"); - } - - /// 死闭包:CREATE_CLOSURE 结果未用 → 删除;nested 槽位不动(D-03 不递归不回收)。 - #[test] - fn dead_create_closure_deleted_nested_untouched() { - let mut f = IRFunction::new(); - f.insts.push(Inst::create_closure(Operand::Reg(1), 0)); // 0: r1 = nested[0] - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 1 - f.nested.push(IRFunction::new()); - - dce(&mut f); - assert_eq!(f.insts.len(), 1); - assert_eq!(f.insts[0].op, OpCode::RETURN); - assert_eq!(f.nested.len(), 1, "nested 槽位不动(D-03)"); - } - - /// 连锁迭代(D-02):写链 LOAD_CONST→ADD→LOAD_CONST 全死,逐轮删除至不动点。 - #[test] - fn chained_dead_writes_deleted_iteratively() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0(ADD 读,轮 2 删) - f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(1), Operand::Reg(0), Operand::Reg(0))); // 1: r1(轮 1 删) - f.insts.push(Inst::load_const(Operand::Reg(2), 2)); // 2: r2 无人读(轮 1 删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 - - dce(&mut f); - assert_eq!(f.insts.len(), 1); - assert_eq!(f.insts[0].op, OpCode::RETURN); - } - - /// 存活 label 新旧下标对应:删除前置死指令后,label 目标重映射到新下标。 - #[test] - fn live_label_remapped_to_new_index() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(5), 1)); // 0: 存活(RETURN 保活 r5) - f.insts.push(Inst::load_const(Operand::Reg(9), 2)); // 1: 死(r9 无人读,删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 - f.label_pos = vec![Some(2)]; // label 0 → inst 2(RETURN) - f.label_count = 1; - - dce(&mut f); - assert_eq!(f.insts.len(), 2, "死 LOAD_CONST 删除"); - assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); - assert_eq!(f.insts[1].op, OpCode::RETURN); - assert_eq!(f.label_pos, vec![Some(1)], "存活 label 2 → 新下标 1"); - } - /// 空函数退化:insts 空时 dce 直接返回,状态不变。 #[test] fn empty_function_unchanged() { @@ -369,82 +66,21 @@ mod tests { assert_eq!(f.label_pos, once_labels, "二次 dce 后 label_pos 不变"); } - /// label 目标指令强制保留(回归:do-while 形态)。`LOAD_CONST r0` 是 label 0 的目标, - /// 虽然 def 无 use 属死值,但删除会使存活 JMP_IF_TRUE 的 label 引用悬空 - /// (lowering 报 "Label not found")——保守保留(D-01 少删不错删)。 + /// if/else 双分支完整链路:Jump + Fallthrough 双出边全可达,指令全保留,label 重映射正确。 #[test] - fn label_target_inst_never_deleted() { + fn if_else_both_branches_reachable_kept() { let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(0), 1)); // 0: body 头,label 0 目标(死值) - f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 1: 条件 true - f.insts.push(Inst::jmp_if_true(1, 0)); // 2: 回跳 label 0 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 - f.label_pos = vec![Some(0)]; - f.label_count = 1; + f.insts.push(Inst::jmp_if_false(0, 0)); // 0: cond → L0(else) + f.insts.push(Inst::call(Operand::Reg(2), Operand::Reg(0), Operand::Reg(3), 1)); // 1: then + 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 + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 + f.label_pos = vec![Some(3), Some(4)]; + f.label_count = 2; dce(&mut f); - assert_eq!(f.insts.len(), 4, "label 目标指令与跳转链全部保留"); - assert_eq!(f.label_pos, vec![Some(0)], "存活 label 目标重映射不变"); - } - - /// STORE_VAR 跨函数逃逸(回归:computed class field key)。顶层写变量槽 Reg(1) 的 - /// STORE_VAR 在本函数无 use,但 nested 构造器内 LOAD_VAR a=Reg(1) 跨函数读该槽—— - /// DCE 只统计当前函数(D-03 不递归),若删除则闭包读到 undefined。nested 引用槽保活顶层写者。 - #[test] - fn store_var_slot_read_by_nested_kept() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(2), 0)); // 0: 'x' - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(1), Operand::Reg(2), Operand::Imm(0))); // 1: k = 'x'(槽 1) - f.insts.push(Inst::create_closure(Operand::Reg(4), 0)); // 2 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 3 - // nested 构造器:LOAD_VAR a=Reg(1) 跨函数读外层变量槽 1(computed key) - let mut sub = IRFunction::new(); - sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(1), Operand::None)); - sub.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(7), Operand::None, Operand::None)); - f.nested.push(sub); - - dce(&mut f); - assert_eq!(f.insts.len(), 3, "nested 引用的变量槽写者 STORE_VAR 与源 LOAD_CONST 保留(死 CREATE_CLOSURE 仍删)"); - assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); - assert_eq!(f.insts[1].op, OpCode::STORE_VAR, "跨函数读槽的 STORE_VAR 保活"); - assert_eq!(f.insts[2].op, OpCode::RETURN); - } - - /// STORE_VAR 无 nested 时仍保留(Pitfall 6):顶层/模块作用域变量赋值全局可观察, - /// 本函数 use 计数为 0 不代表外部不可读——DCE 不删任何 STORE_VAR(保守,Phase 5 精确活度再捡回)。 - #[test] - fn store_var_still_kept_without_nested() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 1)); // 0 - f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None)); // 1 - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 - dce(&mut f); - assert_eq!(f.insts.len(), 3, "顶层 STORE_VAR 及其源保留"); - assert_eq!(f.insts[1].op, OpCode::STORE_VAR); - } - - /// 不动域断言(D-14/D-04/D-03/D-13):constants / n_registers / nested / label_count - /// 一律不动;死 label 置 None、存活 label 重映射、原 None 保持。 - #[test] - fn untouched_domains_asserted() { - let mut f = IRFunction::new(); - f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: label 0 目标(死值,保守保留) - f.insts.push(Inst::load_const(Operand::Reg(2), 1)); // 1: 非 label 目标的死指令(删) - f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 - f.label_pos = vec![Some(0), Some(2), None]; - f.label_count = 3; - f.n_registers = 10; - f.constants = vec![Constant::Number(1.0)]; - f.nested.push(IRFunction::new()); - - dce(&mut f); - assert_eq!(f.insts.len(), 2, "非目标死指令删、label 目标死指令保守保留"); - assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); - assert_eq!(f.insts[1].op, OpCode::RETURN); - assert_eq!(f.label_pos, vec![Some(0), Some(1), None], "存活 label→新下标、原 None 保持"); - assert_eq!(f.label_count, 3, "label_count 不动(D-13)"); - assert_eq!(f.n_registers, 10, "n_registers 不收缩(D-04)"); - assert_eq!(f.constants, vec![Constant::Number(1.0)], "常量池不清理(D-14)"); - assert_eq!(f.nested.len(), 1, "nested 不递归不回收(D-03)"); + assert_eq!(f.insts.len(), 5, "双分支全保留"); + assert_eq!(f.label_pos, vec![Some(3), Some(4)], "存活 label 重映射到新下标"); + assert_eq!(f.label_count, 2, "label_count 不动(D-13)"); } } diff --git a/crates/oxide_dce/src/reachability.rs b/crates/oxide_dce/src/reachability.rs new file mode 100644 index 00000000..a44d9a19 --- /dev/null +++ b/crates/oxide_dce/src/reachability.rs @@ -0,0 +1,84 @@ +//! Pass A:块级可达性。 +//! +//! 消费 `oxide_cfg::build_cfg` 的 Cfg,从 entry 沿 succs DFS(**遍历含 EdgeKind::Exception 边**, +//! D-07/Pitfall 4——catch/finally 入口强制可达),不可达块的全部指令标记删除。 + +use oxide_cfg::build_cfg; +use oxide_ir::IRFunction; + +/// 从 entry 可达的指令位图(keep 前置:不可达指令为 false)。 +pub(super) fn pass_a_reachable(f: &IRFunction) -> Vec { + let cfg = build_cfg(f); + let mut reachable = vec![false; cfg.blocks.len()]; + let mut stack = vec![cfg.entry]; + while let Some(b) = stack.pop() { + if reachable[b] { + continue; + } + reachable[b] = true; + // succs 三边全遍历:Jump / Fallthrough / Exception(D-07 保守) + for &(succ, _kind) in &cfg.blocks[b].succs { + stack.push(succ); + } + } + // 块可达 → 展开到指令粒度 keep + let mut keep = vec![false; f.insts.len()]; + for (b, r) in reachable.iter().enumerate() { + if *r { + for i in cfg.blocks[b].inst_range.clone() { + keep[i] = true; + } + } + } + keep +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_ir::inst::Inst; + use oxide_ir::operand::Operand; + use oxide_bytecode::opcode::OpCode; + + /// 线性函数全可达:所有指令 keep=true。 + #[test] + fn linear_all_insts_reachable() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + let keep = pass_a_reachable(&f); + assert!(keep.iter().all(|k| *k), "线性函数全指令可达"); + } + + /// return 后死块不可达:标记 keep=false(return 后紧跟的自指 JMP 块)。 + #[test] + fn unreachable_block_marked_dead() { + let mut f = IRFunction::new(); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(0), Operand::None, Operand::None)); // 0 + f.insts.push(Inst::jmp(0)); // 1: 死块(L0 目标),自指 JMP + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); // 2 + f.label_pos = vec![Some(1)]; + f.label_count = 1; + + let keep = pass_a_reachable(&f); + assert!(keep[0], "RETURN 指令可达"); + assert!(!keep[1] && !keep[2], "return 后死块不可达"); + } + + /// Exception 边保守(Pitfall 4):TRY_BEGIN 的 Exception 边使 catch 入口强制可达, + /// 即使没有正常边指向它。 + #[test] + fn exception_edge_keeps_catch_entry() { + let mut f = IRFunction::new(); + 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 + 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 + f.label_pos = vec![Some(3)]; + f.label_count = 1; + + let keep = pass_a_reachable(&f); + assert!(keep[3] && keep[4], "catch 入口经 Exception 边强制可达"); + } +} diff --git a/crates/oxide_dce/src/rebuild.rs b/crates/oxide_dce/src/rebuild.rs new file mode 100644 index 00000000..5f04ccc6 --- /dev/null +++ b/crates/oxide_dce/src/rebuild.rs @@ -0,0 +1,80 @@ +//! Pass C:mark-sweep 一次性重建 insts + label_pos 重映射(D-13,不用就地逐删平移—— +//! 多删点叠加偏移易错)。sweep:keep 序 compact 重建;label_pos 逐 id 重写, +//! 目标被删的 label 置 None 保留 id 槽位(lowering 只读 label_pos 不读 label_count)。 + +use oxide_ir::inst::Inst; +use oxide_ir::IRFunction; + +/// mark-sweep 重建:按 `keep` 序 compact `f.insts`,同步重映射 `f.label_pos`。 +pub(super) fn pass_c_sweep(f: &mut IRFunction, keep: &[bool]) { + // old_to_new:存活位置记新下标,被删记 usize::MAX + let mut old_to_new = vec![usize::MAX; f.insts.len()]; + let mut new_insts: Vec = Vec::with_capacity(keep.iter().filter(|k| **k).count()); + for (old, k) in keep.iter().enumerate() { + if *k { + old_to_new[old] = new_insts.len(); + new_insts.push(f.insts[old].clone()); + } + } + f.insts = new_insts; + // label_pos 逐 id 重写:get + usize::MAX 过滤做越界守卫(T-04-03,不信任 label_pos 长度) + for pos in f.label_pos.iter_mut() { + if let Some(old) = *pos { + *pos = old_to_new.get(old).filter(|n| **n != usize::MAX).copied(); + } + } + // 防御性校验(T-04-03):存活 label 新下标不越界(空尾块映射 new insts.len() 合法透传) + let new_len = f.insts.len(); + debug_assert!(f.label_pos.iter().all(|p| p.map_or(true, |np| np <= new_len))); +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_bytecode::module::Constant; + use oxide_bytecode::opcode::OpCode; + use oxide_ir::inst::Inst; + use oxide_ir::operand::Operand; + + /// 存活 label 新旧下标对应:删除前置死指令后,label 目标重映射到新下标。 + #[test] + fn live_label_remapped_to_new_index() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(5), 1)); // 0: 存活 + f.insts.push(Inst::load_const(Operand::Reg(9), 2)); // 1: 死(删) + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.label_pos = vec![Some(2)]; // label 0 → inst 2(RETURN) + f.label_count = 1; + + pass_c_sweep(&mut f, &[true, false, true]); + assert_eq!(f.insts.len(), 2, "死 LOAD_CONST 删除"); + assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); + assert_eq!(f.insts[1].op, OpCode::RETURN); + assert_eq!(f.label_pos, vec![Some(1)], "存活 label 2 → 新下标 1"); + } + + /// 不动域断言(D-14/D-04/D-03/D-13):constants / n_registers / nested / label_count + /// 一律不动;死 label 置 None、存活 label 重映射、原 None 保持。 + #[test] + fn untouched_domains_asserted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // 0: label 0 目标(死值,保守保留) + f.insts.push(Inst::load_const(Operand::Reg(2), 1)); // 1: 非 label 目标的死指令(删) + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2 + f.label_pos = vec![Some(0), Some(2), None]; + f.label_count = 3; + f.n_registers = 10; + f.constants = vec![Constant::Number(1.0)]; + f.nested.push(IRFunction::new()); + + pass_c_sweep(&mut f, &[true, false, true]); + assert_eq!(f.insts.len(), 2, "非目标死指令删、label 目标死指令保守保留"); + assert_eq!(f.insts[0].op, OpCode::LOAD_CONST); + assert_eq!(f.insts[1].op, OpCode::RETURN); + assert_eq!(f.label_pos, vec![Some(0), Some(1), None], "存活 label→新下标、原 None 保持"); + assert_eq!(f.label_count, 3, "label_count 不动(D-13)"); + assert_eq!(f.n_registers, 10, "n_registers 不收缩(D-04)"); + assert_eq!(f.constants, vec![Constant::Number(1.0)], "常量池不清理(D-14)"); + assert_eq!(f.nested.len(), 1, "nested 不递归不回收(D-03)"); + } +}