mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 新增 oxide_regalloc crate,干涉图 + graph coloring + spill 拆分(D-09/D-10/D-11)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "oxide_regalloc"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "OxideJS - register allocation pass over IR (interference graph + graph coloring + spill splitting)"
|
||||
|
||||
[dependencies]
|
||||
oxide_ir = { path = "../oxide_ir" }
|
||||
oxide_liveness = { path = "../oxide_liveness" }
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
|
||||
[dev-dependencies]
|
||||
oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_emit = { path = "../oxide_emit" }
|
||||
oxide_vm = { path = "../oxide_vm" }
|
||||
oxide_cfg = { path = "../oxide_cfg" }
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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 非预着色");
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//! 集成冒烟:真实编译产物(parse→emit→build_cfg→liveness→color)不崩 + 完整性/确定性断言。
|
||||
|
||||
use oxide_regalloc::{Alloc, AllocMap};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
fn run_color(src: &str) -> (AllocMap, oxide_ir::IRFunction) {
|
||||
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 = oxide_cfg::build_cfg(&ir);
|
||||
let live = oxide_liveness::liveness(&ir, &cfg);
|
||||
let m = oxide_regalloc::color(&ir, &live).expect("color");
|
||||
(m, ir)
|
||||
}
|
||||
|
||||
/// 收集函数内全部字面真实 vreg(对照 contract def/use,排除 0/254/255 保留位)。
|
||||
fn literal_regs(ir: &oxide_ir::IRFunction) -> BTreeSet<u32> {
|
||||
let mut set = BTreeSet::new();
|
||||
for inst in &ir.insts {
|
||||
for o in [&inst.rd, &inst.a, &inst.b] {
|
||||
if let oxide_ir::operand::Operand::Reg(r) = o {
|
||||
if *r != 0 && *r != 254 && *r != 255 {
|
||||
set.insert(*r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
fn smoke(src: &str) {
|
||||
let (m, ir) = run_color(src);
|
||||
// 完整性:全部真实 vreg 有条目
|
||||
for r in literal_regs(&ir) {
|
||||
assert!(m.map.contains_key(&r), "vreg {r} 无分配");
|
||||
}
|
||||
// phys ≤ 253;非预着色色 < arg_window_base
|
||||
for a in m.map.values() {
|
||||
if let Alloc::Phys(c) = a {
|
||||
assert!(*c <= 253, "Phys 色 {c} 超 253");
|
||||
assert!(*c < m.arg_window_base || is_precolored(&ir, *c), "色 {c} 应在可分配集或为预着色");
|
||||
}
|
||||
}
|
||||
// spill slot 不重复
|
||||
let mut slots = BTreeSet::new();
|
||||
for s in &m.spills {
|
||||
assert!(slots.insert(s.slot), "spill slot {} 重复", s.slot);
|
||||
}
|
||||
assert!(m.phys_peak <= 253, "phys_peak {} 超 253", m.phys_peak);
|
||||
}
|
||||
|
||||
fn is_precolored(ir: &oxide_ir::IRFunction, c: u32) -> bool {
|
||||
let pl = ir.param_layout;
|
||||
if c >= pl.base && c < pl.base + pl.count {
|
||||
return true;
|
||||
}
|
||||
// escaped 预着色(简化:允许参数段色)
|
||||
false
|
||||
}
|
||||
|
||||
#[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() {
|
||||
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() {
|
||||
smoke("function x() {} function y(e) {} function f() { try { x(); } catch (e) { y(e); } } f()");
|
||||
}
|
||||
|
||||
#[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 smoke_class_field() {
|
||||
smoke("var outerVar = 42; class C { p = outerVar; } new C()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_destructuring() {
|
||||
smoke("function f() { var { a, b } = { a: 1, b: 2 }; return a + b; } f()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_is_deterministic_on_real_ir() {
|
||||
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_color(src);
|
||||
let (b, _) = run_color(src);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
Reference in New Issue
Block a user