diff --git a/crates/oxide_dce/src/lib.rs b/crates/oxide_dce/src/lib.rs index b959fe6..33c5756 100644 --- a/crates/oxide_dce/src/lib.rs +++ b/crates/oxide_dce/src/lib.rs @@ -214,19 +214,22 @@ mod tests { assert_eq!(f.insts.len(), 8, "有副作用指令一律保留"); } - /// STORE_VAR b=None 纯拷贝死删(A2);b=Imm(1) const guard 保留(Pitfall 5)。 + /// STORE_VAR 一律保留(Pitfall 6):写变量槽可能被外部观察(脚本顶层/模块作用域变量、 + /// nested 闭包跨函数读槽),即使本函数内无 use 也不删——b=None/Imm(0) 与 b=Imm(1) 同待遇。 #[test] - fn store_var_pure_copy_deleted_guard_kept() { - // 纯拷贝死删 + 连锁:STORE_VAR 删后其源 LOAD_CONST 也死 + 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(), 1); - assert_eq!(f.insts[0].op, OpCode::RETURN); + 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:不纯保留,源寄存器保活 + // 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 @@ -407,16 +410,17 @@ mod tests { assert_eq!(f.insts[2].op, OpCode::RETURN); } - /// STORE_VAR 纯拷贝死删仍成立(无 nested 逃逸时):b=None 无 guard,槽无跨函数引用 → 删。 + /// STORE_VAR 无 nested 时仍保留(Pitfall 6):顶层/模块作用域变量赋值全局可观察, + /// 本函数 use 计数为 0 不代表外部不可读——DCE 不删任何 STORE_VAR(保守,Phase 5 精确活度再捡回)。 #[test] - fn store_var_pure_copy_still_deleted_without_nested() { + 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(), 1); - assert_eq!(f.insts[0].op, OpCode::RETURN); + 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 diff --git a/crates/oxide_dce/tests/runtime_eq.rs b/crates/oxide_dce/tests/runtime_eq.rs index 1bc7ab6..9cf4a9f 100644 --- a/crates/oxide_dce/tests/runtime_eq.rs +++ b/crates/oxide_dce/tests/runtime_eq.rs @@ -64,3 +64,29 @@ fn single_sample_top_level_return_value() { assert_eq!(a, b, "顶层返回值在 DCE 后变化"); assert_eq!(a, (true, "3".to_string()), "顶层 1+2 应返回 3"); } + +/// Pitfall 6 回归:脚本顶层变量赋值是全局可观察状态(class 计算属性名里的 `x = 1` +/// 会被测试 harness 的 `assert.sameValue(x, 1)` 观察),即使本函数内无 use, +/// DCE 也**不得删除**对应 STORE_VAR——删除会改变外部可观察语义。 +#[test] +fn top_level_store_var_kept() { + let src = "let x = 0; class C { [x = 1]() { return 2; } } new C();"; + 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); + let module = oxide_ir::lower::lower(&ir).expect("lower failed"); + let has_store_var = module + .bytecode + .iter() + .any(|&i| oxide_bytecode::opcode::opcode(i) == oxide_bytecode::opcode::OpCode::STORE_VAR); + assert!( + has_store_var, + "顶层 x 赋值(全局可观察)不应被 DCE 删除: {src}" + ); + + // 运行时兜底:DCE 开/关执行结果一致,且 x 最终值为 1(未被删赋值破坏) + let a = normalize(run_source(src, true)); + let b = normalize(run_source(src, false)); + assert_eq!(a, b, "DCE 前后语义不一致: {src}"); +} diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 7c5bd7e..acf00a7 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -466,10 +466,10 @@ impl Inst { OpCode::LOAD_VAR => { !(self.a == Operand::This && f.is_derived_constructor) } - // STORE_VAR:b==None/Imm(0) 纯拷贝无 guard;b==Imm(1) const guard 运行时判定(Pitfall 5) - OpCode::STORE_VAR => { - matches!(self.b, Operand::None | Operand::Imm(0)) - } + // STORE_VAR:写变量目标寄存器。脚本顶层/模块作用域的变量是**全局可观察状态** + // (Pitfall 6:`let x=0; class C{[x=1](){}}` 无后续本地读取时删除 `x=1` 会改变 + // 外部 `assert(x===1)` 的观察结果);函数内局部未用赋值的优化留给 Phase 5 精确活度。 + OpCode::STORE_VAR => false, // 其余全部有副作用(getter/调用/控制流/迭代器/复合更新/写共享状态/占位防御) _ => false, } @@ -810,9 +810,10 @@ mod tests { assert!(Inst::template_str(Operand::Reg(1), 1, 10, &[0x1234]).is_pure(&f)); // LOAD_VAR 普通(a=Reg)纯 assert!(Inst::new(OpCode::LOAD_VAR, Operand::Reg(1), Operand::Reg(2), Operand::None).is_pure(&f)); - // STORE_VAR b=None/Imm(0) 纯拷贝 - assert!(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); - assert!(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); + // STORE_VAR 一律有副作用(Pitfall 6:顶层变量赋值全局可观察,不删) + assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); + assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); + assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(1)).is_pure(&f)); } #[test] @@ -861,11 +862,12 @@ mod tests { } #[test] - fn store_var_b_branches() { - // Pitfall 5 + A2:None/Imm(0) 纯拷贝,Imm(1) 有 const guard + fn store_var_all_branches_are_impure() { + // Pitfall 6:STORE_VAR 写目标变量寄存器,顶层/模块作用域变量全局可观察,一律不删 + // (const guard 抛错、普通 var/let 赋值都可能改变外部观察状态) let f = IRFunction::new(); - assert!(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); - assert!(Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); + assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::None).is_pure(&f)); + assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(0)).is_pure(&f)); assert!(!Inst::new(OpCode::STORE_VAR, Operand::Reg(0), Operand::Reg(1), Operand::Imm(1)).is_pure(&f)); }