fix: builtin 槽恒等预着色号 >253 时改分配自由色,修复大函数晚引用全局报 too many registers

This commit is contained in:
2026-08-11 00:19:29 +08:00
parent b44bc3d7f2
commit f96c351cf8
2 changed files with 50 additions and 13 deletions
+36 -13
View File
@@ -117,19 +117,6 @@ pub(super) fn build(
// 自己的 vreg 参与染色而移走 → 子模块读错物理槽。分界线 = param_layout.base
// (emit 的 inherited_reg_start 继承机制:子模块 vreg ≥ base,父槽引用 < base)。
collect_own_escaped(f, &mut pre_colors, &mut escaped_colors);
// builtin 槽:VM 帧推入时写 regs[slot]=全局值(无指令 def 却活到入口),
// 物理号必须恒等并排除出可分配集。否则 prologue 死定义临时(如解构
// FOR_OF_DONE 结果)与它同色,在 builtin use 前执行写入 → 覆写全局值
// (B021:解构参数后 builtin 调用读到 false/undefined)。
for (_, reg) in &f.builtin_reg_map {
let v = *reg;
if real.contains(&v) {
pre_colors.entry(v).or_insert(v);
if !escaped_colors.contains(&v) {
escaped_colors.push(v);
}
}
}
// ── 可分配色集 ──
// 窗口只为参数连续性 MOV 桥预留;spread 调用实参经 ext 逐个读寄存器,无连续性要求。
@@ -141,6 +128,42 @@ pub(super) fn build(
.max()
.unwrap_or(0);
let arg_window_base = 254u32.saturating_sub(max_nargs);
// ── builtin 槽 ──
// VM 帧推入时写 regs[slot]=全局值(无指令 def 却活到入口),物理号须恒定且排除出
// 可分配集,否则 prologue 死定义临时(如解构 FOR_OF_DONE 结果)与它同色,在
// builtin use 前执行写入 → 覆写全局值。
// 恒等号 v 在 ≤253 且不落入参数 MOV 窗口时保持恒等;大函数晚引用 builtin 的槽号
// >253(或 ≥arg_window_base 与 MOV 桥冲突)时恒等号不可编码 → 改分配确定性自由色
// R(算法与 spilled_builtin_bindings 同源:1..arg_window_base 中最小未占用)。
let mut builtin_entries: Vec<(String, u32)> = f
.builtin_reg_map
.iter()
.filter(|(_, reg)| real.contains(reg))
.map(|(name, reg)| (name.clone(), *reg))
.collect();
builtin_entries.sort_by(|a, b| a.0.cmp(&b.0));
let mut used_colors: Vec<u32> = Vec::new();
for (_, v) in builtin_entries {
let r = if v <= 253 && v < arg_window_base {
v
} else {
(1u32..arg_window_base)
.find(|c| {
!pre_colors.values().any(|p| *p == *c) && !escaped_colors.contains(c) && !used_colors.contains(c)
})
.unwrap_or_else(|| {
debug_assert!(false, "builtin 槽无自由色(v={v})");
1
})
};
used_colors.push(r);
pre_colors.insert(v, r);
if !escaped_colors.contains(&r) {
escaped_colors.push(r);
}
}
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 {
@@ -53,6 +53,20 @@ fn large_function_errors_without_regalloc() {
assert!(err.contains("RangeError"), "off 模式应报 RangeError: {err}");
}
/// 大函数晚引用全局(builtin 槽号 >253):builtin 槽须重映射到低号物理色,不得以
/// 恒等号 >253 编码(RegAlloc 后 lower 的 Reg>253 兜底检查报 RangeError,见
/// temporalHelpers.js + 测试文件拼接场景)。`late` 首次引用在函数尾部,槽号必 >253。
#[test]
fn late_builtin_slot_beyond_253_compiles() {
let mut src = String::new();
for i in 0..160 {
src.push_str(&format!("var v{i} = {i}; v{i}; "));
}
src.push_str("var result = late;");
let module = compile_source(&src, true).expect("on 模式应编译成功");
assert!(module_max_n_registers(&module) <= 253, "模块树 n_registers 应 ≤253");
}
/// 修复达成:on 模式 250 变量函数编译成功,模块树 n_registers ≤ 253。
#[test]
fn large_function_compiles_with_regalloc() {