perf: 逐指令活集拍平为单一位集加消费方维度守卫同步

This commit is contained in:
2026-10-12 12:18:10 +00:00
parent 215c74fe0b
commit 3cabfb70f9
12 changed files with 92 additions and 71 deletions
Generated
+20 -20
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1257,7 +1257,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1271,7 +1271,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1286,7 +1286,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1298,7 +1298,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1307,7 +1307,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"bumpalo",
"dashmap",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1333,7 +1333,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"tracing",
"tracing-appender",
@@ -1342,11 +1342,11 @@ dependencies = [
[[package]]
name = "oxide_net"
version = "0.16.13"
version = "0.16.14"
[[package]]
name = "oxide_parser"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1361,7 +1361,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1377,7 +1377,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"num-bigint",
"num-traits",
@@ -1390,7 +1390,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1410,7 +1410,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"bumpalo",
"dashmap",
@@ -1423,7 +1423,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.16.13"
version = "0.16.14"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -24,7 +24,7 @@ members = [
]
[workspace.package]
version = "0.16.13"
version = "0.16.14"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+3 -2
View File
@@ -75,8 +75,9 @@ pub fn dce_precise(f: &mut IRFunction, live: &LiveInfo) -> PreciseOutcome {
return PreciseOutcome { live_reusable: true };
}
// 维度守卫:live 与 insts 对齐才可信(删改后的过期 LiveInfo 索引错位会误删活指令);
// 守卫不过时活集必过期,调用方须重建。
if live.inst_live_after.len() != f.insts.len() {
// 拍平后外层长度 = N×words,空 LiveInfo 的 words 为 0(0==N*0 会误判对齐),
// 须同时判 words > 0。守卫不过时活集必过期,调用方须重建。
if live.inst_live_after.len() != f.insts.len() * live.words || live.words == 0 {
return PreciseOutcome { live_reusable: false };
}
let mut keep = vec![true; f.insts.len()];
+1 -1
View File
@@ -38,7 +38,7 @@ pub(super) fn pass_dead_with_liveness(f: &IRFunction, live: &LiveInfo, keep: &mu
if !keep[i] || label_target[i] {
continue;
}
let after = &live.inst_live_after[i];
let after = &live.inst_live_after[i * live.words..(i + 1) * live.words];
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() {
+10 -12
View File
@@ -6,23 +6,21 @@ use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
/// helper:稀疏号集 → u64 位集 live_after。
fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec<Vec<u64>> {
/// helper:稀疏号集 → u64 位集 live_after(拍平为单一 `Vec<u64>`,长度 = 指令数 × words)。
fn live_sets(after: &[&[u32]], reg_count: usize) -> Vec<u64> {
let words = (reg_count + 1).div_ceil(64);
after
.iter()
.map(|set| {
let mut v = vec![0u64; words];
for &r in *set {
v[(r as usize) >> 6] |= 1u64 << ((r as usize) & 63);
}
v
})
.collect()
let mut flat = vec![0u64; after.len() * words];
for (i, set) in after.iter().enumerate() {
for &r in *set {
flat[i * words + ((r as usize) >> 6)] |= 1u64 << ((r as usize) & 63);
}
}
flat
}
fn run_precise(f: &mut IRFunction, after: &[&[u32]], reg_count: usize) {
let mut live = LiveInfo::new();
live.words = (reg_count + 1).div_ceil(64);
live.inst_live_after = live_sets(after, reg_count);
dce_precise(f, &live);
}
+1
View File
@@ -29,5 +29,6 @@ pub fn liveness(f: &IRFunction, cfg: &Cfg) -> LiveInfo {
block_live_out: block_out,
inst_live_before: inst_before,
inst_live_after: inst_after,
words: (reg_count + 1).div_ceil(64),
}
}
+9 -4
View File
@@ -4,25 +4,29 @@
//! 由 dataflow 求上界)。位 r 落在第 `r >> 6` 字的 `r & 63` 位;越界位恒 0(消费方
//! 用 `bitset_get` 越界返回 false 对齐旧 `Vec<bool>::get` 语义)。四字段语义:
//! - `block_live_in` / `block_live_out`:块级数据流迭代结果(精确 DCE 消费)
//! - `inst_live_before` / `inst_live_after`:逐指令活集(RegAlloc 干涉图消费 live_before)
//! - `inst_live_before` / `inst_live_after`:逐指令活集(RegAlloc 干涉图消费 live_before),
//! 拍平为单一 `Vec<u64>`,长度 N×words,消费方按 i×words 切片取指
/// liveness 输出:块级 liveIn/liveOut + 逐指令 live_before/live_after。
#[derive(Debug, Clone)]
pub struct LiveInfo {
pub block_live_in: Vec<Vec<u64>>,
pub block_live_out: Vec<Vec<u64>>,
pub inst_live_before: Vec<Vec<u64>>,
pub inst_live_after: Vec<Vec<u64>>,
pub inst_live_before: Vec<u64>,
pub inst_live_after: Vec<u64>,
/// 位集字数(`(reg_count+1).div_ceil(64)`);空 LiveInfo 为 0,消费方守卫据此判过期。
pub words: usize,
}
impl LiveInfo {
/// 构造空 LiveInfo:全空 vec,等价于 Default。
/// 构造空 LiveInfo:全空 vec,words 为 0,等价于 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(),
words: 0,
}
}
}
@@ -60,6 +64,7 @@ mod tests {
assert!(info.block_live_out.is_empty());
assert!(info.inst_live_before.is_empty());
assert!(info.inst_live_after.is_empty());
assert_eq!(info.words, 0, "空 LiveInfo 的 words 为 0(守卫判过期依据)");
let default = LiveInfo::default();
assert!(default.block_live_in.is_empty());
assert!(default.block_live_out.is_empty());
+28 -18
View File
@@ -6,25 +6,28 @@
//! live_before 错误剔除。Exception 目标块入口 reg 0 隐式 def 在此截断,并以
//! `debug_assert_eq!(live, block_live_in[b])` 校验与 dataflow 一致性。
//!
//! 位集为 `u64` 压缩(与 dataflow 同 words 布局),逐指令集经 `copy_from_slice` 落地。
//! 位集为 `u64` 压缩(与 dataflow 同 words 布局),逐指令集拍平为单一 `Vec<u64>`
//! (长度 N×words)经 `copy_from_slice` 落地。
use oxide_cfg::Cfg;
use oxide_ir::IRFunction;
use crate::live_info::{bitset_clear, bitset_set};
/// 逐指令 liveness:返回 (inst_live_before, inst_live_after),长度 = insts.len()。
/// 逐指令 liveness:返回 (inst_live_before, inst_live_after),拍平为单一 `Vec<u64>`,
/// 长度 = insts.len() × words。
pub(super) fn inst_liveness(
f: &IRFunction, cfg: &Cfg, block_live_out: &[Vec<u64>], block_live_in: &[Vec<u64>], reg_count: usize,
) -> (Vec<Vec<u64>>, Vec<Vec<u64>>) {
) -> (Vec<u64>, Vec<u64>) {
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()];
let n = f.insts.len();
let mut before = vec![0u64; n * words];
let mut after = vec![0u64; n * words];
let exc_targets = crate::dataflow::exception_target_blocks(cfg);
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].copy_from_slice(&live);
after[i * words..(i + 1) * words].copy_from_slice(&live);
// kill 先于 gen:本指令先写后读。COMPOUND_ADD 等读-写同寄存器指令的
// uses 含 Rd,见 oxide_ir::contract 的 def/use 约定。
if let Some(d) = f.insts[i].def_reg() {
@@ -33,7 +36,7 @@ pub(super) fn inst_liveness(
for u in f.insts[i].use_regs() {
bitset_set(&mut live, u as usize);
}
before[i].copy_from_slice(&live);
before[i * words..(i + 1) * words].copy_from_slice(&live);
}
// Exception 目标块:入口 reg 0 隐式 def(异常展开写 regs[0])截断无 def use。
// preds 是 Vec<BBId>(无 EdgeKind),从源块 succs 侧检测 Exception 边。
@@ -69,11 +72,12 @@ 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!(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 存活");
let words = (rc + 1).div_ceil(64);
assert!(bitset_get(&after[0..words], 2), "ADD 后 r2 存活(RETURN 用)");
assert!(bitset_get(&before[0..words], 0) && bitset_get(&before[0..words], 1), "ADD 前 r0、r1 存活");
assert!(!bitset_get(&before[0..words], 2), "ADD 前 r2 不死");
assert!(after[words..2 * words].iter().all(|&w| w == 0), "RETURN 后无存活");
assert!(bitset_get(&before[words..2 * words], 2), "RETURN 前 r2 存活");
}
#[test]
@@ -88,8 +92,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!(bitset_get(&before[0], 1), "COMPOUND_ADD 前 r1 旧值在读(kill 先于 gen)");
assert!(bitset_get(&before[0], 2), "COMPOUND_ADD 前 r2 存活");
let words = (rc + 1).div_ceil(64);
assert!(bitset_get(&before[0..words], 1), "COMPOUND_ADD 前 r1 旧值在读(kill 先于 gen)");
assert!(bitset_get(&before[0..words], 2), "COMPOUND_ADD 前 r2 存活");
assert!(bitset_get(&live_in[0], 1) && bitset_get(&live_in[0], 2), "块级 liveIn 含 r1、r2");
}
@@ -107,9 +112,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!(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))");
let words = (rc + 1).div_ceil(64);
assert!(bitset_get(&before[words..2 * words], 0), "LOAD_VAR 前读 CALL 结果 reg0");
assert!(bitset_get(&after[0..words], 0), "CALL 后 reg0 存活(结果)");
assert!(!bitset_get(&before[0..words], 0), "CALL 前 reg0 不死(区间仅 [CALL, LOAD_VAR))");
assert!(!bitset_get(&live_in[0], 0), "reg0 不污染块入口");
}
@@ -132,7 +138,11 @@ 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!(bitset_get(&before[3], 0), "catch 块内 STORE_VAR 读 reg0(异常值,正确)");
let words = (rc + 1).div_ceil(64);
assert!(
bitset_get(&before[3 * words..4 * words], 0),
"catch 块内 STORE_VAR 读 reg0(异常值,正确)"
);
let catch_block = cfg.blocks.iter().position(|b| b.inst_range == (3..5usize)).unwrap();
assert!(!bitset_get(&live_in[catch_block], 0), "catch 块入口 reg0 被隐式 def 截断");
assert!(!bitset_get(&live_in[0], 0), "try 块(entry)reg0 不污染入口");
+6 -2
View File
@@ -16,8 +16,12 @@ 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 维度一致");
assert_eq!(
info.inst_live_before.len(),
inst_len * info.words,
"inst 维度一致(拍平后长度 = N×words)"
);
assert_eq!(info.inst_live_after.len(), inst_len * info.words, "inst 维度一致(拍平后长度 = N×words)");
}
#[test]
+5 -4
View File
@@ -32,9 +32,10 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) {
.insts
.iter()
.any(|i| matches!(i.op, OpCode::TRY_BEGIN | OpCode::TRY_FINALLY_BEGIN));
// LiveInfo 维度守卫:与 alloc 相同纪律,过期则重算。维度匹配时借用而非复制
// (LiveInfo 稠密矩阵全量 clone 只读数据无收益)。
let live = if live.inst_live_before.len() == f.insts.len() && !f.insts.is_empty() {
// LiveInfo 维度守卫:与 alloc 相同纪律,过期则重算。拍平后外层长度 = N×words,
// 空 LiveInfo 的 words 为 0(0==N*0 会误判对齐),须同时判 words > 0。
// 维度匹配时借用而非复制(LiveInfo 稠密矩阵全量 clone 只读数据无收益)。
let live = if live.inst_live_before.len() == f.insts.len() * live.words && live.words > 0 && !f.insts.is_empty() {
Cow::Borrowed(live)
} else {
let cfg = oxide_cfg::build_cfg(f);
@@ -45,7 +46,7 @@ pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) {
if !has_try && !f.is_generator && !f.is_async {
for (i, inst) in f.insts.iter_mut().enumerate() {
if matches!(inst.op, OpCode::CALL | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL) {
let after = &live.inst_live_after[i];
let after = &live.inst_live_after[i * live.words..(i + 1) * live.words];
// 存活上界 = 最大存活槽号 + 1;254/255 由帧单独保存,不进窗口。
// 按 u64 位集逐字取置位(活集小,仅遍历实际存活的槽)。
let mut upper = 0u32;
+4 -4
View File
@@ -106,8 +106,8 @@ pub(super) fn collect_real_vregs(f: &IRFunction) -> Vec<u32> {
/// spill_set 中的 vreg 不再是节点(其 fresh 替代品入图);fresh 按 at==i 并入该指令活集。
pub(super) fn build(f: &IRFunction, live: &LiveInfo, spill_set: &[u32], fresh: &[FreshVreg]) -> InterferenceGraph {
let mut real = collect_real_vregs(f);
// inst_live_before 长度 = 指令数(逐指令活集索引)
let inst_count = live.inst_live_before.len();
// inst_live_before 拍平后长度 = 指令数 × words,指令数由 len/words 还原(words 恒 ≥1)
let inst_count = live.inst_live_before.len() / live.words;
// 窗口只为参数连续性 MOV 桥预留;spread 调用实参经 ext 逐个读寄存器,无连续性要求。
let max_nargs = f
@@ -270,7 +270,7 @@ pub(super) fn build(f: &IRFunction, live: &LiveInfo, spill_set: &[u32], fresh: &
// 替代逐 node 全扫描——活集小(典型 2-4)时每指令从 O(nodes) 降到 O(live)。
// 升序遍历保证加边顺序与原 node 表升序扫描逐字节一致(确定性不漂移)。
let mut at_i: Vec<u32> = Vec::new();
let before = &live.inst_live_before[i];
let before = &live.inst_live_before[i * live.words..(i + 1) * live.words];
for (word_idx, word) in before.iter().enumerate() {
let mut bits = *word;
while bits != 0 {
@@ -298,7 +298,7 @@ pub(super) fn build(f: &IRFunction, live: &LiveInfo, spill_set: &[u32], fresh: &
// 寄存器同色会覆盖其值(elision 解构返回值被覆盖),须补此干涉边。
if let Some(d) = f.insts[i].def_reg() {
if oxide_liveness::bitset_get(&is_node, d as usize) {
let after = &live.inst_live_after[i];
let after = &live.inst_live_after[i * live.words..(i + 1) * live.words];
for (word_idx, word) in after.iter().enumerate() {
let mut bits = *word;
while bits != 0 {
+4 -3
View File
@@ -48,9 +48,10 @@ pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> {
return Ok(());
}
// LiveInfo 维度守卫:删改后的过期 LiveInfo 索引错位会误删活指令。
// 维度匹配时借用而非复制——LiveInfo 是稠密 bitset 矩阵(大函数可达数百 MB),
// 全量 clone 白白复制一次只读数据。
let live = if live.inst_live_before.len() == f.insts.len() && !f.insts.is_empty() {
// 拍平后外层长度 = N×words,空 LiveInfo 的 words 为 0(0==N*0 会误判对齐),
// 须同时判 words > 0。维度匹配时借用而非复制——LiveInfo 是稠密 bitset 矩阵
// (大函数可达数百 MB),全量 clone 白白复制一次只读数据。
let live = if live.inst_live_before.len() == f.insts.len() * live.words && live.words > 0 && !f.insts.is_empty() {
Cow::Borrowed(live)
} else {
let cfg = oxide_cfg::build_cfg(f);