feat(regalloc): 新增 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 f4e0fdf14e
commit 31bba82912
8 changed files with 876 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
//! AllocMap:寄存器染色决策输出视图(D-01:pass 输出,不住进 IR)。
//!
//! `AllocMap.map` 含全部真实 + fresh vreg 的 Phys/Spill 去向;`spills` 为
//! SPILL/UNSPILL 插入决策表(05-08 rewrite 消费);`phys_peak` 回写 n_registers;
//! `arg_window_base` 供调用点参数连续性 MOV 补位。BTreeMap 保确定性(B010,禁 HashMap)。
use std::collections::BTreeMap;
/// vreg 最终去向:Phys(物理色) / Spill(spill 栈帧内 slot)。
/// Phys 色域 1..=253(预着色节点可含保留号之外的值,见 graph.rs)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alloc {
Phys(u32),
Spill(u16),
}
/// 拆分 fresh vreg 的语义:Def = SPILL 在 def 指令后插入,Use = UNSPILL 在 use 指令前插入。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FreshKind {
Def,
Use,
}
/// 单点活度新 vreg(活点 = inst 下标)。owner = 所属被 spill 的 vreg。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FreshVreg {
pub id: u32,
pub at: usize,
pub kind: FreshKind,
pub owner: u32,
}
/// 溢出决策表:被 spill 的 vreg 的 def/use 点 + fresh vreg id 对照。
/// defs/uses 两 Vec 按下标升序。05-08 rewrite 消费此结构插 SPILL/UNSPILL。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpillPlan {
pub vreg: u32,
pub slot: u16,
pub defs: Vec<(usize, u32)>,
pub uses: Vec<(usize, u32)>,
}
/// 寄存器分配决策:vreg → Phys/Spill 映射 + spill 决策表 + 物理峰值 + 参数窗口基址。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AllocMap {
/// 含真实 + fresh 全部 vreg
pub map: BTreeMap<u32, Alloc>,
/// spill 决策表(按 vreg 升序)
pub spills: Vec<SpillPlan>,
/// 物理峰值(05-08 回写 n_registers,≤253)
pub phys_peak: u32,
/// 参数窗口基址(05-08 调用点 MOV 补位,max_nargs=0 时 = 254 无窗口)
pub arg_window_base: u32,
}
impl AllocMap {
/// 构造空 AllocMap:全空/置零,等价于 Default。
pub fn new() -> Self {
Self {
map: BTreeMap::new(),
spills: Vec::new(),
phys_peak: 0,
arg_window_base: 254,
}
}
}
impl Default for AllocMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_map_new_is_empty() {
let m = AllocMap::new();
assert!(m.map.is_empty());
assert!(m.spills.is_empty());
assert_eq!(m.phys_peak, 0);
assert_eq!(m.arg_window_base, 254);
assert!(AllocMap::default().map.is_empty());
}
}
+330
View File
@@ -0,0 +1,330 @@
//! 染色:Kemp 简化 + 贪心选色 + spill 区间拆分循环 → AllocMap(D-09/D-05/D-02)。
//!
//! 主循环:build 干涉图 → Kemp 简化(degree < k 入栈)→ 卡住选 spill 候选 → 被 spill 的
//! vreg 按 def/use 点拆成单点活度 fresh vreg → 重建图重试(外循环固定点)→ 收敛后贪心
//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(D-02 RangeError 路径)。
//!
//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap(B010)。
use std::collections::{BTreeMap, BTreeSet};
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
use crate::alloc_map::{Alloc, AllocMap, FreshKind, FreshVreg, SpillPlan};
use crate::graph::{self, InterferenceGraph};
/// 主染色循环:产出 AllocMap。
pub(super) fn run(f: &IRFunction, live: &LiveInfo, _graph: &InterferenceGraph) -> Result<AllocMap, String> {
let max_real = graph::collect_real_vregs(f).into_iter().max().unwrap_or(0);
let mut next_fresh_id = max_real + 1;
let mut next_slot: u16 = 0;
let mut spill_set: BTreeSet<u32> = BTreeSet::new();
let mut fresh: Vec<FreshVreg> = Vec::new();
let mut slot_for_vreg: BTreeMap<u32, u16> = BTreeMap::new();
let mut iters = 0usize;
let (colors, arg_window_base): (BTreeMap<u32, u32>, u32) = loop {
let graph = graph::build(f, live, &spill_set, &fresh);
let (colors, failed) = kemp_and_select(&graph);
if failed.is_empty() {
break (colors, graph.arg_window_base);
}
for v in failed {
if fresh.iter().any(|fr| fr.id == v) {
// 单点活度 ≥ k:无可行染色(D-02 错误路径,消息与 lower 逐字一致)
return Err("RangeError: function body uses too many registers (max 253)".into());
}
// 真实 vreg 溢出(D-05 区间拆分 v1)
spill_set.insert(v);
debug_assert!(u32::from(next_slot) <= u16::MAX as u32, "spill slot 超 u16 上限");
slot_for_vreg.insert(v, next_slot);
next_slot += 1;
for d in def_points(f, v) {
fresh.push(FreshVreg { id: next_fresh_id, at: d, kind: FreshKind::Def, owner: v });
next_fresh_id += 1;
}
for u in use_points(live, v) {
fresh.push(FreshVreg { id: next_fresh_id, at: u, kind: FreshKind::Use, owner: v });
next_fresh_id += 1;
}
}
iters += 1;
debug_assert!(
iters < max_real as usize * 2 + fresh.len() + 8,
"RegAlloc 染色疑似不收敛"
);
};
assemble(f, colors, spill_set, fresh, slot_for_vreg, arg_window_base)
}
/// def 点:`def_reg(insts[i]) == Some(v)` 的指令下标。
fn def_points(f: &IRFunction, v: u32) -> Vec<usize> {
f.insts
.iter()
.enumerate()
.filter(|(_, i)| i.def_reg() == Some(v))
.map(|(i, _)| i)
.collect()
}
/// use 点:`live_before[i][v]`(v 的活点)。RMW 指令同时是 def 与 use,各建一个 fresh。
fn use_points(live: &LiveInfo, v: u32) -> Vec<usize> {
live.inst_live_before
.iter()
.enumerate()
.filter(|(_, set)| set.get(v as usize).copied().unwrap_or(false))
.map(|(i, _)| i)
.collect()
}
/// Kemp 简化 + 贪心选色。返回 (vreg → 色, 无色的节点 = spill 候选)。
fn kemp_and_select(graph: &InterferenceGraph) -> (BTreeMap<u32, u32>, Vec<u32>) {
let k = graph.k;
// 工作集 = 未入栈的非预着色节点
let mut worklist: BTreeSet<u32> = graph
.nodes
.iter()
.filter(|(_, n)| n.pre_color.is_none())
.map(|(v, _)| *v)
.collect();
let mut stack: Vec<u32> = Vec::new();
let mut failed: Vec<u32> = Vec::new();
loop {
// 找 degree < k 的非预着色节点入栈
let found = worklist.iter().copied().find(|v| graph.nodes[v].adj.len() < k);
match found {
Some(v) => {
stack.push(v);
worklist.remove(&v);
}
None => {
if worklist.is_empty() {
break;
}
// 卡住:选 spill 候选(degree 最小 / vreg 号最小,确定性)
let cand = worklist
.iter()
.map(|v| (graph.nodes[v].adj.len(), *v))
.min()
.map(|(_, v)| v)
.unwrap();
failed.push(cand);
worklist.remove(&cand);
}
}
}
// 选择阶段:预着色节点固定;栈顶弹出取最低可用色
let mut colors: BTreeMap<u32, u32> = BTreeMap::new();
for (v, n) in &graph.nodes {
if let Some(pc) = n.pre_color {
colors.insert(*v, pc);
}
}
while let Some(v) = stack.pop() {
let mut used: BTreeSet<u32> = BTreeSet::new();
for &n in &graph.nodes[&v].adj {
if let Some(&c) = colors.get(&n) {
used.insert(c);
}
}
match graph.allocatable.iter().copied().find(|c| !used.contains(c)) {
Some(c) => {
colors.insert(v, c);
}
None => failed.push(v), // 理论上不触发(degree < k 保证有色)
}
}
(colors, failed)
}
/// 装配 AllocMap:map(真实 + fresh)+ spills 决策表 + phys_peak + arg_window_base。
fn assemble(
f: &IRFunction, colors: BTreeMap<u32, u32>, spill_set: BTreeSet<u32>, fresh: Vec<FreshVreg>,
slot_for_vreg: BTreeMap<u32, u16>, arg_window_base: u32,
) -> Result<AllocMap, String> {
let mut map = BTreeMap::new();
// 已染色 vreg(含 fresh)→ Phys
for (v, c) in &colors {
map.insert(*v, Alloc::Phys(*c));
}
// spill 决策表
let mut spills: Vec<SpillPlan> = Vec::new();
for v in spill_set {
let slot = slot_for_vreg[&v];
// defs = 该 vreg 的 def 点(kind=Def 且 owner==v)
let defs: Vec<(usize, u32)> = fresh
.iter()
.filter(|fr| fr.owner == v && fr.kind == FreshKind::Def)
.map(|fr| (fr.at, fr.id))
.collect();
let uses: Vec<(usize, u32)> = fresh
.iter()
.filter(|fr| fr.owner == v && fr.kind == FreshKind::Use)
.map(|fr| (fr.at, fr.id))
.collect();
spills.push(SpillPlan { vreg: v, slot, defs, uses });
}
spills.sort_by_key(|s| s.vreg);
let phys_peak = colors
.values()
.copied()
.chain(std::iter::once(f.param_layout.base + f.param_layout.count))
.chain(std::iter::once(1))
.max()
.unwrap_or(1)
.saturating_add(1)
.min(254);
Ok(AllocMap { map, spills, phys_peak, arg_window_base })
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
fn empty_function() -> IRFunction {
IRFunction::new()
}
fn color_of(
insts: Vec<Inst>, param_layout: oxide_ir::ParamLayout, nested: Vec<IRFunction>,
) -> Result<AllocMap, String> {
let mut f = empty_function();
f.insts = insts;
f.param_layout = param_layout;
f.nested = nested;
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
crate::color(&f, &live)
}
#[test]
fn overlapping_vregs_get_distinct_colors() {
// r1/r2 同时 live(ADD 前)→ 必须不同色;r3(ADD 结果)与二者不相交
let m = color_of(
vec![
Inst::load_const(Operand::Reg(1), 0),
Inst::load_const(Operand::Reg(2), 0),
Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)),
Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None),
],
oxide_ir::ParamLayout { base: 0, count: 0 },
Vec::new(),
)
.unwrap();
assert_ne!(m.map[&1], m.map[&2], "同时 live 的 r1/r2 必须不同色");
}
#[test]
fn disjoint_live_ranges_share_color() {
// r1 死于 ADD0,r4 生于 ADD1——活度不相交 → 可同色
let m = color_of(
vec![
Inst::load_const(Operand::Reg(1), 0),
Inst::load_const(Operand::Reg(2), 0),
Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)),
Inst::load_const(Operand::Reg(4), 0),
Inst::load_const(Operand::Reg(5), 0),
Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)),
Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None),
],
oxide_ir::ParamLayout { base: 0, count: 0 },
Vec::new(),
)
.unwrap();
if let (Alloc::Phys(c1), Alloc::Phys(c4)) = (m.map[&1], m.map[&4]) {
assert_eq!(c1, c4, "不相交活度应共享颜色");
} else {
panic!("r1/r4 应为 Phys");
}
}
#[test]
fn param_segment_keeps_colors() {
let m = color_of(
vec![
Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)),
Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None),
],
oxide_ir::ParamLayout { base: 1, count: 2 },
Vec::new(),
)
.unwrap();
assert_eq!(m.map[&1], Alloc::Phys(1));
assert_eq!(m.map[&2], Alloc::Phys(2));
assert_eq!(m.map.values().filter(|&&a| a == Alloc::Phys(1)).count(), 1);
assert_eq!(m.map.values().filter(|&&a| a == Alloc::Phys(2)).count(), 1);
}
#[test]
fn escaped_vreg_keeps_color_and_not_spilled() {
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 cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
let m = crate::color(&f, &live).unwrap();
assert_eq!(m.map[&3], Alloc::Phys(3), "escaped 槽保持原色");
assert!(m.spills.iter().all(|s| s.vreg != 3), "escaped vreg 不得被 spill");
}
#[test]
fn coloring_is_deterministic() {
let src_insts = vec![
Inst::new(OpCode::ADD, Operand::Reg(2), Operand::Reg(0), Operand::Reg(1)),
Inst::new(OpCode::RETURN, Operand::Reg(2), Operand::None, Operand::None),
];
let a = color_of(src_insts.clone(), oxide_ir::ParamLayout { base: 0, count: 0 }, Vec::new()).unwrap();
let b = color_of(src_insts, oxide_ir::ParamLayout { base: 0, count: 0 }, Vec::new()).unwrap();
assert_eq!(a, b);
}
#[test]
fn spill_split_recolors_fresh() {
// 长活 L + 254 短活 B_i(每个 B_i 与 L 在同一 ADD 指令 live):
// L 的 degree = 254 ≥ k=253 → kemp 卡住 → spill L → 各 def/use 点拆成单点 fresh →
// fresh 全部单点活度 < k → 重染色成功。这是"成功 spill"的标准场景。
let mut insts = Vec::new();
insts.push(Inst::load_const(Operand::Reg(1000), 0)); // L
for i in 0..254u32 {
insts.push(Inst::load_const(Operand::Reg(2000 + i), 0)); // B_i
insts.push(Inst::new(OpCode::ADD, Operand::Reg(3000 + i), Operand::Reg(2000 + i), Operand::Reg(1000)));
}
insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1000), Operand::None, Operand::None));
let m = color_of(insts, oxide_ir::ParamLayout { base: 0, count: 0 }, Vec::new()).unwrap();
assert!(!m.spills.is_empty(), "长活 L 应被 spill");
assert!(m.spills.iter().any(|s| s.vreg == 1000), "spill 的是 L");
assert!(m.phys_peak <= 253, "phys_peak ≤ 253");
// 全部 fresh 应有 Phys 分配
let fresh_phys: Vec<&Alloc> = m
.map
.values()
.filter(|a| matches!(a, Alloc::Phys(_)))
.collect();
assert!(!fresh_phys.is_empty(), "fresh vreg 应着色为 Phys");
}
#[test]
fn uncolorable_returns_error() {
// 255 参数 CALL:nargs=255 → arg_window_base=0 → k=0 → 全部 spill → fresh 失败 → Err
let insts = vec![
Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3000), 255),
Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None),
];
let result = color_of(insts, oxide_ir::ParamLayout { base: 0, count: 0 }, Vec::new());
assert!(result.is_err(), "单点活度 ≥ k 应报 Err");
assert!(result.unwrap_err().contains("too many registers"), "Err 消息应含 too many registers");
}
}
+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 非预着色");
}
}
+26
View File
@@ -0,0 +1,26 @@
//! RegAlloc 改写 pass(前半):干涉图染色决策,产出 AllocMap。
//!
//! 消费 `oxide_liveness::LiveInfo`(inst_live_before 逐指令活集,05-04 交付)与
//! `oxide_ir::IRFunction`(param_layout 参数段 + nested escaped 收集),产出 AllocMap
//! 分析视图(D-01:pass 输出不住进 IR)。染色:干涉图(graph.rs)+ Kemp 简化/贪心/
//! spill 区间拆分(color.rs)。**本模块不改写指令**——05-08 rewrite/finish 消费 AllocMap。
mod alloc_map;
mod color;
mod graph;
pub use alloc_map::{Alloc, AllocMap, FreshKind, FreshVreg, SpillPlan};
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
/// 染色:`&IRFunction + &LiveInfo → Result<AllocMap, String>`。纯函数,只读 IR。
/// 空 IRFunction 退化:Ok(AllocMap::new())。Err = 无可行染色(D-02 RangeError 路径,
/// 05-08 接线时转 lower 错误)。
pub fn color(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
if f.insts.is_empty() {
return Ok(AllocMap::new());
}
let graph = graph::build(f, live, &std::collections::BTreeSet::new(), &[]);
color::run(f, live, &graph)
}