diff --git a/Cargo.lock b/Cargo.lock index 954e6d21..e2b701c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.16.10" +version = "0.16.11" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.16.10" +version = "0.16.11" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.16.10" +version = "0.16.11" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.16.10" +version = "0.16.11" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1257,7 +1257,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.16.10" +version = "0.16.11" dependencies = [ "num-bigint", "oxc_allocator", @@ -1286,7 +1286,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1298,7 +1298,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1307,7 +1307,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.16.10" +version = "0.16.11" dependencies = [ "bumpalo", "dashmap", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.16.10" +version = "0.16.11" dependencies = [ "tracing", "tracing-appender", @@ -1342,11 +1342,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.16.10" +version = "0.16.11" [[package]] name = "oxide_parser" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1361,7 +1361,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1377,7 +1377,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.16.10" +version = "0.16.11" dependencies = [ "num-bigint", "num-traits", @@ -1390,7 +1390,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.16.10" +version = "0.16.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1410,7 +1410,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.16.10" +version = "0.16.11" dependencies = [ "bumpalo", "dashmap", @@ -1423,7 +1423,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.16.10" +version = "0.16.11" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index f1cb253b..b7e39f2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ members = [ ] [workspace.package] -version = "0.16.10" +version = "0.16.11" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index f7ad45c5..86f822ff 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -93,24 +93,37 @@ impl Compiler { /// 统一 IR 后处理管线:(可选 DCE)→(可选 liveness/精确 DCE/RegAlloc)→ /// bytecode(`lower`)→ 子模块拍平。 + /// + /// 零删除时复用分析产物:DCE 零删除返回其 Pass A CFG(免重建),精确 DCE + /// 零删除返回 `live_reusable`(免第二轮 CFG/活集重建)。 fn compile_ir(&self, mut ir: oxide_ir::IRFunction) -> Result { crate::compiler_debug!("compile_ir: after emit {} insts", ir.insts.len()); - if self.enable_dce { - oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归 + // DCE 零删除时返回 Pass A CFG 供 liveness 复用;有删除或 DCE 关闭时为 None(须重建)。 + let dce_cfg: Option = if self.enable_dce { + let cfg = oxide_dce::dce(&mut ir); // 顶层函数;nested 不递归 crate::compiler_debug!("compile: after DCE {} insts", ir.insts.len()); - } + cfg + } else { + None + }; if self.enable_regalloc && !ir.const_overflow { // 优化管线:liveness → 精确 DCE → 重算 liveness → RegAlloc → 调用窗口编码 // const_overflow 时跳过:程序已知无效(lower 报 "too many constants"), // 且超大常量池伴生的海量虚拟寄存器会使 liveness 稠密 bitset 爆内存。 - let cfg = oxide_cfg::build_cfg(&ir); + // 零删除复用 DCE 的 Pass A CFG;DCE 关闭或有删除时重建 + let cfg = dce_cfg.unwrap_or_else(|| oxide_cfg::build_cfg(&ir)); let live = oxide_liveness::liveness(&ir, &cfg); // 精确 DCE 以第一轮 liveness 的指令级活集为输入,删除死指令 - oxide_dce::dce_precise(&mut ir, &live); - // 删指令后 inst 下标位移,旧 CFG 失效,按新程序重建 - let cfg2 = oxide_cfg::build_cfg(&ir); - // 指令集已变,旧活集失效,须重算 - let live2 = oxide_liveness::liveness(&ir, &cfg2); + let outcome = oxide_dce::dce_precise(&mut ir, &live); + // 零删除时函数未变,复用第一轮活集;有删除时 inst 下标位移,须重建 + // CFG 并重算活集(重建的 CFG 仅服务活集计算,不单独命名)。 + let (_, live2) = if outcome.live_reusable { + (cfg, live) + } else { + let cfg2 = oxide_cfg::build_cfg(&ir); + let live2 = oxide_liveness::liveness(&ir, &cfg2); + (cfg2, live2) + }; // 无可行染色 → RangeError 上抛 oxide_regalloc::alloc(&mut ir, &live2)?; // RegAlloc 改写指令(虚拟寄存器→物理号并插入 spill)后旧活集失效, diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index 240fa2dd..85e17468 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -12,6 +12,10 @@ //! //! 精确二轮 `dce_precise`:liveness 驱动的死指令 + 局部死 STORE_VAR 删除, //! 消费 `oxide_liveness::LiveInfo`,mark-sweep 重建复用 Pass C。 +//! +//! 两入口均暴露零删除事实供管线复用:`dce` 零删除时返回 Pass A 的 `Cfg` +//! (`Some`,有删除为 `None`),`dce_precise` 返回 `PreciseOutcome` +//! (`live_reusable` 标记第一轮活集是否仍有效)。 mod dce_log; mod iter_sweep; @@ -19,17 +23,24 @@ mod precise_sweep; mod reachability; mod rebuild; +use oxide_cfg::Cfg; use oxide_ir::IRFunction; use oxide_liveness::LiveInfo; /// 死代码消除:块级不可达删除 + 全函数 use 计数迭代删除到不动点 + mark-sweep 重建。 -pub fn dce(f: &mut IRFunction) { - // 空函数无指令可删,直接返回(no-op,f 不变)。 +/// +/// 返回 `Some(Cfg)`:零删除(含空函数早退),insts 未变,Pass A 的 CFG 仍有效, +/// 调用方可直接复用于 liveness(无需重建)。返回 `None`:有删除,insts 已 compact, +/// 旧 CFG 失效,调用方须重建。 +pub fn dce(f: &mut IRFunction) -> Option { + // 空函数无指令可删,直接返回(no-op,f 不变);退化 CFG 与编译器对空函数自建 + // 的退化形态一致,保持「零删除 → Some」语义。 if f.insts.is_empty() { - return; + return Some(oxide_cfg::build_cfg(f)); } // Pass A:块级可达性(消费 build_cfg,Exception 边保守)→ 指令粒度 keep。 - let mut keep = reachability::pass_a_reachable(f); + let cfg = oxide_cfg::build_cfg(f); + let mut keep = reachability::pass_a_reachable(f, &cfg); // Pass B:可达存活指令上全函数 use 计数迭代删除到不动点(连锁删上游写者)。 iter_sweep::pass_b_dead_code(f, &mut keep); let dead = keep.iter().filter(|k| !**k).count(); @@ -39,20 +50,38 @@ pub fn dce(f: &mut IRFunction) { if dead > 0 { rebuild::pass_c_sweep(f, &keep); } + // 零删除 ⇒ 函数未变 ⇒ Pass A CFG 仍有效(复用守卫与零删除判定同源)。 + if dead == 0 { + Some(cfg) + } else { + None + } +} + +/// 精确二轮结果:`live_reusable` 为第一轮 liveness 是否仍可复用(零删除时真)。 +pub struct PreciseOutcome { + /// 零删除:inst 下标未位移,传入活集仍有效,调用方可复用第一轮 CFG 与活集。 + pub live_reusable: bool, } /// 精确二轮:liveness 驱动的死指令 + 局部死 STORE_VAR 删除。 /// /// 消费 oxide_liveness::LiveInfo(RegAlloc 管线传入;若 LiveInfo 已过期,重建 liveness /// 是调用方 RegAlloc 的职责,本 pass 不重复建分析引擎)。mark-sweep 重建复用既有 Pass C。 -pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { - // 空函数无指令可删,直接返回(no-op,f 不变)。 +/// 返回 `PreciseOutcome`:零删除时 `live_reusable` 为真(函数未变,第一轮活集仍有效)。 +pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) -> PreciseOutcome { + // 空函数无指令可删,直接返回(no-op,f 不变);未改任何指令,活集可复用。 if f.insts.is_empty() { - return; + return PreciseOutcome { + live_reusable: true, + }; } - // 维度守卫:live 与 insts 对齐才可信(删改后的过期 LiveInfo 索引错位会误删活指令) + // 维度守卫:live 与 insts 对齐才可信(删改后的过期 LiveInfo 索引错位会误删活指令); + // 守卫不过时活集必过期,调用方须重建。 if live.inst_live_after.len() != f.insts.len() { - return; + return PreciseOutcome { + live_reusable: false, + }; } let mut keep = vec![true; f.insts.len()]; precise_sweep::pass_dead_with_liveness(f, live, &mut keep); @@ -61,4 +90,8 @@ pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { if dead > 0 { rebuild::pass_c_sweep(f, &keep); } + // 零删除 ⇒ 函数未变 ⇒ 第一轮活集仍有效(复用守卫与零删除判定同源)。 + PreciseOutcome { + live_reusable: dead == 0, + } } diff --git a/crates/oxide_dce/src/reachability.rs b/crates/oxide_dce/src/reachability.rs index 1d7ba352..e7294722 100644 --- a/crates/oxide_dce/src/reachability.rs +++ b/crates/oxide_dce/src/reachability.rs @@ -3,12 +3,13 @@ //! 消费 `oxide_cfg::build_cfg` 的 Cfg,从 entry 沿 succs DFS(**遍历含 EdgeKind::Exception 边**, //! catch/finally 入口强制可达),不可达块的全部指令标记删除。 -use oxide_cfg::build_cfg; +use oxide_cfg::Cfg; use oxide_ir::IRFunction; /// 从 entry 可达的指令位图(keep 前置:不可达指令为 false)。 -pub(super) fn pass_a_reachable(f: &IRFunction) -> Vec { - let cfg = build_cfg(f); +/// +/// 消费调用方传入的 `Cfg`(`dce` 建一次复用,不重复构建)。 +pub(super) fn pass_a_reachable(f: &IRFunction, cfg: &Cfg) -> Vec { let mut reachable = vec![false; cfg.blocks.len()]; let mut stack = vec![cfg.entry]; while let Some(b) = stack.pop() { @@ -47,7 +48,8 @@ mod tests { 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); + let cfg = oxide_cfg::build_cfg(&f); + let keep = pass_a_reachable(&f, &cfg); assert!(keep.iter().all(|k| *k), "线性函数全指令可达"); } @@ -63,7 +65,8 @@ mod tests { f.label_pos = vec![Some(1)]; f.label_count = 1; - let keep = pass_a_reachable(&f); + let cfg = oxide_cfg::build_cfg(&f); + let keep = pass_a_reachable(&f, &cfg); assert!(keep[0], "RETURN 指令可达"); assert!(!keep[1] && !keep[2], "return 后死块不可达"); } @@ -83,7 +86,8 @@ mod tests { f.label_pos = vec![Some(3)]; f.label_count = 1; - let keep = pass_a_reachable(&f); + let cfg = oxide_cfg::build_cfg(&f); + let keep = pass_a_reachable(&f, &cfg); assert!(keep[3] && keep[4], "catch 入口经 Exception 边强制可达"); } } diff --git a/crates/oxide_dce/tests/conservative_unit.rs b/crates/oxide_dce/tests/conservative_unit.rs index 2fb09efe..652f566d 100644 --- a/crates/oxide_dce/tests/conservative_unit.rs +++ b/crates/oxide_dce/tests/conservative_unit.rs @@ -10,7 +10,7 @@ use oxide_ir::IRFunction; #[test] fn empty_function_unchanged() { let mut f = IRFunction::new(); - dce(&mut f); + let _ = dce(&mut f); assert!(f.insts.is_empty()); assert!(f.label_pos.is_empty()); assert_eq!(f.label_count, 0); @@ -29,10 +29,10 @@ fn dce_is_idempotent() { .push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); // 4 f.label_pos = vec![Some(0)]; - dce(&mut f); + let _ = dce(&mut f); let once_insts = f.insts.clone(); let once_labels = f.label_pos.clone(); - dce(&mut f); + let _ = dce(&mut f); assert_eq!(f.insts, once_insts, "二次 dce 后 insts 不变"); assert_eq!(f.label_pos, once_labels, "二次 dce 后 label_pos 不变"); } @@ -50,7 +50,7 @@ fn if_else_both_branches_reachable_kept() { f.label_pos = vec![Some(3), Some(4)]; f.label_count = 2; - dce(&mut f); + let _ = 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 保持原值,不参与重建"); @@ -71,7 +71,7 @@ fn all_live_function_unchanged() { let before_insts = f.insts.clone(); let before_labels = f.label_pos.clone(); - dce(&mut f); + let _ = dce(&mut f); assert_eq!(f.insts, before_insts, "全存活时 insts 逐位不变"); assert_eq!(f.label_pos, before_labels, "全存活时 label_pos 逐位不变"); assert_eq!(f.label_count, 1, "label_count 保持原值"); diff --git a/crates/oxide_dce/tests/precise_eq.rs b/crates/oxide_dce/tests/precise_eq.rs index a9aa0a69..85e7412f 100644 --- a/crates/oxide_dce/tests/precise_eq.rs +++ b/crates/oxide_dce/tests/precise_eq.rs @@ -17,7 +17,7 @@ fn run_source(src: &str, precise: bool) -> Result { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); // 保守轮恒定(两跑一致) + let _ = oxide_dce::dce(&mut ir); // 保守轮恒定(两跑一致) if precise { let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); @@ -78,7 +78,7 @@ fn top_level_store_var_survives_precise() { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); oxide_dce::dce_precise(&mut ir, &live); @@ -104,7 +104,7 @@ fn precise_removes_dead_local_store() { .expect("emit failed"); assert_eq!(ir.nested.len(), 1); let mut sub = ir.nested[0].clone(); - oxide_dce::dce(&mut sub); + let _ = oxide_dce::dce(&mut sub); let cfg = oxide_cfg::build_cfg(&sub); let live = oxide_liveness::liveness(&sub, &cfg); oxide_dce::dce_precise(&mut sub, &live); diff --git a/crates/oxide_dce/tests/runtime_eq.rs b/crates/oxide_dce/tests/runtime_eq.rs index f221776d..fff1d526 100644 --- a/crates/oxide_dce/tests/runtime_eq.rs +++ b/crates/oxide_dce/tests/runtime_eq.rs @@ -17,7 +17,7 @@ fn run_source(src: &str, dce: bool) -> Result { .emit_program(&program, false, false) .expect("emit failed"); if dce { - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); } let module = oxide_ir::lower::lower(&ir).expect("lower failed"); let mut vm = Vm::new(); @@ -80,7 +80,7 @@ fn top_level_store_var_kept() { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); let module = oxide_ir::lower::lower(&ir).expect("lower failed"); let has_store_var = module .bytecode diff --git a/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs index a3bc87ce..b43b7979 100644 --- a/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs +++ b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs @@ -19,7 +19,7 @@ fn run_source(src: &str, regalloc: bool) -> (bool, String) { Ok(i) => i, Err(e) => return (false, format!("emit error: {e}")), }; - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); if regalloc { let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); diff --git a/crates/oxide_regalloc/tests/reg_count.rs b/crates/oxide_regalloc/tests/reg_count.rs index c98e4692..dd29e105 100644 --- a/crates/oxide_regalloc/tests/reg_count.rs +++ b/crates/oxide_regalloc/tests/reg_count.rs @@ -6,7 +6,7 @@ fn n_registers(src: &str, regalloc: bool) -> Result { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); if regalloc { let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); diff --git a/crates/oxide_regalloc/tests/regalloc_samples.rs b/crates/oxide_regalloc/tests/regalloc_samples.rs index 0d547615..abbad7f1 100644 --- a/crates/oxide_regalloc/tests/regalloc_samples.rs +++ b/crates/oxide_regalloc/tests/regalloc_samples.rs @@ -13,7 +13,7 @@ fn compile_source(src: &str, regalloc: bool) -> Result { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); if regalloc { let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); diff --git a/crates/oxide_regalloc/tests/runtime_eq.rs b/crates/oxide_regalloc/tests/runtime_eq.rs index d97315dc..ecc19200 100644 --- a/crates/oxide_regalloc/tests/runtime_eq.rs +++ b/crates/oxide_regalloc/tests/runtime_eq.rs @@ -16,7 +16,7 @@ fn run_source(src: &str, regalloc: bool) -> Result { let mut ir = oxide_emit::Emitter::new() .emit_program(&program, false, false) .expect("emit failed"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); if regalloc { let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); @@ -128,7 +128,7 @@ fn large_and_spill_samples_run_correctly() { let allocator = oxide_parser::Allocator::default(); let program = oxide_parser::parse(&allocator, &src_spill).expect("parse"); let mut ir = oxide_emit::Emitter::new().emit_program(&program, false, false).expect("emit"); - oxide_dce::dce(&mut ir); + let _ = oxide_dce::dce(&mut ir); let cfg = oxide_cfg::build_cfg(&ir); let live = oxide_liveness::liveness(&ir, &cfg); oxide_dce::dce_precise(&mut ir, &live);