feat: 新增 oxide_regalloc crate,干涉图 + graph coloring + spill 拆分(D-09/D-10/D-11)

This commit is contained in:
2026-08-06 17:13:27 +08:00
parent 9d619cd9bb
commit 0b0d36fa85
8 changed files with 876 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
//! 干涉图构建 + 预着色 + 可分配色集(D-09/D-10/D-11 + RESEARCH 发现 4)。
//!
//! - 真实 vreg:字面 `Operand::Reg(r)`(rd/a/b)+ CALL 系参数连续区间 [first, first+nargs)
//! - 干涉边:逐指令 `live_before[i]` 中同时 live 的真实 vreg 两两加边(fresh vreg 按 at==i 并入)
//! - 预着色:参数段 [param_base, base+count) 钉死原号(VM 调用契约 D-10);escaped vreg
//! (nested `LOAD_VAR.a`/`STORE_VAR.rd` 直引父槽)钉死原槽号且排除 spill(B012)
//! - 可分配色集:`(1..=253) − 参数色 − escaped 色 − [arg_window_base, 253]`
//! (arg_window_base = 254 − max_nargs,为 05-08 调用点 MOV 补位留空槽)
//!
//! 确定性:全 BTreeMap + Vec 排序,禁 HashMap 迭代序(B010)。
use std::collections::{BTreeMap, BTreeSet};
use oxide_bytecode::opcode::OpCode;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
use crate::alloc_map::FreshVreg;
/// 干涉图节点:vreg(以 map key 表达)+ 预着色 + 邻接表(排序去重)。
#[derive(Debug, Clone)]
pub(super) struct Node {
pub pre_color: Option<u32>,
pub adj: Vec<u32>,
}
/// 干涉图:节点表 + 可分配色集 + 色数 k + 参数窗口基址。
#[derive(Debug, Clone)]
pub(super) struct InterferenceGraph {
pub nodes: BTreeMap<u32, Node>,
pub allocatable: Vec<u32>,
pub k: usize,
pub arg_window_base: u32,
}
/// 收集全部真实 vreg(BTreeSet 保确定性):
/// 字面 `Operand::Reg(r)`(rd/a/b 三槽)+ CALL/CALL_NATIVE/NEW_EXPRESSION b 槽参数区间
/// + SUPER_CALL a 槽参数区间。0 无条件排除(reg 0 永非真实——emit alloc_reg 从 ≥1 起)。
///
/// 254/255 的 over-approx 规则:live bitset 中位 254/255 无法区分 This/NewTarget 占用与
/// 真实 vreg 活度,若函数存在该号真实 vreg(大函数 B005 场景)则按真实 vreg 建节点染色——
/// 多出的边只造成过度约束(安全方向),缺失边才会错值。
pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet<u32> {
let mut real = BTreeSet::new();
for inst in &f.insts {
for o in [&inst.rd, &inst.a, &inst.b] {
if let Operand::Reg(r) = o {
if *r != 0 {
real.insert(*r);
}
}
}
let nargs = inst.ext.first().copied().unwrap_or(0);
let first = match inst.op {
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION => match inst.b {
Operand::Reg(fr) => Some(fr),
_ => None,
},
OpCode::SUPER_CALL => match inst.a {
Operand::Reg(fr) => Some(fr),
_ => None,
},
_ => None,
};
if let Some(fr) = first {
for r in fr..fr + nargs {
if r != 0 {
real.insert(r);
}
}
}
}
real
}
/// 构建干涉图:`build(f, live, spill_set, fresh) -> InterferenceGraph`。
/// spill_set 中的 vreg 不再是节点(其 fresh 替代品入图);fresh 按 at==i 并入该指令活集。
pub(super) fn build(
f: &IRFunction, live: &LiveInfo, spill_set: &BTreeSet<u32>, fresh: &[FreshVreg],
) -> InterferenceGraph {
let real = collect_real_vregs(f);
let reg_count = live.inst_live_before.len();
// ── 预着色 ──
let mut pre_colors: BTreeMap<u32, u32> = BTreeMap::new();
// 参数段:VM 调用写 regs[param_base+i],钉死不可动(D-10)
let pl = f.param_layout;
if pl.count > 0 {
for i in 0..pl.count {
let v = pl.base + i;
pre_colors.insert(v, v);
}
}
// escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012)
let mut escaped_colors: Vec<u32> = Vec::new();
collect_escaped(&f.nested, &mut pre_colors, &mut escaped_colors);
// ── 可分配色集 ──
let max_nargs = f
.insts
.iter()
.filter(|i| {
matches!(
i.op,
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL
)
})
.map(|i| i.ext.first().copied().unwrap_or(0))
.max()
.unwrap_or(0);
let arg_window_base = 254u32.saturating_sub(max_nargs);
let mut allocatable: Vec<u32> = Vec::new();
for c in 1u32..=253 {
if pre_colors.contains_key(&c) || escaped_colors.contains(&c) || c >= arg_window_base {
continue;
}
allocatable.push(c);
}
let k = allocatable.len();
// ── 节点 + 干涉边 ──
let mut nodes: BTreeMap<u32, Node> = BTreeMap::new();
for &v in real.iter() {
if !spill_set.contains(&v) {
nodes.insert(
v,
Node { pre_color: pre_colors.get(&v).copied(), adj: Vec::new() },
);
}
}
for fr in fresh {
nodes.entry(fr.id).or_insert(Node { pre_color: None, adj: Vec::new() });
}
// 邻接表(BTreeMap<u32, BTreeSet<u32>> 去重后转 Vec 排序)
let mut adj_sets: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::new();
let node_ids: Vec<u32> = nodes.keys().copied().collect();
for &v in &node_ids {
adj_sets.entry(v).or_default();
}
for i in 0..reg_count {
let mut at_i: Vec<u32> = Vec::new();
for &v in &node_ids {
if live.inst_live_before[i].get(v as usize).copied().unwrap_or(false) {
at_i.push(v);
}
}
for fr in fresh {
if fr.at == i {
at_i.push(fr.id);
}
}
// 两两加边
for a in 0..at_i.len() {
for b in a + 1..at_i.len() {
let (x, y) = (at_i[a], at_i[b]);
adj_sets.entry(x).or_default().insert(y);
adj_sets.entry(y).or_default().insert(x);
}
}
}
for (v, set) in adj_sets {
if let Some(node) = nodes.get_mut(&v) {
node.adj = set.into_iter().collect();
}
}
InterferenceGraph { nodes, allocatable, k, arg_window_base }
}
/// 递归收集 nested 树中变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽),
/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除,B012)。
fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, out: &mut Vec<u32>) {
for sub in nested {
for inst in &sub.insts {
let slot = match inst.op {
OpCode::LOAD_VAR => inst.a,
OpCode::STORE_VAR => inst.rd,
_ => Operand::None,
};
if let Operand::Reg(r) = slot {
pre_colors.entry(r).or_insert(r);
if !out.contains(&r) {
out.push(r);
}
}
}
collect_escaped(&sub.nested, pre_colors, out);
}
out.sort_unstable();
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
fn empty_function() -> IRFunction {
IRFunction::new()
}
fn build_graph(f: &IRFunction) -> InterferenceGraph {
let cfg = oxide_cfg::build_cfg(f);
let live = oxide_liveness::liveness(f, &cfg);
build(f, &live, &BTreeSet::new(), &[])
}
#[test]
fn simultaneously_live_get_edge() {
// 0: LOAD_CONST r1 1: LOAD_CONST r2 2: ADD r3=r1+r2(r1/r2 同时 live) 3: RETURN r3
let mut f = empty_function();
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
f.insts.push(Inst::load_const(Operand::Reg(2), 0));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
let g = build_graph(&f);
assert!(g.nodes[&1].adj.contains(&2), "r1 与 r2 应邻接");
assert!(g.nodes[&2].adj.contains(&1));
assert!(!g.nodes[&3].adj.contains(&1), "r3 与 r1 无干涉(r1 死于 ADD)");
}
#[test]
fn disjoint_ranges_no_edge() {
// r1/r2 死于 ADD0,r4/r5 生于 ADD1——两段活度不相交
let mut f = empty_function();
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
f.insts.push(Inst::load_const(Operand::Reg(2), 0));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::load_const(Operand::Reg(4), 0));
f.insts.push(Inst::load_const(Operand::Reg(5), 0));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None));
let g = build_graph(&f);
assert!(!g.nodes[&1].adj.contains(&4), "r1 与 r4 活度不相交,无边");
}
#[test]
fn param_segment_precolored() {
let mut f = empty_function();
f.param_layout = oxide_ir::ParamLayout { base: 1, count: 2 };
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
let g = build_graph(&f);
assert_eq!(g.nodes[&1].pre_color, Some(1));
assert_eq!(g.nodes[&2].pre_color, Some(2));
assert!(!g.allocatable.contains(&1) && !g.allocatable.contains(&2), "参数色排除");
assert_eq!(g.k, g.allocatable.len());
}
#[test]
fn escaped_slots_precolored() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let mut sub = empty_function();
sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None));
f.nested.push(sub);
let g = build_graph(&f);
assert_eq!(g.nodes[&3].pre_color, Some(3), "escaped 槽 r3 预着色");
assert!(!g.allocatable.contains(&3), "escaped 色排除");
}
#[test]
fn reserved_colors_excluded() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
let g = build_graph(&f);
assert!(!g.allocatable.contains(&0), "色 0 排除(CALL 隐式 reg0)");
assert!(!g.allocatable.contains(&254) && !g.allocatable.contains(&255), "254/255 排除");
assert!(g.allocatable.iter().all(|&c| (1..=253).contains(&c)));
}
#[test]
fn arg_window_reserved() {
let mut f = empty_function();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 3));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let g = build_graph(&f);
assert_eq!(g.arg_window_base, 251, "max_nargs=3 → base 251");
assert!(!g.allocatable.contains(&251) && !g.allocatable.contains(&252) && !g.allocatable.contains(&253));
}
#[test]
fn high_numbered_vreg_254_is_real_node() {
// 大函数场景:真实 vreg 254 必须建节点(B005 正确性前提)
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(252), Operand::Reg(253)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
let real = collect_real_vregs(&f);
assert!(real.contains(&254), "真实 vreg 254 必须被收集");
let g = build_graph(&f);
assert!(g.nodes.contains_key(&254), "vreg 254 建节点");
assert_eq!(g.nodes[&254].pre_color, None, "254 非预着色");
}
}