Files
OxideJS-New/crates/oxide_regalloc/src/lib.rs
T

141 lines
5.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! RegAlloc 改写 pass:干涉图染色决策(前半)+ 指令改写/元数据回写(后半)。
//!
//! 消费 `oxide_liveness::LiveInfo`(inst_live_before 逐指令活集)与
//! `oxide_ir::IRFunction`(param_layout 参数段 + nested escaped 收集)。染色:干涉图
//! (graph.rs)+ Kemp 简化/贪心/spill 区间拆分(color.rs)→ AllocMap;改写:rewrite.rs
//! (指令槽 vreg→phys 重写 + spill 插入 + 参数连续性 MOV + label 重建)+ finish.rs
//! (元数据回写)。`alloc()` 是完整改写 pass 入口,`color()` 只产决策。
mod alloc_map;
mod call_window;
mod color;
mod finish;
mod graph;
mod regalloc_log;
mod rewrite;
pub use alloc_map::{Alloc, AllocMap, FreshKind, FreshVreg, SpillPlan};
pub use call_window::encode_call_window;
use std::borrow::Cow;
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
/// 染色:`&IRFunction + &LiveInfo → Result<AllocMap, String>`。纯函数,只读 IR。
/// 空 IRFunction 退化:Ok(AllocMap::new())。Err = 无可行染色(RangeError 路径,
/// 消息与 lower 逐字一致,编译接线时原样传播)。
pub fn color(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
if f.insts.is_empty() {
return Ok(AllocMap::new());
}
color::run(f, live)
}
/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`。
///
/// # 步骤
/// - LiveInfo 维度守卫:inst 数不符说明 LiveInfo 已过期(精确 DCE 删指令后下标位移),
/// 内部重跑 build_cfg + liveness 重建,不重复建分析引擎
/// - `color()` 产染色决策(Err 原样传播)
/// - `rewrite::run` 指令改写 → `finish::run` 元数据回写
/// - nested 递归自顶向下:每子函数独立 vreg 空间独立 alloc
///
/// # 注意事项
/// - 嵌套子函数不继承父 LiveInfo,各自重跑 build_cfg + liveness。
pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> {
if f.insts.is_empty() && f.nested.is_empty() {
return Ok(());
}
// LiveInfo 维度守卫:删改后的过期 LiveInfo 索引错位会误删活指令。
// 维度匹配时借用而非复制——LiveInfo 是稠密 bitset 矩阵(大函数可达数百 MB),
// 全量 clone 白白复制一次只读数据。
let live = if live.inst_live_before.len() == f.insts.len() && !f.insts.is_empty() {
Cow::Borrowed(live)
} else {
let cfg = oxide_cfg::build_cfg(f);
Cow::Owned(oxide_liveness::liveness(f, &cfg))
};
let map = color(f, &live)?;
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);
alloc(child, &l)?;
}
Ok(())
}
fn remap_nested_parent_slots(nested: &mut [IRFunction], map: &AllocMap) {
remap_nested_parent_slots_below(nested, map, u32::MAX);
}
fn remap_nested_parent_slots_below(nested: &mut [IRFunction], map: &AllocMap, ancestor_limit: u32) {
for child in nested {
let inherited_limit = child.param_layout.base.min(ancestor_limit);
for (_, slot) in &mut child.builtin_reg_map {
if *slot < inherited_limit {
if let Some(Alloc::Phys(physical)) = map.map.get(slot) {
*slot = *physical;
}
}
}
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;
}
}
}
}
// 子函数自身 vreg 从 param_layout.base 起分配;祖先 map 不得改写同号的后代局部槽。
remap_nested_parent_slots_below(&mut child.nested, map, inherited_limit);
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
#[test]
fn ancestor_remap_preserves_descendant_vreg_collision() {
let mut child = IRFunction::new();
child.param_layout = oxide_ir::ParamLayout { base: 200, count: 0 };
child.builtin_reg_map.push(("root".to_string(), 100));
let mut grandchild = IRFunction::new();
grandchild.param_layout = oxide_ir::ParamLayout { base: 400, count: 0 };
grandchild.builtin_reg_map.push(("root".to_string(), 100));
grandchild.builtin_reg_map.push(("child".to_string(), 300));
grandchild
.insts
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(401), Operand::Reg(100), Operand::None));
grandchild
.insts
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(402), Operand::Reg(300), Operand::None));
child.nested.push(grandchild);
let mut map = AllocMap::new();
map.map.insert(100, Alloc::Phys(7));
map.map.insert(300, Alloc::Phys(8));
remap_nested_parent_slots(std::slice::from_mut(&mut child), &map);
assert_eq!(child.builtin_reg_map, vec![("root".to_string(), 7)]);
assert_eq!(child.nested[0].builtin_reg_map, vec![("root".to_string(), 7), ("child".to_string(), 300)]);
assert_eq!(child.nested[0].insts[0].a, Operand::Reg(7));
assert_eq!(child.nested[0].insts[1].a, Operand::Reg(300));
}
}