Files
OxideJS-New/crates/oxide_dce/src/iter_sweep.rs
T

342 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Pass B:全函数 use 计数工作队列删除到不动点(连锁删上游写者)。
//!
//! 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加),单遍预计算逐寄存器
//! 定义者列表 → 初始候选(`def_reg` 为 Some(r) 且 use_count[r]==0 且 `is_pure(f)` 的
//! 指令)入队 → 出队全量重核判据,命中即删并减去其 use_regs,归零寄存器与已删指令
//! def 寄存器的全部存活定义者入队。删除规则单调(use_count 只减不增、keep 只从真翻假),
//! 不动点删除集合唯一,与逐轮全扫迭代逐位等价。
//! CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删),但 CALL
//! 的 use 计数正常聚合——喂活上游写其参数的纯指令。
//! **label 目标指令强制保留**:块头是控制流汇合点,删除会使存活 JMP 的 label 引用
//! 悬空(lowering "Label not found")——保守保留,少删不错删。
use std::collections::VecDeque;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
/// 全函数 use 计数工作队列删除:就地改写 `keep`(保活/删除标记)。
///
/// 工作队列单遍收敛:预计算逐寄存器定义者列表,初始候选(def 寄存器 use 计数为零的
/// 纯指令)入队;出队全量重核判据(重核是权威门,入队判据只作初筛),命中即删并减
/// use 计数,归零寄存器与已删指令 def 寄存器的全部存活定义者入队。删除规则单调
/// (use_count 只减不增、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);
}
}
}
// use 计数:存活指令逐条累加 use_regs
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;
}
}
}
// 逐寄存器定义者列表:按指令下标升序单遍填充,只收当前存活指令
//(删除后由 keep 位图过滤,列表本身不收缩)
let mut definers: Vec<Vec<usize>> = vec![Vec::new(); max_reg as usize + 1];
for (i, k) in keep.iter().enumerate() {
if *k {
if let Some(r) = f.insts[i].def_reg() {
definers[r as usize].push(i);
}
}
}
// 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 写槽
// 以寄存器号编码)。不递归删除 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);
// 槽逃逸判定:STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等
// 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。DCE 不递归 nested,
// 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活
//(保守"少删不错删")。
let slot_escapes = |i: usize, r: u32| {
f.insts[i].op == OpCode::STORE_VAR
&& matches!(f.insts[i].rd, Operand::Reg(_))
&& escaped_slot.get(r as usize).copied().unwrap_or(false)
};
// 初始工作队列:收全部现判据候选(def 寄存器 use 计数为零、非 label 目标、
// 槽未逃逸、纯指令)
let mut in_queue = vec![false; f.insts.len()];
let mut queue: VecDeque<usize> = VecDeque::new();
for (i, k) in keep.iter().enumerate() {
if !*k || label_target[i] {
continue;
}
let inst = &f.insts[i];
if let Some(r) = inst.def_reg() {
if use_count[r as usize] == 0 && !slot_escapes(i, r) && inst.is_pure(f) {
in_queue[i] = true;
queue.push_back(i);
}
}
}
// 出队全量重核判据(重核是权威门,入队判据只作初筛);命中即置 keep 为假并减
// use 计数,归零寄存器与已删指令 def 寄存器的全部存活且未入队定义者入队
//(已删指令删除时其 def 寄存器计数必为零)
while let Some(i) = queue.pop_front() {
if !keep[i] || label_target[i] {
continue;
}
let inst = &f.insts[i];
let Some(r) = inst.def_reg() else {
continue;
};
if use_count[r as usize] != 0 || slot_escapes(i, r) || !inst.is_pure(f) {
continue;
}
keep[i] = false;
for u in inst.use_regs() {
use_count[u as usize] -= 1;
if use_count[u as usize] == 0 {
for &d in &definers[u as usize] {
if keep[d] && !in_queue[d] {
in_queue[d] = true;
queue.push_back(d);
}
}
}
}
// 已删指令 def 寄存器的计数此时必为零,同式入队
for &d in &definers[r as usize] {
if keep[d] && !in_queue[d] {
in_queue[d] = true;
queue.push_back(d);
}
}
}
}
/// 递归收集 nested 树中全部变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。
///
/// 槽号 = 寄存器号编码的变量槽;其余操作数(局部寄存器/Imm/This)不构成跨函数引用。
/// precise_sweep 复用此函数,保证跨函数保活判定同一来源。
pub(crate) 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→MOV→结果丢弃,全删(剩 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::inst_mov(Operand::Reg(1), Operand::Reg(0))); // 1: r1 = 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 一律保留:写变量槽可能被外部观察(脚本顶层/模块作用域变量、
/// 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 不递归不回收。
#[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 保留");
}
/// 连锁迭代:写链 LOAD_CONST→MOV→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(MOV 读,轮 2 删)
f.insts.push(Inst::inst_mov(Operand::Reg(1), 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")——保守保留,少删不错删。
#[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 只统计当前函数(不递归 nested),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 仍删");
}
/// 长死链:五十条 MOV 链加源常量全死,断言工作队列单遍级联收敛全删至不动点
///(链首常量靠"归零寄存器的定义者入队"逐环传导,钉级联不漏删)。
#[test]
fn long_dead_chain_fully_deleted() {
let mut f = IRFunction::new();
f.insts.push(Inst::load_const(Operand::Reg(0), 0)); // 0: r0 = const(源,链尾传导后删)
for r in 1..=50u32 {
f.insts.push(Inst::inst_mov(Operand::Reg(r), Operand::Reg(r - 1))); // 1..=50: r = r-1
}
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(60), Operand::None, Operand::None)); // 51
let mut keep = vec![true; f.insts.len()];
pass_b_dead_code(&f, &mut keep);
assert!(keep[0..51].iter().all(|k| !*k), "五十条 MOV 链与源常量全删至不动点");
assert!(keep[51], "RETURN 保留");
}
/// 同寄存器多定义者:两个定义者同寄存器均死时两删;一个定义者为 label 目标时
/// 保留、另一删(钉工作队列删除集合与迭代全扫逐位等价)。
#[test]
fn multi_definers_same_register_deleted() {
// 两定义者均死:同寄存器 use 计数为零,全部定义者入队两删
let mut f = IRFunction::new();
f.insts.push(Inst::load_const(Operand::Reg(1), 7)); // 0: r1 = 7
f.insts.push(Inst::load_const(Operand::Reg(1), 8)); // 1: r1 = 8(覆盖)
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_eq!(keep, vec![false, false, true], "同寄存器两定义者均死,两删");
// 一个定义者为 label 目标:静态判据时间不变,保活;另一定义者仍删
let mut f = IRFunction::new();
f.insts.push(Inst::load_const(Operand::Reg(1), 7)); // 0: label 目标(保活)
f.insts.push(Inst::load_const(Operand::Reg(1), 8)); // 1: r1 = 8(死)
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 2
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_eq!(keep, vec![true, false, true], "label 目标定义者保留,另一定义者删");
}
}