diff --git a/crates/oxide_dce/src/precise_sweep.rs b/crates/oxide_dce/src/precise_sweep.rs index e2b4b172..73ac4443 100644 --- a/crates/oxide_dce/src/precise_sweep.rs +++ b/crates/oxide_dce/src/precise_sweep.rs @@ -39,7 +39,7 @@ pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mu continue; } let after = &live.inst_live_after[i]; - let not_live_after = |r: u32| !after.get(r as usize).copied().unwrap_or(false); + let not_live_after = |r: u32| !oxide_liveness::bitset_get(after, r as usize); // 通用规则:纯指令结果不被后续使用 → 删(is_pure=false 已防 CALL/SPILL/UNSPILL) if let Some(r) = inst.def_reg() { if not_live_after(r) && inst.is_pure(f) { @@ -72,14 +72,15 @@ mod tests { use crate::dce_precise; use oxide_ir::inst::Inst; - /// helper:稀疏号集 → bitset live_after。 - fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec> { + /// helper:稀疏号集 → u64 位集 live_after。 + fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec> { + let words = (reg_count + 1).div_ceil(64); after .iter() .map(|set| { - let mut v = vec![false; reg_count + 1]; + let mut v = vec![0u64; words]; for &r in *set { - v[r as usize] = true; + v[(r as usize) >> 6] |= 1u64 << ((r as usize) & 63); } v }) diff --git a/crates/oxide_liveness/src/dataflow.rs b/crates/oxide_liveness/src/dataflow.rs index a5f31f18..87ba9739 100644 --- a/crates/oxide_liveness/src/dataflow.rs +++ b/crates/oxide_liveness/src/dataflow.rs @@ -4,13 +4,18 @@ //! None→0 / This→254 / NewTarget→255 / CALL 隐式 reg0 全内置,不重复建。 //! Exception 边当普通边参与迭代;Exception 目标块入口 reg 0 隐式 def //! (异常展开写 regs[0],vm_runtime.rs:204-205)在此建模:gen.remove(0)+kill.insert(0)。 +//! +//! 位集为 `u64` 压缩(1 字 64 位,密度为 `Vec` 的 8 倍);不动点迭代 +//! 复用 out/input 两块缓冲(clear + 覆盖),不逐块逐迭代分配。 use oxide_cfg::{Cfg, EdgeKind}; use oxide_ir::IRFunction; +use crate::live_info::{bitset_clear, bitset_set}; + /// 块级 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) { +/// 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 { @@ -22,23 +27,23 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< } } - let bits = reg_count + 1; + let words = (reg_count + 1).div_ceil(64); 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]; + let mut gen: Vec> = vec![vec![0u64; words]; n]; + let mut kill: Vec> = vec![vec![0u64; words]; 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]; + let mut live = vec![0u64; words]; for i in block.inst_range.clone().rev() { if let Some(d) = f.insts[i].def_reg() { - live[d as usize] = false; + bitset_clear(&mut live, d as usize); } for u in f.insts[i].use_regs() { - live[u as usize] = true; + bitset_set(&mut live, u as usize); } } gen[b] = live; @@ -46,7 +51,7 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< // 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; + bitset_set(&mut kill[b], d as usize); } } @@ -58,39 +63,39 @@ pub(super) fn block_liveness(f: &IRFunction, cfg: &Cfg) -> (Vec>, Vec< .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; + bitset_clear(&mut gen[b], 0); + bitset_set(&mut kill[b], 0); } } // 3. reverse_postorder(确定性,禁 HashMap 迭代序) let rpo = reverse_postorder(cfg); - // 4. 不动点迭代 - let mut live_in = vec![vec![false; bits]; n]; - let mut live_out = vec![vec![false; bits]; n]; + // 4. 不动点迭代:out/input 缓冲循环外分配复用,clear + 覆盖免逐迭代分配 + let mut live_in = vec![vec![0u64; words]; n]; + let mut live_out = vec![vec![0u64; words]; n]; + let mut out = vec![0u64; words]; + let mut input = vec![0u64; words]; let mut iters = 0usize; loop { let mut changed = false; for &b in &rpo { // live_out[b] = ∪ live_in[s](全部 succs,含 Exception 边当普通边) - let mut out = vec![false; bits]; + out.fill(0); for &(succ, _kind) in &cfg.blocks[b].succs { - for (r, &v) in live_in[succ].iter().enumerate() { - if v { - out[r] = true; - } + let si = &live_in[succ]; + for (w, word) in out.iter_mut().enumerate() { + *word |= si[w]; } } // 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]); + for w in 0..words { + input[w] = gen[b][w] | (out[w] & !kill[b][w]); } if input != live_in[b] || out != live_out[b] { changed = true; - live_in[b] = input; - live_out[b] = out; + live_in[b].copy_from_slice(&input); + live_out[b].copy_from_slice(&out); } } if !changed { @@ -135,8 +140,8 @@ mod tests { use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; - fn set(v: &[bool], regs: &[u32]) -> bool { - regs.iter().all(|&r| v[r as usize]) + fn set(v: &[u64], regs: &[u32]) -> bool { + regs.iter().all(|&r| crate::live_info::bitset_get(v, r as usize)) } fn empty_function() -> IRFunction { @@ -154,12 +159,12 @@ mod tests { 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!(!crate::live_info::bitset_get(&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)); + assert!(live_in[exit].iter().all(|&w| w == 0)); + assert!(live_out[exit].iter().all(|&w| w == 0)); } #[test] @@ -187,15 +192,15 @@ mod tests { 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"); + assert!(crate::live_info::bitset_get(&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]); + assert!(!crate::live_info::bitset_get(&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]); + assert!(!crate::live_info::bitset_get(&live_in[els], 2)); } #[test] @@ -219,11 +224,11 @@ mod tests { 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(跨回边存活)"); + assert!(crate::live_info::bitset_get(&live_in[0], 1), "header liveIn 应含 cond r1"); + assert!(crate::live_info::bitset_get(&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]); + assert!(crate::live_info::bitset_get(&live_in[body], 2)); // 测试通过本身即不动点收敛证明 } @@ -250,11 +255,11 @@ mod tests { 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 截断"); + assert!(!crate::live_info::bitset_get(&live_in[catch], 0), "catch 块入口 reg0 应被隐式 def 截断"); // try 块(entry)liveIn 不含 0(未污染入口) - assert!(!live_in[0][0], "reg0 use 不得污染函数入口"); + assert!(!crate::live_info::bitset_get(&live_in[0], 0), "reg0 use 不得污染函数入口"); // try 块 liveIn 含 2(正常变量照常存活) - assert!(live_in[0][2], "try 块 liveIn 应含 r2"); + assert!(crate::live_info::bitset_get(&live_in[0], 2), "try 块 liveIn 应含 r2"); } #[test] @@ -268,6 +273,6 @@ mod tests { .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 消费生效"); + assert!(crate::live_info::bitset_get(&live_in[0], 0), "None→0 映射由 contract.rs 消费生效"); } } diff --git a/crates/oxide_liveness/src/lib.rs b/crates/oxide_liveness/src/lib.rs index d4a55bb6..853b5c4a 100644 --- a/crates/oxide_liveness/src/lib.rs +++ b/crates/oxide_liveness/src/lib.rs @@ -10,7 +10,7 @@ mod live_info; mod liveness_log; mod ranges; -pub use live_info::LiveInfo; +pub use live_info::{bitset_get, LiveInfo}; use oxide_cfg::Cfg; use oxide_ir::IRFunction; diff --git a/crates/oxide_liveness/src/live_info.rs b/crates/oxide_liveness/src/live_info.rs index a09170e6..8eab405c 100644 --- a/crates/oxide_liveness/src/live_info.rs +++ b/crates/oxide_liveness/src/live_info.rs @@ -1,17 +1,18 @@ //! LiveInfo:liveness 分析输出视图(pass 输出,不住进 IR)。 //! -//! 全部 live 集为按 reg 号索引的 bitset(`Vec`,长度 = reg_count+1,由 -//! dataflow 求上界)。四字段语义: +//! 全部 live 集为按 reg 号索引的 `u64` 位集(长度 = `(reg_count+1 + 63) / 64` 字, +//! 由 dataflow 求上界)。位 r 落在第 `r >> 6` 字的 `r & 63` 位;越界位恒 0(消费方 +//! 用 `bitset_get` 越界返回 false 对齐旧 `Vec::get` 语义)。四字段语义: //! - `block_live_in` / `block_live_out`:块级数据流迭代结果(精确 DCE 消费) //! - `inst_live_before` / `inst_live_after`:逐指令活集(RegAlloc 干涉图消费 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>, + pub block_live_in: Vec>, + pub block_live_out: Vec>, + pub inst_live_before: Vec>, + pub inst_live_after: Vec>, } impl LiveInfo { @@ -32,6 +33,22 @@ impl Default for LiveInfo { } } +/// 查询位集:位 r 置 1 返回 true;越界(r 超出位集容量)返回 false。 +/// 语义对齐旧 `Vec::get(r).copied().unwrap_or(false)`。 +pub fn bitset_get(bitset: &[u64], r: usize) -> bool { + bitset.get(r >> 6).is_some_and(|w| (w >> (r & 63)) & 1 == 1) +} + +/// 置位:位 r 写 1。调用方须保证 r 在位集容量内(def/use reg 号 ≤ reg_count 上界)。 +pub(crate) fn bitset_set(bitset: &mut [u64], r: usize) { + bitset[r >> 6] |= 1u64 << (r & 63); +} + +/// 清位:位 r 写 0。调用方须保证 r 在位集容量内。 +pub(crate) fn bitset_clear(bitset: &mut [u64], r: usize) { + bitset[r >> 6] &= !(1u64 << (r & 63)); +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/oxide_liveness/src/ranges.rs b/crates/oxide_liveness/src/ranges.rs index 22fbdae5..01a3ef11 100644 --- a/crates/oxide_liveness/src/ranges.rs +++ b/crates/oxide_liveness/src/ranges.rs @@ -5,29 +5,33 @@ //! 同寄存器指令 use 含 rd(contract.rs:137),gen 先于 kill 会把 rd 旧值从 //! live_before 错误剔除。Exception 目标块入口 reg 0 隐式 def 在此截断,并以 //! `debug_assert_eq!(live, block_live_in[b])` 校验与 dataflow 一致性。 +//! +//! 位集为 `u64` 压缩(与 dataflow 同 words 布局),逐指令集经 `copy_from_slice` 落地。 use oxide_cfg::{Cfg, EdgeKind}; use oxide_ir::IRFunction; +use crate::live_info::{bitset_clear, bitset_set}; + /// 逐指令 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()]; + f: &IRFunction, cfg: &Cfg, block_live_out: &[Vec], block_live_in: &[Vec], reg_count: usize, +) -> (Vec>, Vec>) { + let words = (reg_count + 1).div_ceil(64); + let mut before = vec![vec![0u64; words]; f.insts.len()]; + let mut after = vec![vec![0u64; words]; 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(); + after[i].copy_from_slice(&live); // kill 先于 gen:本指令先写后读(COMPOUND_ADD 等读-写同寄存器,contract.rs:137) if let Some(d) = f.insts[i].def_reg() { - live[d as usize] = false; + bitset_clear(&mut live, d as usize); } for u in f.insts[i].use_regs() { - live[u as usize] = true; + bitset_set(&mut live, u as usize); } - before[i] = live.clone(); + before[i].copy_from_slice(&live); } // Exception 目标块:入口 reg 0 隐式 def(异常展开写 regs[0])截断无 def use。 // preds 是 Vec(无 EdgeKind),从源块 succs 侧检测 Exception 边。 @@ -36,7 +40,7 @@ pub(super) fn inst_liveness( .iter() .any(|src| src.succs.iter().any(|&(dst, k)| dst == b && k == EdgeKind::Exception)); if is_exception_target { - live[0] = false; + bitset_clear(&mut live, 0); } debug_assert_eq!(live, block_live_in[b], "ranges 反向扫描与 dataflow liveIn 不一致,块 {b}"); } @@ -47,6 +51,7 @@ pub(super) fn inst_liveness( mod tests { use super::*; use crate::dataflow::block_liveness; + use crate::live_info::bitset_get; use oxide_bytecode::opcode::OpCode; use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; @@ -66,11 +71,11 @@ mod tests { 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 存活"); + assert!(bitset_get(&after[0], 2), "ADD 后 r2 存活(RETURN 用)"); + assert!(bitset_get(&before[0], 0) && bitset_get(&before[0], 1), "ADD 前 r0、r1 存活"); + assert!(!bitset_get(&before[0], 2), "ADD 前 r2 不死"); + assert!(after[1].iter().all(|&w| w == 0), "RETURN 后无存活"); + assert!(bitset_get(&before[1], 2), "RETURN 前 r2 存活"); } #[test] @@ -85,9 +90,9 @@ mod tests { 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"); + assert!(bitset_get(&before[0], 1), "COMPOUND_ADD 前 r1 旧值在读(kill 先于 gen)"); + assert!(bitset_get(&before[0], 2), "COMPOUND_ADD 前 r2 存活"); + assert!(bitset_get(&live_in[0], 1) && bitset_get(&live_in[0], 2), "块级 liveIn 含 r1、r2"); } #[test] @@ -104,10 +109,10 @@ mod tests { 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 不污染块入口"); + assert!(bitset_get(&before[1], 0), "LOAD_VAR 前读 CALL 结果 reg0"); + assert!(bitset_get(&after[0], 0), "CALL 后 reg0 存活(结果)"); + assert!(!bitset_get(&before[0], 0), "CALL 前 reg0 不死(区间仅 [CALL, LOAD_VAR))"); + assert!(!bitset_get(&live_in[0], 0), "reg0 不污染块入口"); } #[test] @@ -129,9 +134,9 @@ mod tests { 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(异常值,正确)"); + assert!(bitset_get(&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 不污染入口"); + assert!(!bitset_get(&live_in[catch_block], 0), "catch 块入口 reg0 被隐式 def 截断"); + assert!(!bitset_get(&live_in[0], 0), "try 块(entry)reg0 不污染入口"); } } diff --git a/crates/oxide_liveness/tests/smoke.rs b/crates/oxide_liveness/tests/smoke.rs index 35ba845c..09a67504 100644 --- a/crates/oxide_liveness/tests/smoke.rs +++ b/crates/oxide_liveness/tests/smoke.rs @@ -40,7 +40,7 @@ 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"); + assert!(!oxide_liveness::bitset_get(&info.block_live_in[cfg.entry], 0), "entry 块 liveIn 不得含 reg0"); } #[test] diff --git a/crates/oxide_regalloc/src/call_window.rs b/crates/oxide_regalloc/src/call_window.rs index 629831a5..1932a290 100644 --- a/crates/oxide_regalloc/src/call_window.rs +++ b/crates/oxide_regalloc/src/call_window.rs @@ -47,10 +47,17 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) { if matches!(inst.op, OpCode::CALL | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL) { let after = &live.inst_live_after[i]; // 存活上界 = 最大存活槽号 + 1;254/255 由帧单独保存,不进窗口。 + // 按 u64 位集逐字取置位(活集小,仅遍历实际存活的槽)。 let mut upper = 0u32; - for (reg, &alive) in after.iter().enumerate().take(254) { - if alive { - upper = (reg + 1) as u32; + for (word_idx, word) in after.iter().enumerate() { + let mut bits = *word; + while bits != 0 { + let bit = bits.trailing_zeros() as usize; + let reg = word_idx * 64 + bit; + if reg < 254 { + upper = (reg + 1) as u32; + } + bits &= bits - 1; } } if upper > 0 { diff --git a/crates/oxide_regalloc/src/graph.rs b/crates/oxide_regalloc/src/graph.rs index 64844d29..9c1f8817 100644 --- a/crates/oxide_regalloc/src/graph.rs +++ b/crates/oxide_regalloc/src/graph.rs @@ -253,7 +253,7 @@ pub(super) fn build( for i in 0..inst_count { let mut at_i: Vec = Vec::new(); for &v in &node_ids { - if live.inst_live_before[i].get(v as usize).copied().unwrap_or(false) { + if oxide_liveness::bitset_get(&live.inst_live_before[i], v as usize) { at_i.push(v); } } @@ -279,7 +279,7 @@ pub(super) fn build( if v == d { continue; } - if live.inst_live_after[i].get(v as usize).copied().unwrap_or(false) { + if oxide_liveness::bitset_get(&live.inst_live_after[i], v as usize) { adj_sets.entry(d).or_default().insert(v); adj_sets.entry(v).or_default().insert(d); }