mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: liveness 驱动的精确二轮 dce_precise + is_top_level 标志(D-17/D-19/D-20)
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -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<bool>) {
|
||||
/// precise_sweep 复用(B012 约束同一来源)。
|
||||
pub(crate) fn collect_escaped_slots(nested: &[IRFunction], out: &mut Vec<bool>) {
|
||||
for sub in nested {
|
||||
for inst in &sub.insts {
|
||||
let slot = match inst.op {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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<Vec<bool>> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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<JsValue, String> {
|
||||
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<JsValue, String>) -> (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"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user