diff --git a/Cargo.lock b/Cargo.lock index 100434ac..20f1053a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1175,6 +1175,17 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "oxide_liveness" +version = "0.1.0" +dependencies = [ + "oxide_bytecode", + "oxide_cfg", + "oxide_emit", + "oxide_ir", + "oxide_parser", +] + [[package]] name = "oxide_log" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 15fc2cea..5a63569a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "crates/oxide_ir", "crates/oxide_cfg", "crates/oxide_dce", + "crates/oxide_liveness", "crates/oxide_types", "crates/oxide_vm", "crates/oxide_kernel", diff --git a/crates/oxide_liveness/Cargo.toml b/crates/oxide_liveness/Cargo.toml new file mode 100644 index 00000000..e7c1760f --- /dev/null +++ b/crates/oxide_liveness/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "oxide_liveness" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +description = "OxideJS - liveness analysis pass over IR (CFG dataflow + per-instruction live ranges)" + +[dependencies] +oxide_ir = { path = "../oxide_ir" } +oxide_cfg = { path = "../oxide_cfg" } + +[dev-dependencies] +oxide_parser = { path = "../oxide_parser" } +oxide_emit = { path = "../oxide_emit" } +oxide_bytecode = { path = "../oxide_bytecode" } diff --git a/crates/oxide_liveness/src/dataflow.rs b/crates/oxide_liveness/src/dataflow.rs new file mode 100644 index 00000000..91a8e642 --- /dev/null +++ b/crates/oxide_liveness/src/dataflow.rs @@ -0,0 +1,258 @@ +//! 块级数据流迭代:gen/kill + reverse_postorder 不动点。 +//! +//! gen/kill 直接消费 `oxide_ir::contract` 的 `def_reg`/`use_regs`(D-22), +//! None→0 / This→254 / NewTarget→255 / CALL 隐式 reg0 全内置,不重复建。 +//! Exception 边当普通边参与迭代(D-13);Exception 目标块入口 reg 0 隐式 def +//! (异常展开写 regs[0],vm_runtime.rs:204-205)在此建模:gen.remove(0)+kill.insert(0)。 + +use oxide_cfg::{Cfg, EdgeKind}; +use oxide_ir::IRFunction; + +/// 块级 liveness:返回 (block_live_in, block_live_out, reg_count)。 +/// reg_count = 全部 def/use 最大 reg 号(bitset 长度 = reg_count+1)。 +pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec>, usize) { + // 1. reg_count 上界扫描(不信任 f.n_registers,动态求上界,照 iter_sweep 先例) + let mut reg_count = 255usize; // 兜底 This/NewTarget 254/255 + for inst in &f.insts { + if let Some(d) = inst.def_reg() { + reg_count = reg_count.max(d as usize); + } + for u in inst.use_regs() { + reg_count = reg_count.max(u as usize); + } + } + + let bits = reg_count + 1; + let n = cfg.blocks.len(); + + // 2. 每块 gen/kill + let mut gen: Vec> = vec![vec![false; bits]; n]; + let mut kill: Vec> = vec![vec![false; bits]; n]; + for (b, block) in cfg.blocks.iter().enumerate() { + // gen:块内反向扫描,kill 先于 gen(`live = (live − def) ∪ use`)。 + // 顺序理由:COMPOUND_ADD 等读-写同寄存器指令 use 含 rd(contract.rs:137), + // gen 先于 kill 会把 rd 旧值从 live_before 错误剔除。 + let mut live = vec![false; bits]; + for i in block.inst_range.clone().rev() { + if let Some(d) = f.insts[i].def_reg() { + live[d as usize] = false; + } + for u in f.insts[i].use_regs() { + live[u as usize] = true; + } + } + gen[b] = live; + + // kill:块内正向扫描,def_reg 非 None 即插入 + for i in block.inst_range.clone() { + if let Some(d) = f.insts[i].def_reg() { + kill[b][d as usize] = true; + } + } + + // Exception 目标块:入口 reg 0 隐式 def(异常展开在块外写 regs[0])。 + // gen 去 0 防"无 def use"把活度污染到函数入口;kill 插 0 防 0 从任何 pred 流入。 + // 注:preds 是 Vec(无 EdgeKind),须从源块 succs 侧检测 Exception 边。 + let is_exception_target = cfg + .blocks + .iter() + .any(|src| src.succs.iter().any(|&(dst, k)| dst == b && k == EdgeKind::Exception)); + if is_exception_target { + gen[b][0] = false; + kill[b][0] = true; + } + } + + // 3. reverse_postorder(确定性,禁 HashMap 迭代序——B010) + let rpo = reverse_postorder(cfg); + + // 4. 不动点迭代 + let mut live_in = vec![vec![false; bits]; n]; + let mut live_out = vec![vec![false; bits]; n]; + let mut iters = 0usize; + loop { + let mut changed = false; + for &b in &rpo { + // live_out[b] = ∪ live_in[s](全部 succs,含 Exception 边——D-13 当普通边) + let mut out = vec![false; bits]; + for &(succ, _kind) in &cfg.blocks[b].succs { + for (r, &v) in live_in[succ].iter().enumerate() { + if v { + out[r] = true; + } + } + } + // live_in[b] = gen[b] | (live_out[b] − kill[b]) + let mut input = vec![false; bits]; + for r in 0..bits { + input[r] = gen[b][r] || (out[r] && !kill[b][r]); + } + if input != live_in[b] || out != live_out[b] { + changed = true; + live_in[b] = input; + live_out[b] = out; + } + } + if !changed { + break; + } + iters += 1; + debug_assert!(iters < cfg.blocks.len() * 2 + 16, "liveness 不动点疑似不收敛"); + } + + (live_in, live_out, reg_count) +} + +/// 从 entry 出发 DFS 沿 succs 存储序(确定序)收集 postorder 后反转; +/// 不可达块按升序追加在末尾。纯 Vec + visited 位图(B010)。 +fn reverse_postorder(cfg: &Cfg) -> Vec { + let n = cfg.blocks.len(); + let mut visited = vec![false; n]; + let mut postorder = Vec::with_capacity(n); + fn dfs(cfg: &Cfg, visited: &mut [bool], postorder: &mut Vec, b: usize) { + visited[b] = true; + for &(succ, _) in &cfg.blocks[b].succs { + if !visited[succ] { + dfs(cfg, visited, postorder, succ); + } + } + postorder.push(b); + } + dfs(cfg, &mut visited, &mut postorder, cfg.entry); + postorder.reverse(); + for (b, &vis) in visited.iter().enumerate() { + if !vis { + postorder.push(b); + } + } + postorder +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_ir::inst::Inst; + use oxide_ir::operand::Operand; + use oxide_bytecode::opcode::OpCode; + + fn set(v: &[bool], regs: &[u32]) -> bool { + regs.iter().all(|&r| v[r as usize]) + } + + fn empty_function() -> IRFunction { + IRFunction::new() + } + + #[test] + fn linear_function_liveness() { + let mut f = empty_function(); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, live_out, reg_count) = block_liveness(&f, &cfg); + assert!(reg_count >= 2); + assert!(set(&live_in[0], &[0, 1]), "entry liveIn 应含 0、1"); + assert!(!live_in[0][2], "r2 是 def 不应在 liveIn"); + assert!(set(&live_out[0], &[]), "live_out 空"); + // exit 哨兵为空块,live 全空 + let exit = cfg.exit; + assert!(live_in[exit].iter().all(|&v| !v)); + assert!(live_out[exit].iter().all(|&v| !v)); + } + + #[test] + fn if_else_join_unions_live_sets() { + // 0: jmp_if_false(1, L1) cond=Reg(1) + // 1: ADD r3=r3+r2 def 3 use {3,2} + // 2: jmp L2 + // 3: SUB r4=r4+r5 def 4 use {4,5} + // 4: NOP + // 5: RETURN Reg(6) + let mut f = empty_function(); + f.insts.push(Inst::jmp_if_false(1, 1)); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::jmp(2)); + f.insts.push(Inst::new(OpCode::SUB, Operand::Reg(4), Operand::Reg(4), Operand::Reg(5))); + f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); + f.label_pos = vec![None, Some(3), Some(4)]; + f.label_count = 3; + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, _, _) = block_liveness(&f, &cfg); + // join 块(NOP 所在块)liveIn 含 6(RETURN 用) + let join = cfg.blocks.iter().position(|b| b.inst_range == (4..6usize)).unwrap(); + assert!(live_in[join][6], "join 块 liveIn 应含 r6"); + // then 分支块 liveIn 含 2、3,不含 5 + let then = cfg.blocks.iter().position(|b| b.inst_range == (1..3usize)).unwrap(); + assert!(set(&live_in[then], &[2, 3])); + assert!(!live_in[then][5]); + // else 分支块 liveIn 含 4、5,不含 2 + let els = cfg.blocks.iter().position(|b| b.inst_range == (3..4usize)).unwrap(); + assert!(set(&live_in[els], &[4, 5])); + assert!(!live_in[els][2]); + } + + #[test] + fn loop_back_edge_keeps_value_alive() { + // 0: jmp_if_false(1, L1) + // 1: ADD r3=r3+r2 + // 2: jmp L0 + // 3: NOP + // 4: RETURN Reg(6) + let mut f = empty_function(); + f.insts.push(Inst::jmp_if_false(1, 1)); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::jmp(0)); + f.insts.push(Inst::new(OpCode::NOP, Operand::None, Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None)); + f.label_pos = vec![Some(0), Some(3)]; + f.label_count = 2; + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, _, _) = block_liveness(&f, &cfg); + // header 块(含 inst 0)liveIn 含 2(r2 跨回边存活)与 1(cond) + assert!(live_in[0][1], "header liveIn 应含 cond r1"); + assert!(live_in[0][2], "header liveIn 应含 r2(跨回边存活)"); + // body 块 liveIn 含 2 + let body = cfg.blocks.iter().position(|b| b.inst_range == (1..3usize)).unwrap(); + assert!(live_in[body][2]); + // 测试通过本身即不动点收敛证明 + } + + #[test] + fn try_catch_reg0_not_polluting_entry() { + // 0: try_begin(L_catch) + // 1: ADD r3=r3+r2 + // 2: RETURN Reg(3) + // 3: STORE_VAR(Reg(5), None, None) catch 入口:a=None→0 读 reg 0 + // 4: RETURN Reg(5) + let mut f = empty_function(); + f.insts.push(Inst::try_begin(3)); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.label_pos = vec![None, None, None, Some(3)]; + f.label_count = 1; + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, _, _) = block_liveness(&f, &cfg); + // catch 块 liveIn 不含 0(隐式 def 截断) + let catch = cfg.blocks.iter().position(|b| b.inst_range == (3..5usize)).unwrap(); + assert!(!live_in[catch][0], "catch 块入口 reg0 应被隐式 def 截断"); + // try 块(entry)liveIn 不含 0(未污染入口) + assert!(!live_in[0][0], "reg0 use 不得污染函数入口"); + // try 块 liveIn 含 2(正常变量照常存活) + assert!(live_in[0][2], "try 块 liveIn 应含 r2"); + } + + #[test] + fn none_operand_maps_to_reg0() { + // 0: LOAD_VAR(Reg(5), None, None) def 5 use {0} + // 1: HALT(None, None, None) contract.rs HALT 读 regs[0] + let mut f = empty_function(); + f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::HALT, Operand::None, Operand::None, Operand::None)); + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, _, _) = block_liveness(&f, &cfg); + assert!(live_in[0][0], "None→0 映射由 contract.rs 消费生效"); + } +} diff --git a/crates/oxide_liveness/src/lib.rs b/crates/oxide_liveness/src/lib.rs new file mode 100644 index 00000000..f33a3a3b --- /dev/null +++ b/crates/oxide_liveness/src/lib.rs @@ -0,0 +1,31 @@ +//! liveness 分析 pass:IRFunction + CFG → LiveInfo(只读,D-15)。 +//! +//! 消费 `oxide_ir::IRFunction`(只读)与 `oxide_cfg::Cfg`(preds/succs + exit 哨兵,D-12), +//! 产出 LiveInfo 分析视图(D-01:pass 输出不住进 IR)。两阶段:`dataflow`(块级 +//! liveIn/liveOut 不动点)→ `ranges`(块内反向扫描逐指令 live 集)。gen/kill 直接消费 +//! contract.rs def_reg/use_regs(D-22),None→0 / This→254 / NewTarget→255 零重复映射。 + +mod dataflow; +mod live_info; +mod ranges; + +pub use live_info::LiveInfo; + +use oxide_cfg::Cfg; +use oxide_ir::IRFunction; + +/// 计算 liveness:`&IRFunction + &Cfg → LiveInfo`。纯函数,只读 IR(D-15)。 +/// 空 IRFunction 退化:返回 LiveInfo::new()(照 oxide_dce 空函数先例)。 +pub fn liveness(f: &IRFunction, cfg: &Cfg) -> LiveInfo { + if f.insts.is_empty() { + return LiveInfo::new(); + } + let (block_in, block_out, reg_count) = dataflow::block_liveness(f, cfg); + let (inst_before, inst_after) = ranges::inst_liveness(f, cfg, &block_out, &block_in, reg_count); + LiveInfo { + block_live_in: block_in, + block_live_out: block_out, + inst_live_before: inst_before, + inst_live_after: inst_after, + } +} diff --git a/crates/oxide_liveness/src/live_info.rs b/crates/oxide_liveness/src/live_info.rs new file mode 100644 index 00000000..595a1a53 --- /dev/null +++ b/crates/oxide_liveness/src/live_info.rs @@ -0,0 +1,50 @@ +//! LiveInfo:liveness 分析输出视图(D-15:pass 输出,不住进 IR)。 +//! +//! 全部 live 集为按 reg 号索引的 bitset(`Vec`,长度 = reg_count+1,由 +//! dataflow 求上界)。四字段语义: +//! - `block_live_in` / `block_live_out`:块级数据流迭代结果(05-06 精确 DCE 消费) +//! - `inst_live_before` / `inst_live_after`:逐指令活集(05-07 干涉图消费 live_before) + +/// liveness 输出:块级 liveIn/liveOut + 逐指令 live_before/live_after。 +#[derive(Debug, Clone)] +pub struct LiveInfo { + pub block_live_in: Vec>, + pub block_live_out: Vec>, + pub inst_live_before: Vec>, + pub inst_live_after: Vec>, +} + +impl LiveInfo { + /// 构造空 LiveInfo:全空 vec,等价于 Default。 + pub fn new() -> Self { + Self { + block_live_in: Vec::new(), + block_live_out: Vec::new(), + inst_live_before: Vec::new(), + inst_live_after: Vec::new(), + } + } +} + +impl Default for LiveInfo { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn live_info_new_is_empty() { + let info = LiveInfo::new(); + assert!(info.block_live_in.is_empty()); + assert!(info.block_live_out.is_empty()); + assert!(info.inst_live_before.is_empty()); + assert!(info.inst_live_after.is_empty()); + let default = LiveInfo::default(); + assert!(default.block_live_in.is_empty()); + assert!(default.block_live_out.is_empty()); + } +} diff --git a/crates/oxide_liveness/src/ranges.rs b/crates/oxide_liveness/src/ranges.rs new file mode 100644 index 00000000..b3a68c9d --- /dev/null +++ b/crates/oxide_liveness/src/ranges.rs @@ -0,0 +1,128 @@ +//! 块内反向扫描:块级 liveOut → 逐指令 live_before/live_after。 +//! +//! 起点是 **block_live_out**(非 liveIn,RESEARCH 伪代码笔误已修正——从 liveIn +//! 出发会得到错误放大的逐指令集)。反向扫描 **kill 先于 gen**(`live = (live − def) +//! ∪ use`):COMPOUND_ADD 等读-写同寄存器指令 use 含 rd(contract.rs:137), +//! gen 先于 kill 会把 rd 旧值从 live_before 错误剔除。Exception 目标块入口 +//! reg 0 隐式 def 在此截断,并以 `debug_assert_eq!(live, block_live_in[b])` +//! 校验与 dataflow 一致性。 + +use oxide_cfg::{Cfg, EdgeKind}; +use oxide_ir::IRFunction; + +/// 逐指令 liveness:返回 (inst_live_before, inst_live_after),长度 = insts.len()。 +pub(super) fn inst_liveness( + f: &IRFunction, cfg: &Cfg, block_live_out: &[Vec], block_live_in: &[Vec], reg_count: usize, +) -> (Vec>, Vec>) { + let bits = reg_count + 1; + let mut before = vec![vec![false; bits]; f.insts.len()]; + let mut after = vec![vec![false; bits]; f.insts.len()]; + for (b, block) in cfg.blocks.iter().enumerate() { + let mut live = block_live_out[b].clone(); // 从块级 liveOut 出发(非 liveIn) + for i in block.inst_range.clone().rev() { + after[i] = live.clone(); + // kill 先于 gen:本指令先写后读(COMPOUND_ADD 等读-写同寄存器,contract.rs:137) + if let Some(d) = f.insts[i].def_reg() { + live[d as usize] = false; + } + for u in f.insts[i].use_regs() { + live[u as usize] = true; + } + before[i] = live.clone(); + } + // Exception 目标块:入口 reg 0 隐式 def(异常展开写 regs[0])截断无 def use。 + // preds 是 Vec(无 EdgeKind),从源块 succs 侧检测 Exception 边。 + let is_exception_target = cfg + .blocks + .iter() + .any(|src| src.succs.iter().any(|&(dst, k)| dst == b && k == EdgeKind::Exception)); + if is_exception_target { + live[0] = false; + } + debug_assert_eq!(live, block_live_in[b], "ranges 反向扫描与 dataflow liveIn 不一致,块 {b}"); + } + (before, after) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dataflow::block_liveness; + use oxide_ir::inst::Inst; + use oxide_ir::operand::Operand; + use oxide_bytecode::opcode::OpCode; + + fn empty_function() -> IRFunction { + IRFunction::new() + } + + #[test] + fn linear_block_instruction_sets() { + // 0: ADD r2=r0+r1 1: RETURN r2 + let mut f = empty_function(); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1))); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None)); + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, live_out, rc) = block_liveness(&f, &cfg); + let (before, after) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); + assert!(after[0][2], "ADD 后 r2 存活(RETURN 用)"); + assert!(before[0][0] && before[0][1], "ADD 前 r0、r1 存活"); + assert!(!before[0][2], "ADD 前 r2 不死"); + assert!(after[1].iter().all(|&v| !v), "RETURN 后无存活"); + assert!(before[1][2], "RETURN 前 r2 存活"); + } + + #[test] + fn read_modify_write_kills_before_gen() { + // 0: COMPOUND_ADD rd=1, a=2 def {1} use {1,2} + // 1: RETURN r1 + let mut f = empty_function(); + f.insts.push(Inst::new(OpCode::COMPOUND_ADD, Operand::Reg(1), Operand::Reg(2), Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None)); + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, live_out, rc) = block_liveness(&f, &cfg); + let (before, _) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); + assert!(before[0][1], "COMPOUND_ADD 前 r1 旧值在读(kill 先于 gen)"); + assert!(before[0][2], "COMPOUND_ADD 前 r2 存活"); + assert!(live_in[0][1] && live_in[0][2], "块级 liveIn 含 r1、r2"); + } + + #[test] + fn call_result_reg0_range_is_short() { + // 0: call(callee=1, this=2, first_arg=3, nargs=1) def {0} use {1,2,3} + // 1: LOAD_VAR(Reg(4), None, None) def {4} use {0} + // 2: RETURN r4 + let mut f = empty_function(); + f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1)); + f.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(4), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(4), Operand::None, Operand::None)); + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, live_out, rc) = block_liveness(&f, &cfg); + let (before, after) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); + assert!(before[1][0], "LOAD_VAR 前读 CALL 结果 reg0"); + assert!(after[0][0], "CALL 后 reg0 存活(结果)"); + assert!(!before[0][0], "CALL 前 reg0 不死(区间仅 [CALL, LOAD_VAR))"); + assert!(!live_in[0][0], "reg0 不污染块入口"); + } + + #[test] + fn catch_entry_reg0_not_live_before_block() { + // 0: try_begin(3) 1: ADD r3=r3+r2 2: RETURN r3 + // 3: STORE_VAR(r5, None, None) 4: RETURN r5 + let mut f = empty_function(); + f.insts.push(Inst::try_begin(3)); + f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(3), Operand::Reg(2))); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::STORE_VAR, Operand::Reg(5), Operand::None, Operand::None)); + f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None)); + f.label_pos = vec![None, None, None, Some(3)]; + f.label_count = 1; + let cfg = oxide_cfg::build_cfg(&f); + let (live_in, live_out, rc) = block_liveness(&f, &cfg); + let (before, _) = inst_liveness(&f, &cfg, &live_out, &live_in, rc); + assert!(before[3][0], "catch 块内 STORE_VAR 读 reg0(异常值,正确)"); + let catch_block = cfg.blocks.iter().position(|b| b.inst_range == (3..5usize)).unwrap(); + assert!(!live_in[catch_block][0], "catch 块入口 reg0 被隐式 def 截断"); + assert!(!live_in[0][0], "try 块(entry)reg0 不污染入口"); + } +} diff --git a/crates/oxide_liveness/tests/smoke.rs b/crates/oxide_liveness/tests/smoke.rs new file mode 100644 index 00000000..b5011717 --- /dev/null +++ b/crates/oxide_liveness/tests/smoke.rs @@ -0,0 +1,61 @@ +//! 集成冒烟:真实编译产物(parse→emit→build_cfg→liveness)跑不崩 + 维度一致性 + try/catch reg0 断言 + 确定性。 + +use oxide_cfg::build_cfg; +use oxide_liveness::{liveness, LiveInfo}; + +fn run_liveness(src: &str) -> (LiveInfo, oxide_cfg::Cfg, usize) { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, src).expect("parse"); + let ir = oxide_emit::Emitter::new().emit_program(&program).expect("emit"); + let cfg = build_cfg(&ir); + let info = liveness(&ir, &cfg); + (info, cfg, ir.insts.len()) +} + +fn smoke(src: &str) { + let (info, cfg, inst_len) = run_liveness(src); + assert_eq!(info.block_live_in.len(), cfg.blocks.len(), "block 维度一致"); + assert_eq!(info.block_live_out.len(), cfg.blocks.len(), "block 维度一致"); + assert_eq!(info.inst_live_before.len(), inst_len, "inst 维度一致"); + assert_eq!(info.inst_live_after.len(), inst_len, "inst 维度一致"); +} + +#[test] +fn smoke_simple_function() { + smoke("function f(a, b) { return a + b; } f(1, 2)"); +} + +#[test] +fn smoke_if_else() { + smoke("function f(x) { if (x > 0) { return 1; } else { return 2; } } f(5)"); +} + +#[test] +fn smoke_loop_with_variables() { + smoke("function f(n) { var s = 0; for (var i = 0; i < n; i++) { s = s + i; } return s; } f(10)"); +} + +#[test] +fn smoke_try_catch() { + let (info, cfg, _) = run_liveness( + "function x() {} function y(e) {} function f() { try { x(); } catch (e) { y(e); } } f()", + ); + // entry 块 liveIn 不含 reg 0(异常边 reg0 截断在真实产物上成立) + assert!(!info.block_live_in[cfg.entry][0], "entry 块 liveIn 不得含 reg0"); +} + +#[test] +fn smoke_nested_calls() { + smoke("function g(x) { return x + 1; } function f(x) { return g(x) + g(x + 1); } f(1)"); +} + +#[test] +fn liveness_is_deterministic() { + let src = "function f(n) { var s = 0; for (var i = 0; i < n; i++) { s = s + i; } return s; } f(10)"; + let (a, _, _) = run_liveness(src); + let (b, _, _) = run_liveness(src); + assert_eq!(a.block_live_in, b.block_live_in); + assert_eq!(a.block_live_out, b.block_live_out); + assert_eq!(a.inst_live_before, b.inst_live_before); + assert_eq!(a.inst_live_after, b.inst_live_after); +}