From d65fa03de6dde9547911926e7ed075f83f39cc74 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 6 Aug 2026 20:34:55 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20nested=20=E5=AD=90=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E7=88=B6=E6=A7=BD=E6=97=A0=E9=A2=84=E7=9D=80?= =?UTF-8?q?=E8=89=B2=E4=BF=9D=E6=8A=A4=E8=87=B4=E8=AF=BB=E9=94=99=E7=89=A9?= =?UTF-8?q?=E7=90=86=E6=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 子模块 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 干净。 --- crates/oxide_regalloc/src/graph.rs | 32 +++++++ crates/oxide_regalloc/src/rewrite.rs | 24 +++++ .../tests/nested_parent_slot_regress.rs | 89 +++++++++++++++++++ 3 files changed, 145 insertions(+) create mode 100644 crates/oxide_regalloc/tests/nested_parent_slot_regress.rs diff --git a/crates/oxide_regalloc/src/graph.rs b/crates/oxide_regalloc/src/graph.rs index 2342e062..4742b463 100644 --- a/crates/oxide_regalloc/src/graph.rs +++ b/crates/oxide_regalloc/src/graph.rs @@ -95,6 +95,11 @@ pub(super) fn build( // escaped:递归 nested 收集 LOAD_VAR.a / STORE_VAR.rd 直引的父槽(B012) let mut escaped_colors: Vec = 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, 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, out: &mut Vec) { + 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::*; diff --git a/crates/oxide_regalloc/src/rewrite.rs b/crates/oxide_regalloc/src/rewrite.rs index 3d34e210..38905949 100644 --- a/crates/oxide_regalloc/src/rewrite.rs +++ b/crates/oxide_regalloc/src/rewrite.rs @@ -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) { } } +/// 收集本函数 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) { + 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::*; diff --git a/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs new file mode 100644 index 00000000..f4e023e2 --- /dev/null +++ b/crates/oxide_regalloc/tests/nested_parent_slot_regress.rs @@ -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:?}"); +}