mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
perf: DCE Pass B 迭代全扫改工作队列单遍收敛
This commit is contained in:
@@ -1,17 +1,28 @@
|
|||||||
//! Pass B:全函数 use 计数迭代删除到不动点(连锁删上游写者)。
|
//! Pass B:全函数 use 计数工作队列删除到不动点(连锁删上游写者)。
|
||||||
//!
|
//!
|
||||||
//! 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加)→ 收集 `def_reg` 为
|
//! 只对可达存活指令聚合 use 计数(逐条 `inst.use_regs()` 累加),单遍预计算逐寄存器
|
||||||
//! Some(r) 且 use_count[r]==0 且 `is_pure(f)` 的指令 → 删除并减去其 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
|
//! CALL 隐式 def reg0 不参与死删(CALL 本身 is_pure=false 永不删),但 CALL
|
||||||
//! 的 use 计数正常聚合——喂活上游写其参数的纯指令。
|
//! 的 use 计数正常聚合——喂活上游写其参数的纯指令。
|
||||||
//! **label 目标指令强制保留**:块头是控制流汇合点,删除会使存活 JMP 的 label 引用
|
//! **label 目标指令强制保留**:块头是控制流汇合点,删除会使存活 JMP 的 label 引用
|
||||||
//! 悬空(lowering "Label not found")——保守保留,少删不错删。
|
//! 悬空(lowering "Label not found")——保守保留,少删不错删。
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use oxide_bytecode::opcode::OpCode;
|
use oxide_bytecode::opcode::OpCode;
|
||||||
use oxide_ir::operand::Operand;
|
use oxide_ir::operand::Operand;
|
||||||
use oxide_ir::IRFunction;
|
use oxide_ir::IRFunction;
|
||||||
|
|
||||||
/// 全函数 use 计数迭代删除:就地改写 `keep`(保活/删除标记)。
|
/// 全函数 use 计数工作队列删除:就地改写 `keep`(保活/删除标记)。
|
||||||
|
///
|
||||||
|
/// 工作队列单遍收敛:预计算逐寄存器定义者列表,初始候选(def 寄存器 use 计数为零的
|
||||||
|
/// 纯指令)入队;出队全量重核判据(重核是权威门,入队判据只作初筛),命中即删并减
|
||||||
|
/// use 计数,归零寄存器与已删指令 def 寄存器的全部存活定义者入队。删除规则单调
|
||||||
|
/// (use_count 只减不增、keep 只从真翻假),不动点删除集合唯一,删除集合与逐轮
|
||||||
|
/// 全扫迭代逐位等价。
|
||||||
pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) {
|
pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) {
|
||||||
// 寄存器号上界:覆盖可达指令全部 def/use(手工 IR 可能超 n_registers,动态取上界)
|
// 寄存器号上界:覆盖可达指令全部 def/use(手工 IR 可能超 n_registers,动态取上界)
|
||||||
let mut max_reg: u32 = 0;
|
let mut max_reg: u32 = 0;
|
||||||
@@ -25,6 +36,8 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// use 计数:存活指令逐条累加 use_regs
|
||||||
let mut use_count = vec![0usize; max_reg as usize + 1];
|
let mut use_count = vec![0usize; max_reg as usize + 1];
|
||||||
for (i, k) in keep.iter().enumerate() {
|
for (i, k) in keep.iter().enumerate() {
|
||||||
if *k {
|
if *k {
|
||||||
@@ -33,6 +46,18 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 逐寄存器定义者列表:按指令下标升序单遍填充,只收当前存活指令
|
||||||
|
//(删除后由 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 下标(越界防御性忽略)
|
// label 目标指令位图:label_pos 指向的 Inst 下标(越界防御性忽略)
|
||||||
let mut label_target = vec![false; f.insts.len()];
|
let mut label_target = vec![false; f.insts.len()];
|
||||||
for pos in f.label_pos.iter().flatten() {
|
for pos in f.label_pos.iter().flatten() {
|
||||||
@@ -40,39 +65,72 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) {
|
|||||||
*t = true;
|
*t = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// nested 树引用的变量槽位图:闭包可跨函数读外层变量槽(LOAD_VAR.a 读槽 / STORE_VAR.rd 写槽
|
// nested 树引用的变量槽位图:闭包可跨函数读外层变量槽(LOAD_VAR.a 读槽 / STORE_VAR.rd 写槽
|
||||||
// 以寄存器号编码)。不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的
|
// 以寄存器号编码)。不递归删除 nested,但跨函数引用必须保活当前函数内写该槽的
|
||||||
// STORE_VAR——否则死 `var k='x'` + 构造器内 `[k]` computed key 读槽被误删(回归锚)。
|
// STORE_VAR——否则死 `var k='x'` + 构造器内 `[k]` computed key 读槽被误删(回归锚)。
|
||||||
let mut escaped_slot = vec![false; max_reg as usize + 1];
|
let mut escaped_slot = vec![false; max_reg as usize + 1];
|
||||||
collect_escaped_slots(&f.nested, &mut escaped_slot);
|
collect_escaped_slots(&f.nested, &mut escaped_slot);
|
||||||
// 迭代删除至不动点:删一条纯指令 → 减其 use_regs → 上游写者可能连锁变死
|
|
||||||
loop {
|
// 槽逃逸判定:STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等
|
||||||
let mut deleted = false;
|
// 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。DCE 不递归 nested,
|
||||||
for (i, k) in keep.iter_mut().enumerate() {
|
// 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活
|
||||||
if !*k || label_target[i] {
|
//(保守"少删不错删")。
|
||||||
continue;
|
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);
|
||||||
}
|
}
|
||||||
let inst = &f.insts[i];
|
}
|
||||||
// CALL 系隐式 def reg0 是注册写入不参与死删收集;is_pure=false 已保证永不删
|
}
|
||||||
if let Some(r) = inst.def_reg() {
|
|
||||||
// STORE_VAR 写变量槽可被 nested 闭包跨函数读取(computed class field key 等
|
// 出队全量重核判据(重核是权威门,入队判据只作初筛);命中即置 keep 为假并减
|
||||||
// 场景:构造函数 submodule 内 LOAD_VAR a 槽引外层变量槽)。DCE 不递归 nested,
|
// use 计数,归零寄存器与已删指令 def 寄存器的全部存活且未入队定义者入队
|
||||||
// 顶层 use 计数看不到跨函数读——凡 nested 树引用的变量槽,其顶层写者保活
|
//(已删指令删除时其 def 寄存器计数必为零)
|
||||||
//(保守"少删不错删")。
|
while let Some(i) = queue.pop_front() {
|
||||||
let slot_escapes = inst.op == OpCode::STORE_VAR
|
if !keep[i] || label_target[i] {
|
||||||
&& matches!(inst.rd, Operand::Reg(_))
|
continue;
|
||||||
&& escaped_slot.get(r as usize).copied().unwrap_or(false);
|
}
|
||||||
if use_count[r as usize] == 0 && !slot_escapes && inst.is_pure(f) {
|
let inst = &f.insts[i];
|
||||||
*k = false;
|
let Some(r) = inst.def_reg() else {
|
||||||
for u in inst.use_regs() {
|
continue;
|
||||||
use_count[u as usize] -= 1;
|
};
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
deleted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !deleted {
|
// 已删指令 def 寄存器的计数此时必为零,同式入队
|
||||||
break;
|
for &d in &definers[r as usize] {
|
||||||
|
if keep[d] && !in_queue[d] {
|
||||||
|
in_queue[d] = true;
|
||||||
|
queue.push_back(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,4 +291,51 @@ mod tests {
|
|||||||
assert!(keep[0] && keep[1], "nested 引用槽的写者 STORE_VAR 与源 LOAD_CONST 保留");
|
assert!(keep[0] && keep[1], "nested 引用槽的写者 STORE_VAR 与源 LOAD_CONST 保留");
|
||||||
assert!(!keep[2], "死 CREATE_CLOSURE 仍删");
|
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 目标定义者保留,另一定义者删");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user