diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 3cd2514f..b7ec1c10 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -384,34 +384,48 @@ impl Vm { /// 读取当前闭包的 upvalue:命中 cell 判初始化后取值,未命中委托惰性建 cell。 /// /// # 步骤 - /// 1. 取 imm16 为 upvalue 下标,从当前 callee 的 `upvalues` 命中 cell。 + /// 1. 取 imm16 为 upvalue 下标;缓存命中(键 = 当前 callee 位级相等)直接 + /// 解引用缓存切片指针,未命中从 callee 重算并填缓存。 /// 2. 命中且非空:未初始化(TDZ)抛 ReferenceError,否则值写入 `regs[rd]`。 /// 3. 未命中/空槽(`CREATE_CLOSURE` 早于 `MAKE_CELL`):委托 /// `lazy_create_upvalue_cell` 建 cell。 /// /// # 边界与前提 /// - `uv_idx` 越界或当前无 callee 时同样走惰性路径,最终回退 undefined。 - /// - /// # 注意事项 - /// - 手写 IR 保留指令(同 `dispatch_make_cell`)。 - #[allow(dead_code)] + /// - 缓存指针在键匹配期间恒有效:callee 对象是 GC 根,`upvalues` Box + /// 创建后不替换。 pub(crate) fn dispatch_load_upvalue(&mut self, rd: usize, instr: u32) -> Result<(), String> { let uv_idx = opcode::imm16(instr) as usize; - if let Some(callee) = self.current_callee() { - if callee.is_object() { - let obj = unsafe { &*callee.as_js_object_ptr() }; - let upvals = obj.upvalues_slice(); - if uv_idx < upvals.len() { - let cell = upvals[uv_idx]; - if !cell.is_null() { - let c = unsafe { &*cell }; - if !c.is_initialized() { - return self - .raise_error_kind("ReferenceError", "Cannot access variable before initialization"); - } - self.regs[rd] = c.value; - return Ok(()); + let callee = self.current_callee(); + // 命中:键位级相等保证缓存切片指针对应当前 callee 对象(对象为 GC 根、 + // Box 创建后不替换),直接解引用安全。 + let mut upvals: Option<&[*mut Cell]> = None; + if let Some((key, ptr)) = self.upvalue_cache { + if callee == Some(key) { + upvals = Some(unsafe { &*ptr }); + } + } + // 未命中:重算切片指针并填缓存(含空切片,免每次 null 判定)。 + if upvals.is_none() { + if let Some(callee) = callee { + if callee.is_object() { + let obj = unsafe { &*callee.as_js_object_ptr() }; + let slice = obj.upvalues_slice(); + self.upvalue_cache = Some((callee, slice as *const [*mut Cell])); + upvals = Some(slice); + } + } + } + if let Some(upvals) = upvals { + if uv_idx < upvals.len() { + let cell = upvals[uv_idx]; + if !cell.is_null() { + let c = unsafe { &*cell }; + if !c.is_initialized() { + return self.raise_error_kind("ReferenceError", "Cannot access variable before initialization"); } + self.regs[rd] = c.value; + return Ok(()); } } } diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index eb1d37d4..9f49541e 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -80,6 +80,9 @@ impl SuspendedFrame { pub fn save_from(&mut self, vm: &mut Vm, callee: JsValue) -> Result<(), String> { let frame = vm.frames.pop().ok_or_else(|| "frame missing on suspend".to_string())?; self.frame = Some(frame); + // 挂起弹帧不经 restore_frame:失效 upvalue 切片缓存(值相等键已自愈, + // 此为双保险)。 + vm.upvalue_cache = None; *self.regs = vm.regs; self.pc = vm.pc; self.bytecode = std::mem::take(&mut vm.bytecode); diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index ce105e25..e7bce928 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -317,6 +317,8 @@ impl Vm { strict: sub_is_strict, continuation, }); + // 压帧后 callee 换为被调函数:失效 upvalue 切片缓存。 + self.upvalue_cache = None; self.pc = 0; self.bytecode = sub_bytecode; diff --git a/crates/oxide_vm/src/vm/inline.rs b/crates/oxide_vm/src/vm/inline.rs index 68de74bd..c15e2c0e 100644 --- a/crates/oxide_vm/src/vm/inline.rs +++ b/crates/oxide_vm/src/vm/inline.rs @@ -40,6 +40,8 @@ pub(crate) struct InlineSyncState { pub(crate) spill_stack: Vec, pub(crate) cell_stack: Vec>, pub(crate) inline_callee: Option, + /// upvalue 切片指针缓存(与 `inline_callee` 同生命周期,随本快照保存/恢复)。 + pub(crate) upvalue_cache: Option<(JsValue, *const [*mut Cell])>, /// 三个内嵌 dispatch 调度标志(`Vm::generator_dispatch` / `async_dispatch` / /// `construct_dispatch`)的属主快照。save 时记录外层值并清零 VM 侧、restore /// 时写回:嵌套 state-swap 调用不得继承外层调度上下文,否则其内部构造帧 diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index 6abd5ebb..ee5524c8 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -255,6 +255,11 @@ pub struct Vm { /// frames 为空(inline 隔离状态)时由此取闭包 upvalues。嵌套 inline 由 /// InlineSyncState 保存/恢复。 pub(crate) inline_callee: Option, + /// upvalue 切片指针缓存:键 = 当前 callee 值(位级相等),值 = 该 callee 的 + /// `upvalues` 切片指针(含空切片情形)。LOAD_UPVALUE 命中时省 `is_object` + + /// 对象解引用 + 切片解引用。压帧/弹帧/挂起/run 边界失效;键匹配期间指针恒 + /// 有效——callee 对象是 GC 根,`upvalues` Box 创建后不替换。 + pub(crate) upvalue_cache: Option<(JsValue, *const [*mut Cell])>, /// inline 目标函数的严格模式标志(与 `inline_callee` 同生命周期, /// InlineSyncState 保存/恢复):内联执行期间帧表被隔离,写路径的 /// strict/sloppy 判定取此值而非帧标志。 diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index f5cadc48..76ad7d6f 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -101,6 +101,9 @@ macro_rules! inline_save_field { ($recv:ident, $window_regs:ident, inline_callee, opt_copy) => { $recv.inline_callee }; + ($recv:ident, $window_regs:ident, upvalue_cache, copy) => { + $recv.upvalue_cache + }; ($recv:ident, $window_regs:ident, generator_dispatch, flag_zero) => {{ let prev = $recv.generator_dispatch; $recv.generator_dispatch = false; @@ -208,6 +211,9 @@ macro_rules! inline_restore_field { ($recv:ident, $saved:ident, inline_callee, opt_copy) => { $recv.inline_callee = $saved.inline_callee }; + ($recv:ident, $saved:ident, upvalue_cache, copy) => { + $recv.upvalue_cache = $saved.upvalue_cache + }; ($recv:ident, $saved:ident, generator_dispatch, flag_zero) => { $recv.generator_dispatch = $saved.generator_dispatch }; @@ -271,6 +277,7 @@ macro_rules! inline_core_fields { (spill_stack, move_field), // V M (cell_stack, move_field), // V M (inline_callee, opt_copy), // V M + (upvalue_cache, copy), // V (generator_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 (async_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 (construct_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 @@ -523,6 +530,8 @@ impl Vm { frame.return_addr, frame.caller_reg_limit ); + // 弹帧后 callee 换回调用方:失效 upvalue 切片缓存。 + self.upvalue_cache = None; if let Some(saved_bc) = self.saved_bytecode_stack.pop() { self.bytecode = saved_bc; } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index acbae004..439b0ad4 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -100,6 +100,7 @@ impl Vm { inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, + upvalue_cache: None, inline_strict: false, inline_frames_base: 0, top_level_strict: false, @@ -236,6 +237,7 @@ impl Vm { inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, + upvalue_cache: None, inline_strict: false, inline_frames_base: 0, top_level_strict: false, @@ -550,6 +552,9 @@ impl Vm { self.reentry_hops = 0; // inline 窗口缓冲池内容为已废弃快照,跨 run/reset 不保留。 self.inline_reg_pool = None; + // upvalue 切片缓存是执行期状态:键为 callee 值,跨 run/reset 后缓存的 + // 切片指针失效(callee 对象可能已回收)。 + self.upvalue_cache = None; // 微任务队列是执行期状态:跨 run 不保留。 self.job_queue.clear(); // Atomics waiter 表同属执行期状态:未结算 promise 清位后无强根。