mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
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:
@@ -77,6 +77,11 @@ impl Compiler {
|
||||
let cfg2 = oxide_cfg::build_cfg(&ir); // 精确 DCE 删指令后 inst 下标位移,CFG 重建
|
||||
let live2 = oxide_liveness::liveness(&ir, &cfg2); // 重算,绝不用过期 live
|
||||
oxide_regalloc::alloc(&mut ir, &live2)?; // 无可行染色 → RangeError 上抛
|
||||
// RegAlloc 改写(vreg→phys + spill 插入)后补第三次 liveness:为调用点
|
||||
// 存活上界提供物理级活集(改写过后的 LiveInfo 已过期,必须重算)。
|
||||
let cfg3 = oxide_cfg::build_cfg(&ir);
|
||||
let live3 = oxide_liveness::liveness(&ir, &cfg3);
|
||||
oxide_regalloc::encode_call_window(&mut ir, &live3);
|
||||
crate::compiler_debug!("compile: after regalloc {} insts", ir.insts.len());
|
||||
}
|
||||
crate::compiler_debug!("compile: done, {} instructions", ir.insts.len());
|
||||
|
||||
@@ -124,9 +124,12 @@ impl Inst {
|
||||
Self::with_ext(OpCode::COMPOUND_MEMBER_USHR, obj, val, key, &[0; IC_EXT_WORDS])
|
||||
}
|
||||
|
||||
// ── Call 系:ext = [nargs] ──
|
||||
// ── Call 系:ext = [nargs | (存活上界 << 8)] ──
|
||||
// 低 8 位 nargs;高 8 位由 RegAlloc 后 `encode_call_window` 回填调用点存活
|
||||
// 上界(窗口 regs[0..上界],0 = 全量)。emit 构造时高 8 位恒 0。
|
||||
|
||||
/// 普通函数调用:rd=callee,a=this,b=首参,ext=\[nargs\]。参数从 `first_arg` 起连续占 nargs 个寄存器。
|
||||
/// 普通函数调用:rd=callee,a=this,b=首参,ext=[nargs|(上界<<8)]。
|
||||
/// 参数从 `first_arg` 起连续占 nargs 个寄存器。
|
||||
pub fn call(callee: Operand, this: Operand, first_arg: Operand, nargs: u8) -> Self {
|
||||
Self::with_ext(OpCode::CALL, callee, this, first_arg, &[nargs as u32])
|
||||
}
|
||||
@@ -136,12 +139,12 @@ impl Inst {
|
||||
Self::with_ext(OpCode::CALL_NATIVE, callee, this, first_arg, &[nargs as u32])
|
||||
}
|
||||
|
||||
/// `new` 表达式:结果写入 `result`,构造函数为 `constructor`。
|
||||
/// `new` 表达式:结果写入 `result`,构造函数为 `constructor`。ext 编码同 `call`。
|
||||
pub fn new_expression(result: Operand, constructor: Operand, first_arg: Operand, nargs: u8) -> Self {
|
||||
Self::with_ext(OpCode::NEW_EXPRESSION, result, constructor, first_arg, &[nargs as u32])
|
||||
}
|
||||
|
||||
/// 派生类构造中的 `super(...)`:结果写入 `result`。
|
||||
/// 派生类构造中的 `super(...)`:结果写入 `result`。ext 编码同 `call`。
|
||||
pub fn super_call(result: Operand, first_arg: Operand, nargs: u8) -> Self {
|
||||
Self::with_ext(OpCode::SUPER_CALL, result, first_arg, Operand::None, &[nargs as u32])
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -87,7 +87,16 @@ impl Vm {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.push_bytecode_frame(callee, this, &args, None, None, JsValue::undefined(), FrameContinuation::None)
|
||||
self.push_bytecode_frame(
|
||||
callee,
|
||||
this,
|
||||
&args,
|
||||
None,
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::None,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -389,6 +398,8 @@ impl Vm {
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let arg_count = (ext & 0xFF) as usize;
|
||||
// ext 高 8 位 = 调用点存活上界(0 = 未编码/全量),压帧窗口按此截断。
|
||||
let call_window = (ext >> 8) as u8;
|
||||
|
||||
let Some(frame) = self.frames.last() else {
|
||||
self.raise_error_kind("ReferenceError", "super() used outside class constructor")?;
|
||||
@@ -483,7 +494,8 @@ impl Vm {
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let caller_active_reg_limit = self.active_reg_limit.max(1);
|
||||
let caller_reg_limit = self.call_window_limit(caller_active_reg_limit, call_window);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]);
|
||||
let saved_this = self.regs[254];
|
||||
@@ -517,6 +529,7 @@ impl Vm {
|
||||
return_addr: self.pc,
|
||||
function_name,
|
||||
caller_reg_limit,
|
||||
caller_active_reg_limit,
|
||||
saved_reg_offset,
|
||||
spill_offset: self.spill_stack.len() as u32,
|
||||
arguments_base: args_base,
|
||||
@@ -786,6 +799,7 @@ impl Vm {
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::None,
|
||||
0,
|
||||
)?;
|
||||
return Ok(true);
|
||||
}
|
||||
@@ -892,6 +906,7 @@ impl Vm {
|
||||
Some(new_obj_val),
|
||||
constructor,
|
||||
FrameContinuation::None,
|
||||
0,
|
||||
)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
@@ -979,6 +994,7 @@ impl Vm {
|
||||
Some(derived_this),
|
||||
new_target,
|
||||
FrameContinuation::None,
|
||||
0,
|
||||
)?;
|
||||
Ok(true)
|
||||
} else {
|
||||
|
||||
@@ -667,6 +667,7 @@ mod tests {
|
||||
return_addr: 0,
|
||||
function_name: 0,
|
||||
caller_reg_limit: 1,
|
||||
caller_active_reg_limit: 1,
|
||||
saved_reg_offset: 0,
|
||||
spill_offset: 0,
|
||||
arguments_base: 0,
|
||||
|
||||
@@ -272,6 +272,7 @@ impl SuspendedFrame {
|
||||
return_addr: f.return_addr,
|
||||
function_name: f.function_name,
|
||||
caller_reg_limit: f.caller_reg_limit,
|
||||
caller_active_reg_limit: f.caller_active_reg_limit,
|
||||
saved_reg_offset: f.saved_reg_offset,
|
||||
spill_offset: f.spill_offset,
|
||||
arguments_base: f.arguments_base,
|
||||
@@ -331,6 +332,7 @@ mod tests {
|
||||
return_addr: 7,
|
||||
function_name: 1,
|
||||
caller_reg_limit: 2,
|
||||
caller_active_reg_limit: 2,
|
||||
saved_reg_offset: 3,
|
||||
spill_offset: 4,
|
||||
arguments_base: 5,
|
||||
|
||||
@@ -170,7 +170,13 @@ pub enum FrameContinuation {
|
||||
pub struct CallFrame {
|
||||
pub return_addr: usize,
|
||||
pub function_name: u32,
|
||||
/// 压帧保存的调用方寄存器窗口长度(= save_stack 中本帧段大小)。
|
||||
/// 普通字节码调用按调用点存活上界截断;运行时发起(accessor/内联)为
|
||||
/// 调用方 `active_reg_limit` 全量。
|
||||
pub caller_reg_limit: u8,
|
||||
/// 压帧时刻调用方 `active_reg_limit`(真实值):帧恢复时据此还原,与
|
||||
/// `caller_reg_limit`(可能被存活上界截断的窗口)解耦。
|
||||
pub caller_active_reg_limit: u8,
|
||||
pub saved_reg_offset: u32,
|
||||
/// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。
|
||||
pub spill_offset: u32,
|
||||
@@ -1139,10 +1145,24 @@ impl Vm {
|
||||
self.call_bytecode_function_inline(callee, callee_obj, receiver, args)
|
||||
}
|
||||
|
||||
/// 压帧窗口上界:调用方活动寄存器与调用点存活上界取 min。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - `call_window` 为 CALL ext 高 8 位编码的存活上界;0 = 未编码(回退全量)。
|
||||
/// - 窗口至少为 1(reg 0 恒为调用结果槽)。
|
||||
pub(crate) fn call_window_limit(&self, caller_active_reg_limit: u8, call_window: u8) -> u8 {
|
||||
if call_window == 0 {
|
||||
caller_active_reg_limit
|
||||
} else {
|
||||
caller_active_reg_limit.min(call_window)
|
||||
}
|
||||
.max(1)
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn push_bytecode_frame(
|
||||
&mut self, callee: JsValue, this_value: JsValue, args: &[JsValue], construct_result_reg: Option<u8>,
|
||||
constructed_this: Option<JsValue>, new_target: JsValue, continuation: FrameContinuation,
|
||||
constructed_this: Option<JsValue>, new_target: JsValue, continuation: FrameContinuation, call_window: u8,
|
||||
) -> Result<(), String> {
|
||||
vm_trace!(
|
||||
"push_bytecode_frame: depth={}, args={}, continuation={:?}",
|
||||
@@ -1174,7 +1194,11 @@ impl Vm {
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let sub_is_arrow = self.sub_modules[sub_idx].is_arrow;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
// 窗口 = min(调用方活动寄存器, 存活上界);call_window=0 表示调用方全量
|
||||
// (运行时发起路径 / 未编码的旧模块)。恢复按窗口回拷,active_reg_limit
|
||||
// 仍还原为调用方真实值(caller_active_reg_limit)。
|
||||
let caller_active_reg_limit = self.active_reg_limit.max(1);
|
||||
let caller_reg_limit = self.call_window_limit(caller_active_reg_limit, call_window);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]);
|
||||
let saved_this = self.regs[254];
|
||||
@@ -1205,6 +1229,7 @@ impl Vm {
|
||||
return_addr: self.pc,
|
||||
function_name,
|
||||
caller_reg_limit,
|
||||
caller_active_reg_limit,
|
||||
saved_reg_offset,
|
||||
spill_offset: self.spill_stack.len() as u32,
|
||||
arguments_base: args_base,
|
||||
@@ -2213,6 +2238,7 @@ mod tests {
|
||||
return_addr: 1,
|
||||
function_name: 0,
|
||||
caller_reg_limit: 2,
|
||||
caller_active_reg_limit: 2,
|
||||
saved_reg_offset: 0,
|
||||
spill_offset: 0,
|
||||
arguments_base: 0,
|
||||
|
||||
@@ -25,6 +25,8 @@ impl Vm {
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let arg_count = (ext & 0xFF) as usize;
|
||||
// ext 高 8 位 = 调用点存活上界(0 = 未编码/全量),压帧窗口按此截断。
|
||||
let call_window = (ext >> 8) as u8;
|
||||
crate::vm_debug!("CALL rd={} this={} args={} depth={}", rd, this_reg, arg_count, self.frames.len());
|
||||
|
||||
if obj.native_fn().is_some() {
|
||||
@@ -65,6 +67,7 @@ impl Vm {
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::None,
|
||||
call_window,
|
||||
)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ impl Vm {
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let arg_count = (ext & 0xFF) as usize;
|
||||
// ext 高 8 位 = 调用点存活上界(0 = 未编码/全量),压帧窗口按此截断。
|
||||
let call_window = (ext >> 8) as u8;
|
||||
|
||||
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
|
||||
let new_obj = self.alloc_object(JsObject::new_empty(
|
||||
@@ -113,7 +115,8 @@ impl Vm {
|
||||
let sub_n_args = self.sub_modules[sub_idx].n_args as usize;
|
||||
let sub_n_registers = self.sub_modules[sub_idx].n_registers;
|
||||
let sub_param_base = self.sub_modules[sub_idx].param_base as usize;
|
||||
let caller_reg_limit = self.active_reg_limit.max(1);
|
||||
let caller_active_reg_limit = self.active_reg_limit.max(1);
|
||||
let caller_reg_limit = self.call_window_limit(caller_active_reg_limit, call_window);
|
||||
let saved_reg_offset = self.save_stack.len() as u32;
|
||||
self.save_stack.extend_from_slice(&self.regs[..caller_reg_limit as usize]);
|
||||
let saved_this = self.regs[254];
|
||||
@@ -149,6 +152,7 @@ impl Vm {
|
||||
.map(|name| self.kernel_core.perm_interner().intern(name).0)
|
||||
.unwrap_or(0),
|
||||
caller_reg_limit,
|
||||
caller_active_reg_limit,
|
||||
saved_reg_offset,
|
||||
spill_offset: self.spill_stack.len() as u32,
|
||||
arguments_base: args_base,
|
||||
|
||||
@@ -131,6 +131,7 @@ impl Vm {
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::AccessorGet { target_reg },
|
||||
0,
|
||||
)?;
|
||||
self.accessor_frame_target_reg = Some(target_reg);
|
||||
Ok(true)
|
||||
@@ -330,6 +331,7 @@ impl Vm {
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::AccessorSet,
|
||||
0,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -202,7 +202,16 @@ impl Vm {
|
||||
self.saved_immutables_stack.clear();
|
||||
self.cell_stack.clear();
|
||||
self.inline_callee = None;
|
||||
self.push_bytecode_frame(callee, this_value, args, None, None, JsValue::undefined(), FrameContinuation::None)
|
||||
self.push_bytecode_frame(
|
||||
callee,
|
||||
this_value,
|
||||
args,
|
||||
None,
|
||||
None,
|
||||
JsValue::undefined(),
|
||||
FrameContinuation::None,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn call_bytecode_function_inline(
|
||||
@@ -331,7 +340,8 @@ impl Vm {
|
||||
self.spill_stack.truncate(frame.spill_offset as usize);
|
||||
self.regs[254] = frame.saved_this;
|
||||
self.regs[255] = frame.saved_new_target;
|
||||
self.active_reg_limit = frame.caller_reg_limit;
|
||||
// active_reg_limit 还原为调用方真实值(caller_reg_limit 可能被存活上界截断)。
|
||||
self.active_reg_limit = frame.caller_active_reg_limit;
|
||||
self.pc = frame.return_addr;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
//! 调用帧窗口按存活上界拷贝的语义测试:验证压帧窗口缩小后调用方存活寄存器、
|
||||
//! this/new.target、spill 区与异常展开在各类调用路径下恢复正确。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::promise::promise_settled_value;
|
||||
use oxide_vm::vm::Vm;
|
||||
use oxide_vm::JsValue;
|
||||
|
||||
/// 执行源码并格式化顶层结果;Promise 结果取其 drain 后的 settled 值。
|
||||
fn eval(source: &str) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let program = match oxide_parser::parse(&allocator, source) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return format!("parse error: {}", e[0].message),
|
||||
};
|
||||
let module = match Compiler::new().compile(&program) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return format!("compile error: {e}"),
|
||||
};
|
||||
let mut vm = Vm::new();
|
||||
match vm.run(&module) {
|
||||
Ok(result) => format_value(&vm, result),
|
||||
Err(e) => format!("vm error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_value(vm: &Vm, val: JsValue) -> String {
|
||||
if val.is_string() {
|
||||
format!("\"{}\"", vm.lookup_str(val).unwrap_or_default())
|
||||
} else if val.is_object() {
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
if obj.is_promise_obj() {
|
||||
match promise_settled_value(obj) {
|
||||
Some((true, v)) => format_value(vm, v),
|
||||
Some((false, v)) => format!("<rejected {}>", format_value(vm, v)),
|
||||
None => "<pending>".to_string(),
|
||||
}
|
||||
} else {
|
||||
"[object]".to_string()
|
||||
}
|
||||
} else {
|
||||
format!("{val}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_basic_call_preserves_live_registers() {
|
||||
// 多值跨调用存活:sink 之后 x2..x5 仍要参与求和。
|
||||
let src = "function sink(a){ return a; } \
|
||||
function caller(){ var x1=1,x2=2,x3=3,x4=4,x5=5; \
|
||||
var r = sink(x1); return r + x2 + x3 + x4 + x5; } \
|
||||
caller()";
|
||||
assert_eq!(eval(src), "15");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_nested_calls_keep_outer_values() {
|
||||
// outer 的 base 跨 mid/inner 两层调用存活,返回后仍参与求和。
|
||||
let src = "function inner(a,b){ return a*b; } \
|
||||
function mid(x){ return inner(x+1, x+2); } \
|
||||
function outer(){ var base = 10; return mid(base) + base; } \
|
||||
outer()";
|
||||
assert_eq!(eval(src), "142");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_deep_recursion_returns_correct() {
|
||||
let src = "function fib(n){ if (n < 2) return n; return fib(n-1) + fib(n-2); } fib(18)";
|
||||
assert_eq!(eval(src), "2584");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_recursive_counter_accumulates() {
|
||||
// 每层调用点都有跨调用存活的累加器,验证窗口逐层截断不丢值。
|
||||
let src = "function sum(n){ if (n == 0) return 0; return n + sum(n-1); } sum(200)";
|
||||
assert_eq!(eval(src), "20100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_closure_upvalue_survives_calls() {
|
||||
let src = "function outer(){ var count = 0; \
|
||||
function inc(step){ count += step; return count; } \
|
||||
var a = inc(1); var b = inc(2); var c = inc(3); \
|
||||
return [a,b,c].join(','); } \
|
||||
outer()";
|
||||
assert_eq!(eval(src), "\"1,3,6\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_generator_suspend_resume() {
|
||||
let src = "function* gen(){ var a = 1; yield a; var b = yield a + 10; return a + b; } \
|
||||
var g = gen(); var r1 = g.next(); var r2 = g.next(100); var r3 = g.next(5); \
|
||||
[r1.value, r2.value, r3.value].join(',')";
|
||||
assert_eq!(eval(src), "\"1,11,6\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_generator_calls_inside_body() {
|
||||
// 生成器体内调用普通函数(生成器体调用点保持全量窗口),挂起恢复后值正确。
|
||||
let src = "function add(a,b){ return a+b; } \
|
||||
function* gen(){ var x = add(20, 22); yield x; x = add(x, 1); yield x; } \
|
||||
var g = gen(); var r1 = g.next(); var r2 = g.next(); \
|
||||
[r1.value, r2.value].join(',')";
|
||||
assert_eq!(eval(src), "\"42,43\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_async_await_chain() {
|
||||
let src = "async function f(){ var x = 40; var y = await 2; return x + y; } f()";
|
||||
assert_eq!(eval(src), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_apply_call_bind() {
|
||||
let src = "function f(a,b,c){ return [a,b,c].join('-'); } \
|
||||
f.apply(null, [1,2,3]) + '|' + f.call(null, 4,5,6) + '|' + f.bind(null,7,8)(9)";
|
||||
assert_eq!(eval(src), "\"1-2-3|4-5-6|7-8-9\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_new_expression() {
|
||||
let src = "function P(x){ this.x = x; this.y = x * 2; } \
|
||||
var p = new P(21); p.x + p.y";
|
||||
assert_eq!(eval(src), "63");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_derived_super_call() {
|
||||
let src = "class A { constructor(v){ this.v = v; } } \
|
||||
class B extends A { constructor(v){ super(v + 1); this.w = v * 10; } } \
|
||||
var b = new B(4); b.v + b.w";
|
||||
assert_eq!(eval(src), "45");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_many_live_values_large_function() {
|
||||
// 高槽存活值跨调用:callee 写入区大,调用方存活槽需在窗口内完整恢复。
|
||||
let src = "function heavy(a){ var t = a; for (var i = 0; i < 100; i++){ t += i; } return t; } \
|
||||
function caller(){ \
|
||||
var k1=1,k2=2,k3=3,k4=4,k5=5,k6=6,k7=7,k8=8,k9=9,k10=10; \
|
||||
var r = heavy(1); \
|
||||
return k1+k2+k3+k4+k5+k6+k7+k8+k9+k10 + r; } \
|
||||
caller()";
|
||||
assert_eq!(eval(src), "5006");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_accessor_getter_and_setter() {
|
||||
// accessor 压帧走全量窗口路径,getter/setter 返回后写回目标寄存器正确。
|
||||
let src = "var obj = { _v: 0, get v(){ return this._v; }, set v(x){ this._v = x * 2; } }; \
|
||||
obj.v = 21; obj.v";
|
||||
assert_eq!(eval(src), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_throw_unwind_restores_frame() {
|
||||
let src = "function boom(){ throw 'err'; } \
|
||||
function caller(){ var keep = 99; try { boom(); } catch(e) { keep += 1; } return keep; } \
|
||||
caller()";
|
||||
assert_eq!(eval(src), "100");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_tail_call_through_native_dispatcher() {
|
||||
// bound 函数尾调用返回字节码函数,窗口恢复与结果交付正确。
|
||||
let src = "function add(a,b){ return a + b; } \
|
||||
var bound = add.bind(null, 30); bound(12)";
|
||||
assert_eq!(eval(src), "42");
|
||||
}
|
||||
|
||||
/// 本测试所用小程序中带 1 个扩展字的 opcode 集合(推进扫描用)。
|
||||
fn ext_words(op: oxide_bytecode::opcode::OpCode) -> usize {
|
||||
use oxide_bytecode::opcode::OpCode;
|
||||
match op {
|
||||
OpCode::SPILL
|
||||
| OpCode::UNSPILL
|
||||
| OpCode::CALL
|
||||
| OpCode::CALL_NATIVE
|
||||
| OpCode::NEW_EXPRESSION
|
||||
| OpCode::SUPER_CALL => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_window_upper_bound_encoded_into_bytecode() {
|
||||
// 编译产物级断言:CALL 的 ext 高 8 位编码了调用点存活上界且小于调用方
|
||||
// n_registers(窗口确实被截断,非停留在全量路径);nargs 低 8 位不变。
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(
|
||||
&allocator,
|
||||
"function f(x){ return x + 1; } \
|
||||
function g(){ var a = 1; var b = 2; var c = 3; var d = 4; return f(a) + b + c + d; } \
|
||||
g()",
|
||||
)
|
||||
.unwrap();
|
||||
let module = Compiler::new().compile(&program).unwrap();
|
||||
let bc = &module.bytecode;
|
||||
let mut i = 0;
|
||||
let mut call_ext = None;
|
||||
while i < bc.len() {
|
||||
let op = oxide_bytecode::opcode::opcode(bc[i]);
|
||||
if op == oxide_bytecode::opcode::OpCode::CALL {
|
||||
call_ext = Some(bc[i + 1]);
|
||||
i += 2;
|
||||
} else {
|
||||
i += 1 + ext_words(op);
|
||||
}
|
||||
}
|
||||
let ext = call_ext.expect("脚本中应有 CALL 指令");
|
||||
assert_eq!(ext & 0xFF, 0, "g() 无实参,nargs 低 8 位保持 0");
|
||||
let upper = (ext >> 8) & 0xFF;
|
||||
assert!(upper > 0, "调用点存活上界应被编码进 ext 高 8 位");
|
||||
assert!(
|
||||
upper <= module.n_registers as u32,
|
||||
"存活上界 {upper} 不应超过调用方 n_registers {}",
|
||||
module.n_registers
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user