fix: support high register slots in register allocation
This commit is contained in:
@@ -47,7 +47,7 @@ pub struct AllocMap {
|
||||
pub map: BTreeMap<u32, Alloc>,
|
||||
/// spill 决策表(按 vreg 升序)
|
||||
pub spills: Vec<SpillPlan>,
|
||||
/// 物理峰值(finish 回写 n_registers,≤253)
|
||||
/// 物理窗口大小(finish 回写 n_registers,最高槽 253 对应值 254)。
|
||||
pub phys_peak: u32,
|
||||
/// 参数窗口基址(调用点 MOV 补位,max_nargs=0 时 = 254 无窗口)
|
||||
pub arg_window_base: u32,
|
||||
|
||||
@@ -17,7 +17,12 @@ use crate::graph::{self, InterferenceGraph};
|
||||
|
||||
/// 主染色循环:产出 AllocMap。
|
||||
pub(super) fn run(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
|
||||
let max_real = graph::collect_real_vregs(f).into_iter().max().unwrap_or(0);
|
||||
let max_param = if f.param_layout.count == 0 {
|
||||
0
|
||||
} else {
|
||||
f.param_layout.base + f.param_layout.count - 1
|
||||
};
|
||||
let max_real = graph::collect_real_vregs(f).into_iter().max().unwrap_or(0).max(max_param);
|
||||
let mut next_fresh_id = max_real + 1;
|
||||
let mut next_slot: u16 = 0;
|
||||
let mut spill_set: BTreeSet<u32> = BTreeSet::new();
|
||||
@@ -64,7 +69,7 @@ pub(super) fn run(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
|
||||
debug_assert!(iters < max_real as usize * 2 + fresh.len() + 8, "RegAlloc 染色疑似不收敛");
|
||||
};
|
||||
|
||||
assemble(f, colors, spill_set, fresh, slot_for_vreg, arg_window_base)
|
||||
assemble(colors, spill_set, fresh, slot_for_vreg, arg_window_base)
|
||||
}
|
||||
|
||||
/// def 点:`def_reg(insts[i]) == Some(v)` 的指令下标。
|
||||
@@ -154,8 +159,8 @@ fn kemp_and_select(graph: &InterferenceGraph) -> (BTreeMap<u32, u32>, Vec<u32>)
|
||||
|
||||
/// 装配 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,
|
||||
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
|
||||
@@ -184,7 +189,6 @@ fn assemble(
|
||||
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)
|
||||
@@ -299,6 +303,19 @@ mod tests {
|
||||
assert!(m.spills.iter().all(|s| s.vreg != 3), "escaped vreg 不得被 spill");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highest_legal_color_uses_254_register_window() {
|
||||
let mut f = empty_function();
|
||||
f.builtin_reg_map = vec![("late".to_string(), 253)];
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(253), Operand::None, Operand::None));
|
||||
let cfg = oxide_cfg::build_cfg(&f);
|
||||
let live = oxide_liveness::liveness(&f, &cfg);
|
||||
let map = crate::color(&f, &live).unwrap();
|
||||
assert_eq!(map.map[&253], Alloc::Phys(253));
|
||||
assert_eq!(map.phys_peak, 254);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn coloring_is_deterministic() {
|
||||
let src_insts = vec![
|
||||
@@ -325,7 +342,7 @@ mod tests {
|
||||
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");
|
||||
assert!(m.phys_peak <= 254, "phys_peak ≤ 254");
|
||||
// 全部 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");
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures。
|
||||
//!
|
||||
//! - n_registers = map.phys_peak(≤253;不含参数窗口——窗口是调用点暂存槽,
|
||||
//! - n_registers = map.phys_peak(≤254;最高合法物理槽 253 对应窗口大小 254;
|
||||
//! 不含参数窗口——窗口是调用点暂存槽,
|
||||
//! CALL handler 在 push 前收集 args 进 Vec,n_registers 只控 save_stack 窗口)
|
||||
//! - builtin_reg_map:Phys → 新号;Spill → spilled_builtin_bindings 的自由色 R(与 rewrite 同源)
|
||||
//! - param_layout 不动(参数段 pre-colored 恒等);upvalue_captures 不动(escaped 预着色恒等)
|
||||
//! - param_layout 按参数首槽物理色回写;upvalue_captures 不动(escaped 预着色恒等)
|
||||
|
||||
use crate::alloc_map::{Alloc, AllocMap};
|
||||
use crate::rewrite::spilled_builtin_bindings;
|
||||
@@ -12,36 +13,44 @@ use oxide_ir::IRFunction;
|
||||
/// 回写 IRFunction 元数据(不动 insts——rewrite 已完成改写)。
|
||||
pub(super) fn run(f: &mut IRFunction, map: &AllocMap) {
|
||||
// (a) n_registers = phys_peak
|
||||
debug_assert!(map.phys_peak <= 253, "phys_peak 超 253");
|
||||
debug_assert!(map.phys_peak <= 254, "phys_peak 超 254");
|
||||
f.n_registers = map.phys_peak;
|
||||
|
||||
// (b) builtin_reg_map 回写
|
||||
let spilled = spilled_builtin_bindings(f, map);
|
||||
for (name, vreg) in &mut f.builtin_reg_map {
|
||||
let mut rewritten_builtins = Vec::with_capacity(f.builtin_reg_map.len());
|
||||
for (name, vreg) in &f.builtin_reg_map {
|
||||
let v = *vreg;
|
||||
match map.map.get(&v) {
|
||||
Some(Alloc::Phys(p)) => {
|
||||
*vreg = *p;
|
||||
rewritten_builtins.push((name.clone(), *p));
|
||||
}
|
||||
Some(Alloc::Spill(_)) => {
|
||||
if let Some((_, r, _)) = spilled.iter().find(|(n, _, _)| n == name) {
|
||||
debug_assert!(*r <= 253);
|
||||
*vreg = *r;
|
||||
rewritten_builtins.push((name.clone(), *r));
|
||||
} else {
|
||||
debug_assert!(false, "spilled builtin {name} 无对应入口 SPILL");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// 未使用的 builtin(引用被 DCE 删除,vreg 不在 AllocMap)。
|
||||
// builtin 槽是预注册低号,保持原值无害(VM 入口写入,无读取者)。
|
||||
// 保持原项(*vreg 不变)。
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
f.builtin_reg_map = rewritten_builtins;
|
||||
|
||||
// (c) param_layout 不动(参数段预着色恒等;窗口与参数段重叠仅参数段贴 254 的罕见情形)
|
||||
// (c) 参数段保持连续,但继承父上下文产生的高虚拟段会整体移动到低位物理段。
|
||||
let pl = f.param_layout;
|
||||
debug_assert!(pl.base + pl.count <= map.arg_window_base || map.arg_window_base == 254);
|
||||
if pl.count > 0 {
|
||||
if let Some(Alloc::Phys(base)) = map.map.get(&pl.base) {
|
||||
for i in 0..pl.count {
|
||||
debug_assert_eq!(map.map.get(&(pl.base + i)), Some(&Alloc::Phys(base + i)));
|
||||
}
|
||||
f.param_layout.base = *base;
|
||||
}
|
||||
debug_assert!(f.param_layout.base + f.param_layout.count <= map.arg_window_base || map.arg_window_base == 254);
|
||||
} else {
|
||||
f.param_layout.base = 0;
|
||||
}
|
||||
|
||||
// (d) upvalue_captures 不动。enclosing_reg 是编译期产物(MAKE_CELL 定位用),VM 运行时
|
||||
// 不读(CREATE_CLOSURE 走 cell_idx,cell 捕获后值在 cell 中)——无需跨函数同步。
|
||||
@@ -101,6 +110,18 @@ mod tests {
|
||||
assert_eq!(f.builtin_reg_map[0].1, binds[0].1, "回写 R 与入口 SPILL 同源");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unused_builtin_is_removed_from_frame_metadata() {
|
||||
let mut f = IRFunction::new();
|
||||
f.builtin_reg_map = vec![("late".to_string(), 426)];
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
|
||||
let mut map = AllocMap::new();
|
||||
map.map.insert(1, Alloc::Phys(1));
|
||||
run(&mut f, &map);
|
||||
assert!(f.builtin_reg_map.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_layout_untouched() {
|
||||
let mut f = IRFunction::new();
|
||||
@@ -111,6 +132,15 @@ mod tests {
|
||||
assert_eq!(f.param_layout.count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_parameter_layout_discards_compile_time_boundary() {
|
||||
let mut f = IRFunction::new();
|
||||
f.param_layout = oxide_ir::ParamLayout { base: 426, count: 0 };
|
||||
let map = AllocMap::new();
|
||||
run(&mut f, &map);
|
||||
assert_eq!(f.param_layout, oxide_ir::ParamLayout { base: 0, count: 0 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enclosing_reg_unchanged() {
|
||||
let mut f = IRFunction::new();
|
||||
|
||||
@@ -95,30 +95,10 @@ pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet<u32> {
|
||||
pub(super) fn build(
|
||||
f: &IRFunction, live: &LiveInfo, spill_set: &BTreeSet<u32>, fresh: &[FreshVreg],
|
||||
) -> InterferenceGraph {
|
||||
let real = collect_real_vregs(f);
|
||||
let mut real = collect_real_vregs(f);
|
||||
// inst_live_before 长度 = 指令数(逐指令活集索引)
|
||||
let inst_count = live.inst_live_before.len();
|
||||
|
||||
// ── 预着色 ──
|
||||
let mut pre_colors: BTreeMap<u32, u32> = BTreeMap::new();
|
||||
// 参数段:VM 调用写 regs[param_base+i],钉死不可动(调用契约)
|
||||
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 直引的父槽
|
||||
let mut escaped_colors: Vec<u32> = Vec::new();
|
||||
collect_escaped(&f.nested, &mut pre_colors, &mut escaped_colors);
|
||||
// 对称缺口:子模块自身引用的父槽也必须预着色恒等。父侧 collect_escaped 只保证父不
|
||||
// 移动槽;但子模块 alloc 时,它引用父槽的 LOAD_VAR.a / STORE_VAR.rd 会被当作子模块
|
||||
// 自己的 vreg 参与染色而移走 → 子模块读错物理槽。分界线 = param_layout.base
|
||||
// (emit 的 inherited_reg_start 继承机制:子模块 vreg ≥ base,父槽引用 < base)。
|
||||
collect_own_escaped(f, &mut pre_colors, &mut escaped_colors);
|
||||
|
||||
// ── 可分配色集 ──
|
||||
// 窗口只为参数连续性 MOV 桥预留;spread 调用实参经 ext 逐个读寄存器,无连续性要求。
|
||||
let max_nargs = f
|
||||
.insts
|
||||
@@ -129,6 +109,68 @@ pub(super) fn build(
|
||||
.unwrap_or(0);
|
||||
let arg_window_base = 254u32.saturating_sub(max_nargs);
|
||||
|
||||
// ── 预着色 ──
|
||||
let mut pre_colors: BTreeMap<u32, u32> = BTreeMap::new();
|
||||
// 当前函数读取的父槽已经是父函数分配后的物理号,必须恒等保护。
|
||||
let mut escaped_colors: Vec<u32> = Vec::new();
|
||||
collect_own_escaped(f, &mut pre_colors, &mut escaped_colors);
|
||||
|
||||
// 被子函数捕获的当前函数槽需要固定颜色,但高虚拟槽不能恒等映射到 254+。
|
||||
// 父函数完成分配后,alloc 会把选定物理色同步到整个 nested 树的父槽引用。
|
||||
let mut nested_escaped = Vec::new();
|
||||
collect_escaped(&f.nested, &mut nested_escaped);
|
||||
let param_end = f.param_layout.base.saturating_add(f.param_layout.count);
|
||||
for vreg in nested_escaped {
|
||||
if !real.contains(&vreg) || (f.param_layout.base..param_end).contains(&vreg) {
|
||||
continue;
|
||||
}
|
||||
let identity_available =
|
||||
vreg >= 1 && vreg < arg_window_base && !pre_colors.values().any(|color| *color == vreg);
|
||||
let physical = if identity_available {
|
||||
vreg
|
||||
} else {
|
||||
(1..arg_window_base)
|
||||
.find(|color| !pre_colors.values().any(|existing| existing == color))
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "escaped 槽无自由色(vreg={vreg})");
|
||||
1
|
||||
})
|
||||
};
|
||||
pre_colors.insert(vreg, physical);
|
||||
escaped_colors.push(physical);
|
||||
}
|
||||
escaped_colors.sort_unstable();
|
||||
escaped_colors.dedup();
|
||||
|
||||
// 参数段必须连续。继承父上下文后虚拟段可能高于 253,此时选择不与 escaped 槽
|
||||
// 冲突的低位连续物理段;finish 按首参数颜色同步回写 param_layout.base。
|
||||
let pl = f.param_layout;
|
||||
if pl.count > 0 {
|
||||
let identity_end = pl.base.saturating_add(pl.count);
|
||||
let identity_available = pl.base >= 1
|
||||
&& identity_end <= arg_window_base
|
||||
&& (pl.base..identity_end).all(|color| !escaped_colors.contains(&color));
|
||||
let physical_base = if identity_available {
|
||||
pl.base
|
||||
} else {
|
||||
(1..=arg_window_base.saturating_sub(pl.count))
|
||||
.find(|base| {
|
||||
(*base..*base + pl.count).all(|color| {
|
||||
!pre_colors.values().any(|existing| *existing == color) && !escaped_colors.contains(&color)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
debug_assert!(false, "参数段无连续自由色(base={}, count={})", pl.base, pl.count);
|
||||
1
|
||||
})
|
||||
};
|
||||
for i in 0..pl.count {
|
||||
let vreg = pl.base + i;
|
||||
real.insert(vreg);
|
||||
pre_colors.insert(vreg, physical_base + i);
|
||||
}
|
||||
}
|
||||
|
||||
// ── builtin 槽 ──
|
||||
// VM 帧推入时写 regs[slot]=全局值(无指令 def 却活到入口),物理号须恒定且排除出
|
||||
// 可分配集,否则 prologue 死定义临时(如解构 FOR_OF_DONE 结果)与它同色,在
|
||||
@@ -166,7 +208,7 @@ pub(super) fn build(
|
||||
|
||||
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 {
|
||||
if pre_colors.values().any(|color| *color == c) || escaped_colors.contains(&c) || c >= arg_window_base {
|
||||
continue;
|
||||
}
|
||||
allocatable.push(c);
|
||||
@@ -249,9 +291,8 @@ pub(super) fn build(
|
||||
}
|
||||
}
|
||||
|
||||
/// 递归收集 nested 树中变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽),
|
||||
/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除)。
|
||||
fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, out: &mut Vec<u32>) {
|
||||
/// 递归收集 nested 树中的父槽虚拟号(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)。
|
||||
fn collect_escaped(nested: &[IRFunction], out: &mut Vec<u32>) {
|
||||
for sub in nested {
|
||||
for inst in &sub.insts {
|
||||
let slot = match inst.op {
|
||||
@@ -260,13 +301,12 @@ fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, o
|
||||
_ => 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);
|
||||
collect_escaped(&sub.nested, out);
|
||||
}
|
||||
out.sort_unstable();
|
||||
}
|
||||
@@ -379,6 +419,23 @@ mod tests {
|
||||
assert!(!g.allocatable.contains(&3), "escaped 色排除");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn high_escaped_slot_uses_encodable_color() {
|
||||
let mut f = empty_function();
|
||||
f.insts.push(Inst::load_const(Operand::Reg(254), 0));
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
|
||||
let mut sub = empty_function();
|
||||
sub.param_layout = oxide_ir::ParamLayout { base: 300, count: 0 };
|
||||
sub.insts
|
||||
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(301), Operand::Reg(254), Operand::None));
|
||||
f.nested.push(sub);
|
||||
let g = build_graph(&f);
|
||||
let color = g.nodes[&254].pre_color.expect("escaped 槽应预着色");
|
||||
assert!((1..=253).contains(&color));
|
||||
assert_ne!(color, 254);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_colors_excluded() {
|
||||
let mut f = empty_function();
|
||||
|
||||
@@ -54,6 +54,7 @@ pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> {
|
||||
regalloc_debug!("alloc: {} vregs -> {} phys", map.map.len(), map.phys_peak);
|
||||
rewrite::run(f, &map);
|
||||
finish::run(f, &map);
|
||||
remap_nested_parent_slots(&mut f.nested, &map);
|
||||
for child in &mut f.nested {
|
||||
let cfg = oxide_cfg::build_cfg(child);
|
||||
let l = oxide_liveness::liveness(child, &cfg);
|
||||
@@ -62,6 +63,27 @@ pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remap_nested_parent_slots(nested: &mut [IRFunction], map: &AllocMap) {
|
||||
for child in nested {
|
||||
let inherited_limit = child.param_layout.base;
|
||||
for inst in &mut child.insts {
|
||||
let slot = match inst.op {
|
||||
oxide_bytecode::opcode::OpCode::LOAD_VAR => &mut inst.a,
|
||||
oxide_bytecode::opcode::OpCode::STORE_VAR => &mut inst.rd,
|
||||
_ => continue,
|
||||
};
|
||||
if let oxide_ir::operand::Operand::Reg(vreg) = slot {
|
||||
if *vreg < inherited_limit {
|
||||
if let Some(Alloc::Phys(physical)) = map.map.get(vreg) {
|
||||
*vreg = *physical;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
remap_nested_parent_slots(&mut child.nested, map);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -95,6 +117,67 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_remaps_high_parameter_segment() {
|
||||
let mut f = IRFunction::new();
|
||||
f.param_layout = oxide_ir::ParamLayout { base: 300, count: 1 };
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(300), Operand::None, Operand::None));
|
||||
let cfg = oxide_cfg::build_cfg(&f);
|
||||
let live = oxide_liveness::liveness(&f, &cfg);
|
||||
alloc(&mut f, &live).unwrap();
|
||||
assert_eq!(f.param_layout, oxide_ir::ParamLayout { base: 1, count: 1 });
|
||||
assert!(matches!(f.insts[0].rd, Operand::Reg(1)));
|
||||
assert_eq!(f.n_registers, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_propagates_moved_parameter_to_nested_slot_reads() {
|
||||
let mut f = IRFunction::new();
|
||||
f.param_layout = oxide_ir::ParamLayout { base: 300, count: 1 };
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(300), Operand::None, Operand::None));
|
||||
let mut child = IRFunction::new();
|
||||
child.param_layout = oxide_ir::ParamLayout { base: 301, count: 0 };
|
||||
child
|
||||
.insts
|
||||
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(302), Operand::Reg(300), Operand::None));
|
||||
child
|
||||
.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(302), Operand::None, Operand::None));
|
||||
f.nested.push(child);
|
||||
let cfg = oxide_cfg::build_cfg(&f);
|
||||
let live = oxide_liveness::liveness(&f, &cfg);
|
||||
alloc(&mut f, &live).unwrap();
|
||||
assert!(matches!(f.nested[0].insts[0].a, Operand::Reg(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_propagates_high_escaped_slot_to_nested_reads() {
|
||||
let mut f = IRFunction::new();
|
||||
f.insts.push(Inst::load_const(Operand::Reg(254), 0));
|
||||
f.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
|
||||
let mut child = IRFunction::new();
|
||||
child.param_layout = oxide_ir::ParamLayout { base: 300, count: 0 };
|
||||
child
|
||||
.insts
|
||||
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(301), Operand::Reg(254), Operand::None));
|
||||
child
|
||||
.insts
|
||||
.push(Inst::new(OpCode::RETURN, Operand::Reg(301), Operand::None, Operand::None));
|
||||
f.nested.push(child);
|
||||
let cfg = oxide_cfg::build_cfg(&f);
|
||||
let live = oxide_liveness::liveness(&f, &cfg);
|
||||
alloc(&mut f, &live).unwrap();
|
||||
let physical = match f.insts[0].rd {
|
||||
Operand::Reg(reg) => reg,
|
||||
_ => panic!("LOAD_CONST 目标应为寄存器"),
|
||||
};
|
||||
assert!((1..=253).contains(&physical));
|
||||
assert!(matches!(f.nested[0].insts[0].a, Operand::Reg(reg) if reg == physical));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alloc_err_propagates() {
|
||||
// 255 参数 CALL:参数窗口吞并全部可分配色 → k=0 → 无可行染色 → Err
|
||||
|
||||
@@ -46,7 +46,7 @@ fn smoke(src: &str) {
|
||||
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);
|
||||
assert!(m.phys_peak <= 254, "phys_peak {} 超 254", m.phys_peak);
|
||||
}
|
||||
|
||||
fn is_precolored(ir: &oxide_ir::IRFunction, c: u32) -> bool {
|
||||
|
||||
Reference in New Issue
Block a user