perf: Kemp 染色工作列表改两集合,弹出取首元素消除二次方扫描

This commit is contained in:
2026-09-28 17:53:56 +00:00
parent 6f63ba67a4
commit 1d613001ed
2 changed files with 78 additions and 29 deletions
+24 -29
View File
@@ -5,7 +5,8 @@
//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(RangeError 路径,消息与
//! lower 逐字一致)。
//!
//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap。
//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap;Kemp 两集合的弹出顺序取
//! BTreeSet 首元素即现判定(light 最小 vreg、heavy 最小 (degree, vreg)),禁动态度数递减。
use std::collections::{BTreeMap, BTreeSet};
@@ -98,38 +99,32 @@ fn use_points(f: &IRFunction, v: u32) -> Vec<usize> {
/// 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();
// 两集合替代单工作列表,弹出顺序是现判定的直译:light 以 vreg 为键,首元素即
// light 节点中最小 vreg(现 `find(degree < k)` 的命中);heavy 以 (degree, vreg)
// 为键,首元素即最小 (degree, vreg)(现卡住分支的 min)。degree 取全图邻接长度,
// 循环中不递减;初始化分流与弹出移除必须同步,同一节点只进一个集合。
let mut light: BTreeSet<u32> = BTreeSet::new();
let mut heavy: BTreeSet<(u32, u32)> = BTreeSet::new();
for (v, n) in &graph.nodes {
if n.pre_color.is_none() {
if n.adj.len() < k {
light.insert(*v);
} else {
heavy.insert((n.adj.len() as u32, *v));
}
}
}
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);
}
if let Some(v) = light.pop_first() {
stack.push(v);
} else if let Some((_, v)) = heavy.pop_first() {
// 卡住:light 已空,取最小 (degree, vreg) 作 spill 候选
failed.push(v);
} else {
break;
}
}
+54
View File
@@ -154,3 +154,57 @@ fn uncolorable_returns_error() {
assert!(result.is_err(), "单点活度 ≥ k 应报 Err");
assert!(result.unwrap_err().contains("too many registers"), "Err 消息应含 too many registers");
}
#[test]
fn pop_order_is_lowest_vreg_among_light_nodes() {
// 混合图:vreg 5 的 degree 为 2(邻 1/7)、vreg 7 的 degree 为 1(邻 5)、vreg 1 的 degree 为 1
// (邻 5),最小 vreg 的 degree 不是全图最小。弹出顺序是"light 节点中最小 vreg"(1, 5, 7 依次),
// 不是"最小 degree 优先"(后者会先弹 8/9)。两种顺序染色结果不同:现实现 5→2、7→1;
// 若按最小 degree 优先弹,则 5→1、7→2。断言值由现实现跑出后固化,钉死弹出顺序语义。
let m = color_of(
vec![
Inst::load_const(Operand::Reg(1), 0),
Inst::load_const(Operand::Reg(5), 0),
Inst::new(OpCode::ADD, Operand::Reg(8), Operand::Reg(1), Operand::Reg(5)),
Inst::load_const(Operand::Reg(7), 0),
Inst::new(OpCode::ADD, Operand::Reg(9), Operand::Reg(5), Operand::Reg(7)),
Inst::new(OpCode::RETURN, Operand::Reg(9), Operand::None, Operand::None),
],
ParamLayout { base: 0, count: 0 },
Vec::new(),
)
.unwrap();
assert_eq!(m.map[&5], Alloc::Phys(2), "5 先弹、后染色,7 先占 1 号色");
assert_eq!(m.map[&7], Alloc::Phys(1), "7 后弹、先染色,取最低可用色 1");
assert_eq!(m.map[&1], Alloc::Phys(1), "1 最后染色,邻 5 已占 2 号色");
}
#[test]
fn stuck_spill_order_is_degree_then_vreg() {
// 250 个未使用参数预着色 1..250(k=3)+ 六个非预着色节点全部 degree ≥ 3:
// 300/301/302/303 同时 live(K4),304 与 300/301/302 同时 live,305 是 303 的
// kill 结果(def 规则向 live_after 的 300/301/302 补边)。degree 为
// 304:3、305:3、303:4、300:5、301:5、302:5。全部卡住,spill 候选序按
// (degree, vreg) 升序:304、305、303、300、301、302(不是 vreg 号序 300..305)。
// slot 号记录第一轮 failed 顺序,断言其钉死卡住路径的取最小 (degree, vreg) 语义。
let insts = vec![
Inst::load_const(Operand::Reg(300), 0),
Inst::load_const(Operand::Reg(301), 0),
Inst::load_const(Operand::Reg(302), 0),
Inst::load_const(Operand::Reg(303), 0),
Inst::new(OpCode::ADD, Operand::Reg(305), Operand::Reg(303), Operand::Reg(1)),
Inst::load_const(Operand::Reg(304), 0),
Inst::new(OpCode::ADD, Operand::Reg(306), Operand::Reg(300), Operand::Reg(304)),
Inst::new(OpCode::ADD, Operand::Reg(307), Operand::Reg(301), Operand::Reg(302)),
Inst::new(OpCode::RETURN, Operand::Reg(307), Operand::None, Operand::None),
];
let m = color_of(insts, ParamLayout { base: 1, count: 250 }, Vec::new()).unwrap();
assert_eq!(m.spills.len(), 6, "六个非预着色节点应全部 spill");
let slot = |v: u32| m.spills.iter().find(|s| s.vreg == v).map(|s| s.slot);
assert_eq!(slot(304), Some(0), "最小 (degree, vreg) 是 304,最先 spill");
assert_eq!(slot(305), Some(1));
assert_eq!(slot(303), Some(2));
assert_eq!(slot(300), Some(3));
assert_eq!(slot(301), Some(4));
assert_eq!(slot(302), Some(5));
}