From b3255faa46a6955142dfaea56596bfe6a0d00126 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Mon, 28 Sep 2026 17:24:10 +0000 Subject: [PATCH] =?UTF-8?q?perf:=20=E5=8E=8B=E5=B8=A7=E6=97=B6=E5=9B=BA?= =?UTF-8?q?=E5=8C=96=20callee=20upvalue=20=E8=A1=A8=EF=BC=8C=E7=83=AD?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=85=8D=E6=AF=8F=E6=8C=87=E4=BB=A4=E9=87=8D?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_types/src/object.rs | 5 ++ crates/oxide_vm/src/dispatch/call.rs | 99 ++++++++------------- crates/oxide_vm/src/session_gc/tests.rs | 1 + crates/oxide_vm/src/suspended.rs | 14 ++- crates/oxide_vm/src/vm/call.rs | 10 ++- crates/oxide_vm/src/vm/frames.rs | 8 ++ crates/oxide_vm/src/vm/inline.rs | 5 +- crates/oxide_vm/src/vm/mod.rs | 10 +-- crates/oxide_vm/src/vm_runtime.rs | 22 +++-- crates/oxide_vm/src/vm_support.rs | 10 +-- crates/oxide_vm/src/vm_support/tests.rs | 1 + crates/oxide_vm/tests/generator_gc_tests.rs | 13 +++ 12 files changed, 110 insertions(+), 88 deletions(-) diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 441fc974..114be645 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -504,6 +504,8 @@ impl JsObject { /// 深拷贝本对象到 session epoch:复制属性向量与元数据,标记为新 session 对象。 /// /// 用于把持久对象快照进当前调用上下文,修改不反向传播到源对象。 + /// `upvalues` 指针别名共享(非深拷):调用帧固化的 upvalue 表跨 GC 晋升 + /// 恒有效依赖此不变量。 pub fn clone_for_session_epoch(&self) -> Self { let hash_props = self .hash_props_vec() @@ -626,6 +628,9 @@ impl JsObject { } /// 替换 upvalue cell 列表(释放旧列表)。 + /// + /// 仅闭包创建时调用一次;调用帧在压帧时固化的 upvalue 表(`CallFrame.upvalues`) + /// 跨 GC 恒有效以"本 Box 创建后不替换"为前提。 pub fn set_upvalues(&mut self, v: Box>) { if !self.upvalues.is_null() { unsafe { diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index b7ec1c10..0247c67f 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -384,49 +384,30 @@ impl Vm { /// 读取当前闭包的 upvalue:命中 cell 判初始化后取值,未命中委托惰性建 cell。 /// /// # 步骤 - /// 1. 取 imm16 为 upvalue 下标;缓存命中(键 = 当前 callee 位级相等)直接 - /// 解引用缓存切片指针,未命中从 callee 重算并填缓存。 + /// 1. 取 imm16 为 upvalue 下标;直接索引活动镜像表(当前执行函数的表, + /// 非闭包为 null 空切片)。 /// 2. 命中且非空:未初始化(TDZ)抛 ReferenceError,否则值写入 `regs[rd]`。 /// 3. 未命中/空槽(`CREATE_CLOSURE` 早于 `MAKE_CELL`):委托 /// `lazy_create_upvalue_cell` 建 cell。 /// /// # 边界与前提 /// - `uv_idx` 越界或当前无 callee 时同样走惰性路径,最终回退 undefined。 - /// - 缓存指针在键匹配期间恒有效:callee 对象是 GC 根,`upvalues` Box + /// - 镜像指针跨 GC 恒有效: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; - 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(()); + // 活动镜像是当前执行函数的表(压帧/内联/弹帧/挂起恢复边界置位): + // 直接解引用安全(callee 是 GC 根、Box 创建后不替换)。 + let upvals = unsafe { &*self.active_upvalues }; + 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(()); } } // cell 尚未创建(hoisting 顺序:CREATE_CLOSURE 先于 MAKE_CELL), @@ -470,7 +451,7 @@ impl Vm { /// /// # 步骤 /// 1. b 槽为 upvalue 下标,源值为 `regs[a]`;`rd` 复用为 const 标志。 - /// 2. 命中闭包 `upvalues` 且非空:未初始化(TDZ)抛 ReferenceError,const 标志非 0 + /// 2. 命中活动镜像表且非空:未初始化(TDZ)抛 ReferenceError,const 标志非 0 /// 抛 TypeError,否则写值置 initialized。 /// 3. 槽位为空(尚未建 cell)以源值新建已初始化 cell;下标越界或无 callee 静默返回。 /// @@ -484,36 +465,30 @@ impl Vm { let const_flag = rd; let uv_idx = b; let src_val = self.regs[a]; - if let Some(callee) = self.current_callee() { - if callee.is_object() { - let obj = unsafe { &mut *callee.as_js_object_ptr() }; - let upvals = obj.upvalues_slice_mut(); - if uv_idx < upvals.len() { - if !upvals[uv_idx].is_null() { - unsafe { - let cell = &mut *upvals[uv_idx]; - // TDZ 写检查:cell 未初始化(捕获绑定声明点前)写抛 ReferenceError。 - if !cell.is_initialized() { - self.raise_error_kind( - "ReferenceError", - "Cannot access variable before initialization", - )?; - return Ok(()); - } - // const guard:TDZ 检查后 cell 必已初始化(存在性判定,与值无关), - // const 初始值为 undefined 时再赋值同样抛。 - if const_flag != 0 { - self.raise_error_kind("TypeError", "Assignment to constant variable")?; - return Ok(()); - } - cell.value = src_val; - cell.set_initialized(true); - } - vm_debug!("STORE_UPVALUE len={} wrote existing", upvals.len()); - } else { - upvals[uv_idx] = self.gc_state.alloc_cell(src_val, true); + // 活动镜像是当前执行函数的表(非闭包为 null 空切片):经 *mut 转可变 + // 切片,与 upvalues_slice_mut 同口径(callee 是 GC 根、Box 单所有权)。 + let upvals = unsafe { &mut *(self.active_upvalues as *mut [*mut Cell]) }; + if uv_idx < upvals.len() { + if !upvals[uv_idx].is_null() { + unsafe { + let cell = &mut *upvals[uv_idx]; + // TDZ 写检查:cell 未初始化(捕获绑定声明点前)写抛 ReferenceError。 + if !cell.is_initialized() { + self.raise_error_kind("ReferenceError", "Cannot access variable before initialization")?; + return Ok(()); } + // const guard:TDZ 检查后 cell 必已初始化(存在性判定,与值无关), + // const 初始值为 undefined 时再赋值同样抛。 + if const_flag != 0 { + self.raise_error_kind("TypeError", "Assignment to constant variable")?; + return Ok(()); + } + cell.value = src_val; + cell.set_initialized(true); } + vm_debug!("STORE_UPVALUE len={} wrote existing", upvals.len()); + } else { + upvals[uv_idx] = self.gc_state.alloc_cell(src_val, true); } } Ok(()) diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 21bcabab..576870c5 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -107,6 +107,7 @@ fn gc_roots_contains_registers_frames_and_root_roots() { is_derived_constructor: false, super_called: false, continuation: FrameContinuation::None, + upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), }); vm.save_stack.push(JsValue::from_js_object(frame_session)); diff --git a/crates/oxide_vm/src/suspended.rs b/crates/oxide_vm/src/suspended.rs index 83f0ab70..ccc6805b 100644 --- a/crates/oxide_vm/src/suspended.rs +++ b/crates/oxide_vm/src/suspended.rs @@ -83,9 +83,8 @@ 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; + // 挂起弹帧不经 restore_frame:帧已弹入状态盒,活动镜像清零(null 空切片)。 + vm.active_upvalues = std::ptr::slice_from_raw_parts(std::ptr::null(), 0); *self.regs = vm.regs; self.pc = vm.pc; self.bytecode = std::mem::take(&mut vm.bytecode); @@ -150,9 +149,16 @@ impl SuspendedFrame { vm.active_reg_limit = self.active_reg_limit; vm.root_reg_limit = self.root_reg_limit; vm.frames.clear(); + // 帧弹回前取被恢复函数的表,弹回后置回活动镜像(帧空即顶层空切片)。 + let up = self + .frame + .as_ref() + .map(|f| f.upvalues) + .unwrap_or(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); if let Some(frame) = self.frame.take() { vm.frames.push(frame); } + vm.active_upvalues = up; vm.spill_stack = std::mem::take(&mut self.spill_stack); vm.save_stack = std::mem::take(&mut self.save_stack); vm.cell_stack = std::mem::take(&mut self.cell_stack); @@ -350,6 +356,7 @@ impl SuspendedFrame { super_called: f.super_called, strict: f.strict, continuation: f.continuation, + upvalues: f.upvalues, }), spill_stack: self.spill_stack.iter().copied().map(&mut rewrite).collect(), save_stack: self.save_stack.iter().copied().map(&mut rewrite).collect(), @@ -447,6 +454,7 @@ mod tests { super_called: false, strict: false, continuation: FrameContinuation::None, + upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), } } diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index b8b94d30..5cbd8993 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use oxide_runtime_api::NativeResult; -use oxide_types::object::JsObject; +use oxide_types::object::{Cell, JsObject}; use oxide_types::value::JsValue; use smallvec::SmallVec; @@ -301,6 +301,9 @@ impl Vm { .map(|name| self.kernel_core.perm_interner().intern(name).0) .unwrap_or(0); + // 压帧时固化 callee 的 upvalue 表(非闭包为 null 空切片):热路径免 + // 每指令重查 callee 对象,指针跨 GC 恒有效(callee 是 GC 根)。 + let up = obj.upvalues_slice() as *const [*mut Cell]; self.frames.push(CallFrame { return_addr: self.pc, function_name, @@ -319,9 +322,10 @@ impl Vm { super_called: false, strict: sub_is_strict, continuation, + upvalues: up, }); - // 压帧后 callee 换为被调函数:失效 upvalue 切片缓存。 - self.upvalue_cache = None; + // 压帧后活动镜像切到被调函数的表(非闭包为 null 空切片)。 + self.active_upvalues = up; self.pc = 0; self.bytecode = sub_bytecode; diff --git a/crates/oxide_vm/src/vm/frames.rs b/crates/oxide_vm/src/vm/frames.rs index c2d33f4f..11258fd7 100644 --- a/crates/oxide_vm/src/vm/frames.rs +++ b/crates/oxide_vm/src/vm/frames.rs @@ -1,6 +1,7 @@ //! 调用帧与完成值类型:帧续行方式、压帧实参来源、调用帧、for-in 迭代游标、 //! try/catch/finally 处理记录,以及 break/continue/return 完成值(含访问器)。 +use oxide_types::object::Cell; use oxide_types::value::JsValue; /// 调用帧被挂起后,恢复时需要继续的执行方式。 @@ -75,6 +76,13 @@ pub struct CallFrame { /// 属性写失败时据此分派:严格模式抛 TypeError,sloppy 静默 no-op。 pub strict: bool, pub continuation: FrameContinuation, + /// 被调函数 upvalue cell 表(压帧时从 callee 对象固化,非闭包为 null 空切片)。 + /// + /// SAFETY 前提:`callee` 是 GC 根(根遍历/重写/深拷贝三处均遍历该字段), + /// 其 `upvalues` Box 创建后不替换(`set_upvalues` 仅 CREATE_CLOSURE 调用一次, + /// 晋升克隆共享同一 Box),故指针跨 GC 恒有效;写路径经 `*mut` 转可变切片 + /// 与 `upvalues_slice_mut` 同口径(Box 单所有权)。 + pub upvalues: *const [*mut Cell], } /// 一次 `for-in` 迭代的游标:已收集的 key 列表与当前下标。 diff --git a/crates/oxide_vm/src/vm/inline.rs b/crates/oxide_vm/src/vm/inline.rs index c15e2c0e..5c22c58a 100644 --- a/crates/oxide_vm/src/vm/inline.rs +++ b/crates/oxide_vm/src/vm/inline.rs @@ -40,8 +40,9 @@ 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])>, + /// 当前执行函数的 upvalue cell 表镜像(与 `inline_callee` 同生命周期, + /// 随本快照保存/恢复)。 + pub(crate) active_upvalues: *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 b038d0eb..50da538d 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -270,11 +270,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])>, + /// 当前执行函数的 upvalue cell 表(活动镜像,与 `CallFrame.upvalues` 同型: + /// 胖指针,非闭包为 null 空切片)。压帧/内联/弹帧/挂起恢复四个边界置位, + /// run 边界清零;指针恒有效——callee 对象是 GC 根,`upvalues` Box 创建后不 + /// 替换,LOAD/STORE_UPVALUE 热路径直接按下标取 cell。 + pub(crate) active_upvalues: *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 5b081f0c..89afa104 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -4,7 +4,7 @@ use oxide_bytecode::module::CompiledModule; use crate::vm::{CallFrame, FrameArgs, FrameContinuation, InlineSyncState, TableGen, Vm}; use crate::{vm_debug, vm_info, vm_trace, vm_warn}; -use oxide_types::object::JsObject; +use oxide_types::object::{Cell, JsObject}; use oxide_types::value::JsValue; /// 按 `flat_id` 下标收集整棵子模块树为平表,供 `run()` 装载。 @@ -101,8 +101,8 @@ 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, active_upvalues, copy) => { + $recv.active_upvalues }; ($recv:ident, $window_regs:ident, generator_dispatch, flag_zero) => {{ let prev = $recv.generator_dispatch; @@ -211,8 +211,8 @@ 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, active_upvalues, copy) => { + $recv.active_upvalues = $saved.active_upvalues }; ($recv:ident, $saved:ident, generator_dispatch, flag_zero) => { $recv.generator_dispatch = $saved.generator_dispatch @@ -277,7 +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 + (active_upvalues, copy), // M (generator_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 (async_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 (construct_dispatch, flag_zero), // M 调度标志:快照属主值,嵌套期间清零 @@ -477,6 +477,8 @@ impl Vm { self.root_reg_limit = self.active_reg_limit; self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); self.inline_callee = Some(callee); + // 内联无帧:活动镜像直接取被调对象表(非闭包为 null 空切片)。 + self.active_upvalues = callee_obj.upvalues_slice() as *const [*mut Cell]; // inline 无 CallFrame:目标函数严格模式单独记录,内联执行期间的写路径 // strict/sloppy 判定据此分派(嵌套内联时外层值由 InlineSyncState 恢复)。 self.inline_strict = sub.is_strict; @@ -550,8 +552,12 @@ impl Vm { callee_mut.set_prop_at(slot, JsValue::undefined()); } } - // 弹帧后 callee 换回调用方:失效 upvalue 切片缓存。 - self.upvalue_cache = None; + // 弹帧后活动镜像切回新栈顶的表(帧空即顶层空切片)。 + self.active_upvalues = self + .frames + .last() + .map(|f| f.upvalues) + .unwrap_or(std::ptr::slice_from_raw_parts(std::ptr::null(), 0)); 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 71a8df5e..650cdd74 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -101,7 +101,7 @@ impl Vm { inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, - upvalue_cache: None, + active_upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), inline_strict: false, inline_frames_base: 0, top_level_strict: false, @@ -239,7 +239,7 @@ impl Vm { inline_args_count: 0, accessor_frame_target_reg: None, inline_callee: None, - upvalue_cache: None, + active_upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), inline_strict: false, inline_frames_base: 0, top_level_strict: false, @@ -556,9 +556,9 @@ impl Vm { self.reentry_hops = 0; // inline 窗口缓冲池内容为已废弃快照,跨 run/reset 不保留。 self.inline_reg_pool = None; - // upvalue 切片缓存是执行期状态:键为 callee 值,跨 run/reset 后缓存的 - // 切片指针失效(callee 对象可能已回收)。 - self.upvalue_cache = None; + // upvalue 活动镜像是执行期状态:跨 run/reset 后指针失效(callee 对象 + // 可能已回收),清零为 null 空切片。 + self.active_upvalues = std::ptr::slice_from_raw_parts(std::ptr::null(), 0); // 微任务队列是执行期状态:跨 run 不保留。 self.job_queue.clear(); // Atomics waiter 表同属执行期状态:未结算 promise 清位后无强根。 diff --git a/crates/oxide_vm/src/vm_support/tests.rs b/crates/oxide_vm/src/vm_support/tests.rs index 05bf6f5f..cc4b7df9 100644 --- a/crates/oxide_vm/src/vm_support/tests.rs +++ b/crates/oxide_vm/src/vm_support/tests.rs @@ -844,6 +844,7 @@ fn reset_clears_runtime_state_like_rerun() { is_derived_constructor: false, super_called: false, continuation: crate::vm::FrameContinuation::None, + upvalues: std::ptr::slice_from_raw_parts(std::ptr::null(), 0), }); vm.save_stack.push(JsValue::undefined()); vm.iters diff --git a/crates/oxide_vm/tests/generator_gc_tests.rs b/crates/oxide_vm/tests/generator_gc_tests.rs index 401a0bfd..6af204bd 100644 --- a/crates/oxide_vm/tests/generator_gc_tests.rs +++ b/crates/oxide_vm/tests/generator_gc_tests.rs @@ -40,3 +40,16 @@ fn generator_promoted_to_global_survives_full_reset_cleanly() { .expect("run3"); assert_eq!(format!("{ok}"), "9"); } + +/// 闭包跨 yield 读 upvalue:生成器挂起再恢复时活动镜像必须还原为生成器自己的 +/// 表(恢复置位漏掉时,恢复体读顶层空表,upvalue 读回退惰性路径得 undefined)。 +#[test] +fn generator_reads_upvalue_across_yield() { + let mut vm = Vm::new(); + let result = vm + .run(&Arc::new(compile( + "function make() { var x = 1; function* g() { yield x; yield x; } var it = g(); it.next(); x = 2; return it.next().value; } make()", + ))) + .expect("run"); + assert_eq!(format!("{result}"), "2"); +}