From f4e0fdf14ee4eb87a5b1b9d68ae0051fad7db73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Thu, 6 Aug 2026 16:55:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(dce):=20liveness=20=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=E7=9A=84=E7=B2=BE=E7=A1=AE=E4=BA=8C=E8=BD=AE=20dce=5Fprecise?= =?UTF-8?q?=20+=20is=5Ftop=5Flevel=20=E6=A0=87=E5=BF=97=EF=BC=88D-17/D-19/?= =?UTF-8?q?D-20=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_dce/Cargo.toml | 1 + crates/oxide_dce/src/iter_sweep.rs | 3 +- crates/oxide_dce/src/lib.rs | 23 +++ crates/oxide_dce/src/precise_sweep.rs | 223 ++++++++++++++++++++++++++ crates/oxide_dce/tests/precise_eq.rs | 117 ++++++++++++++ crates/oxide_emit/src/emit.rs | 1 + crates/oxide_ir/src/lib.rs | 5 + 7 files changed, 372 insertions(+), 1 deletion(-) create mode 100644 crates/oxide_dce/src/precise_sweep.rs create mode 100644 crates/oxide_dce/tests/precise_eq.rs diff --git a/crates/oxide_dce/Cargo.toml b/crates/oxide_dce/Cargo.toml index 4405ce4..dab2a2b 100644 --- a/crates/oxide_dce/Cargo.toml +++ b/crates/oxide_dce/Cargo.toml @@ -10,6 +10,7 @@ description = "OxideJS - dead code elimination pass over IR (reachability + use- oxide_ir = { path = "../oxide_ir" } oxide_cfg = { path = "../oxide_cfg" } oxide_bytecode = { path = "../oxide_bytecode" } +oxide_liveness = { path = "../oxide_liveness" } [dev-dependencies] oxide_parser = { path = "../oxide_parser" } diff --git a/crates/oxide_dce/src/iter_sweep.rs b/crates/oxide_dce/src/iter_sweep.rs index c3c8031..4c2f91b 100644 --- a/crates/oxide_dce/src/iter_sweep.rs +++ b/crates/oxide_dce/src/iter_sweep.rs @@ -79,7 +79,8 @@ pub(super) fn pass_b_dead_code(f: &IRFunction, keep: &mut [bool]) { /// 递归收集 nested 树中全部变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。 /// 槽号 = 寄存器号编码的变量槽;其余操作数(局部寄存器/Imm/This)不构成跨函数引用。 -fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec) { +/// precise_sweep 复用(B012 约束同一来源)。 +pub(crate) fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec) { for sub in nested { for inst in &sub.insts { let slot = match inst.op { diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index a3b7901..e468cf2 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -9,12 +9,17 @@ //! 本 pass 不碰 nested(D-03 不递归)、常量池(D-14 不清理)、n_registers(D-04 不收缩)、 //! label_count(D-13 不读不改)。中间产物(keep/use_count)用局部 Vec 显式传参, //! 不做 struct 状态持有(无共享可变状态约定)。零 unsafe。 +//! +//! 精确二轮 `dce_precise`(D-17/D-19/D-20):liveness 驱动的死指令 + 局部死 STORE_VAR 删除, +//! 消费 `oxide_liveness::LiveInfo`,mark-sweep 重建复用 Pass C。 mod iter_sweep; +mod precise_sweep; mod reachability; mod rebuild; use oxide_ir::IRFunction; +use oxide_liveness::LiveInfo; /// 死代码消除:块级不可达删除 + 全函数 use 计数迭代删除到不动点 + mark-sweep 重建。 pub fn dce(f: &mut IRFunction) { @@ -30,6 +35,24 @@ pub fn dce(f: &mut IRFunction) { rebuild::pass_c_sweep(f, &keep); } +/// 精确二轮(D-17/D-19/D-20):liveness 驱动的死指令 + 局部死 STORE_VAR 删除。 +/// 消费 oxide_liveness::LiveInfo(RegAlloc 前由 05-09 管线传入;入口处重跑 liveness +/// 是 RegAlloc 的职责,D-17 意图:不重复建分析引擎)。mark-sweep 重建复用既有 Pass C。 +pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) { + // 空函数退化(照 dce 先例) + if f.insts.is_empty() { + return; + } + // 维度守卫:live 与 insts 对齐才可信(删改后的过期 LiveInfo 索引错位会误删活指令) + if live.inst_live_after.len() != f.insts.len() { + return; + } + let mut keep = vec![true; f.insts.len()]; + precise_sweep::pass_dead_with_liveness(f, live, &mut keep); + // D-20:mark-sweep 一次性重建 + rebuild::pass_c_sweep(f, &keep); +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/oxide_dce/src/precise_sweep.rs b/crates/oxide_dce/src/precise_sweep.rs new file mode 100644 index 0000000..6abf63b --- /dev/null +++ b/crates/oxide_dce/src/precise_sweep.rs @@ -0,0 +1,223 @@ +//! 精确轮(Pass D):liveness 驱动的死指令 + 局部死 STORE_VAR 删除(D-17/D-19)。 +//! +//! 消费 `oxide_liveness::LiveInfo::inst_live_after` 判定(D-22 契约复用,不重复建): +//! - 通用规则:纯指令 def_reg 不在 live_after → 删(含寄存器复用死写,保守 use 计数抓不到) +//! - STORE_VAR 特例(def_reg=None 不命中通用规则):非顶层 && b==Imm(0) && 槽非 escaped && 槽不在 live_after → 删 +//! +//! label 目标保守保留;单遍不级联(保守轮已做连锁);nested 不递归(D-03 延续)。 + +use oxide_bytecode::opcode::OpCode; +use oxide_ir::operand::Operand; +use oxide_ir::IRFunction; +use oxide_liveness::LiveInfo; + +/// 单遍死指令标记:就地改写 `keep`(保活/删除标记),f 只读。 +pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mut [bool]) { + // label 目标位图:label_pos 指向的 Inst 下标(照 iter_sweep:36-42,越界防御性忽略) + 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; + } + } + // escaped 槽位图:nested 树 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012——父 liveness + // 看不到跨函数读,删 STORE_VAR 后子模块读陈旧/undefined 槽) + let mut max_reg: usize = 0; + for inst in &f.insts { + if let Some(d) = inst.def_reg() { + max_reg = max_reg.max(d as usize); + } + for u in inst.use_regs() { + max_reg = max_reg.max(u as usize); + } + } + let mut escaped_slot = vec![false; max_reg + 1]; + super::iter_sweep::collect_escaped_slots(&f.nested, &mut escaped_slot); + // 单遍扫描:不级联(删 STORE_VAR 后 a 源残留死纯代码无害,保守轮已做连锁) + for (i, inst) in f.insts.iter().enumerate() { + if !keep[i] || label_target[i] { + continue; + } + let after = &live.inst_live_after[i]; + let not_live_after = |r: u32| !after.get(r as usize).copied().unwrap_or(false); + // 通用规则:纯指令结果不被后续使用 → 删(is_pure=false 已防 CALL/SPILL/UNSPILL) + if let Some(r) = inst.def_reg() { + if not_live_after(r) && inst.is_pure(f) { + keep[i] = false; + continue; + } + } + // STORE_VAR 特例:def_reg=None 不命中通用规则,单独判定(四条件全满足才删) + // 槽活度条件是必须的(RESEARCH Pattern 5 补齐):`var y=1; return y` 删掉 + // STORE_VAR 会让 LOAD_VAR 读到陈旧/undefined 槽(vreg 化 + RegAlloc 复用后脏槽)。 + if inst.op == OpCode::STORE_VAR { + let slot = match inst.rd { + Operand::Reg(s) => s as usize, + _ => continue, + }; + let deletable = !f.is_top_level // B008:顶层赋值全局可观察 + && matches!(inst.b, Operand::Imm(0)) // const 赋值 b=1 抛 TypeError 不可删 + && !escaped_slot.get(slot).copied().unwrap_or(false) // B012:nested 直读槽不可动 + && not_live_after(slot as u32); // 槽无后续读(vreg 复用后脏槽风险) + if deletable { + keep[i] = false; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dce_precise; + use oxide_ir::inst::Inst; + + /// helper:稀疏号集 → bitset live_after。 + fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec> { + after + .iter() + .map(|set| { + let mut v = vec![false; reg_count + 1]; + for &r in *set { + v[r as usize] = true; + } + v + }) + .collect() + } + + fn run_precise(f: &mut IRFunction, after: &[&[u32]], reg_count: usize) { + let mut live = LiveInfo::new(); + live.inst_live_after = live_sets(after, reg_count); + dce_precise(f, &live); + } + + /// 精确轮标志性测试:寄存器复用后被后写杀死的死写(保守 use 计数抓不到)。 + #[test] + fn reuse_killed_dead_write_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::ADD, Operand::Reg(5), Operand::Reg(1), Operand::Reg(2))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + // inst_live_after = [{3,4}, {5}, {}]:inst0 def 5 在 live_after[0] 无 5(后写杀死) + run_precise(&mut f, &[&[3, 4], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "死写(前一个 ADD)应被删"); + } + + /// 局部死 STORE_VAR 删除(四条件全满足)。 + #[test] + fn local_dead_store_var_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + // is_top_level 默认 false;inst_live_after = [{1,5}, {5}, {}] + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "局部死 STORE_VAR 应被删(LOAD_CONST 单遍不级联保留)"); + } + + /// 顶层 STORE_VAR 永不删(B008)。 + #[test] + fn top_level_store_var_kept() { + let mut f = IRFunction::new(); + f.is_top_level = true; + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "顶层 STORE_VAR 不可删"); + } + + /// escaped 槽 STORE_VAR 永不删(B012 nested 直读)。 + #[test] + fn escaped_slot_store_var_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + let mut sub = IRFunction::new(); + sub.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::LOAD_VAR, Operand::Reg(5), Operand::Reg(2), Operand::None)); + f.nested.push(sub); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "escaped 槽 STORE_VAR 不可删(B012)"); + } + + /// const 赋值路径 b=1 保留(运行时抛 TypeError 可观察)。 + #[test] + fn const_guard_store_var_kept() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(1))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 3, "b=1 const 赋值路径不可删"); + } + + /// label 目标指令永不删。 + #[test] + fn label_target_inst_never_deleted() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); // label 0 目标,纯且结果未用 + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.label_pos = vec![Some(0)]; + run_precise(&mut f, &[&[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "label 目标指令不可删"); + } + + /// SPILL/UNSPILL 永不删(is_pure=false);活 MOV 保留。 + #[test] + fn spill_unspill_never_deleted_mov_kept_when_live() { + let mut f = IRFunction::new(); + f.insts.push(Inst::inst_spill(Operand::Reg(1), 0)); + f.insts.push(Inst::inst_unspill(Operand::Reg(2), 0)); + f.insts.push(Inst::inst_mov(Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + run_precise(&mut f, &[&[], &[2], &[3], &[]], 5); + assert_eq!(f.insts.len(), 4, "SPILL/UNSPILL 永不删,活 MOV 保留"); + } + + /// 过期 LiveInfo 维度守卫:直接 return 零删除。 + #[test] + fn stale_liveinfo_no_op() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + let live = LiveInfo::new(); // 空 LiveInfo:维度不符 + dce_precise(&mut f, &live); + assert_eq!(f.insts.len(), 3, "过期 LiveInfo 不得删除任何指令"); + } + + /// 收敛性:单遍不级联设计下,重复跑会逐轮收敛(删 STORE_VAR 后其源 LOAD_CONST 变死), + /// 收敛到不动点后不再改变。 + #[test] + fn dce_precise_converges() { + let mut f = IRFunction::new(); + f.insts.push(Inst::load_const(Operand::Reg(1), 0)); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::STORE_VAR, Operand::Reg(2), Operand::Reg(1), Operand::Imm(0))); + f.insts.push(Inst::new(oxide_bytecode::opcode::OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + // 第一遍:手填 live(STORE_VAR 死) + run_precise(&mut f, &[&[1, 5], &[5], &[]], 5); + assert_eq!(f.insts.len(), 2, "第一遍删 STORE_VAR"); + // 第二遍:重算 live → LOAD_CONST 变死 + let cfg = oxide_cfg::build_cfg(&f); + let live = oxide_liveness::liveness(&f, &cfg); + dce_precise(&mut f, &live); + assert_eq!(f.insts.len(), 1, "第二遍删 LOAD_CONST(级联收敛)"); + let after2 = f.insts.clone(); + // 第三遍:不动点,不再改变 + let cfg = oxide_cfg::build_cfg(&f); + let live = oxide_liveness::liveness(&f, &cfg); + dce_precise(&mut f, &live); + assert_eq!(f.insts, after2, "第三遍不再删除(收敛)"); + } + + /// 空函数退化。 + #[test] + fn empty_function_unchanged() { + let mut f = IRFunction::new(); + let live = LiveInfo::new(); + dce_precise(&mut f, &live); + assert!(f.insts.is_empty()); + } +} diff --git a/crates/oxide_dce/tests/precise_eq.rs b/crates/oxide_dce/tests/precise_eq.rs new file mode 100644 index 0000000..b6cff53 --- /dev/null +++ b/crates/oxide_dce/tests/precise_eq.rs @@ -0,0 +1,117 @@ +//! 精确 DCE 二轮(D-17/D-19/D-20)运行时等价 + 语义断言。 +//! +//! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(precise on/off)→ lower → run, +//! 对比顶层执行结果。**不经 `Compiler::compile`**(测试层直调底层函数)。B008/B012 样例做 +//! 字节码/IR 级断言,is_top_level 标志直接断言。 + +use oxide_bytecode::opcode::OpCode; +use oxide_vm::vm::Vm; +use oxide_vm::JsValue; + +/// parse→emit→dce(保守)→(precise?)→lower→run 全链路。 +fn run_source(src: &str, precise: bool) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, src).expect("parse failed"); + let mut ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); + oxide_dce::dce(&mut ir); // 保守轮恒定(两跑一致) + if precise { + let cfg = oxide_cfg::build_cfg(&ir); + let live = oxide_liveness::liveness(&ir, &cfg); + oxide_dce::dce_precise(&mut ir, &live); // 精确轮(on 才跑) + } + let module = oxide_ir::lower::lower(&ir).expect("lower failed"); + let mut vm = Vm::new(); + vm.run(&module) +} + +/// 结果规约成可断言形态:`(是否 Ok, 字符串化结果或错误消息)`。 +fn normalize(r: Result) -> (bool, String) { + match r { + Ok(v) => (true, v.to_string()), + Err(e) => (false, e), + } +} + +/// 精确轮 on/off 运行时等价样例集(覆盖 B008/B012/const 抛错/try-catch/双写/纯链)。 +#[test] +fn precise_dce_semantics_preserved() { + let samples = [ + // 函数内局部死存储(精确轮删除,返回值不变) + "function f() { var y = 1; return 5; } f();", + // 死 a 链(a 从未读) + "function f() { var a = 1 + 2; var b = 3 + 4; return b; } f();", + // 顶层 STORE_VAR(B008:is_top_level 保护,不删) + "var x = 1; 2;", + // B008 既有回归样例(顶层赋值全局可观察) + "let x = 0; class C { [x = 1]() { return 2; } } new C();", + // B012 回归:k 只被嵌套子模块 LOAD_VAR.a 直读(父 liveness 死、escaped 保活) + "function f() { let k = 'a'; class C { [k]() { return 1; } } return Object.keys(new C())[0]; } f();", + // 死 const 声明存储(05-01 后 b=0,删之无害) + "function f() { const c = 5; return 6; } f();", + // const 重赋值抛 TypeError(b=1 保留,两跑同抛) + "const x = 1; x = 2;", + // catch 块内死存储 + 异常 reg0 路径(liveness 截断) + "function f() { try { throw 1; } catch (e) { var y = 2; return e; } } f();", + // 双写读尾值(两 STORE_VAR 槽活 → 全保留,结果 6) + "function f() { var z = 5; z = 6; return z; } f();", + // 顶层纯链(保守轮已删,精确轮幂等) + "1 + 2;", + // 局部死变量在循环后仍被读(活,保留) + "function f(n) { var s = 0; for (var i = 0; i < n; i++) { s = s + i; } return s; } f(10);", + ]; + for src in samples { + let off = normalize(run_source(src, false)); + let on = normalize(run_source(src, true)); + assert_eq!(on, off, "precise on/off 语义不一致: {src}"); + } +} + +/// 字节码级断言:顶层 `var x = 1; 2;` 经 dce+dce_precise 后 STORE_VAR 仍存在(B008)。 +#[test] +fn top_level_store_var_survives_precise() { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, "var x = 1; 2;").expect("parse failed"); + let mut ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); + 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); + let module = oxide_ir::lower::lower(&ir).expect("lower failed"); + assert!( + module.bytecode.iter().any(|&i| OpCode::try_from(i as u8).ok() == Some(OpCode::STORE_VAR)), + "顶层 STORE_VAR 必须存活" + ); +} + +/// IR 级断言:函数内局部死 STORE_VAR 被精确轮删除。 +/// dce_precise 按 D-03 不递归 nested,管线(05-09)逐函数处理——此处直接对 +/// 嵌套函数本体跑 liveness + dce_precise 证明删除生效。 +#[test] +fn precise_removes_dead_local_store() { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, "function f() { var y = 1; return 5; } f();").expect("parse failed"); + let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); + assert_eq!(ir.nested.len(), 1); + let mut sub = ir.nested[0].clone(); + 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); + assert!( + sub.insts.iter().all(|i| i.op != OpCode::STORE_VAR), + "函数内局部死 STORE_VAR 应被删除" + ); +} + +/// is_top_level 标志直接断言(D-19):顶层 true / 嵌套 false。 +#[test] +fn top_level_flag_filled_by_emit() { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, "var x = 1; function g() { return 2; }").expect("parse failed"); + let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit failed"); + assert!(ir.is_top_level, "顶层脚本 is_top_level 应为 true"); + assert!( + ir.nested.iter().any(|sub| !sub.is_top_level), + "嵌套函数 is_top_level 应为 false" + ); +} diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 35cccd4..31f2960 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -496,6 +496,7 @@ impl CompileCtx { needs_home_object: false, captured_this_const_idx: 0, function_name: None, + is_top_level: parent_ctx.is_none(), const_overflow: self.const_overflow, nested: std::mem::take(&mut self.nested), } diff --git a/crates/oxide_ir/src/lib.rs b/crates/oxide_ir/src/lib.rs index 9d262a4..5c98553 100644 --- a/crates/oxide_ir/src/lib.rs +++ b/crates/oxide_ir/src/lib.rs @@ -52,6 +52,10 @@ pub struct IRFunction { pub needs_home_object: bool, pub captured_this_const_idx: u16, pub function_name: Option, + /// 本函数是否为顶层脚本函数(D-19):顶层 STORE_VAR 全局可观察(B008)不可删, + /// 函数内局部 STORE_VAR 才可被精确 DCE 删除。emit 在 assemble_ir 处由 + /// `parent_ctx.is_none()` 填充;nested/手工构造默认 false。 + pub is_top_level: bool, pub const_overflow: bool, // nested 域 pub nested: Vec, @@ -76,6 +80,7 @@ impl IRFunction { needs_home_object: false, captured_this_const_idx: 0, function_name: None, + is_top_level: false, const_overflow: false, nested: Vec::new(), }