fix(regalloc): nested 子模块引用父槽无预着色保护致读错物理槽(B014)
子模块 alloc 时引用父槽的 LOAD_VAR.a/STORE_VAR.rd 被当作自身 vreg 染色移走, 父侧 collect_escaped 只保护父不移动槽,子侧无对称保护。以 param_layout.base (inherited_reg_start 分界)识别父槽引用并预着色恒等,spill 自由色排除。 新增 nested_parent_slot_regress 回归测试(field/method + 高压力变量), workspace 全绿 + clippy 干净。
This commit is contained in:
@@ -95,6 +95,11 @@ pub(super) fn build(
|
||||
// escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012)
|
||||
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
|
||||
// (emit 的 inherited_reg_start 继承机制:子模块 vreg ≥ base,父槽引用 < base)。
|
||||
collect_own_escaped(f, &mut pre_colors, &mut escaped_colors);
|
||||
|
||||
// ── 可分配色集 ──
|
||||
let max_nargs = f
|
||||
@@ -190,6 +195,33 @@ 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 时这些引用不被染色移走。
|
||||
fn collect_own_escaped(f: &IRFunction, pre_colors: &mut BTreeMap<u32, u32>, out: &mut Vec<u32>) {
|
||||
let base = f.param_layout.base;
|
||||
if base == 0 {
|
||||
return; // 顶层/无父槽场景无分界,跳过
|
||||
}
|
||||
for inst in &f.insts {
|
||||
let slot = match inst.op {
|
||||
OpCode::LOAD_VAR => inst.a,
|
||||
OpCode::STORE_VAR => inst.rd,
|
||||
_ => Operand::None,
|
||||
};
|
||||
if let Operand::Reg(r) = slot {
|
||||
if r < base {
|
||||
pre_colors.entry(r).or_insert(r);
|
||||
if !out.contains(&r) {
|
||||
out.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out.sort_unstable();
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -269,6 +269,9 @@ 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)。
|
||||
// 与 graph.rs collect_own_escaped 对称——spill 自由色不得占用父槽号。
|
||||
collect_own_escaped(f, &mut excluded);
|
||||
// 窗口
|
||||
for c in map.arg_window_base..=253 {
|
||||
excluded.push(c);
|
||||
@@ -321,6 +324,27 @@ fn collect_escaped(nested: &[IRFunction], out: &mut Vec<u32>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// 收集本函数 LOAD_VAR.a / STORE_VAR.rd 中引用父槽(槽号 < param_layout.base)的 vreg。
|
||||
/// 与 graph.rs::collect_own_escaped 对称:base = emit inherited_reg_start 分界线。
|
||||
fn collect_own_escaped(f: &IRFunction, out: &mut Vec<u32>) {
|
||||
let base = f.param_layout.base;
|
||||
if base == 0 {
|
||||
return;
|
||||
}
|
||||
for inst in &f.insts {
|
||||
let slot = match inst.op {
|
||||
OpCode::LOAD_VAR => inst.a,
|
||||
OpCode::STORE_VAR => inst.rd,
|
||||
_ => Operand::None,
|
||||
};
|
||||
if let Operand::Reg(r) = slot {
|
||||
if r < base {
|
||||
out.push(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//! B013 延伸回归:nested 子模块直接槽引用父槽,RegAlloc on/off 运行时等价。
|
||||
//!
|
||||
//! 父侧 collect_escaped 保证父不移动槽;但子模块自身 alloc 时引用父槽的
|
||||
//! LOAD_VAR.a / STORE_VAR.rd 曾被当作子模块自己的 vreg 染色移走(→ 读错物理槽,
|
||||
//! on={object} vs off=42)。修复:graph.rs/rewrite.rs 对称 collect_own_escaped。
|
||||
//! 槽号 ≥ inherited_reg_start(param_layout.base)时触发,故用高压力变量推高槽号。
|
||||
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn run_source(src: &str, regalloc: bool) -> (bool, String) {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, src) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return (false, format!("parse error: {}", e[0].message)),
|
||||
};
|
||||
let mut ir = match oxide_emit::Emitter::new().emit_program(&program) {
|
||||
Ok(i) => i,
|
||||
Err(e) => return (false, format!("emit error: {e}")),
|
||||
};
|
||||
oxide_dce::dce(&mut ir);
|
||||
if regalloc {
|
||||
let cfg = oxide_cfg::build_cfg(&ir);
|
||||
let live = oxide_liveness::liveness(&ir, &cfg);
|
||||
oxide_dce::dce_precise(&mut ir, &live);
|
||||
let cfg2 = oxide_cfg::build_cfg(&ir);
|
||||
let live2 = oxide_liveness::liveness(&ir, &cfg2);
|
||||
if let Err(e) = oxide_regalloc::alloc(&mut ir, &live2) {
|
||||
return (false, format!("alloc error: {e}"));
|
||||
}
|
||||
}
|
||||
let module = match oxide_ir::lower::lower(&ir) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return (false, format!("lower error: {e}")),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(v) => (true, v.to_string()),
|
||||
Err(e) => (false, e),
|
||||
}
|
||||
}
|
||||
|
||||
fn pressure(n: usize) -> String {
|
||||
let mut src = String::new();
|
||||
for i in 0..n {
|
||||
src.push_str(&format!("var v{i} = {i}; "));
|
||||
}
|
||||
src
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_reads_outer_40_pressure() {
|
||||
let src = format!("{}var a = 42; class C {{ p = a; }} new C().p;", pressure(40));
|
||||
let off = run_source(&src, false);
|
||||
let on = run_source(&src, true);
|
||||
assert_eq!(on, off, "field 读外层 40 压力: off={off:?} on={on:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_reads_outer_40_pressure() {
|
||||
let src = format!("{}var a = 42; class C {{ m() {{ return a; }} }} new C().m();", pressure(40));
|
||||
let off = run_source(&src, false);
|
||||
let on = run_source(&src, true);
|
||||
assert_eq!(on, off, "method 读外层 40 压力: off={off:?} on={on:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_reads_outer_30_pressure() {
|
||||
let src = format!("{}var a = 42; class C {{ p = a; }} new C().p;", pressure(30));
|
||||
let off = run_source(&src, false);
|
||||
let on = run_source(&src, true);
|
||||
assert_eq!(on, off, "field 读外层 30 压力: off={off:?} on={on:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_reads_outer_25_pressure() {
|
||||
let src = format!("{}var a = 42; class C {{ p = a; }} new C().p;", pressure(25));
|
||||
let off = run_source(&src, false);
|
||||
let on = run_source(&src, true);
|
||||
assert_eq!(on, off, "field 读外层 25 压力: off={off:?} on={on:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn field_reads_outer_hi_last_40_pressure() {
|
||||
let mut src = pressure(40);
|
||||
src.push_str("var a_hi = 777; class C { p = a_hi; } new C().p;");
|
||||
let off = run_source(&src, false);
|
||||
let on = run_source(&src, true);
|
||||
assert_eq!(on, off, "field 读外层末位 40 压力: off={off:?} on={on:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user