fix: STORE_VAR 一律判有副作用,修复顶层变量赋值被误删

This commit is contained in:
2026-08-06 13:29:07 +08:00
parent bca499fc39
commit 6c84ccca07
3 changed files with 53 additions and 21 deletions
+14 -10
View File
@@ -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
+26
View File
@@ -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}");
}