diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 7618f914..7239a70c 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -105,11 +105,13 @@ impl Vm { pub(crate) fn dispatch_create_closure(&mut self, rd: usize, instr: u32) -> Result<(), String> { let sub_idx = opcode::imm16(instr) as u32; vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx); - // 闭包按当前 run 的平表解析(CREATE_CLOSURE 只在本 run 装载的模块内发射)。 - let table = self.current_table(); + // 指令操作数是发射期 flat_id,口径为执行帧字节码所属代际的平表:跨 run + // 调用在当前代际上下文执行旧代字节码,须按执行帧的表代际解析,按当前 + // 代际会悬空(表更短)或命中他代异模块(表足够长)。 + let table = self.active_table(); if sub_idx == 0 || (sub_idx as usize) >= table.modules.len() { - // 逃逸闭包(函数对象在定义模块之外被创建)时 sub_idx 相对定义模块, - // 超出当前平表上下文——按运行时错误处理而非索引越界 panic。 + // sub_idx 超出执行帧代际平表:定义模块缺失或发射期编号错位——按 + // 运行时错误处理而非索引越界 panic。 return Err(format!( "CREATE_CLOSURE: sub_module_index {} out of bounds (max {})", sub_idx, @@ -124,8 +126,11 @@ impl Vm { let upvalue_captures = sub.upvalue_captures.clone(); let function_name = sub.function_name.clone(); let function_length = sub.function_length; + // 新闭包盖执行帧代际:sub_idx 与 gen 同域,跨 run 调用时新闭包仍解析 + // 回定义模块所在的原表。 let result = self.create_function_object( sub_idx, + self.active_table_gen, is_arrow, is_class_constructor, is_derived_constructor, @@ -507,10 +512,11 @@ impl Vm { } else if super_obj.sub_module_index() > 0 { let sub_idx = super_obj.sub_module_index() as usize; if self.callee_module(super_obj).is_none() { + // 上界取 super 目标自身代际平表长度(跨 run 调用时可异于当前代际)。 return Err(format!( "SUPER_CALL: sub_module_index {} out of bounds (max {})", sub_idx, - self.current_table().modules.len() + self.tables.get(&super_obj.table_gen()).map(|t| t.modules.len()).unwrap_or(0) )); } // 收敛到统一压帧入口:this = derived_this(super() 把实例交予父构造器), diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index f9363734..85674bf1 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -107,13 +107,19 @@ impl SuspendedFrame { } /// 恢复进 VM:写回各栈段、在途异常/完成,按 callee 记录的表代际重激活 - /// immutables。 + /// immutables,并把执行键维度(表代际 / flat_id)还原到被恢复模块。 /// /// # 边界 /// - `callee` 须为挂起函数对象值(状态盒 `callee` 槽):代际按对象创建期 /// 记录解析,存活函数对象按代际保活其表,跨 run 恢复命中同一张表。 /// - callee 非对象(gen 0 哨兵)、代际表已被回收或下标越界返回 Err, /// 调用方须按各自路径回滚并报错(保持现有三处行为)。 + /// + /// # 副作用 + /// - `active_table_gen` / `active_flat_id` 还原为被恢复模块的 (代际, + /// flat_id):恢复体后续发射的标签模板须按本模块 (代际, flat_id) 命中 + /// 模板缓存——不还原则同顶层连续恢复两代挂起体撞调用方侧同键,静默 + /// 取回他侧模板对象。 pub fn restore_into(&mut self, vm: &mut Vm, callee: JsValue) -> Result<(), String> { vm.regs = *self.regs; vm.pc = self.pc; @@ -131,6 +137,9 @@ impl SuspendedFrame { Some(c) => vm.activate_immutables(gen, self.sub_idx as usize, &c), None => return Err("suspended state module table is no longer available".into()), } + // 模板缓存键维度随模块恢复:与 activate_immutables 同位还原。 + vm.active_table_gen = gen; + vm.active_flat_id = self.sub_idx; vm.active_reg_limit = self.active_reg_limit; vm.root_reg_limit = self.root_reg_limit; vm.frames.clear(); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 1c1759ab..57e00f74 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -800,16 +800,23 @@ impl Vm { } } - /// 当前代际的子模块平表(动态扩表与测试注入的目标)。 - pub(crate) fn current_table(&self) -> &TableGen { - self.tables.get(&self.current_gen).expect("当前代际表构造期已预登记") - } - - /// 当前代际的子模块平表(可变)。 + /// 当前代际的子模块平表(可变,动态扩表与测试注入的目标)。 pub(crate) fn current_table_mut(&mut self) -> &mut TableGen { self.tables.get_mut(&self.current_gen).expect("当前代际表构造期已预登记") } + /// 当前执行帧所属代际的子模块平表:按帧切换时记录的 `active_table_gen` + /// 解析(压帧 / inline 入口 / run 顶层 / 挂起恢复置位,弹帧还原),跨 run + /// 调用时可异于 `current_gen`——帧内指令操作数是帧字节码所属代际平表里的 + /// flat_id,须按该代际解析。 + /// + /// # 边界与前提 + /// - 执行帧代际表恒在注册表中:顶层即当前代际(恒保留),帧 / inline 的 + /// 代际由存活 callee 函数对象保活,挂起恢复已按 callee 对象验证在位。 + pub(crate) fn active_table(&self) -> &TableGen { + self.tables.get(&self.active_table_gen).expect("执行帧的代际表须在注册表中") + } + /// 函数对象 `sub_module_index` 指向的子模块条目:按对象自身记录的表代际 /// 解析平表后按下标定位。 /// @@ -2672,7 +2679,7 @@ impl Vm { // 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。 table.immutables.resize(table.modules.len(), OnceLock::new()); - let func_val = self.create_function_object(base, false, false, false, false); + let func_val = self.create_function_object(base, self.current_gen, false, false, false, false); let func_obj = unsafe { &mut *func_val.as_js_object_ptr() }; let length_si = self.kernel_core.perm_interner().intern("length").0; let name_si = self.kernel_core.perm_interner().intern("name").0; @@ -2719,7 +2726,7 @@ impl Vm { Arc::make_mut(&mut table.modules).extend(added); // 平表变长后同步扩容常量缓存,否则激活新模块常量时越界 panic。 table.immutables.resize(table.modules.len(), OnceLock::new()); - Ok(self.create_function_object(base, false, false, false, false)) + Ok(self.create_function_object(base, self.current_gen, false, false, false, false)) } } @@ -2752,6 +2759,11 @@ fn rehome_subtree(module: &CompiledModule, base: u32, out: &mut Vec &TableGen { + self.tables.get(&self.current_gen).expect("当前代际表构造期已预登记") + } + /// 测试用:整表替换当前代际的子模块平表并同步常量缓存槽位(绕过 run() 装载, /// 直接注入模块表后压帧/内联调用)。 pub(crate) fn install_module_table_for_test(&mut self, modules: Arc>>) { @@ -3205,7 +3217,7 @@ mod tests { vm.active_reg_limit = 254; vm.regs[253] = JsValue::int(42); - let callee = vm.create_function_object(1, false, false, false, false); + let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false); let callee_obj = unsafe { &*callee.as_js_object_ptr() }; let result = vm .call_bytecode_function_inline(callee, callee_obj, JsValue::undefined(), &[]) @@ -3229,7 +3241,7 @@ mod tests { vm.regs[253] = JsValue::int(7); vm.regs[254] = JsValue::int(8); - let inner_callee = vm.create_function_object(1, false, false, false, false); + let inner_callee = vm.create_function_object(1, vm.current_gen, false, false, false, false); let outer_native = native_function(&mut vm, native_nested_inline_254); let result = vm .call_function_sync(outer_native, JsValue::int(99), &[inner_callee]) @@ -3258,7 +3270,7 @@ mod tests { vm.regs[1] = JsValue::int(10); vm.regs[2] = JsValue::int(20); - let callee = vm.create_function_object(1, false, false, false, false); + let callee = vm.create_function_object(1, vm.current_gen, false, false, false, false); vm.push_bytecode_frame( callee, JsValue::undefined(), @@ -3300,7 +3312,7 @@ mod tests { // 调用方实参区 regs[1..3):arg0=10, arg1=20;regs[2] 同时是 callee 形参槽(param_base=2) vm.regs[1] = JsValue::int(10); vm.regs[2] = JsValue::int(20); - vm.regs[5] = vm.create_function_object(1, false, false, false, false); + vm.regs[5] = vm.create_function_object(1, vm.current_gen, false, false, false, false); vm.dispatch_new_expression(0, 5, 1).expect("NEW 压帧成功"); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 44e90daa..fe61bcf3 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -105,11 +105,12 @@ impl Vm { let sub = match self.callee_module(ctor_obj) { Some(m) => m, None => { + // 上界取构造器自身代际平表长度(跨 run 调用时可异于当前代际)。 return Err(format!( "NEW_EXPRESSION: sub_module_index {} out of bounds (max {})", sub_idx, - self.current_table().modules.len() - )) + self.tables.get(&ctor_obj.table_gen()).map(|t| t.modules.len()).unwrap_or(0) + )); } }; // 生成器函数不是构造器:`new g()` 抛 TypeError。 diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index dda4ce06..660d7086 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -689,15 +689,20 @@ impl Vm { /// 为 BytecodeFunc 常量创建函数 JsObject。 /// 当 `is_arrow` 为 true 时,捕获当前 `this`(regs[254]),供调用时词法 this 绑定。 + /// + /// # 边界与前提 + /// - `gen` 为函数对象所属表代际:`sub_idx` 的口径与对象头的 `table_gen` + /// 盖写均以它为准(闭包创建传执行帧代际,动态构造传当前代际——动态扩表 + /// 追加进当前代际平表)。 pub(crate) fn create_function_object( - &mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool, + &mut self, sub_idx: u32, gen: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool, needs_home_object: bool, ) -> JsValue { // 生成器函数对象:原型为 %GeneratorFunction.prototype%(constructor 链解析到 // "GeneratorFunction"),且不像普通函数那样拥有 `prototype` 属性。 - // 按当前代际平表解析:函数对象恒在本次 run 内创建(CREATE_CLOSURE / - // 动态构造),sub_idx 口径即当前代际表。 - let table = self.current_table(); + // 标志按函数对象所属代际平表解析:跨 run 调用时执行帧代际可异于当前代际, + // sub_idx 口径与 gen 同域。 + let table = self.tables.get(&gen).expect("函数对象的代际表须在注册表中"); let is_generator = table.modules.get(sub_idx as usize).map(|m| m.is_generator).unwrap_or(false); let is_async = table.modules.get(sub_idx as usize).map(|m| m.is_async).unwrap_or(false); // 异步生成器(`async function*`)函数对象:原型为 %AsyncGeneratorFunction.prototype%。 @@ -716,7 +721,7 @@ impl Vm { obj.set_sub_module_index(sub_idx); // 记录创建期表代际:调用点按 (table_gen, sub_module_index) 解析子模块 // 平表,跨 run 换表后存活函数仍命中原表(注册表按代际保活)。 - obj.set_table_gen(self.current_gen); + obj.set_table_gen(gen); obj.set_class_constructor(is_class_constructor); obj.set_derived_constructor(is_derived_constructor); let _ = needs_home_object; diff --git a/crates/oxide_vm/tests/global_var_binding_tests.rs b/crates/oxide_vm/tests/global_var_binding_tests.rs index 7ff06853..095f5431 100644 --- a/crates/oxide_vm/tests/global_var_binding_tests.rs +++ b/crates/oxide_vm/tests/global_var_binding_tests.rs @@ -278,6 +278,26 @@ fn dynamic_function_survives_reset_and_calls() { )); } +/// 跨 run 嵌套闭包:run1 的 f 体内定义嵌套函数 g(f 帧内 CREATE_CLOSURE 按 +/// run1 代际平表发射 flat_id);reset 后 run2 调用 f——嵌套闭包须按执行帧 +/// 代际解析 flat 表,返回闭包的 name/调用值须命中 f 模块的字节码。 +#[test] +fn cross_run_call_nested_closure_resolves_frame_generation() { + // 当前代际平表长度 ≥ 旧代索引:按当前代际解析会静默命中他模块 + // (错 name / 错字节码),值断言与 name 断言同时捕获。 + assert!(eval_two_phases( + "function f(){ function g(){ return 41 + 1; } return g; } globalThis.f = f; 0", + "var filler_a = function(){}; var filler_b = function(){}; \ + var h = globalThis.f(); h() === 42 && h.name === 'g'", + )); + // 当前代际平表更短(run2 脚本无顶层函数):旧代索引越界,合法代码不得 + // 转显式 Err。 + assert!(eval_two_phases( + "function f(){ function g(){ return 41 + 1; } return g; } globalThis.f = f; 0", + "var h = globalThis.f(); h() === 42 && h.name === 'g'", + )); +} + /// Map 原生盒按键值直插不经写屏障:epoch 值跨 reset 由边界晋升克隆进 /// session。run2 先分配新 epoch 对象覆写旧内存再读盒内值——未晋升则读到 /// 覆写后的垃圾。