diff --git a/crates/oxide_regalloc/src/color.rs b/crates/oxide_regalloc/src/color.rs index 275a0e13..6937908a 100644 --- a/crates/oxide_regalloc/src/color.rs +++ b/crates/oxide_regalloc/src/color.rs @@ -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 { /// Kemp 简化 + 贪心选色。返回 (vreg → 色, 无色的节点 = spill 候选)。 fn kemp_and_select(graph: &InterferenceGraph) -> (BTreeMap, Vec) { let k = graph.k; - // 工作集 = 未入栈的非预着色节点 - let mut worklist: BTreeSet = 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 = 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 = Vec::new(); let mut failed: Vec = 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; } } diff --git a/crates/oxide_regalloc/tests/color_api.rs b/crates/oxide_regalloc/tests/color_api.rs index ef07d94b..466cea67 100644 --- a/crates/oxide_regalloc/tests/color_api.rs +++ b/crates/oxide_regalloc/tests/color_api.rs @@ -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)); +}