refactor: 拆分单文件为三 Pass 独立模块(可达性/迭代删除/重建)

This commit is contained in:
2026-08-06 14:01:31 +08:00
parent 6c84ccca07
commit 54720a9bfc
4 changed files with 408 additions and 388 deletions
+220
View File
@@ -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<bool>) {
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 仍删");
}
}
+24 -388
View File
@@ -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<bool> {
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<bool>) {
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<Inst> = 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)");
}
}
+84
View File
@@ -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<bool> {
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 边强制可达");
}
}
+80
View File
@@ -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<Inst> = 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)");
}
}