feat: 指令改写 + 元数据回写 + alloc 入口,RegAlloc on/off 运行时等价(D-16/D-20)

This commit is contained in:
2026-08-06 17:44:31 +08:00
parent 0b0d36fa85
commit 08f061dfd3
6 changed files with 974 additions and 5 deletions
+122
View File
@@ -0,0 +1,122 @@
//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures(D-10/D-03)。
//!
//! - n_registers = map.phys_peak(≤253;不含参数窗口——窗口是调用点暂存槽,
//! 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 预着色恒等)
use crate::alloc_map::{Alloc, AllocMap};
use crate::rewrite::spilled_builtin_bindings;
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");
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 v = *vreg;
match map.map.get(&v) {
Some(Alloc::Phys(p)) => {
*vreg = *p;
}
Some(Alloc::Spill(_)) => {
if let Some((_, r, _)) = spilled.iter().find(|(n, _, _)| n == name) {
debug_assert!(*r <= 253);
*vreg = *r;
} else {
debug_assert!(false, "spilled builtin {name} 无对应入口 SPILL");
}
}
None => {
debug_assert!(false, "builtin vreg {v} 不在 AllocMap");
}
}
}
// (c) param_layout 不动(参数段 pre-colored 恒等;窗口与参数段重叠仅 05-07 标注的罕见情形)
let pl = f.param_layout;
debug_assert!(pl.base + pl.count <= map.arg_window_base || map.arg_window_base == 254);
// (d) upvalue_captures 不动。enclosing_reg 是编译期产物(MAKE_CELL 定位用),VM 运行时
// 不读(CREATE_CLOSURE 走 cell_idx,cell 捕获后值在 cell 中)——无需跨函数同步。
// 直接槽引用(class field 计算键等)的 escaped vreg 已由 05-07 预着色恒等保留。
// 注:cell 捕获变量的父 vreg 可被 RegAlloc 移动(MAKE_CELL 后值入 cell,register 无关),
// 此处不做恒等断言。
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_bytecode::opcode::OpCode;
#[test]
fn n_registers_equals_phys_peak() {
let mut f = IRFunction::new();
let mut map = AllocMap::new();
map.phys_peak = 42;
run(&mut f, &map);
assert_eq!(f.n_registers, 42);
}
#[test]
fn builtin_moved_rewritten() {
let mut f = IRFunction::new();
f.builtin_reg_map = vec![("Math".to_string(), 5)];
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
let mut map = AllocMap::new();
map.map.insert(5, Alloc::Phys(9));
map.map.insert(1, Alloc::Phys(1));
run(&mut f, &map);
assert_eq!(f.builtin_reg_map[0].1, 9, "builtin 移动后回写新 phys");
}
#[test]
fn builtin_spilled_bound_to_free_color() {
let mut f = IRFunction::new();
f.builtin_reg_map = vec![("Math".to_string(), 5)];
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
let mut map = AllocMap::new();
map.map.insert(5, Alloc::Spill(0));
map.map.insert(1, Alloc::Phys(1));
map.map.insert(20, Alloc::Phys(7));
map.spills.push(crate::alloc_map::SpillPlan {
vreg: 5,
slot: 0,
defs: vec![],
uses: vec![(0, 20)],
});
let binds = spilled_builtin_bindings(&f, &map);
run(&mut f, &map);
assert_eq!(f.builtin_reg_map[0].0, "Math");
assert_eq!(f.builtin_reg_map[0].1, binds[0].1, "回写 R 与入口 SPILL 同源");
}
#[test]
fn param_layout_untouched() {
let mut f = IRFunction::new();
f.param_layout = oxide_ir::ParamLayout { base: 1, count: 2 };
let map = AllocMap::new();
run(&mut f, &map);
assert_eq!(f.param_layout.base, 1);
assert_eq!(f.param_layout.count, 2);
}
#[test]
fn enclosing_reg_unchanged() {
let mut f = IRFunction::new();
f.upvalue_captures.push(oxide_bytecode::module::UpvalueCapture {
name: "x".to_string(),
enclosing_reg: 7,
cell_idx: 0,
});
let map = AllocMap::new();
run(&mut f, &map);
assert_eq!(f.upvalue_captures[0].enclosing_reg, 7);
}
}
+104 -4
View File
@@ -1,13 +1,16 @@
//! RegAlloc 改写 pass(前半):干涉图染色决策,产出 AllocMap。
//! RegAlloc 改写 pass:干涉图染色决策(前半)+ 指令改写/元数据回写(后半)。
//!
//! 消费 `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。
//! `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 入口(D-16),`color()` 只产决策。
mod alloc_map;
mod color;
mod finish;
mod graph;
mod rewrite;
pub use alloc_map::{Alloc, AllocMap, FreshKind, FreshVreg, SpillPlan};
@@ -24,3 +27,100 @@ pub fn color(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
let graph = graph::build(f, live, &std::collections::BTreeSet::new(), &[]);
color::run(f, live, &graph)
}
/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`(D-16)。
///
/// 流程:LiveInfo 维度守卫(inst 数不符 → 内部重跑 build_cfg + liveness,D-17 修正:
/// 精确 DCE 删指令后 LiveInfo 过期,重跑纯函数一次调用,不重复建分析引擎)→
/// `color()`(Err 原样传播)→ `rewrite::run` → `finish::run` → nested 递归自顶向下
/// (每子函数独立 vreg 空间独立 alloc,D-03/D-10)。
pub fn alloc(f: &mut IRFunction, live: &LiveInfo) -> Result<(), String> {
if f.insts.is_empty() && f.nested.is_empty() {
return Ok(());
}
// LiveInfo 维度守卫:删改后的过期 LiveInfo 索引错位会误删活指令
let live = if live.inst_live_before.len() == f.insts.len() && !f.insts.is_empty() {
live.clone()
} else {
let cfg = oxide_cfg::build_cfg(f);
oxide_liveness::liveness(f, &cfg)
};
let map = color(f, &live)?;
rewrite::run(f, &map);
finish::run(f, &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(())
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
#[test]
fn alloc_empty_ok() {
let mut f = IRFunction::new();
assert!(alloc(&mut f, &LiveInfo::new()).is_ok());
}
#[test]
fn alloc_stale_live_reruns() {
let mut f = IRFunction::new();
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));
// 空 LiveInfo(维度不符)→ 内部重跑
let stale = LiveInfo::new();
assert!(alloc(&mut f, &stale).is_ok());
// 重写后全部槽 ≤253
for inst in &f.insts {
for o in [&inst.rd, &inst.a, &inst.b] {
if let Operand::Reg(r) = o {
assert!(*r <= 253, "重写后槽 {r} 超 253");
}
}
}
}
#[test]
fn alloc_err_propagates() {
// 255 参数 CALL:窗口吞并 → k=0 → 无可行染色 → Err
let mut f = IRFunction::new();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3000), 255));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
let err = alloc(&mut f, &live).unwrap_err();
assert!(err.contains("too many registers"), "Err: {err}");
}
#[test]
fn alloc_recurses_nested() {
let mut f = IRFunction::new();
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None));
let mut sub = IRFunction::new();
sub.insts.push(Inst::load_const(Operand::Reg(3), 0));
sub.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(3), Operand::None, Operand::None));
f.nested.push(sub);
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
alloc(&mut f, &live).unwrap();
// 父与子函数全部槽 ≤253
for inst in &f.insts {
for o in [&inst.rd, &inst.a, &inst.b] {
if let Operand::Reg(r) = o {
assert!(*r <= 253);
}
}
}
assert!(!f.nested[0].insts.is_empty(), "子函数也被重写");
assert!(f.nested[0].n_registers <= 253, "子函数 n_registers 已回写");
}
}
+519
View File
@@ -0,0 +1,519 @@
//! 指令改写:消费 AllocMap 就地重写 IR(D-05/D-07/D-20/RESEARCH 发现 4)。
//!
//! - 槽位 vreg→phys 重写:仅 `Operand::Reg`;None/This/NewTarget/Const/Imm/Label 零改动
//! (None→0、This→254、NewTarget→255 由 lower 映射,D-11)
//! - spill 落点(D-05 区间拆分 v1):def 点后 SPILL、use 点前 UNSPILL;RMW(COMPOUND 系
//! rd 读旧值)UNSPILL 进 def-fresh 寄存器、use-fresh 弃用
//! - 调用点参数连续性(RESEARCH 发现 4):参数分散则 MOV 搬进 ARG_WINDOW
//! - spilled builtin 入口 SPILL(builtin 无指令 def,VM 入口隐式绑定)
//! - label_pos mark-sweep 重建:label 落 group 起点(跳转先执行 before-插入)
//!
//! 确定性:全 BTreeMap/Vec 排序,禁 HashMap(B010)。
use std::collections::BTreeMap;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_ir::IRFunction;
use crate::alloc_map::{Alloc, AllocMap};
/// 就地改写 f.insts + f.label_pos。
pub(super) fn run(f: &mut IRFunction, map: &AllocMap) {
// ── 1. spill 查询表 ──
// defs_at: (inst, vreg) → def-fresh 色;uses_at: (inst, vreg) → use-fresh 色;spill_slot: vreg → slot
let mut defs_at: BTreeMap<(usize, u32), u32> = BTreeMap::new();
let mut uses_at: BTreeMap<(usize, u32), u32> = BTreeMap::new();
let mut spill_slot: BTreeMap<u32, u16> = BTreeMap::new();
for sp in &map.spills {
spill_slot.insert(sp.vreg, sp.slot);
for &(inst, fresh_id) in &sp.defs {
if let Some(Alloc::Phys(c)) = map.map.get(&fresh_id) {
defs_at.insert((inst, sp.vreg), *c);
}
}
for &(inst, fresh_id) in &sp.uses {
if let Some(Alloc::Phys(c)) = map.map.get(&fresh_id) {
uses_at.insert((inst, sp.vreg), *c);
}
}
}
// ── 2. spilled builtin 入口 SPILL ──
let spilled_builtins = spilled_builtin_bindings(f, map);
// ── 3. 单遍构建新 insts + old_to_new ──
let mut new_insts: Vec<Inst> = Vec::new();
let mut old_to_new: Vec<usize> = vec![0; f.insts.len()];
// 入口 SPILL(先于所有 before-插入与 inst 0)
for (_, r, slot) in &spilled_builtins {
new_insts.push(Inst::inst_spill(Operand::Reg(*r), *slot));
}
for (i, inst) in f.insts.iter().enumerate() {
old_to_new[i] = new_insts.len(); // group 起点(before-插入首条位置)
// RMW 判定:inst 同时 def 且 use 同一 spilled vreg
let rmw_v = inst
.def_reg()
.filter(|d| uses_at.contains_key(&(i, *d)) && spill_slot.contains_key(d));
// before-插入:非 RMW 的 use 点 UNSPILL
let mut use_unspills: Vec<(u32, u32)> = Vec::new(); // (vreg, fresh 色)
for (&(inst_at, vreg), &color) in &uses_at {
if inst_at == i && rmw_v != Some(vreg) {
use_unspills.push((vreg, color));
}
}
use_unspills.sort_unstable();
// 重写后该 inst 各槽的 spill 色映射(vreg → fresh 色)
let mut slot_color: BTreeMap<u32, u32> = BTreeMap::new();
if let Some(v) = rmw_v {
// RMW:UNSPILL 进 def-fresh 寄存器(rd 读旧值写新值),use-fresh 弃用
if let Some(&c) = defs_at.get(&(i, v)) {
slot_color.insert(v, c);
}
}
for &(vreg, color) in &use_unspills {
slot_color.insert(vreg, color);
}
// 纯 def 点:rd → def-fresh 色(值写入后立即 SPILL)
if rmw_v.is_none() {
if let Some(d) = inst.def_reg() {
if let Some(&c) = defs_at.get(&(i, d)) {
slot_color.insert(d, c);
}
}
}
// 重写 inst 本体(含调用点参数连续性 MOV)
let (rewritten, arg_movs) = rewrite_inst(f, inst, map, &slot_color, i);
// 发射顺序:UNSPILLs(按 vreg 升序)→ RMW UNSPILL → 连续性 MOVs → 指令 → SPILLs
for &(vreg, color) in &use_unspills {
let slot = spill_slot[&vreg];
new_insts.push(Inst::inst_unspill(Operand::Reg(color), slot));
}
if let Some(v) = rmw_v {
if let Some(&c) = defs_at.get(&(i, v)) {
let slot = spill_slot[&v];
new_insts.push(Inst::inst_unspill(Operand::Reg(c), slot));
}
}
for (dst, src) in arg_movs {
new_insts.push(Inst::inst_mov(Operand::Reg(dst), Operand::Reg(src)));
}
new_insts.push(rewritten);
// after-插入:def 点 SPILL(RMW 与纯 def 都写 rd;terminator 跳过——def 结果必死)
if let Some(d) = inst.def_reg() {
if spill_slot.contains_key(&d) {
if let Some(&c) = defs_at.get(&(i, d)) {
if !is_terminator(inst.op) {
let slot = spill_slot[&d];
new_insts.push(Inst::inst_spill(Operand::Reg(c), slot));
}
}
}
}
}
f.insts = new_insts;
// ── 4. label_pos 重映射(label 落 group 起点)──
for pos in f.label_pos.iter_mut() {
if let Some(old) = *pos {
*pos = old_to_new.get(old).copied();
}
}
debug_assert!(f.label_pos.iter().all(|p| p.map_or(true, |np| np <= f.insts.len())));
}
/// 重写单条指令:槽位映射 + 调用点参数连续性 MOV 补位。返回 (重写后指令, 参数 MOV 列表)。
fn rewrite_inst(
f: &IRFunction, inst: &Inst, map: &AllocMap, slot_color: &BTreeMap<u32, u32>, i: usize,
) -> (Inst, Vec<(u32, u32)>) {
let nargs = inst.ext.first().copied().unwrap_or(0);
let first_arg = match inst.op {
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION => Some(inst.b),
OpCode::SUPER_CALL => Some(inst.a),
_ => None,
};
// 调用点参数 phys 映射(arg k → 物理号)
let mut arg_movs: Vec<(u32, u32)> = Vec::new();
let mut arg_base: Option<u32> = None;
if let Some(Operand::Reg(fr)) = first_arg {
if nargs > 0 {
let mut args_phys: Vec<u32> = Vec::with_capacity(nargs as usize);
for k in 0..nargs {
let v = fr + k;
let p = if let Some(&c) = slot_color.get(&v) {
c
} else {
match map.map.get(&v) {
Some(Alloc::Phys(p)) => *p,
_ => v, // 非真实 vreg / 防御:原样
}
};
args_phys.push(p);
}
// 连续性检查:phys[k+1] == phys[k] + 1
let contiguous = args_phys.windows(2).all(|w| w[1] == w[0] + 1);
if !contiguous {
for (k, &p) in args_phys.iter().enumerate() {
arg_movs.push((map.arg_window_base + k as u32, p));
}
arg_base = Some(map.arg_window_base);
}
}
}
let _ = i;
// 槽位重写
let rewrite = |o: Operand| -> Operand {
match o {
Operand::Reg(r) => {
if let Some(&c) = slot_color.get(&r) {
Operand::Reg(c)
} else {
match map.map.get(&r) {
Some(Alloc::Phys(p)) => Operand::Reg(*p),
Some(Alloc::Spill(_)) => {
// spilled vreg 出现在非 def/use 点(不应发生,防御保留原样)
debug_assert!(false, "spilled vreg {r} 出现在无插入点");
Operand::Reg(r)
}
None => Operand::Reg(r),
}
}
}
other => other, // None/This/NewTarget/Const/Imm/Label 零改动(D-11)
}
};
let mut rd = rewrite(inst.rd);
let mut a = rewrite(inst.a);
let mut b = rewrite(inst.b);
// 调用点首参槽改指 arg_window_base(桥接后)
if let Some(ab) = arg_base {
match inst.op {
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION => {
if matches!(first_arg, Some(Operand::Reg(_))) {
b = Operand::Reg(ab);
}
}
OpCode::SUPER_CALL => {
if matches!(first_arg, Some(Operand::Reg(_))) {
a = Operand::Reg(ab);
}
}
_ => {}
}
}
let _ = &mut rd;
let _ = &mut a;
let _ = &mut b;
let _ = f;
(Inst { op: inst.op, rd, a, b, ext: inst.ext.clone() }, arg_movs)
}
/// terminator 判定:跳转族 + RETURN/HALT/THROW(def 结果必死,SPILL 跳过)。
fn is_terminator(op: OpCode) -> bool {
matches!(
op,
OpCode::JMP
| OpCode::JMP_IF_TRUE
| OpCode::JMP_IF_FALSE
| OpCode::JMP_IF_NULLISH
| OpCode::RETURN
| OpCode::HALT
| OpCode::THROW
)
}
/// spilled builtin:对 builtin_reg_map 中 map[v]=Spill 的项分配确定性自由色 R。
/// 返回 (name, R, slot)。rewrite(入口 SPILL)与 finish(回写)共享唯一来源。
pub(super) fn spilled_builtin_bindings(f: &IRFunction, map: &AllocMap) -> Vec<(String, u32, u16)> {
// 候选色排除集
let mut excluded: Vec<u32> = Vec::new();
for (v, a) in &map.map {
if let Alloc::Phys(c) = a {
excluded.push(*c);
}
let _ = v;
}
let pl = f.param_layout;
for c in pl.base..pl.base + pl.count {
excluded.push(c);
}
// escaped 色(递归 nested 的 LOAD_VAR.a / STORE_VAR.rd)
collect_escaped(&f.nested, &mut excluded);
// 窗口
for c in map.arg_window_base..=253 {
excluded.push(c);
}
excluded.sort_unstable();
excluded.dedup();
let excluded_set: std::collections::BTreeSet<u32> = excluded.iter().copied().collect();
let mut result: Vec<(String, u32, u16)> = Vec::new();
let mut used: Vec<u32> = Vec::new();
// 按 name 升序确定性
let mut candidates: Vec<&(String, u32)> = f
.builtin_reg_map
.iter()
.filter(|(_, v)| matches!(map.map.get(v), Some(Alloc::Spill(_))))
.collect();
candidates.sort_by(|a, b| a.0.cmp(&b.0));
for (name, vreg) in candidates {
let slot = match map.map.get(vreg) {
Some(Alloc::Spill(s)) => *s,
_ => continue,
};
// 自由色 = 1..=253 中最小未排除未使用
let r = (1u32..=253)
.find(|c| !excluded_set.contains(c) && !used.contains(c))
.unwrap_or_else(|| {
debug_assert!(false, "spilled builtin 无自由色");
1
});
used.push(r);
result.push((name.clone(), r, slot));
}
result
}
/// 递归收集 nested 树中变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽)→ escaped 色。
fn collect_escaped(nested: &[IRFunction], 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 {
out.push(r);
}
}
collect_escaped(&sub.nested, out);
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::operand::Operand;
fn empty_function() -> IRFunction {
IRFunction::new()
}
fn rewrite_with_map(insts: Vec<Inst>, map: AllocMap) -> IRFunction {
let mut f = empty_function();
f.insts = insts;
run(&mut f, &map);
f
}
#[test]
fn semantic_operands_untouched() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::This, Operand::NewTarget, Operand::None));
f.insts.push(Inst::load_const(Operand::Reg(1), 3));
let map = AllocMap::new();
run(&mut f, &map);
assert_eq!(f.insts[0].rd, Operand::This);
assert_eq!(f.insts[0].a, Operand::NewTarget);
assert_eq!(f.insts[0].b, Operand::None);
}
#[test]
fn slots_rewritten_per_map() {
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 mut map = AllocMap::new();
map.map.insert(1, Alloc::Phys(10));
map.map.insert(2, Alloc::Phys(11));
map.map.insert(3, Alloc::Phys(12));
run(&mut f, &map);
assert_eq!(f.insts[0].rd, Operand::Reg(12));
assert_eq!(f.insts[0].a, Operand::Reg(10));
assert_eq!(f.insts[0].b, Operand::Reg(11));
assert_eq!(f.insts[1].rd, Operand::Reg(12));
}
#[test]
fn scattered_args_insert_mov_bridge() {
let insts = vec![Inst::call(Operand::Reg(2), Operand::Reg(3), Operand::Reg(4), 3)];
let mut map = AllocMap::new();
map.arg_window_base = 251;
map.map.insert(2, Alloc::Phys(2));
map.map.insert(3, Alloc::Phys(3));
map.map.insert(4, Alloc::Phys(7));
map.map.insert(5, Alloc::Phys(9));
map.map.insert(6, Alloc::Phys(8));
let f = rewrite_with_map(insts, map);
// 期望:3 条 MOV(251←7, 252←9, 253←8)+ 调用点 b 槽 → Reg(251)
let movs: Vec<&Inst> = f.insts.iter().filter(|i| i.op == OpCode::MOV).collect();
assert_eq!(movs.len(), 3, "分散参数应插 3 条 MOV");
let call = f.insts.iter().find(|i| i.op == OpCode::CALL).unwrap();
assert_eq!(call.b, Operand::Reg(251), "调用点 b 槽应改指窗口基址");
// MOV 源分别为 7/9/8,目标 251/252/253
let mv: Vec<(u32, u32)> = movs
.iter()
.map(|i| match (i.rd, i.a) {
(Operand::Reg(d), Operand::Reg(s)) => (d, s),
_ => panic!("MOV 槽位异常"),
})
.collect();
assert!(mv.contains(&(251, 7)) && mv.contains(&(252, 9)) && mv.contains(&(253, 8)));
}
#[test]
fn spill_points_inserted_at_defs_and_uses() {
let insts = 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),
];
let mut map = AllocMap::new();
map.map.insert(1, Alloc::Spill(0));
map.map.insert(2, Alloc::Phys(2));
map.map.insert(3, Alloc::Phys(3));
map.map.insert(10, Alloc::Phys(5)); // def-fresh
map.map.insert(11, Alloc::Phys(6)); // use-fresh
map.spills.push(crate::alloc_map::SpillPlan {
vreg: 1,
slot: 0,
defs: vec![(0, 10)],
uses: vec![(2, 11)],
});
let f = rewrite_with_map(insts, map);
// inst 0 后 SPILL(Reg(5), 0);inst 2 前 UNSPILL(Reg(6), 0);inst 2 的 a 槽(r1) → Reg(6)
let ops: Vec<OpCode> = f.insts.iter().map(|i| i.op).collect();
// 找 SPILL 与 UNSPILL 位置
let spill_pos = ops.iter().position(|&o| o == OpCode::SPILL).unwrap();
let unspill_pos = ops.iter().position(|&o| o == OpCode::UNSPILL).unwrap();
assert_eq!(f.insts[spill_pos].rd, Operand::Reg(5));
assert_eq!(f.insts[unspill_pos].rd, Operand::Reg(6));
// 指令序:UNSPILL 在 ADD 前
let add_pos = ops.iter().position(|&o| o == OpCode::ADD).unwrap();
assert!(unspill_pos < add_pos, "UNSPILL 应在 ADD 前");
let add = &f.insts[add_pos];
assert_eq!(add.a, Operand::Reg(6), "ADD 的 a 槽(r1) → use-fresh 色 6");
}
#[test]
fn rmw_spill_reloads_def_fresh() {
// COMPOUND_ADD RMW(r1 读旧值写新值)→ def-fresh 5;RETURN 读新值 → use-fresh 8
let insts = vec![
Inst::new(OpCode::COMPOUND_ADD, Operand::Reg(1), Operand::Reg(2), Operand::None),
Inst::new(OpCode::RETURN, Operand::Reg(1), Operand::None, Operand::None),
];
let mut map = AllocMap::new();
map.map.insert(1, Alloc::Spill(0));
map.map.insert(2, Alloc::Phys(2));
map.map.insert(5, Alloc::Phys(5)); // def-fresh
map.map.insert(6, Alloc::Phys(6)); // use-fresh@0(弃用)
map.map.insert(7, Alloc::Phys(8)); // use-fresh@1(RETURN)
map.spills.push(crate::alloc_map::SpillPlan {
vreg: 1,
slot: 0,
defs: vec![(0, 5)],
uses: vec![(0, 6), (1, 7)],
});
let f = rewrite_with_map(insts, map);
let ops: Vec<OpCode> = f.insts.iter().map(|i| i.op).collect();
let unspill_pos = ops.iter().position(|&o| o == OpCode::UNSPILL).unwrap();
let compound_pos = ops.iter().position(|&o| o == OpCode::COMPOUND_ADD).unwrap();
let spill_pos = ops.iter().position(|&o| o == OpCode::SPILL).unwrap();
let ret_pos = ops.iter().position(|&o| o == OpCode::RETURN).unwrap();
// UNSPILL(def-fresh 5) → COMPOUND(rd=5) → SPILL(5);RETURN 前 UNSPILL(use-fresh 8)
assert_eq!(f.insts[unspill_pos].rd, Operand::Reg(5));
assert!(unspill_pos < compound_pos && compound_pos < spill_pos);
assert_eq!(f.insts[compound_pos].rd, Operand::Reg(5), "COMPOUND rd → def-fresh 色");
assert_eq!(f.insts[spill_pos].rd, Operand::Reg(5));
assert!(spill_pos < ret_pos, "SPILL 在 RETURN 前");
// RETURN rd → use-fresh 8,其前有 UNSPILL(8)
assert_eq!(f.insts[ret_pos].rd, Operand::Reg(8));
let unspill8 = f.insts.iter().position(|i| i.op == OpCode::UNSPILL && i.rd == Operand::Reg(8)).unwrap();
assert!(unspill8 < ret_pos, "RETURN 前 UNSPILL use-fresh");
// use-fresh 6(RMW 点弃用)不得产生 UNSPILL
assert!(
f.insts.iter().all(|i| !(i.op == OpCode::UNSPILL && i.rd == Operand::Reg(6))),
"RMW use-fresh 弃用"
);
}
#[test]
fn label_pos_lands_on_group_start() {
// 1: JMP_IF_FALSE(Reg(9), Label(0)) → 跳转目标 inst 3 是 spill use 点(有 before-UNSPILL)
let insts = vec![
Inst::load_const(Operand::Reg(2), 0),
Inst::jmp_if_false(9, 0),
Inst::load_const(Operand::Reg(3), 0),
Inst::new(OpCode::ADD, Operand::Reg(4), Operand::Reg(1), Operand::Reg(2)),
Inst::new(OpCode::RETURN, Operand::Reg(4), Operand::None, Operand::None),
];
let mut f = empty_function();
f.insts = insts;
f.label_pos = vec![Some(3)];
f.label_count = 1;
let mut map = AllocMap::new();
map.map.insert(1, Alloc::Spill(0));
map.map.insert(2, Alloc::Phys(2));
map.map.insert(3, Alloc::Phys(3));
map.map.insert(4, Alloc::Phys(4));
map.map.insert(9, Alloc::Phys(9));
map.map.insert(11, Alloc::Phys(6)); // use-fresh for r1 at inst 3
map.spills.push(crate::alloc_map::SpillPlan {
vreg: 1,
slot: 0,
defs: vec![],
uses: vec![(3, 11)],
});
run(&mut f, &map);
// label 0 应指向 inst 3 的 group 起点(UNSPILL 位置)
let label_target = f.label_pos[0].expect("label 重映射");
let target_inst = &f.insts[label_target];
assert_eq!(target_inst.op, OpCode::UNSPILL, "label 落 group 起点(UNSPILL)");
// 跳转 offset 落在 UNSPILL 前 → lower 成功
let result = oxide_ir::lower::lower(&f);
assert!(result.is_ok(), "lower 应成功(label 解析到 group 起点)");
}
#[test]
fn spilled_builtin_entry_spill_inserted() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
f.builtin_reg_map = vec![("Math".to_string(), 10)];
let mut map = AllocMap::new();
map.map.insert(10, Alloc::Spill(0));
map.map.insert(5, Alloc::Phys(5));
map.map.insert(20, Alloc::Phys(7)); // use-fresh
map.spills.push(crate::alloc_map::SpillPlan {
vreg: 10,
slot: 0,
defs: vec![],
uses: vec![(0, 20)],
});
let binds = spilled_builtin_bindings(&f, &map);
assert_eq!(binds.len(), 1);
let (name, r, slot) = &binds[0];
assert_eq!(name, "Math");
assert_eq!(*slot, 0);
assert!(*r >= 1 && *r <= 253, "R 应在物理域");
assert_ne!(*r, 5, "R 不得与 Phys 色冲突");
run(&mut f, &map);
// 入口 SPILL 存在
let first = f.insts.first().expect("入口 SPILL");
assert_eq!(first.op, OpCode::SPILL);
assert_eq!(first.rd, Operand::Reg(*r));
assert_eq!(first.ext.as_slice(), &[0]);
}
}