docs: 补充注释与格式化

This commit is contained in:
爱特每羊队
2026-08-06 23:10:27 +08:00
parent 4e0fd762c2
commit 328b8a3ddc
156 changed files with 2393 additions and 1687 deletions
+6 -6
View File
@@ -1,8 +1,8 @@
//! AllocMap:寄存器染色决策输出视图(D-01:pass 输出,不住进 IR)。
//! AllocMap:寄存器染色决策输出视图(pass 输出,不住进 IR)。
//!
//! `AllocMap.map` 含全部真实 + fresh vreg 的 Phys/Spill 去向;`spills` 为
//! SPILL/UNSPILL 插入决策表(05-08 rewrite 消费);`phys_peak` 回写 n_registers;
//! `arg_window_base` 供调用点参数连续性 MOV 补位。BTreeMap 保确定性(B010,禁 HashMap)。
//! SPILL/UNSPILL 插入决策表(rewrite 消费);`phys_peak` 回写 n_registers;
//! `arg_window_base` 供调用点参数连续性 MOV 补位。BTreeMap 保确定性。
use std::collections::BTreeMap;
@@ -31,7 +31,7 @@ pub struct FreshVreg {
}
/// 溢出决策表:被 spill 的 vreg 的 def/use 点 + fresh vreg id 对照。
/// defs/uses 两 Vec 按下标升序。05-08 rewrite 消费此结构插 SPILL/UNSPILL。
/// defs/uses 两 Vec 按下标升序,rewrite 消费此结构插 SPILL/UNSPILL。
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpillPlan {
pub vreg: u32,
@@ -47,9 +47,9 @@ pub struct AllocMap {
pub map: BTreeMap<u32, Alloc>,
/// spill 决策表(按 vreg 升序)
pub spills: Vec<SpillPlan>,
/// 物理峰值(05-08 回写 n_registers,≤253)
/// 物理峰值(finish 回写 n_registers,≤253)
pub phys_peak: u32,
/// 参数窗口基址(05-08 调用点 MOV 补位,max_nargs=0 时 = 254 无窗口)
/// 参数窗口基址(调用点 MOV 补位,max_nargs=0 时 = 254 无窗口)
pub arg_window_base: u32,
}
+32 -20
View File
@@ -1,10 +1,11 @@
//! 染色:Kemp 简化 + 贪心选色 + spill 区间拆分循环 → AllocMap(D-09/D-05/D-02)。
//! 染色:Kemp 简化 + 贪心选色 + spill 区间拆分循环 → AllocMap。
//!
//! 主循环:build 干涉图 → Kemp 简化(degree < k 入栈)→ 卡住选 spill 候选 → 被 spill 的
//! vreg 按 def/use 点拆成单点活度 fresh vreg → 重建图重试(外循环固定点)→ 收敛后贪心
//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(D-02 RangeError 路径)。
//! 选色。fresh 染色失败 = 单点活度 ≥ k = 无可行染色 → Err(RangeError 路径,消息与
//! lower 逐字一致)。
//!
//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap(B010)。
//! 确定性:BTreeMap/BTreeSet + Vec 排序,禁 HashMap。
use std::collections::{BTreeMap, BTreeSet};
@@ -32,28 +33,35 @@ pub(super) fn run(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
}
for v in failed {
if fresh.iter().any(|fr| fr.id == v) {
// 单点活度 ≥ k:无可行染色(D-02 错误路径,消息与 lower 逐字一致)
// 单点活度 ≥ k:无可行染色(消息与 lower 逐字一致)
return Err("RangeError: function body uses too many registers (max 253)".into());
}
// 真实 vreg 溢出(D-05 区间拆分 v1)
// 真实 vreg 溢出:按 def/use 点拆成单点活度 fresh,重建图重试
spill_set.insert(v);
debug_assert!(u32::from(next_slot) <= u16::MAX as u32, "spill slot 超 u16 上限");
slot_for_vreg.insert(v, next_slot);
next_slot += 1;
for d in def_points(f, v) {
fresh.push(FreshVreg { id: next_fresh_id, at: d, kind: FreshKind::Def, owner: v });
fresh.push(FreshVreg {
id: next_fresh_id,
at: d,
kind: FreshKind::Def,
owner: v,
});
next_fresh_id += 1;
}
for u in use_points(f, v) {
fresh.push(FreshVreg { id: next_fresh_id, at: u, kind: FreshKind::Use, owner: v });
fresh.push(FreshVreg {
id: next_fresh_id,
at: u,
kind: FreshKind::Use,
owner: v,
});
next_fresh_id += 1;
}
}
iters += 1;
debug_assert!(
iters < max_real as usize * 2 + fresh.len() + 8,
"RegAlloc 染色疑似不收敛"
);
debug_assert!(iters < max_real as usize * 2 + fresh.len() + 8, "RegAlloc 染色疑似不收敛");
};
assemble(f, colors, spill_set, fresh, slot_for_vreg, arg_window_base)
@@ -183,7 +191,12 @@ fn assemble(
.saturating_add(1)
.min(254);
Ok(AllocMap { map, spills, phys_peak, arg_window_base })
Ok(AllocMap {
map,
spills,
phys_peak,
arg_window_base,
})
}
#[cfg(test)]
@@ -271,10 +284,13 @@ mod tests {
#[test]
fn escaped_vreg_keeps_color_and_not_spilled() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let mut sub = empty_function();
sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None));
sub.insts
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None));
f.nested.push(sub);
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
@@ -311,11 +327,7 @@ mod tests {
assert!(m.spills.iter().any(|s| s.vreg == 1000), "spill 的是 L");
assert!(m.phys_peak <= 253, "phys_peak ≤ 253");
// 全部 fresh 应有 Phys 分配
let fresh_phys: Vec<&Alloc> = m
.map
.values()
.filter(|a| matches!(a, Alloc::Phys(_)))
.collect();
let fresh_phys: Vec<&Alloc> = m.map.values().filter(|a| matches!(a, Alloc::Phys(_))).collect();
assert!(!fresh_phys.is_empty(), "fresh vreg 应着色为 Phys");
}
+9 -7
View File
@@ -1,4 +1,4 @@
//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures(D-10/D-03)。
//! 元数据回写:n_registers / builtin_reg_map / param_layout / upvalue_captures。
//!
//! - n_registers = map.phys_peak(≤253;不含参数窗口——窗口是调用点暂存槽,
//! CALL handler 在 push 前收集 args 进 Vec,n_registers 只控 save_stack 窗口)
@@ -39,23 +39,23 @@ pub(super) fn run(f: &mut IRFunction, map: &AllocMap) {
}
}
// (c) param_layout 不动(参数段 pre-colored 恒等;窗口与参数段重叠仅 05-07 标注的罕见情形)
// (c) param_layout 不动(参数段预着色恒等;窗口与参数段重叠仅参数段贴 254 的罕见情形)
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 无关),
// 直接槽引用(class field 计算键等)的 escaped vreg 已由预着色恒等保留。
// 注:cell 捕获变量的父 vreg 可被 RegAlloc 移动(MAKE_CELL 后值入 cell,与寄存器无关),
// 此处不做恒等断言。
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
use oxide_bytecode::opcode::OpCode;
#[test]
fn n_registers_equals_phys_peak() {
@@ -70,7 +70,8 @@ mod tests {
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));
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));
@@ -82,7 +83,8 @@ mod tests {
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));
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));
+61 -40
View File
@@ -1,13 +1,13 @@
//! 干涉图构建 + 预着色 + 可分配色集(D-09/D-10/D-11 + RESEARCH 发现 4)。
//! 干涉图构建 + 预着色 + 可分配色集。
//!
//! - 真实 vreg:字面 `Operand::Reg(r)`(rd/a/b)+ CALL 系参数连续区间 [first, first+nargs)
//! - 干涉边:逐指令 `live_before[i]` 中同时 live 的真实 vreg 两两加边(fresh vreg 按 at==i 并入)
//! - 预着色:参数段 [param_base, base+count) 钉死原号(VM 调用契约 D-10);escaped vreg
//! (nested `LOAD_VAR.a`/`STORE_VAR.rd` 直引父槽)钉死原槽号且排除 spill(B012)
//! - 预着色:参数段 [param_base, base+count) 钉死原号(VM 调用契约);escaped vreg
//! (nested `LOAD_VAR.a`/`STORE_VAR.rd` 直引父槽)钉死原槽号且排除 spill
//! - 可分配色集:`(1..=253) − 参数色 − escaped 色 − [arg_window_base, 253]`
//! (arg_window_base = 254 − max_nargs,为 05-08 调用点 MOV 补位留空槽)
//! (arg_window_base = 254 − max_nargs,为调用点参数连续性 MOV 补位留空槽)
//!
//! 确定性:全 BTreeMap + Vec 排序,禁 HashMap 迭代序(B010)。
//! 确定性:全 BTreeMap + Vec 排序,禁 HashMap 迭代序。
use std::collections::{BTreeMap, BTreeSet};
@@ -36,10 +36,10 @@ pub(super) struct InterferenceGraph {
/// 收集全部真实 vreg(BTreeSet 保确定性):
/// 字面 `Operand::Reg(r)`(rd/a/b 三槽)+ CALL/CALL_NATIVE/NEW_EXPRESSION b 槽参数区间
/// + SUPER_CALL a 槽参数区间。0 无条件排除(reg 0 永非真实——emit alloc_reg 从 ≥1 起)。
/// + SUPER_CALL a 槽参数区间。0 无条件排除(reg 0 永非真实——emit 分配寄存器从 ≥1 起)。
///
/// 254/255 的 over-approx 规则:live bitset 中位 254/255 无法区分 This/NewTarget 占用与
/// 真实 vreg 活度,若函数存在该号真实 vreg(大函数 B005 场景)则按真实 vreg 建节点染色——
/// 254/255 的过近似规则:live bitset 中位 254/255 无法区分 This/NewTarget 占用与
/// 真实 vreg 活度,若函数存在该号真实 vreg(大函数场景)则按真实 vreg 建节点染色——
/// 多出的边只造成过度约束(安全方向),缺失边才会错值。
pub(super) fn collect_real_vregs(f: &IRFunction) -> BTreeSet<u32> {
let mut real = BTreeSet::new();
@@ -85,7 +85,7 @@ pub(super) fn build(
// ── 预着色 ──
let mut pre_colors: BTreeMap<u32, u32> = BTreeMap::new();
// 参数段:VM 调用写 regs[param_base+i],钉死不可动(D-10)
// 参数段:VM 调用写 regs[param_base+i],钉死不可动(调用契约)
let pl = f.param_layout;
if pl.count > 0 {
for i in 0..pl.count {
@@ -93,12 +93,12 @@ pub(super) fn build(
pre_colors.insert(v, v);
}
}
// escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012)
// 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);
// 对称缺口(B013 延伸):子模块自身引用的父槽也必须预着色恒等。父侧 collect_escaped
// 只保证父不移动槽;但子模块 alloc 时,它引用父槽的 LOAD_VAR.a / STORE_VAR.rd 会被
// 当作子模块自己的 vreg 参与染色而移走 → 子模块读错物理槽。分界线 = param_layout.base
// 对称缺口:子模块自身引用的父槽也必须预着色恒等。父侧 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);
@@ -106,12 +106,7 @@ pub(super) fn build(
let max_nargs = f
.insts
.iter()
.filter(|i| {
matches!(
i.op,
OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL
)
})
.filter(|i| matches!(i.op, OpCode::CALL | OpCode::CALL_NATIVE | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL))
.map(|i| i.ext.first().copied().unwrap_or(0))
.max()
.unwrap_or(0);
@@ -131,12 +126,18 @@ pub(super) fn build(
if !spill_set.contains(&v) {
nodes.insert(
v,
Node { pre_color: pre_colors.get(&v).copied(), adj: Vec::new() },
Node {
pre_color: pre_colors.get(&v).copied(),
adj: Vec::new(),
},
);
}
}
for fr in fresh {
nodes.entry(fr.id).or_insert(Node { pre_color: None, adj: Vec::new() });
nodes.entry(fr.id).or_insert(Node {
pre_color: None,
adj: Vec::new(),
});
}
// 邻接表(BTreeMap<u32, BTreeSet<u32>> 去重后转 Vec 排序)
let mut adj_sets: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::new();
@@ -171,11 +172,16 @@ pub(super) fn build(
}
}
InterferenceGraph { nodes, allocatable, k, arg_window_base }
InterferenceGraph {
nodes,
allocatable,
k,
arg_window_base,
}
}
/// 递归收集 nested 树中变量槽引用(LOAD_VAR.a 读槽、STORE_VAR.rd 写槽),
/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除,B012)。
/// 注入 pre_colors 并记录 escaped 色列表(可分配集排除 + spill 候选排除)。
fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, out: &mut Vec<u32>) {
for sub in nested {
for inst in &sub.insts {
@@ -196,7 +202,7 @@ fn collect_escaped(nested: &[IRFunction], pre_colors: &mut BTreeMap<u32, u32>, o
out.sort_unstable();
}
/// 收集当前函数自身对父槽的直接引用(B013 延伸,父侧保护的对称缺口):
/// 收集当前函数自身对父槽的直接引用(父侧保护的对称缺口):
/// `LOAD_VAR.a` / `STORE_VAR.rd` 中槽号 < param_layout.base 的 vreg 是父槽引用
/// (emit inherited_reg_start 分界:子函数自身 vreg 从 base 起分配,父槽引用 < base)。
/// 父侧 collect_escaped 只保护父不移动槽;此处保证子函数 alloc 时这些引用不被染色移走。
@@ -245,8 +251,10 @@ mod tests {
let mut f = empty_function();
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
f.insts.push(Inst::load_const(Operand::Reg(2), 0));
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));
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 g = build_graph(&f);
assert!(g.nodes[&1].adj.contains(&2), "r1 与 r2 应邻接");
assert!(g.nodes[&2].adj.contains(&1));
@@ -259,11 +267,14 @@ mod tests {
let mut f = empty_function();
f.insts.push(Inst::load_const(Operand::Reg(1), 0));
f.insts.push(Inst::load_const(Operand::Reg(2), 0));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(3), Operand::Reg(1), Operand::Reg(2)));
f.insts.push(Inst::load_const(Operand::Reg(4), 0));
f.insts.push(Inst::load_const(Operand::Reg(5), 0));
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(6), Operand::Reg(4), Operand::Reg(5)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None));
let g = build_graph(&f);
assert!(!g.nodes[&1].adj.contains(&4), "r1 与 r4 活度不相交,无边");
}
@@ -272,8 +283,10 @@ mod tests {
fn param_segment_precolored() {
let mut f = empty_function();
f.param_layout = oxide_ir::ParamLayout { base: 1, count: 2 };
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));
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 g = build_graph(&f);
assert_eq!(g.nodes[&1].pre_color, Some(1));
assert_eq!(g.nodes[&2].pre_color, Some(2));
@@ -284,10 +297,13 @@ mod tests {
#[test]
fn escaped_slots_precolored() {
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(3), Operand::Reg(4)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let mut sub = empty_function();
sub.insts.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None));
sub.insts
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(9), Operand::Reg(3), Operand::None));
f.nested.push(sub);
let g = build_graph(&f);
assert_eq!(g.nodes[&3].pre_color, Some(3), "escaped 槽 r3 预着色");
@@ -297,8 +313,10 @@ mod tests {
#[test]
fn reserved_colors_excluded() {
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));
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 g = build_graph(&f);
assert!(!g.allocatable.contains(&0), "色 0 排除(CALL 隐式 reg0)");
assert!(!g.allocatable.contains(&254) && !g.allocatable.contains(&255), "254/255 排除");
@@ -309,7 +327,8 @@ mod tests {
fn arg_window_reserved() {
let mut f = empty_function();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 3));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
let g = build_graph(&f);
assert_eq!(g.arg_window_base, 251, "max_nargs=3 → base 251");
assert!(!g.allocatable.contains(&251) && !g.allocatable.contains(&252) && !g.allocatable.contains(&253));
@@ -317,10 +336,12 @@ mod tests {
#[test]
fn high_numbered_vreg_254_is_real_node() {
// 大函数场景:真实 vreg 254 必须建节点(B005 正确性前提)
// 大函数场景:真实 vreg 254 必须建节点(正确性前提)
let mut f = empty_function();
f.insts.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(252), Operand::Reg(253)));
f.insts.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(254), Operand::Reg(252), Operand::Reg(253)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(254), Operand::None, Operand::None));
let real = collect_real_vregs(&f);
assert!(real.contains(&254), "真实 vreg 254 必须被收集");
let g = build_graph(&f);
+27 -17
View File
@@ -1,10 +1,10 @@
//! RegAlloc 改写 pass:干涉图染色决策(前半)+ 指令改写/元数据回写(后半)。
//!
//! 消费 `oxide_liveness::LiveInfo`(inst_live_before 逐指令活集,05-04 交付)与
//! 消费 `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 入口(D-16),`color()` 只产决策。
//! (元数据回写)。`alloc()` 是完整改写 pass 入口,`color()` 只产决策。
mod alloc_map;
mod color;
@@ -18,8 +18,8 @@ use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
/// 染色:`&IRFunction + &LiveInfo → Result<AllocMap, String>`。纯函数,只读 IR。
/// 空 IRFunction 退化:Ok(AllocMap::new())。Err = 无可行染色(D-02 RangeError 路径,
/// 05-08 接线时转 lower 错误)。
/// 空 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());
@@ -27,12 +27,17 @@ pub fn color(f: &IRFunction, live: &LiveInfo) -> Result<AllocMap, String> {
color::run(f, live)
}
/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`(D-16)。
/// 寄存器分配改写:`&mut IRFunction + &LiveInfo → Result<(), String>`。
///
/// 流程:LiveInfo 维度守卫(inst 数不符 → 内部重跑 build_cfg + liveness,D-17 修正:
/// 精确 DCE 删指令后 LiveInfo 过期,重跑纯函数一次调用,不重复建分析引擎)→
/// `color()`(Err 原样传播)→ `rewrite::run` → `finish::run` → nested 递归自顶向下
/// (每子函数独立 vreg 空间独立 alloc,D-03/D-10)。
/// # 步骤
/// - 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(());
@@ -71,8 +76,10 @@ mod tests {
#[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));
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());
@@ -88,10 +95,12 @@ mod tests {
#[test]
fn alloc_err_propagates() {
// 255 参数 CALL:窗口吞并 → k=0 → 无可行染色 → Err
// 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));
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();
@@ -102,10 +111,12 @@ mod tests {
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));
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));
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);
@@ -122,4 +133,3 @@ mod tests {
assert!(f.nested[0].n_registers <= 253, "子函数 n_registers 已回写");
}
}
+21 -13
View File
@@ -1,14 +1,14 @@
//! 指令改写:消费 AllocMap 就地重写 IR(D-05/D-07/D-20/RESEARCH 发现 4)。
//! 指令改写:消费 AllocMap 就地重写 IR。
//!
//! - 槽位 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
//! (None→0、This→254、NewTarget→255 由 lower 映射)
//! - spill 落点:def 点后 SPILL、use 点前 UNSPILL;RMW(COMPOUND 系 rd 读旧值)UNSPILL
//! 进 def-fresh 寄存器、use-fresh 弃用
//! - 调用点参数连续性:参数分散则 MOV 搬进 ARG_WINDOW
//! - spilled builtin 入口 SPILL(builtin 无指令 def,VM 入口隐式绑定)
//! - label_pos mark-sweep 重建:label 落 group 起点(跳转先执行 before-插入)
//!
//! 确定性:全 BTreeMap/Vec 排序,禁 HashMap(B010)。
//! 确定性:全 BTreeMap/Vec 排序,禁 HashMap。
use std::collections::BTreeMap;
@@ -189,7 +189,7 @@ fn rewrite_inst(
}
}
}
other => other, // None/This/NewTarget/Const/Imm/Label 零改动(D-11)
other => other, // None/This/NewTarget/Const/Imm/Label 零改动,lower 映射语义号
}
};
@@ -269,7 +269,7 @@ pub(super) fn spilled_builtin_bindings(f: &IRFunction, map: &AllocMap) -> Vec<(S
}
// escaped 色(递归 nested 的 LOAD_VAR.a / STORE_VAR.rd)
collect_escaped(&f.nested, &mut excluded);
// own-escaped 色(B013 延伸):本函数 LOAD_VAR.a / STORE_VAR.rd 引用父槽(< base)。
// own-escaped 色:本函数 LOAD_VAR.a / STORE_VAR.rd 引用父槽(< base)。
// 与 graph.rs collect_own_escaped 对称——spill 自由色不得占用父槽号。
collect_own_escaped(f, &mut excluded);
// 窗口
@@ -364,7 +364,8 @@ mod tests {
#[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::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);
@@ -376,8 +377,10 @@ mod tests {
#[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));
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));
@@ -484,7 +487,11 @@ mod tests {
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();
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!(
@@ -533,7 +540,8 @@ mod tests {
#[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.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));
+13 -16
View File
@@ -1,9 +1,9 @@
//! B005 复现 + n_registers 汇总断言(REG-04 直接验收)。
//! 大函数寄存器收缩复现 + n_registers 汇总断言。
//!
//! 低层管线直调(不经 Compiler——oxide_regalloc dev-dep oxide_compiler 会构成依赖环):
//! parse → emit → dce(保守)→(regalloc on/off)→ lower。on 分支按 RESEARCH 例 6:
//! 精确 DCE 后重跑 build_cfg + liveness 再 alloc(D-17 索引位移修正);off 分支 = D-18
//! 降级路径(vreg 原样当物理号 → lower Reg>253 抛 RangeError)。
//! parse → emit → dce(保守)→(regalloc on/off)→ lower。on 分支按编译管线:
//! 精确 DCE 后重跑 build_cfg + liveness 再 alloc(删指令后下标位移,重建避免错位);
//! off 分支为降级路径(vreg 原样当物理号 → lower Reg>253 抛 RangeError)。
use oxide_bytecode::module::CompiledModule;
@@ -23,7 +23,7 @@ fn compile_source(src: &str, regalloc: bool) -> Result<CompiledModule, String> {
oxide_ir::lower::lower(&ir)
}
/// B005 样例生成器:n 个逐语句独立 vreg(各值立即死)→ vreg 总数 ≈ 2n ≫ 253,
/// 大函数样例生成器:n 个逐语句独立 vreg(各值立即死)→ vreg 总数 ≈ 2n ≫ 253,
/// 但峰值活度极小 → RegAlloc 有充分复用空间。
fn gen_big(n: usize) -> String {
let mut src = String::from("function f() { ");
@@ -43,9 +43,9 @@ fn module_max_n_registers(m: &CompiledModule) -> u8 {
max
}
/// B005 前置复现:off 模式(降级路径)250 变量函数报 RangeError。
/// 前置复现:off 模式(降级路径)250 变量函数报 RangeError。
#[test]
fn b005_repro_errors_without_regalloc() {
fn large_function_errors_without_regalloc() {
let err = match compile_source(&gen_big(250), false) {
Err(e) => e,
Ok(_) => panic!("off 模式应报 RangeError"),
@@ -53,14 +53,14 @@ fn b005_repro_errors_without_regalloc() {
assert!(err.contains("RangeError"), "off 模式应报 RangeError: {err}");
}
/// B005 修复达成:on 模式 250 变量函数编译成功,模块树 n_registers ≤ 253。
/// 修复达成:on 模式 250 变量函数编译成功,模块树 n_registers ≤ 253。
#[test]
fn b005_compiles_with_regalloc() {
fn large_function_compiles_with_regalloc() {
let module = compile_source(&gen_big(250), true).expect("on 模式应编译成功");
assert!(module_max_n_registers(&module) <= 253, "模块树 n_registers 应 ≤253");
}
/// 样例集 n_registers on ≤ off 且至少一个严格下降(证明寄存器复用,REG-04 核心)。
/// 样例集 n_registers on ≤ off 且至少一个严格下降(证明寄存器复用)。
#[test]
fn n_registers_on_off_aggregate() {
let samples = [
@@ -78,7 +78,7 @@ fn n_registers_on_off_aggregate() {
"function f() { var [a, b] = [1, 2]; var { c, d } = { c: 3, d: 4 }; return a + b + c + d; } f();",
// 箭头函数
"var f = (a, b) => a * b; f(6, 7);",
// switch+const(B011 回归)
// switch+const(const guard 回归)
"function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);",
// 数组字面量
"function f() { var a = [1, 2, 3, 4, 5]; return a[0] + a[4]; } f();",
@@ -90,10 +90,7 @@ fn n_registers_on_off_aggregate() {
assert!(on_max <= 253, "样例 {i} on n_registers {on_max} 超 253");
if let Ok(off) = compile_source(src, false) {
let off_max = module_max_n_registers(&off);
assert!(
on_max <= off_max,
"样例 {i} on({on_max}) > off({off_max})——RegAlloc 不应增寄存器窗口"
);
assert!(on_max <= off_max, "样例 {i} on({on_max}) > off({off_max})——RegAlloc 不应增寄存器窗口");
if on_max < off_max {
strict_decrease = true;
}
@@ -102,7 +99,7 @@ fn n_registers_on_off_aggregate() {
assert!(strict_decrease, "至少一个样例应严格 on < off(复用证明)");
}
/// 确定性:同一源码 on 模式编译两次字节码逐字节相同(B010,structural_hash 缓存依赖)。
/// 确定性:同一源码 on 模式编译两次字节码逐字节相同(确定性排序,缓存 key 依赖)。
#[test]
fn regalloc_deterministic_output() {
let a = compile_source(&gen_big(50), true).expect("on 编译成功");
@@ -1,4 +1,4 @@
//! B013 延伸回归:nested 子模块直接槽引用父槽,RegAlloc on/off 运行时等价。
//! 嵌套父槽引用回归:nested 子模块直接槽引用父槽,RegAlloc on/off 运行时等价。
//!
//! 父侧 collect_escaped 保证父不移动槽;但子模块自身 alloc 时引用父槽的
//! LOAD_VAR.a / STORE_VAR.rd 曾被当作子模块自己的 vreg 染色移走(→ 读错物理槽,
+5 -5
View File
@@ -1,4 +1,4 @@
//! REG-04 断言:RegAlloc on ≤253 且 < off(寄存器收缩);B005 大函数 on 编译成功。
//! 寄存器收缩断言:RegAlloc on ≤253 且 < off(寄存器复用);大函数 on 编译成功。
fn n_registers(src: &str, regalloc: bool) -> Result<u8, String> {
let allocator = oxide_parser::Allocator::default();
@@ -70,15 +70,15 @@ fn n_registers_shrinks_with_regalloc() {
}
}
/// B005 大函数:on 编译成功 ≤253;off 报 RangeError(B005 解决证据)。
/// 大函数:on 编译成功 ≤253;off 报 RangeError(vreg 超上限)。
#[test]
fn b005_large_function_fits_in_253() {
fn large_function_fits_in_253() {
let src = gen_sum_function(220);
let on = n_registers(&src, true);
assert!(on.is_ok(), "B005 样例 on 应编译成功");
assert!(on.is_ok(), "大函数样例 on 应编译成功");
assert!(on.unwrap() <= 253, "on ≤253");
let off = n_registers(&src, false);
assert!(off.is_err(), "B005 样例 off 应报 RangeError");
assert!(off.is_err(), "大函数样例 off 应报 RangeError");
assert!(off.unwrap_err().contains("too many registers"), "off Err 消息");
}
+16 -12
View File
@@ -1,4 +1,4 @@
//! RegAlloc on/off 运行时等价(D-21 主验证)+ B005/spill 样例运行正确(REG-04)。
//! RegAlloc on/off 运行时等价(主验证)+ 大函数/spill 样例运行正确。
//!
//! 同一 JS 源码走两条管线:parse → emit → dce(保守恒定)→(regalloc on/off)→ lower → run,
//! 对比顶层执行结果。**不经 Compiler::compile**(测试层直调底层函数)。
@@ -38,9 +38,9 @@ fn normalize(r: Result<JsValue, String>) -> (bool, String) {
#[test]
fn regalloc_preserves_semantics() {
let samples = [
// switch+const(B011 回归)
// switch+const(const guard 回归)
"function f(x) { switch (x) { case 1: break; } const c = 5; return c; } f(1);",
// 类字段(B012 行为锚)
// 类字段(escaped 槽行为锚)
"var x = 0; class C { p = (x = 1); } new C(); x;",
// try-catch
"function f() { try { throw 1; } catch (e) { return e + 1; } } f();",
@@ -95,15 +95,16 @@ fn gen_spill_function(n: u32) -> String {
src
}
/// B005 大函数(200+ 变量)与 spill 压力样例 on 运行正确。
/// off 路径 lower 失败(B005 上限)——不能做 on/off 相等,on 运行正确即 REG-04 证据。
/// 大函数(200+ 变量)与 spill 压力样例 on 运行正确。
/// off 路径 lower 失败(vreg 超 253 上限)——不能做 on/off 相等,on 运行正确即
/// 寄存器复用与 spill 的证据。
#[test]
fn b005_and_spill_samples_run_correctly() {
// B005:220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决
let src_b005 = gen_sum_function(220);
let expected_b005 = 220 * 219 / 2;
let out = run_source(&src_b005, true).expect("B005 样例 on 应编译成功");
assert_eq!(out.to_string(), expected_b005.to_string(), "B005 样例结果错误");
fn large_and_spill_samples_run_correctly() {
// 220 变量 → vreg 数远超 253,但同时存活 < 253 → RegAlloc 复用解决
let src_large = gen_sum_function(220);
let expected_large = 220 * 219 / 2;
let out = run_source(&src_large, true).expect("大函数样例 on 应编译成功");
assert_eq!(out.to_string(), expected_large.to_string(), "大函数样例结果错误");
// spill 压力:长活 L + 254 短活 → L 溢出,验证 SPILL/UNSPILL 真实发生且结果正确
let src_spill = gen_spill_function(254);
@@ -124,7 +125,10 @@ fn b005_and_spill_samples_run_correctly() {
let module = oxide_ir::lower::lower(&ir).expect("lower");
// 递归扫描模块树(spill 发生在嵌套函数 f 中,非顶层 f(1) 调用)
fn has_op(m: &oxide_bytecode::module::CompiledModule, target: OpCode) -> bool {
if m.bytecode.iter().any(|&instr| OpCode::try_from(instr as u8).ok() == Some(target)) {
if m.bytecode
.iter()
.any(|&instr| OpCode::try_from(instr as u8).ok() == Some(target))
{
return true;
}
m.sub_modules.iter().any(|s| has_op(s, target))