perf: 字节码调用压帧窗口按调用点存活上界拷贝减少整窗搬移

RegAlloc 改写后补第三次 liveness,为每条 CALL/NEW/SUPER_CALL 计算调用点
存活上界(最大存活物理槽号 + 1,254/255 由帧单独保存不计入),编码进
ext 高 8 位(nargs 低 8 位不变)。三处手写压帧窗口 = min(调用方活动寄存
器, 存活上界),restore_frame 按窗口回拷;CallFrame 新增
caller_active_reg_limit 保存调用方真实活动上限,恢复时不随截断窗口缩水。
生成器/异步函数体调用点保持全量窗口。spread 调用与运行时发起压帧
(accessor/内联)走全量路径不受影响。
This commit is contained in:
2026-08-15 09:56:22 +08:00
parent 5e14817eaa
commit d9acda7417
13 changed files with 448 additions and 11 deletions
+143
View File
@@ -0,0 +1,143 @@
//! 调用点存活上界编码:RegAlloc 改写后按物理存活集为调用指令回填 ext 高 8 位。
//!
//! 调用指令 `ext[0] = nargs(低 8 位) | 存活上界(高 8 位)`。上界 = 调用点存活
//! 物理寄存器最大槽号 + 1(即压帧窗口 `regs[0..上界]`);上界 0 表示未编码
//! (运行时回退到调用方 `active_reg_limit` 全量窗口)。`regs[254]/[255]`
//! (this/new.target)由帧单独保存不占窗口,计算时排除。生成器 / 异步函数体
//! 内部调用点不编码——挂起恢复按全量寄存器快照搬移,保持保守语义。
use oxide_bytecode::opcode::OpCode;
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;
/// 为每个字节码调用指令(CALL/NEW_EXPRESSION/SUPER_CALL)编码存活上界到 ext 高 8 位。
/// 嵌套子函数递归处理:每个子函数独立重跑 CFG + liveness(与 `alloc` 同纪律)。
///
/// # 边界与前提
/// - `live` 与 `f.insts` 维度不符时内部重算(防过期 LiveInfo 下标错位)
/// - 生成器 / 异步函数体内部调用点不编码(保持全量窗口)
/// - 只改写 `ext[0]` 高 8 位,nargs 低 8 位不动;无存活时保持 0(全量)
pub fn encode_call_window(f: &mut IRFunction, live: &LiveInfo) {
if f.insts.is_empty() && f.nested.is_empty() {
return;
}
// LiveInfo 维度守卫:与 alloc 相同纪律,过期则重算。
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)
};
// 生成器 / 异步函数体:调用点保持全量窗口(挂起恢复按全量寄存器快照搬移)。
if !f.is_generator && !f.is_async {
for (i, inst) in f.insts.iter_mut().enumerate() {
if matches!(inst.op, OpCode::CALL | OpCode::NEW_EXPRESSION | OpCode::SUPER_CALL) {
let after = &live.inst_live_after[i];
// 存活上界 = 最大存活槽号 + 1;254/255 由帧单独保存,不进窗口。
let mut upper = 0u32;
for (reg, &alive) in after.iter().enumerate().take(254) {
if alive {
upper = (reg + 1) as u32;
}
}
if upper > 0 {
inst.ext[0] = (inst.ext[0] & 0xFF) | (upper << 8);
}
}
}
}
for child in &mut f.nested {
let cfg = oxide_cfg::build_cfg(child);
let l = oxide_liveness::liveness(child, &cfg);
encode_call_window(child, &l);
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
#[test]
fn call_window_packs_upper_bound_from_live_after() {
// 0: CALL(r1=callee, r2=this, r3=首参, 1 参) def {0} use {1,2,3}
// 1: ADD r5 = r4 + r4 r4 跨调用存活
// 2: RETURN r5
let mut f = IRFunction::new();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(4), Operand::Reg(4)));
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);
encode_call_window(&mut f, &live);
// 存活集 {4} → 上界 5,ext = 1 | (5 << 8)
assert_eq!(f.insts[0].ext[0], 1 | (5 << 8));
}
#[test]
fn call_window_zero_when_nothing_survives() {
// 0: CALL(结果未用) 1: LOAD_CONST r6 2: RETURN r6
let mut f = IRFunction::new();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
f.insts.push(Inst::load_const(Operand::Reg(6), 0));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(6), Operand::None, Operand::None));
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
encode_call_window(&mut f, &live);
// 无跨调用存活 → 保持全量(ext 高 8 位 = 0)
assert_eq!(f.insts[0].ext[0], 1);
}
#[test]
fn call_window_excludes_this_and_new_target() {
// 0: LOAD_CONST r6 1: CALL 2: LOAD_VAR r8, This(254 跨调用存活)
// 3: ADD r7=r6+r8 4: RETURN r7
let mut f = IRFunction::new();
f.insts.push(Inst::load_const(Operand::Reg(6), 0));
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
f.insts
.push(Inst::new(OpCode::LOAD_VAR, Operand::Reg(8), Operand::This, Operand::None));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(7), Operand::Reg(6), Operand::Reg(8)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(7), Operand::None, Operand::None));
let cfg = oxide_cfg::build_cfg(&f);
let live = oxide_liveness::liveness(&f, &cfg);
encode_call_window(&mut f, &live);
// 存活 {6, 254} → 上界 7(254 由帧单独保存,不放大窗口)
assert_eq!(f.insts[1].ext[0], 1 | (7 << 8));
}
#[test]
fn call_window_skips_generator_body() {
let mut f = IRFunction::new();
f.is_generator = true;
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(4), Operand::Reg(4)));
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);
encode_call_window(&mut f, &live);
// 生成器体不编码 → ext 保持纯 nargs
assert_eq!(f.insts[0].ext[0], 1);
}
#[test]
fn call_window_stale_live_reruns() {
// 空 LiveInfo(维度不符)→ 内部重跑,仍正确编码。
let mut f = IRFunction::new();
f.insts.push(Inst::call(Operand::Reg(1), Operand::Reg(2), Operand::Reg(3), 1));
f.insts
.push(Inst::new(OpCode::ADD, Operand::Reg(5), Operand::Reg(4), Operand::Reg(4)));
f.insts
.push(Inst::new(OpCode::RETURN, Operand::Reg(5), Operand::None, Operand::None));
encode_call_window(&mut f, &LiveInfo::new());
assert_eq!(f.insts[0].ext[0], 1 | (5 << 8));
}
}
+2
View File
@@ -7,6 +7,7 @@
//! (元数据回写)。`alloc()` 是完整改写 pass 入口,`color()` 只产决策。
mod alloc_map;
mod call_window;
mod color;
mod finish;
mod graph;
@@ -14,6 +15,7 @@ mod regalloc_log;
mod rewrite;
pub use alloc_map::{Alloc, AllocMap, FreshKind, FreshVreg, SpillPlan};
pub use call_window::encode_call_window;
use oxide_ir::IRFunction;
use oxide_liveness::LiveInfo;